@i-cell/ids-angular 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/core/public-api.d.ts +1 -1
  2. package/core/utils/date.d.ts +24 -0
  3. package/core/utils/number.d.ts +4 -0
  4. package/datepicker/calendar/calendar-page.d.ts +26 -0
  5. package/datepicker/calendar/calendar.component.d.ts +40 -0
  6. package/datepicker/datepicker-defaults.d.ts +7 -0
  7. package/datepicker/datepicker-intl.d.ts +22 -0
  8. package/datepicker/datepicker-positions.d.ts +2 -0
  9. package/datepicker/datepicker.directive.d.ts +58 -0
  10. package/datepicker/day-selector/day-selector.component.d.ts +26 -0
  11. package/datepicker/index.d.ts +5 -0
  12. package/datepicker/month-selector/month-selector.component.d.ts +20 -0
  13. package/datepicker/public-api.d.ts +7 -0
  14. package/datepicker/tokens/date-formatter.d.ts +3 -0
  15. package/datepicker/tokens/date-parser.d.ts +3 -0
  16. package/datepicker/tokens/datepicker-view.d.ts +6 -0
  17. package/datepicker/trigger/datepicker-trigger.component.d.ts +12 -0
  18. package/datepicker/year-selector/year-selector.component.d.ts +20 -0
  19. package/fesm2022/i-cell-ids-angular-core.mjs +156 -13
  20. package/fesm2022/i-cell-ids-angular-core.mjs.map +1 -1
  21. package/fesm2022/i-cell-ids-angular-datepicker.mjs +860 -0
  22. package/fesm2022/i-cell-ids-angular-datepicker.mjs.map +1 -0
  23. package/fesm2022/i-cell-ids-angular-forms.mjs +79 -66
  24. package/fesm2022/i-cell-ids-angular-forms.mjs.map +1 -1
  25. package/fesm2022/i-cell-ids-angular-select.mjs +8 -63
  26. package/fesm2022/i-cell-ids-angular-select.mjs.map +1 -1
  27. package/forms/components/form-field/form-field-control.d.ts +41 -17
  28. package/forms/components/form-field/form-field.component.d.ts +2 -1
  29. package/forms/components/form-field/tokens/form-field-control.d.ts +1 -1
  30. package/forms/components/input/input.directive.d.ts +7 -36
  31. package/package.json +10 -6
  32. package/select/select.component.d.ts +8 -37
  33. package/core/utils/even-odd.d.ts +0 -2
