diff --git a/CHANGELOG.md b/CHANGELOG.md index b34431f83c1..4132382cc32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes for each version of this project will be documented in this file. + ## 20.1.0 ### New Features - `IgxCarousel` @@ -10,6 +11,15 @@ All notable changes for each version of this project will be documented in this this.carousel.select(2, Direction.NEXT); ``` +- `IgxDateRangePicker` + - Added new properties: + - `usePredefinedRanges` - Whether to render built-in predefined ranges + - `customRanges` - Allows the user to provide custom ranges rendered as chips + - `resourceStrings` - Allows the user to provide set of resource strings + +- `IgxPredefinedRangesAreaComponent` + - Added new component for rendering the predefined or custom ranges inside the calendar of the `IgxDateRangePicker` + ### General - `IgxDropDown` now exposes a `role` input property, allowing users to customize the role attribute based on the use case. The default is `listbox`. diff --git a/projects/igniteui-angular-i18n/src/i18n/BG/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/BG/date-range-picker-resources.ts index 654c8557353..41d7ccbd62f 100644 --- a/projects/igniteui-angular-i18n/src/i18n/BG/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/BG/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsBG = { igx_date_range_picker_date_separator: 'до', - igx_date_range_picker_done_button: 'Завърши' + igx_date_range_picker_done_button: 'Завърши', + igx_date_range_picker_last7Days: 'Последните 7 дни', + igx_date_range_picker_currentMonth: 'Текущ месец', + igx_date_range_picker_last30Days: 'Последните 30 дни', + igx_date_range_picker_yearToDate: 'От началото на годината' } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/CS/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/CS/date-range-picker-resources.ts index 8095dde0e71..24cfad460b9 100644 --- a/projects/igniteui-angular-i18n/src/i18n/CS/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/CS/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsCS = { igx_date_range_picker_date_separator: 'na', - igx_date_range_picker_done_button: 'Hotovo' + igx_date_range_picker_done_button: 'Hotovo', + igx_date_range_picker_last7Days: 'Posledních 7 dní', + igx_date_range_picker_currentMonth: 'Tento měsíc', + igx_date_range_picker_last30Days: 'Posledních 30 dní', + igx_date_range_picker_yearToDate: 'Od začátku roku', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/DA/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/DA/date-range-picker-resources.ts index 6d9ee56c3bb..3c540bf8c68 100644 --- a/projects/igniteui-angular-i18n/src/i18n/DA/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/DA/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsDA = { igx_date_range_picker_date_separator: 'till', - igx_date_range_picker_done_button: 'Færdigt' + igx_date_range_picker_done_button: 'Færdigt', + igx_date_range_picker_last7Days: 'Sidste 7 dage', + igx_date_range_picker_currentMonth: 'Denne måned', + igx_date_range_picker_last30Days: 'Sidste 30 dage', + igx_date_range_picker_yearToDate: 'Året til dato', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/DE/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/DE/date-range-picker-resources.ts index f9483f2d0c9..0a5610d79c3 100644 --- a/projects/igniteui-angular-i18n/src/i18n/DE/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/DE/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsDE = { igx_date_range_picker_date_separator: 'bis', - igx_date_range_picker_done_button: 'Fertig' + igx_date_range_picker_done_button: 'Fertig', + igx_date_range_picker_last7Days: 'Letzte 7 Tage', + igx_date_range_picker_currentMonth: 'Aktueller Monat', + igx_date_range_picker_last30Days: 'Letzte 30 Tage', + igx_date_range_picker_yearToDate: 'Jahr bis heute', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/ES/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/ES/date-range-picker-resources.ts index a7367bbb588..bc4a68eb5b7 100644 --- a/projects/igniteui-angular-i18n/src/i18n/ES/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/ES/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsES = { igx_date_range_picker_date_separator: 'a', - igx_date_range_picker_done_button: 'Listo' + igx_date_range_picker_done_button: 'Listo', + igx_date_range_picker_last7Days: 'Últimos 7 días', + igx_date_range_picker_currentMonth: 'Mes actual', + igx_date_range_picker_last30Days: 'Últimos 30 días', + igx_date_range_picker_yearToDate: 'Año hasta la fecha', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/FR/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/FR/date-range-picker-resources.ts index 4a9dc66a55e..51e71ffbbd5 100644 --- a/projects/igniteui-angular-i18n/src/i18n/FR/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/FR/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsFR = { igx_date_range_picker_date_separator: 'à', - igx_date_range_picker_done_button: 'Terminée' + igx_date_range_picker_done_button: 'Terminée', + igx_date_range_picker_last7Days: '7 derniers jours', + igx_date_range_picker_currentMonth: 'Mois en cours', + igx_date_range_picker_last30Days: '30 derniers jours', + igx_date_range_picker_yearToDate: 'Année à ce jour', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/HU/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/HU/date-range-picker-resources.ts index e71b93c5a09..2a79fc5fc38 100644 --- a/projects/igniteui-angular-i18n/src/i18n/HU/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/HU/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsHU = { igx_date_range_picker_date_separator: '-', - igx_date_range_picker_done_button: 'Kész' + igx_date_range_picker_done_button: 'Kész', + igx_date_range_picker_last7Days: 'Az elmúlt 7 nap', + igx_date_range_picker_currentMonth: 'Aktuális hónap', + igx_date_range_picker_last30Days: 'Az elmúlt 30 nap', + igx_date_range_picker_yearToDate: 'Év elejétől napjainkig', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/IT/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/IT/date-range-picker-resources.ts index a67887a21da..e506a3502d3 100644 --- a/projects/igniteui-angular-i18n/src/i18n/IT/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/IT/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsIT = { igx_date_range_picker_date_separator: 'a', - igx_date_range_picker_done_button: 'Fine' + igx_date_range_picker_done_button: 'Fine', + igx_date_range_picker_last7Days: 'Ultimi 7 giorni', + igx_date_range_picker_currentMonth: 'Mese corrente', + igx_date_range_picker_last30Days: 'Ultimi 30 giorni', + igx_date_range_picker_yearToDate: 'Anno fino ad oggi', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/JA/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/JA/date-range-picker-resources.ts index b2af1024ca0..0e4f59086fc 100644 --- a/projects/igniteui-angular-i18n/src/i18n/JA/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/JA/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsJA = { igx_date_range_picker_date_separator: '~', - igx_date_range_picker_done_button: '完了' + igx_date_range_picker_done_button: '完了', + igx_date_range_picker_last7Days: '過去7日間', + igx_date_range_picker_currentMonth: '今月', + igx_date_range_picker_last30Days: '過去30日間', + igx_date_range_picker_yearToDate: '年初来', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/KO/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/KO/date-range-picker-resources.ts index def99afdc7f..0d7deb92db8 100644 --- a/projects/igniteui-angular-i18n/src/i18n/KO/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/KO/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsKO = { igx_date_range_picker_date_separator: '에', - igx_date_range_picker_done_button: '완료' + igx_date_range_picker_done_button: '완료', + igx_date_range_picker_last7Days: '지난 7일', + igx_date_range_picker_currentMonth: '이번 달', + igx_date_range_picker_last30Days: '지난 30일', + igx_date_range_picker_yearToDate: '올해 초부터 현재까지', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/NB/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/NB/date-range-picker-resources.ts index e36e6959f84..6841165d616 100644 --- a/projects/igniteui-angular-i18n/src/i18n/NB/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/NB/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsNB = { igx_date_range_picker_date_separator: 'til', - igx_date_range_picker_done_button: 'Ferdig' + igx_date_range_picker_done_button: 'Ferdig', + igx_date_range_picker_last7Days: 'Siste 7 dager', + igx_date_range_picker_currentMonth: 'Denne måneden', + igx_date_range_picker_last30Days: 'Siste 30 dager', + igx_date_range_picker_yearToDate: 'Året til dato', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/NL/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/NL/date-range-picker-resources.ts index 31ada214108..1c76dff2051 100644 --- a/projects/igniteui-angular-i18n/src/i18n/NL/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/NL/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsNL = { igx_date_range_picker_date_separator: 'tot', - igx_date_range_picker_done_button: 'Gereed' + igx_date_range_picker_done_button: 'Gereed', + igx_date_range_picker_last7Days: 'Laatste 7 dagen', + igx_date_range_picker_currentMonth: 'Huidige maand', + igx_date_range_picker_last30Days: 'Laatste 30 dagen', + igx_date_range_picker_yearToDate: 'Jaar tot datum', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/PL/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/PL/date-range-picker-resources.ts index 41c9240ea27..60f63d1ad84 100644 --- a/projects/igniteui-angular-i18n/src/i18n/PL/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/PL/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsPL = { igx_date_range_picker_date_separator: 'do', - igx_date_range_picker_done_button: 'Gotowe' + igx_date_range_picker_done_button: 'Gotowe', + igx_date_range_picker_last7Days: 'Ostatnie 7 dni', + igx_date_range_picker_currentMonth: 'Bieżący miesiąc', + igx_date_range_picker_last30Days: 'Ostatnie 30 dni', + igx_date_range_picker_yearToDate: 'Od początku roku', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/PT/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/PT/date-range-picker-resources.ts index 6fdc9d0a1aa..527ec0b093f 100644 --- a/projects/igniteui-angular-i18n/src/i18n/PT/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/PT/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsPT = { igx_date_range_picker_date_separator: 'para', - igx_date_range_picker_done_button: 'Concluído' + igx_date_range_picker_done_button: 'Concluído', + igx_date_range_picker_last7Days: 'Últimos 7 dias', + igx_date_range_picker_currentMonth: 'Mês atual', + igx_date_range_picker_last30Days: 'Últimos 30 dias', + igx_date_range_picker_yearToDate: 'Ano até hoje', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/RO/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/RO/date-range-picker-resources.ts index a09900d3814..cf177f1cb9b 100644 --- a/projects/igniteui-angular-i18n/src/i18n/RO/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/RO/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsRO = { igx_date_range_picker_date_separator: 'la', - igx_date_range_picker_done_button: 'Gata' + igx_date_range_picker_done_button: 'Gata', + igx_date_range_picker_last7Days: 'Ultimele 7 zile', + igx_date_range_picker_currentMonth: 'Luna curentă', + igx_date_range_picker_last30Days: 'Ultimele 30 de zile', + igx_date_range_picker_yearToDate: 'De la începutul anului', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/SV/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/SV/date-range-picker-resources.ts index d77a61d3077..af4255aee17 100644 --- a/projects/igniteui-angular-i18n/src/i18n/SV/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/SV/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsSV = { igx_date_range_picker_date_separator: 'till', - igx_date_range_picker_done_button: 'Färdig' + igx_date_range_picker_done_button: 'Färdig', + igx_date_range_picker_last7Days: 'Senaste 7 dagarna', + igx_date_range_picker_currentMonth: 'Aktuell månad', + igx_date_range_picker_last30Days: 'Senaste 30 dagarna', + igx_date_range_picker_yearToDate: 'Året hittills', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/TR/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/TR/date-range-picker-resources.ts index 1615b8d100c..3da509bb9d4 100644 --- a/projects/igniteui-angular-i18n/src/i18n/TR/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/TR/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsTR = { igx_date_range_picker_date_separator: '-', - igx_date_range_picker_done_button: 'Bitti' + igx_date_range_picker_done_button: 'Bitti', + igx_date_range_picker_last7Days: 'Son 7 gün', + igx_date_range_picker_currentMonth: 'Geçerli ay', + igx_date_range_picker_last30Days: 'Son 30 gün', + igx_date_range_picker_yearToDate: 'Yılbaşı itibarıyla', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/ZH-HANS/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/ZH-HANS/date-range-picker-resources.ts index 8a5cf97d998..1eb2f04a5b6 100644 --- a/projects/igniteui-angular-i18n/src/i18n/ZH-HANS/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/ZH-HANS/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsZHHANS = { igx_date_range_picker_date_separator: '至', - igx_date_range_picker_done_button: '完成' + igx_date_range_picker_done_button: '完成', + igx_date_range_picker_last7Days: '最近7天', + igx_date_range_picker_currentMonth: '本月', + igx_date_range_picker_last30Days: '最近30天', + igx_date_range_picker_yearToDate: '年初至今', } satisfies MakeRequired; diff --git a/projects/igniteui-angular-i18n/src/i18n/ZH-HANT/date-range-picker-resources.ts b/projects/igniteui-angular-i18n/src/i18n/ZH-HANT/date-range-picker-resources.ts index 1095f5a3e5e..dd056cea8e6 100644 --- a/projects/igniteui-angular-i18n/src/i18n/ZH-HANT/date-range-picker-resources.ts +++ b/projects/igniteui-angular-i18n/src/i18n/ZH-HANT/date-range-picker-resources.ts @@ -6,5 +6,9 @@ import { IDateRangePickerResourceStrings } from 'igniteui-angular'; */ export const DateRangePickerResourceStringsZHHANT = { igx_date_range_picker_date_separator: '到', - igx_date_range_picker_done_button: '完成' + igx_date_range_picker_done_button: '完成', + igx_date_range_picker_last7Days: '最近7天', + igx_date_range_picker_currentMonth: '本月', + igx_date_range_picker_last30Days: '最近30天', + igx_date_range_picker_yearToDate: '年初至今', } satisfies MakeRequired; diff --git a/projects/igniteui-angular/src/lib/core/i18n/date-range-picker-resources.ts b/projects/igniteui-angular/src/lib/core/i18n/date-range-picker-resources.ts index 3b0d2729e02..8863ef87e3d 100644 --- a/projects/igniteui-angular/src/lib/core/i18n/date-range-picker-resources.ts +++ b/projects/igniteui-angular/src/lib/core/i18n/date-range-picker-resources.ts @@ -1,9 +1,18 @@ export interface IDateRangePickerResourceStrings { igx_date_range_picker_date_separator?: string; igx_date_range_picker_done_button?: string; + igx_date_range_picker_last7Days?: string; + igx_date_range_picker_currentMonth?: string; + igx_date_range_picker_last30Days?: string; + igx_date_range_picker_yearToDate?: string; } export const DateRangePickerResourceStringsEN: IDateRangePickerResourceStrings = { igx_date_range_picker_date_separator: 'to', - igx_date_range_picker_done_button: 'Done' + igx_date_range_picker_done_button: 'Done', + igx_date_range_picker_last7Days: 'Last 7 Days', + igx_date_range_picker_currentMonth: 'Current Month', + igx_date_range_picker_last30Days: 'Last 30 Days', + igx_date_range_picker_yearToDate: 'Year to Date', + }; diff --git a/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.html b/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.html index 0d54b8105b3..0372461423a 100644 --- a/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.html +++ b/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.html @@ -28,6 +28,15 @@ + + @if( usePredefinedRanges || (customRanges?.length || 0) > 0 ){ + + + } @if (pickerActions?.template || (closeButtonLabel || todayButtonLabel)) { } diff --git a/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.ts b/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.ts index 510be7214f1..24bbc07e5b7 100644 --- a/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.ts +++ b/projects/igniteui-angular/src/lib/date-common/calendar-container/calendar-container.component.ts @@ -13,6 +13,9 @@ import { IgxRippleDirective } from '../../directives/ripple/ripple.directive'; import { IgxPickerActionsDirective } from '../picker-icons.common'; import { IgxCalendarComponent } from '../../calendar/calendar.component'; import { IgxDividerDirective } from "../../directives/divider/divider.directive"; +import { CustomDateRange, DateRange } from '../../date-range-picker/date-range-picker-inputs.common'; +import { IDateRangePickerResourceStrings } from '../../core/i18n/date-range-picker-resources'; +import { IgxPredefinedRangesAreaComponent } from '../../date-range-picker/predefined-ranges/predefined-ranges-area.component'; /** @hidden */ @Component({ @@ -25,6 +28,7 @@ import { IgxDividerDirective } from "../../directives/divider/divider.directive" IgxCalendarComponent, NgTemplateOutlet, IgxDividerDirective, + IgxPredefinedRangesAreaComponent ] }) export class IgxCalendarContainerComponent { @@ -37,6 +41,10 @@ export class IgxCalendarContainerComponent { @Output() public todaySelection = new EventEmitter(); + @Output() + public rangeSelected = new EventEmitter(); + + @HostBinding('class.igx-date-picker') public styleClass = 'igx-date-picker'; @@ -45,6 +53,9 @@ export class IgxCalendarContainerComponent { return this.mode === PickerInteractionMode.DropDown; } + public usePredefinedRanges = false; + public customRanges: CustomDateRange[] = []; + public resourceStrings!: IDateRangePickerResourceStrings; public vertical = false; public closeButtonLabel: string; public todayButtonLabel: string; diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts index ea426fde4b9..9eb9c3b9b42 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker-inputs.common.ts @@ -17,6 +17,11 @@ export interface DateRange { start: Date | string; end: Date | string; } +/** Represents a range between two dates and a label used for predefined and custom date ranges. */ +export interface CustomDateRange { + label: string; + dateRange: DateRange; +} /** @hidden @internal */ @Pipe({ diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts index 01d03dc791d..fd5a04e4153 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.spec.ts @@ -9,7 +9,7 @@ import { ControlsFunction } from '../test-utils/controls-functions.spec'; import { UIInteractions } from '../test-utils/ui-interactions.spec'; import { HelperTestFunctions } from '../test-utils/calendar-helper-utils'; import { CancelableEventArgs } from '../core/utils'; -import { DateRange, IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent } from './date-range-picker-inputs.common'; +import { CustomDateRange, DateRange, IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent } from './date-range-picker-inputs.common'; import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/public_api'; import { DateRangeType } from '../core/dates'; import { IgxDateRangePickerComponent, IgxDateRangeEndComponent } from './public_api'; @@ -25,6 +25,7 @@ import { IgxIconComponent } from '../icon/icon.component'; import { registerLocaleData } from "@angular/common"; import localeJa from "@angular/common/locales/ja"; import localeBg from "@angular/common/locales/bg"; +import { CalendarDay } from '../calendar/common/model'; // The number of milliseconds in one day const DEBOUNCE_TIME = 16; @@ -1382,6 +1383,173 @@ describe('IgxDateRangePicker', () => { expect((fixture.componentInstance.dateRange.value.end as Date).getTime()).toEqual(range.end.getTime()); })); }); + + describe('Predefined ranges', ()=> { + const predefinedRangesLength = 4; + const today = CalendarDay.today.native; + const last7DaysEnd = CalendarDay.today.add('day', -7).native; + const last30DaysEnd = CalendarDay.today.add('day', -29).native; + const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); + const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0); + const startOfYear = new Date(today.getFullYear(), 0, 1); + const previousThreeDaysStart = CalendarDay.today.add('day', -3).native; + const nextThreeDaysEnd = CalendarDay.today.add('day', 3).native; + + const customRanges: CustomDateRange[] = [ + { + label: 'Previous Three Days', + dateRange: { + start: previousThreeDaysStart, + end: today, + }, + }, + { + label: 'Next Three Days', + dateRange: { + start: today, + end: nextThreeDaysEnd, + }, + }, + ]; + + const dateRanges: DateRange[] = [ + {start: last7DaysEnd, end: today}, + {start: startOfMonth, end: endOfMonth}, + {start: last30DaysEnd, end: today}, + {start: startOfYear, end: today}, + {start: previousThreeDaysStart, end: today}, + {start: today, end: nextThreeDaysEnd}, + ]; + + beforeEach(() => { + fixture = TestBed.createComponent(DateRangeTwoInputsTestComponent); + fixture.detectChanges(); + dateRange = fixture.componentInstance.dateRange; + + + }); + + it('should not render predefined area when usePredefinedRanges is false and no custom ranges are provided', () => { + dateRange.open(); + fixture.detectChanges(); + + const predefinedArea = document.querySelector('igx-predefined-ranges-area'); + const chips = document.querySelectorAll('igx-chip'); + + console.log(predefinedArea); + + expect(predefinedArea).toBeNull(); + expect(chips.length).toEqual(0); + + }); + + it('should render predefined area when usePredefinedRanges is true and no custom ranges are provided', () => { + dateRange.usePredefinedRanges = true; + fixture.detectChanges(); + + dateRange.open(); + fixture.detectChanges(); + + const predefinedArea = document.querySelector('igx-predefined-ranges-area'); + const chips = document.querySelectorAll('igx-chip'); + + expect(predefinedArea).toBeDefined(); + expect(chips.length).toEqual(predefinedRangesLength); + }); + + it('should render predefined area when only custom ranges are provided', () => { + dateRange.customRanges = customRanges; + fixture.detectChanges(); + + dateRange.open(); + fixture.detectChanges(); + + const predefinedArea = document.querySelector('igx-predefined-ranges-area'); + const chips = document.querySelectorAll('igx-chip'); + + expect(predefinedArea).toBeDefined(); + expect(chips.length).toEqual(customRanges.length); + }); + + it('should render predefined area when usePredefinedRanges is true and custom ranges are provided', () => { + dateRange.usePredefinedRanges = true; + dateRange.customRanges = customRanges; + fixture.detectChanges(); + + dateRange.open(); + fixture.detectChanges(); + + const predefinedArea = document.querySelector('igx-predefined-ranges-area'); + const chips = document.querySelectorAll('igx-chip'); + + expect(predefinedArea).toBeDefined(); + expect(chips.length).toEqual(predefinedRangesLength + customRanges.length); + }); + + it('should render predefined area and emit selection event when the user performs selection via chips', () => { + const selectionSpy = spyOn(dateRange as any, 'handleSelection').and.callThrough(); + + dateRange.usePredefinedRanges = true; + dateRange.customRanges = customRanges; + fixture.detectChanges(); + + dateRange.open(); + fixture.detectChanges(); + + const predefinedArea = document.querySelector('igx-predefined-ranges-area'); + const chips = document.querySelectorAll('igx-chip'); + + expect(predefinedArea).toBeDefined(); + expect(chips.length).toEqual(predefinedRangesLength + customRanges.length); + + + chips.forEach((chip, i) => { + chip.dispatchEvent(UIInteractions.getMouseEvent('click')); + fixture.detectChanges(); + expect(dateRange.value).toEqual(dateRanges[i]); + + }); + + expect(selectionSpy).toHaveBeenCalledTimes(predefinedRangesLength + customRanges.length); + }); + + it('should use provided resourceStrings for labels when available', () => { + const strings: any = { + last7Days: 'Last 7 - localized', + currentMonth: 'Current Month - localized', + yearToDate: 'YTD - localized', + igx_date_range_picker_last7Days: 'Last 7 - localized', + igx_date_range_picker_currentMonth: 'Current Month - localized', + igx_date_range_picker_yearToDate: 'YTD - localized', + // last30Days omitted to test fallback + }; + + dateRange.resourceStrings = strings; + dateRange.usePredefinedRanges = true; + dateRange.customRanges = []; + fixture.detectChanges(); + + dateRange.open(); + fixture.detectChanges(); + + const predefinedArea = document.querySelector('igx-predefined-ranges-area'); + const chips = document.querySelectorAll('igx-chip'); + + expect(predefinedArea).toBeDefined(); + expect(chips.length).toEqual(predefinedRangesLength); + const labels: string[] = []; + + chips.forEach((chip) => { + labels.push(chip.textContent.trim()); + }); + + expect(labels).toContain('Last 7 - localized'); + expect(labels).toContain('Current Month - localized'); + expect(labels).toContain('YTD - localized'); + + expect(labels).toContain('Last 30 Days'); + }); + }); }); describe('Rendering', () => { @@ -1606,7 +1774,10 @@ export class DateRangeDefaultComponent extends DateRangeTestComponent { [disabled]="disabled" [(ngModel)]="range" [inputFormat]="inputFormat" - [displayFormat]="displayFormat" required> + [displayFormat]="displayFormat" + required + [usePredefinedRanges]="usePredefinedRanges" + [customRanges]="customRanges"> calendar_view_day @@ -1637,6 +1808,8 @@ export class DateRangeTwoInputsTestComponent extends DateRangeTestComponent { public inputFormat: string; public displayFormat: string; public override disabled = false; + public usePredefinedRanges = false; + public customRanges: CustomDateRange[] = []; } @Component({ selector: 'igx-date-range-two-inputs-ng-model', diff --git a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts index e6b5edca0ba..b0403c43493 100644 --- a/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts +++ b/projects/igniteui-angular/src/lib/date-range-picker/date-range-picker.component.ts @@ -30,7 +30,7 @@ import { AutoPositionStrategy, IgxOverlayService, OverlayCancelableEventArgs, OverlayEventArgs, OverlaySettings, PositionSettings } from '../services/public_api'; -import { DateRange, IgxDateRangeEndComponent, IgxDateRangeInputsBaseComponent, IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent, DateRangePickerFormatPipe } from './date-range-picker-inputs.common'; +import { DateRange, IgxDateRangeEndComponent, IgxDateRangeInputsBaseComponent, IgxDateRangeSeparatorDirective, IgxDateRangeStartComponent, DateRangePickerFormatPipe, CustomDateRange } from './date-range-picker-inputs.common'; import { IgxPrefixDirective } from '../directives/prefix/prefix.directive'; import { IgxIconComponent } from '../icon/icon.component'; import { getCurrentResourceStrings } from '../core/i18n/resources'; @@ -283,6 +283,27 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective * * ``` */ + + /** + * Whether to render built-in predefined ranges. + * + * @example + * ```html + * + * `` + * */ + @Input() public usePredefinedRanges = false; + + /** + * Custom ranges rendered as chips. + * + * @example + * ```html + * + * `` + */ + @Input() public customRanges: CustomDateRange[] = []; + @Output() public valueChange = new EventEmitter(); @@ -1092,6 +1113,20 @@ export class IgxDateRangePickerComponent extends PickerBaseDirective componentInstance.mode = this.mode; componentInstance.closeButtonLabel = !this.isDropdown ? this.doneButtonText : null; componentInstance.pickerActions = this.pickerActions; + componentInstance.usePredefinedRanges = this.usePredefinedRanges; + componentInstance.customRanges = this.customRanges; + componentInstance.resourceStrings = this.resourceStrings; componentInstance.calendarClose.pipe(takeUntil(this._destroy$)).subscribe(() => this.close()); + componentInstance.rangeSelected + .pipe(takeUntil(this._destroy$)) + .subscribe((r: DateRange) => { + if (r?.start && r?.end) { + this.select(new Date(r.start), new Date(r.end)); + } + + if (this.isDropdown) { + this.close(); + } + }); } } diff --git a/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area-component.html b/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area-component.html new file mode 100644 index 00000000000..4462f89684d --- /dev/null +++ b/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area-component.html @@ -0,0 +1,7 @@ +
+ @for (r of ranges; track r.label) { + + {{ r.label }} + + } +
diff --git a/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area-component.spec.ts b/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area-component.spec.ts new file mode 100644 index 00000000000..509d86b9ea5 --- /dev/null +++ b/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area-component.spec.ts @@ -0,0 +1,158 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { IgxPredefinedRangesAreaComponent } from './predefined-ranges-area.component'; +import { CalendarDay } from '../../calendar/common/model'; +import { CustomDateRange } from '../date-range-picker-inputs.common'; +import { IDateRangePickerResourceStrings } from '../../core/i18n/date-range-picker-resources'; +import { IgxChipComponent } from '../../chips/chip.component'; +import { IgxChipsModule } from 'igniteui-angular'; +import { Component, ViewChild } from '@angular/core'; + +describe('IgxPredefinedRangesAreaComponent', () => { + let fixture: ComponentFixture; + let component: PredefinedRangesDefaultComponent; + let predefinedRanges:IgxPredefinedRangesAreaComponent; + + const customRanges: CustomDateRange[] = [ + { + label: 'Previous Three Months', + dateRange: { + start: CalendarDay.today.add('month', -3).set({ date: 1 }).native, + end: CalendarDay.today.set({ date: 1 }).add('day', -1).native, + }, + }, + { + label: 'Next Three Months', + dateRange: { + start: CalendarDay.today.add('month', 1).set({ date: 1 }).native, + end: CalendarDay.today.add('month', 4).add('day', -1).native, + }, + }, + ]; + + function getChips() { + return fixture.debugElement.queryAll(By.css('igx-chip')); + } + + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [IgxPredefinedRangesAreaComponent, IgxChipComponent, IgxChipsModule, PredefinedRangesDefaultComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(PredefinedRangesDefaultComponent); + component = fixture.componentInstance; + predefinedRanges = component.predefinedRanges; + fixture.detectChanges(); + }); + + it('should render no chips by default', () => { + expect(getChips().length).toBe(0); + }); + + it('should render predefined ranges when usePredefinedRanges = true', () => { + component.usePredefinedRanges = true; + fixture.detectChanges(); + + const chips = getChips(); + expect(chips.length).toBe(predefinedRanges.ranges.length); + chips.forEach((de, i) => { + const text = (de.nativeElement as HTMLElement).innerText.trim(); + expect(text).toBe(predefinedRanges.ranges[i].label); + }); + }); + + it('should render predefined + custom ranges together', () => { + component.usePredefinedRanges = true; + component.customRanges = customRanges; + fixture.detectChanges(); + + const chips = getChips(); + const ranges = predefinedRanges.ranges; + + expect(chips.length).toBe(ranges.length); + chips.forEach((de, i) => { + const text = (de.nativeElement as HTMLElement).innerText.trim(); + expect(text).toBe(ranges[i].label); + }); + }); + + it('should render only custom ranges when usePredefinedRanges = false', () => { + component.usePredefinedRanges = false; + component.customRanges = customRanges; + fixture.detectChanges(); + + const chips = getChips(); + const ranges = predefinedRanges.ranges; + + expect(chips.length).toBe(ranges.length); + chips.forEach((de, i) => { + const text = (de.nativeElement as HTMLElement).innerText.trim(); + expect(text).toBe(ranges[i].label); + }); + }); + + it('should emit selected range on chip click', () => { + component.usePredefinedRanges = true; + component.customRanges = customRanges; + fixture.detectChanges(); + + const chips = getChips(); + const ranges = predefinedRanges.ranges; + expect(chips.length).toBe(ranges.length); + + const emitSpy = spyOn(predefinedRanges.rangeSelect, 'emit'); + + chips.forEach((de, i) => { + (de.nativeElement as HTMLElement).click(); + fixture.detectChanges(); + expect(emitSpy).toHaveBeenCalledWith(ranges[i].dateRange); + }); + }); + + it('should use provided resourceStrings for labels when available', () => { + const strings: any = { + last7Days: 'Last 7 - localized', + currentMonth: 'Current Month - localized', + yearToDate: 'YTD - localized', + igx_date_range_picker_last7Days: 'Last 7 - localized', + igx_date_range_picker_currentMonth: 'Current Month - localized', + igx_date_range_picker_yearToDate: 'YTD - localized', + // last30Days omitted to test fallback + }; + + predefinedRanges.resourceStrings = strings; + component.usePredefinedRanges = true; + component.customRanges = []; + fixture.detectChanges(); + + const chips = getChips(); + const labels = chips.map(de => (de.nativeElement as HTMLElement).innerText.trim()); + + expect(labels).toContain('Last 7 - localized'); + expect(labels).toContain('Current Month - localized'); + expect(labels).toContain('YTD - localized'); + + expect(labels).toContain('Last 30 Days'); + }); +}); + +@Component({ + standalone: true, + template: ` + + + `, + imports: [IgxPredefinedRangesAreaComponent] +}) +class PredefinedRangesDefaultComponent { + public usePredefinedRanges = false; + public customRanges = []; + public resourceStrings?: IDateRangePickerResourceStrings; + + @ViewChild(IgxPredefinedRangesAreaComponent, { static: true }) + public predefinedRanges!: IgxPredefinedRangesAreaComponent; +} diff --git a/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area.component.ts b/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area.component.ts new file mode 100644 index 00000000000..090a3943fef --- /dev/null +++ b/projects/igniteui-angular/src/lib/date-range-picker/predefined-ranges/predefined-ranges-area.component.ts @@ -0,0 +1,73 @@ +import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IgxChipComponent } from '../../chips/chip.component'; +import { DateRangePickerResourceStringsEN, IDateRangePickerResourceStrings } from '../../core/i18n/date-range-picker-resources'; +import { DateRange, CustomDateRange} from '.././date-range-picker-inputs.common'; +import { CalendarDay } from '../../calendar/common/model'; + + +type PredefinedRangeKey = 'last7Days' | 'currentMonth' | 'last30Days' | 'yearToDate'; + +@Component({ + selector: 'igx-predefined-ranges-area', + standalone: true, + imports: [CommonModule, IgxChipComponent], + templateUrl: './predefined-ranges-area-component.html', + styles: [` + :host { display:block; } + .igx-predefined-ranges { + display:flex; flex-wrap:wrap; gap:.5rem; padding:.5rem .75rem; + } + `], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class IgxPredefinedRangesAreaComponent { + @Input() public usePredefinedRanges = false; + @Input() public customRanges: CustomDateRange[] = []; + @Input() public resourceStrings: IDateRangePickerResourceStrings = DateRangePickerResourceStringsEN as any; + + @Output() public rangeSelect = new EventEmitter(); + + public get ranges(): CustomDateRange[] { + const base = this.usePredefinedRanges ? this.getPredefinedRanges() : []; + return [...base, ...(this.customRanges ?? [])]; + } + + public trackByLabel = (i: number, r: CustomDateRange) => r.label; + + public onSelect(range: DateRange) { + this.rangeSelect.emit(range); + } + + private getLabel(rs: any, shortKey: string, prefixedKey: string, fallback: string): string { + return rs?.[shortKey] ?? rs?.[prefixedKey] ?? fallback; + } + + private getPredefinedRanges(): CustomDateRange[] { + const today = CalendarDay.today; + const rs: any = this.resourceStrings ?? {}; + + const labels = { + last7Days: this.getLabel(rs, 'last7Days', 'igx_date_range_picker_last7Days', 'Last 7 Days'), + currentMonth: this.getLabel(rs, 'currentMonth', 'igx_date_range_picker_currentMonth', 'Current Month'), + last30Days: this.getLabel(rs, 'last30Days', 'igx_date_range_picker_last30Days', 'Last 30 Days'), + yearToDate: this.getLabel(rs, 'yearToDate', 'igx_date_range_picker_yearToDate', 'Year to Date') + }; + + const startOfMonth = new Date(today.native.getFullYear(), today.native.getMonth(), 1); + const endOfMonth = new Date(today.native.getFullYear(), today.native.getMonth() + 1, 0); + const startOfYear = new Date(today.native.getFullYear(), 0, 1); + + const predefinedRanges: { key: PredefinedRangeKey; get: () => { start: Date; end: Date } }[] = [ + { key: 'last7Days', get: () => ({ start: today.add('day', -7).native, end: today.native }) }, + { key: 'currentMonth', get: () => ({ start: startOfMonth, end: endOfMonth }) }, + { key: 'last30Days', get: () => ({ start: today.add('day', -29).native, end: today.native }) }, + { key: 'yearToDate', get: () => ({ start: startOfYear, end: today.native }) }, + ]; + + return predefinedRanges.map(range => ({ + label: labels[range.key], + dateRange: range.get() + })); + } +} diff --git a/src/app/date-range/date-range.sample.html b/src/app/date-range/date-range.sample.html index fceb8e868b9..1a11cd4447d 100644 --- a/src/app/date-range/date-range.sample.html +++ b/src/app/date-range/date-range.sample.html @@ -3,14 +3,15 @@
Without forms
-

Default range picker

- + +

Default range picker with predefined and custom ranges

+

Drop down single input with custom suffix

diff --git a/src/app/date-range/date-range.sample.ts b/src/app/date-range/date-range.sample.ts index 52a26f19be6..75904f6be6a 100644 --- a/src/app/date-range/date-range.sample.ts +++ b/src/app/date-range/date-range.sample.ts @@ -1,7 +1,8 @@ import { Component, ViewChild } from '@angular/core'; import { JsonPipe } from '@angular/common'; import { UntypedFormGroup, UntypedFormBuilder, Validators, UntypedFormControl, ValidatorFn, AbstractControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { DateRange, IgxButtonDirective, IgxDateRangeEndComponent, IgxDateRangePickerComponent, IgxDateRangeStartComponent, IgxDateTimeEditorDirective, IgxIconComponent, IgxInputDirective, IgxLabelDirective, IgxPickerToggleComponent, IgxPrefixDirective, IgxRadioComponent, IgxRippleDirective, IgxSuffixDirective, IGX_INPUT_GROUP_TYPE, IChangeCheckboxEventArgs } from 'igniteui-angular'; +import { DateRange, IgxButtonDirective, IgxDateRangeEndComponent, IgxDateRangePickerComponent, IgxDateRangeStartComponent, IgxDateTimeEditorDirective, IgxIconComponent, IgxInputDirective, IgxLabelDirective, IgxPickerToggleComponent, IgxPrefixDirective, IgxRadioComponent, IgxRippleDirective, IgxSuffixDirective, IGX_INPUT_GROUP_TYPE, IChangeCheckboxEventArgs, CustomDateRange } from 'igniteui-angular'; +import { CalendarDay } from 'igniteui-angular/src/lib/calendar/common/model'; @Component({ @@ -31,6 +32,31 @@ export class DateRangeSampleComponent { public range6: DateRange = { start: this.range6Start, end: this.range6End }; public minDate: Date = new Date(); public maxDate: Date = new Date(new Date().setDate(new Date().getDate() + 25)); + public today = CalendarDay.today; + public previousThreeMonthsStart = new Date( + this.today.native.getFullYear(), + this.today.native.getMonth() - 3, + 1 + ); + public previousThreeMonthsEnd = new Date( + this.today.native.getFullYear(), + this.today.native.getMonth(), + 0 + ); + public nextThreeMonthsStart = new Date( + this.today.native.getFullYear(), + this.today.native.getMonth() + 1, + 1 + ); + public nextThreeMonthsEnd = new Date( + this.today.native.getFullYear(), + this.today.native.getMonth() + 4, + 0 + ); + public customRanges : CustomDateRange[] = [ + { label:"Next three months", dateRange: {start: this.nextThreeMonthsStart, end: this.nextThreeMonthsEnd} }, + { label:"Previous three months", dateRange: {start: this.previousThreeMonthsStart, end: this.previousThreeMonthsEnd} }, + ] public reactiveForm: UntypedFormGroup;