@updevs/components 1.0.0-alpha.48 → 1.0.0-alpha.49

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 (90) hide show
  1. package/README.md +1 -13
  2. package/calendar/calendar.component.d.ts +58 -122
  3. package/calendar/models/calendar-date.model.d.ts +10 -0
  4. package/calendar/models/calendar-month.model.d.ts +7 -0
  5. package/calendar/models/calendar-selected.model.d.ts +5 -0
  6. package/calendar/models/calendar-year.model.d.ts +6 -0
  7. package/calendar/models/date-range.model.d.ts +0 -4
  8. package/calendar/models/enums/calendar-view.enum.d.ts +5 -0
  9. package/calendar/models/public-api.d.ts +5 -0
  10. package/calendar/upd-calendar.module.d.ts +6 -11
  11. package/card/public-api.d.ts +1 -0
  12. package/card/types/public-api.d.ts +5 -0
  13. package/dropdown/dropdown.component.d.ts +3 -0
  14. package/esm2022/calendar/calendar.component.mjs +288 -217
  15. package/esm2022/calendar/models/calendar-date.model.mjs +2 -0
  16. package/esm2022/calendar/models/calendar-month.model.mjs +2 -0
  17. package/esm2022/calendar/models/calendar-selected.model.mjs +2 -0
  18. package/esm2022/calendar/models/calendar-year.model.mjs +2 -0
  19. package/esm2022/calendar/models/date-range.model.mjs +1 -1
  20. package/esm2022/calendar/models/enums/calendar-view.enum.mjs +7 -0
  21. package/esm2022/calendar/models/public-api.mjs +6 -1
  22. package/esm2022/calendar/upd-calendar.module.mjs +2 -20
  23. package/esm2022/card/public-api.mjs +2 -1
  24. package/esm2022/card/types/public-api.mjs +6 -0
  25. package/esm2022/dropdown/dropdown.component.mjs +23 -7
  26. package/esm2022/form-controls/date-picker/date-picker.component.mjs +45 -60
  27. package/esm2022/form-controls/input/input.component.mjs +3 -3
  28. package/esm2022/popover/popover.directive.mjs +10 -26
  29. package/esm2022/pricing-cards/index.mjs +2 -0
  30. package/esm2022/pricing-cards/models/price-item-feature.model.mjs +2 -0
  31. package/esm2022/pricing-cards/models/price-item-features-group.model.mjs +2 -0
  32. package/esm2022/pricing-cards/models/price-item.model.mjs +2 -0
  33. package/esm2022/pricing-cards/models/public-api.mjs +4 -0
  34. package/esm2022/pricing-cards/pricing-cards.component.mjs +24 -0
  35. package/esm2022/pricing-cards/public-api.mjs +4 -0
  36. package/esm2022/pricing-cards/upd-pricing-cards.module.mjs +36 -0
  37. package/esm2022/pricing-cards/updevs-components-pricing-cards.mjs +5 -0
  38. package/esm2022/table/abstractions/base.column.mjs +30 -44
  39. package/esm2022/table/table.component.mjs +9 -12
  40. package/fesm2022/updevs-components-calendar.mjs +319 -1449
  41. package/fesm2022/updevs-components-calendar.mjs.map +1 -1
  42. package/fesm2022/updevs-components-dropdown.mjs +22 -6
  43. package/fesm2022/updevs-components-dropdown.mjs.map +1 -1
  44. package/fesm2022/updevs-components-form-controls-date-picker.mjs +44 -59
  45. package/fesm2022/updevs-components-form-controls-date-picker.mjs.map +1 -1
  46. package/fesm2022/updevs-components-form-controls-input.mjs +2 -2
  47. package/fesm2022/updevs-components-form-controls-input.mjs.map +1 -1
  48. package/fesm2022/updevs-components-popover.mjs +9 -25
  49. package/fesm2022/updevs-components-popover.mjs.map +1 -1
  50. package/fesm2022/updevs-components-pricing-cards.mjs +64 -0
  51. package/fesm2022/updevs-components-pricing-cards.mjs.map +1 -0
  52. package/fesm2022/updevs-components-table.mjs +38 -55
  53. package/fesm2022/updevs-components-table.mjs.map +1 -1
  54. package/form-controls/date-picker/date-picker.component.d.ts +10 -31
  55. package/package.json +37 -31
  56. package/pricing-cards/index.d.ts +1 -0
  57. package/pricing-cards/models/price-item-feature.model.d.ts +5 -0
  58. package/pricing-cards/models/price-item-features-group.model.d.ts +5 -0
  59. package/pricing-cards/models/price-item.model.d.ts +19 -0
  60. package/pricing-cards/models/public-api.d.ts +3 -0
  61. package/pricing-cards/pricing-cards.component.d.ts +11 -0
  62. package/pricing-cards/public-api.d.ts +3 -0
  63. package/pricing-cards/upd-pricing-cards.module.d.ts +11 -0
  64. package/table/abstractions/base.column.d.ts +4 -0
  65. package/calendar/components/calendar-body/calendar-body.component.d.ts +0 -263
  66. package/calendar/components/calendar-header/calendar-header.component.d.ts +0 -57
  67. package/calendar/components/calendar-month-view/calendar-month-view.component.d.ts +0 -151
  68. package/calendar/components/calendar-multi-year-view/calendar-multi-year-view.component.d.ts +0 -10
  69. package/calendar/components/calendar-year-view/calendar-year-view.component.d.ts +0 -9
  70. package/calendar/models/calendar-cell.model.d.ts +0 -16
  71. package/calendar/services/calendar.service.d.ts +0 -15
  72. package/calendar/tools/events-options.d.ts +0 -12
  73. package/calendar/tools/keyboard-tools.d.ts +0 -9
  74. package/calendar/tools/passive-listeners.d.ts +0 -16
  75. package/calendar/types/calendar-cell-class-function.type.d.ts +0 -3
  76. package/calendar/types/calendar-cell-css-classes.type.d.ts +0 -3
  77. package/calendar/types/modifier-key.type.d.ts +0 -1
  78. package/esm2022/calendar/components/calendar-body/calendar-body.component.mjs +0 -515
  79. package/esm2022/calendar/components/calendar-header/calendar-header.component.mjs +0 -144
  80. package/esm2022/calendar/components/calendar-month-view/calendar-month-view.component.mjs +0 -423
  81. package/esm2022/calendar/components/calendar-multi-year-view/calendar-multi-year-view.component.mjs +0 -18
  82. package/esm2022/calendar/components/calendar-year-view/calendar-year-view.component.mjs +0 -17
  83. package/esm2022/calendar/models/calendar-cell.model.mjs +0 -18
  84. package/esm2022/calendar/services/calendar.service.mjs +0 -40
  85. package/esm2022/calendar/tools/events-options.mjs +0 -14
  86. package/esm2022/calendar/tools/keyboard-tools.mjs +0 -14
  87. package/esm2022/calendar/tools/passive-listeners.mjs +0 -29
  88. package/esm2022/calendar/types/calendar-cell-class-function.type.mjs +0 -2
  89. package/esm2022/calendar/types/calendar-cell-css-classes.type.mjs +0 -2
  90. package/esm2022/calendar/types/modifier-key.type.mjs +0 -2
@@ -1,16 +1,11 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, Injectable, signal, EventEmitter, Injector, ElementRef, NgZone, afterNextRender, Output, Input, Component, ChangeDetectorRef, effect, ViewChild, NgModule } from '@angular/core';
2
+ import { inject, Injectable, input, output, signal, computed, Component, NgModule } from '@angular/core';
3
3
  import { DateService } from '@updevs/sdk/core';
4
- import { BaseComponent } from '@updevs/sdk/layout';
5
- import * as i1$2 from '@jsverse/transloco';
6
- import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
7
- import { startWith } from 'rxjs';
8
- import * as i1 from '@angular/common';
9
4
  import { CommonModule } from '@angular/common';
10
- import * as i1$1 from '@updevs/components/button';
11
5
  import { UpdButtonModule } from '@updevs/components/button';
12
- import * as i3 from '@updevs/icons';
13
6
  import { UpdIconsModule } from '@updevs/icons';
7
+ import * as i1 from '@jsverse/transloco';
8
+ import { TranslocoDirective } from '@jsverse/transloco';
14
9
 
15
10
  /**
16
11
  * A class representing a range of dates.
@@ -30,6 +25,13 @@ class DateRangeModel {
30
25
  }
31
26
  }
32
27
 
28
+ var CalendarViewEnum;
29
+ (function (CalendarViewEnum) {
30
+ CalendarViewEnum["Days"] = "days";
31
+ CalendarViewEnum["Months"] = "months";
32
+ CalendarViewEnum["Years"] = "years";
33
+ })(CalendarViewEnum || (CalendarViewEnum = {}));
34
+
33
35
  /**
34
36
  * Provides the default date range selection behavior.
35
37
  */
@@ -125,1484 +127,319 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
125
127
  type: Injectable
126
128
  }] });
127
129
 