@@ -0,0 +1,860 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Injectable, inject, input, Component, ViewEncapsulation, ChangeDetectionStrategy, ElementRef, Injector, ChangeDetectorRef, model, output, afterNextRender, Directive, computed, viewChild, linkedSignal, signal, ViewContainerRef, effect, booleanAttribute, forwardRef } from '@angular/core';
3
+ import { formatDate, parseDate, isValidDate, startOfMonth, addMonths, startOfWeek, endOfWeek, endOfMonth, getDatesBetween, addDays, compareDates, equalDates, addYears, clampDate, createNormalizedDate, positiveModulus, ComponentBaseWithDefaults, IdsSize, startOfYear, DirectiveBaseWithDefaults, getValidDateOrNull, deserializeDate } from '@i-cell/ids-angular/core';
4
+ import { Subject, startWith, map, Subscription, merge, filter } from 'rxjs';
5
+ import { IdsFormFieldComponent } from '@i-cell/ids-angular/forms';
6
+ import { IdsIconComponent } from '@i-cell/ids-angular/icon';
7
+ import { IdsIconButtonComponent } from '@i-cell/ids-angular/icon-button';
8
+ import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
9
+ import * as i1 from '@angular/cdk/a11y';
10
+ import { CdkTrapFocus } from '@angular/cdk/a11y';
11
+ import { IdsButtonComponent } from '@i-cell/ids-angular/button';
12
+ import { hasModifierKey } from '@angular/cdk/keycodes';
13
+ import { Overlay } from '@angular/cdk/overlay';
14
+ import { ComponentPortal } from '@angular/cdk/portal';
15
+ import { Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
16
+
17
+ // eslint-disable-next-line @typescript-eslint/naming-convention
18
+ const IdsDatepickerView = {
19
+ DAY: 'day',
20
+ MONTH: 'month',
21
+ YEAR: 'year',
22
+ };
23
+
24
+ const IDS_DATE_FORMATTER = new InjectionToken('idsDateFormatter', {
25
+ providedIn: 'root',
26
+ factory: () => formatDate,
27
+ });
28
+
29
+ const IDS_DATE_PARSER = new InjectionToken('idsDateParser', {
30
+ providedIn: 'root',
31
+ factory: () => parseDate,
32
+ });
33
+
34
+ const dayAriaLabelFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long', day: 'numeric' });
35
+ const monthLabelFormat = new Intl.DateTimeFormat('en-GB', { month: 'short' });
36
+ const monthAriaLabelFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long' });
37
+ const daySelectorHeaderLabelFormat = new Intl.DateTimeFormat('en-GB', { year: 'numeric', month: 'long' });
38
+ const weekdayLabelFormat = new Intl.DateTimeFormat('en-GB', { weekday: 'narrow' });
39
+ class IdsDatepickerIntl {
40
+ constructor() {
41
+ this.changes = new Subject();
42
+ this.triggerButtonLabel = 'Open calendar';
43
+ this.prevMonthLabel = 'Previous month';
44
+ this.nextMonthLabel = 'Next month';
45
+ this.prevYearLabel = 'Previous year';
46
+ this.nextYearLabel = 'Next year';
47
+ this.prevYearsPageLabel = 'Previous 24 years';
48
+ this.nextYearsPageLabel = 'Next 24 years';
49
+ this.switchToDaySelectorLabel = 'Choose date';
50
+ this.switchToYearSelectorLabel = 'Choose year and month';
51
+ }
52
+ getDayAriaLabel(date) {
53
+ return this._formatDate(date, dayAriaLabelFormat);
54
+ }
55
+ getMonthLabel(date) {
56
+ return this._formatDate(date, monthLabelFormat);
57
+ }
58
+ getMonthAriaLabel(date) {
59
+ return this._formatDate(date, monthAriaLabelFormat);
60
+ }
61
+ getDaySelectorHeaderLabel(date) {
62
+ return this._formatDate(date, daySelectorHeaderLabelFormat);
63
+ }
64
+ getWeekdayHeaderLabel(date) {
65
+ return this._formatDate(date, weekdayLabelFormat);
66
+ }
67
+ _formatDate(date, dateFormat) {
68
+ return isValidDate(date) ? dateFormat.format(date) : null;
69
+ }
70
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDatepickerIntl, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
71
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDatepickerIntl, providedIn: 'root' }); }
72
+ }
73
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDatepickerIntl, decorators: [{
74
+ type: Injectable,
75
+ args: [{ providedIn: 'root' }]
76
+ }] });
77
+
78
+ class IdsDatepickerTriggerComponent {
79
+ constructor() {
80
+ this._parent = inject(IdsFormFieldComponent);
81
+ this._intl = inject(IdsDatepickerIntl);
82
+ this.datepicker = input.required({ alias: 'for' });
83
+ this.ariaLabel = input(null, { alias: 'aria-label' });
84
+ }
85
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDatepickerTriggerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
86
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.1.2", type: IdsDatepickerTriggerComponent, isStandalone: true, selector: "ids-datepicker-trigger", inputs: { datepicker: { classPropertyName: "datepicker", publicName: "for", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<button\n type=\"button\"\n idsIconButton\n aria-haspopup=\"true\"\n [size]=\"'dense'\"\n [variant]=\"'surface'\"\n [disabled]=\"_parent.disabled()\"\n [attr.aria-label]=\"ariaLabel() || _intl.triggerButtonLabel\"\n [attr.aria-expanded]=\"datepicker() ? datepicker().opened() : null\"\n (click)=\"datepicker().open()\"\n>\n <ids-icon aria-hidden=\"true\" fontIcon=\"calendar-days\" [attr.alt]=\"ariaLabel() || _intl.triggerButtonLabel\" />\n</button>\n", dependencies: [{ kind: "component", type: IdsIconComponent, selector: "ids-icon", inputs: ["size", "sizeCollection", "variant", "fontIcon", "svgIcon", "aria-hidden"] }, { kind: "component", type: IdsIconButtonComponent, selector: "button[idsIconButton]", inputs: ["appearance", "size", "variant", "disabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
87
+ }
88
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDatepickerTriggerComponent, decorators: [{
89
+ type: Component,
90
+ args: [{ selector: 'ids-datepicker-trigger', imports: [
91
+ IdsIconComponent,
92
+ IdsIconButtonComponent,
93
+ ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n type=\"button\"\n idsIconButton\n aria-haspopup=\"true\"\n [size]=\"'dense'\"\n [variant]=\"'surface'\"\n [disabled]=\"_parent.disabled()\"\n [attr.aria-label]=\"ariaLabel() || _intl.triggerButtonLabel\"\n [attr.aria-expanded]=\"datepicker() ? datepicker().opened() : null\"\n (click)=\"datepicker().open()\"\n>\n <ids-icon aria-hidden=\"true\" fontIcon=\"calendar-days\" [attr.alt]=\"ariaLabel() || _intl.triggerButtonLabel\" />\n</button>\n" }]
94
+ }] });
95
+
96
+ const IDS_DATEPICKER_DEFAULT_CONFIG = new InjectionToken('IDS_DATEPICKER_DEFAULT_CONFIG', {
97
+ providedIn: 'root',
98
+ factory: IDS_DATEPICKER_DEFAULT_CONFIG_FACTORY,
99
+ });
100
+ function IDS_DATEPICKER_DEFAULT_CONFIG_FACTORY() {
101
+ return {
102
+ view: IdsDatepickerView.DAY,
103
+ };
104
+ }
105
+
106
+ class IdsCalendarPage {
107
+ constructor() {
108
+ this._calendarButtonSelector = '.ids-calendar-button-focused';
109
+ this._element = inject(ElementRef);
110
+ this._injector = inject(Injector);
111
+ this._cdRef = inject(ChangeDetectorRef);
112
+ this._intl = inject(IdsDatepickerIntl);
113
+ this.value = input(null);
114
+ this.focusedDate = model.required();
115
+ this.min = input(null);
116
+ this.max = input(null);
117
+ this.selected = output();
118
+ this._today = new Date();
119
+ toObservable(this.focusedDate).pipe(takeUntilDestroyed()).subscribe(() => this._moveFocusToCurrent());
120
+ this._intl.changes.pipe(takeUntilDestroyed()).subscribe(() => this._cdRef.markForCheck());
121
+ }
122
+ ngAfterViewInit() {
123
+ this._moveFocusToCurrent();
124
+ }
125
+ _moveFocusToCurrent() {
126
+ afterNextRender(() => {
127
+ const focusedDayButton = this._element.nativeElement.querySelector(this._calendarButtonSelector);
128
+ if (focusedDayButton) {
129
+ focusedDayButton.focus();
130
+ }
131
+ }, { injector: this._injector });
132
+ }
133
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsCalendarPage, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
134
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.1.2", type: IdsCalendarPage, isStandalone: true, inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, focusedDate: { classPropertyName: "focusedDate", publicName: "focusedDate", isSignal: true, isRequired: true, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { focusedDate: "focusedDateChange", selected: "selected" }, ngImport: i0 }); }
135
+ }
136
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsCalendarPage, decorators: [{
137
+ type: Directive
138
+ }], ctorParameters: () => [] });
139
+
140
+ const firstDayOfWeekOffset = 1;
141
+ const daysPerWeek = 7;
142
+ class IdsDaySelectorComponent extends IdsCalendarPage {
143
+ constructor() {
144
+ super(...arguments);
145
+ this.headerLabel = computed(() => (this._intl.getDaySelectorHeaderLabel(this._currentMonth()) ?? ''));
146
+ this._currentMonth = computed(() => startOfMonth(this.focusedDate()));
147
+ this._previousMonth = computed(() => addMonths(this._currentMonth(), -1));
148
+ this._nextMonth = computed(() => addMonths(this._currentMonth(), 1));
149
+ this._gridStartDate = computed(() => startOfWeek(this._currentMonth(), firstDayOfWeekOffset));
150
+ this._gridEndDate = computed(() => endOfWeek(endOfMonth(this._currentMonth()), firstDayOfWeekOffset));
151
+ this._days = computed(() => getDatesBetween(this._gridStartDate(), this._gridEndDate()));
152
+ this._weekdayLabels = computed(() => this._days().slice(0, daysPerWeek).map((date) => this._intl.getWeekdayHeaderLabel(date) ?? ''));
153
+ }
154
+ hasPreviousPage() {
155
+ const dayOnPrevPage = addDays(this._gridStartDate(), -1);
156
+ const min = this.min() ?? dayOnPrevPage;
157
+ return compareDates(dayOnPrevPage, min) > -1;
158
+ }
159
+ gotoPreviousPage() {
160
+ const current = this._currentMonth();
161
+ this.focusedDate.set(new Date(current.getFullYear(), current.getMonth(), 0));
162
+ }
163
+ hasNextPage() {
164
+ const dayOnNextPage = addDays(this._gridEndDate(), 1);
165
+ const max = this.max() ?? dayOnNextPage;
166
+ return compareDates(dayOnNextPage, max) < 1;
167
+ }
168
+ gotoNextPage() {
169
+ const current = this._currentMonth();
170
+ this.focusedDate.set(new Date(current.getFullYear(), current.getMonth() + 1, 1));
171
+ }
172
+ _selectDate(event, date) {
173
+ event.stopPropagation();
174
+ event.preventDefault();
175
+ this.selected.emit(date);
176
+ }
177
+ _isDateInCurrentMonth(date) {
178
+ return date.getMonth() === this._currentMonth().getMonth();
179
+ }
180
+ _isDateFocused(date) {
181
+ return equalDates(date, this.focusedDate());
182
+ }
183
+ _isDateSelected(date) {
184
+ return equalDates(date, this.value());
185
+ }
186
+ _isDateDisabled(date) {
187
+ const min = this.min();
188
+ const max = this.max();
189
+ return (isValidDate(min) && compareDates(date, min) === -1) || (isValidDate(max) && compareDates(date, max) === 1);
190
+ }
191
+ _isDateToday(date) {
192
+ return equalDates(date, this._today);
193
+ }
194
+ _handleKeydown(event) {
195
+ let focusedDate = this.focusedDate();
196
+ switch (event.code) {
197
+ case 'ArrowUp':
198
+ focusedDate = addDays(focusedDate, -daysPerWeek);
199
+ break;
200
+ case 'ArrowDown':
201
+ focusedDate = addDays(focusedDate, daysPerWeek);
202
+ break;
203
+ case 'ArrowLeft':
204
+ focusedDate = addDays(focusedDate, -1);
205
+ break;
206
+ case 'ArrowRight':
207
+ focusedDate = addDays(focusedDate, 1);
208
+ break;
209
+ case 'Home':
210
+ focusedDate = startOfWeek(focusedDate, firstDayOfWeekOffset);
211
+ break;
212
+ case 'End':
213
+ focusedDate = endOfWeek(focusedDate, firstDayOfWeekOffset);
214
+ break;
215
+ case 'PageUp':
216
+ focusedDate = this._hasShiftModifierKey(event) ? addYears(focusedDate, -1) : addMonths(focusedDate, -1);
217
+ break;
218
+ case 'PageDown':
219
+ focusedDate = this._hasShiftModifierKey(event) ? addYears(focusedDate, 1) : addMonths(focusedDate, 1);
220
+ break;
221
+ }
222
+ this.focusedDate.set(clampDate(focusedDate, this.min(), this.max()));
223
+ }
224
+ _hasShiftModifierKey(event) {
225
+ return event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey;
226
+ }
227
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDaySelectorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
228
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsDaySelectorComponent, isStandalone: true, selector: "ids-day-selector", host: { listeners: { "keydown": "_handleKeydown($event)" }, classAttribute: "ids-day-selector" }, providers: [
229
+ {
230
+ provide: IdsCalendarPage,
231
+ useExisting: IdsDaySelectorComponent,
232
+ },
233
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"ids-day-selector__days-grid ids-calendar-grid\">\n @for (label of _weekdayLabels(); track $index) {\n <div class=\"ids-calendar-cell\">\n <span class=\"ids-day-selector__header--label ids-calendar-weekday-label\">{{ label }}</span>\n </div>\n }\n @for (day of _days(); track $index) {\n @let dayLabel = day.getDate();\n @let isSelected = _isDateSelected(day);\n @let isFocused = _isDateFocused(day);\n @let isToday = _isDateToday(day);\n @let isDisabled = _isDateDisabled(day) || !_isDateInCurrentMonth(day);\n @let tabIndex = isFocused ? 0 : -1;\n @let ariaLabel = _intl.getDayAriaLabel(day);\n <div class=\"ids-calendar-cell\" [class.ids-calendar-cell-selected]=\"isSelected\">\n <button\n type=\"button\"\n class=\"ids-day-selector__day-button ids-calendar-button\"\n [attr.disabled]=\"isDisabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.aria-pressed]=\"isSelected\"\n [attr.aria-current]=\"isToday\"\n [tabIndex]=\"tabIndex\"\n [class.ids-calendar-button-today]=\"isToday\"\n [class.ids-calendar-button-selected]=\"isSelected\"\n [class.ids-calendar-button-focused]=\"isFocused\"\n (click)=\"_selectDate($event, day)\"\n >{{ dayLabel }}</button>\n </div>\n }\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
234
+ }
235
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDaySelectorComponent, decorators: [{
236
+ type: Component,
237
+ args: [{ selector: 'ids-day-selector', imports: [], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
238
+ {
239
+ provide: IdsCalendarPage,
240
+ useExisting: IdsDaySelectorComponent,
241
+ },
242
+ ], host: {
243
+ 'class': 'ids-day-selector',
244
+ '(keydown)': '_handleKeydown($event)',
245
+ }, template: "<div class=\"ids-day-selector__days-grid ids-calendar-grid\">\n @for (label of _weekdayLabels(); track $index) {\n <div class=\"ids-calendar-cell\">\n <span class=\"ids-day-selector__header--label ids-calendar-weekday-label\">{{ label }}</span>\n </div>\n }\n @for (day of _days(); track $index) {\n @let dayLabel = day.getDate();\n @let isSelected = _isDateSelected(day);\n @let isFocused = _isDateFocused(day);\n @let isToday = _isDateToday(day);\n @let isDisabled = _isDateDisabled(day) || !_isDateInCurrentMonth(day);\n @let tabIndex = isFocused ? 0 : -1;\n @let ariaLabel = _intl.getDayAriaLabel(day);\n <div class=\"ids-calendar-cell\" [class.ids-calendar-cell-selected]=\"isSelected\">\n <button\n type=\"button\"\n class=\"ids-day-selector__day-button ids-calendar-button\"\n [attr.disabled]=\"isDisabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.aria-pressed]=\"isSelected\"\n [attr.aria-current]=\"isToday\"\n [tabIndex]=\"tabIndex\"\n [class.ids-calendar-button-today]=\"isToday\"\n [class.ids-calendar-button-selected]=\"isSelected\"\n [class.ids-calendar-button-focused]=\"isFocused\"\n (click)=\"_selectDate($event, day)\"\n >{{ dayLabel }}</button>\n </div>\n }\n</div>\n" }]
246
+ }] });
247
+
248
+ const monthsPerRow = 4;
249
+ const lastMonth = 11;
250
+ class IdsMonthSelectorComponent extends IdsCalendarPage {
251
+ constructor() {
252
+ super(...arguments);
253
+ this._focusedYear = computed(() => this.focusedDate().getFullYear());
254
+ this._monthLabels = toSignal(this._intl.changes.pipe(startWith(null), map(() => {
255
+ const currentYear = new Date().getFullYear();
256
+ const monthLabels = Array.from({ length: 12 })
257
+ .map((nil, month) => this._intl.getMonthLabel(createNormalizedDate(currentYear, month, 1)) ?? '');
258
+ return monthLabels;
259
+ })));
260
+ this.headerLabel = computed(() => String(this._focusedYear()));
261
+ }
262
+ hasPreviousPage() {
263
+ const min = this.min();
264
+ if (!isValidDate(min)) {
265
+ return true;
266
+ }
267
+ const startOfYear = createNormalizedDate(this._focusedYear(), 0, 1);
268
+ return compareDates(min, startOfYear) === -1;
269
+ }
270
+ gotoPreviousPage() {
271
+ this.focusedDate.set(createNormalizedDate(this._focusedYear() - 1, lastMonth, 1));
272
+ }
273
+ hasNextPage() {
274
+ const max = this.max();
275
+ if (!isValidDate(max)) {
276
+ return true;
277
+ }
278
+ // eslint-disable-next-line no-magic-numbers
279
+ const endOfYear = createNormalizedDate(this._focusedYear(), 11, 31);
280
+ return compareDates(max, endOfYear) === 1;
281
+ }
282
+ gotoNextPage() {
283
+ this.focusedDate.set(createNormalizedDate(this._focusedYear() + 1, 0, 1));
284
+ }
285
+ _selectMonth(event, month) {
286
+ event.stopPropagation();
287
+ event.preventDefault();
288
+ const currentFocusedDate = this.focusedDate();
289
+ const nextFocusedDate = createNormalizedDate(currentFocusedDate.getFullYear(), month, currentFocusedDate.getDate());
290
+ this.selected.emit(clampDate(nextFocusedDate, this.min(), this.max()));
291
+ }
292
+ _isMonthFocused(month) {
293
+ return month === this.focusedDate().getMonth();
294
+ }
295
+ _isMonthSelected(month) {
296
+ const selectedYear = this.value()?.getFullYear();
297
+ const selectedMonth = this.value()?.getMonth();
298
+ return this._focusedYear() === selectedYear && month === selectedMonth;
299
+ }
300
+ _isCurrentMonth(month) {
301
+ return this._focusedYear() === this._today.getFullYear() && month === this._today.getMonth();
302
+ }
303
+ _isMonthDisabled(month) {
304
+ const min = this.min();
305
+ const max = this.max();
306
+ const monthAsDate = new Date(this._focusedYear(), month, 1);
307
+ return (isValidDate(min) && compareDates(endOfMonth(monthAsDate), startOfMonth(min)) === -1) ||
308
+ (isValidDate(max) && compareDates(monthAsDate, endOfMonth(max)) === 1);
309
+ }
310
+ _getMonthAsDate(month) {
311
+ return createNormalizedDate(this._focusedYear(), month, 1);
312
+ }
313
+ _handleKeydown(event) {
314
+ let focusedDate = this.focusedDate();
315
+ switch (event.code) {
316
+ case 'ArrowUp':
317
+ focusedDate = addMonths(focusedDate, -monthsPerRow);
318
+ break;
319
+ case 'ArrowDown':
320
+ focusedDate = addMonths(focusedDate, monthsPerRow);
321
+ break;
322
+ case 'ArrowLeft':
323
+ focusedDate = addMonths(focusedDate, -1);
324
+ break;
325
+ case 'ArrowRight':
326
+ focusedDate = addMonths(focusedDate, 1);
327
+ break;
328
+ case 'Home':
329
+ focusedDate = new Date(focusedDate.getFullYear(), 0, focusedDate.getDate());
330
+ break;
331
+ case 'End':
332
+ // eslint-disable-next-line no-magic-numbers
333
+ focusedDate = new Date(focusedDate.getFullYear(), 11, focusedDate.getDate());
334
+ break;
335
+ case 'PageUp':
336
+ focusedDate = addYears(focusedDate, -1);
337
+ break;
338
+ case 'PageDown':
339
+ focusedDate = addYears(focusedDate, 1);
340
+ break;
341
+ }
342
+ this.focusedDate.set(clampDate(focusedDate, this.min(), this.max()));
343
+ }
344
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsMonthSelectorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
345
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsMonthSelectorComponent, isStandalone: true, selector: "ids-month-selector", host: { listeners: { "keydown": "_handleKeydown($event)" }, classAttribute: "ids-month-selector" }, providers: [
346
+ {
347
+ provide: IdsCalendarPage,
348
+ useExisting: IdsMonthSelectorComponent,
349
+ },
350
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"ids-month-selector__months-grid ids-calendar-grid\">\n @for (monthLabel of _monthLabels(); track $index; let month = $index) {\n @let isSelected = _isMonthSelected(month);\n @let isFocused = _isMonthFocused(month);\n @let isCurrentMonth = _isCurrentMonth(month);\n @let tabIndex = isFocused ? 0 : -1;\n @let isDisabled = _isMonthDisabled(month);\n @let ariaLabel = _intl.getMonthAriaLabel(_getMonthAsDate(month));\n <div class=\"ids-calendar-cell\">\n <button\n type=\"button\"\n class=\"ids-month-selector__month-button ids-calendar-button\"\n [attr.disabled]=\"isDisabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.aria-pressed]=\"isSelected\"\n [attr.aria-current]=\"isCurrentMonth\"\n [tabIndex]=\"tabIndex\"\n [class.ids-month-selector__month-button--current-month]=\"isCurrentMonth\"\n [class.ids-calendar-button-selected]=\"isSelected\"\n [class.ids-calendar-button-focused]=\"isFocused\"\n (click)=\"_selectMonth($event, month)\"\n >{{ monthLabel }}</button>\n </div>\n }\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
351
+ }
352
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsMonthSelectorComponent, decorators: [{
353
+ type: Component,
354
+ args: [{ selector: 'ids-month-selector', imports: [], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
355
+ {
356
+ provide: IdsCalendarPage,
357
+ useExisting: IdsMonthSelectorComponent,
358
+ },
359
+ ], host: {
360
+ 'class': 'ids-month-selector',
361
+ '(keydown)': '_handleKeydown($event)',
362
+ }, template: "<div class=\"ids-month-selector__months-grid ids-calendar-grid\">\n @for (monthLabel of _monthLabels(); track $index; let month = $index) {\n @let isSelected = _isMonthSelected(month);\n @let isFocused = _isMonthFocused(month);\n @let isCurrentMonth = _isCurrentMonth(month);\n @let tabIndex = isFocused ? 0 : -1;\n @let isDisabled = _isMonthDisabled(month);\n @let ariaLabel = _intl.getMonthAriaLabel(_getMonthAsDate(month));\n <div class=\"ids-calendar-cell\">\n <button\n type=\"button\"\n class=\"ids-month-selector__month-button ids-calendar-button\"\n [attr.disabled]=\"isDisabled || null\"\n [attr.aria-label]=\"ariaLabel\"\n [attr.aria-pressed]=\"isSelected\"\n [attr.aria-current]=\"isCurrentMonth\"\n [tabIndex]=\"tabIndex\"\n [class.ids-month-selector__month-button--current-month]=\"isCurrentMonth\"\n [class.ids-calendar-button-selected]=\"isSelected\"\n [class.ids-calendar-button-focused]=\"isFocused\"\n (click)=\"_selectMonth($event, month)\"\n >{{ monthLabel }}</button>\n </div>\n }\n</div>\n" }]
363
+ }] });
364
+
365
+ const yearsPerRow = 4;
366
+ const yearsPerPage = 24;
367
+ class IdsYearSelectorComponent extends IdsCalendarPage {
368
+ constructor() {
369
+ super(...arguments);
370
+ this._firstYearOnPage = computed(() => {
371
+ const focusedYear = this.focusedDate().getFullYear();
372
+ const firstYearOffset = this._getFirstYearOffset(focusedYear, this.min()?.getFullYear(), this.max()?.getFullYear());
373
+ const firstYearOnPage = focusedYear - firstYearOffset;
374
+ return firstYearOnPage;
375
+ });
376
+ this._years = computed(() => {
377
+ const firstYearOnPage = this._firstYearOnPage();
378
+ const years = Array.from({ length: yearsPerPage }).map((_nil, index) => firstYearOnPage + index);
379
+ return years;
380
+ });
381
+ this.headerLabel = computed(() => `${this._years()[0]} - ${this._years().at(-1)}`);
382
+ }
383
+ hasPreviousPage() {
384
+ const min = Number.isFinite(this.min()?.getFullYear()) ? this.min().getFullYear() : Number.POSITIVE_INFINITY;
385
+ const firstYearOnPage = Number.isFinite(this._firstYearOnPage()) ? this._firstYearOnPage() : Number.NEGATIVE_INFINITY;
386
+ return firstYearOnPage > min;
387
+ }
388
+ gotoPreviousPage() {
389
+ const firstYearOnPage = this._firstYearOnPage();
390
+ const currentFocusedDate = this.focusedDate();
391
+ this.focusedDate.set(createNormalizedDate(firstYearOnPage - 1, currentFocusedDate.getMonth(), currentFocusedDate.getDate()));
392
+ }
393
+ hasNextPage() {
394
+ const max = Number.isFinite(this.max()?.getFullYear()) ? this.max().getFullYear() : Number.NEGATIVE_INFINITY;
395
+ const lastYearOnPage = Number.isFinite(this._firstYearOnPage()) ? this._firstYearOnPage() + yearsPerPage - 1 : Number.POSITIVE_INFINITY;
396
+ return lastYearOnPage < max;
397
+ }
398
+ gotoNextPage() {
399
+ const firstYearOnPage = this._firstYearOnPage();
400
+ const currentFocusedDate = this.focusedDate();
401
+ this.focusedDate.set(createNormalizedDate(firstYearOnPage + yearsPerPage, currentFocusedDate.getMonth(), currentFocusedDate.getDate()));
402
+ }
403
+ _selectYear(event, year) {
404
+ event.stopPropagation();
405
+ event.preventDefault();
406
+ const currentFocusedDate = this.focusedDate();
407
+ const nextFocusedDate = createNormalizedDate(year, currentFocusedDate.getMonth(), currentFocusedDate.getDate());
408
+ this.selected.emit(clampDate(nextFocusedDate, this.min(), this.max()));
409
+ }
410
+ _isYearFocused(year) {
411
+ return year === this.focusedDate().getFullYear();
412
+ }
413
+ _isYearSelected(year) {
414
+ return year === this.value()?.getFullYear();
415
+ }
416
+ _isCurrentYear(year) {
417
+ return year === new Date().getFullYear();
418
+ }
419
+ _isYearDisabled(year) {
420
+ const min = this.min();
421
+ const max = this.max();
422
+ return (isValidDate(min) && year < min.getFullYear()) || (isValidDate(max) && year > max.getFullYear());
423
+ }
424
+ _handleKeydown(event) {
425
+ let focusedDate = this.focusedDate();
426
+ switch (event.code) {
427
+ case 'ArrowUp':
428
+ focusedDate = addYears(focusedDate, -yearsPerRow);
429
+ break;
430
+ case 'ArrowDown':
431
+ focusedDate = addYears(focusedDate, yearsPerRow);
432
+ break;
433
+ case 'ArrowLeft':
434
+ focusedDate = addYears(focusedDate, -1);
435
+ break;
436
+ case 'ArrowRight':
437
+ focusedDate = addYears(focusedDate, 1);
438
+ break;
439
+ case 'Home':
440
+ focusedDate = new Date(this._years()[0], focusedDate.getMonth(), focusedDate.getDate());
441
+ break;
442
+ case 'End':
443
+ focusedDate = new Date(this._years().at(-1), focusedDate.getMonth(), focusedDate.getDate());
444
+ break;
445
+ case 'PageUp':
446
+ focusedDate = addYears(focusedDate, -yearsPerPage);
447
+ break;
448
+ case 'PageDown':
449
+ focusedDate = addYears(focusedDate, yearsPerPage);
450
+ break;
451
+ }
452
+ this.focusedDate.set(clampDate(focusedDate, this.min(), this.max()));
453
+ }
454
+ _getFirstYearOffset(focusedYear, minYear, maxYear = (yearsPerPage - 1)) {
455
+ // If we have a max year we calculate an offset so that the max year will be the last in the years list
456
+ // Otherwise if we have a min year we calculate an offset so that the min year will be the first in the years list
457
+ const minMaxOffset = maxYear - yearsPerPage + 1 || minYear || 0;
458
+ return positiveModulus(focusedYear - minMaxOffset, yearsPerPage);
459
+ }
460
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsYearSelectorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
461
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsYearSelectorComponent, isStandalone: true, selector: "ids-year-selector", host: { listeners: { "keydown": "_handleKeydown($event)" }, classAttribute: "ids-year-selector" }, providers: [
462
+ {
463
+ provide: IdsCalendarPage,
464
+ useExisting: IdsYearSelectorComponent,
465
+ },
466
+ ], usesInheritance: true, ngImport: i0, template: "<div class=\"ids-year-selector__years-grid ids-calendar-grid\">\n @for (year of _years(); track $index) {\n @let isSelected = _isYearSelected(year);\n @let isFocused = _isYearFocused(year);\n @let isCurrentYear = _isCurrentYear(year);\n @let tabIndex = isFocused ? 0 : -1;\n @let isDisabled = _isYearDisabled(year);\n <div class=\"ids-calendar-cell\">\n <button\n type=\"button\"\n class=\"ids-year-selector__year-button ids-calendar-button\"\n [attr.disabled]=\"isDisabled || null\"\n [attr.aria-pressed]=\"isSelected\"\n [attr.aria-current]=\"isCurrentYear\"\n [tabIndex]=\"tabIndex\"\n [class.ids-year-selector__year-button--current-year]=\"isCurrentYear\"\n [class.ids-calendar-button-selected]=\"isSelected\"\n [class.ids-calendar-button-focused]=\"isFocused\"\n (click)=\"_selectYear($event, year)\"\n >{{ year }}</button>\n </div>\n }\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
467
+ }
468
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsYearSelectorComponent, decorators: [{
469
+ type: Component,
470
+ args: [{ selector: 'ids-year-selector', imports: [], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
471
+ {
472
+ provide: IdsCalendarPage,
473
+ useExisting: IdsYearSelectorComponent,
474
+ },
475
+ ], host: {
476
+ 'class': 'ids-year-selector',
477
+ '(keydown)': '_handleKeydown($event)',
478
+ }, template: "<div class=\"ids-year-selector__years-grid ids-calendar-grid\">\n @for (year of _years(); track $index) {\n @let isSelected = _isYearSelected(year);\n @let isFocused = _isYearFocused(year);\n @let isCurrentYear = _isCurrentYear(year);\n @let tabIndex = isFocused ? 0 : -1;\n @let isDisabled = _isYearDisabled(year);\n <div class=\"ids-calendar-cell\">\n <button\n type=\"button\"\n class=\"ids-year-selector__year-button ids-calendar-button\"\n [attr.disabled]=\"isDisabled || null\"\n [attr.aria-pressed]=\"isSelected\"\n [attr.aria-current]=\"isCurrentYear\"\n [tabIndex]=\"tabIndex\"\n [class.ids-year-selector__year-button--current-year]=\"isCurrentYear\"\n [class.ids-calendar-button-selected]=\"isSelected\"\n [class.ids-calendar-button-focused]=\"isFocused\"\n (click)=\"_selectYear($event, year)\"\n >{{ year }}</button>\n </div>\n }\n</div>\n" }]
479
+ }] });
480
+
481
+ class IdsCalendarComponent extends ComponentBaseWithDefaults {
482
+ constructor() {
483
+ super(...arguments);
484
+ this._defaultConfig = {};
485
+ this._cdRef = inject(ChangeDetectorRef);
486
+ this._intl = inject(IdsDatepickerIntl);
487
+ this.value = input(null);
488
+ this.size = input.required();
489
+ this.view = input.required();
490
+ this.min = input(null);
491
+ this.max = input(null);
492
+ this.selected = output();
493
+ this.monthSelected = output();
494
+ this.yearSelected = output();
495
+ this._calendarPage = viewChild(IdsCalendarPage);
496
+ this._view = linkedSignal(() => this.view());
497
+ this._hostClasses = computed(() => this._getHostClasses([this.size()]));
498
+ this._headerButtonSize = computed(() => (this.size() === IdsSize.DENSE ? IdsSize.COMPACT : this.size()));
499
+ this._headerLabel = computed(() => this._calendarPage()?.headerLabel() ?? '');
500
+ this._prevPageLabel = computed(() => {
501
+ switch (this._view()) {
502
+ case IdsDatepickerView.DAY:
503
+ return this._intl.prevMonthLabel;
504
+ case IdsDatepickerView.MONTH:
505
+ return this._intl.prevYearLabel;
506
+ case IdsDatepickerView.YEAR:
507
+ return this._intl.prevYearsPageLabel;
508
+ }
509
+ });
510
+ this._nextPageLabel = computed(() => {
511
+ switch (this._view()) {
512
+ case IdsDatepickerView.DAY:
513
+ return this._intl.nextMonthLabel;
514
+ case IdsDatepickerView.MONTH:
515
+ return this._intl.nextYearLabel;
516
+ case IdsDatepickerView.YEAR:
517
+ return this._intl.nextYearsPageLabel;
518
+ }
519
+ });
520
+ this._switchViewLabel = computed(() => (this._view() === IdsDatepickerView.DAY ? this._intl.switchToYearSelectorLabel : this._intl.switchToDaySelectorLabel));
521
+ this._focusedDate = new Date();
522
+ }
523
+ get _hostName() {
524
+ return 'calendar';
525
+ }
526
+ ngOnInit() {
527
+ this._intl.changes.pipe(takeUntilDestroyed(this._destroyRef)).subscribe(() => this._cdRef.markForCheck());
528
+ this._setFocusedDate(this.value());
529
+ }
530
+ _selectYear(selectedYear) {
531
+ this._setFocusedDate(selectedYear);
532
+ this.yearSelected.emit(startOfYear(selectedYear));
533
+ this._view.set(IdsDatepickerView.MONTH);
534
+ }
535
+ _selectMonth(selectedMonth) {
536
+ this._setFocusedDate(selectedMonth);
537
+ this.monthSelected.emit(startOfMonth(selectedMonth));
538
+ this._view.set(IdsDatepickerView.DAY);
539
+ }
540
+ _selectDate(date) {
541
+ this.selected.emit(date);
542
+ }
543
+ _hasPrevPage() {
544
+ return this._calendarPage()?.hasPreviousPage() ?? true;
545
+ }
546
+ _gotoPrevPage() {
547
+ this._calendarPage().gotoPreviousPage();
548
+ }
549
+ _hasNextPage() {
550
+ return this._calendarPage()?.hasNextPage() ?? true;
551
+ }
552
+ _gotoNextPage() {
553
+ this._calendarPage().gotoNextPage();
554
+ }
555
+ _switchView() {
556
+ switch (this._view()) {
557
+ case IdsDatepickerView.DAY:
558
+ this._view.set(IdsDatepickerView.YEAR);
559
+ break;
560
+ case IdsDatepickerView.YEAR:
561
+ case IdsDatepickerView.MONTH:
562
+ this._view.set(IdsDatepickerView.DAY);
563
+ break;
564
+ }
565
+ }
566
+ _setFocusedDate(date) {
567
+ const safeValue = isValidDate(date) ? date : new Date();
568
+ this._focusedDate = clampDate(safeValue, this.min(), this.max());
569
+ this._cdRef.markForCheck();
570
+ }
571
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsCalendarComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
572
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.2", type: IdsCalendarComponent, isStandalone: true, selector: "ids-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: true, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: true, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected", monthSelected: "monthSelected", yearSelected: "yearSelected" }, host: { classAttribute: "ids-calendar-surface" }, viewQueries: [{ propertyName: "_calendarPage", first: true, predicate: IdsCalendarPage, descendants: true, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: i1.CdkTrapFocus }], ngImport: i0, template: "<div class=\"ids-calendar__header\">\n <button\n type=\"button\"\n idsIconButton\n [size]=\"_headerButtonSize()\"\n [variant]=\"'surface'\"\n [appearance]=\"'standard'\"\n [disabled]=\"!_hasPrevPage()\"\n [attr.aria-label]=\"_prevPageLabel()\"\n (click)=\"_gotoPrevPage()\"\n >\n <ids-icon aria-hidden=\"true\" alt=\"\" fontIcon=\"chevron-left\" [sizeCollection]=\"'big'\" />\n </button>\n <button\n type=\"button\"\n idsButton\n [size]=\"_headerButtonSize()\"\n [variant]=\"'surface'\"\n [appearance]=\"'text'\"\n [attr.aria-label]=\"_switchViewLabel()\"\n [attr.aria-description]=\"_headerLabel()\"\n (click)=\"_switchView()\"\n >\n {{ _headerLabel() }}\n <ids-icon icon-trailing aria-hidden=\"true\" alt=\"\" fontIcon=\"chevron-down\" />\n </button>\n <button\n type=\"button\"\n idsIconButton\n [size]=\"_headerButtonSize()\"\n [variant]=\"'surface'\"\n [appearance]=\"'standard'\"\n [disabled]=\"!_hasNextPage()\"\n [attr.aria-label]=\"_nextPageLabel()\"\n (click)=\"_gotoNextPage()\"\n >\n <ids-icon aria-hidden=\"true\" alt=\"\" fontIcon=\"chevron-right\" />\n </button>\n</div>\n@switch (_view()) {\n @case ('day') {\n <ids-day-selector [value]=\"value()\" [min]=\"min()\" [max]=\"max()\" [(focusedDate)]=\"_focusedDate\" (selected)=\"_selectDate($event)\" />\n }\n @case ('month') {\n <ids-month-selector [value]=\"value()\" [min]=\"min()\" [max]=\"max()\" [(focusedDate)]=\"_focusedDate\" (selected)=\"_selectMonth($event)\" />\n }\n @case ('year') {\n <ids-year-selector [value]=\"value()\" [min]=\"min()\" [max]=\"max()\" [(focusedDate)]=\"_focusedDate\" (selected)=\"_selectYear($event)\" />\n }\n}\n", dependencies: [{ kind: "component", type: IdsButtonComponent, selector: "button[idsButton]", inputs: ["appearance", "size", "variant", "disabled"] }, { kind: "component", type: IdsIconComponent, selector: "ids-icon", inputs: ["size", "sizeCollection", "variant", "fontIcon", "svgIcon", "aria-hidden"] }, { kind: "component", type: IdsIconButtonComponent, selector: "button[idsIconButton]", inputs: ["appearance", "size", "variant", "disabled"] }, { kind: "component", type: IdsDaySelectorComponent, selector: "ids-day-selector" }, { kind: "component", type: IdsMonthSelectorComponent, selector: "ids-month-selector" }, { kind: "component", type: IdsYearSelectorComponent, selector: "ids-year-selector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
573
+ }
574
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsCalendarComponent, decorators: [{
575
+ type: Component,
576
+ args: [{ selector: 'ids-calendar', imports: [
577
+ IdsButtonComponent,
578
+ IdsIconComponent,
579
+ IdsIconButtonComponent,
580
+ IdsDaySelectorComponent,
581
+ IdsMonthSelectorComponent,
582
+ IdsYearSelectorComponent,
583
+ ], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [CdkTrapFocus], host: {
584
+ 'class': 'ids-calendar-surface',
585
+ }, template: "<div class=\"ids-calendar__header\">\n <button\n type=\"button\"\n idsIconButton\n [size]=\"_headerButtonSize()\"\n [variant]=\"'surface'\"\n [appearance]=\"'standard'\"\n [disabled]=\"!_hasPrevPage()\"\n [attr.aria-label]=\"_prevPageLabel()\"\n (click)=\"_gotoPrevPage()\"\n >\n <ids-icon aria-hidden=\"true\" alt=\"\" fontIcon=\"chevron-left\" [sizeCollection]=\"'big'\" />\n </button>\n <button\n type=\"button\"\n idsButton\n [size]=\"_headerButtonSize()\"\n [variant]=\"'surface'\"\n [appearance]=\"'text'\"\n [attr.aria-label]=\"_switchViewLabel()\"\n [attr.aria-description]=\"_headerLabel()\"\n (click)=\"_switchView()\"\n >\n {{ _headerLabel() }}\n <ids-icon icon-trailing aria-hidden=\"true\" alt=\"\" fontIcon=\"chevron-down\" />\n </button>\n <button\n type=\"button\"\n idsIconButton\n [size]=\"_headerButtonSize()\"\n [variant]=\"'surface'\"\n [appearance]=\"'standard'\"\n [disabled]=\"!_hasNextPage()\"\n [attr.aria-label]=\"_nextPageLabel()\"\n (click)=\"_gotoNextPage()\"\n >\n <ids-icon aria-hidden=\"true\" alt=\"\" fontIcon=\"chevron-right\" />\n </button>\n</div>\n@switch (_view()) {\n @case ('day') {\n <ids-day-selector [value]=\"value()\" [min]=\"min()\" [max]=\"max()\" [(focusedDate)]=\"_focusedDate\" (selected)=\"_selectDate($event)\" />\n }\n @case ('month') {\n <ids-month-selector [value]=\"value()\" [min]=\"min()\" [max]=\"max()\" [(focusedDate)]=\"_focusedDate\" (selected)=\"_selectMonth($event)\" />\n }\n @case ('year') {\n <ids-year-selector [value]=\"value()\" [min]=\"min()\" [max]=\"max()\" [(focusedDate)]=\"_focusedDate\" (selected)=\"_selectYear($event)\" />\n }\n}\n" }]
586
+ }] });
587
+
588
+ const datepickerConnectedPositions = [
589
+ {
590
+ originX: 'start',
591
+ originY: 'bottom',
592
+ overlayX: 'start',
593
+ overlayY: 'top',
594
+ },
595
+ {
596
+ originX: 'end',
597
+ originY: 'bottom',
598
+ overlayX: 'end',
599
+ overlayY: 'top',
600
+ },
601
+ {
602
+ originX: 'start',
603
+ originY: 'top',
604
+ overlayX: 'start',
605
+ overlayY: 'bottom',
606
+ panelClass: 'ids-datepicker-calendar-above',
607
+ },
608
+ {
609
+ originX: 'end',
610
+ originY: 'top',
611
+ overlayX: 'end',
612
+ overlayY: 'bottom',
613
+ panelClass: 'ids-datepicker-calendar-above',
614
+ },
615
+ ];
616
+
617
+ const defaultConfig = IDS_DATEPICKER_DEFAULT_CONFIG_FACTORY();
618
+ class IdsDatepickerDirective extends DirectiveBaseWithDefaults {
619
+ constructor() {
620
+ super(...arguments);
621
+ this._defaultConfig = this._getDefaultConfig(defaultConfig, IDS_DATEPICKER_DEFAULT_CONFIG);
622
+ this._hostClasses = signal('');
623
+ this._viewContainerRef = inject(ViewContainerRef);
624
+ this._elementRef = inject(ElementRef);
625
+ this._parent = inject(IdsFormFieldComponent);
626
+ this._overlay = inject(Overlay);
627
+ this._injector = inject(Injector);
628
+ this.formatter = input(inject(IDS_DATE_FORMATTER));
629
+ this.parser = input(inject(IDS_DATE_PARSER));
630
+ this.view = input(this._defaultConfig.view);
631
+ this.minDate = input(null, { transform: (value) => getValidDateOrNull(deserializeDate(value)) });
632
+ this.maxDate = input(null, { transform: (value) => getValidDateOrNull(deserializeDate(value)) });
633
+ this._inputChanges = effect(() => {
634
+ const min = this.minDate();
635
+ const max = this.maxDate();
636
+ const view = this.view();
637
+ const size = this._parent.size();
638
+ const calendar = this._componentRef?.instance;
639
+ if (!calendar) {
640
+ return;
641
+ }
642
+ if (!equalDates(min, calendar.min())) {
643
+ this._componentRef?.setInput('min', min);
644
+ }
645
+ if (!equalDates(max, calendar.max())) {
646
+ this._componentRef?.setInput('max', max);
647
+ }
648
+ if (view !== calendar.view()) {
649
+ this._componentRef?.setInput('view', view);
650
+ }
651
+ if (size !== calendar.size()) {
652
+ this._componentRef?.setInput('size', size);
653
+ }
654
+ });
655
+ this.openedInput = input(false, { transform: booleanAttribute, alias: 'opened' });
656
+ this._opened = signal(this.openedInput() ?? false);
657
+ this.opened = this._opened.asReadonly();
658
+ this.monthSelected = output();
659
+ this.yearSelected = output();
660
+ this._onChange = () => { };
661
+ this._onTouched = () => { };
662
+ this._onValidatorChange = () => { };
663
+ this._overlayRef = null;
664
+ this._componentRef = null;
665
+ this._scrollStrategy = this._overlay.scrollStrategies.reposition();
666
+ this._overlayCloseSub = Subscription.EMPTY;
667
+ this._isValid = signal(false);
668
+ this._dateFormatValidator = () => (this._isValid() ? null : { idsDateFormat: true });
669
+ this._minValidator = () => {
670
+ const current = getValidDateOrNull(deserializeDate(this.value));
671
+ const min = this.minDate();
672
+ return !min || !current || compareDates(min, current) <= 0 ? null : { 'idsDateMin': { min, current } };
673
+ };
674
+ this._maxValidator = () => {
675
+ const current = getValidDateOrNull(deserializeDate(this.value));
676
+ const max = this.maxDate();
677
+ return !max || !current || compareDates(max, current) >= 0 ? null : { 'idsDateMax': { max, current } };
678
+ };
679
+ // eslint-disable-next-line @stylistic/js/array-bracket-newline, @stylistic/js/array-element-newline
680
+ this._validator = Validators.compose([this._dateFormatValidator, this._minValidator, this._maxValidator]);
681
+ }
682
+ get value() {
683
+ return deserializeDate(this._parent.controlDir()?.value);
684
+ }
685
+ ngOnChanges(changes) {
686
+ if ('opened' in changes) {
687
+ if (this.openedInput()) {
688
+ this.open();
689
+ }
690
+ else {
691
+ this.close();
692
+ }
693
+ }
694
+ if (('minDate' in changes && !changes['minDate'].firstChange) || ('maxDate' in changes && !changes['maxDate'].firstChange)) {
695
+ this._onValidatorChange();
696
+ }
697
+ }
698
+ // #region Value accessor implementation
699
+ writeValue(obj) {
700
+ this._isValid.set(this._isValidValue(deserializeDate(obj)));
701
+ this._setInputValue(obj);
702
+ }
703
+ registerOnChange(fn) {
704
+ this._onChange = fn;
705
+ }
706
+ registerOnTouched(fn) {
707
+ this._onTouched = fn;
708
+ }
709
+ // #endregion
710
+ // #region Validator implementation
711
+ validate(control) {
712
+ return this._validator(control);
713
+ }
714
+ registerOnValidatorChange(fn) {
715
+ this._onValidatorChange = fn;
716
+ }
717
+ // #endregion
718
+ open() {
719
+ if (this.opened() || this._parent.disabled()) {
720
+ return;
721
+ }
722
+ this._overlayRef = this._overlay.create(this._getOverlayConfig());
723
+ this._overlayCloseSub = this._onOverlayClose(this._overlayRef).subscribe((event) => {
724
+ event?.preventDefault();
725
+ this.close();
726
+ });
727
+ this._onPageNavigationKeydown(this._overlayRef).subscribe((event) => event?.preventDefault());
728
+ this._componentRef = this._overlayRef.attach(new ComponentPortal(IdsCalendarComponent, this._viewContainerRef));
729
+ this._componentRef.setInput('value', this.value);
730
+ this._componentRef.setInput('min', this.minDate());
731
+ this._componentRef.setInput('max', this.maxDate());
732
+ this._componentRef.setInput('view', this.view());
733
+ this._componentRef.setInput('size', this._parent.size());
734
+ this._componentRef.instance.selected.subscribe((selectedDate) => {
735
+ this._onChange(selectedDate);
736
+ this._setInputValue(this.value);
737
+ this.close();
738
+ });
739
+ this._componentRef.instance.monthSelected.subscribe((selectedMonth) => this.monthSelected.emit(selectedMonth));
740
+ this._componentRef.instance.yearSelected.subscribe((selectedYear) => this.yearSelected.emit(selectedYear));
741
+ this._opened.set(true);
742
+ }
743
+ close() {
744
+ if (!this._opened()) {
745
+ return;
746
+ }
747
+ this._overlayCloseSub.unsubscribe();
748
+ this._overlayCloseSub = Subscription.EMPTY;
749
+ this._disposeOverlay();
750
+ this._opened.set(false);
751
+ afterNextRender(() => this._elementRef.nativeElement.focus(), { injector: this._injector });
752
+ }
753
+ _handleInput(value) {
754
+ const parsed = this.parser()(value);
755
+ this._isValid.set(this._isValidValue(parsed));
756
+ const date = getValidDateOrNull(parsed);
757
+ const valueChanged = !equalDates(date, this.value);
758
+ if (!date || valueChanged) {
759
+ this._onChange(date);
760
+ }
761
+ }
762
+ _handleBlur() {
763
+ this._onTouched();
764
+ }
765
+ _handleKeydown(event) {
766
+ if (event.code === 'ArrowDown' && event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey) {
767
+ this.open();
768
+ }
769
+ }
770
+ _isValidValue(value) {
771
+ return !value || isValidDate(value);
772
+ }
773
+ _setInputValue(value) {
774
+ this._elementRef.nativeElement.value = isValidDate(value) ? this.formatter()(value) : '';
775
+ }
776
+ _getOverlayConfig() {
777
+ const overlayViewportMargin = 8;
778
+ const positionStrategy = this._overlay
779
+ .position()
780
+ .flexibleConnectedTo(this._parent.getConnectedOverlayOrigin())
781
+ .withTransformOriginOn('.mat-datepicker-content')
782
+ .withFlexibleDimensions(false)
783
+ .withViewportMargin(overlayViewportMargin)
784
+ .withLockedPosition()
785
+ .withPositions(datepickerConnectedPositions);
786
+ return {
787
+ positionStrategy,
788
+ hasBackdrop: true,
789
+ backdropClass: [],
790
+ direction: 'ltr',
791
+ scrollStrategy: this._scrollStrategy,
792
+ panelClass: 'ids-datepicker-panel',
793
+ };
794
+ }
795
+ _disposeOverlay() {
796
+ if (this._overlayRef) {
797
+ this._overlayRef.dispose();
798
+ this._overlayRef = null;
799
+ this._componentRef = null;
800
+ }
801
+ }
802
+ _onOverlayClose(overlayRef) {
803
+ return merge(overlayRef.backdropClick(), overlayRef.detachments(), overlayRef.keydownEvents().pipe(filter((event) => event.code === 'Escape' && !hasModifierKey(event))));
804
+ }
805
+ _onPageNavigationKeydown(overlayRef) {
806
+ return overlayRef.keydownEvents().pipe(filter((event) => [
807
+ 'ArrowUp',
808
+ 'ArrowDown',
809
+ 'ArrowLeft',
810
+ 'ArrowRight',
811
+ 'PageUp',
812
+ 'PageDown',
813
+ ].includes(event.code)));
814
+ }
815
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDatepickerDirective, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
816
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.1.2", type: IdsDatepickerDirective, isStandalone: true, selector: "input[idsDatepicker]", inputs: { formatter: { classPropertyName: "formatter", publicName: "formatter", isSignal: true, isRequired: false, transformFunction: null }, parser: { classPropertyName: "parser", publicName: "parser", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, openedInput: { classPropertyName: "openedInput", publicName: "opened", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { monthSelected: "monthSelected", yearSelected: "yearSelected" }, host: { listeners: { "input": "_handleInput($event.target.value)", "blur": "_handleBlur()", "keydown": "_handleKeydown($event)" }, properties: { "disabled": "disabled" } }, providers: [
817
+ {
818
+ provide: NG_VALUE_ACCESSOR,
819
+ useExisting: forwardRef(() => IdsDatepickerDirective),
820
+ multi: true,
821
+ },
822
+ {
823
+ provide: NG_VALIDATORS,
824
+ useExisting: forwardRef(() => IdsDatepickerDirective),
825
+ multi: true,
826
+ },
827
+ ], exportAs: ["idsDatepicker"], usesInheritance: true, usesOnChanges: true, ngImport: i0 }); }
828
+ }
829
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.2", ngImport: i0, type: IdsDatepickerDirective, decorators: [{
830
+ type: Directive,
831
+ args: [{
832
+ selector: 'input[idsDatepicker]',
833
+ providers: [
834
+ {
835
+ provide: NG_VALUE_ACCESSOR,
836
+ useExisting: forwardRef(() => IdsDatepickerDirective),
837
+ multi: true,
838
+ },
839
+ {
840
+ provide: NG_VALIDATORS,
841
+ useExisting: forwardRef(() => IdsDatepickerDirective),
842
+ multi: true,
843
+ },
844
+ ],
845
+ host: {
846
+ '[disabled]': 'disabled',
847
+ '(input)': '_handleInput($event.target.value)',
848
+ '(blur)': '_handleBlur()',
849
+ '(keydown)': '_handleKeydown($event)',
850
+ },
851
+ exportAs: 'idsDatepicker',
852
+ }]
853
+ }] });
854
+
855
+ /**
856
+ * Generated bundle index. Do not edit.
857
+ */
858
+
859
+ export { IDS_DATEPICKER_DEFAULT_CONFIG, IDS_DATEPICKER_DEFAULT_CONFIG_FACTORY, IDS_DATE_FORMATTER, IDS_DATE_PARSER, IdsDatepickerDirective, IdsDatepickerIntl, IdsDatepickerTriggerComponent, IdsDatepickerView };
860
+ //# sourceMappingURL=i-cell-ids-angular-datepicker.mjs.map