ngx-com 0.0.21 → 0.1.0

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 (114) hide show
  1. package/README.md +137 -33
  2. package/fesm2022/ngx-com-components-alert.mjs +21 -11
  3. package/fesm2022/ngx-com-components-alert.mjs.map +1 -1
  4. package/fesm2022/ngx-com-components-avatar.mjs +9 -7
  5. package/fesm2022/ngx-com-components-avatar.mjs.map +1 -1
  6. package/fesm2022/ngx-com-components-button.mjs +1 -1
  7. package/fesm2022/ngx-com-components-button.mjs.map +1 -1
  8. package/fesm2022/ngx-com-components-calendar.mjs +27 -3112
  9. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -1
  10. package/fesm2022/ngx-com-components-card.mjs +8 -8
  11. package/fesm2022/ngx-com-components-card.mjs.map +1 -1
  12. package/fesm2022/ngx-com-components-carousel.mjs +16 -4
  13. package/fesm2022/ngx-com-components-carousel.mjs.map +1 -1
  14. package/fesm2022/ngx-com-components-checkbox.mjs +1 -1
  15. package/fesm2022/ngx-com-components-checkbox.mjs.map +1 -1
  16. package/fesm2022/ngx-com-components-code-block.mjs +9 -9
  17. package/fesm2022/ngx-com-components-code-block.mjs.map +1 -1
  18. package/fesm2022/ngx-com-components-collapsible.mjs +15 -13
  19. package/fesm2022/ngx-com-components-collapsible.mjs.map +1 -1
  20. package/fesm2022/ngx-com-components-confirm.mjs +4 -4
  21. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -1
  22. package/fesm2022/ngx-com-components-datepicker.mjs +2334 -0
  23. package/fesm2022/ngx-com-components-datepicker.mjs.map +1 -0
  24. package/fesm2022/ngx-com-components-dialog.mjs +47 -45
  25. package/fesm2022/ngx-com-components-dialog.mjs.map +1 -1
  26. package/fesm2022/ngx-com-components-dropdown.mjs +446 -340
  27. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -1
  28. package/fesm2022/ngx-com-components-empty-state.mjs +5 -3
  29. package/fesm2022/ngx-com-components-empty-state.mjs.map +1 -1
  30. package/fesm2022/ngx-com-components-form-field.mjs +11 -6
  31. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -1
  32. package/fesm2022/ngx-com-components-icon-lucide.mjs +41 -0
  33. package/fesm2022/ngx-com-components-icon-lucide.mjs.map +1 -0
  34. package/fesm2022/ngx-com-components-icon.mjs +89 -61
  35. package/fesm2022/ngx-com-components-icon.mjs.map +1 -1
  36. package/fesm2022/ngx-com-components-item.mjs +14 -4
  37. package/fesm2022/ngx-com-components-item.mjs.map +1 -1
  38. package/fesm2022/ngx-com-components-menu.mjs +61 -69
  39. package/fesm2022/ngx-com-components-menu.mjs.map +1 -1
  40. package/fesm2022/ngx-com-components-native-control.mjs +170 -0
  41. package/fesm2022/ngx-com-components-native-control.mjs.map +1 -0
  42. package/fesm2022/ngx-com-components-paginator.mjs +11 -3
  43. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -1
  44. package/fesm2022/ngx-com-components-popover.mjs +58 -33
  45. package/fesm2022/ngx-com-components-popover.mjs.map +1 -1
  46. package/fesm2022/ngx-com-components-radio.mjs +4 -4
  47. package/fesm2022/ngx-com-components-radio.mjs.map +1 -1
  48. package/fesm2022/ngx-com-components-segmented-control.mjs +6 -4
  49. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -1
  50. package/fesm2022/ngx-com-components-sort.mjs +63 -57
  51. package/fesm2022/ngx-com-components-sort.mjs.map +1 -1
  52. package/fesm2022/ngx-com-components-spinner.mjs +6 -6
  53. package/fesm2022/ngx-com-components-spinner.mjs.map +1 -1
  54. package/fesm2022/ngx-com-components-switch.mjs +18 -9
  55. package/fesm2022/ngx-com-components-switch.mjs.map +1 -1
  56. package/fesm2022/ngx-com-components-table.mjs +23 -9
  57. package/fesm2022/ngx-com-components-table.mjs.map +1 -1
  58. package/fesm2022/ngx-com-components-tabs.mjs +81 -58
  59. package/fesm2022/ngx-com-components-tabs.mjs.map +1 -1
  60. package/fesm2022/ngx-com-components-timepicker.mjs +1048 -0
  61. package/fesm2022/ngx-com-components-timepicker.mjs.map +1 -0
  62. package/fesm2022/ngx-com-components-toast.mjs +18 -14
  63. package/fesm2022/ngx-com-components-toast.mjs.map +1 -1
  64. package/fesm2022/ngx-com-components-tooltip.mjs +5 -5
  65. package/fesm2022/ngx-com-components-tooltip.mjs.map +1 -1
  66. package/fesm2022/ngx-com-components.mjs +0 -13
  67. package/fesm2022/ngx-com-components.mjs.map +1 -1
  68. package/fesm2022/ngx-com-tokens.mjs +0 -8
  69. package/fesm2022/ngx-com-tokens.mjs.map +1 -1
  70. package/fesm2022/ngx-com-utils.mjs +13 -1
  71. package/fesm2022/ngx-com-utils.mjs.map +1 -1
  72. package/fesm2022/ngx-com.mjs +1 -1
  73. package/fesm2022/ngx-com.mjs.map +1 -1
  74. package/package.json +51 -8
  75. package/styles/animations.css +38 -0
  76. package/styles/candy.css +121 -0
  77. package/styles/dark.css +159 -0
  78. package/styles/forest.css +117 -0
  79. package/styles/ocean.css +117 -0
  80. package/styles/themes.css +7 -0
  81. package/styles/tokens.css +277 -0
  82. package/styles/utilities.css +16 -0
  83. package/types/ngx-com-components-alert.d.ts +14 -4
  84. package/types/ngx-com-components-avatar.d.ts +2 -0
  85. package/types/ngx-com-components-calendar.d.ts +3 -747
  86. package/types/ngx-com-components-card.d.ts +2 -2
  87. package/types/ngx-com-components-carousel.d.ts +11 -1
  88. package/types/ngx-com-components-code-block.d.ts +4 -4
  89. package/types/ngx-com-components-collapsible.d.ts +10 -2
  90. package/types/ngx-com-components-confirm.d.ts +2 -2
  91. package/types/ngx-com-components-datepicker.d.ts +623 -0
  92. package/types/ngx-com-components-dialog.d.ts +5 -2
  93. package/types/ngx-com-components-dropdown.d.ts +22 -4
  94. package/types/ngx-com-components-empty-state.d.ts +2 -0
  95. package/types/ngx-com-components-form-field.d.ts +4 -1
  96. package/types/ngx-com-components-icon-lucide.d.ts +32 -0
  97. package/types/ngx-com-components-icon.d.ts +49 -35
  98. package/types/ngx-com-components-item.d.ts +12 -2
  99. package/types/ngx-com-components-menu.d.ts +38 -38
  100. package/types/ngx-com-components-native-control.d.ts +99 -0
  101. package/types/ngx-com-components-paginator.d.ts +2 -0
  102. package/types/ngx-com-components-popover.d.ts +19 -12
  103. package/types/ngx-com-components-segmented-control.d.ts +3 -1
  104. package/types/ngx-com-components-sort.d.ts +13 -10
  105. package/types/ngx-com-components-switch.d.ts +7 -2
  106. package/types/ngx-com-components-table.d.ts +16 -2
  107. package/types/ngx-com-components-tabs.d.ts +46 -34
  108. package/types/ngx-com-components-timepicker.d.ts +273 -0
  109. package/types/ngx-com-components-toast.d.ts +4 -2
  110. package/types/ngx-com-components-tooltip.d.ts +1 -1
  111. package/types/ngx-com-components.d.ts +6 -7
  112. package/types/ngx-com-tokens.d.ts +5 -3
  113. package/types/ngx-com-utils.d.ts +11 -1
  114. package/types/ngx-com.d.ts +1 -1