128
- class PassiveListeners {
129
- /**
130
- * Checks whether the user's browser supports passive event listeners.
131
- * See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
132
- */
133
- static supportsPassiveEventListeners() {
134
- if (this.supportsPassiveEvents === undefined && typeof window !== 'undefined') {
135
- try {
136
- window.addEventListener('test', undefined, Object.defineProperty({}, 'passive', {
137
- get: () => (this.supportsPassiveEvents = true)
138
- }));
139
- }
140
- finally {
141
- this.supportsPassiveEvents = this.supportsPassiveEvents || false;
142
- }
143
- }
144
- return this.supportsPassiveEvents;
145
- }
146
- /**
147
- * Normalizes an `AddEventListener` object to something that can be passed
148
- * to `addEventListener` on any browser, no matter whether it supports the
149
- * `options` parameter.
150
- * @param options Object to be normalized.
151
- */
152
- static normalizePassiveListenerOptions(options) {
153
- return this.supportsPassiveEventListeners() ? options : !!options.capture;
154
- }
155
- }
156
-
157
- /**
158
- * Event options that can be used to bind an active, capturing event.
159
- */
160
- const activeCapturingEventOptions = PassiveListeners.normalizePassiveListenerOptions({ passive: false, capture: true });
161
- /**
162
- * Event options that can be used to bind a passive, capturing event.
163
- */
164
- const passiveCapturingEventOptions = PassiveListeners.normalizePassiveListenerOptions({ passive: true, capture: true });
165
- /**
166
- * Event options that can be used to bind a passive, non-capturing event.
167
- */
168
- const passiveEventOptions = PassiveListeners.normalizePassiveListenerOptions({ passive: true });
169
-
170
- class CalendarSignalsService {
171
- constructor() {
172
- this.minDateSignal = signal(undefined);
173
- this.maxDateSignal = signal(undefined);
174
- this.activeDateSignal = signal(new Date());
175
- this.selectedDateSignal = signal(undefined);
176
- this.currentViewSignal = signal('month');
177
- }
178
- get minDate() {
179
- return this.minDateSignal.asReadonly();
180
- }
181
- set minDate(value) {
182
- this.minDateSignal.set(value);
183
- }
184
- get maxDate() {
185
- return this.maxDateSignal.asReadonly();
186
- }
187
- set maxDate(value) {
188
- this.maxDateSignal.set(value);
189
- }
190
- get activeDate() {
191
- return this.activeDateSignal.asReadonly();
192
- }
193
- set activeDate(value) {
194
- this.activeDateSignal.set(value);
195
- }
196
- get selectedDate() {
197
- return this.selectedDateSignal.asReadonly();
198
- }
199
- set selectedDate(value) {
200
- this.selectedDateSignal.set(value);
201
- }
202
- get currentView() {
203
- return this.currentViewSignal.asReadonly();
204
- }
205
- set currentView(value) {
206
- this.currentViewSignal.set(value);
207
- }
208
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarSignalsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
209
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarSignalsService }); }
210
- }
211
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarSignalsService, decorators: [{
212
- type: Injectable
213
- }] });
214
-
215
- let calendarBodyId = 1;
216
- class CalendarBodyComponent {
217
- get startDateLabelId() {
218
- return `${this.id}-start-date`;
219
- }
220
- get endDateLabelId() {
221
- return `${this.id}-end-date`;
222
- }
223
- get colspanClass() {
224
- return `col-${this.firstRowOffset}`;
225
- }
226
- constructor() {
227
- /**
228
- * The number of columns in the table.
229
- */
230
- this.numCols = 7;
231
- /**
232
- * The cell number of the active cell in the table.
233
- */
234
- this.activeCell = 0;
235
- /**
236
- * Whether a range is being selected.
237
- */
238
- this.isRange = false;
239
- /**
240
- * The aspect ratio (width / height) to use for the cells in the table. This aspect ratio will be
241
- * maintained even as the table resizes.
242
- */
243
- this.cellAspectRatio = 1;
244
- /**
245
- * Emits when a new value is selected.
246
- */
247
- this.selectedValueChange = new EventEmitter();
248
- /**
249
- * Emits when the preview has changed as a result of a user action.
250
- */
251
- this.previewChange = new EventEmitter();
252
- /**
253
- * Emits the date at the possible start of a drag event.
254
- */
255
- this.dragStarted = new EventEmitter();
256
- /**
257
- * Emits the date at the conclusion of a drag, or null if mouse was not released on a date.
258
- */
259
- this.dragEnded = new EventEmitter();
260
- this.didDragSinceMouseDown = false;
261
- this.id = `upd-calendar-body-${calendarBodyId++}`;
262
- /**
263
- * Used to focus the active cell after change detection has run.
264
- */
265
- this.focusActiveCellAfterViewChecked = false;
266
- this.injector = inject(Injector);
267
- this.elementRef = inject((ElementRef));
268
- this.ngZone = inject(NgZone);
269
- this.calendarSignalsService = inject(CalendarSignalsService);
270
- this.dateService = inject(DateService);
271
- /**
272
- * Tracking function for rows based on their identity. Ideally we would use some sort of
273
- * key on the row, but that would require a breaking change for the `rows` input. We don't
274
- * use the built-in identity tracking, because it logs warnings.
275
- */
276
- this.trackRow = (row) => row;
277
- /**
278
- * Event handler for when the user enters an element
279
- * inside the calendar body (e.g. by hovering in or focus).
280
- */
281
- this.enterHandler = (event) => {
282
- if (this.skipNextFocus && event.type === 'focus') {
283
- this.skipNextFocus = false;
284
- return;
285
- }
286
- // We only need to hit the zone when we're selecting a range.
287
- if (!!event.target && this.isRange) {
288
- const cell = this.getCellFromElement(event.target);
289
- if (cell) {
290
- this.ngZone.run(() => this.previewChange.emit({ value: cell.isEnabled ? cell : undefined, event }));
291
- }
292
- }
293
- };
294
- this.touchmoveHandler = (event) => {
295
- if (!this.isRange) {
296
- return;
297
- }
298
- const target = this.getActualTouchTarget(event);
299
- const cell = target ? this.getCellFromElement(target) : undefined;
300
- if (target !== event.target) {
301
- this.didDragSinceMouseDown = true;
302
- }
303
- // If the initial target of the touch is a date cell, prevent default so
304
- // that the move is not handled as a scroll.
305
- if (this.getCellElement(event.target)) {
306
- event.preventDefault();
307
- }
308
- this.ngZone.run(() => this.previewChange.emit({ value: cell?.isEnabled ? cell : undefined, event }));
309
- };
310
- /**
311
- * Event handler for when the user's pointer leaves an element
312
- * inside the calendar body (e.g. by hovering out or blurring).
313
- */
314
- this.leaveHandler = (event) => {
315
- // We only need to hit the zone when we're selecting a range.
316
- if (this.previewEnd !== undefined && this.isRange) {
317
- if (event.type !== 'blur') {
318
- this.didDragSinceMouseDown = true;
319
- }
320
- // Only reset the preview end value when leaving cells. This looks better, because
321
- // we have a gap between the cells and the rows and we don't want to remove the
322
- // range just for it to show up again when the user moves a few pixels to the side.
323
- if (event.target &&
324
- this.getCellFromElement(event.target) &&
325
- !(event.relatedTarget &&
326
- this.getCellFromElement(event.relatedTarget))) {
327
- this.ngZone.run(() => this.previewChange.emit({ value: undefined, event }));
328
- }
329
- }
330
- };
331
- /**
332
- * Triggered on mousedown or touchstart on a date cell.
333
- * Responsible for starting a drag sequence.
334
- */
335
- this.mousedownHandler = (event) => {
336
- if (!this.isRange) {
337
- return;
338
- }
339
- this.didDragSinceMouseDown = false;
340
- // Begin a drag if a cell within the current range was targeted.
341
- const cell = event.target && this.getCellFromElement(event.target);
342
- if (!cell || !this.isInRange(cell.compareValue)) {
343
- return;
344
- }
345
- this.ngZone.run(() => {
346
- this.dragStarted.emit({
347
- value: cell.rawValue,
348
- event
349
- });
350
- });
351
- };
352
- /** Triggered on mouseup anywhere. Responsible for ending a drag sequence. */
353
- this.mouseupHandler = (event) => {
354
- if (!this.isRange) {
355
- return;
356
- }
357
- const cellElement = this.getCellElement(event.target);
358
- if (!cellElement) {
359
- // Mouseup happened outside of datepicker. Cancel drag.
360
- this.ngZone.run(() => {
361
- this.dragEnded.emit({ value: undefined, event });
362
- });
363
- return;
364
- }
365
- if (cellElement.closest('.upd-calendar-body') !== this.elementRef.nativeElement) {
366
- // Mouseup happened inside a different month instance.
367
- // Allow it to handle the event.
368
- return;
369
- }
370
- this.ngZone.run(() => {
371
- const cell = this.getCellFromElement(cellElement);
372
- this.dragEnded.emit({ value: cell?.rawValue ?? undefined, event });
373
- });
374
- };
375
- /** Triggered on touchend anywhere. Respsonsible for ending a drag sequence. */
376
- this.touchendHandler = (event) => {
377
- const target = this.getActualTouchTarget(event);
378
- if (target) {
379
- this.mouseupHandler({ target });
380
- }
381
- };
382
- this.ngZone.runOutsideAngular(() => {
383
- const element = this.elementRef.nativeElement;
384
- // `touchmove` is active since we need to call `preventDefault`.
385
- element.addEventListener('touchmove', this.touchmoveHandler, activeCapturingEventOptions);
386
- element.addEventListener('mouseenter', this.enterHandler, passiveCapturingEventOptions);
387
- element.addEventListener('focus', this.enterHandler, passiveCapturingEventOptions);
388
- element.addEventListener('mouseleave', this.leaveHandler, passiveCapturingEventOptions);
389
- element.addEventListener('blur', this.leaveHandler, passiveCapturingEventOptions);
390
- element.addEventListener('mousedown', this.mousedownHandler, passiveEventOptions);
391
- element.addEventListener('touchstart', this.mousedownHandler, passiveEventOptions);
392
- window.addEventListener('mouseup', this.mouseupHandler);
393
- window.addEventListener('touchend', this.touchendHandler);
394
- });
395
- }
396
- ngAfterViewChecked() {
397
- if (this.focusActiveCellAfterViewChecked) {
398
- this.focusActiveCell();
399
- this.focusActiveCellAfterViewChecked = false;
400
- }
401
- }
402
- ngOnChanges(changes) {
403
- const columnChanges = changes['numCols'];
404
- const { rows, numCols } = this;
405
- if (changes['rows'] || columnChanges) {
406
- this.firstRowOffset = rows && rows.length && rows[0].length ? numCols - rows[0].length : 0;
407
- }
408
- if (changes['cellAspectRatio'] || columnChanges || !this.cellPadding) {
409
- this.cellPadding = `${(50 * this.cellAspectRatio) / numCols}%`;
410
- }
411
- if (columnChanges || !this.cellWidth) {
412
- this.cellWidth = `${100 / numCols}%`;
413
- }
414
- }
415
- ngOnDestroy() {
416
- const element = this.elementRef.nativeElement;
417
- element.removeEventListener('touchmove', this.touchmoveHandler, activeCapturingEventOptions);
418
- element.removeEventListener('mouseenter', this.enterHandler, passiveCapturingEventOptions);
419
- element.removeEventListener('focus', this.enterHandler, passiveCapturingEventOptions);
420
- element.removeEventListener('mouseleave', this.leaveHandler, passiveCapturingEventOptions);
421
- element.removeEventListener('blur', this.leaveHandler, passiveCapturingEventOptions);
422
- element.removeEventListener('mousedown', this.mousedownHandler, passiveEventOptions);
423
- element.removeEventListener('touchstart', this.mousedownHandler, passiveEventOptions);
424
- window.removeEventListener('mouseup', this.mouseupHandler);
425
- window.removeEventListener('touchend', this.touchendHandler);
426
- }
427
- getIndex(colIndex, rowIndex) {
428
- return `${colIndex}${rowIndex}`;
429
- }
430
- onDateEnter(index) {
431
- this.hoveredDate = index;
432
- }
433
- onDateLeave() {
434
- this.hoveredDate = undefined;
435
- }
436
- /**
437
- * Called when a cell is clicked.
438
- * @param cell
439
- * @param event
440
- */
441
- cellClicked(cell, event) {
442
- // Ignore "clicks" that are actually canceled drags (eg the user dragged off and then went back to this cell to undo).
443
- if (this.didDragSinceMouseDown) {
444
- return;
445
- }
446
- if (cell.isEnabled) {
447
- this.selectedValueChange.emit({ value: cell.value, event });
448
- }
449
- }
450
- emitActiveDateChange(cell) {
451
- if (cell.isEnabled) {
452
- const month = cell.value;
453
- const oldActiveDate = this.calendarSignalsService.activeDate();
454
- const tempDate = this.getDateFromDayOfMonth(oldActiveDate, month);
455
- if (this.dateService.compare(oldActiveDate, tempDate)) {
456
- this.calendarSignalsService.activeDate = tempDate;
457
- }
458
- }
459
- }
460
- /**
461
- * Returns whether a cell should be marked as selected.
462
- * @param value
463
- */
464
- isSelected(value) {
465
- return this.startValue === value || this.endValue === value;
466
- }
467
- /**
468
- * Returns whether a cell is active.
469
- * @param rowIndex
470
- * @param colIndex
471
- */
472
- isActiveCell(rowIndex, colIndex) {
473
- let cellNumber = rowIndex * this.numCols + colIndex;
474
- // Account for the fact that the first row may not have as many cells.
475
- if (rowIndex) {
476
- cellNumber -= this.firstRowOffset;
477
- }
478
- return cellNumber === this.activeCell;
479
- }
480
- /**
481
- * Focuses the active cell after the microtask queue is empty.
482
- *
483
- * Adding a 0ms setTimeout seems to fix Voiceover losing focus when pressing PageUp/PageDown
484
- * (issue #24330).
485
- *
486
- * Determined a 0ms by gradually increasing duration from 0 and testing two use cases with screen
487
- * reader enabled:
488
- *
489
- * 1. Pressing PageUp/PageDown repeatedly with pausing between each key press.
490
- * 2. Pressing and holding the PageDown key with repeated keys enabled.
491
- *
492
- * Test 1 worked roughly 95-99% of the time with 0ms and got a little bit better as the duration
493
- * increased. Test 2 got slightly better until the duration was long enough to interfere with
494
- * repeated keys. If the repeated key speed was faster than the timeout duration, then pressing
495
- * and holding pagedown caused the entire page to scroll.
496
- *
497
- * Since repeated key speed can verify across machines, determined that any duration could
498
- * potentially interfere with repeated keys. 0ms would be best because it almost entirely
499
- * eliminates the focus being lost in Voiceover (#24330) without causing unintended side effects.
500
- * Adding delay also complicates writing tests.
501
- */
502
- focusActiveCell(movePreview = true) {
503
- afterNextRender(() => {
504
- setTimeout(() => {
505
- const activeCell = this.elementRef.nativeElement.querySelector('.upd-calendar-body-active');
506
- if (!!activeCell) {
507
- if (!movePreview) {
508
- this.skipNextFocus = true;
509
- }
510
- activeCell.focus();
511
- }
512
- });
513
- }, { injector: this.injector });
514
- }
515
- /**
516
- * Focuses the active cell after change detection has run and the microtask queue is empty.
517
- */
518
- scheduleFocusActiveCellAfterViewChecked() {
519
- this.focusActiveCellAfterViewChecked = true;
520
- }
521
- /**
522
- * Gets whether a value is the start of the main range.
523
- * @param value
524
- */
525
- isRangeStart(value) {
526
- return this.isStart(value, this.startValue, this.endValue);
527
- }
528
- /** Gets whether a value is the end of the main range. */
529
- isRangeEnd(value) {
530
- return this.isEnd(value, this.startValue, this.endValue);
531
- }
532
- /** Gets whether a value is within the currently-selected range. */
533
- isInRange(value) {
534
- return this.isInsideRange(value, this.startValue, this.endValue, this.isRange);
535
- }
536
- /** Gets whether a value is the start of the comparison range. */
537
- isComparisonStart(value) {
538
- return this.isStart(value, this.comparisonStart, this.comparisonEnd);
539
- }
540
- /** Whether the cell is a start bridge cell between the main and comparison ranges. */
541
- isComparisonBridgeStart(value, rowIndex, colIndex) {
542
- if (!this.isComparisonStart(value) || this.isRangeStart(value) || !this.isInRange(value)) {
543
- return false;
544
- }
545
- let previousCell = this.rows[rowIndex][colIndex - 1];
546
- if (!previousCell) {
547
- const previousRow = this.rows[rowIndex - 1];
548
- previousCell = previousRow && previousRow[previousRow.length - 1];
549
- }
550
- return previousCell && !this.isRangeEnd(previousCell.compareValue);
551
- }
552
- /** Whether the cell is an end bridge cell between the main and comparison ranges. */
553
- isComparisonBridgeEnd(value, rowIndex, colIndex) {
554
- if (!this.isComparisonEnd(value) || this.isRangeEnd(value) || !this.isInRange(value)) {
555
- return false;
556
- }
557
- let nextCell = this.rows[rowIndex][colIndex + 1];
558
- if (!nextCell) {
559
- const nextRow = this.rows[rowIndex + 1];
560
- nextCell = nextRow && nextRow[0];
561
- }
562
- return nextCell && !this.isRangeStart(nextCell.compareValue);
563
- }
564
- /** Gets whether a value is the end of the comparison range. */
565
- isComparisonEnd(value) {
566
- return this.isEnd(value, this.comparisonStart, this.comparisonEnd);
567
- }
568
- /** Gets whether a value is within the current comparison range. */
569
- isInComparisonRange(value) {
570
- return this.isInsideRange(value, this.comparisonStart, this.comparisonEnd, this.isRange);
571
- }
572
- /**
573
- * Gets whether a value is the same as the start and end of the comparison range.
574
- * For context, the functions that we use to determine whether something is the start/end of
575
- * a range don't allow for the start and end to be on the same day, because we'd have to use
576
- * much more specific CSS selectors to style them correctly in all scenarios. This is fine for
577
- * the regular range, because when it happens, the selected styles take over and still show where
578
- * the range would've been, however we don't have these selected styles for a comparison range.
579
- * This function is used to apply a class that serves the same purpose as the one for selected
580
- * dates, but it only applies in the context of a comparison range.
581
- */
582
- isComparisonIdentical(value) {
583
- // Note that we don't need to null check the start/end
584
- // here, because the `value` will always be defined.
585
- return this.comparisonStart === this.comparisonEnd && value === this.comparisonStart;
586
- }
587
- /** Gets whether a value is the start of the preview range. */
588
- isPreviewStart(value) {
589
- return this.isStart(value, this.previewStart, this.previewEnd);
590
- }
591
- /** Gets whether a value is the end of the preview range. */
592
- isPreviewEnd(value) {
593
- return this.isEnd(value, this.previewStart, this.previewEnd);
594
- }
595
- /** Gets whether a value is inside the preview range. */
596
- isInPreview(value) {
597
- // console.log('is in range', this.isInsideRange(value, this.previewStart, this.previewEnd, this.isRange));
598
- return this.isInsideRange(value, this.previewStart, this.previewEnd, this.isRange);
599
- }
600
- /** Gets ids of aria descriptions for the start and end of a date range. */
601
- getDescribedby(value) {
602
- if (!this.isRange) {
603
- return undefined;
604
- }
605
- if (this.startValue === value && this.endValue === value) {
606
- return `${this.startDateLabelId} ${this.endDateLabelId}`;
607
- }
608
- else if (this.startValue === value) {
609
- return this.startDateLabelId;
610
- }
611
- else if (this.endValue === value) {
612
- return this.endDateLabelId;
613
- }
614
- return undefined;
615
- }
616
- /** Finds the CalendarCell that corresponds to a DOM node. */
617
- getCellFromElement(element) {
618
- const cell = this.getCellElement(element);
619
- if (!!cell) {
620
- const row = cell.getAttribute('data-upd-row');
621
- const col = cell.getAttribute('data-upd-col');
622
- if (row && col) {
623
- return this.rows[parseInt(row, 10)][parseInt(col, 10)];
624
- }
625
- }
626
- return undefined;
627
- }
628
- /** Checks whether a node is a table cell element. */
629
- isTableCell(node) {
630
- return node?.nodeName === 'DIV';
631
- }
632
- /**
633
- * Gets the date table cell element that is or contains the specified element.
634
- * Or returns null if element is not part of a date cell.
635
- */
636
- getCellElement(element) {
637
- let cell;
638
- if (this.isTableCell(element)) {
639
- cell = element;
640
- }
641
- else if (this.isTableCell(element.parentNode || undefined)) {
642
- cell = element.parentNode;
643
- }
644
- else if (this.isTableCell(element.parentNode?.parentNode || undefined)) {
645
- cell = element.parentNode.parentNode;
646
- }
647
- return cell?.getAttribute('data-upd-row') !== undefined ? cell : undefined;
648
- }
649
- /** Checks whether a value is the start of a range. */
650
- isStart(value, start, end) {
651
- return end !== undefined && start !== end && value < end && value === start;
652
- }
653
- /** Checks whether a value is the end of a range. */
654
- isEnd(value, start, end) {
655
- return start !== undefined && start !== end && value >= start && value === end;
656
- }
657
- /** Checks whether a value is inside of a range. */
658
- isInsideRange(value, start, end, rangeEnabled) {
659
- return (rangeEnabled && start !== undefined && end !== undefined && start !== end && value >= start && value <= end);
660
- }
661
- /**
662
- * Extracts the element that actually corresponds to a touch event's location
663
- * (rather than the element that initiated the sequence of touch events).
664
- */
665
- getActualTouchTarget(event) {
666
- const touchLocation = event.changedTouches[0];
667
- return document.elementFromPoint(touchLocation.clientX, touchLocation.clientY);
668
- }
669
- /**
670
- * Takes a day of the month and returns a new date in the same month and year as the currently
671
- * active date. The returned date will have the same day of the month as the argument date.
672
- */
673
- getDateFromDayOfMonth(activeDate, dayOfMonth) {
674
- return this.dateService.create(activeDate.getFullYear(), activeDate.getMonth(), dayOfMonth);
675
- }
676
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarBodyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
677
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.1.0", type: CalendarBodyComponent, selector: "upd-calendar-body", inputs: { label: "label", rows: "rows", todayValue: "todayValue", startValue: "startValue", endValue: "endValue", labelMinRequiredCells: "labelMinRequiredCells", numCols: "numCols", activeCell: "activeCell", isRange: "isRange", cellAspectRatio: "cellAspectRatio", comparisonStart: "comparisonStart", comparisonEnd: "comparisonEnd", previewStart: "previewStart", previewEnd: "previewEnd", startDateAccessibleName: "startDateAccessibleName", endDateAccessibleName: "endDateAccessibleName" }, outputs: { selectedValueChange: "selectedValueChange", previewChange: "previewChange", dragStarted: "dragStarted", dragEnded: "dragEnded" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"container\">\n <!--\n If there's not enough space in the first row, create a separate label row. We mark this row as\n aria-hidden because we don't want it to be read out as one of the weeks in the month.\n -->\n @if (firstRowOffset < labelMinRequiredCells) {\n <div class=\"row seven-cols\" aria-hidden=\"true\">\n <div class=\"col-7 p-1 d-flex align-items-center\" [style.min-height.px]=\"40\">\n {{ label }}\n </div>\n </div>\n }\n\n <!-- Create the first row separately so we can include a special spacer cell. -->\n @for (row of rows; track trackRow(row); let rowIndex = $index) {\n <div class=\"row seven-cols\">\n <!--\n This cell is purely decorative, but we can't put `aria-hidden` or `role=\"presentation\"` on it,\n because it throws off the week days for the rest of the row on NVDA. The aspect ratio of the\n table cells is maintained by setting the top and bottom padding as a percentage of the width\n (a variant of the trick described here: https://www.w3schools.com/howto/howto_css_aspect_ratio.asp).\n -->\n @if (rowIndex === 0 && firstRowOffset) {\n <div [ngClass]=\"colspanClass\" class=\"p-1 d-flex align-items-center\" [style.min-height.px]=\"40\">\n {{ firstRowOffset >= labelMinRequiredCells ? label : '' }}\n </div>\n }\n\n <!--\n Each gridcell in the calendar contains a button, which signals to assistive technology that the\n cell is interactable, as well as the selection state via `aria-pressed`. See #23476 for\n background.\n -->\n @for (item of row; track item.id; let colIndex = $index) {\n <div class=\"col-1 justify-content-center p-1 d-flex align-items-center cursor-pointer\" [attr.data-upd-row]=\"rowIndex\"\n [attr.data-upd-col]=\"colIndex\" [style.min-height.px]=\"40\"\n [class.bg-secondary-lt]=\"hoveredDate === getIndex(rowIndex, colIndex)\" [class.border]=\"isActiveCell(rowIndex, colIndex)\"\n [class.border-secondary]=\"isActiveCell(rowIndex, colIndex)\" [class.bg-azure]=\"isSelected(item.compareValue)\"\n [class.text-azure-fg]=\"isSelected(item.compareValue)\" [tabindex]=\"isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n [class.upd-calendar-body-disabled]=\"!item.isEnabled\" [class.bg-danger]=\"isRangeStart(item.compareValue)\"\n [class.text-danger-fg]=\"isRangeStart(item.compareValue)\" [class.bg-muted]=\"isRangeEnd(item.compareValue)\"\n [class.text-muted-fg]=\"isRangeEnd(item.compareValue)\"\n [class.upd-calendar-body-range-end]=\"isRangeEnd(item.compareValue)\"\n [class.upd-calendar-body-in-range]=\"isInRange(item.compareValue)\"\n [class.upd-calendar-body-comparison-bridge-start]=\"isComparisonBridgeStart(item.compareValue, rowIndex, colIndex)\"\n [class.upd-calendar-body-comparison-bridge-end]=\"isComparisonBridgeEnd(item.compareValue, rowIndex, colIndex)\"\n [class.upd-calendar-body-comparison-start]=\"isComparisonStart(item.compareValue)\"\n [class.upd-calendar-body-comparison-end]=\"isComparisonEnd(item.compareValue)\"\n [class.upd-calendar-body-in-comparison-range]=\"isInComparisonRange(item.compareValue)\"\n [class.rounded-start]=\"isPreviewStart(item.compareValue)\" [class.rounded-end]=\"isPreviewEnd(item.compareValue)\"\n [class.border-top]=\"isInPreview(item.compareValue)\" [class.fw-bold]=\"isInPreview(item.compareValue)\"\n [class.border-bottom]=\"isInPreview(item.compareValue)\" [class.border-secondary-lt]=\"isInPreview(item.compareValue)\"\n [attr.aria-label]=\"item.ariaLabel\" [attr.aria-disabled]=\"!item.isEnabled || undefined\"\n [attr.aria-pressed]=\"isSelected(item.compareValue)\"\n [attr.aria-current]=\"todayValue === item.compareValue ? 'date' : undefined\"\n [attr.aria-describedby]=\"getDescribedby(item.compareValue)\" (click)=\"cellClicked(item, $event)\"\n (focus)=\"emitActiveDateChange(item)\" (mouseenter)=\"onDateEnter(getIndex(rowIndex, colIndex))\"\n (mouseleave)=\"onDateLeave()\">\n <span [class.upd-calendar-body-selected]=\"isSelected(item.compareValue)\"\n [class.upd-calendar-body-comparison-identical]=\"isComparisonIdentical(item.compareValue)\"\n [class.upd-calendar-body-today]=\"todayValue === item.compareValue\">\n {{ item.displayValue }}\n </span>\n </div>\n }\n </div>\n }\n\n <label [id]=\"startDateLabelId\" class=\"upd-calendar-body-hidden-label\">\n {{ startDateAccessibleName }}\n </label>\n <label [id]=\"endDateLabelId\" class=\"upd-calendar-body-hidden-label\">\n {{ endDateAccessibleName }}\n </label>\n</div>\n", styles: ["@media (min-width: 768px){.seven-cols .col-1{width:100%}.seven-cols .col-6{width:85.7142857143%}}@media (min-width: 992px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}@media (min-width: 1200px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
678
- }
679
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarBodyComponent, decorators: [{
680
- type: Component,
681
- args: [{ selector: 'upd-calendar-body', template: "<div class=\"container\">\n <!--\n If there's not enough space in the first row, create a separate label row. We mark this row as\n aria-hidden because we don't want it to be read out as one of the weeks in the month.\n -->\n @if (firstRowOffset < labelMinRequiredCells) {\n <div class=\"row seven-cols\" aria-hidden=\"true\">\n <div class=\"col-7 p-1 d-flex align-items-center\" [style.min-height.px]=\"40\">\n {{ label }}\n </div>\n </div>\n }\n\n <!-- Create the first row separately so we can include a special spacer cell. -->\n @for (row of rows; track trackRow(row); let rowIndex = $index) {\n <div class=\"row seven-cols\">\n <!--\n This cell is purely decorative, but we can't put `aria-hidden` or `role=\"presentation\"` on it,\n because it throws off the week days for the rest of the row on NVDA. The aspect ratio of the\n table cells is maintained by setting the top and bottom padding as a percentage of the width\n (a variant of the trick described here: https://www.w3schools.com/howto/howto_css_aspect_ratio.asp).\n -->\n @if (rowIndex === 0 && firstRowOffset) {\n <div [ngClass]=\"colspanClass\" class=\"p-1 d-flex align-items-center\" [style.min-height.px]=\"40\">\n {{ firstRowOffset >= labelMinRequiredCells ? label : '' }}\n </div>\n }\n\n <!--\n Each gridcell in the calendar contains a button, which signals to assistive technology that the\n cell is interactable, as well as the selection state via `aria-pressed`. See #23476 for\n background.\n -->\n @for (item of row; track item.id; let colIndex = $index) {\n <div class=\"col-1 justify-content-center p-1 d-flex align-items-center cursor-pointer\" [attr.data-upd-row]=\"rowIndex\"\n [attr.data-upd-col]=\"colIndex\" [style.min-height.px]=\"40\"\n [class.bg-secondary-lt]=\"hoveredDate === getIndex(rowIndex, colIndex)\" [class.border]=\"isActiveCell(rowIndex, colIndex)\"\n [class.border-secondary]=\"isActiveCell(rowIndex, colIndex)\" [class.bg-azure]=\"isSelected(item.compareValue)\"\n [class.text-azure-fg]=\"isSelected(item.compareValue)\" [tabindex]=\"isActiveCell(rowIndex, colIndex) ? 0 : -1\"\n [class.upd-calendar-body-disabled]=\"!item.isEnabled\" [class.bg-danger]=\"isRangeStart(item.compareValue)\"\n [class.text-danger-fg]=\"isRangeStart(item.compareValue)\" [class.bg-muted]=\"isRangeEnd(item.compareValue)\"\n [class.text-muted-fg]=\"isRangeEnd(item.compareValue)\"\n [class.upd-calendar-body-range-end]=\"isRangeEnd(item.compareValue)\"\n [class.upd-calendar-body-in-range]=\"isInRange(item.compareValue)\"\n [class.upd-calendar-body-comparison-bridge-start]=\"isComparisonBridgeStart(item.compareValue, rowIndex, colIndex)\"\n [class.upd-calendar-body-comparison-bridge-end]=\"isComparisonBridgeEnd(item.compareValue, rowIndex, colIndex)\"\n [class.upd-calendar-body-comparison-start]=\"isComparisonStart(item.compareValue)\"\n [class.upd-calendar-body-comparison-end]=\"isComparisonEnd(item.compareValue)\"\n [class.upd-calendar-body-in-comparison-range]=\"isInComparisonRange(item.compareValue)\"\n [class.rounded-start]=\"isPreviewStart(item.compareValue)\" [class.rounded-end]=\"isPreviewEnd(item.compareValue)\"\n [class.border-top]=\"isInPreview(item.compareValue)\" [class.fw-bold]=\"isInPreview(item.compareValue)\"\n [class.border-bottom]=\"isInPreview(item.compareValue)\" [class.border-secondary-lt]=\"isInPreview(item.compareValue)\"\n [attr.aria-label]=\"item.ariaLabel\" [attr.aria-disabled]=\"!item.isEnabled || undefined\"\n [attr.aria-pressed]=\"isSelected(item.compareValue)\"\n [attr.aria-current]=\"todayValue === item.compareValue ? 'date' : undefined\"\n [attr.aria-describedby]=\"getDescribedby(item.compareValue)\" (click)=\"cellClicked(item, $event)\"\n (focus)=\"emitActiveDateChange(item)\" (mouseenter)=\"onDateEnter(getIndex(rowIndex, colIndex))\"\n (mouseleave)=\"onDateLeave()\">\n <span [class.upd-calendar-body-selected]=\"isSelected(item.compareValue)\"\n [class.upd-calendar-body-comparison-identical]=\"isComparisonIdentical(item.compareValue)\"\n [class.upd-calendar-body-today]=\"todayValue === item.compareValue\">\n {{ item.displayValue }}\n </span>\n </div>\n }\n </div>\n }\n\n <label [id]=\"startDateLabelId\" class=\"upd-calendar-body-hidden-label\">\n {{ startDateAccessibleName }}\n </label>\n <label [id]=\"endDateLabelId\" class=\"upd-calendar-body-hidden-label\">\n {{ endDateAccessibleName }}\n </label>\n</div>\n", styles: ["@media (min-width: 768px){.seven-cols .col-1{width:100%}.seven-cols .col-6{width:85.7142857143%}}@media (min-width: 992px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}@media (min-width: 1200px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}\n"] }]
682
- }], ctorParameters: () => [], propDecorators: { label: [{
683
- type: Input
684
- }], rows: [{
685
- type: Input
686
- }], todayValue: [{
687
- type: Input
688
- }], startValue: [{
689
- type: Input
690
- }], endValue: [{
691
- type: Input
692
- }], labelMinRequiredCells: [{
693
- type: Input
694
- }], numCols: [{
695
- type: Input
696
- }], activeCell: [{
697
- type: Input
698
- }], isRange: [{
699
- type: Input
700
- }], cellAspectRatio: [{
701
- type: Input
702
- }], comparisonStart: [{
703
- type: Input
704
- }], comparisonEnd: [{
705
- type: Input
706
- }], previewStart: [{
707
- type: Input
708
- }], previewEnd: [{
709
- type: Input
710
- }], startDateAccessibleName: [{
711
- type: Input
712
- }], endDateAccessibleName: [{
713
- type: Input
714
- }], selectedValueChange: [{
715
- type: Output
716
- }], previewChange: [{
717
- type: Output
718
- }], dragStarted: [{
719
- type: Output
720
- }], dragEnded: [{
721
- type: Output
722
- }] } });
723
-
724
- let uniqueIdCounter$1 = 0;
725
- /**
726
- * An internal class that represents the data corresponding to a single calendar cell.
727
- * @docs-private
728
- */
729
- class CalendarCellModel {
730
- constructor(value, displayValue, ariaLabel, isEnabled, cssClasses = {}, compareValue = value, rawValue) {
731
- this.value = value;
732
- this.displayValue = displayValue;
733
- this.ariaLabel = ariaLabel;
734
- this.isEnabled = isEnabled;
735
- this.cssClasses = cssClasses;
736
- this.compareValue = compareValue;
737
- this.rawValue = rawValue;
738
- this.id = uniqueIdCounter$1++;
739
- }
740
- }
741
-
742
- class KeyboardTools {
743
- /**
744
- * Checks whether a modifier key is pressed.
745
- * @param event Event to be checked.
746
- * @param modifiers
747
- */
748
- static hasModifierKey(event, ...modifiers) {
749
- if (modifiers.length) {
750
- return modifiers.some(modifier => event[modifier]);
751
- }
752
- return event.altKey || event.shiftKey || event.ctrlKey || event.metaKey;
753
- }
754
- }
755
-
756
- const yearsPerPage = 24;
757
- class CalendarMultiYearViewComponent {
758
- /** Initializes this month view. */
759
- init() {
760
- }
761
- /** Focuses the active cell after the microtask queue is empty. */
762
- focusActiveCell(movePreview) {
763
- }
764
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarMultiYearViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
765
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.1.0", type: CalendarMultiYearViewComponent, selector: "upd-calendar-multi-year-view", ngImport: i0, template: "<p>calendar-multi-year-view works!</p>\n", styles: [""] }); }
766
- }
767
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarMultiYearViewComponent, decorators: [{
768
- type: Component,
769
- args: [{ selector: 'upd-calendar-multi-year-view', template: "<p>calendar-multi-year-view works!</p>\n" }]
770
- }] });
771
-
772
- class CalendarService {
773
- /**
774
- * When the multi-year view is first opened, the active year will be in view.
775
- * So we compute how many years are between the active year and the *slot* where our
776
- * "startingYear" will render when paged into view.
777
- */
778
- getMultiYearActiveOffset(activeDate, minDate, maxDate) {
779
- const activeYear = activeDate.getFullYear();
780
- return this.euclideanModulo(activeYear - this.getStartingYear(minDate, maxDate), yearsPerPage);
781
- }
782
- isSameMultiYearView(date1, date2, minDate, maxDate) {
783
- const year1 = date1.getFullYear();
784
- const year2 = date2.getFullYear();
785
- const startingYear = this.getStartingYear(minDate, maxDate);
786
- return Math.floor((year1 - startingYear) / yearsPerPage) === Math.floor((year2 - startingYear) / yearsPerPage);
787
- }
788
- getStartingYear(minDate, maxDate) {
789
- let startingYear = 0;
790
- if (maxDate) {
791
- const maxYear = maxDate.getFullYear();
792
- startingYear = maxYear - yearsPerPage + 1;
793
- }
794
- else if (minDate) {
795
- startingYear = minDate.getFullYear();
796
- }
797
- return startingYear;
798
- }
799
- euclideanModulo(a, b) {
800
- return ((a % b) + b) % b;
801
- }
802
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
803
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarService }); }
804
- }
805
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarService, decorators: [{
806
- type: Injectable
807
- }] });
808
-
809
- let calendarHeaderId = 1;
810
- class CalendarHeaderComponent {
811
- constructor() {
812
- this.id = `upd-calendar-header-${calendarHeaderId++}`;
813
- this.calendarService = inject(CalendarService);
814
- this.calendarSignalsService = inject(CalendarSignalsService);
815
- this.dateService = inject(DateService);
816
- }
817
- /**
818
- * The `aria-label` for changing the calendar view.
819
- */
820
- get periodButtonLabel() {
821
- return this.calendarSignalsService.currentView() === 'month' ? 'SwitchToMultiYearView' : 'SwitchToMonthView';
822
- }
823
- /**
824
- * The label for the previous button.
825
- */
826
- get prevButtonLabel() {
827
- return {
828
- 'month': 'PreviousMonth',
829
- 'year': 'PreviousYear',
830
- 'multi-year': 'PreviousMultiYear'
831
- }[this.calendarSignalsService.currentView()];
832
- }
833
- /**
834
- * The label for the next button.
835
- */
836
- get nextButtonLabel() {
837
- return {
838
- 'month': 'NextMonth',
839
- 'year': 'NextYear',
840
- 'multi-year': 'NextMultiYear'
841
- }[this.calendarSignalsService.currentView()];
842
- }
843
- /**
844
- * Whether the selected view is not a month.
845
- */
846
- get isNotMonth() {
847
- return this.calendarSignalsService.currentView() !== 'month';
848
- }
849
- /**
850
- * ID of the label of the period button.
851
- */
852
- get periodButtonLabelId() {
853
- return `${this.id}-period-label`;
854
- }
855
- get minYearOfPageLabel() {
856
- return this.dateService.getYearName(this.dateService.create(this.minYearOfPage, 0, 1));
857
- }
858
- get maxYearOfPageLabel() {
859
- const maxYearOfPage = this.minYearOfPage + yearsPerPage - 1;
860
- return this.dateService.getYearName(this.dateService.create(maxYearOfPage, 0, 1));
861
- }
862
- get periodText() {
863
- if (this.calendarSignalsService.currentView() === 'month') {
864
- return this.dateService
865
- .format(this.calendarSignalsService.activeDate(), { year: 'numeric', month: 'short' })
866
- .toLocaleUpperCase();
867
- }
868
- if (this.calendarSignalsService.currentView() === 'year') {
869
- return this.dateService.getYearName(this.calendarSignalsService.activeDate());
870
- }
871
- return '';
872
- }
873
- get minYearOfPage() {
874
- // The offset from the active year to the "slot" for the starting year is the
875
- // *actual* first rendered year in the multi-year view, and the last year is
876
- // just yearsPerPage - 1 away.
877
- const activeDate = this.calendarSignalsService.activeDate();
878
- const activeYear = activeDate.getFullYear();
879
- const offset = this.calendarService.getMultiYearActiveOffset(activeDate, this.calendarSignalsService.minDate(), this.calendarSignalsService.maxDate());
880
- return activeYear - offset;
881
- }
882
- /**
883
- * Handles user clicks on the period label.
884
- */
885
- currentPeriodClicked() {
886
- this.calendarSignalsService.currentView = this.calendarSignalsService.currentView() === 'month' ? 'multi-year' : 'month';
887
- }
888
- /**
889
- * Handles user clicks on the previous button.
890
- */
891
- previousClicked() {
892
- this.calendarSignalsService.activeDate =
893
- this.calendarSignalsService.currentView() === 'month'
894
- ? this.dateService.addMonths(this.calendarSignalsService.activeDate(), -1)
895
- : this.dateService.addYears(this.calendarSignalsService.activeDate(), this.calendarSignalsService.currentView() === 'year' ? -1 : -yearsPerPage);
896
- }
897
- /**
898
- * Handles user clicks on the next button.
899
- */
900
- nextClicked() {
901
- this.calendarSignalsService.activeDate =
902
- this.calendarSignalsService.currentView() === 'month'
903
- ? this.dateService.addMonths(this.calendarSignalsService.activeDate(), 1)
904
- : this.dateService.addYears(this.calendarSignalsService.activeDate(), this.calendarSignalsService.currentView() === 'year' ? 1 : yearsPerPage);
905
- }
906
- /**
907
- * Whether the previous period button is enabled.
908
- */
909
- previousEnabled() {
910
- if (!this.calendarSignalsService.minDate()) {
911
- return true;
912
- }
913
- return !this.calendarSignalsService.minDate()
914
- || !this.isSameView(this.calendarSignalsService.activeDate(), this.calendarSignalsService.minDate());
915
- }
916
- /**
917
- * Whether the next period button is enabled.
918
- */
919
- nextEnabled() {
920
- return !this.calendarSignalsService.maxDate()
921
- || !this.isSameView(this.calendarSignalsService.activeDate(), this.calendarSignalsService.maxDate());
922
- }
923
- /**
924
- * Whether the two dates represent the same view in the current view mode (month or year).
925
- */
926
- isSameView(date1, date2) {
927
- if (this.calendarSignalsService.currentView() === 'month') {
928
- return date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth();
929
- }
930
- if (this.calendarSignalsService.currentView() === 'year') {
931
- return date1.getFullYear() === date2.getFullYear();
932
- }
933
- // Otherwise we are in 'multi-year' view.
934
- return this.calendarService.isSameMultiYearView(date1, date2, this.calendarSignalsService.minDate(), this.calendarSignalsService.maxDate());
935
- }
936
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
937
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.1.0", type: CalendarHeaderComponent, selector: "upd-calendar-header", ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'UpDevs.Calendar'\">\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col d-flex\">\n <label [id]=\"periodButtonLabelId\" class=\"visually-hidden\" aria-live=\"polite\">\n {{ periodText || t('YearRangeLabel', { start: minYearOfPageLabel, end: maxYearOfPageLabel }) }}\n </label>\n\n <upd-button [isIcon]=\"true\" [iconModel]=\"{tablerIcon:'chevron-left'}\" [isDisabled]=\"!previousEnabled()\"\n (clicked)=\"previousClicked()\" [attr.aria-label]=\"t(prevButtonLabel)\">\n </upd-button>\n\n <upd-button (click)=\"currentPeriodClicked()\" [attr.aria-label]=\"t(periodButtonLabel)\"\n [attr.aria-describedby]=\"periodButtonLabelId\" customClasses=\"mx-1 flex-grow-1\">\n <span aria-hidden=\"true\">\n {{ periodText || t('YearRange', { start: minYearOfPageLabel, end: maxYearOfPageLabel }) }}\n </span>\n <upd-icon [tablerIcon]=\"!isNotMonth ? 'chevron-down' : 'chevron-up'\"></upd-icon>\n </upd-button>\n\n <upd-button [isIcon]=\"true\" [iconModel]=\"{tablerIcon:'chevron-right'}\" [isDisabled]=\"!nextEnabled()\"\n (clicked)=\"nextClicked()\" [attr.aria-label]=\"t(nextButtonLabel)\">\n </upd-button>\n </div>\n </div>\n </div>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "component", type: i1$1.ButtonComponent, selector: "upd-button", inputs: ["model", "text", "brandColorStyle", "customClasses", "isOutline", "isGhost", "isSquare", "isPill", "isIcon", "isLoading", "isList", "isFloating", "isAction", "isNavigationLink", "isLink", "shouldIgnoreBtnClass", "iconModel", "iconPosition", "isDisabled", "isActive", "badgeConfig", "colorStyle", "size", "title"], outputs: ["clicked"] }, { kind: "directive", type: i1$2.TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: i3.IconComponent, selector: "upd-icon", inputs: ["model", "wrapperClasses", "color", "colorClass", "removeDefaultClasses", "customClasses", "tablerIcon", "tablerIconWeight", "tablerIconType", "tablerIconSize", "heroIcon", "heroIconSize", "heroIconType"] }] }); }
938
- }
939
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarHeaderComponent, decorators: [{
940
- type: Component,
941
- args: [{ selector: 'upd-calendar-header', template: "<ng-container *transloco=\"let t; prefix: 'UpDevs.Calendar'\">\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col d-flex\">\n <label [id]=\"periodButtonLabelId\" class=\"visually-hidden\" aria-live=\"polite\">\n {{ periodText || t('YearRangeLabel', { start: minYearOfPageLabel, end: maxYearOfPageLabel }) }}\n </label>\n\n <upd-button [isIcon]=\"true\" [iconModel]=\"{tablerIcon:'chevron-left'}\" [isDisabled]=\"!previousEnabled()\"\n (clicked)=\"previousClicked()\" [attr.aria-label]=\"t(prevButtonLabel)\">\n </upd-button>\n\n <upd-button (click)=\"currentPeriodClicked()\" [attr.aria-label]=\"t(periodButtonLabel)\"\n [attr.aria-describedby]=\"periodButtonLabelId\" customClasses=\"mx-1 flex-grow-1\">\n <span aria-hidden=\"true\">\n {{ periodText || t('YearRange', { start: minYearOfPageLabel, end: maxYearOfPageLabel }) }}\n </span>\n <upd-icon [tablerIcon]=\"!isNotMonth ? 'chevron-down' : 'chevron-up'\"></upd-icon>\n </upd-button>\n\n <upd-button [isIcon]=\"true\" [iconModel]=\"{tablerIcon:'chevron-right'}\" [isDisabled]=\"!nextEnabled()\"\n (clicked)=\"nextClicked()\" [attr.aria-label]=\"t(nextButtonLabel)\">\n </upd-button>\n </div>\n </div>\n </div>\n</ng-container>\n" }]
942
- }] });
943
-
944
- const daysPerWeek = 7;
945
- let uniqueIdCounter = 0;
946
- class CalendarMonthViewComponent extends BaseComponent {
947
- /**
948
- * The date to display in this month view (everything other than the month and year is ignored).
949
- */
950
- get activeDate() {
951
- return this.calendarSignalsService.activeDate();
952
- }
953
- set activeDate(value) {
954
- this.updateActiveDate(value);
955
- }
956
- /** The currently selected date. */
957
- get selected() {
958
- return this.calendarSignalsService.selectedDate();
959
- }
960
- set selected(value) {
961
- let currValue;
962
- if (value instanceof DateRangeModel) {
963
- currValue = value;
964
- }
965
- else {
966
- currValue = this.dateService.getValidOrUndefined(this.dateService.deserialize(value));
967
- }
968
- this.calendarSignalsService.selectedDate = currValue;
969
- this.setRanges(currValue);
970
- }
971
- /** The minimum selectable date. */
972
- get minDate() {
973
- return this._minDate;
974
- }
975
- set minDate(value) {
976
- this._minDate = this.dateService.getValidOrUndefined(this.dateService.deserialize(value));
977
- }
978
- /** The maximum selectable date. */
979
- get maxDate() {
980
- return this._maxDate;
981
- }
982
- set maxDate(value) {
983
- this._maxDate = this.dateService.getValidOrUndefined(this.dateService.deserialize(value));
984
- }
130
+ class CalendarComponent {
985
131
  constructor() {
986
- super();
987
- /**
988
- * First day of week.
989
- * Zero-based starting from Sunday.
990
- * Default: 0 - Sunday.
991
- */
992
- this.firstDayOfWeek = 0;
993
- /** Whether the user is currently selecting a range of dates. */
994
- this.isRange = false;
995
- /** Emits when a new date is selected. */
996
- this.selectedChange = new EventEmitter();
997
- /** Emits when any date is selected. */
998
- this.userSelection = new EventEmitter();
999
- /** Emits when the user initiates a date range drag via mouse or touch. */
1000
- this.dragStarted = new EventEmitter();
1001
- /**
1002
- * Emits when the user completes or cancels a date range drag.
1003
- * Emits null when the drag was canceled or the newly selected date range if completed.
1004
- */
1005
- this.dragEnded = new EventEmitter();
1006
- /** Emits when any date is activated. */
1007
- this.activeDateChange = new EventEmitter();
1008
- /** Grid of calendar cells representing the dates of the month. */
1009
- this.weeks = [];
1010
- /** The number of blank cells in the first row before the 1st of the month. */
1011
- this.firstWeekOffset = 0;
1012
- /** The names of the weekdays. */
1013
- this.weekdays = [];
1014
- /** Flag used to filter out space/enter keyup events that originated outside of the view. */
1015
- this._selectionKeyPressed = false;
1016
- this.translocoService = inject(TranslocoService);
1017
- this.changeDetectorRef = inject(ChangeDetectorRef);
132
+ this.selectedDate = input();
133
+ this.selectedStartDate = input();
134
+ this.selectedEndDate = input();
135
+ this.minDate = input();
136
+ this.maxDate = input();
137
+ this.disabledDatesFilter = input();
138
+ this.isRange = input(false);
139
+ this.shouldRemoveExternalBorder = input(false);
140
+ this.selected = output();
141
+ this.monthChanged = output();
142
+ this.CalendarViewEnum = CalendarViewEnum;
143
+ this.currentView = signal(CalendarViewEnum.Days);
144
+ this.currentDate = signal(new Date());
145
+ this.yearRange = signal({ start: 2020, end: 2030 });
146
+ this.currentYear = computed(() => this.currentDate().getFullYear());
147
+ this.currentMonth = computed(() => this.currentDate().getMonth());
148
+ this.currentMonthName = computed(() => this.dateService.format(this.currentDate(), { month: 'short', year: 'numeric' }).toUpperCase());
149
+ this.calendarDays = computed(() => this.generateCalendarDays());
150
+ this.calendarMonths = computed(() => this.generateCalendarMonths());
151
+ this.calendarYears = computed(() => this.generateCalendarYears());
152
+ this.weekDayNames = computed(() => this.dateService.getDayOfWeekNames('narrow'));
153
+ this.internalSelectedDate = signal(undefined);
154
+ this.internalSelectedStartDate = signal(undefined);
155
+ this.internalSelectedEndDate = signal(undefined);
1018
156
  this.dateService = inject(DateService);
1019
- this.calendarSignalsService = inject(CalendarSignalsService);
1020
- this.calendarRangeStrategy = inject(CalendarRangeStrategyService);
1021
- effect(() => this.updateActiveDate(this.calendarSignalsService.activeDate(), true));
1022
- }
1023
- ngAfterContentInit() {
1024
- const langSub = this.translocoService.langChanges$
1025
- .pipe(startWith(undefined))
1026
- .subscribe(() => this.init());
1027
- this.addSubscriptions(langSub);
1028
- }
1029
- ngOnChanges(changes) {
1030
- const comparisonChange = changes['comparisonStart'] || changes['comparisonEnd'];
1031
- if (comparisonChange && !comparisonChange.firstChange) {
1032
- this.setRanges(this.selected);
1033
- }
1034
- if (changes['activeDrag'] && !this.activeDrag) {
1035
- this.clearPreview();
1036
- }
157
+ this.rangeSelectionState = 'start';
1037
158
  }
1038
- /** Handles when a new date is selected. */
1039
- dateSelected(event) {
1040
- const date = event.value;
1041
- const selectedDate = this.getDateFromDayOfMonth(date);
1042
- const currSelection = this.calendarSignalsService.selectedDate();
1043
- let rangeStartDate;
1044
- let rangeEndDate;
1045
- if (currSelection instanceof DateRangeModel) {
1046
- rangeStartDate = this.getDateInCurrentMonth(currSelection.start);
1047
- rangeEndDate = this.getDateInCurrentMonth(currSelection.end);
1048
- }
1049
- else {
1050
- rangeStartDate = rangeEndDate = this.getDateInCurrentMonth(currSelection);
159
+ ngOnInit() {
160
+ this.syncInternalState();
161
+ if (this.selectedDate()) {
162
+ this.currentDate.set(new Date(this.selectedDate()));
1051
163
  }
1052
- if (rangeStartDate !== date || rangeEndDate !== date) {
1053
- this.calendarSignalsService.selectedDate = selectedDate;
164
+ else if (this.selectedStartDate()) {
165
+ this.currentDate.set(new Date(this.selectedStartDate()));
1054
166
  }
1055
- this.userSelection.emit({ value: selectedDate, event: event.event });
1056
- this.clearPreview();
1057
- this.changeDetectorRef.markForCheck();
167
+ this.updateYearRange();
1058
168
  }
1059
- /** Handles keydown events on the calendar body when calendar is in month view. */
1060
- handleCalendarBodyKeydown(event) {
1061
- // TODO(mmalerba): We currently allow keyboard navigation to disabled dates, but just prevent
1062
- // disabled ones from being selected. This may not be ideal, we should look into whether
1063
- // navigation should skip over disabled dates, and if so, how to implement that efficiently.
1064
- const oldActiveDate = this.activeDate;
1065
- let tempActiveDate;
1066
- switch (event.key) {
1067
- case 'LEFT_ARROW':
1068
- tempActiveDate = this.dateService.addDays(this.activeDate, -1);
169
+ onHeaderClick() {
170
+ switch (this.currentView()) {
171
+ case CalendarViewEnum.Days:
172
+ this.currentView.set(CalendarViewEnum.Months);
1069
173
  break;
1070
- case 'RIGHT_ARROW':
1071
- tempActiveDate = this.dateService.addDays(this.activeDate, 1);
174
+ case CalendarViewEnum.Months:
175
+ this.currentView.set(CalendarViewEnum.Years);
1072
176
  break;
1073
- case 'UP_ARROW':
1074
- tempActiveDate = this.dateService.addDays(this.activeDate, -7);
177
+ }
178
+ }
179
+ onPreviousClick() {
180
+ const current = this.currentDate();
181
+ switch (this.currentView()) {
182
+ case CalendarViewEnum.Days:
183
+ this.currentDate.set(this.dateService.addMonths(current, -1));
184
+ this.monthChanged.emit(this.currentDate());
1075
185
  break;
1076
- case 'DOWN_ARROW':
1077
- tempActiveDate = this.dateService.addDays(this.activeDate, 7);
186
+ case CalendarViewEnum.Months:
187
+ this.currentDate.set(this.dateService.addYears(current, -1));
1078
188
  break;
1079
- case 'HOME':
1080
- tempActiveDate = this.dateService.addDays(this.activeDate, 1 - this.activeDate.getDate());
189
+ case CalendarViewEnum.Years:
190
+ this.moveYearRange(-12);
1081
191
  break;
1082
- case 'END':
1083
- tempActiveDate = this.dateService.addDays(this.activeDate, this.dateService.getNumDaysInMonth(this.activeDate) -
1084
- this.activeDate.getDate());
192
+ }
193
+ }
194
+ onNextClick() {
195
+ const current = this.currentDate();
196
+ switch (this.currentView()) {
197
+ case CalendarViewEnum.Days:
198
+ this.currentDate.set(this.dateService.addMonths(current, 1));
199
+ this.monthChanged.emit(this.currentDate());
1085
200
  break;
1086
- case 'PAGE_UP':
1087
- tempActiveDate = event.altKey
1088
- ? this.dateService.addYears(this.activeDate, -1)
1089
- : this.dateService.addMonths(this.activeDate, -1);
201
+ case CalendarViewEnum.Months:
202
+ this.currentDate.set(this.dateService.addYears(current, 1));
1090
203
  break;
1091
- case 'PAGE_DOWN':
1092
- tempActiveDate = event.altKey
1093
- ? this.dateService.addYears(this.activeDate, 1)
1094
- : this.dateService.addMonths(this.activeDate, 1);
204
+ case CalendarViewEnum.Years:
205
+ this.moveYearRange(12);
1095
206
  break;
1096
- case 'ENTER':
1097
- case 'SPACE':
1098
- this._selectionKeyPressed = true;
1099
- if (this.canSelect(this.activeDate)) {
1100
- // Prevent unexpected default actions such as form submission.
1101
- // Note that we only prevent the default action here while the selection happens in
1102
- // `keyup` below. We can't do the selection here, because it can cause the calendar to
1103
- // reopen if focus is restored immediately. We also can't call `preventDefault` on `keyup`
1104
- // because it's too late (see #23305).
1105
- event.preventDefault();
1106
- }
1107
- return;
1108
- case 'ESCAPE':
1109
- // Abort the current range selection if the user presses escape mid-selection.
1110
- if (this.previewEnd !== undefined && !KeyboardTools.hasModifierKey(event)) {
1111
- this.clearPreview();
1112
- // If a drag is in progress, cancel the drag without changing the
1113
- // current selection.
1114
- if (this.activeDrag) {
1115
- this.dragEnded.emit({ value: undefined, event });
1116
- }
1117
- else {
1118
- this.selectedChange.emit(undefined);
1119
- this.userSelection.emit({ value: undefined, event });
1120
- }
1121
- event.preventDefault();
1122
- event.stopPropagation(); // Prevents the overlay from closing.
1123
- }
1124
- return;
1125
- default:
1126
- // Don't prevent default or focus active cell on keys that we don't explicitly handle.
1127
- return;
1128
- }
1129
- if (this.dateService.compare(oldActiveDate, this.activeDate)) {
1130
- this.calendarSignalsService.activeDate = tempActiveDate;
1131
- this.focusActiveCellAfterViewChecked();
1132
207
  }
1133
- // Prevent unexpected default actions such as form submission.
1134
- event.preventDefault();
1135
208
  }
1136
- /** Handles keyup events on the calendar body when calendar is in month view. */
1137
- handleCalendarBodyKeyup(event) {
1138
- if (event.key === 'SPACE' || event.key === 'ENTER') {
1139
- if (this._selectionKeyPressed && this.canSelect(this.activeDate)) {
1140
- this.dateSelected({ value: this.activeDate.getDate(), event });
1141
- }
1142
- this._selectionKeyPressed = false;
209
+ onDateClick(dateModel) {
210
+ if (!dateModel.isCurrentMonth) {
211
+ return;
1143
212
  }
1144
- }
1145
- /** Initializes this month view. */
1146
- init() {
1147
- this.setRanges(this.selected);
1148
- this.todayDate = this.getCellCompareValue(new Date());
1149
- this.monthLabel = this.dateService.getMonthNames('short')[this.activeDate.getMonth()].toLocaleUpperCase();
1150
- const firstOfMonth = this.dateService.create(this.activeDate.getFullYear(), this.activeDate.getMonth(), 1);
1151
- this.firstWeekOffset = (daysPerWeek + firstOfMonth.getDay() - this.firstDayOfWeek) % daysPerWeek;
1152
- this.initWeekdays();
1153
- this.createWeekCells();
1154
- this.changeDetectorRef.markForCheck();
1155
- }
1156
- /** Focuses the active cell after the microtask queue is empty. */
1157
- focusActiveCell(movePreview) {
1158
- this._matCalendarBody.focusActiveCell(movePreview);
1159
- }
1160
- /** Focuses the active cell after change detection has run and the microtask queue is empty. */
1161
- focusActiveCellAfterViewChecked() {
1162
- this._matCalendarBody.scheduleFocusActiveCellAfterViewChecked();
1163
- }
1164
- /** Called when the user has activated a new cell and the preview needs to be updated. */
1165
- previewChanged({ value: cell }) {
1166
- if (!this.selected) {
213
+ if (dateModel.isDisabled) {
1167
214
  return;
1168
215
  }
1169
- if (this.isRange && !(this.selected instanceof DateRangeModel)) {
1170
- this.selected = new DateRangeModel(this.selected);
216
+ if (this.isRange()) {
217
+ this.handleRangeSelection(dateModel.date);
1171
218
  }
1172
- // We can assume that this will be a range, because preview
1173
- // events aren't fired for single date selections.
1174
- const value = cell ? cell.rawValue : undefined;
1175
- const previewRange = this.calendarRangeStrategy.createPreview(value, this.selected);
1176
- this.previewStart = this.getCellCompareValue(previewRange.start);
1177
- this.previewEnd = this.getCellCompareValue(previewRange.end);
1178
- if (!!this.activeDrag && !!value) {
1179
- const dragRange = this.calendarRangeStrategy.createDrag?.(this.activeDrag.value, this.selected, value);
1180
- if (!!dragRange) {
1181
- this.previewStart = this.getCellCompareValue(dragRange.start);
1182
- this.previewEnd = this.getCellCompareValue(dragRange.end);
1183
- }
219
+ else {
220
+ this.internalSelectedDate.set(dateModel.date);
221
+ this.selected.emit({ selectedDate: dateModel.date });
1184
222
  }
1185
- // Note that here we need to use `detectChanges`, rather than `markForCheck`, because
1186
- // the way `_focusActiveCell` is set up at the moment makes it fire at the wrong time
1187
- // when navigating one month back using the keyboard which will cause this handler
1188
- // to throw a "changed after checked" error when updating the preview state.
1189
- this.changeDetectorRef.detectChanges();
1190
223
  }
1191
- /**
1192
- * Called when the user has ended a drag. If the drag/drop was successful,
1193
- * computes and emits the new range selection.
1194
- */
1195
- _dragEnded(event) {
1196
- if (!this.activeDrag) {
224
+ onMonthClick(monthModel) {
225
+ if (monthModel.isDisabled) {
1197
226
  return;
1198
227
  }
1199
- if (event.value) {
1200
- // Propagate drag effect
1201
- const dragDropResult = this.calendarRangeStrategy?.createDrag?.(this.activeDrag.value, this.selected, event.value);
1202
- this.dragEnded.emit({ value: dragDropResult ?? undefined, event: event.event });
1203
- }
1204
- else {
1205
- this.dragEnded.emit({ value: undefined, event: event.event });
1206
- }
228
+ const newDate = new Date(this.currentYear(), monthModel.monthIndex, 1);
229
+ this.currentDate.set(newDate);
230
+ this.currentView.set(CalendarViewEnum.Days);
231
+ this.monthChanged.emit(newDate);
1207
232
  }
1208
- updateActiveDate(value, isFromEffect = false) {
1209
- this.oldActiveDate = this.oldActiveDate || value;
1210
- const validDate = this.dateService.getValidOrUndefined(this.dateService.deserialize(value)) || new Date();
1211
- if (!isFromEffect) {
1212
- this.calendarSignalsService.activeDate = this.dateService.clamp(validDate, this.minDate, this.maxDate);
233
+ onYearClick(yearModel) {
234
+ if (yearModel.isDisabled) {
235
+ return;
1213
236
  }
1214
- if (!this.isSameMonthAndYearAsActiveDate(this.oldActiveDate, this.calendarSignalsService.activeDate())) {
1215
- this.init();
237
+ const newDate = new Date(yearModel.year, this.currentMonth(), 1);
238
+ this.currentDate.set(newDate);
239
+ this.currentView.set(CalendarViewEnum.Months);
240
+ }
241
+ clearSelection() {
242
+ this.internalSelectedDate.set(undefined);
243
+ this.internalSelectedStartDate.set(undefined);
244
+ this.internalSelectedEndDate.set(undefined);
245
+ this.rangeSelectionState = 'start';
246
+ this.selected.emit(undefined);
247
+ }
248
+ syncInternalState() {
249
+ this.internalSelectedDate.set(this.selectedDate());
250
+ this.internalSelectedStartDate.set(this.selectedStartDate());
251
+ this.internalSelectedEndDate.set(this.selectedEndDate());
252
+ }
253
+ handleRangeSelection(selectedDate) {
254
+ if (this.rangeSelectionState === 'start' || !this.internalSelectedStartDate()) {
255
+ this.internalSelectedStartDate.set(selectedDate);
256
+ this.internalSelectedEndDate.set(undefined);
257
+ this.rangeSelectionState = 'end';
258
+ this.selected.emit({ startDate: selectedDate });
1216
259
  }
1217
- this.oldActiveDate = value;
1218
- this.activeDateChange.emit(this.calendarSignalsService.activeDate());
1219
- }
1220
- /**
1221
- * Takes a day of the month and returns a new date in the same month and year as the currently
1222
- * active date. The returned date will have the same day of the month as the argument date.
1223
- */
1224
- getDateFromDayOfMonth(dayOfMonth) {
1225
- return this.dateService.create(this.activeDate.getFullYear(), this.activeDate.getMonth(), dayOfMonth);
1226
- }
1227
- /** Initializes the weekdays. */
1228
- initWeekdays() {
1229
- const firstDayOfWeek = this.firstDayOfWeek;
1230
- const narrowWeekdays = this.dateService.getDayOfWeekNames('narrow');
1231
- const longWeekdays = this.dateService.getDayOfWeekNames('long');
1232
- // Rotate the labels for days of the week based on the configured first day of the week.
1233
- const weekdays = longWeekdays.map((long, i) => ({ long, narrow: narrowWeekdays[i], id: uniqueIdCounter++ }));
1234
- this.weekdays = weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
1235
- }
1236
- /** Creates MatCalendarCells for the dates in this month. */
1237
- createWeekCells() {
1238
- const daysInMonth = this.dateService.getNumDaysInMonth(this.activeDate);
1239
- const dateNames = this.dateService.getDateNames();
1240
- this.weeks = [[]];
1241
- for (let i = 0, cell = this.firstWeekOffset; i < daysInMonth; i++, cell++) {
1242
- if (cell === daysPerWeek) {
1243
- this.weeks.push([]);
1244
- cell = 0;
260
+ else {
261
+ const startDate = this.internalSelectedStartDate();
262
+ const endDate = selectedDate;
263
+ if (this.dateService.compare(selectedDate, startDate) < 0) {
264
+ this.internalSelectedStartDate.set(selectedDate);
265
+ this.internalSelectedEndDate.set(startDate);
266
+ this.selected.emit({
267
+ startDate: selectedDate,
268
+ endDate: startDate
269
+ });
270
+ }
271
+ else {
272
+ this.internalSelectedEndDate.set(endDate);
273
+ this.selected.emit({
274
+ startDate: startDate,
275
+ endDate: endDate
276
+ });
277
+ }
278
+ this.rangeSelectionState = 'start';
279
+ }
280
+ }
281
+ generateCalendarDays() {
282
+ const current = this.currentDate();
283
+ const year = current.getFullYear();
284
+ const month = current.getMonth();
285
+ const firstDayOfMonth = new Date(year, month, 1);
286
+ const lastDayOfMonth = new Date(year, month + 1, 0);
287
+ const firstDayOfWeek = firstDayOfMonth.getDay();
288
+ const days = [];
289
+ const today = new Date();
290
+ if (firstDayOfWeek > 0) {
291
+ const prevMonthLastDay = new Date(year, month, 0);
292
+ const startDate = prevMonthLastDay.getDate() - firstDayOfWeek + 1;
293
+ for (let day = startDate; day <= prevMonthLastDay.getDate(); day++) {
294
+ const date = new Date(year, month - 1, day);
295
+ days.push({
296
+ date,
297
+ isCurrentMonth: false,
298
+ isToday: this.dateService.areEqual(date, today),
299
+ isSelected: false,
300
+ isDisabled: true,
301
+ isInRange: false,
302
+ isRangeStart: false,
303
+ isRangeEnd: false
304
+ });
1245
305
  }
1246
- const date = this.dateService.create(this.activeDate.getFullYear(), this.activeDate.getMonth(), i + 1);
1247
- const isEnabled = this.shouldEnableDate(date);
1248
- const ariaLabel = this.dateService.format(date, { year: 'numeric', month: 'long', day: 'numeric' });
1249
- const cellClasses = this.dateClass ? this.dateClass(date, 'month') : undefined;
1250
- this.weeks[this.weeks.length - 1].push(new CalendarCellModel(i + 1, dateNames[i], ariaLabel, isEnabled, cellClasses, this.getCellCompareValue(date), date));
1251
306
  }
1252
- }
1253
- /** Date filter for the month */
1254
- shouldEnableDate(date) {
1255
- return !!date &&
1256
- (!this.minDate || this.dateService.compare(date, this.minDate) >= 0) &&
1257
- (!this.maxDate || this.dateService.compare(date, this.maxDate) <= 0) &&
1258
- (!this.dateFilter || this.dateFilter(date));
1259
- }
1260
- /**
1261
- * Gets the date in this month that the given Date falls on.
1262
- * Returns null if the given Date is in another month.
1263
- */
1264
- getDateInCurrentMonth(date) {
1265
- return !!date && this.isSameMonthAndYearAsActiveDate(date, this.calendarSignalsService.activeDate())
1266
- ? date.getDate()
1267
- : undefined;
1268
- }
1269
- /** Checks whether the 2 dates are non-null and fall within the same month of the same year. */
1270
- isSameMonthAndYearAsActiveDate(d1, d2) {
1271
- return (!!d1 && !!d2 && d1.getMonth() === d2.getMonth() && d1.getFullYear() === d2.getFullYear());
1272
- }
1273
- /** Gets the value that will be used to one cell to another. */
1274
- getCellCompareValue(date) {
1275
- if (!!date) {
1276
- // We use the time since the Unix epoch to compare dates in this view, rather than the
1277
- // cell values, because we need to support ranges that span across multiple months/years.
1278
- const year = date.getFullYear();
1279
- const month = date.getMonth();
1280
- const day = date.getDate();
1281
- return new Date(year, month, day).getTime();
307
+ for (let day = 1; day <= lastDayOfMonth.getDate(); day++) {
308
+ const date = new Date(year, month, day);
309
+ days.push({
310
+ date,
311
+ isCurrentMonth: true,
312
+ isToday: this.dateService.areEqual(date, today),
313
+ isSelected: this.isDateSelected(date),
314
+ isDisabled: this.isDateDisabled(date),
315
+ isInRange: this.isDateInRange(date),
316
+ isRangeStart: this.isDateRangeStart(date),
317
+ isRangeEnd: this.isDateRangeEnd(date)
318
+ });
1282
319
  }
1283
- return undefined;
1284
- }
1285
- /** Sets the current range based on a model value. */
1286
- setRanges(selectedValue) {
1287
- if (selectedValue instanceof DateRangeModel) {
1288
- this.rangeStart = this.getCellCompareValue(selectedValue.start);
1289
- this.rangeEnd = this.getCellCompareValue(selectedValue.end);
1290
- this.isRange = true;
320
+ const totalCells = Math.ceil(days.length / 7) * 7;
321
+ const nextMonthDays = totalCells - days.length;
322
+ for (let day = 1; day <= nextMonthDays; day++) {
323
+ const date = new Date(year, month + 1, day);
324
+ days.push({
325
+ date,
326
+ isCurrentMonth: false,
327
+ isToday: this.dateService.areEqual(date, today),
328
+ isSelected: false,
329
+ isDisabled: true,
330
+ isInRange: false,
331
+ isRangeStart: false,
332
+ isRangeEnd: false
333
+ });
1291
334
  }
1292
- else {
1293
- this.rangeStart = this.rangeEnd = this.getCellCompareValue(selectedValue);
335
+ return days;
336
+ }
337
+ generateCalendarMonths() {
338
+ const currentYear = this.currentYear();
339
+ const currentMonth = this.currentMonth();
340
+ const today = new Date();
341
+ const monthNames = this.dateService.getMonthNames('short');
342
+ return monthNames.map((name, index) => ({
343
+ monthIndex: index,
344
+ name: name.toUpperCase(),
345
+ isCurrentMonth: today.getFullYear() === currentYear && today.getMonth() === index,
346
+ isSelected: currentMonth === index,
347
+ isDisabled: this.isMonthDisabled(currentYear, index)
348
+ }));
349
+ }
350
+ generateCalendarYears() {
351
+ const range = this.yearRange();
352
+ const currentYear = this.currentYear();
353
+ const today = new Date();
354
+ const years = [];
355
+ for (let year = range.start; year <= range.end; year++) {
356
+ years.push({
357
+ year,
358
+ isCurrentYear: today.getFullYear() === year,
359
+ isSelected: currentYear === year,
360
+ isDisabled: this.isYearDisabled(year)
361
+ });
1294
362
  }
1295
- this.comparisonRangeStart = this.getCellCompareValue(this.comparisonStart);
1296
- this.comparisonRangeEnd = this.getCellCompareValue(this.comparisonEnd);
1297
- }
1298
- /** Gets whether a date can be selected in the month view. */
1299
- canSelect(date) {
1300
- return !this.dateFilter || this.dateFilter(date);
363
+ return years;
1301
364
  }
1302
- /** Clears out preview state. */
1303
- clearPreview() {
1304
- this.previewStart = this.previewEnd = undefined;
1305
- }
1306
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarMonthViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1307
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.1.0", type: CalendarMonthViewComponent, selector: "upd-calendar-month-view", inputs: { activeDate: "activeDate", selected: "selected", minDate: "minDate", maxDate: "maxDate", dateFilter: "dateFilter", dateClass: "dateClass", comparisonStart: "comparisonStart", comparisonEnd: "comparisonEnd", startDateAccessibleName: "startDateAccessibleName", endDateAccessibleName: "endDateAccessibleName", firstDayOfWeek: "firstDayOfWeek", activeDrag: "activeDrag", isRange: "isRange" }, outputs: { selectedChange: "selectedChange", userSelection: "userSelection", dragStarted: "dragStarted", dragEnded: "dragEnded", activeDateChange: "activeDateChange" }, viewQueries: [{ propertyName: "_matCalendarBody", first: true, predicate: CalendarBodyComponent, descendants: true }], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<upd-calendar-header></upd-calendar-header>\n<div class=\"container mt-2\">\n <div class=\"row seven-cols\">\n @for (day of weekdays; track day.id) {\n <div class=\"col-1 p-1 d-flex justify-content-center align-items-center\">\n <span class=\"visually-hidden\">{{ day.long }}</span>\n <span aria-hidden=\"true\">{{ day.narrow }}</span>\n </div>\n }\n </div>\n</div>\n<upd-calendar-body [label]=\"monthLabel\" [rows]=\"weeks\" [todayValue]=\"todayDate!\" [startValue]=\"rangeStart!\"\n [endValue]=\"rangeEnd!\" [comparisonStart]=\"comparisonRangeStart\" [comparisonEnd]=\"comparisonRangeEnd\"\n [previewStart]=\"previewStart\" [previewEnd]=\"previewEnd\" [isRange]=\"isRange\" [labelMinRequiredCells]=\"3\"\n [activeCell]=\"activeDate.getDate() - 1\" [startDateAccessibleName]=\"startDateAccessibleName\"\n [endDateAccessibleName]=\"endDateAccessibleName\" (selectedValueChange)=\"dateSelected($event)\"\n (previewChange)=\"previewChanged($event)\" (dragStarted)=\"dragStarted.emit($event)\" (dragEnded)=\"_dragEnded($event)\"\n (keyup)=\"handleCalendarBodyKeyup($event)\" (keydown)=\"handleCalendarBodyKeydown($event)\">\n</upd-calendar-body>\n", styles: ["@media (min-width: 768px){.seven-cols .col-1{width:100%}.seven-cols .col-6{width:85.7142857143%}}@media (min-width: 992px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}@media (min-width: 1200px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}\n"], dependencies: [{ kind: "component", type: CalendarHeaderComponent, selector: "upd-calendar-header" }, { kind: "component", type: CalendarBodyComponent, selector: "upd-calendar-body", inputs: ["label", "rows", "todayValue", "startValue", "endValue", "labelMinRequiredCells", "numCols", "activeCell", "isRange", "cellAspectRatio", "comparisonStart", "comparisonEnd", "previewStart", "previewEnd", "startDateAccessibleName", "endDateAccessibleName"], outputs: ["selectedValueChange", "previewChange", "dragStarted", "dragEnded"] }] }); }
1308
- }
1309
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarMonthViewComponent, decorators: [{
1310
- type: Component,
1311
- args: [{ selector: 'upd-calendar-month-view', template: "<upd-calendar-header></upd-calendar-header>\n<div class=\"container mt-2\">\n <div class=\"row seven-cols\">\n @for (day of weekdays; track day.id) {\n <div class=\"col-1 p-1 d-flex justify-content-center align-items-center\">\n <span class=\"visually-hidden\">{{ day.long }}</span>\n <span aria-hidden=\"true\">{{ day.narrow }}</span>\n </div>\n }\n </div>\n</div>\n<upd-calendar-body [label]=\"monthLabel\" [rows]=\"weeks\" [todayValue]=\"todayDate!\" [startValue]=\"rangeStart!\"\n [endValue]=\"rangeEnd!\" [comparisonStart]=\"comparisonRangeStart\" [comparisonEnd]=\"comparisonRangeEnd\"\n [previewStart]=\"previewStart\" [previewEnd]=\"previewEnd\" [isRange]=\"isRange\" [labelMinRequiredCells]=\"3\"\n [activeCell]=\"activeDate.getDate() - 1\" [startDateAccessibleName]=\"startDateAccessibleName\"\n [endDateAccessibleName]=\"endDateAccessibleName\" (selectedValueChange)=\"dateSelected($event)\"\n (previewChange)=\"previewChanged($event)\" (dragStarted)=\"dragStarted.emit($event)\" (dragEnded)=\"_dragEnded($event)\"\n (keyup)=\"handleCalendarBodyKeyup($event)\" (keydown)=\"handleCalendarBodyKeydown($event)\">\n</upd-calendar-body>\n", styles: ["@media (min-width: 768px){.seven-cols .col-1{width:100%}.seven-cols .col-6{width:85.7142857143%}}@media (min-width: 992px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}@media (min-width: 1200px){.seven-cols .col-1{width:14.2857142857%}.seven-cols .col-2{width:28.5714285714%}.seven-cols .col-3{width:42.8571428571%}.seven-cols .col-4{width:57.1428571429%}.seven-cols .col-5{width:71.4285714286%}.seven-cols .col-6{width:85.7142857143%}.seven-cols .col-7{width:100%}}\n"] }]
1312
- }], ctorParameters: () => [], propDecorators: { _matCalendarBody: [{
1313
- type: ViewChild,
1314
- args: [CalendarBodyComponent]
1315
- }], activeDate: [{
1316
- type: Input
1317
- }], selected: [{
1318
- type: Input
1319
- }], minDate: [{
1320
- type: Input
1321
- }], maxDate: [{
1322
- type: Input
1323
- }], dateFilter: [{
1324
- type: Input
1325
- }], dateClass: [{
1326
- type: Input
1327
- }], comparisonStart: [{
1328
- type: Input
1329
- }], comparisonEnd: [{
1330
- type: Input
1331
- }], startDateAccessibleName: [{
1332
- type: Input
1333
- }], endDateAccessibleName: [{
1334
- type: Input
1335
- }], firstDayOfWeek: [{
1336
- type: Input
1337
- }], activeDrag: [{
1338
- type: Input
1339
- }], isRange: [{
1340
- type: Input
1341
- }], selectedChange: [{
1342
- type: Output
1343
- }], userSelection: [{
1344
- type: Output
1345
- }], dragStarted: [{
1346
- type: Output
1347
- }], dragEnded: [{
1348
- type: Output
1349
- }], activeDateChange: [{
1350
- type: Output
1351
- }] } });
1352
-
1353
- class CalendarYearViewComponent {
1354
- /** Initializes this month view. */
1355
- init() {
1356
- }
1357
- /** Focuses the active cell after the microtask queue is empty. */
1358
- focusActiveCell(movePreview) {
1359
- }
1360
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarYearViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1361
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.1.0", type: CalendarYearViewComponent, selector: "upd-calendar-year-view", ngImport: i0, template: "<p>calendar-year-view works!</p>\n", styles: [""] }); }
1362
- }
1363
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarYearViewComponent, decorators: [{
1364
- type: Component,
1365
- args: [{ selector: 'upd-calendar-year-view', template: "<p>calendar-year-view works!</p>\n" }]
1366
- }] });
1367
-
1368
- class CalendarComponent {
1369
- constructor() {
1370
- /**
1371
- * Whether the calendar should be started in month or year view.
1372
- */
1373
- this.startView = 'month';
1374
- /** Whether the user is currently selecting a range of dates. */
1375
- this.isRange = false;
1376
- /** Emits when the currently selected date changes. */
1377
- this.selectedChange = new EventEmitter();
1378
- /**
1379
- * Emits the year chosen in multiyear view.
1380
- * This doesn't imply a change on the selected date.
1381
- */
1382
- this.yearSelected = new EventEmitter();
1383
- /**
1384
- * Emits the month chosen in year view.
1385
- * This doesn't imply a change on the selected date.
1386
- */
1387
- this.monthSelected = new EventEmitter();
1388
- /**
1389
- * Emits when the current view changes.
1390
- */
1391
- this.viewChanged = new EventEmitter(true);
1392
- /** Emits when any date is selected. */
1393
- this.userSelection = new EventEmitter();
1394
- /** Emits a new date range value when the user completes a drag drop operation. */
1395
- this.userDragDrop = new EventEmitter();
1396
- /**
1397
- * Used for scheduling that focus should be moved to the active cell on the next tick.
1398
- * We need to schedule it, rather than do it immediately, because we have to wait
1399
- * for Angular to re-evaluate the view children.
1400
- */
1401
- this._moveFocusOnNextTick = false;
1402
- this.dateService = inject(DateService);
1403
- this._changeDetectorRef = inject(ChangeDetectorRef);
1404
- }
1405
- /** A date representing the period (month or year) to start the calendar in. */
1406
- get startAt() {
1407
- return this._startAt;
1408
- }
1409
- set startAt(value) {
1410
- this._startAt = this.dateService.getValidOrUndefined(this.dateService.deserialize(value));
1411
- }
1412
- /**
1413
- * The currently selected date.
1414
- */
1415
- get selected() {
1416
- return this._selected;
1417
- }
1418
- set selected(value) {
1419
- console.log('setting selected', value);
1420
- if (value instanceof DateRangeModel) {
1421
- this._selected = value;
365
+ isDateSelected(date) {
366
+ if (this.isRange()) {
367
+ if (this.internalSelectedStartDate() && this.dateService.areEqual(date, this.internalSelectedStartDate())) {
368
+ return true;
369
+ }
370
+ return !!(this.internalSelectedEndDate() && this.dateService.areEqual(date, this.internalSelectedEndDate()));
1422
371
  }
1423
372
  else {
1424
- this._selected = this.dateService.getValidOrUndefined(this.dateService.deserialize(value));
373
+ return this.internalSelectedDate() ? this.dateService.areEqual(date, this.internalSelectedDate()) : false;
1425
374
  }
1426
375
  }
1427
- /**
1428
- * The minimum selectable date.
1429
- */
1430
- get minDate() {
1431
- return this._minDate;
1432
- }
1433
- set minDate(value) {
1434
- this._minDate = this.dateService.getValidOrUndefined(this.dateService.deserialize(value));
1435
- }
1436
- /**
1437
- * The maximum selectable date.
1438
- */
1439
- get maxDate() {
1440
- return this._maxDate;
1441
- }
1442
- set maxDate(value) {
1443
- this._maxDate = this.dateService.getValidOrUndefined(this.dateService.deserialize(value));
1444
- }
1445
- /**
1446
- * The current active date. This determines which time period is shown and which date is
1447
- * highlighted when using keyboard navigation.
1448
- */
1449
- get activeDate() {
1450
- return this._clampedActiveDate;
376
+ isDateInRange(date) {
377
+ if (!this.isRange() || !this.internalSelectedStartDate() || !this.internalSelectedEndDate()) {
378
+ return false;
379
+ }
380
+ return this.dateService.compare(date, this.internalSelectedStartDate()) > 0
381
+ && this.dateService.compare(date, this.internalSelectedEndDate()) < 0;
1451
382
  }
1452
- set activeDate(value) {
1453
- this._clampedActiveDate = this.dateService.clamp(value, this.minDate, this.maxDate);
1454
- this._changeDetectorRef.markForCheck();
383
+ isDateRangeStart(date) {
384
+ return this.isRange() && this.internalSelectedStartDate()
385
+ ? this.dateService.areEqual(date, this.internalSelectedStartDate())
386
+ : false;
1455
387
  }
1456
- /** Whether the calendar is in month view. */
1457
- get currentView() {
1458
- return this._currentView;
388
+ isDateRangeEnd(date) {
389
+ return this.isRange() && this.internalSelectedEndDate()
390
+ ? this.dateService.areEqual(date, this.internalSelectedEndDate())
391
+ : false;
1459
392
  }
1460
- set currentView(value) {
1461
- const viewChangedResult = this._currentView !== value ? value : undefined;
1462
- this._currentView = value;
1463
- this._moveFocusOnNextTick = true;
1464
- this._changeDetectorRef.markForCheck();
1465
- if (viewChangedResult) {
1466
- this.viewChanged.emit(viewChangedResult);
393
+ isDateDisabled(date) {
394
+ if (this.minDate() && this.dateService.compare(date, this.minDate()) < 0) {
395
+ return true;
1467
396
  }
1468
- }
1469
- ngAfterContentInit() {
1470
- this.activeDate = this.startAt || new Date();
1471
- // Assign to the private property since we don't want to move focus on init.
1472
- this._currentView = this.startView;
1473
- }
1474
- ngAfterViewChecked() {
1475
- if (this._moveFocusOnNextTick) {
1476
- this._moveFocusOnNextTick = false;
1477
- this.focusActiveCell();
397
+ if (this.maxDate() && this.dateService.compare(date, this.maxDate()) > 0) {
398
+ return true;
1478
399
  }
1479
- }
1480
- ngOnChanges(changes) {
1481
- // Ignore date changes that are at a different time on the same day. This fixes issues where
1482
- // the calendar re-renders when there is no meaningful change to [minDate] or [maxDate]
1483
- // (#24435).
1484
- const minDateChange = changes['minDate'] && !this.dateService.areEqual(changes['minDate'].previousValue, changes['minDate'].currentValue)
1485
- ? changes['minDate']
1486
- : undefined;
1487
- const maxDateChange = changes['maxDate'] && !this.dateService.areEqual(changes['maxDate'].previousValue, changes['maxDate'].currentValue)
1488
- ? changes['maxDate']
1489
- : undefined;
1490
- const change = minDateChange || maxDateChange || changes['dateFilter'];
1491
- if (change && !change.firstChange) {
1492
- const view = this._getCurrentViewComponent();
1493
- if (view) {
1494
- // We need to `detectChanges` manually here, because the `minDate`, `maxDate` etc. are
1495
- // passed down to the view via data bindings which won't be up-to-date when we call `_init`.
1496
- this._changeDetectorRef.detectChanges();
1497
- view.init();
1498
- }
400
+ const filterFn = this.disabledDatesFilter();
401
+ if (!!filterFn) {
402
+ return !filterFn(date);
1499
403
  }
404
+ return false;
1500
405
  }
1501
- /** Focuses the active date. */
1502
- focusActiveCell() {
1503
- this._getCurrentViewComponent().focusActiveCell(false);
1504
- }
1505
- /** Updates today's date after an update of the active date */
1506
- updateTodaysDate() {
1507
- this._getCurrentViewComponent().init();
1508
- }
1509
- /** Handles date selection in the month view. */
1510
- _dateSelected(event) {
1511
- const date = event.value;
1512
- if (this.selected instanceof DateRangeModel || (date && !this.dateService.areEqual(date, this.selected))) {
1513
- this.selectedChange.emit(date);
406
+ isMonthDisabled(year, month) {
407
+ const firstDayOfMonth = new Date(year, month, 1);
408
+ const lastDayOfMonth = new Date(year, month + 1, 0);
409
+ if (this.minDate() && this.dateService.compare(lastDayOfMonth, this.minDate()) < 0) {
410
+ return true;
1514
411
  }
1515
- this.userSelection.emit(event);
1516
- }
1517
- /** Handles year selection in the multiyear view. */
1518
- _yearSelectedInMultiYearView(normalizedYear) {
1519
- this.yearSelected.emit(normalizedYear);
1520
- }
1521
- /** Handles month selection in the year view. */
1522
- _monthSelectedInYearView(normalizedMonth) {
1523
- this.monthSelected.emit(normalizedMonth);
412
+ return !!(this.maxDate() && this.dateService.compare(firstDayOfMonth, this.maxDate()) > 0);
1524
413
  }
1525
- /** Handles year/month selection in the multi-year/year views. */
1526
- _goToDateInView(date, view) {
1527
- this.activeDate = date;
1528
- this.currentView = view;
1529
- }
1530
- /** Called when the user starts dragging to change a date range. */
1531
- _dragStarted(event) {
1532
- this._activeDrag = event;
1533
- }
1534
- /**
1535
- * Called when a drag completes. It may end in cancelation or in the selection
1536
- * of a new range.
1537
- */
1538
- _dragEnded(event) {
1539
- if (!this._activeDrag) {
1540
- return;
1541
- }
1542
- if (event.value) {
1543
- this.userDragDrop.emit(event);
414
+ isYearDisabled(year) {
415
+ const firstDayOfYear = new Date(year, 0, 1);
416
+ const lastDayOfYear = new Date(year, 11, 31);
417
+ if (this.minDate() && this.dateService.compare(lastDayOfYear, this.minDate()) < 0) {
418
+ return true;
1544
419
  }
1545
- this._activeDrag = undefined;
420
+ return !!(this.maxDate() && this.dateService.compare(firstDayOfYear, this.maxDate()) > 0);
1546
421
  }
1547
- /** Returns the component instance that corresponds to the current calendar view. */
1548
- _getCurrentViewComponent() {
1549
- // The return type is explicitly written as a union to ensure that the Closure compiler does
1550
- // not optimize calls to _init(). Without the explicit return type, TypeScript narrows it to
1551
- // only the first component type. See https://github.com/angular/components/issues/22996.
1552
- return (this.monthView || this.yearView || this.multiYearView);
422
+ updateYearRange() {
423
+ const currentYear = this.currentYear();
424
+ this.yearRange.set({
425
+ start: currentYear - 6,
426
+ end: currentYear + 5
427
+ });
428
+ }
429
+ moveYearRange(offset) {
430
+ const current = this.yearRange();
431
+ this.yearRange.set({
432
+ start: current.start + offset,
433
+ end: current.end + offset
434
+ });
1553
435
  }
1554
436
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1555
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.1.0", type: CalendarComponent, selector: "upd-calendar", inputs: { startAt: "startAt", startView: "startView", selected: "selected", minDate: "minDate", maxDate: "maxDate", dateFilter: "dateFilter", dateClass: "dateClass", comparisonStart: "comparisonStart", comparisonEnd: "comparisonEnd", startDateAccessibleName: "startDateAccessibleName", endDateAccessibleName: "endDateAccessibleName", isRange: "isRange" }, outputs: { selectedChange: "selectedChange", yearSelected: "yearSelected", monthSelected: "monthSelected", viewChanged: "viewChanged", userSelection: "userSelection", userDragDrop: "userDragDrop" }, viewQueries: [{ propertyName: "monthView", first: true, predicate: CalendarMonthViewComponent, descendants: true }, { propertyName: "yearView", first: true, predicate: CalendarYearViewComponent, descendants: true }, { propertyName: "multiYearView", first: true, predicate: CalendarMultiYearViewComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@switch (currentView) {\n @case ('month') {\n <upd-calendar-month-view [(activeDate)]=\"activeDate\" [isRange]=\"isRange\" [selected]=\"selected\" [dateFilter]=\"dateFilter\"\n [maxDate]=\"maxDate\" [minDate]=\"minDate\" [dateClass]=\"dateClass\" [comparisonStart]=\"comparisonStart\"\n [comparisonEnd]=\"comparisonEnd\" [startDateAccessibleName]=\"startDateAccessibleName\"\n [endDateAccessibleName]=\"endDateAccessibleName\" (userSelection)=\"_dateSelected($event)\" (dragStarted)=\"_dragStarted($event)\"\n (dragEnded)=\"_dragEnded($event)\" [activeDrag]=\"_activeDrag\">\n </upd-calendar-month-view>\n }\n @case ('year') {\n YEAR!\n <!-- <upd-calendar-year-view-->\n <!-- [(activeDate)]=\"activeDate\"-->\n <!-- [selected]=\"selected\"-->\n <!-- [dateFilter]=\"dateFilter\"-->\n <!-- [maxDate]=\"maxDate\"-->\n <!-- [minDate]=\"minDate\"-->\n <!-- [dateClass]=\"dateClass\"-->\n <!-- (monthSelected)=\"_monthSelectedInYearView($event)\"-->\n <!-- (selectedChange)=\"_goToDateInView($event, 'month')\"></upd-calendar-year-view>-->\n }\n @case ('multi-year') {\n MUUMUMUULTI-YEAR!\n <!-- <upd-calendar-multi-year-view-->\n <!-- [(activeDate)]=\"activeDate\"-->\n <!-- [selected]=\"selected\"-->\n <!-- [dateFilter]=\"dateFilter\"-->\n <!-- [maxDate]=\"maxDate\"-->\n <!-- [minDate]=\"minDate\"-->\n <!-- [dateClass]=\"dateClass\"-->\n <!-- (yearSelected)=\"_yearSelectedInMultiYearView($event)\"-->\n <!-- (selectedChange)=\"_goToDateInView($event, 'year')\"></upd-calendar-multi-year-view>-->\n }\n}\n", styles: [""], dependencies: [{ kind: "component", type: CalendarMonthViewComponent, selector: "upd-calendar-month-view", inputs: ["activeDate", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "comparisonStart", "comparisonEnd", "startDateAccessibleName", "endDateAccessibleName", "firstDayOfWeek", "activeDrag", "isRange"], outputs: ["selectedChange", "userSelection", "dragStarted", "dragEnded", "activeDateChange"] }] }); }
437
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.1.0", type: CalendarComponent, selector: "upd-calendar", inputs: { selectedDate: { classPropertyName: "selectedDate", publicName: "selectedDate", isSignal: true, isRequired: false, transformFunction: null }, selectedStartDate: { classPropertyName: "selectedStartDate", publicName: "selectedStartDate", isSignal: true, isRequired: false, transformFunction: null }, selectedEndDate: { classPropertyName: "selectedEndDate", publicName: "selectedEndDate", 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 }, disabledDatesFilter: { classPropertyName: "disabledDatesFilter", publicName: "disabledDatesFilter", isSignal: true, isRequired: false, transformFunction: null }, isRange: { classPropertyName: "isRange", publicName: "isRange", isSignal: true, isRequired: false, transformFunction: null }, shouldRemoveExternalBorder: { classPropertyName: "shouldRemoveExternalBorder", publicName: "shouldRemoveExternalBorder", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected", monthChanged: "monthChanged" }, ngImport: i0, template: "<div class=\"calendar\" [class.calendar-border]=\"!shouldRemoveExternalBorder()\">\n <div class=\"calendar-header\">\n <button type=\"button\" class=\"btn btn-sm btn-ghost-secondary\" (click)=\"onPreviousClick()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\"\n stroke=\"currentColor\" fill=\"none\">\n <path stroke=\"none\" d=\"m0 0h24v24H0z\" fill=\"none\" />\n <path d=\"m15 6-6 6 6 6\" />\n </svg>\n </button>\n\n <button type=\"button\" class=\"btn btn-sm btn-ghost-secondary calendar-title\" (click)=\"onHeaderClick()\">\n @switch (currentView()) {\n @case (CalendarViewEnum.Days) {\n {{ currentMonthName() }}\n }\n @case (CalendarViewEnum.Months) {\n {{ currentYear() }}\n }\n @case (CalendarViewEnum.Years) {\n {{ yearRange().start }} - {{ yearRange().end }}\n }\n }\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon ms-1\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" stroke-width=\"2\"\n stroke=\"currentColor\" fill=\"none\">\n <path stroke=\"none\" d=\"m0 0h24v24H0z\" fill=\"none\" />\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <button type=\"button\" class=\"btn btn-sm btn-ghost-secondary\" (click)=\"onNextClick()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\"\n stroke=\"currentColor\" fill=\"none\">\n <path stroke=\"none\" d=\"m0 0h24v24H0z\" fill=\"none\" />\n <path d=\"m9 6 6 6-6 6\" />\n </svg>\n </button>\n </div>\n\n @if (currentView() === CalendarViewEnum.Days) {\n <div class=\"calendar-weekdays\">\n @for (dayName of weekDayNames(); track dayName) {\n <div class=\"calendar-weekday\">{{ dayName }}</div>\n }\n </div>\n\n <div class=\"calendar-days\">\n @for (day of calendarDays(); track day.date.getTime()) {\n <button type=\"button\" class=\"calendar-day\" [class.calendar-day-other-month]=\"!day.isCurrentMonth\"\n [class.calendar-day-today]=\"day.isToday\" [class.calendar-day-selected]=\"day.isSelected\"\n [class.calendar-day-in-range]=\"day.isInRange\" [class.calendar-day-range-start]=\"day.isRangeStart\"\n [class.calendar-day-range-end]=\"day.isRangeEnd\" [disabled]=\"day.isDisabled\" (click)=\"onDateClick(day)\">\n {{ day.date.getDate() }}\n </button>\n }\n </div>\n }\n\n @if (currentView() === CalendarViewEnum.Months) {\n <div class=\"calendar-months\">\n @for (month of calendarMonths(); track month.monthIndex) {\n <button type=\"button\" class=\"calendar-month\" [class.calendar-month-current]=\"month.isCurrentMonth\"\n [class.calendar-month-selected]=\"month.isSelected\" [disabled]=\"month.isDisabled\" (click)=\"onMonthClick(month)\">\n {{ month.name }}\n </button>\n }\n </div>\n }\n\n @if (currentView() === CalendarViewEnum.Years) {\n <div class=\"calendar-years\">\n @for (year of calendarYears(); track year.year) {\n <button type=\"button\" class=\"calendar-year\" [class.calendar-year-current]=\"year.isCurrentYear\"\n [class.calendar-year-selected]=\"year.isSelected\" [disabled]=\"year.isDisabled\" (click)=\"onYearClick(year)\">\n {{ year.year }}\n </button>\n }\n </div>\n }\n</div>\n", styles: [".calendar{background:var(--bs-body-bg);padding:1rem;width:100%;height:100%;border:0!important}.calendar-border{border:1px solid var(--bs-border-color)!important;border-radius:var(--bs-border-radius, .375rem);box-shadow:var(--bs-box-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075))}.calendar-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.calendar-header .calendar-title{font-weight:600;flex:1;text-align:center;display:flex;align-items:center;justify-content:center;cursor:pointer}.calendar-header .calendar-title:hover{color:var(--bs-primary)}.calendar-header button{background:transparent;border:none;padding:.25rem;border-radius:var(--bs-border-radius-sm, .25rem);cursor:pointer;color:var(--bs-secondary)}.calendar-header button:hover{background-color:var(--bs-gray-100);color:var(--bs-gray-700)}.calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:.25rem;margin-bottom:.5rem}.calendar-weekday{text-align:center;font-size:.75rem;font-weight:600;color:var(--bs-secondary);padding:.25rem;text-transform:uppercase}.calendar-days{display:grid;grid-template-columns:repeat(7,1fr);gap:.125rem}.calendar-day{aspect-ratio:1;border:none;background:transparent;border-radius:var(--bs-border-radius-sm, .25rem);font-size:.875rem;cursor:pointer;transition:all .15s ease-in-out;display:flex;align-items:center;justify-content:center;min-height:32px;font-weight:400;position:relative}.calendar-day:hover:not(:disabled):not(.calendar-day-other-month){background-color:var(--bs-gray-100)}.calendar-day:disabled{cursor:not-allowed}.calendar-day.calendar-day-other-month{color:var(--bs-gray-400, #adb5bd);cursor:default;pointer-events:none}.calendar-day.calendar-day-other-month:hover{background-color:transparent}.calendar-day.calendar-day-today{background-color:var(--bs-primary-bg-subtle, rgba(13, 110, 253, .1));color:var(--bs-primary, #0d6efd);font-weight:600}.calendar-day.calendar-day-today:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-border-subtle, rgba(13, 110, 253, .2))}.calendar-day.calendar-day-selected{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600}.calendar-day.calendar-day-selected:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day.calendar-day-in-range{border-radius:0;background-color:var(--bs-secondary-bg-subtle, rgba(108, 117, 125, .1));color:var(--bs-secondary-emphasis, #495057)}.calendar-day.calendar-day-in-range:hover:not(.calendar-day-other-month){background-color:var(--bs-secondary-border-subtle, rgba(108, 117, 125, .2))}.calendar-day.calendar-day-range-start{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600;border-top-right-radius:0;border-bottom-right-radius:0}.calendar-day.calendar-day-range-start:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day.calendar-day-range-end{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600;border-top-left-radius:0;border-bottom-left-radius:0}.calendar-day.calendar-day-range-end:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day.calendar-day-range-start.calendar-day-range-end{border-radius:var(--bs-border-radius-sm, .25rem)}.calendar-day.calendar-day-other-month.calendar-day-selected{background-color:transparent;color:var(--bs-gray-400, #adb5bd);font-weight:400}.calendar-months{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem}.calendar-month{padding:.75rem .5rem;border:none;background:transparent;border-radius:var(--bs-border-radius-sm, .25rem);font-size:.875rem;font-weight:500;cursor:pointer;transition:all .15s ease-in-out}.calendar-month:hover:not(:disabled){background-color:var(--bs-gray-100, #f8f9fa)}.calendar-month:disabled{color:var(--bs-gray-400, #adb5bd);cursor:not-allowed}.calendar-month.calendar-month-current{background-color:var(--bs-primary-bg-subtle, rgba(13, 110, 253, .1));color:var(--bs-primary, #0d6efd);font-weight:600}.calendar-month.calendar-month-selected{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600}.calendar-month.calendar-month-selected:hover{background-color:var(--bs-primary-dark, #0a58ca)}.calendar-years{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem}.calendar-year{padding:.75rem .5rem;border:none;background:transparent;border-radius:var(--bs-border-radius-sm, .25rem);font-size:.875rem;font-weight:500;cursor:pointer;transition:all .15s ease-in-out}.calendar-year:hover:not(:disabled){background-color:var(--bs-gray-100, #f8f9fa)}.calendar-year:disabled{color:var(--bs-gray-400, #adb5bd);cursor:not-allowed}.calendar-year.calendar-year-current{background-color:var(--bs-primary-bg-subtle, rgba(13, 110, 253, .1));color:var(--bs-primary, #0d6efd);font-weight:600}.calendar-year.calendar-year-selected{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600}.calendar-year.calendar-year-selected:hover{background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day-not-selectable{pointer-events:none;opacity:.5}\n"] }); }
1556
438
  }
1557
439
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarComponent, decorators: [{
1558
440
  type: Component,
1559
- args: [{ selector: 'upd-calendar', template: "@switch (currentView) {\n @case ('month') {\n <upd-calendar-month-view [(activeDate)]=\"activeDate\" [isRange]=\"isRange\" [selected]=\"selected\" [dateFilter]=\"dateFilter\"\n [maxDate]=\"maxDate\" [minDate]=\"minDate\" [dateClass]=\"dateClass\" [comparisonStart]=\"comparisonStart\"\n [comparisonEnd]=\"comparisonEnd\" [startDateAccessibleName]=\"startDateAccessibleName\"\n [endDateAccessibleName]=\"endDateAccessibleName\" (userSelection)=\"_dateSelected($event)\" (dragStarted)=\"_dragStarted($event)\"\n (dragEnded)=\"_dragEnded($event)\" [activeDrag]=\"_activeDrag\">\n </upd-calendar-month-view>\n }\n @case ('year') {\n YEAR!\n <!-- <upd-calendar-year-view-->\n <!-- [(activeDate)]=\"activeDate\"-->\n <!-- [selected]=\"selected\"-->\n <!-- [dateFilter]=\"dateFilter\"-->\n <!-- [maxDate]=\"maxDate\"-->\n <!-- [minDate]=\"minDate\"-->\n <!-- [dateClass]=\"dateClass\"-->\n <!-- (monthSelected)=\"_monthSelectedInYearView($event)\"-->\n <!-- (selectedChange)=\"_goToDateInView($event, 'month')\"></upd-calendar-year-view>-->\n }\n @case ('multi-year') {\n MUUMUMUULTI-YEAR!\n <!-- <upd-calendar-multi-year-view-->\n <!-- [(activeDate)]=\"activeDate\"-->\n <!-- [selected]=\"selected\"-->\n <!-- [dateFilter]=\"dateFilter\"-->\n <!-- [maxDate]=\"maxDate\"-->\n <!-- [minDate]=\"minDate\"-->\n <!-- [dateClass]=\"dateClass\"-->\n <!-- (yearSelected)=\"_yearSelectedInMultiYearView($event)\"-->\n <!-- (selectedChange)=\"_goToDateInView($event, 'year')\"></upd-calendar-multi-year-view>-->\n }\n}\n" }]
1560
- }], propDecorators: { monthView: [{
1561
- type: ViewChild,
1562
- args: [CalendarMonthViewComponent]
1563
- }], yearView: [{
1564
- type: ViewChild,
1565
- args: [CalendarYearViewComponent]
1566
- }], multiYearView: [{
1567
- type: ViewChild,
1568
- args: [CalendarMultiYearViewComponent]
1569
- }], startAt: [{
1570
- type: Input
1571
- }], startView: [{
1572
- type: Input
1573
- }], selected: [{
1574
- type: Input
1575
- }], minDate: [{
1576
- type: Input
1577
- }], maxDate: [{
1578
- type: Input
1579
- }], dateFilter: [{
1580
- type: Input
1581
- }], dateClass: [{
1582
- type: Input
1583
- }], comparisonStart: [{
1584
- type: Input
1585
- }], comparisonEnd: [{
1586
- type: Input
1587
- }], startDateAccessibleName: [{
1588
- type: Input
1589
- }], endDateAccessibleName: [{
1590
- type: Input
1591
- }], isRange: [{
1592
- type: Input
1593
- }], selectedChange: [{
1594
- type: Output
1595
- }], yearSelected: [{
1596
- type: Output
1597
- }], monthSelected: [{
1598
- type: Output
1599
- }], viewChanged: [{
1600
- type: Output
1601
- }], userSelection: [{
1602
- type: Output
1603
- }], userDragDrop: [{
1604
- type: Output
1605
- }] } });
441
+ args: [{ selector: 'upd-calendar', template: "<div class=\"calendar\" [class.calendar-border]=\"!shouldRemoveExternalBorder()\">\n <div class=\"calendar-header\">\n <button type=\"button\" class=\"btn btn-sm btn-ghost-secondary\" (click)=\"onPreviousClick()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\"\n stroke=\"currentColor\" fill=\"none\">\n <path stroke=\"none\" d=\"m0 0h24v24H0z\" fill=\"none\" />\n <path d=\"m15 6-6 6 6 6\" />\n </svg>\n </button>\n\n <button type=\"button\" class=\"btn btn-sm btn-ghost-secondary calendar-title\" (click)=\"onHeaderClick()\">\n @switch (currentView()) {\n @case (CalendarViewEnum.Days) {\n {{ currentMonthName() }}\n }\n @case (CalendarViewEnum.Months) {\n {{ currentYear() }}\n }\n @case (CalendarViewEnum.Years) {\n {{ yearRange().start }} - {{ yearRange().end }}\n }\n }\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon ms-1\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" stroke-width=\"2\"\n stroke=\"currentColor\" fill=\"none\">\n <path stroke=\"none\" d=\"m0 0h24v24H0z\" fill=\"none\" />\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <button type=\"button\" class=\"btn btn-sm btn-ghost-secondary\" (click)=\"onNextClick()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" stroke-width=\"2\"\n stroke=\"currentColor\" fill=\"none\">\n <path stroke=\"none\" d=\"m0 0h24v24H0z\" fill=\"none\" />\n <path d=\"m9 6 6 6-6 6\" />\n </svg>\n </button>\n </div>\n\n @if (currentView() === CalendarViewEnum.Days) {\n <div class=\"calendar-weekdays\">\n @for (dayName of weekDayNames(); track dayName) {\n <div class=\"calendar-weekday\">{{ dayName }}</div>\n }\n </div>\n\n <div class=\"calendar-days\">\n @for (day of calendarDays(); track day.date.getTime()) {\n <button type=\"button\" class=\"calendar-day\" [class.calendar-day-other-month]=\"!day.isCurrentMonth\"\n [class.calendar-day-today]=\"day.isToday\" [class.calendar-day-selected]=\"day.isSelected\"\n [class.calendar-day-in-range]=\"day.isInRange\" [class.calendar-day-range-start]=\"day.isRangeStart\"\n [class.calendar-day-range-end]=\"day.isRangeEnd\" [disabled]=\"day.isDisabled\" (click)=\"onDateClick(day)\">\n {{ day.date.getDate() }}\n </button>\n }\n </div>\n }\n\n @if (currentView() === CalendarViewEnum.Months) {\n <div class=\"calendar-months\">\n @for (month of calendarMonths(); track month.monthIndex) {\n <button type=\"button\" class=\"calendar-month\" [class.calendar-month-current]=\"month.isCurrentMonth\"\n [class.calendar-month-selected]=\"month.isSelected\" [disabled]=\"month.isDisabled\" (click)=\"onMonthClick(month)\">\n {{ month.name }}\n </button>\n }\n </div>\n }\n\n @if (currentView() === CalendarViewEnum.Years) {\n <div class=\"calendar-years\">\n @for (year of calendarYears(); track year.year) {\n <button type=\"button\" class=\"calendar-year\" [class.calendar-year-current]=\"year.isCurrentYear\"\n [class.calendar-year-selected]=\"year.isSelected\" [disabled]=\"year.isDisabled\" (click)=\"onYearClick(year)\">\n {{ year.year }}\n </button>\n }\n </div>\n }\n</div>\n", styles: [".calendar{background:var(--bs-body-bg);padding:1rem;width:100%;height:100%;border:0!important}.calendar-border{border:1px solid var(--bs-border-color)!important;border-radius:var(--bs-border-radius, .375rem);box-shadow:var(--bs-box-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075))}.calendar-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.calendar-header .calendar-title{font-weight:600;flex:1;text-align:center;display:flex;align-items:center;justify-content:center;cursor:pointer}.calendar-header .calendar-title:hover{color:var(--bs-primary)}.calendar-header button{background:transparent;border:none;padding:.25rem;border-radius:var(--bs-border-radius-sm, .25rem);cursor:pointer;color:var(--bs-secondary)}.calendar-header button:hover{background-color:var(--bs-gray-100);color:var(--bs-gray-700)}.calendar-weekdays{display:grid;grid-template-columns:repeat(7,1fr);gap:.25rem;margin-bottom:.5rem}.calendar-weekday{text-align:center;font-size:.75rem;font-weight:600;color:var(--bs-secondary);padding:.25rem;text-transform:uppercase}.calendar-days{display:grid;grid-template-columns:repeat(7,1fr);gap:.125rem}.calendar-day{aspect-ratio:1;border:none;background:transparent;border-radius:var(--bs-border-radius-sm, .25rem);font-size:.875rem;cursor:pointer;transition:all .15s ease-in-out;display:flex;align-items:center;justify-content:center;min-height:32px;font-weight:400;position:relative}.calendar-day:hover:not(:disabled):not(.calendar-day-other-month){background-color:var(--bs-gray-100)}.calendar-day:disabled{cursor:not-allowed}.calendar-day.calendar-day-other-month{color:var(--bs-gray-400, #adb5bd);cursor:default;pointer-events:none}.calendar-day.calendar-day-other-month:hover{background-color:transparent}.calendar-day.calendar-day-today{background-color:var(--bs-primary-bg-subtle, rgba(13, 110, 253, .1));color:var(--bs-primary, #0d6efd);font-weight:600}.calendar-day.calendar-day-today:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-border-subtle, rgba(13, 110, 253, .2))}.calendar-day.calendar-day-selected{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600}.calendar-day.calendar-day-selected:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day.calendar-day-in-range{border-radius:0;background-color:var(--bs-secondary-bg-subtle, rgba(108, 117, 125, .1));color:var(--bs-secondary-emphasis, #495057)}.calendar-day.calendar-day-in-range:hover:not(.calendar-day-other-month){background-color:var(--bs-secondary-border-subtle, rgba(108, 117, 125, .2))}.calendar-day.calendar-day-range-start{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600;border-top-right-radius:0;border-bottom-right-radius:0}.calendar-day.calendar-day-range-start:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day.calendar-day-range-end{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600;border-top-left-radius:0;border-bottom-left-radius:0}.calendar-day.calendar-day-range-end:hover:not(.calendar-day-other-month){background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day.calendar-day-range-start.calendar-day-range-end{border-radius:var(--bs-border-radius-sm, .25rem)}.calendar-day.calendar-day-other-month.calendar-day-selected{background-color:transparent;color:var(--bs-gray-400, #adb5bd);font-weight:400}.calendar-months{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem}.calendar-month{padding:.75rem .5rem;border:none;background:transparent;border-radius:var(--bs-border-radius-sm, .25rem);font-size:.875rem;font-weight:500;cursor:pointer;transition:all .15s ease-in-out}.calendar-month:hover:not(:disabled){background-color:var(--bs-gray-100, #f8f9fa)}.calendar-month:disabled{color:var(--bs-gray-400, #adb5bd);cursor:not-allowed}.calendar-month.calendar-month-current{background-color:var(--bs-primary-bg-subtle, rgba(13, 110, 253, .1));color:var(--bs-primary, #0d6efd);font-weight:600}.calendar-month.calendar-month-selected{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600}.calendar-month.calendar-month-selected:hover{background-color:var(--bs-primary-dark, #0a58ca)}.calendar-years{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem}.calendar-year{padding:.75rem .5rem;border:none;background:transparent;border-radius:var(--bs-border-radius-sm, .25rem);font-size:.875rem;font-weight:500;cursor:pointer;transition:all .15s ease-in-out}.calendar-year:hover:not(:disabled){background-color:var(--bs-gray-100, #f8f9fa)}.calendar-year:disabled{color:var(--bs-gray-400, #adb5bd);cursor:not-allowed}.calendar-year.calendar-year-current{background-color:var(--bs-primary-bg-subtle, rgba(13, 110, 253, .1));color:var(--bs-primary, #0d6efd);font-weight:600}.calendar-year.calendar-year-selected{background-color:var(--bs-primary, #0d6efd);color:var(--bs-white, white);font-weight:600}.calendar-year.calendar-year-selected:hover{background-color:var(--bs-primary-dark, #0a58ca)}.calendar-day-not-selectable{pointer-events:none;opacity:.5}\n"] }]
442
+ }] });
1606
443
 
1607
444
  var UpDevs$1 = {
1608
445
  Calendar: {
@@ -1652,24 +489,63 @@ var pt$1 = /*#__PURE__*/Object.freeze({
1652
489
  default: pt
1653
490
  });
1654
491
 
492
+ class CalendarSignalsService {
493
+ constructor() {
494
+ this.minDateSignal = signal(undefined);
495
+ this.maxDateSignal = signal(undefined);
496
+ this.activeDateSignal = signal(new Date());
497
+ this.selectedDateSignal = signal(undefined);
498
+ this.currentViewSignal = signal('month');
499
+ }
500
+ get minDate() {
501
+ return this.minDateSignal.asReadonly();
502
+ }
503
+ set minDate(value) {
504
+ this.minDateSignal.set(value);
505
+ }
506
+ get maxDate() {
507
+ return this.maxDateSignal.asReadonly();
508
+ }
509
+ set maxDate(value) {
510
+ this.maxDateSignal.set(value);
511
+ }
512
+ get activeDate() {
513
+ return this.activeDateSignal.asReadonly();
514
+ }
515
+ set activeDate(value) {
516
+ this.activeDateSignal.set(value);
517
+ }
518
+ get selectedDate() {
519
+ return this.selectedDateSignal.asReadonly();
520
+ }
521
+ set selectedDate(value) {
522
+ this.selectedDateSignal.set(value);
523
+ }
524
+ get currentView() {
525
+ return this.currentViewSignal.asReadonly();
526
+ }
527
+ set currentView(value) {
528
+ this.currentViewSignal.set(value);
529
+ }
530
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarSignalsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
531
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarSignalsService }); }
532
+ }
533
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: CalendarSignalsService, decorators: [{
534
+ type: Injectable
535
+ }] });
536
+
1655
537
  class UpdCalendarModule {
1656
538
  constructor(translocoService) {
1657
539
  translocoService.setFallbackLangForMissingTranslation({ fallbackLang: 'en' });
1658
540
  translocoService.setTranslation(en$1, 'en');
1659
541
  translocoService.setTranslation(pt$1, 'pt');
1660
542
  }
1661
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: UpdCalendarModule, deps: [{ token: i1$2.TranslocoService }], target: i0.ɵɵFactoryTarget.NgModule }); }
1662
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.1.0", ngImport: i0, type: UpdCalendarModule, declarations: [CalendarHeaderComponent,
1663
- CalendarMultiYearViewComponent,
1664
- CalendarYearViewComponent,
1665
- CalendarMonthViewComponent,
1666
- CalendarBodyComponent,
1667
- CalendarComponent], imports: [CommonModule,
543
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: UpdCalendarModule, deps: [{ token: i1.TranslocoService }], target: i0.ɵɵFactoryTarget.NgModule }); }
544
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.1.0", ngImport: i0, type: UpdCalendarModule, declarations: [CalendarComponent], imports: [CommonModule,
1668
545
  UpdButtonModule,
1669
546
  TranslocoDirective,
1670
547
  UpdIconsModule], exports: [CalendarComponent] }); }
1671
548
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: UpdCalendarModule, providers: [
1672
- CalendarService,
1673
549
  CalendarSignalsService,
1674
550
  CalendarRangeStrategyService
1675
551
  ], imports: [CommonModule,
@@ -1686,27 +562,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImpor
1686
562
  UpdIconsModule
1687
563
  ],
1688
564
  declarations: [
1689
- CalendarHeaderComponent,
1690
- CalendarMultiYearViewComponent,
1691
- CalendarYearViewComponent,
1692
- CalendarMonthViewComponent,
1693
- CalendarBodyComponent,
1694
565
  CalendarComponent
1695
566
  ],
1696
567
  exports: [
1697
568
  CalendarComponent
1698
569
  ],
1699
570
  providers: [
1700
- CalendarService,
1701
571
  CalendarSignalsService,
1702
572
  CalendarRangeStrategyService
1703
573
  ]
1704
574
  }]
1705
- }], ctorParameters: () => [{ type: i1$2.TranslocoService }] });
575
+ }], ctorParameters: () => [{ type: i1.TranslocoService }] });
1706
576
 
1707
577
  /**
1708
578
  * Generated bundle index. Do not edit.
1709
579
  */
1710
580
 
1711
- export { CalendarComponent, CalendarRangeStrategyService, DateRangeModel, UpdCalendarModule };
581
+ export { CalendarComponent, CalendarRangeStrategyService, CalendarViewEnum, DateRangeModel, UpdCalendarModule };
1712
582
  //# sourceMappingURL=updevs-components-calendar.mjs.map