@@ -0,0 +1,2334 @@
1
+ import { cva } from 'class-variance-authority';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, ElementRef, DestroyRef, ViewContainerRef, DOCUMENT, viewChild, model, input, output, signal, linkedSignal, computed, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { NgControl, NgForm, FormGroupDirective } from '@angular/forms';
6
+ import { Overlay, OverlayModule } from '@angular/cdk/overlay';
7
+ import { TemplatePortal } from '@angular/cdk/portal';
8
+ import * as i1 from '@angular/cdk/a11y';
9
+ import { A11yModule } from '@angular/cdk/a11y';
10
+ import { DATE_ADAPTER, ComCalendar, SingleSelectionStrategy, CALENDAR_SELECTION_STRATEGY, RangeSelectionStrategy } from 'ngx-com/components/calendar';
11
+ import { ComIcon } from 'ngx-com/components/icon';
12
+ import { mergeClasses, joinClasses } from 'ngx-com/utils';
13
+ import { ErrorStateMatcher, FormFieldControl } from 'ngx-com/components/form-field';
14
+ import { timepickerSectionVariants, createTimeValue, ComTimePicker, timepickerLabelVariants } from 'ngx-com/components/timepicker';
15
+
16
+ /**
17
+ * Types and interfaces for DatePicker and DateRangePicker components.
18
+ */
19
+ /** Creates a DateRangeValue */
20
+ function createDateRangeValue(start = null, end = null) {
21
+ return { start, end };
22
+ }
23
+ /** Generates a unique ID for datepicker instances */
24
+ let datepickerIdCounter = 0;
25
+ function generateDatepickerId() {
26
+ return `com-datepicker-${datepickerIdCounter++}`;
27
+ }
28
+
29
+ /**
30
+ * CVA variants for the datepicker trigger input.
31
+ * Uses semantic theme tokens for consistent cross-theme styling.
32
+ *
33
+ * @tokens `--color-input-background`, `--color-input-foreground`, `--color-input-border`,
34
+ * `--color-input-placeholder`, `--color-ring`, `--color-muted`, `--color-muted-hover`,
35
+ * `--color-warn`, `--color-success`, `--color-primary`, `--color-border`,
36
+ * `--color-disabled`, `--color-disabled-foreground`, `--radius-input`
37
+ */
38
+ const datepickerTriggerVariants = cva([
39
+ 'inline-flex',
40
+ 'items-center',
41
+ 'justify-between',
42
+ 'w-full',
43
+ 'rounded-input',
44
+ 'border',
45
+ 'bg-input-background',
46
+ 'text-input-foreground',
47
+ 'transition-colors',
48
+ 'duration-normal',
49
+ 'placeholder:text-input-placeholder',
50
+ 'focus-within:outline-[1px]',
51
+ 'focus-within:outline-offset-2',
52
+ 'focus-within:outline-ring',
53
+ 'cursor-pointer',
54
+ ], {
55
+ variants: {
56
+ variant: {
57
+ default: [
58
+ 'border-input-border',
59
+ 'hover:border-border',
60
+ ],
61
+ outline: [
62
+ 'border-2',
63
+ 'border-input-border',
64
+ 'hover:border-foreground',
65
+ ],
66
+ ghost: [
67
+ 'border-transparent',
68
+ 'bg-transparent',
69
+ 'hover:bg-muted',
70
+ ],
71
+ filled: [
72
+ 'border-transparent',
73
+ 'bg-muted',
74
+ 'hover:bg-muted-hover',
75
+ ],
76
+ naked: [
77
+ 'border-transparent',
78
+ 'bg-transparent',
79
+ 'shadow-none',
80
+ 'focus-within:outline-none',
81
+ 'rounded-none',
82
+ ],
83
+ },
84
+ size: {
85
+ sm: ['h-8', 'px-2', 'text-xs', 'gap-1'],
86
+ default: ['h-10', 'px-3', 'text-sm', 'gap-2'],
87
+ lg: ['h-12', 'px-4', 'text-base', 'gap-3'],
88
+ },
89
+ state: {
90
+ default: [],
91
+ error: [
92
+ 'border-warn',
93
+ 'focus-within:outline-warn',
94
+ ],
95
+ success: [
96
+ 'border-success',
97
+ 'focus-within:outline-success',
98
+ ],
99
+ },
100
+ open: {
101
+ true: ['outline-[1px]', 'outline-ring', 'border-primary'],
102
+ false: [],
103
+ },
104
+ },
105
+ compoundVariants: [
106
+ {
107
+ open: true,
108
+ variant: 'default',
109
+ class: ['border-primary'],
110
+ },
111
+ {
112
+ open: true,
113
+ variant: 'outline',
114
+ class: ['border-primary'],
115
+ },
116
+ // Naked variant should not show ring when open (form-field provides focus styling)
117
+ {
118
+ open: true,
119
+ variant: 'naked',
120
+ class: ['outline-none', 'border-transparent'],
121
+ },
122
+ // Naked variant should not show error border (form-field provides error styling)
123
+ {
124
+ state: 'error',
125
+ variant: 'naked',
126
+ class: ['border-transparent', 'focus-within:outline-none'],
127
+ },
128
+ // Naked variant should not show success border (form-field provides styling)
129
+ {
130
+ state: 'success',
131
+ variant: 'naked',
132
+ class: ['border-transparent', 'focus-within:outline-none'],
133
+ },
134
+ ],
135
+ defaultVariants: {
136
+ variant: 'default',
137
+ size: 'default',
138
+ state: 'default',
139
+ open: false,
140
+ },
141
+ });
142
+ /**
143
+ * CVA variants for the disabled state of datepicker trigger.
144
+ *
145
+ * @tokens `--color-disabled`, `--color-disabled-foreground`
146
+ */
147
+ const datepickerDisabledVariants = cva([
148
+ 'cursor-not-allowed',
149
+ 'bg-disabled',
150
+ 'text-disabled-foreground',
151
+ 'hover:border-input-border',
152
+ 'pointer-events-none',
153
+ ]);
154
+ /**
155
+ * CVA variants for the datepicker input field.
156
+ *
157
+ * @tokens `--color-input-foreground`, `--color-input-placeholder`
158
+ */
159
+ const datepickerInputVariants = cva([
160
+ 'flex-1',
161
+ 'bg-transparent',
162
+ 'outline-none',
163
+ 'placeholder:text-input-placeholder',
164
+ 'disabled:cursor-not-allowed',
165
+ 'min-w-0',
166
+ 'truncate',
167
+ ], {
168
+ variants: {
169
+ size: {
170
+ sm: ['text-xs'],
171
+ default: ['text-sm'],
172
+ lg: ['text-base'],
173
+ },
174
+ },
175
+ defaultVariants: {
176
+ size: 'default',
177
+ },
178
+ });
179
+ /**
180
+ * CVA variants for the calendar icon button.
181
+ *
182
+ * @tokens `--color-ring`, `--color-muted-foreground`, `--radius-interactive-sm`
183
+ */
184
+ const datepickerIconVariants = cva([
185
+ 'inline-flex',
186
+ 'items-center',
187
+ 'justify-center',
188
+ 'shrink-0',
189
+ 'text-muted-foreground',
190
+ 'transition-colors',
191
+ 'hover:text-foreground',
192
+ 'focus-visible:outline-[1px]',
193
+ 'focus-visible:outline-offset-2',
194
+ 'focus-visible:outline-ring',
195
+ 'rounded-interactive-sm',
196
+ ], {
197
+ variants: {
198
+ size: {
199
+ sm: ['h-4', 'w-4'],
200
+ default: ['h-5', 'w-5'],
201
+ lg: ['h-6', 'w-6'],
202
+ },
203
+ },
204
+ defaultVariants: {
205
+ size: 'default',
206
+ },
207
+ });
208
+ /**
209
+ * CVA variants for the clear button.
210
+ *
211
+ * @tokens `--color-ring`, `--color-muted-foreground`, `--color-foreground`, `--radius-interactive-sm`
212
+ */
213
+ const datepickerClearVariants = cva([
214
+ 'inline-flex',
215
+ 'items-center',
216
+ 'justify-center',
217
+ 'rounded-interactive-sm',
218
+ 'text-muted-foreground',
219
+ 'transition-colors',
220
+ 'hover:text-foreground',
221
+ 'focus-visible:outline-[1px]',
222
+ 'focus-visible:outline-offset-2',
223
+ 'focus-visible:outline-ring',
224
+ 'shrink-0',
225
+ ], {
226
+ variants: {
227
+ size: {
228
+ sm: ['h-4', 'w-4'],
229
+ default: ['h-5', 'w-5'],
230
+ lg: ['h-6', 'w-6'],
231
+ },
232
+ },
233
+ defaultVariants: {
234
+ size: 'default',
235
+ },
236
+ });
237
+ /**
238
+ * CVA variants for the datepicker panel (overlay).
239
+ *
240
+ * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border-subtle`, `--radius-overlay`
241
+ */
242
+ const datepickerPanelVariants = cva([
243
+ 'z-50',
244
+ 'overflow-hidden',
245
+ 'rounded-overlay',
246
+ 'border',
247
+ 'border-border-subtle',
248
+ 'bg-popover',
249
+ 'text-popover-foreground',
250
+ 'shadow-overlay',
251
+ 'outline-none',
252
+ ], {
253
+ variants: {
254
+ size: {
255
+ sm: ['p-2', 'text-xs'],
256
+ default: ['p-3', 'text-sm'],
257
+ lg: ['p-4', 'text-base'],
258
+ },
259
+ },
260
+ defaultVariants: {
261
+ size: 'default',
262
+ },
263
+ });
264
+ /**
265
+ * CVA variants for the footer section of the datepicker panel.
266
+ *
267
+ * @tokens `--color-border-subtle`
268
+ */
269
+ const datepickerFooterVariants = cva([
270
+ 'flex',
271
+ 'items-center',
272
+ 'justify-between',
273
+ 'border-t',
274
+ 'border-border-subtle',
275
+ 'mt-3',
276
+ 'pt-3',
277
+ ], {
278
+ variants: {
279
+ size: {
280
+ sm: ['mt-2', 'pt-2', 'gap-1'],
281
+ default: ['mt-3', 'pt-3', 'gap-2'],
282
+ lg: ['mt-4', 'pt-4', 'gap-3'],
283
+ },
284
+ },
285
+ defaultVariants: {
286
+ size: 'default',
287
+ },
288
+ });
289
+ /**
290
+ * CVA variants for the footer buttons.
291
+ *
292
+ * @tokens `--color-primary`, `--color-primary-foreground`, `--color-primary-hover`,
293
+ * `--color-muted`, `--color-muted-foreground`, `--color-muted-hover`, `--radius-control-sm`
294
+ */
295
+ const datepickerFooterButtonVariants = cva([
296
+ 'inline-flex',
297
+ 'items-center',
298
+ 'justify-center',
299
+ 'rounded-control-sm',
300
+ 'font-medium',
301
+ 'transition-colors',
302
+ 'focus-visible:outline-[1px]',
303
+ 'focus-visible:outline-offset-2',
304
+ 'focus-visible:outline-ring',
305
+ ], {
306
+ variants: {
307
+ size: {
308
+ sm: ['h-7', 'px-2', 'text-xs'],
309
+ default: ['h-8', 'px-3', 'text-sm'],
310
+ lg: ['h-9', 'px-4', 'text-base'],
311
+ },
312
+ variant: {
313
+ primary: [
314
+ 'bg-primary',
315
+ 'text-primary-foreground',
316
+ 'hover:bg-primary-hover',
317
+ ],
318
+ secondary: [
319
+ 'bg-muted',
320
+ 'text-muted-foreground',
321
+ 'hover:bg-muted-hover',
322
+ ],
323
+ },
324
+ },
325
+ defaultVariants: {
326
+ size: 'default',
327
+ variant: 'secondary',
328
+ },
329
+ });
330
+ /**
331
+ * CVA variants for the range separator.
332
+ *
333
+ * @tokens `--color-muted-foreground`
334
+ */
335
+ const datepickerRangeSeparatorVariants = cva([
336
+ 'inline-flex',
337
+ 'items-center',
338
+ 'justify-center',
339
+ 'text-muted-foreground',
340
+ 'shrink-0',
341
+ ], {
342
+ variants: {
343
+ size: {
344
+ sm: ['px-1', 'text-xs'],
345
+ default: ['px-2', 'text-sm'],
346
+ lg: ['px-3', 'text-base'],
347
+ },
348
+ },
349
+ defaultVariants: {
350
+ size: 'default',
351
+ },
352
+ });
353
+
354
+ /** Default position for the datepicker panel. */
355
+ const DEFAULT_POSITIONS$1 = [
356
+ // Below trigger, aligned start
357
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
358
+ // Above trigger, aligned start
359
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
360
+ // Below trigger, aligned end
361
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
362
+ // Above trigger, aligned end
363
+ { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -4 },
364
+ ];
365
+ /**
366
+ * Single date picker component with calendar popup.
367
+ * Implements ControlValueAccessor for Reactive Forms and Template-driven Forms.
368
+ *
369
+ * @tokens `--color-input-background`, `--color-input-foreground`, `--color-input-border`,
370
+ * `--color-input-placeholder`, `--color-ring`, `--color-muted`, `--color-muted-foreground`,
371
+ * `--color-popover`, `--color-popover-foreground`, `--color-border-subtle`,
372
+ * `--color-primary`, `--color-primary-foreground`, `--color-primary-hover`,
373
+ * `--color-warn`, `--color-success`, `--color-disabled`, `--color-disabled-foreground`
374
+ *
375
+ * @example
376
+ * ```html
377
+ * <com-datepicker
378
+ * formControlName="birthDate"
379
+ * placeholder="Select date..."
380
+ * [min]="minDate"
381
+ * [max]="maxDate"
382
+ * [showTodayButton]="true"
383
+ * [showClearButton]="true"
384
+ * />
385
+ * ```
386
+ */
387
+ class ComDatepicker {
388
+ elementRef = inject(ElementRef);
389
+ destroyRef = inject(DestroyRef);
390
+ overlay = inject(Overlay);
391
+ viewContainerRef = inject(ViewContainerRef);
392
+ document = inject(DOCUMENT);
393
+ dateAdapter = inject(DATE_ADAPTER);
394
+ /** NgControl bound to this datepicker (if using reactive forms). */
395
+ ngControl = inject(NgControl, { optional: true, self: true });
396
+ defaultErrorStateMatcher = inject(ErrorStateMatcher);
397
+ parentForm = inject(NgForm, { optional: true });
398
+ parentFormGroup = inject(FormGroupDirective, { optional: true });
399
+ /** Reference to the trigger element. */
400
+ triggerRef = viewChild.required('triggerElement');
401
+ /** Reference to the input element. */
402
+ inputRef = viewChild.required('inputElement');
403
+ /** Reference to the panel template. */
404
+ panelTemplateRef = viewChild.required('panelTemplate');
405
+ /** Overlay reference. */
406
+ overlayRef = null;
407
+ /** Unique ID for the datepicker. */
408
+ datepickerId = generateDatepickerId();
409
+ // ============ INPUTS ============
410
+ /** Current value. */
411
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
412
+ /** Minimum selectable date. */
413
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
414
+ /** Maximum selectable date. */
415
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
416
+ /** Custom filter function to disable specific dates. */
417
+ dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : []));
418
+ /** Date the calendar opens to (defaults to selected or today). */
419
+ startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : []));
420
+ /** Initial calendar view. */
421
+ startView = input('month', ...(ngDevMode ? [{ debugName: "startView" }] : []));
422
+ /** First day of week override (0=Sun, 1=Mon, ..., 6=Sat). */
423
+ firstDayOfWeek = input(null, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : []));
424
+ /** Placeholder text. */
425
+ placeholder = input('Select date...', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
426
+ /** Whether the datepicker is disabled. */
427
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
428
+ /** Whether the datepicker is required. */
429
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
430
+ /** Display format for the date. */
431
+ dateFormat = input('medium', ...(ngDevMode ? [{ debugName: "dateFormat" }] : []));
432
+ /** Show a clear button in the trigger. */
433
+ showClearButton = input(false, ...(ngDevMode ? [{ debugName: "showClearButton" }] : []));
434
+ /** Show a today button in the footer. */
435
+ showTodayButton = input(false, ...(ngDevMode ? [{ debugName: "showTodayButton" }] : []));
436
+ /** Show a clear button in the footer. */
437
+ showFooterClearButton = input(false, ...(ngDevMode ? [{ debugName: "showFooterClearButton" }] : []));
438
+ /** Don't auto-close on selection. */
439
+ keepOpen = input(false, ...(ngDevMode ? [{ debugName: "keepOpen" }] : []));
440
+ /** Allow manual text input. */
441
+ allowManualInput = input(true, ...(ngDevMode ? [{ debugName: "allowManualInput" }] : []));
442
+ /** Additional CSS classes for the panel. */
443
+ panelClass = input('', ...(ngDevMode ? [{ debugName: "panelClass" }] : []));
444
+ /** Panel width strategy. */
445
+ panelWidth = input('auto', ...(ngDevMode ? [{ debugName: "panelWidth" }] : []));
446
+ /** CVA variant for trigger styling. */
447
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : []));
448
+ /** Size variant. */
449
+ size = input('default', ...(ngDevMode ? [{ debugName: "size" }] : []));
450
+ /** Validation state. */
451
+ state = input('default', ...(ngDevMode ? [{ debugName: "state" }] : []));
452
+ /** Additional CSS classes for the trigger. */
453
+ userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : {}), alias: 'class' });
454
+ /** Accessible label for the input. */
455
+ ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
456
+ /** ID of element describing the input. */
457
+ ariaDescribedBy = input(null, ...(ngDevMode ? [{ debugName: "ariaDescribedBy" }] : []));
458
+ /** Custom error state matcher for determining when to show errors. */
459
+ errorStateMatcher = input(...(ngDevMode ? [undefined, { debugName: "errorStateMatcher" }] : []));
460
+ // Signal Forms inputs — set automatically by [formField] via setInputOnDirectives
461
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : []));
462
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : []));
463
+ sfErrors = input([], { ...(ngDevMode ? { debugName: "sfErrors" } : {}), alias: 'errors' });
464
+ /** Whether to show the time picker below the calendar. */
465
+ showTimePicker = input(false, ...(ngDevMode ? [{ debugName: "showTimePicker" }] : []));
466
+ /** 12h vs 24h format for the time picker. `null` = auto-detect. */
467
+ use12HourFormat = input(null, ...(ngDevMode ? [{ debugName: "use12HourFormat" }] : []));
468
+ /** Whether the time picker shows seconds. */
469
+ showSeconds = input(false, ...(ngDevMode ? [{ debugName: "showSeconds" }] : []));
470
+ /** Step interval for minutes in the time picker. */
471
+ minuteStep = input(1, ...(ngDevMode ? [{ debugName: "minuteStep" }] : []));
472
+ // ============ OUTPUTS ============
473
+ /** Emitted when a date is selected. */
474
+ dateChange = output();
475
+ /** Emitted when the panel opens. */
476
+ opened = output();
477
+ /** Emitted when the panel closes. */
478
+ closed = output();
479
+ // ============ INTERNAL STATE ============
480
+ /** Whether the panel is open. */
481
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
482
+ /** Internal value state (managed by CVA or input). */
483
+ internalValue = linkedSignal(() => this.value() ?? null, ...(ngDevMode ? [{ debugName: "internalValue" }] : []));
484
+ /** Calendar active date for navigation. Recomputes when value or startAt changes; user navigation overrides via .set(). */
485
+ calendarActiveDate = linkedSignal({ ...(ngDevMode ? { debugName: "calendarActiveDate" } : {}), source: () => ({ value: this.internalValue(), startAt: this.startAt() }),
486
+ computation: ({ value, startAt }) => value ?? startAt ?? this.dateAdapter.today() });
487
+ /** Live announcements for screen readers. */
488
+ liveAnnouncement = signal('', ...(ngDevMode ? [{ debugName: "liveAnnouncement" }] : []));
489
+ /** Whether the input is focused (not panel). */
490
+ _inputFocused = signal(false, ...(ngDevMode ? [{ debugName: "_inputFocused" }] : []));
491
+ /** IDs for aria-describedby (set by form-field). */
492
+ _describedByIds = signal('', ...(ngDevMode ? [{ debugName: "_describedByIds" }] : []));
493
+ /** Form field appearance (set by form-field). */
494
+ _appearance = signal('outline', ...(ngDevMode ? [{ debugName: "_appearance" }] : []));
495
+ // ============ FormFieldControl SIGNALS ============
496
+ /** Whether the datepicker is focused (input focused or panel open). Implements FormFieldControl. */
497
+ focused = computed(() => this._inputFocused() || this.isOpen(), ...(ngDevMode ? [{ debugName: "focused" }] : []));
498
+ /** Whether the label should float. Label floats when focused or has a value. */
499
+ shouldLabelFloat = computed(() => this.focused() || this.hasValue(), ...(ngDevMode ? [{ debugName: "shouldLabelFloat" }] : []));
500
+ /** Whether the control is in an error state. Implements FormFieldControl. */
501
+ errorState = computed(() => {
502
+ if (!this.ngControl) {
503
+ // Signal Forms: gate on invalid AND touched
504
+ return this.invalid() && this.touched();
505
+ }
506
+ // Reactive Forms: use ErrorStateMatcher
507
+ this.isOpen();
508
+ this.hasValue();
509
+ const matcher = this.errorStateMatcher() ?? this.defaultErrorStateMatcher;
510
+ const form = this.parentFormGroup ?? this.parentForm;
511
+ return matcher.isErrorState(this.ngControl.control ?? null, form);
512
+ }, ...(ngDevMode ? [{ debugName: "errorState" }] : []));
513
+ /** Structured validation errors from Signal Forms, exposed for the parent form field. */
514
+ errors = computed(() => !this.ngControl ? this.sfErrors() : null, ...(ngDevMode ? [{ debugName: "errors" }] : []));
515
+ /** Unique ID for the control. Implements FormFieldControl. */
516
+ id = computed(() => `${this.datepickerId}-input`, ...(ngDevMode ? [{ debugName: "id" }] : []));
517
+ /**
518
+ * Effective state combining manual state with automatic error detection.
519
+ * Manual state takes precedence over auto-detected error state.
520
+ */
521
+ effectiveState = computed(() => {
522
+ const manualState = this.state();
523
+ if (manualState !== 'default')
524
+ return manualState;
525
+ return this.errorState() ? 'error' : 'default';
526
+ }, ...(ngDevMode ? [{ debugName: "effectiveState" }] : []));
527
+ /** Combined aria-describedby from form-field and manual input. */
528
+ effectiveAriaDescribedBy = computed(() => this._describedByIds() || this.ariaDescribedBy() || null, ...(ngDevMode ? [{ debugName: "effectiveAriaDescribedBy" }] : []));
529
+ // ============ COMPUTED STATE ============
530
+ /** Input element ID (alias for FormFieldControl id). */
531
+ inputId = this.id;
532
+ /** Panel element ID. */
533
+ panelId = computed(() => `${this.datepickerId}-panel`, ...(ngDevMode ? [{ debugName: "panelId" }] : []));
534
+ /** Whether the datepicker has a value. */
535
+ hasValue = computed(() => this.internalValue() !== null, ...(ngDevMode ? [{ debugName: "hasValue" }] : []));
536
+ /** Icon size based on datepicker size. */
537
+ iconSize = computed(() => {
538
+ const sizeMap = {
539
+ sm: 'sm',
540
+ default: 'md',
541
+ lg: 'lg',
542
+ };
543
+ return sizeMap[this.size()];
544
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
545
+ /** Formatted display value. */
546
+ displayValue = computed(() => {
547
+ const value = this.internalValue();
548
+ if (!value)
549
+ return '';
550
+ return this.dateAdapter.format(value, this.effectiveDateFormat());
551
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : []));
552
+ /** Computed trigger classes. */
553
+ triggerClasses = computed(() => {
554
+ const baseClasses = datepickerTriggerVariants({
555
+ variant: this.variant(),
556
+ size: this.size(),
557
+ state: this.effectiveState(),
558
+ open: this.isOpen(),
559
+ });
560
+ const disabledClasses = this.disabled() ? datepickerDisabledVariants() : '';
561
+ // For naked variant, add padding based on form-field appearance
562
+ let paddingClasses = '';
563
+ if (this.variant() === 'naked') {
564
+ paddingClasses = this._appearance() === 'fill' ? 'pt-5 pb-1.5 px-3' : 'py-2.5 px-3';
565
+ }
566
+ return mergeClasses(baseClasses, disabledClasses, paddingClasses, this.userClass());
567
+ }, ...(ngDevMode ? [{ debugName: "triggerClasses" }] : []));
568
+ /** Computed input classes. */
569
+ inputClasses = computed(() => {
570
+ return datepickerInputVariants({ size: this.size() });
571
+ }, ...(ngDevMode ? [{ debugName: "inputClasses" }] : []));
572
+ /** Computed icon classes. */
573
+ iconClasses = computed(() => {
574
+ return datepickerIconVariants({ size: this.size() });
575
+ }, ...(ngDevMode ? [{ debugName: "iconClasses" }] : []));
576
+ /** Computed clear button classes. */
577
+ clearClasses = computed(() => {
578
+ return datepickerClearVariants({ size: this.size() });
579
+ }, ...(ngDevMode ? [{ debugName: "clearClasses" }] : []));
580
+ /** Computed panel classes. */
581
+ panelClasses = computed(() => {
582
+ const baseClasses = datepickerPanelVariants({ size: this.size() });
583
+ return joinClasses(baseClasses, this.panelClass());
584
+ }, ...(ngDevMode ? [{ debugName: "panelClasses" }] : []));
585
+ /** Computed footer classes. */
586
+ footerClasses = computed(() => {
587
+ return datepickerFooterVariants({ size: this.size() });
588
+ }, ...(ngDevMode ? [{ debugName: "footerClasses" }] : []));
589
+ /** Computed today button classes. */
590
+ todayButtonClasses = computed(() => {
591
+ return datepickerFooterButtonVariants({ size: this.size(), variant: 'primary' });
592
+ }, ...(ngDevMode ? [{ debugName: "todayButtonClasses" }] : []));
593
+ /** Computed clear button classes (footer). */
594
+ clearButtonClasses = computed(() => {
595
+ return datepickerFooterButtonVariants({ size: this.size(), variant: 'secondary' });
596
+ }, ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : []));
597
+ /** Time section divider classes. */
598
+ timeSectionClasses = computed(() => {
599
+ return timepickerSectionVariants({ size: this.size() });
600
+ }, ...(ngDevMode ? [{ debugName: "timeSectionClasses" }] : []));
601
+ /** Time value derived from the current date value. */
602
+ timeValue = computed(() => {
603
+ const date = this.internalValue();
604
+ if (!date)
605
+ return null;
606
+ return createTimeValue(this.dateAdapter.getHours(date), this.dateAdapter.getMinutes(date), this.dateAdapter.getSeconds(date));
607
+ }, ...(ngDevMode ? [{ debugName: "timeValue" }] : []));
608
+ /** Effective display format — switches to dateTime when time picker is shown. */
609
+ effectiveDateFormat = computed(() => {
610
+ if (this.showTimePicker()) {
611
+ return this.showSeconds() ? 'dateTimeLong' : 'dateTimeMedium';
612
+ }
613
+ return this.dateFormat();
614
+ }, ...(ngDevMode ? [{ debugName: "effectiveDateFormat" }] : []));
615
+ /** Whether the panel should stay open (keepOpen or time picker shown). */
616
+ effectiveKeepOpen = computed(() => {
617
+ return this.keepOpen() || this.showTimePicker();
618
+ }, ...(ngDevMode ? [{ debugName: "effectiveKeepOpen" }] : []));
619
+ // ============ CVA CALLBACKS ============
620
+ onChange = () => { };
621
+ onTouched = () => { };
622
+ onValidatorChange = () => { };
623
+ constructor() {
624
+ // Wire up NgControl if present
625
+ if (this.ngControl) {
626
+ this.ngControl.valueAccessor = this;
627
+ }
628
+ }
629
+ ngOnDestroy() {
630
+ this.destroyOverlay();
631
+ }
632
+ // ============ CVA IMPLEMENTATION ============
633
+ writeValue(value) {
634
+ this.internalValue.set(value);
635
+ }
636
+ registerOnChange(fn) {
637
+ this.onChange = fn;
638
+ }
639
+ registerOnTouched(fn) {
640
+ this.onTouched = fn;
641
+ }
642
+ setDisabledState(_isDisabled) {
643
+ // Disabled state is handled via the disabled input
644
+ }
645
+ // ============ VALIDATOR IMPLEMENTATION ============
646
+ validate() {
647
+ const value = this.internalValue();
648
+ // Required validation
649
+ if (this.required() && !value) {
650
+ return { required: true };
651
+ }
652
+ if (value) {
653
+ // Min validation
654
+ const min = this.min();
655
+ if (min && this.dateAdapter.compareDate(value, min) < 0) {
656
+ return { minDate: { min, actual: value } };
657
+ }
658
+ // Max validation
659
+ const max = this.max();
660
+ if (max && this.dateAdapter.compareDate(value, max) > 0) {
661
+ return { maxDate: { max, actual: value } };
662
+ }
663
+ // Date filter validation
664
+ const dateFilter = this.dateFilter();
665
+ if (dateFilter && !dateFilter(value)) {
666
+ return { dateFilter: true };
667
+ }
668
+ }
669
+ return null;
670
+ }
671
+ registerOnValidatorChange(fn) {
672
+ this.onValidatorChange = fn;
673
+ }
674
+ // ============ PUBLIC METHODS ============
675
+ /** Opens the datepicker panel. */
676
+ open() {
677
+ if (this.disabled() || this.isOpen()) {
678
+ return;
679
+ }
680
+ this.createOverlay();
681
+ this.isOpen.set(true);
682
+ this.opened.emit();
683
+ this.announce('Calendar opened');
684
+ }
685
+ /** Closes the datepicker panel. */
686
+ close() {
687
+ if (!this.isOpen()) {
688
+ return;
689
+ }
690
+ this.destroyOverlay();
691
+ this.isOpen.set(false);
692
+ this.closed.emit();
693
+ this.onTouched();
694
+ this.touched.set(true);
695
+ // Return focus to trigger
696
+ this.inputRef().nativeElement.focus();
697
+ }
698
+ /** Toggles the datepicker panel. */
699
+ toggle() {
700
+ if (this.isOpen()) {
701
+ this.close();
702
+ }
703
+ else {
704
+ this.open();
705
+ }
706
+ }
707
+ /** Clears the selected date. */
708
+ clear(event) {
709
+ event?.preventDefault();
710
+ event?.stopPropagation();
711
+ this.updateValue(null);
712
+ this.announce('Date cleared');
713
+ }
714
+ /** Selects today's date. */
715
+ selectToday() {
716
+ const today = this.dateAdapter.today();
717
+ this.updateValue(today);
718
+ if (!this.effectiveKeepOpen()) {
719
+ this.close();
720
+ }
721
+ }
722
+ // ============ EVENT HANDLERS ============
723
+ onInputFocus() {
724
+ this._inputFocused.set(true);
725
+ }
726
+ onTriggerClick() {
727
+ if (!this.disabled()) {
728
+ this.toggle();
729
+ }
730
+ }
731
+ onTriggerKeydown(event) {
732
+ switch (event.key) {
733
+ case 'ArrowDown':
734
+ case 'ArrowUp':
735
+ event.preventDefault();
736
+ this.open();
737
+ break;
738
+ case 'Escape':
739
+ if (this.isOpen()) {
740
+ event.preventDefault();
741
+ this.close();
742
+ }
743
+ break;
744
+ }
745
+ }
746
+ onInputKeydown(event) {
747
+ switch (event.key) {
748
+ case 'Enter':
749
+ event.preventDefault();
750
+ if (this.isOpen()) {
751
+ // Commit manual input
752
+ this.parseAndSetValue(this.inputRef().nativeElement.value);
753
+ }
754
+ else {
755
+ this.open();
756
+ }
757
+ break;
758
+ case 'Escape':
759
+ if (this.isOpen()) {
760
+ event.preventDefault();
761
+ this.close();
762
+ }
763
+ break;
764
+ case 'ArrowDown':
765
+ if (!this.isOpen()) {
766
+ event.preventDefault();
767
+ this.open();
768
+ }
769
+ break;
770
+ }
771
+ }
772
+ onInputChange(event) {
773
+ if (!this.allowManualInput()) {
774
+ return;
775
+ }
776
+ const _input = event.target;
777
+ // Debounce or wait for blur/enter to actually parse
778
+ // For now, we just allow typing without immediate parsing
779
+ }
780
+ onInputBlur() {
781
+ this._inputFocused.set(false);
782
+ if (this.allowManualInput()) {
783
+ this.parseAndSetValue(this.inputRef().nativeElement.value);
784
+ }
785
+ this.onTouched();
786
+ this.touched.set(true);
787
+ }
788
+ onPanelKeydown(event) {
789
+ switch (event.key) {
790
+ case 'Escape':
791
+ event.preventDefault();
792
+ this.close();
793
+ break;
794
+ }
795
+ }
796
+ onDateSelected(date) {
797
+ // Preserve time when selecting a new date if time picker is shown
798
+ if (this.showTimePicker()) {
799
+ const currentValue = this.internalValue();
800
+ if (currentValue) {
801
+ const withTime = this.dateAdapter.setTime(date, this.dateAdapter.getHours(currentValue), this.dateAdapter.getMinutes(currentValue), this.dateAdapter.getSeconds(currentValue));
802
+ this.updateValue(withTime);
803
+ }
804
+ else {
805
+ this.updateValue(date);
806
+ }
807
+ }
808
+ else {
809
+ this.updateValue(date);
810
+ }
811
+ if (!this.effectiveKeepOpen()) {
812
+ this.close();
813
+ }
814
+ this.announce(`Selected ${this.dateAdapter.format(date, 'long')}`);
815
+ }
816
+ onTimeChange(time) {
817
+ if (!time)
818
+ return;
819
+ const current = this.internalValue() ?? this.dateAdapter.today();
820
+ const updated = this.dateAdapter.setTime(current, time.hours, time.minutes, time.seconds);
821
+ this.updateValue(updated);
822
+ }
823
+ onActiveDateChange(date) {
824
+ this.calendarActiveDate.set(date);
825
+ }
826
+ // ============ FormFieldControl IMPLEMENTATION ============
827
+ /**
828
+ * Called when the form field container is clicked.
829
+ * Implements FormFieldControl.
830
+ */
831
+ onContainerClick(event) {
832
+ const target = event.target;
833
+ if (!this.disabled() && !this.triggerRef().nativeElement.contains(target)) {
834
+ this.toggle();
835
+ }
836
+ }
837
+ /**
838
+ * Sets the describedBy IDs from the form field.
839
+ * Called by the parent form field component.
840
+ */
841
+ setDescribedByIds(ids) {
842
+ this._describedByIds.set(ids);
843
+ }
844
+ /**
845
+ * Sets the appearance for styling.
846
+ * Called by the parent form field component.
847
+ */
848
+ setAppearance(appearance) {
849
+ this._appearance.set(appearance);
850
+ }
851
+ // ============ PRIVATE METHODS ============
852
+ createOverlay() {
853
+ if (this.overlayRef) {
854
+ return;
855
+ }
856
+ const hostEl = this.elementRef.nativeElement;
857
+ const positionStrategy = this.overlay
858
+ .position()
859
+ .flexibleConnectedTo(hostEl)
860
+ .withPositions(DEFAULT_POSITIONS$1)
861
+ .withFlexibleDimensions(false)
862
+ .withPush(true);
863
+ this.overlayRef = this.overlay.create({
864
+ positionStrategy,
865
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
866
+ hasBackdrop: true,
867
+ backdropClass: 'cdk-overlay-transparent-backdrop',
868
+ });
869
+ // Attach panel template
870
+ const portal = new TemplatePortal(this.panelTemplateRef(), this.viewContainerRef);
871
+ this.overlayRef.attach(portal);
872
+ // Close on backdrop click
873
+ this.overlayRef
874
+ .backdropClick()
875
+ .pipe(takeUntilDestroyed(this.destroyRef))
876
+ .subscribe(() => this.close());
877
+ // Close on outside click
878
+ this.overlayRef
879
+ .outsidePointerEvents()
880
+ .pipe(takeUntilDestroyed(this.destroyRef))
881
+ .subscribe(() => this.close());
882
+ }
883
+ destroyOverlay() {
884
+ if (this.overlayRef) {
885
+ this.overlayRef.dispose();
886
+ this.overlayRef = null;
887
+ }
888
+ }
889
+ updateValue(value) {
890
+ this.value.set(value);
891
+ this.onChange(value);
892
+ this.dateChange.emit(value);
893
+ this.onValidatorChange();
894
+ }
895
+ parseAndSetValue(inputValue) {
896
+ if (!inputValue.trim()) {
897
+ // Empty input - clear if allowed
898
+ if (this.hasValue()) {
899
+ this.updateValue(null);
900
+ }
901
+ return;
902
+ }
903
+ const parsed = this.dateAdapter.parse(inputValue, this.effectiveDateFormat());
904
+ if (parsed && this.dateAdapter.isValid(parsed)) {
905
+ // Validate against min/max/filter
906
+ if (this.isDateValid(parsed)) {
907
+ this.updateValue(parsed);
908
+ }
909
+ else {
910
+ // Invalid date - revert to current value
911
+ this.inputRef().nativeElement.value = this.displayValue();
912
+ }
913
+ }
914
+ else {
915
+ // Parse failed - revert to current value
916
+ this.inputRef().nativeElement.value = this.displayValue();
917
+ }
918
+ }
919
+ isDateValid(date) {
920
+ const min = this.min();
921
+ const max = this.max();
922
+ const filter = this.dateFilter();
923
+ if (min && this.dateAdapter.compareDate(date, min) < 0) {
924
+ return false;
925
+ }
926
+ if (max && this.dateAdapter.compareDate(date, max) > 0) {
927
+ return false;
928
+ }
929
+ if (filter && !filter(date)) {
930
+ return false;
931
+ }
932
+ return true;
933
+ }
934
+ announce(message) {
935
+ this.liveAnnouncement.set(message);
936
+ }
937
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDatepicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
938
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComDatepicker, isStandalone: true, selector: "com-datepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, startView: { classPropertyName: "startView", publicName: "startView", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, showClearButton: { classPropertyName: "showClearButton", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, showTodayButton: { classPropertyName: "showTodayButton", publicName: "showTodayButton", isSignal: true, isRequired: false, transformFunction: null }, showFooterClearButton: { classPropertyName: "showFooterClearButton", publicName: "showFooterClearButton", isSignal: true, isRequired: false, transformFunction: null }, keepOpen: { classPropertyName: "keepOpen", publicName: "keepOpen", isSignal: true, isRequired: false, transformFunction: null }, allowManualInput: { classPropertyName: "allowManualInput", publicName: "allowManualInput", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, sfErrors: { classPropertyName: "sfErrors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, showTimePicker: { classPropertyName: "showTimePicker", publicName: "showTimePicker", isSignal: true, isRequired: false, transformFunction: null }, use12HourFormat: { classPropertyName: "use12HourFormat", publicName: "use12HourFormat", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", dateChange: "dateChange", opened: "opened", closed: "closed" }, host: { properties: { "class.com-datepicker-disabled": "disabled()", "class.com-datepicker-open": "isOpen()" }, classAttribute: "com-datepicker-host inline-block" }, providers: [
939
+ SingleSelectionStrategy,
940
+ { provide: CALENDAR_SELECTION_STRATEGY, useExisting: SingleSelectionStrategy },
941
+ { provide: FormFieldControl, useExisting: forwardRef(() => ComDatepicker) },
942
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerElement"], descendants: true, isSignal: true }, { propertyName: "inputRef", first: true, predicate: ["inputElement"], descendants: true, isSignal: true }, { propertyName: "panelTemplateRef", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }], exportAs: ["comDatepicker"], ngImport: i0, template: `
943
+ <!-- Trigger container -->
944
+ <div
945
+ #triggerElement
946
+ [class]="triggerClasses()"
947
+ role="group"
948
+ tabindex="-1"
949
+ [attr.aria-expanded]="isOpen()"
950
+ [attr.aria-haspopup]="'dialog'"
951
+ [attr.aria-owns]="panelId()"
952
+ [attr.aria-disabled]="disabled() || null"
953
+ (click)="onTriggerClick()"
954
+ (keydown)="onTriggerKeydown($event)"
955
+ >
956
+ <!-- Date input display -->
957
+ <input
958
+ #inputElement
959
+ type="text"
960
+ [class]="inputClasses()"
961
+ [value]="displayValue()"
962
+ [placeholder]="placeholder()"
963
+ [disabled]="disabled()"
964
+ [readonly]="!allowManualInput()"
965
+ [attr.id]="inputId()"
966
+ [attr.aria-label]="ariaLabel() || placeholder()"
967
+ [attr.aria-describedby]="effectiveAriaDescribedBy() || null"
968
+ [attr.aria-invalid]="effectiveState() === 'error' || null"
969
+ [attr.aria-required]="required() || null"
970
+ (focus)="onInputFocus()"
971
+ (input)="onInputChange($event)"
972
+ (blur)="onInputBlur()"
973
+ (keydown)="onInputKeydown($event)"
974
+ />
975
+
976
+ <!-- Clear button -->
977
+ @if (showClearButton() && hasValue() && !disabled()) {
978
+ <button
979
+ type="button"
980
+ [class]="clearClasses()"
981
+ [attr.aria-label]="'Clear date'"
982
+ (click)="clear($event)"
983
+ >
984
+ <com-icon name="x" [size]="iconSize()" />
985
+ </button>
986
+ }
987
+
988
+ <!-- Calendar icon -->
989
+ <button
990
+ type="button"
991
+ [class]="iconClasses()"
992
+ [attr.aria-label]="isOpen() ? 'Close calendar' : 'Open calendar'"
993
+ [disabled]="disabled()"
994
+ tabindex="-1"
995
+ >
996
+ <com-icon name="calendar" [size]="iconSize()" />
997
+ </button>
998
+ </div>
999
+
1000
+ <!-- Panel template (rendered in overlay) -->
1001
+ <ng-template #panelTemplate>
1002
+ <div
1003
+ [class]="panelClasses()"
1004
+ [attr.id]="panelId()"
1005
+ role="dialog"
1006
+ aria-modal="true"
1007
+ [attr.aria-label]="'Choose date'"
1008
+ (keydown)="onPanelKeydown($event)"
1009
+ cdkTrapFocus
1010
+ [cdkTrapFocusAutoCapture]="true"
1011
+ >
1012
+ <com-calendar
1013
+ [activeDate]="calendarActiveDate()"
1014
+ [selected]="internalValue()"
1015
+ [minDate]="min()"
1016
+ [maxDate]="max()"
1017
+ [dateFilter]="dateFilter()"
1018
+ [startView]="startView()"
1019
+ [firstDayOfWeek]="firstDayOfWeek()"
1020
+ [bordered]="false"
1021
+ (selectedChange)="onDateSelected($event)"
1022
+ (activeDateChange)="onActiveDateChange($event)"
1023
+ />
1024
+
1025
+ @if (showTimePicker()) {
1026
+ <div [class]="timeSectionClasses()">
1027
+ <com-time-picker
1028
+ variant="embedded"
1029
+ [size]="size()"
1030
+ [value]="timeValue()"
1031
+ [use12HourFormat]="use12HourFormat()"
1032
+ [showSeconds]="showSeconds()"
1033
+ [minuteStep]="minuteStep()"
1034
+ [disabled]="disabled()"
1035
+ (timeChange)="onTimeChange($event)"
1036
+ />
1037
+ </div>
1038
+ }
1039
+
1040
+ @if (showTodayButton() || showFooterClearButton() || showTimePicker()) {
1041
+ <div [class]="footerClasses()">
1042
+ @if (showTodayButton()) {
1043
+ <button
1044
+ type="button"
1045
+ [class]="todayButtonClasses()"
1046
+ (click)="selectToday()"
1047
+ >
1048
+ Today
1049
+ </button>
1050
+ }
1051
+ @if (showFooterClearButton()) {
1052
+ <button
1053
+ type="button"
1054
+ [class]="clearButtonClasses()"
1055
+ (click)="clear($event)"
1056
+ >
1057
+ Clear
1058
+ </button>
1059
+ }
1060
+ @if (showTimePicker()) {
1061
+ <button
1062
+ type="button"
1063
+ [class]="todayButtonClasses()"
1064
+ (click)="close()"
1065
+ >
1066
+ Done
1067
+ </button>
1068
+ }
1069
+ </div>
1070
+ }
1071
+ </div>
1072
+ </ng-template>
1073
+
1074
+ <!-- Live announcer region -->
1075
+ <div class="sr-only" aria-live="polite" aria-atomic="true">
1076
+ {{ liveAnnouncement() }}
1077
+ </div>
1078
+ `, isInline: true, styles: [".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: ComCalendar, selector: "com-calendar", inputs: ["activeDate", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "bordered", "startView", "firstDayOfWeek", "monthColumns", "cellTemplate"], outputs: ["selectedChange", "viewChanged", "activeDateChange"] }, { kind: "component", type: ComIcon, selector: "com-icon", inputs: ["name", "img", "color", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: ComTimePicker, selector: "com-time-picker", inputs: ["value", "disabled", "required", "showSeconds", "use12HourFormat", "minuteStep", "secondStep", "minTime", "maxTime", "variant", "size", "state", "ariaLabel", "class", "placeholder", "errorStateMatcher", "touched", "invalid", "errors"], outputs: ["valueChange", "disabledChange", "touchedChange", "timeChange"], exportAs: ["comTimePicker"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1079
+ }
1080
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDatepicker, decorators: [{
1081
+ type: Component,
1082
+ args: [{ selector: 'com-datepicker', exportAs: 'comDatepicker', template: `
1083
+ <!-- Trigger container -->
1084
+ <div
1085
+ #triggerElement
1086
+ [class]="triggerClasses()"
1087
+ role="group"
1088
+ tabindex="-1"
1089
+ [attr.aria-expanded]="isOpen()"
1090
+ [attr.aria-haspopup]="'dialog'"
1091
+ [attr.aria-owns]="panelId()"
1092
+ [attr.aria-disabled]="disabled() || null"
1093
+ (click)="onTriggerClick()"
1094
+ (keydown)="onTriggerKeydown($event)"
1095
+ >
1096
+ <!-- Date input display -->
1097
+ <input
1098
+ #inputElement
1099
+ type="text"
1100
+ [class]="inputClasses()"
1101
+ [value]="displayValue()"
1102
+ [placeholder]="placeholder()"
1103
+ [disabled]="disabled()"
1104
+ [readonly]="!allowManualInput()"
1105
+ [attr.id]="inputId()"
1106
+ [attr.aria-label]="ariaLabel() || placeholder()"
1107
+ [attr.aria-describedby]="effectiveAriaDescribedBy() || null"
1108
+ [attr.aria-invalid]="effectiveState() === 'error' || null"
1109
+ [attr.aria-required]="required() || null"
1110
+ (focus)="onInputFocus()"
1111
+ (input)="onInputChange($event)"
1112
+ (blur)="onInputBlur()"
1113
+ (keydown)="onInputKeydown($event)"
1114
+ />
1115
+
1116
+ <!-- Clear button -->
1117
+ @if (showClearButton() && hasValue() && !disabled()) {
1118
+ <button
1119
+ type="button"
1120
+ [class]="clearClasses()"
1121
+ [attr.aria-label]="'Clear date'"
1122
+ (click)="clear($event)"
1123
+ >
1124
+ <com-icon name="x" [size]="iconSize()" />
1125
+ </button>
1126
+ }
1127
+
1128
+ <!-- Calendar icon -->
1129
+ <button
1130
+ type="button"
1131
+ [class]="iconClasses()"
1132
+ [attr.aria-label]="isOpen() ? 'Close calendar' : 'Open calendar'"
1133
+ [disabled]="disabled()"
1134
+ tabindex="-1"
1135
+ >
1136
+ <com-icon name="calendar" [size]="iconSize()" />
1137
+ </button>
1138
+ </div>
1139
+
1140
+ <!-- Panel template (rendered in overlay) -->
1141
+ <ng-template #panelTemplate>
1142
+ <div
1143
+ [class]="panelClasses()"
1144
+ [attr.id]="panelId()"
1145
+ role="dialog"
1146
+ aria-modal="true"
1147
+ [attr.aria-label]="'Choose date'"
1148
+ (keydown)="onPanelKeydown($event)"
1149
+ cdkTrapFocus
1150
+ [cdkTrapFocusAutoCapture]="true"
1151
+ >
1152
+ <com-calendar
1153
+ [activeDate]="calendarActiveDate()"
1154
+ [selected]="internalValue()"
1155
+ [minDate]="min()"
1156
+ [maxDate]="max()"
1157
+ [dateFilter]="dateFilter()"
1158
+ [startView]="startView()"
1159
+ [firstDayOfWeek]="firstDayOfWeek()"
1160
+ [bordered]="false"
1161
+ (selectedChange)="onDateSelected($event)"
1162
+ (activeDateChange)="onActiveDateChange($event)"
1163
+ />
1164
+
1165
+ @if (showTimePicker()) {
1166
+ <div [class]="timeSectionClasses()">
1167
+ <com-time-picker
1168
+ variant="embedded"
1169
+ [size]="size()"
1170
+ [value]="timeValue()"
1171
+ [use12HourFormat]="use12HourFormat()"
1172
+ [showSeconds]="showSeconds()"
1173
+ [minuteStep]="minuteStep()"
1174
+ [disabled]="disabled()"
1175
+ (timeChange)="onTimeChange($event)"
1176
+ />
1177
+ </div>
1178
+ }
1179
+
1180
+ @if (showTodayButton() || showFooterClearButton() || showTimePicker()) {
1181
+ <div [class]="footerClasses()">
1182
+ @if (showTodayButton()) {
1183
+ <button
1184
+ type="button"
1185
+ [class]="todayButtonClasses()"
1186
+ (click)="selectToday()"
1187
+ >
1188
+ Today
1189
+ </button>
1190
+ }
1191
+ @if (showFooterClearButton()) {
1192
+ <button
1193
+ type="button"
1194
+ [class]="clearButtonClasses()"
1195
+ (click)="clear($event)"
1196
+ >
1197
+ Clear
1198
+ </button>
1199
+ }
1200
+ @if (showTimePicker()) {
1201
+ <button
1202
+ type="button"
1203
+ [class]="todayButtonClasses()"
1204
+ (click)="close()"
1205
+ >
1206
+ Done
1207
+ </button>
1208
+ }
1209
+ </div>
1210
+ }
1211
+ </div>
1212
+ </ng-template>
1213
+
1214
+ <!-- Live announcer region -->
1215
+ <div class="sr-only" aria-live="polite" aria-atomic="true">
1216
+ {{ liveAnnouncement() }}
1217
+ </div>
1218
+ `, imports: [
1219
+ OverlayModule,
1220
+ A11yModule,
1221
+ ComCalendar,
1222
+ ComIcon,
1223
+ ComTimePicker,
1224
+ ], providers: [
1225
+ SingleSelectionStrategy,
1226
+ { provide: CALENDAR_SELECTION_STRATEGY, useExisting: SingleSelectionStrategy },
1227
+ { provide: FormFieldControl, useExisting: forwardRef(() => ComDatepicker) },
1228
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
1229
+ class: 'com-datepicker-host inline-block',
1230
+ '[class.com-datepicker-disabled]': 'disabled()',
1231
+ '[class.com-datepicker-open]': 'isOpen()',
1232
+ }, styles: [".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
1233
+ }], ctorParameters: () => [], propDecorators: { triggerRef: [{ type: i0.ViewChild, args: ['triggerElement', { isSignal: true }] }], inputRef: [{ type: i0.ViewChild, args: ['inputElement', { isSignal: true }] }], panelTemplateRef: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], startView: [{ type: i0.Input, args: [{ isSignal: true, alias: "startView", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], showClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], showTodayButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTodayButton", required: false }] }], showFooterClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooterClearButton", required: false }] }], keepOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepOpen", required: false }] }], allowManualInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowManualInput", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], sfErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], showTimePicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTimePicker", required: false }] }], use12HourFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "use12HourFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
1234
+
1235
+ /** Default position for the datepicker panel. */
1236
+ const DEFAULT_POSITIONS = [
1237
+ // Below trigger, aligned start
1238
+ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
1239
+ // Above trigger, aligned start
1240
+ { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
1241
+ // Below trigger, aligned end
1242
+ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: 4 },
1243
+ // Above trigger, aligned end
1244
+ { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -4 },
1245
+ ];
1246
+ /**
1247
+ * Date range picker component with calendar popup.
1248
+ * Allows selecting a start and end date via a two-click interaction.
1249
+ * Implements ControlValueAccessor for Reactive Forms and Template-driven Forms.
1250
+ *
1251
+ * @tokens `--color-input-background`, `--color-input-foreground`, `--color-input-border`,
1252
+ * `--color-input-placeholder`, `--color-ring`, `--color-muted`, `--color-muted-foreground`,
1253
+ * `--color-popover`, `--color-popover-foreground`, `--color-border-subtle`,
1254
+ * `--color-primary`, `--color-primary-foreground`, `--color-primary-hover`,
1255
+ * `--color-warn`, `--color-success`, `--color-disabled`, `--color-disabled-foreground`
1256
+ *
1257
+ * @example
1258
+ * ```html
1259
+ * <com-date-range-picker
1260
+ * formControlName="dateRange"
1261
+ * startPlaceholder="Start date"
1262
+ * endPlaceholder="End date"
1263
+ * [min]="minDate"
1264
+ * [max]="maxDate"
1265
+ * [showTodayButton]="true"
1266
+ * />
1267
+ * ```
1268
+ */
1269
+ class ComDateRangePicker {
1270
+ elementRef = inject(ElementRef);
1271
+ destroyRef = inject(DestroyRef);
1272
+ overlay = inject(Overlay);
1273
+ viewContainerRef = inject(ViewContainerRef);
1274
+ document = inject(DOCUMENT);
1275
+ dateAdapter = inject(DATE_ADAPTER);
1276
+ /** NgControl bound to this date range picker (if using reactive forms). */
1277
+ ngControl = inject(NgControl, { optional: true, self: true });
1278
+ defaultErrorStateMatcher = inject(ErrorStateMatcher);
1279
+ parentForm = inject(NgForm, { optional: true });
1280
+ parentFormGroup = inject(FormGroupDirective, { optional: true });
1281
+ /** Reference to the trigger element. */
1282
+ triggerRef = viewChild.required('triggerElement');
1283
+ /** Reference to the start input element. */
1284
+ startInputRef = viewChild.required('startInputElement');
1285
+ /** Reference to the end input element. */
1286
+ endInputRef = viewChild.required('endInputElement');
1287
+ /** Reference to the panel template. */
1288
+ panelTemplateRef = viewChild.required('panelTemplate');
1289
+ /** Overlay reference. */
1290
+ overlayRef = null;
1291
+ /** Unique ID for the datepicker. */
1292
+ datepickerId = generateDatepickerId();
1293
+ // ============ INPUTS ============
1294
+ /** Current value. */
1295
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
1296
+ /** Minimum selectable date. */
1297
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
1298
+ /** Maximum selectable date. */
1299
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
1300
+ /** Custom filter function to disable specific dates. */
1301
+ dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : []));
1302
+ /** Date the calendar opens to (defaults to start date or today). */
1303
+ startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : []));
1304
+ /** Initial calendar view. */
1305
+ startView = input('month', ...(ngDevMode ? [{ debugName: "startView" }] : []));
1306
+ /** First day of week override (0=Sun, 1=Mon, ..., 6=Sat). */
1307
+ firstDayOfWeek = input(null, ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : []));
1308
+ /** Placeholder text for start date. */
1309
+ startPlaceholder = input('Start date', ...(ngDevMode ? [{ debugName: "startPlaceholder" }] : []));
1310
+ /** Placeholder text for end date. */
1311
+ endPlaceholder = input('End date', ...(ngDevMode ? [{ debugName: "endPlaceholder" }] : []));
1312
+ /** Whether the datepicker is disabled. */
1313
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
1314
+ /** Whether the datepicker is required. */
1315
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
1316
+ /** Display format for the date. */
1317
+ dateFormat = input('medium', ...(ngDevMode ? [{ debugName: "dateFormat" }] : []));
1318
+ /** Show a clear button in the trigger. */
1319
+ showClearButton = input(false, ...(ngDevMode ? [{ debugName: "showClearButton" }] : []));
1320
+ /** Show a today button in the footer. */
1321
+ showTodayButton = input(false, ...(ngDevMode ? [{ debugName: "showTodayButton" }] : []));
1322
+ /** Show a clear button in the footer. */
1323
+ showFooterClearButton = input(false, ...(ngDevMode ? [{ debugName: "showFooterClearButton" }] : []));
1324
+ /** Don't auto-close on complete range selection. */
1325
+ keepOpen = input(false, ...(ngDevMode ? [{ debugName: "keepOpen" }] : []));
1326
+ /** Allow manual text input. */
1327
+ allowManualInput = input(true, ...(ngDevMode ? [{ debugName: "allowManualInput" }] : []));
1328
+ /** Additional CSS classes for the panel. */
1329
+ panelClass = input('', ...(ngDevMode ? [{ debugName: "panelClass" }] : []));
1330
+ /** Panel width strategy. */
1331
+ panelWidth = input('auto', ...(ngDevMode ? [{ debugName: "panelWidth" }] : []));
1332
+ /** CVA variant for trigger styling. */
1333
+ variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : []));
1334
+ /** Size variant. */
1335
+ size = input('default', ...(ngDevMode ? [{ debugName: "size" }] : []));
1336
+ /** Validation state. */
1337
+ state = input('default', ...(ngDevMode ? [{ debugName: "state" }] : []));
1338
+ /** Additional CSS classes for the trigger. */
1339
+ userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : {}), alias: 'class' });
1340
+ /** Accessible label for the start input. */
1341
+ startAriaLabel = input(null, ...(ngDevMode ? [{ debugName: "startAriaLabel" }] : []));
1342
+ /** Accessible label for the end input. */
1343
+ endAriaLabel = input(null, ...(ngDevMode ? [{ debugName: "endAriaLabel" }] : []));
1344
+ /** Custom error state matcher for determining when to show errors. */
1345
+ errorStateMatcher = input(...(ngDevMode ? [undefined, { debugName: "errorStateMatcher" }] : []));
1346
+ // Signal Forms inputs — set automatically by [formField] via setInputOnDirectives
1347
+ touched = model(false, ...(ngDevMode ? [{ debugName: "touched" }] : []));
1348
+ invalid = input(false, ...(ngDevMode ? [{ debugName: "invalid" }] : []));
1349
+ sfErrors = input([], { ...(ngDevMode ? { debugName: "sfErrors" } : {}), alias: 'errors' });
1350
+ /** Whether to show time pickers below the calendar. */
1351
+ showTimePicker = input(false, ...(ngDevMode ? [{ debugName: "showTimePicker" }] : []));
1352
+ /** 12h vs 24h format for the time pickers. `null` = auto-detect. */
1353
+ use12HourFormat = input(null, ...(ngDevMode ? [{ debugName: "use12HourFormat" }] : []));
1354
+ /** Whether the time pickers show seconds. */
1355
+ showSeconds = input(false, ...(ngDevMode ? [{ debugName: "showSeconds" }] : []));
1356
+ /** Step interval for minutes in the time pickers. */
1357
+ minuteStep = input(1, ...(ngDevMode ? [{ debugName: "minuteStep" }] : []));
1358
+ // ============ OUTPUTS ============
1359
+ /** Emitted when a complete range is selected. */
1360
+ rangeChange = output();
1361
+ /** Emitted when the panel opens. */
1362
+ opened = output();
1363
+ /** Emitted when the panel closes. */
1364
+ closed = output();
1365
+ // ============ INTERNAL STATE ============
1366
+ /** Whether the panel is open. */
1367
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1368
+ /** Which input is currently focused. */
1369
+ activeInput = signal(null, ...(ngDevMode ? [{ debugName: "activeInput" }] : []));
1370
+ /** Internal value state (managed by CVA or input). */
1371
+ internalValue = linkedSignal(() => this.value() ?? null, ...(ngDevMode ? [{ debugName: "internalValue" }] : []));
1372
+ /** Calendar active date for navigation. Recomputes when value or startAt changes; user navigation overrides via .set(). */
1373
+ calendarActiveDate = linkedSignal({ ...(ngDevMode ? { debugName: "calendarActiveDate" } : {}), source: () => ({ value: this.internalValue(), startAt: this.startAt() }),
1374
+ computation: ({ value, startAt }) => value?.start ?? startAt ?? this.dateAdapter.today() });
1375
+ /** Live announcements for screen readers. */
1376
+ liveAnnouncement = signal('', ...(ngDevMode ? [{ debugName: "liveAnnouncement" }] : []));
1377
+ /** Whether any input is focused. */
1378
+ _inputFocused = signal(false, ...(ngDevMode ? [{ debugName: "_inputFocused" }] : []));
1379
+ /** IDs for aria-describedby (set by form-field). */
1380
+ _describedByIds = signal('', ...(ngDevMode ? [{ debugName: "_describedByIds" }] : []));
1381
+ /** Form field appearance (set by form-field). */
1382
+ _appearance = signal('outline', ...(ngDevMode ? [{ debugName: "_appearance" }] : []));
1383
+ // ============ FormFieldControl SIGNALS ============
1384
+ /** Whether the date range picker is focused. Implements FormFieldControl. */
1385
+ focused = computed(() => this._inputFocused() || this.isOpen(), ...(ngDevMode ? [{ debugName: "focused" }] : []));
1386
+ /** Whether the label should float. */
1387
+ shouldLabelFloat = computed(() => this.focused() || this.hasValue(), ...(ngDevMode ? [{ debugName: "shouldLabelFloat" }] : []));
1388
+ /** Whether the control is in an error state. Implements FormFieldControl. */
1389
+ errorState = computed(() => {
1390
+ if (!this.ngControl) {
1391
+ return this.invalid() && this.touched();
1392
+ }
1393
+ this.isOpen();
1394
+ this.hasValue();
1395
+ const matcher = this.errorStateMatcher() ?? this.defaultErrorStateMatcher;
1396
+ const form = this.parentFormGroup ?? this.parentForm;
1397
+ return matcher.isErrorState(this.ngControl.control ?? null, form);
1398
+ }, ...(ngDevMode ? [{ debugName: "errorState" }] : []));
1399
+ /** Structured validation errors from Signal Forms. */
1400
+ errors = computed(() => !this.ngControl ? this.sfErrors() : null, ...(ngDevMode ? [{ debugName: "errors" }] : []));
1401
+ /** Unique ID for the control (maps to start input). Implements FormFieldControl. */
1402
+ id = computed(() => `${this.datepickerId}-start`, ...(ngDevMode ? [{ debugName: "id" }] : []));
1403
+ /**
1404
+ * Effective state combining manual state with automatic error detection.
1405
+ */
1406
+ effectiveState = computed(() => {
1407
+ const manualState = this.state();
1408
+ if (manualState !== 'default')
1409
+ return manualState;
1410
+ return this.errorState() ? 'error' : 'default';
1411
+ }, ...(ngDevMode ? [{ debugName: "effectiveState" }] : []));
1412
+ /** Combined aria-describedby from form-field. */
1413
+ effectiveAriaDescribedBy = computed(() => this._describedByIds() || null, ...(ngDevMode ? [{ debugName: "effectiveAriaDescribedBy" }] : []));
1414
+ // ============ COMPUTED STATE ============
1415
+ /** Start input element ID (alias for FormFieldControl id). */
1416
+ startInputId = this.id;
1417
+ /** End input element ID. */
1418
+ endInputId = computed(() => `${this.datepickerId}-end`, ...(ngDevMode ? [{ debugName: "endInputId" }] : []));
1419
+ /** Panel element ID. */
1420
+ panelId = computed(() => `${this.datepickerId}-panel`, ...(ngDevMode ? [{ debugName: "panelId" }] : []));
1421
+ /** Whether the datepicker has a value. */
1422
+ hasValue = computed(() => {
1423
+ const value = this.internalValue();
1424
+ return value !== null && (value.start !== null || value.end !== null);
1425
+ }, ...(ngDevMode ? [{ debugName: "hasValue" }] : []));
1426
+ /** Icon size based on datepicker size. */
1427
+ iconSize = computed(() => {
1428
+ const sizeMap = {
1429
+ sm: 'sm',
1430
+ default: 'md',
1431
+ lg: 'lg',
1432
+ };
1433
+ return sizeMap[this.size()];
1434
+ }, ...(ngDevMode ? [{ debugName: "iconSize" }] : []));
1435
+ /** Calendar selection (converts DateRangeValue to DateRange for calendar). */
1436
+ calendarSelection = computed(() => {
1437
+ const value = this.internalValue();
1438
+ if (!value)
1439
+ return null;
1440
+ return { start: value.start, end: value.end };
1441
+ }, ...(ngDevMode ? [{ debugName: "calendarSelection" }] : []));
1442
+ /** Formatted start display value. */
1443
+ startDisplayValue = computed(() => {
1444
+ const value = this.internalValue();
1445
+ if (!value?.start)
1446
+ return '';
1447
+ return this.dateAdapter.format(value.start, this.effectiveDateFormat());
1448
+ }, ...(ngDevMode ? [{ debugName: "startDisplayValue" }] : []));
1449
+ /** Formatted end display value. */
1450
+ endDisplayValue = computed(() => {
1451
+ const value = this.internalValue();
1452
+ if (!value?.end)
1453
+ return '';
1454
+ return this.dateAdapter.format(value.end, this.effectiveDateFormat());
1455
+ }, ...(ngDevMode ? [{ debugName: "endDisplayValue" }] : []));
1456
+ /** Computed trigger classes. */
1457
+ triggerClasses = computed(() => {
1458
+ const baseClasses = datepickerTriggerVariants({
1459
+ variant: this.variant(),
1460
+ size: this.size(),
1461
+ state: this.effectiveState(),
1462
+ open: this.isOpen(),
1463
+ });
1464
+ const disabledClasses = this.disabled() ? datepickerDisabledVariants() : '';
1465
+ // For naked variant, add padding based on form-field appearance
1466
+ let paddingClasses = '';
1467
+ if (this.variant() === 'naked') {
1468
+ paddingClasses = this._appearance() === 'fill' ? 'pt-5 pb-1.5 px-3' : 'py-2.5 px-3';
1469
+ }
1470
+ return mergeClasses(baseClasses, disabledClasses, paddingClasses, this.userClass());
1471
+ }, ...(ngDevMode ? [{ debugName: "triggerClasses" }] : []));
1472
+ /** Computed input classes. */
1473
+ inputClasses = computed(() => {
1474
+ return datepickerInputVariants({ size: this.size() });
1475
+ }, ...(ngDevMode ? [{ debugName: "inputClasses" }] : []));
1476
+ /** Computed separator classes. */
1477
+ separatorClasses = computed(() => {
1478
+ return datepickerRangeSeparatorVariants({ size: this.size() });
1479
+ }, ...(ngDevMode ? [{ debugName: "separatorClasses" }] : []));
1480
+ /** Computed icon classes. */
1481
+ iconClasses = computed(() => {
1482
+ return datepickerIconVariants({ size: this.size() });
1483
+ }, ...(ngDevMode ? [{ debugName: "iconClasses" }] : []));
1484
+ /** Computed clear button classes. */
1485
+ clearClasses = computed(() => {
1486
+ return datepickerClearVariants({ size: this.size() });
1487
+ }, ...(ngDevMode ? [{ debugName: "clearClasses" }] : []));
1488
+ /** Computed panel classes. */
1489
+ panelClasses = computed(() => {
1490
+ const baseClasses = datepickerPanelVariants({ size: this.size() });
1491
+ return joinClasses(baseClasses, this.panelClass());
1492
+ }, ...(ngDevMode ? [{ debugName: "panelClasses" }] : []));
1493
+ /** Computed footer classes. */
1494
+ footerClasses = computed(() => {
1495
+ return datepickerFooterVariants({ size: this.size() });
1496
+ }, ...(ngDevMode ? [{ debugName: "footerClasses" }] : []));
1497
+ /** Computed today button classes. */
1498
+ todayButtonClasses = computed(() => {
1499
+ return datepickerFooterButtonVariants({ size: this.size(), variant: 'primary' });
1500
+ }, ...(ngDevMode ? [{ debugName: "todayButtonClasses" }] : []));
1501
+ /** Computed clear button classes (footer). */
1502
+ clearButtonClasses = computed(() => {
1503
+ return datepickerFooterButtonVariants({ size: this.size(), variant: 'secondary' });
1504
+ }, ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : []));
1505
+ /** Time section divider classes. */
1506
+ timeSectionClasses = computed(() => {
1507
+ return timepickerSectionVariants({ size: this.size() });
1508
+ }, ...(ngDevMode ? [{ debugName: "timeSectionClasses" }] : []));
1509
+ /** Time label classes. */
1510
+ timeLabelClasses = computed(() => {
1511
+ return timepickerLabelVariants({ size: this.size() });
1512
+ }, ...(ngDevMode ? [{ debugName: "timeLabelClasses" }] : []));
1513
+ /** Start time value derived from the start date. */
1514
+ startTimeValue = computed(() => {
1515
+ const value = this.internalValue();
1516
+ if (!value?.start)
1517
+ return null;
1518
+ return createTimeValue(this.dateAdapter.getHours(value.start), this.dateAdapter.getMinutes(value.start), this.dateAdapter.getSeconds(value.start));
1519
+ }, ...(ngDevMode ? [{ debugName: "startTimeValue" }] : []));
1520
+ /** End time value derived from the end date. */
1521
+ endTimeValue = computed(() => {
1522
+ const value = this.internalValue();
1523
+ if (!value?.end)
1524
+ return null;
1525
+ return createTimeValue(this.dateAdapter.getHours(value.end), this.dateAdapter.getMinutes(value.end), this.dateAdapter.getSeconds(value.end));
1526
+ }, ...(ngDevMode ? [{ debugName: "endTimeValue" }] : []));
1527
+ /** Effective display format — switches to dateTime when time picker is shown. */
1528
+ effectiveDateFormat = computed(() => {
1529
+ if (this.showTimePicker()) {
1530
+ return this.showSeconds() ? 'dateTimeLong' : 'dateTimeMedium';
1531
+ }
1532
+ return this.dateFormat();
1533
+ }, ...(ngDevMode ? [{ debugName: "effectiveDateFormat" }] : []));
1534
+ /** Whether the panel should stay open (keepOpen or time picker shown). */
1535
+ effectiveKeepOpen = computed(() => {
1536
+ return this.keepOpen() || this.showTimePicker();
1537
+ }, ...(ngDevMode ? [{ debugName: "effectiveKeepOpen" }] : []));
1538
+ // ============ CVA CALLBACKS ============
1539
+ onChange = () => { };
1540
+ onTouched = () => { };
1541
+ onValidatorChange = () => { };
1542
+ constructor() {
1543
+ // Wire up NgControl if present
1544
+ if (this.ngControl) {
1545
+ this.ngControl.valueAccessor = this;
1546
+ }
1547
+ }
1548
+ ngOnDestroy() {
1549
+ this.destroyOverlay();
1550
+ }
1551
+ // ============ CVA IMPLEMENTATION ============
1552
+ writeValue(value) {
1553
+ this.internalValue.set(value);
1554
+ }
1555
+ registerOnChange(fn) {
1556
+ this.onChange = fn;
1557
+ }
1558
+ registerOnTouched(fn) {
1559
+ this.onTouched = fn;
1560
+ }
1561
+ setDisabledState(_isDisabled) {
1562
+ // Disabled state is handled via the disabled input
1563
+ }
1564
+ // ============ VALIDATOR IMPLEMENTATION ============
1565
+ validate() {
1566
+ const value = this.internalValue();
1567
+ // Required validation
1568
+ if (this.required() && (!value || (!value.start && !value.end))) {
1569
+ return { required: true };
1570
+ }
1571
+ if (value) {
1572
+ const { start, end } = value;
1573
+ const min = this.min();
1574
+ const max = this.max();
1575
+ const dateFilter = this.dateFilter();
1576
+ // Min validation for start
1577
+ if (start && min && this.dateAdapter.compareDate(start, min) < 0) {
1578
+ return { minDate: { min, actual: start } };
1579
+ }
1580
+ // Max validation for end
1581
+ if (end && max && this.dateAdapter.compareDate(end, max) > 0) {
1582
+ return { maxDate: { max, actual: end } };
1583
+ }
1584
+ // Date filter validation
1585
+ if (start && dateFilter && !dateFilter(start)) {
1586
+ return { dateFilter: { date: start } };
1587
+ }
1588
+ if (end && dateFilter && !dateFilter(end)) {
1589
+ return { dateFilter: { date: end } };
1590
+ }
1591
+ // Range validation (start must be before or equal to end)
1592
+ if (start && end && this.dateAdapter.compareDate(start, end) > 0) {
1593
+ return { rangeInvalid: { start, end } };
1594
+ }
1595
+ }
1596
+ return null;
1597
+ }
1598
+ registerOnValidatorChange(fn) {
1599
+ this.onValidatorChange = fn;
1600
+ }
1601
+ // ============ PUBLIC METHODS ============
1602
+ /** Opens the datepicker panel. */
1603
+ open() {
1604
+ if (this.disabled() || this.isOpen()) {
1605
+ return;
1606
+ }
1607
+ this.createOverlay();
1608
+ this.isOpen.set(true);
1609
+ this.opened.emit();
1610
+ this.announce('Calendar opened. Select a start date.');
1611
+ }
1612
+ /** Closes the datepicker panel. */
1613
+ close() {
1614
+ if (!this.isOpen()) {
1615
+ return;
1616
+ }
1617
+ this.destroyOverlay();
1618
+ this.isOpen.set(false);
1619
+ this.closed.emit();
1620
+ this.onTouched();
1621
+ this.touched.set(true);
1622
+ // Return focus to appropriate input
1623
+ const activeInput = this.activeInput();
1624
+ if (activeInput === 'end') {
1625
+ this.endInputRef().nativeElement.focus();
1626
+ }
1627
+ else {
1628
+ this.startInputRef().nativeElement.focus();
1629
+ }
1630
+ }
1631
+ /** Toggles the datepicker panel. */
1632
+ toggle() {
1633
+ if (this.isOpen()) {
1634
+ this.close();
1635
+ }
1636
+ else {
1637
+ this.open();
1638
+ }
1639
+ }
1640
+ /** Clears the selected date range. */
1641
+ clear(event) {
1642
+ event?.preventDefault();
1643
+ event?.stopPropagation();
1644
+ this.updateValue(null);
1645
+ this.announce('Date range cleared');
1646
+ }
1647
+ /** Selects today's date as the start date. */
1648
+ selectToday() {
1649
+ const today = this.dateAdapter.today();
1650
+ const currentValue = this.internalValue();
1651
+ // Set today as start if no start, or as end if we have a start
1652
+ if (!currentValue?.start) {
1653
+ this.updateValue(createDateRangeValue(today, null));
1654
+ this.announce('Start date set to today');
1655
+ }
1656
+ else if (!currentValue.end) {
1657
+ // Ensure proper ordering
1658
+ const newRange = this.dateAdapter.compareDate(today, currentValue.start) < 0
1659
+ ? createDateRangeValue(today, currentValue.start)
1660
+ : createDateRangeValue(currentValue.start, today);
1661
+ this.updateValue(newRange);
1662
+ if (!this.effectiveKeepOpen()) {
1663
+ this.close();
1664
+ }
1665
+ }
1666
+ }
1667
+ // ============ EVENT HANDLERS ============
1668
+ onTriggerClick() {
1669
+ if (!this.disabled() && !this.isOpen()) {
1670
+ this.open();
1671
+ }
1672
+ }
1673
+ onTriggerKeydown(event) {
1674
+ switch (event.key) {
1675
+ case 'Escape':
1676
+ if (this.isOpen()) {
1677
+ event.preventDefault();
1678
+ this.close();
1679
+ }
1680
+ break;
1681
+ }
1682
+ }
1683
+ onStartInputFocus() {
1684
+ this.activeInput.set('start');
1685
+ this._inputFocused.set(true);
1686
+ }
1687
+ onEndInputFocus() {
1688
+ this.activeInput.set('end');
1689
+ this._inputFocused.set(true);
1690
+ }
1691
+ onInputKeydown(event, inputType) {
1692
+ switch (event.key) {
1693
+ case 'Enter':
1694
+ event.preventDefault();
1695
+ if (this.isOpen()) {
1696
+ // Commit manual input
1697
+ if (inputType === 'start') {
1698
+ this.parseAndSetStart(this.startInputRef().nativeElement.value);
1699
+ }
1700
+ else {
1701
+ this.parseAndSetEnd(this.endInputRef().nativeElement.value);
1702
+ }
1703
+ }
1704
+ else {
1705
+ this.open();
1706
+ }
1707
+ break;
1708
+ case 'Escape':
1709
+ if (this.isOpen()) {
1710
+ event.preventDefault();
1711
+ this.close();
1712
+ }
1713
+ break;
1714
+ case 'ArrowDown':
1715
+ if (!this.isOpen()) {
1716
+ event.preventDefault();
1717
+ this.open();
1718
+ }
1719
+ break;
1720
+ case 'Tab':
1721
+ // Allow natural tab navigation between inputs
1722
+ break;
1723
+ }
1724
+ }
1725
+ onStartInputChange(_event) {
1726
+ // Debounced in blur handler
1727
+ }
1728
+ onEndInputChange(_event) {
1729
+ // Debounced in blur handler
1730
+ }
1731
+ onStartInputBlur() {
1732
+ this._inputFocused.set(false);
1733
+ if (this.allowManualInput()) {
1734
+ this.parseAndSetStart(this.startInputRef().nativeElement.value);
1735
+ }
1736
+ this.onTouched();
1737
+ this.touched.set(true);
1738
+ }
1739
+ onEndInputBlur() {
1740
+ this._inputFocused.set(false);
1741
+ if (this.allowManualInput()) {
1742
+ this.parseAndSetEnd(this.endInputRef().nativeElement.value);
1743
+ }
1744
+ this.onTouched();
1745
+ this.touched.set(true);
1746
+ }
1747
+ onPanelKeydown(event) {
1748
+ switch (event.key) {
1749
+ case 'Escape':
1750
+ event.preventDefault();
1751
+ this.close();
1752
+ break;
1753
+ }
1754
+ }
1755
+ onCalendarSelectionChange(selection) {
1756
+ // The calendar emits the selection from the strategy
1757
+ const range = selection;
1758
+ if (range) {
1759
+ const newValue = createDateRangeValue(range.start, range.end);
1760
+ this.updateValue(newValue);
1761
+ if (range.start && range.end) {
1762
+ // Complete range selected
1763
+ this.announce(`Range selected: ${this.formatDate(range.start)} to ${this.formatDate(range.end)}`);
1764
+ if (!this.effectiveKeepOpen()) {
1765
+ this.close();
1766
+ }
1767
+ }
1768
+ else if (range.start) {
1769
+ this.announce(`Start date selected: ${this.formatDate(range.start)}. Now select end date.`);
1770
+ }
1771
+ }
1772
+ }
1773
+ onActiveDateChange(date) {
1774
+ this.calendarActiveDate.set(date);
1775
+ }
1776
+ onStartTimeChange(time) {
1777
+ if (!time)
1778
+ return;
1779
+ const value = this.internalValue();
1780
+ const startDate = value?.start ?? this.dateAdapter.today();
1781
+ const updated = this.dateAdapter.setTime(startDate, time.hours, time.minutes, time.seconds);
1782
+ this.updateValue(createDateRangeValue(updated, value?.end ?? null));
1783
+ }
1784
+ onEndTimeChange(time) {
1785
+ if (!time)
1786
+ return;
1787
+ const value = this.internalValue();
1788
+ const endDate = value?.end ?? this.dateAdapter.today();
1789
+ const updated = this.dateAdapter.setTime(endDate, time.hours, time.minutes, time.seconds);
1790
+ this.updateValue(createDateRangeValue(value?.start ?? null, updated));
1791
+ }
1792
+ // ============ FormFieldControl IMPLEMENTATION ============
1793
+ /**
1794
+ * Called when the form field container is clicked.
1795
+ * Implements FormFieldControl.
1796
+ */
1797
+ onContainerClick(event) {
1798
+ const target = event.target;
1799
+ if (!this.disabled() && !this.triggerRef().nativeElement.contains(target)) {
1800
+ this.open();
1801
+ this.startInputRef().nativeElement.focus();
1802
+ }
1803
+ }
1804
+ /**
1805
+ * Sets the describedBy IDs from the form field.
1806
+ * Called by the parent form field component.
1807
+ */
1808
+ setDescribedByIds(ids) {
1809
+ this._describedByIds.set(ids);
1810
+ }
1811
+ /**
1812
+ * Sets the appearance for styling.
1813
+ * Called by the parent form field component.
1814
+ */
1815
+ setAppearance(appearance) {
1816
+ this._appearance.set(appearance);
1817
+ }
1818
+ // ============ PRIVATE METHODS ============
1819
+ createOverlay() {
1820
+ if (this.overlayRef) {
1821
+ return;
1822
+ }
1823
+ const hostEl = this.elementRef.nativeElement;
1824
+ const positionStrategy = this.overlay
1825
+ .position()
1826
+ .flexibleConnectedTo(hostEl)
1827
+ .withPositions(DEFAULT_POSITIONS)
1828
+ .withFlexibleDimensions(false)
1829
+ .withPush(true);
1830
+ this.overlayRef = this.overlay.create({
1831
+ positionStrategy,
1832
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
1833
+ hasBackdrop: true,
1834
+ backdropClass: 'cdk-overlay-transparent-backdrop',
1835
+ });
1836
+ // Attach panel template
1837
+ const portal = new TemplatePortal(this.panelTemplateRef(), this.viewContainerRef);
1838
+ this.overlayRef.attach(portal);
1839
+ // Close on backdrop click
1840
+ this.overlayRef
1841
+ .backdropClick()
1842
+ .pipe(takeUntilDestroyed(this.destroyRef))
1843
+ .subscribe(() => this.close());
1844
+ // Close on outside click
1845
+ this.overlayRef
1846
+ .outsidePointerEvents()
1847
+ .pipe(takeUntilDestroyed(this.destroyRef))
1848
+ .subscribe(() => this.close());
1849
+ }
1850
+ destroyOverlay() {
1851
+ if (this.overlayRef) {
1852
+ this.overlayRef.dispose();
1853
+ this.overlayRef = null;
1854
+ }
1855
+ }
1856
+ updateValue(value) {
1857
+ this.value.set(value);
1858
+ this.onChange(value);
1859
+ this.rangeChange.emit(value);
1860
+ this.onValidatorChange();
1861
+ }
1862
+ parseAndSetStart(inputValue) {
1863
+ const currentValue = this.internalValue();
1864
+ if (!inputValue.trim()) {
1865
+ // Clear start date
1866
+ if (currentValue?.start) {
1867
+ this.updateValue(createDateRangeValue(null, currentValue.end));
1868
+ }
1869
+ return;
1870
+ }
1871
+ const parsed = this.dateAdapter.parse(inputValue, this.effectiveDateFormat());
1872
+ if (parsed && this.dateAdapter.isValid(parsed) && this.isDateValid(parsed)) {
1873
+ // Ensure start <= end
1874
+ if (currentValue?.end && this.dateAdapter.compareDate(parsed, currentValue.end) > 0) {
1875
+ // Swap dates
1876
+ this.updateValue(createDateRangeValue(currentValue.end, parsed));
1877
+ }
1878
+ else {
1879
+ this.updateValue(createDateRangeValue(parsed, currentValue?.end ?? null));
1880
+ }
1881
+ }
1882
+ else {
1883
+ // Revert to current value
1884
+ this.startInputRef().nativeElement.value = this.startDisplayValue();
1885
+ }
1886
+ }
1887
+ parseAndSetEnd(inputValue) {
1888
+ const currentValue = this.internalValue();
1889
+ if (!inputValue.trim()) {
1890
+ // Clear end date
1891
+ if (currentValue?.end) {
1892
+ this.updateValue(createDateRangeValue(currentValue.start, null));
1893
+ }
1894
+ return;
1895
+ }
1896
+ const parsed = this.dateAdapter.parse(inputValue, this.effectiveDateFormat());
1897
+ if (parsed && this.dateAdapter.isValid(parsed) && this.isDateValid(parsed)) {
1898
+ // Ensure start <= end
1899
+ if (currentValue?.start && this.dateAdapter.compareDate(parsed, currentValue.start) < 0) {
1900
+ // Swap dates
1901
+ this.updateValue(createDateRangeValue(parsed, currentValue.start));
1902
+ }
1903
+ else {
1904
+ this.updateValue(createDateRangeValue(currentValue?.start ?? null, parsed));
1905
+ }
1906
+ }
1907
+ else {
1908
+ // Revert to current value
1909
+ this.endInputRef().nativeElement.value = this.endDisplayValue();
1910
+ }
1911
+ }
1912
+ isDateValid(date) {
1913
+ const min = this.min();
1914
+ const max = this.max();
1915
+ const filter = this.dateFilter();
1916
+ if (min && this.dateAdapter.compareDate(date, min) < 0) {
1917
+ return false;
1918
+ }
1919
+ if (max && this.dateAdapter.compareDate(date, max) > 0) {
1920
+ return false;
1921
+ }
1922
+ if (filter && !filter(date)) {
1923
+ return false;
1924
+ }
1925
+ return true;
1926
+ }
1927
+ formatDate(date) {
1928
+ return this.dateAdapter.format(date, 'long');
1929
+ }
1930
+ announce(message) {
1931
+ this.liveAnnouncement.set(message);
1932
+ }
1933
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1934
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ComDateRangePicker, isStandalone: true, selector: "com-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, startView: { classPropertyName: "startView", publicName: "startView", isSignal: true, isRequired: false, transformFunction: null }, firstDayOfWeek: { classPropertyName: "firstDayOfWeek", publicName: "firstDayOfWeek", isSignal: true, isRequired: false, transformFunction: null }, startPlaceholder: { classPropertyName: "startPlaceholder", publicName: "startPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, endPlaceholder: { classPropertyName: "endPlaceholder", publicName: "endPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, showClearButton: { classPropertyName: "showClearButton", publicName: "showClearButton", isSignal: true, isRequired: false, transformFunction: null }, showTodayButton: { classPropertyName: "showTodayButton", publicName: "showTodayButton", isSignal: true, isRequired: false, transformFunction: null }, showFooterClearButton: { classPropertyName: "showFooterClearButton", publicName: "showFooterClearButton", isSignal: true, isRequired: false, transformFunction: null }, keepOpen: { classPropertyName: "keepOpen", publicName: "keepOpen", isSignal: true, isRequired: false, transformFunction: null }, allowManualInput: { classPropertyName: "allowManualInput", publicName: "allowManualInput", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, startAriaLabel: { classPropertyName: "startAriaLabel", publicName: "startAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, endAriaLabel: { classPropertyName: "endAriaLabel", publicName: "endAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, sfErrors: { classPropertyName: "sfErrors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, showTimePicker: { classPropertyName: "showTimePicker", publicName: "showTimePicker", isSignal: true, isRequired: false, transformFunction: null }, use12HourFormat: { classPropertyName: "use12HourFormat", publicName: "use12HourFormat", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", rangeChange: "rangeChange", opened: "opened", closed: "closed" }, host: { properties: { "class.com-date-range-picker-disabled": "disabled()", "class.com-date-range-picker-open": "isOpen()" }, classAttribute: "com-date-range-picker-host inline-block" }, providers: [
1935
+ RangeSelectionStrategy,
1936
+ { provide: CALENDAR_SELECTION_STRATEGY, useExisting: RangeSelectionStrategy },
1937
+ { provide: FormFieldControl, useExisting: forwardRef(() => ComDateRangePicker) },
1938
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerElement"], descendants: true, isSignal: true }, { propertyName: "startInputRef", first: true, predicate: ["startInputElement"], descendants: true, isSignal: true }, { propertyName: "endInputRef", first: true, predicate: ["endInputElement"], descendants: true, isSignal: true }, { propertyName: "panelTemplateRef", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }], exportAs: ["comDateRangePicker"], ngImport: i0, template: `
1939
+ <!-- Trigger container -->
1940
+ <div
1941
+ #triggerElement
1942
+ [class]="triggerClasses()"
1943
+ role="group"
1944
+ tabindex="-1"
1945
+ [attr.aria-expanded]="isOpen()"
1946
+ [attr.aria-haspopup]="'dialog'"
1947
+ [attr.aria-owns]="panelId()"
1948
+ [attr.aria-disabled]="disabled() || null"
1949
+ (click)="onTriggerClick()"
1950
+ (keydown)="onTriggerKeydown($event)"
1951
+ >
1952
+ <!-- Start date input -->
1953
+ <input
1954
+ #startInputElement
1955
+ type="text"
1956
+ [class]="inputClasses()"
1957
+ [value]="startDisplayValue()"
1958
+ [placeholder]="startPlaceholder()"
1959
+ [disabled]="disabled()"
1960
+ [readonly]="!allowManualInput()"
1961
+ [attr.id]="startInputId()"
1962
+ [attr.aria-label]="startAriaLabel() || startPlaceholder()"
1963
+ [attr.aria-describedby]="effectiveAriaDescribedBy() || null"
1964
+ [attr.aria-invalid]="effectiveState() === 'error' || null"
1965
+ [attr.aria-required]="required() || null"
1966
+ (focus)="onStartInputFocus()"
1967
+ (input)="onStartInputChange($event)"
1968
+ (blur)="onStartInputBlur()"
1969
+ (keydown)="onInputKeydown($event, 'start')"
1970
+ />
1971
+
1972
+ <!-- Range separator -->
1973
+ <span [class]="separatorClasses()">
1974
+ <com-icon name="arrow-right" [size]="iconSize()" />
1975
+ </span>
1976
+
1977
+ <!-- End date input -->
1978
+ <input
1979
+ #endInputElement
1980
+ type="text"
1981
+ [class]="inputClasses()"
1982
+ [value]="endDisplayValue()"
1983
+ [placeholder]="endPlaceholder()"
1984
+ [disabled]="disabled()"
1985
+ [readonly]="!allowManualInput()"
1986
+ [attr.id]="endInputId()"
1987
+ [attr.aria-label]="endAriaLabel() || endPlaceholder()"
1988
+ [attr.aria-invalid]="effectiveState() === 'error' || null"
1989
+ (focus)="onEndInputFocus()"
1990
+ (input)="onEndInputChange($event)"
1991
+ (blur)="onEndInputBlur()"
1992
+ (keydown)="onInputKeydown($event, 'end')"
1993
+ />
1994
+
1995
+ <!-- Clear button -->
1996
+ @if (showClearButton() && hasValue() && !disabled()) {
1997
+ <button
1998
+ type="button"
1999
+ [class]="clearClasses()"
2000
+ [attr.aria-label]="'Clear date range'"
2001
+ (click)="clear($event)"
2002
+ >
2003
+ <com-icon name="x" [size]="iconSize()" />
2004
+ </button>
2005
+ }
2006
+
2007
+ <!-- Calendar icon -->
2008
+ <button
2009
+ type="button"
2010
+ [class]="iconClasses()"
2011
+ [attr.aria-label]="isOpen() ? 'Close calendar' : 'Open calendar'"
2012
+ [disabled]="disabled()"
2013
+ tabindex="-1"
2014
+ >
2015
+ <com-icon name="calendar" [size]="iconSize()" />
2016
+ </button>
2017
+ </div>
2018
+
2019
+ <!-- Panel template (rendered in overlay) -->
2020
+ <ng-template #panelTemplate>
2021
+ <div
2022
+ [class]="panelClasses()"
2023
+ [attr.id]="panelId()"
2024
+ role="dialog"
2025
+ aria-modal="true"
2026
+ [attr.aria-label]="'Choose date range'"
2027
+ (keydown)="onPanelKeydown($event)"
2028
+ cdkTrapFocus
2029
+ [cdkTrapFocusAutoCapture]="true"
2030
+ >
2031
+ <com-calendar
2032
+ [activeDate]="calendarActiveDate()"
2033
+ [selected]="calendarSelection()"
2034
+ [minDate]="min()"
2035
+ [maxDate]="max()"
2036
+ [dateFilter]="dateFilter()"
2037
+ [startView]="startView()"
2038
+ [firstDayOfWeek]="firstDayOfWeek()"
2039
+ [monthColumns]="2"
2040
+ [bordered]="false"
2041
+ (selectedChange)="onCalendarSelectionChange($event)"
2042
+ (activeDateChange)="onActiveDateChange($event)"
2043
+ />
2044
+
2045
+ @if (showTimePicker()) {
2046
+ <div [class]="timeSectionClasses()">
2047
+ <div class="flex items-center gap-3">
2048
+ <div class="flex flex-col gap-1">
2049
+ <span [class]="timeLabelClasses()">Start time</span>
2050
+ <com-time-picker
2051
+ variant="embedded"
2052
+ [size]="size()"
2053
+ [value]="startTimeValue()"
2054
+ [use12HourFormat]="use12HourFormat()"
2055
+ [showSeconds]="showSeconds()"
2056
+ [minuteStep]="minuteStep()"
2057
+ [disabled]="disabled()"
2058
+ ariaLabel="Start time"
2059
+ (timeChange)="onStartTimeChange($event)"
2060
+ />
2061
+ </div>
2062
+ <span class="text-muted-foreground mt-5">
2063
+ <com-icon name="arrow-right" [size]="iconSize()" />
2064
+ </span>
2065
+ <div class="flex flex-col gap-1">
2066
+ <span [class]="timeLabelClasses()">End time</span>
2067
+ <com-time-picker
2068
+ variant="embedded"
2069
+ [size]="size()"
2070
+ [value]="endTimeValue()"
2071
+ [use12HourFormat]="use12HourFormat()"
2072
+ [showSeconds]="showSeconds()"
2073
+ [minuteStep]="minuteStep()"
2074
+ [disabled]="disabled()"
2075
+ ariaLabel="End time"
2076
+ (timeChange)="onEndTimeChange($event)"
2077
+ />
2078
+ </div>
2079
+ </div>
2080
+ </div>
2081
+ }
2082
+
2083
+ @if (showTodayButton() || showFooterClearButton() || showTimePicker()) {
2084
+ <div [class]="footerClasses()">
2085
+ @if (showTodayButton()) {
2086
+ <button
2087
+ type="button"
2088
+ [class]="todayButtonClasses()"
2089
+ (click)="selectToday()"
2090
+ >
2091
+ Today
2092
+ </button>
2093
+ }
2094
+ @if (showFooterClearButton()) {
2095
+ <button
2096
+ type="button"
2097
+ [class]="clearButtonClasses()"
2098
+ (click)="clear($event)"
2099
+ >
2100
+ Clear
2101
+ </button>
2102
+ }
2103
+ @if (showTimePicker()) {
2104
+ <button
2105
+ type="button"
2106
+ [class]="todayButtonClasses()"
2107
+ (click)="close()"
2108
+ >
2109
+ Done
2110
+ </button>
2111
+ }
2112
+ </div>
2113
+ }
2114
+ </div>
2115
+ </ng-template>
2116
+
2117
+ <!-- Live announcer region -->
2118
+ <div class="sr-only" aria-live="polite" aria-atomic="true">
2119
+ {{ liveAnnouncement() }}
2120
+ </div>
2121
+ `, isInline: true, styles: [".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: ComCalendar, selector: "com-calendar", inputs: ["activeDate", "selected", "minDate", "maxDate", "dateFilter", "dateClass", "bordered", "startView", "firstDayOfWeek", "monthColumns", "cellTemplate"], outputs: ["selectedChange", "viewChanged", "activeDateChange"] }, { kind: "component", type: ComIcon, selector: "com-icon", inputs: ["name", "img", "color", "size", "strokeWidth", "absoluteStrokeWidth", "ariaLabel"] }, { kind: "component", type: ComTimePicker, selector: "com-time-picker", inputs: ["value", "disabled", "required", "showSeconds", "use12HourFormat", "minuteStep", "secondStep", "minTime", "maxTime", "variant", "size", "state", "ariaLabel", "class", "placeholder", "errorStateMatcher", "touched", "invalid", "errors"], outputs: ["valueChange", "disabledChange", "touchedChange", "timeChange"], exportAs: ["comTimePicker"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2122
+ }
2123
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComDateRangePicker, decorators: [{
2124
+ type: Component,
2125
+ args: [{ selector: 'com-date-range-picker', exportAs: 'comDateRangePicker', template: `
2126
+ <!-- Trigger container -->
2127
+ <div
2128
+ #triggerElement
2129
+ [class]="triggerClasses()"
2130
+ role="group"
2131
+ tabindex="-1"
2132
+ [attr.aria-expanded]="isOpen()"
2133
+ [attr.aria-haspopup]="'dialog'"
2134
+ [attr.aria-owns]="panelId()"
2135
+ [attr.aria-disabled]="disabled() || null"
2136
+ (click)="onTriggerClick()"
2137
+ (keydown)="onTriggerKeydown($event)"
2138
+ >
2139
+ <!-- Start date input -->
2140
+ <input
2141
+ #startInputElement
2142
+ type="text"
2143
+ [class]="inputClasses()"
2144
+ [value]="startDisplayValue()"
2145
+ [placeholder]="startPlaceholder()"
2146
+ [disabled]="disabled()"
2147
+ [readonly]="!allowManualInput()"
2148
+ [attr.id]="startInputId()"
2149
+ [attr.aria-label]="startAriaLabel() || startPlaceholder()"
2150
+ [attr.aria-describedby]="effectiveAriaDescribedBy() || null"
2151
+ [attr.aria-invalid]="effectiveState() === 'error' || null"
2152
+ [attr.aria-required]="required() || null"
2153
+ (focus)="onStartInputFocus()"
2154
+ (input)="onStartInputChange($event)"
2155
+ (blur)="onStartInputBlur()"
2156
+ (keydown)="onInputKeydown($event, 'start')"
2157
+ />
2158
+
2159
+ <!-- Range separator -->
2160
+ <span [class]="separatorClasses()">
2161
+ <com-icon name="arrow-right" [size]="iconSize()" />
2162
+ </span>
2163
+
2164
+ <!-- End date input -->
2165
+ <input
2166
+ #endInputElement
2167
+ type="text"
2168
+ [class]="inputClasses()"
2169
+ [value]="endDisplayValue()"
2170
+ [placeholder]="endPlaceholder()"
2171
+ [disabled]="disabled()"
2172
+ [readonly]="!allowManualInput()"
2173
+ [attr.id]="endInputId()"
2174
+ [attr.aria-label]="endAriaLabel() || endPlaceholder()"
2175
+ [attr.aria-invalid]="effectiveState() === 'error' || null"
2176
+ (focus)="onEndInputFocus()"
2177
+ (input)="onEndInputChange($event)"
2178
+ (blur)="onEndInputBlur()"
2179
+ (keydown)="onInputKeydown($event, 'end')"
2180
+ />
2181
+
2182
+ <!-- Clear button -->
2183
+ @if (showClearButton() && hasValue() && !disabled()) {
2184
+ <button
2185
+ type="button"
2186
+ [class]="clearClasses()"
2187
+ [attr.aria-label]="'Clear date range'"
2188
+ (click)="clear($event)"
2189
+ >
2190
+ <com-icon name="x" [size]="iconSize()" />
2191
+ </button>
2192
+ }
2193
+
2194
+ <!-- Calendar icon -->
2195
+ <button
2196
+ type="button"
2197
+ [class]="iconClasses()"
2198
+ [attr.aria-label]="isOpen() ? 'Close calendar' : 'Open calendar'"
2199
+ [disabled]="disabled()"
2200
+ tabindex="-1"
2201
+ >
2202
+ <com-icon name="calendar" [size]="iconSize()" />
2203
+ </button>
2204
+ </div>
2205
+
2206
+ <!-- Panel template (rendered in overlay) -->
2207
+ <ng-template #panelTemplate>
2208
+ <div
2209
+ [class]="panelClasses()"
2210
+ [attr.id]="panelId()"
2211
+ role="dialog"
2212
+ aria-modal="true"
2213
+ [attr.aria-label]="'Choose date range'"
2214
+ (keydown)="onPanelKeydown($event)"
2215
+ cdkTrapFocus
2216
+ [cdkTrapFocusAutoCapture]="true"
2217
+ >
2218
+ <com-calendar
2219
+ [activeDate]="calendarActiveDate()"
2220
+ [selected]="calendarSelection()"
2221
+ [minDate]="min()"
2222
+ [maxDate]="max()"
2223
+ [dateFilter]="dateFilter()"
2224
+ [startView]="startView()"
2225
+ [firstDayOfWeek]="firstDayOfWeek()"
2226
+ [monthColumns]="2"
2227
+ [bordered]="false"
2228
+ (selectedChange)="onCalendarSelectionChange($event)"
2229
+ (activeDateChange)="onActiveDateChange($event)"
2230
+ />
2231
+
2232
+ @if (showTimePicker()) {
2233
+ <div [class]="timeSectionClasses()">
2234
+ <div class="flex items-center gap-3">
2235
+ <div class="flex flex-col gap-1">
2236
+ <span [class]="timeLabelClasses()">Start time</span>
2237
+ <com-time-picker
2238
+ variant="embedded"
2239
+ [size]="size()"
2240
+ [value]="startTimeValue()"
2241
+ [use12HourFormat]="use12HourFormat()"
2242
+ [showSeconds]="showSeconds()"
2243
+ [minuteStep]="minuteStep()"
2244
+ [disabled]="disabled()"
2245
+ ariaLabel="Start time"
2246
+ (timeChange)="onStartTimeChange($event)"
2247
+ />
2248
+ </div>
2249
+ <span class="text-muted-foreground mt-5">
2250
+ <com-icon name="arrow-right" [size]="iconSize()" />
2251
+ </span>
2252
+ <div class="flex flex-col gap-1">
2253
+ <span [class]="timeLabelClasses()">End time</span>
2254
+ <com-time-picker
2255
+ variant="embedded"
2256
+ [size]="size()"
2257
+ [value]="endTimeValue()"
2258
+ [use12HourFormat]="use12HourFormat()"
2259
+ [showSeconds]="showSeconds()"
2260
+ [minuteStep]="minuteStep()"
2261
+ [disabled]="disabled()"
2262
+ ariaLabel="End time"
2263
+ (timeChange)="onEndTimeChange($event)"
2264
+ />
2265
+ </div>
2266
+ </div>
2267
+ </div>
2268
+ }
2269
+
2270
+ @if (showTodayButton() || showFooterClearButton() || showTimePicker()) {
2271
+ <div [class]="footerClasses()">
2272
+ @if (showTodayButton()) {
2273
+ <button
2274
+ type="button"
2275
+ [class]="todayButtonClasses()"
2276
+ (click)="selectToday()"
2277
+ >
2278
+ Today
2279
+ </button>
2280
+ }
2281
+ @if (showFooterClearButton()) {
2282
+ <button
2283
+ type="button"
2284
+ [class]="clearButtonClasses()"
2285
+ (click)="clear($event)"
2286
+ >
2287
+ Clear
2288
+ </button>
2289
+ }
2290
+ @if (showTimePicker()) {
2291
+ <button
2292
+ type="button"
2293
+ [class]="todayButtonClasses()"
2294
+ (click)="close()"
2295
+ >
2296
+ Done
2297
+ </button>
2298
+ }
2299
+ </div>
2300
+ }
2301
+ </div>
2302
+ </ng-template>
2303
+
2304
+ <!-- Live announcer region -->
2305
+ <div class="sr-only" aria-live="polite" aria-atomic="true">
2306
+ {{ liveAnnouncement() }}
2307
+ </div>
2308
+ `, imports: [
2309
+ OverlayModule,
2310
+ A11yModule,
2311
+ ComCalendar,
2312
+ ComIcon,
2313
+ ComTimePicker,
2314
+ ], providers: [
2315
+ RangeSelectionStrategy,
2316
+ { provide: CALENDAR_SELECTION_STRATEGY, useExisting: RangeSelectionStrategy },
2317
+ { provide: FormFieldControl, useExisting: forwardRef(() => ComDateRangePicker) },
2318
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: {
2319
+ class: 'com-date-range-picker-host inline-block',
2320
+ '[class.com-date-range-picker-disabled]': 'disabled()',
2321
+ '[class.com-date-range-picker-open]': 'isOpen()',
2322
+ }, styles: [".sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n"] }]
2323
+ }], ctorParameters: () => [], propDecorators: { triggerRef: [{ type: i0.ViewChild, args: ['triggerElement', { isSignal: true }] }], startInputRef: [{ type: i0.ViewChild, args: ['startInputElement', { isSignal: true }] }], endInputRef: [{ type: i0.ViewChild, args: ['endInputElement', { isSignal: true }] }], panelTemplateRef: [{ type: i0.ViewChild, args: ['panelTemplate', { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], dateFilter: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFilter", required: false }] }], startAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAt", required: false }] }], startView: [{ type: i0.Input, args: [{ isSignal: true, alias: "startView", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], startPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPlaceholder", required: false }] }], endPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "endPlaceholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], showClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], showTodayButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTodayButton", required: false }] }], showFooterClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooterClearButton", required: false }] }], keepOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepOpen", required: false }] }], allowManualInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowManualInput", required: false }] }], panelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelClass", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], startAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAriaLabel", required: false }] }], endAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "endAriaLabel", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], sfErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], showTimePicker: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTimePicker", required: false }] }], use12HourFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "use12HourFormat", required: false }] }], showSeconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSeconds", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
2324
+
2325
+ /**
2326
+ * Datepicker components public API.
2327
+ */
2328
+
2329
+ /**
2330
+ * Generated bundle index. Do not edit.
2331
+ */
2332
+
2333
+ export { ComDateRangePicker, ComDatepicker, createDateRangeValue, datepickerClearVariants, datepickerDisabledVariants, datepickerFooterButtonVariants, datepickerFooterVariants, datepickerIconVariants, datepickerInputVariants, datepickerPanelVariants, datepickerRangeSeparatorVariants, datepickerTriggerVariants, generateDatepickerId };
2334
+ //# sourceMappingURL=ngx-com-components-datepicker.mjs.map