@cdevhub/ngx-tw 0.2.1 → 0.3.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 (139) hide show
  1. package/README.md +264 -49
  2. package/fesm2022/cdevhub-ngx-tw-accordion.mjs +11 -7
  3. package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
  4. package/fesm2022/cdevhub-ngx-tw-alert.mjs +43 -29
  5. package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -1
  6. package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs +103 -0
  7. package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs.map +1 -0
  8. package/fesm2022/cdevhub-ngx-tw-avatar.mjs +56 -31
  9. package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
  10. package/fesm2022/cdevhub-ngx-tw-badge.mjs +61 -35
  11. package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
  12. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +154 -93
  13. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
  14. package/fesm2022/cdevhub-ngx-tw-button.mjs +24 -15
  15. package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
  16. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +3 -3
  17. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
  18. package/fesm2022/cdevhub-ngx-tw-calendar.mjs +408 -215
  19. package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
  20. package/fesm2022/cdevhub-ngx-tw-card.mjs +33 -24
  21. package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
  22. package/fesm2022/cdevhub-ngx-tw-carousel.mjs +144 -79
  23. package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
  24. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +99 -47
  25. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
  26. package/fesm2022/cdevhub-ngx-tw-code-block.mjs +34 -20
  27. package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
  28. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +46 -29
  29. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
  30. package/fesm2022/cdevhub-ngx-tw-combobox.mjs +195 -110
  31. package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
  32. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +140 -73
  33. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
  34. package/fesm2022/cdevhub-ngx-tw-core.mjs +16 -11
  35. package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
  36. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +393 -149
  37. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
  38. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +305 -150
  39. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
  40. package/fesm2022/cdevhub-ngx-tw-dialog.mjs +56 -43
  41. package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
  42. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +51 -33
  43. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
  44. package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +905 -0
  45. package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -0
  46. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +49 -26
  47. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
  48. package/fesm2022/cdevhub-ngx-tw-form-field.mjs +133 -74
  49. package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
  50. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +2 -2
  51. package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -1
  52. package/fesm2022/cdevhub-ngx-tw-icon.mjs +28 -17
  53. package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
  54. package/fesm2022/cdevhub-ngx-tw-input.mjs +37 -21
  55. package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
  56. package/fesm2022/cdevhub-ngx-tw-item.mjs +33 -24
  57. package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
  58. package/fesm2022/cdevhub-ngx-tw-menu.mjs +63 -48
  59. package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
  60. package/fesm2022/cdevhub-ngx-tw-number-input.mjs +622 -0
  61. package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -0
  62. package/fesm2022/cdevhub-ngx-tw-paginator.mjs +137 -71
  63. package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
  64. package/fesm2022/cdevhub-ngx-tw-popover.mjs +101 -46
  65. package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
  66. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +57 -30
  67. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
  68. package/fesm2022/cdevhub-ngx-tw-radio.mjs +108 -57
  69. package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
  70. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +46 -26
  71. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
  72. package/fesm2022/cdevhub-ngx-tw-select.mjs +217 -122
  73. package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
  74. package/fesm2022/cdevhub-ngx-tw-separator.mjs +21 -12
  75. package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -1
  76. package/fesm2022/cdevhub-ngx-tw-sheet.mjs +58 -44
  77. package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
  78. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +25 -14
  79. package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -1
  80. package/fesm2022/cdevhub-ngx-tw-slider.mjs +155 -79
  81. package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
  82. package/fesm2022/cdevhub-ngx-tw-sort.mjs +44 -25
  83. package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
  84. package/fesm2022/cdevhub-ngx-tw-spinner.mjs +29 -16
  85. package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -1
  86. package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -42
  87. package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
  88. package/fesm2022/cdevhub-ngx-tw-stat.mjs +107 -64
  89. package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
  90. package/fesm2022/cdevhub-ngx-tw-stepper.mjs +113 -47
  91. package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
  92. package/fesm2022/cdevhub-ngx-tw-switch.mjs +61 -32
  93. package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
  94. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +88 -42
  95. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
  96. package/fesm2022/cdevhub-ngx-tw-table.mjs +266 -118
  97. package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
  98. package/fesm2022/cdevhub-ngx-tw-tabs.mjs +101 -51
  99. package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
  100. package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +798 -0
  101. package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -0
  102. package/fesm2022/cdevhub-ngx-tw-textarea.mjs +11 -7
  103. package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
  104. package/fesm2022/cdevhub-ngx-tw-theme.mjs +22 -14
  105. package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
  106. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +243 -95
  107. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
  108. package/fesm2022/cdevhub-ngx-tw-timeline.mjs +149 -82
  109. package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
  110. package/fesm2022/cdevhub-ngx-tw-toast.mjs +89 -55
  111. package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
  112. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +50 -28
  113. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
  114. package/fesm2022/cdevhub-ngx-tw-transfer.mjs +1129 -0
  115. package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -0
  116. package/fesm2022/cdevhub-ngx-tw-tree.mjs +554 -0
  117. package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -0
  118. package/fesm2022/cdevhub-ngx-tw.mjs +7 -0
  119. package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -1
  120. package/package.json +45 -20
  121. package/theme/index.css +10 -2
  122. package/types/cdevhub-ngx-tw-aspect-ratio.d.ts +40 -0
  123. package/types/cdevhub-ngx-tw-calendar.d.ts +39 -8
  124. package/types/cdevhub-ngx-tw-command-palette.d.ts +7 -0
  125. package/types/cdevhub-ngx-tw-date-picker.d.ts +36 -10
  126. package/types/cdevhub-ngx-tw-file-upload.d.ts +250 -0
  127. package/types/cdevhub-ngx-tw-form-field.d.ts +22 -3
  128. package/types/cdevhub-ngx-tw-icon-lucide.d.ts +21 -3
  129. package/types/cdevhub-ngx-tw-number-input.d.ts +130 -0
  130. package/types/cdevhub-ngx-tw-popover.d.ts +8 -0
  131. package/types/cdevhub-ngx-tw-stepper.d.ts +30 -2
  132. package/types/cdevhub-ngx-tw-tab-nav.d.ts +8 -0
  133. package/types/cdevhub-ngx-tw-table.d.ts +30 -1
  134. package/types/cdevhub-ngx-tw-tabs.d.ts +8 -0
  135. package/types/cdevhub-ngx-tw-tags-input.d.ts +217 -0
  136. package/types/cdevhub-ngx-tw-time-picker.d.ts +31 -6
  137. package/types/cdevhub-ngx-tw-transfer.d.ts +330 -0
  138. package/types/cdevhub-ngx-tw-tree.d.ts +189 -0
  139. package/types/cdevhub-ngx-tw.d.ts +7 -0
@@ -1,14 +1,14 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, ElementRef, viewChild, signal, computed, ChangeDetectionStrategy, Component, input, booleanAttribute, model, output, ViewContainerRef, Injector, DestroyRef, linkedSignal, effect, untracked, afterNextRender, isDevMode, forwardRef } from '@angular/core';
3
- import { NgControl, NgForm, FormGroupDirective, Validators } from '@angular/forms';
3
+ import { NgForm, FormGroupDirective, Validators, NgControl, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
4
4
  import { Overlay } from '@angular/cdk/overlay';
5
5
  import { FocusMonitor, LiveAnnouncer } from '@angular/cdk/a11y';
6
6
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
7
7
  import { merge } from 'rxjs';
8
8
  import { tv } from 'tailwind-variants';
9
9
  import { PickerOverlayCoordinator, TW_ERROR_STATE_MATCHER, timeOfDaySeconds, resolveSelectScrollStrategy, buildSelectLikePositions } from '@cdevhub/ngx-tw/core';
10
- import { FormFieldControl, FormFieldComponent, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
11
- import { CalendarComponent, provideSingleSelectionStrategy, DATE_ADAPTER } from '@cdevhub/ngx-tw/calendar';
10
+ import { FormFieldControl, TW_FORM_FIELD, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
11
+ import { CalendarComponent, provideSingleSelectionStrategy, DATE_ADAPTER, calendarValidator } from '@cdevhub/ngx-tw/calendar';
12
12
  import { ButtonDirective } from '@cdevhub/ngx-tw/button';
13
13
  import { TimePickerComponent } from '@cdevhub/ngx-tw/time-picker';
14
14
 
@@ -20,89 +20,127 @@ import { TimePickerComponent } from '@cdevhub/ngx-tw/time-picker';
20
20
  */
21
21
  class DatePickerOverlayComponent {
22
22
  elementRef = inject((ElementRef));
23
- calendarRef = viewChild('calendar', ...(ngDevMode ? [{ debugName: "calendarRef" }] : /* istanbul ignore next */ []));
23
+ calendarRef = viewChild('calendar', /* @ts-ignore */
24
+ ...(ngDevMode ? [{ debugName: "calendarRef" }] : /* istanbul ignore next */ []));
24
25
  // ── Config signals set by the parent component ──
25
26
  /** @internal */
26
- size = signal('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
27
+ size = signal('md', /* @ts-ignore */
28
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
27
29
  /** @internal */
28
- color = signal('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
30
+ color = signal('primary', /* @ts-ignore */
31
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
29
32
  /** @internal */
30
- minDate = signal(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
33
+ minDate = signal(null, /* @ts-ignore */
34
+ ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
31
35
  /** @internal */
32
- maxDate = signal(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
36
+ maxDate = signal(null, /* @ts-ignore */
37
+ ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
33
38
  /** @internal */
34
- dateFilter = signal(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
39
+ dateFilter = signal(null, /* @ts-ignore */
40
+ ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
35
41
  /** @internal */
36
- startView = signal('day', ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
42
+ startView = signal('day', /* @ts-ignore */
43
+ ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
37
44
  /** @internal */
38
- startAt = signal(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
45
+ startAt = signal(null, /* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
39
47
  /** @internal Current committed-or-pending value shown as selected in the calendar. */
40
- pendingValue = signal(null, ...(ngDevMode ? [{ debugName: "pendingValue" }] : /* istanbul ignore next */ []));
48
+ pendingValue = signal(null, /* @ts-ignore */
49
+ ...(ngDevMode ? [{ debugName: "pendingValue" }] : /* istanbul ignore next */ []));
41
50
  /** @internal */
42
- showActions = signal(false, ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
51
+ showActions = signal(false, /* @ts-ignore */
52
+ ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
43
53
  /** @internal */
44
- todayLabel = signal('Today', ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
54
+ todayLabel = signal('Today', /* @ts-ignore */
55
+ ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
45
56
  /** @internal */
46
- clearLabel = signal('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
57
+ clearLabel = signal('Clear', /* @ts-ignore */
58
+ ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
47
59
  /** @internal */
48
- cancelLabel = signal('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
60
+ cancelLabel = signal('Cancel', /* @ts-ignore */
61
+ ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
49
62
  /** @internal */
50
- applyLabel = signal('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
63
+ applyLabel = signal('Apply', /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
51
65
  /** @internal */
52
- dialogId = signal('', ...(ngDevMode ? [{ debugName: "dialogId" }] : /* istanbul ignore next */ []));
66
+ dialogId = signal('', /* @ts-ignore */
67
+ ...(ngDevMode ? [{ debugName: "dialogId" }] : /* istanbul ignore next */ []));
53
68
  /** @internal */
54
- dialogAriaLabel = signal('Choose a date', ...(ngDevMode ? [{ debugName: "dialogAriaLabel" }] : /* istanbul ignore next */ []));
69
+ dialogAriaLabel = signal('Choose a date', /* @ts-ignore */
70
+ ...(ngDevMode ? [{ debugName: "dialogAriaLabel" }] : /* istanbul ignore next */ []));
55
71
  /** @internal */
56
- panelClassValue = signal('', ...(ngDevMode ? [{ debugName: "panelClassValue" }] : /* istanbul ignore next */ []));
72
+ panelClassValue = signal('', /* @ts-ignore */
73
+ ...(ngDevMode ? [{ debugName: "panelClassValue" }] : /* istanbul ignore next */ []));
57
74
  /** @internal */
58
- leaving = signal(false, ...(ngDevMode ? [{ debugName: "leaving" }] : /* istanbul ignore next */ []));
75
+ leaving = signal(false, /* @ts-ignore */
76
+ ...(ngDevMode ? [{ debugName: "leaving" }] : /* istanbul ignore next */ []));
59
77
  /** @internal Forwarded to the calendar — per-cell CSS classes. */
60
- dateClass = signal(null, ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
78
+ dateClass = signal(null, /* @ts-ignore */
79
+ ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
61
80
  /** @internal Forwarded to the calendar — cell-content template. */
62
- cellTemplate = signal(null, ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
81
+ cellTemplate = signal(null, /* @ts-ignore */
82
+ ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
63
83
  /** @internal Forwarded to the calendar — per-instance locale override. */
64
- locale = signal(null, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
84
+ locale = signal(null, /* @ts-ignore */
85
+ ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
65
86
  /** @internal Quick-select presets rendered above the calendar. */
66
- presets = signal([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
87
+ presets = signal([], /* @ts-ignore */
88
+ ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
67
89
  // ── Time-picker config ──
68
90
  /** @internal */
69
- withTime = signal(false, ...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
91
+ withTime = signal(false, /* @ts-ignore */
92
+ ...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
70
93
  /** @internal */
71
- timeFormat = signal('24h', ...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
94
+ timeFormat = signal('24h', /* @ts-ignore */
95
+ ...(ngDevMode ? [{ debugName: "timeFormat" }] : /* istanbul ignore next */ []));
72
96
  /** @internal */
73
- showSeconds = signal(false, ...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
97
+ showSeconds = signal(false, /* @ts-ignore */
98
+ ...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
74
99
  /** @internal */
75
- hourStep = signal(1, ...(ngDevMode ? [{ debugName: "hourStep" }] : /* istanbul ignore next */ []));
100
+ hourStep = signal(1, /* @ts-ignore */
101
+ ...(ngDevMode ? [{ debugName: "hourStep" }] : /* istanbul ignore next */ []));
76
102
  /** @internal */
77
- minuteStep = signal(1, ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
103
+ minuteStep = signal(1, /* @ts-ignore */
104
+ ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
78
105
  /** @internal */
79
- secondStep = signal(1, ...(ngDevMode ? [{ debugName: "secondStep" }] : /* istanbul ignore next */ []));
106
+ secondStep = signal(1, /* @ts-ignore */
107
+ ...(ngDevMode ? [{ debugName: "secondStep" }] : /* istanbul ignore next */ []));
80
108
  /** @internal */
81
- minTime = signal(null, ...(ngDevMode ? [{ debugName: "minTime" }] : /* istanbul ignore next */ []));
109
+ minTime = signal(null, /* @ts-ignore */
110
+ ...(ngDevMode ? [{ debugName: "minTime" }] : /* istanbul ignore next */ []));
82
111
  /** @internal */
83
- maxTime = signal(null, ...(ngDevMode ? [{ debugName: "maxTime" }] : /* istanbul ignore next */ []));
112
+ maxTime = signal(null, /* @ts-ignore */
113
+ ...(ngDevMode ? [{ debugName: "maxTime" }] : /* istanbul ignore next */ []));
84
114
  // ── Callbacks set by the parent component ──
85
115
  /** @internal */
86
- onCalendarSelect = signal(() => { }, ...(ngDevMode ? [{ debugName: "onCalendarSelect" }] : /* istanbul ignore next */ []));
116
+ onCalendarSelect = signal(() => { }, /* @ts-ignore */
117
+ ...(ngDevMode ? [{ debugName: "onCalendarSelect" }] : /* istanbul ignore next */ []));
87
118
  /** @internal Fires whenever the embedded time-picker commits a new time. */
88
- onTimeInput = signal(() => { }, ...(ngDevMode ? [{ debugName: "onTimeInput" }] : /* istanbul ignore next */ []));
119
+ onTimeInput = signal(() => { }, /* @ts-ignore */
120
+ ...(ngDevMode ? [{ debugName: "onTimeInput" }] : /* istanbul ignore next */ []));
89
121
  /** @internal */
90
- onToday = signal(() => { }, ...(ngDevMode ? [{ debugName: "onToday" }] : /* istanbul ignore next */ []));
122
+ onToday = signal(() => { }, /* @ts-ignore */
123
+ ...(ngDevMode ? [{ debugName: "onToday" }] : /* istanbul ignore next */ []));
91
124
  /** @internal */
92
- onClear = signal(() => { }, ...(ngDevMode ? [{ debugName: "onClear" }] : /* istanbul ignore next */ []));
125
+ onClear = signal(() => { }, /* @ts-ignore */
126
+ ...(ngDevMode ? [{ debugName: "onClear" }] : /* istanbul ignore next */ []));
93
127
  /** @internal */
94
- onCancel = signal(() => { }, ...(ngDevMode ? [{ debugName: "onCancel" }] : /* istanbul ignore next */ []));
128
+ onCancel = signal(() => { }, /* @ts-ignore */
129
+ ...(ngDevMode ? [{ debugName: "onCancel" }] : /* istanbul ignore next */ []));
95
130
  /** @internal */
96
- onApply = signal(() => { }, ...(ngDevMode ? [{ debugName: "onApply" }] : /* istanbul ignore next */ []));
131
+ onApply = signal(() => { }, /* @ts-ignore */
132
+ ...(ngDevMode ? [{ debugName: "onApply" }] : /* istanbul ignore next */ []));
97
133
  /** @internal */
98
- onPresetSelect = signal(() => { }, ...(ngDevMode ? [{ debugName: "onPresetSelect" }] : /* istanbul ignore next */ []));
134
+ onPresetSelect = signal(() => { }, /* @ts-ignore */
135
+ ...(ngDevMode ? [{ debugName: "onPresetSelect" }] : /* istanbul ignore next */ []));
99
136
  // ── Classes ──
100
137
  /** @internal */
101
138
  panelClasses = computed(() => {
102
139
  const base = 'block bg-surface-overlay border border-border rounded-lg shadow-md overflow-hidden flex flex-col';
103
140
  const custom = this.panelClassValue();
104
141
  return custom ? `${base} ${custom}` : base;
105
- }, ...(ngDevMode ? [{ debugName: "panelClasses" }] : /* istanbul ignore next */ []));
142
+ }, /* @ts-ignore */
143
+ ...(ngDevMode ? [{ debugName: "panelClasses" }] : /* istanbul ignore next */ []));
106
144
  // ── Template callbacks ──
107
145
  /** @internal */
108
146
  onCalendarDateSelected(value) {
@@ -151,7 +189,8 @@ class DatePickerOverlayComponent {
151
189
  return !!preset.id && this.activePresetId() === preset.id;
152
190
  }
153
191
  /** @internal Tracks the most recently picked preset for visual state. Set by the parent. */
154
- activePresetId = signal(undefined, ...(ngDevMode ? [{ debugName: "activePresetId" }] : /* istanbul ignore next */ []));
192
+ activePresetId = signal(undefined, /* @ts-ignore */
193
+ ...(ngDevMode ? [{ debugName: "activePresetId" }] : /* istanbul ignore next */ []));
155
194
  /** @internal Moves focus to the active calendar cell. Called by the parent after open. */
156
195
  focusCalendar() {
157
196
  this.calendarRef()?.focusActiveCell();
@@ -160,8 +199,8 @@ class DatePickerOverlayComponent {
160
199
  getElement() {
161
200
  return this.elementRef.nativeElement;
162
201
  }
163
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
164
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DatePickerOverlayComponent, isStandalone: true, selector: "tw-date-picker-overlay", host: { properties: { "class": "panelClasses()", "id": "dialogId()", "attr.role": "\"dialog\"", "attr.aria-modal": "\"true\"", "attr.aria-label": "dialogAriaLabel()", "animate.enter": "\"scale-in\"", "animate.leave": "leaving() ? \"scale-out\" : \"\"" } }, providers: [provideSingleSelectionStrategy()], viewQueries: [{ propertyName: "calendarRef", first: true, predicate: ["calendar"], descendants: true, isSignal: true }], ngImport: i0, template: `
202
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: DatePickerOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
203
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: DatePickerOverlayComponent, isStandalone: true, selector: "tw-date-picker-overlay", host: { properties: { "class": "panelClasses()", "id": "dialogId()", "attr.role": "\"dialog\"", "attr.aria-modal": "\"true\"", "attr.aria-label": "dialogAriaLabel()", "animate.enter": "\"scale-in\"", "animate.leave": "leaving() ? \"scale-out\" : \"\"" } }, providers: [provideSingleSelectionStrategy()], viewQueries: [{ propertyName: "calendarRef", first: true, predicate: ["calendar"], descendants: true, isSignal: true }], ngImport: i0, template: `
165
204
  @if (presets().length) {
166
205
  <div
167
206
  class="flex flex-col gap-1 p-2 border-b border-border bg-surface-muted"
@@ -246,9 +285,9 @@ class DatePickerOverlayComponent {
246
285
  </div>
247
286
  </div>
248
287
  }
249
- `, isInline: true, dependencies: [{ kind: "component", type: CalendarComponent, selector: "tw-calendar", inputs: ["mode", "value", "startAt", "minDate", "maxDate", "dateFilter", "disabledDates", "disabledDaysOfWeek", "constraints", "minRangeLength", "maxRangeLength", "maxSelections", "maxSelectionBehavior", "errorAriaDescribedBy", "rangeClickBehavior", "rangeBehavior", "dateClass", "bordered", "startView", "firstDayOfWeek", "monthColumns", "disabled", "readonly", "resetBehavior", "cellTemplate", "locale", "intl"], outputs: ["modeChange", "valueChange", "selectionStart", "rangePreview", "selectionComplete", "selectionRestart", "selectionCleared", "selectionLimitReached", "presetChange", "viewChange", "activeDateChange", "monthChange", "yearChange", "cellClick", "cellHover"] }, { kind: "component", type: TimePickerComponent, selector: "tw-time-picker", inputs: ["id", "disabled", "required", "readonly", "size", "color", "variant", "format", "showSeconds", "hourStep", "minuteStep", "secondStep", "minTime", "maxTime", "referenceDate", "placeholder", "showSteppers", "showClear", "clearLabel", "errorStateMatcher", "aria-label", "aria-labelledby", "aria-describedby", "value"], outputs: ["valueChange", "timeInput", "timeChange"] }, { kind: "directive", type: ButtonDirective, selector: "[twButton]", inputs: ["variant", "color", "size", "disabled", "loading"], exportAs: ["twButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
288
+ `, isInline: true, dependencies: [{ kind: "component", type: CalendarComponent, selector: "tw-calendar", inputs: ["mode", "value", "startAt", "minDate", "maxDate", "dateFilter", "disabledDates", "disabledDaysOfWeek", "constraints", "minRangeLength", "maxRangeLength", "maxSelections", "maxSelectionBehavior", "errorAriaDescribedBy", "rangeClickBehavior", "rangeBehavior", "dateClass", "bordered", "startView", "firstDayOfWeek", "monthColumns", "disabled", "readonly", "resetBehavior", "cellTemplate", "locale", "intl"], outputs: ["valueChange", "selectionStart", "rangePreview", "selectionComplete", "selectionRestart", "selectionCleared", "selectionLimitReached", "presetChange", "viewChange", "activeDateChange", "monthChange", "yearChange", "cellClick", "cellHover", "modeChange"] }, { kind: "component", type: TimePickerComponent, selector: "tw-time-picker", inputs: ["id", "disabled", "required", "readonly", "size", "color", "variant", "format", "showSeconds", "hourStep", "minuteStep", "secondStep", "minTime", "maxTime", "referenceDate", "placeholder", "showSteppers", "showClear", "clearLabel", "errorStateMatcher", "aria-label", "aria-labelledby", "aria-describedby", "value"], outputs: ["valueChange", "timeInput", "timeChange"] }, { kind: "directive", type: ButtonDirective, selector: "[twButton]", inputs: ["variant", "color", "size", "disabled", "loading"], exportAs: ["twButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
250
289
  }
251
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerOverlayComponent, decorators: [{
290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: DatePickerOverlayComponent, decorators: [{
252
291
  type: Component,
253
292
  args: [{
254
293
  selector: 'tw-date-picker-overlay',
@@ -466,22 +505,30 @@ class DatePickerComponent extends FormFieldControl {
466
505
  // ── Inputs ──
467
506
  /** Id on the date-picker's input element. Auto-generated when not provided. Used by the form-field's `<label for>` attribute. */
468
507
  idInput = input(undefined, { ...(ngDevMode ? { debugName: "idInput" } : /* istanbul ignore next */ {}), alias: 'id' });
469
- /** Minimum selectable date. Typed input earlier than this sets `errorState` and the calendar disables the cell. Defaults to `null`. */
470
- minDate = input(null, ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
471
- /** Maximum selectable date. Typed input later than this sets `errorState` and the calendar disables the cell. Defaults to `null`. */
472
- maxDate = input(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
473
- /** Per-date predicate return `false` to disable. Applied in both the calendar and the text-parse path. */
474
- dateFilter = input(null, ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
508
+ /** Minimum selectable date. Typed input earlier than this commits the value and marks the bound control invalid with `calendarMinDate`; the calendar disables the cell. Defaults to `null`. */
509
+ minDate = input(null, /* @ts-ignore */
510
+ ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
511
+ /** Maximum selectable date. Typed input later than this commits the value and marks the bound control invalid with `calendarMaxDate`; the calendar disables the cell. Defaults to `null`. */
512
+ maxDate = input(null, /* @ts-ignore */
513
+ ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
514
+ /** Per-date predicate — return `false` to disable. Applied in the calendar, the text-parse path, and the `calendarDisabledDate` validation error. */
515
+ dateFilter = input(null, /* @ts-ignore */
516
+ ...(ngDevMode ? [{ debugName: "dateFilter" }] : /* istanbul ignore next */ []));
475
517
  /** Which calendar view opens first — `'day'`, `'month'`, or `'year'`. Defaults to `'day'`. */
476
- startView = input('day', ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
518
+ startView = input('day', /* @ts-ignore */
519
+ ...(ngDevMode ? [{ debugName: "startView" }] : /* istanbul ignore next */ []));
477
520
  /** Date to focus when the calendar opens with no selection. Falls back to today. */
478
- startAt = input(null, ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
521
+ startAt = input(null, /* @ts-ignore */
522
+ ...(ngDevMode ? [{ debugName: "startAt" }] : /* istanbul ignore next */ []));
479
523
  /** Display format passed to `DateAdapter.format()`. With the default adapter, accepts `{ dateTimeFormat: Intl.DateTimeFormatOptions }`. When `withTime` is true and this is left at the default, hour/minute (and optional seconds) are folded in automatically. */
480
- format = input(DEFAULT_DISPLAY_FORMAT, ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
524
+ format = input(DEFAULT_DISPLAY_FORMAT, /* @ts-ignore */
525
+ ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
481
526
  /** Optional format hint passed to `DateAdapter.parse()`. Ignored by the native adapter. */
482
- parseFormat = input(undefined, ...(ngDevMode ? [{ debugName: "parseFormat" }] : /* istanbul ignore next */ []));
527
+ parseFormat = input(undefined, /* @ts-ignore */
528
+ ...(ngDevMode ? [{ debugName: "parseFormat" }] : /* istanbul ignore next */ []));
483
529
  /** Placeholder text shown in the input when no value is entered. */
484
- placeholder = input(undefined, ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
530
+ placeholder = input(undefined, /* @ts-ignore */
531
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
485
532
  /** When true, the input is disabled, the trigger cannot open the calendar, and `aria-disabled="true"` is set. Defaults to `false`. */
486
533
  disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
487
534
  transform: booleanAttribute });
@@ -492,50 +539,70 @@ class DatePickerComponent extends FormFieldControl {
492
539
  readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly',
493
540
  transform: booleanAttribute });
494
541
  /** Trigger padding, font size, and calendar cell density. Defaults to `'md'`. */
495
- size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
542
+ size = input('md', /* @ts-ignore */
543
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
496
544
  /** Semantic color for focused border, calendar selection ring, and today marker. Defaults to `'primary'`. */
497
- color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
545
+ color = input('primary', /* @ts-ignore */
546
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
498
547
  /** Visual style of the trigger. `'default'` draws its own border; `'naked'` strips chrome. Auto-resolves to `'naked'` when inside `<tw-form-field>`. */
499
- variant = input(undefined, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
548
+ variant = input(undefined, /* @ts-ignore */
549
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
500
550
  /** Whether to show a clear-button affordance inside the trigger when a value is set. Defaults to `true`. */
501
551
  // TRUE-default: a picker without a clear affordance forces consumers to wire one — most form pickers expect the inline clear, matching `<tw-input>`'s clear button.
502
- showClear = input(true, ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
552
+ showClear = input(true, /* @ts-ignore */
553
+ ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
503
554
  /** When true, renders a `Today / Clear / Cancel / Apply` action bar at the bottom of the overlay. Defaults to `false`. */
504
- showActions = input(false, ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
555
+ showActions = input(false, /* @ts-ignore */
556
+ ...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
505
557
  /** Label for the `Today` action in the overlay's action bar. */
506
- todayLabel = input('Today', ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
558
+ todayLabel = input('Today', /* @ts-ignore */
559
+ ...(ngDevMode ? [{ debugName: "todayLabel" }] : /* istanbul ignore next */ []));
507
560
  /** Label for the `Clear` action in the overlay's action bar. */
508
- clearLabel = input('Clear', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
561
+ clearLabel = input('Clear', /* @ts-ignore */
562
+ ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
509
563
  /** Label for the `Cancel` action in the overlay's action bar. */
510
- cancelLabel = input('Cancel', ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
564
+ cancelLabel = input('Cancel', /* @ts-ignore */
565
+ ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
511
566
  /** Label for the `Apply` action in the overlay's action bar. */
512
- applyLabel = input('Apply', ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
567
+ applyLabel = input('Apply', /* @ts-ignore */
568
+ ...(ngDevMode ? [{ debugName: "applyLabel" }] : /* istanbul ignore next */ []));
513
569
  /** When true, focusing the text input opens the overlay. Defaults to `false`. */
514
- openOnFocus = input(false, ...(ngDevMode ? [{ debugName: "openOnFocus" }] : /* istanbul ignore next */ []));
570
+ openOnFocus = input(false, /* @ts-ignore */
571
+ ...(ngDevMode ? [{ debugName: "openOnFocus" }] : /* istanbul ignore next */ []));
515
572
  /** Extra class(es) applied to the overlay panel element. */
516
- panelClass = input('', ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
573
+ panelClass = input('', /* @ts-ignore */
574
+ ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
517
575
  /** CDK scroll strategy for the overlay. Defaults to `'reposition'`. */
518
- scrollStrategy = input('reposition', ...(ngDevMode ? [{ debugName: "scrollStrategy" }] : /* istanbul ignore next */ []));
576
+ scrollStrategy = input('reposition', /* @ts-ignore */
577
+ ...(ngDevMode ? [{ debugName: "scrollStrategy" }] : /* istanbul ignore next */ []));
519
578
  /** Pixel distance between trigger and overlay. Defaults to `4`. */
520
- offset = input(4, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
579
+ offset = input(4, /* @ts-ignore */
580
+ ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
521
581
  /** Accessible name for the calendar trigger button. Defaults to `'Open calendar'`. */
522
- triggerAriaLabel = input('Open calendar', ...(ngDevMode ? [{ debugName: "triggerAriaLabel" }] : /* istanbul ignore next */ []));
582
+ triggerAriaLabel = input('Open calendar', /* @ts-ignore */
583
+ ...(ngDevMode ? [{ debugName: "triggerAriaLabel" }] : /* istanbul ignore next */ []));
523
584
  /**
524
585
  * Bundles the time-of-day configuration. Passing a non-null object turns on the embedded `<tw-time-picker>` and forwards each field.
525
586
  *
526
587
  * Defaults to `null` (no time field). Pass an empty object `{}` to enable the time picker with all defaults.
527
588
  */
528
- timeConfig = input(null, ...(ngDevMode ? [{ debugName: "timeConfig" }] : /* istanbul ignore next */ []));
589
+ timeConfig = input(null, /* @ts-ignore */
590
+ ...(ngDevMode ? [{ debugName: "timeConfig" }] : /* istanbul ignore next */ []));
529
591
  /** Per-instance locale override. Forwarded to the embedded calendar and the underlying `DateAdapter` for parse/format. Falls back to Angular `LOCALE_ID` when `null`. */
530
- locale = input(null, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
592
+ locale = input(null, /* @ts-ignore */
593
+ ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
531
594
  /** Function producing per-cell CSS classes, forwarded to the embedded calendar. Useful for visually marking special dates (holidays, billing cycles). */
532
- dateClass = input(null, ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
595
+ dateClass = input(null, /* @ts-ignore */
596
+ ...(ngDevMode ? [{ debugName: "dateClass" }] : /* istanbul ignore next */ []));
533
597
  /** Optional cell-content template, forwarded to the embedded calendar. Use to customize cell visuals beyond `dateClass`. */
534
- cellTemplate = input(null, ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
598
+ cellTemplate = input(null, /* @ts-ignore */
599
+ ...(ngDevMode ? [{ debugName: "cellTemplate" }] : /* istanbul ignore next */ []));
535
600
  /** Optional quick-select presets rendered as a vertical list before the calendar. Each preset provides a `label` and a `date` factory. An empty array hides the preset panel. */
536
- presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
601
+ presets = input([], /* @ts-ignore */
602
+ ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
537
603
  /** Per-instance override of the `ErrorStateMatcher`. */
538
- errorStateMatcher = input(undefined, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
604
+ errorStateMatcher = input(undefined, /* @ts-ignore */
605
+ ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
539
606
  /** Accessible name for the input. Required when no visible label is supplied. Alias: `aria-label`. */
540
607
  ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
541
608
  /** ID of an external element that labels the input. Alias: `aria-labelledby`. */
@@ -544,9 +611,11 @@ class DatePickerComponent extends FormFieldControl {
544
611
  userAriaDescribedByInput = input(undefined, { ...(ngDevMode ? { debugName: "userAriaDescribedByInput" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
545
612
  // ── Models (two-way) ──
546
613
  /** Two-way bound selected date. `null` when no selection. Setting programmatically updates the display; does NOT trigger `onChange`. */
547
- value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
614
+ value = model(null, /* @ts-ignore */
615
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
548
616
  /** Two-way bound open state of the calendar overlay. */
549
- open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
617
+ open = model(false, /* @ts-ignore */
618
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
550
619
  // ── Outputs ──
551
620
  /** Fires after the overlay's enter animation completes. Payload is the trigger element. */
552
621
  opened = output();
@@ -568,16 +637,24 @@ class DatePickerComponent extends FormFieldControl {
568
637
  viewContainerRef = inject(ViewContainerRef);
569
638
  injector = inject(Injector);
570
639
  destroyRef = inject(DestroyRef);
571
- formField = inject(FormFieldComponent, { optional: true });
572
- ngControl = inject(NgControl, { optional: true, self: true });
640
+ formField = inject(TW_FORM_FIELD, { optional: true });
641
+ // Resolved lazily in `ngOnInit`, not via eager `inject(NgControl, { self })`:
642
+ // this component provides NG_VALIDATORS with `useExisting`, so NgModel /
643
+ // FormControlName pulling the validator set during construction would resolve
644
+ // back into this instance while it is still being created. Mirrors
645
+ // `date-range-picker.ts` and `calendar.ts`.
646
+ ngControl = null;
573
647
  parentForm = inject(NgForm, { optional: true });
574
648
  parentFormGroup = inject(FormGroupDirective, { optional: true });
575
649
  defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
576
650
  // ── View refs ──
577
- inputRef = viewChild.required('dateInput');
578
- triggerRef = viewChild('triggerBtn', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
651
+ inputRef = viewChild.required('dateInput', /* @ts-ignore */
652
+ ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
653
+ triggerRef = viewChild('triggerBtn', /* @ts-ignore */
654
+ ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
579
655
  /** @internal Whether a `[slot=trigger]` is projected. Detected post-mount via DOM query so we can drop the default chrome. */
580
- hasCustomTrigger = signal(false, ...(ngDevMode ? [{ debugName: "hasCustomTrigger" }] : /* istanbul ignore next */ []));
656
+ hasCustomTrigger = signal(false, /* @ts-ignore */
657
+ ...(ngDevMode ? [{ debugName: "hasCustomTrigger" }] : /* istanbul ignore next */ []));
581
658
  // ── Identity ──
582
659
  uid = nextDatePickerId++;
583
660
  /** @internal */
@@ -586,26 +663,65 @@ class DatePickerComponent extends FormFieldControl {
586
663
  dialogId = `${this.hostId}-dialog`;
587
664
  // ── Internal state ──
588
665
  /** @internal Decouples programmatic writes from user commits so `writeValue` doesn't re-trigger parent effects. */
589
- internalValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
666
+ internalValue = linkedSignal(() => this.value(), /* @ts-ignore */
667
+ ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
590
668
  /** @internal */
591
- rawInputText = signal('', ...(ngDevMode ? [{ debugName: "rawInputText" }] : /* istanbul ignore next */ []));
592
- /** @internal */
593
- parseError = signal(false, ...(ngDevMode ? [{ debugName: "parseError" }] : /* istanbul ignore next */ []));
594
- /** @internal */
595
- rangeError = signal(false, ...(ngDevMode ? [{ debugName: "rangeError" }] : /* istanbul ignore next */ []));
596
- /** @internal */
597
- focusedSignal = signal(false, ...(ngDevMode ? [{ debugName: "focusedSignal" }] : /* istanbul ignore next */ []));
598
- cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
599
- describedByIdsSignal = signal([], ...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
600
- closingSignal = signal(false, ...(ngDevMode ? [{ debugName: "closingSignal" }] : /* istanbul ignore next */ []));
601
- lastValueBeforeOpen = signal(null, ...(ngDevMode ? [{ debugName: "lastValueBeforeOpen" }] : /* istanbul ignore next */ []));
602
- pendingCalendarValue = signal(null, ...(ngDevMode ? [{ debugName: "pendingCalendarValue" }] : /* istanbul ignore next */ []));
603
- activePresetIdSignal = signal(undefined, ...(ngDevMode ? [{ debugName: "activePresetIdSignal" }] : /* istanbul ignore next */ []));
604
- _ngControlRev = signal(0, ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
605
- _formSubmitRev = signal(0, ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
669
+ rawInputText = signal('', /* @ts-ignore */
670
+ ...(ngDevMode ? [{ debugName: "rawInputText" }] : /* istanbul ignore next */ []));
671
+ /**
672
+ * @internal The text the user typed that could not be parsed into a date,
673
+ * or `null` when the current entry parsed cleanly.
674
+ *
675
+ * This is the single source of truth for "the typed text is bad". It drives
676
+ * both the visual error state (via `parseError` below) and the form error
677
+ * (via `validate()`, which feeds it to `calendarValidator` as
678
+ * `lastInvalidFormValue`). Keeping one field instead of a boolean flag plus a
679
+ * separate string is what stops the two from disagreeing.
680
+ */
681
+ unparseableText = signal(null, /* @ts-ignore */
682
+ ...(ngDevMode ? [{ debugName: "unparseableText" }] : /* istanbul ignore next */ []));
683
+ /** @internal Whether the current input text failed to parse. */
684
+ parseError = computed(() => this.unparseableText() !== null, /* @ts-ignore */
685
+ ...(ngDevMode ? [{ debugName: "parseError" }] : /* istanbul ignore next */ []));
686
+ /**
687
+ * @internal Whether the committed value violates `minDate` / `maxDate` /
688
+ * `dateFilter`.
689
+ *
690
+ * Derived, not a flag. An out-of-range entry is committed to the form (so the
691
+ * control holds what the user typed and is *invalid*, rather than silently
692
+ * keeping an older value), which means the value-sync effect below runs and
693
+ * would immediately clear any manually-set flag. Deriving it also keeps the
694
+ * error correct when the consumer moves a constraint after the fact, which a
695
+ * flag set once at commit time cannot do.
696
+ */
697
+ rangeError = computed(() => {
698
+ const v = this.internalValue();
699
+ return v !== null && !this.isInRange(v);
700
+ }, /* @ts-ignore */
701
+ ...(ngDevMode ? [{ debugName: "rangeError" }] : /* istanbul ignore next */ []));
702
+ /** @internal */
703
+ focusedSignal = signal(false, /* @ts-ignore */
704
+ ...(ngDevMode ? [{ debugName: "focusedSignal" }] : /* istanbul ignore next */ []));
705
+ cvaDisabled = signal(false, /* @ts-ignore */
706
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
707
+ describedByIdsSignal = signal([], /* @ts-ignore */
708
+ ...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
709
+ closingSignal = signal(false, /* @ts-ignore */
710
+ ...(ngDevMode ? [{ debugName: "closingSignal" }] : /* istanbul ignore next */ []));
711
+ lastValueBeforeOpen = signal(null, /* @ts-ignore */
712
+ ...(ngDevMode ? [{ debugName: "lastValueBeforeOpen" }] : /* istanbul ignore next */ []));
713
+ pendingCalendarValue = signal(null, /* @ts-ignore */
714
+ ...(ngDevMode ? [{ debugName: "pendingCalendarValue" }] : /* istanbul ignore next */ []));
715
+ activePresetIdSignal = signal(undefined, /* @ts-ignore */
716
+ ...(ngDevMode ? [{ debugName: "activePresetIdSignal" }] : /* istanbul ignore next */ []));
717
+ _ngControlRev = signal(0, /* @ts-ignore */
718
+ ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
719
+ _formSubmitRev = signal(0, /* @ts-ignore */
720
+ ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
606
721
  onChange = () => { };
607
722
  onTouched = () => { };
608
- overlayInstanceSignal = signal(null, ...(ngDevMode ? [{ debugName: "overlayInstanceSignal" }] : /* istanbul ignore next */ []));
723
+ overlayInstanceSignal = signal(null, /* @ts-ignore */
724
+ ...(ngDevMode ? [{ debugName: "overlayInstanceSignal" }] : /* istanbul ignore next */ []));
609
725
  returnFocusTo = null;
610
726
  get overlayInstance() {
611
727
  return this.overlayInstanceSignal();
@@ -615,13 +731,17 @@ class DatePickerComponent extends FormFieldControl {
615
731
  isDisabled = computed(() => {
616
732
  this._ngControlRev();
617
733
  return this.disabledInput() || this.cvaDisabled() || !!this.ngControl?.disabled;
618
- }, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
734
+ }, /* @ts-ignore */
735
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
619
736
  /** @internal Effective variant — auto-naked when wrapped in tw-form-field. */
620
- resolvedVariant = computed(() => this.variant() ?? (this.formField ? 'naked' : 'default'), ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
737
+ resolvedVariant = computed(() => this.variant() ?? (this.formField ? 'naked' : 'default'), /* @ts-ignore */
738
+ ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
621
739
  /** @internal */
622
- isEmpty = computed(() => this.internalValue() === null && !this.rawInputText(), ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
740
+ isEmpty = computed(() => this.internalValue() === null && !this.rawInputText(), /* @ts-ignore */
741
+ ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
623
742
  /** Whether the time picker is rendered inside the overlay. True iff `timeConfig` is non-null. */
624
- withTime = computed(() => this.timeConfig() !== null, ...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
743
+ withTime = computed(() => this.timeConfig() !== null, /* @ts-ignore */
744
+ ...(ngDevMode ? [{ debugName: "withTime" }] : /* istanbul ignore next */ []));
625
745
  /** @internal Effective time-of-day configuration — merges `timeConfig` over the documented per-field defaults. */
626
746
  effectiveTimeConfig = computed(() => {
627
747
  const cfg = this.timeConfig();
@@ -634,7 +754,8 @@ class DatePickerComponent extends FormFieldControl {
634
754
  minTime: cfg?.minTime ?? null,
635
755
  maxTime: cfg?.maxTime ?? null,
636
756
  };
637
- }, ...(ngDevMode ? [{ debugName: "effectiveTimeConfig" }] : /* istanbul ignore next */ []));
757
+ }, /* @ts-ignore */
758
+ ...(ngDevMode ? [{ debugName: "effectiveTimeConfig" }] : /* istanbul ignore next */ []));
638
759
  /** @internal Effective display format — folds hour/minute (and optional seconds) into the default when `withTime` is on and the user hasn't overridden `format`. */
639
760
  effectiveFormat = computed(() => {
640
761
  const fmt = this.format();
@@ -653,7 +774,8 @@ class DatePickerComponent extends FormFieldControl {
653
774
  if (time.showSeconds)
654
775
  base.second = '2-digit';
655
776
  return { dateTimeFormat: base };
656
- }, ...(ngDevMode ? [{ debugName: "effectiveFormat" }] : /* istanbul ignore next */ []));
777
+ }, /* @ts-ignore */
778
+ ...(ngDevMode ? [{ debugName: "effectiveFormat" }] : /* istanbul ignore next */ []));
657
779
  /** @internal */
658
780
  describedBy = computed(() => {
659
781
  const extra = this.describedByIdsSignal();
@@ -666,7 +788,8 @@ class DatePickerComponent extends FormFieldControl {
666
788
  }
667
789
  }
668
790
  return merged.length ? merged.join(' ') : '';
669
- }, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
791
+ }, /* @ts-ignore */
792
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
670
793
  // ── tv() output ──
671
794
  variantResult = computed(() => datePickerVariants({
672
795
  size: this.size(),
@@ -676,20 +799,27 @@ class DatePickerComponent extends FormFieldControl {
676
799
  errorState: this.errorState(),
677
800
  focused: this.focusedSignal(),
678
801
  color: this.color(),
679
- }), ...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
802
+ }), /* @ts-ignore */
803
+ ...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
680
804
  /** @internal */
681
- rootClasses = computed(() => this.variantResult().root(), ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
805
+ rootClasses = computed(() => this.variantResult().root(), /* @ts-ignore */
806
+ ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
682
807
  /** @internal */
683
- inputClasses = computed(() => this.variantResult().input(), ...(ngDevMode ? [{ debugName: "inputClasses" }] : /* istanbul ignore next */ []));
808
+ inputClasses = computed(() => this.variantResult().input(), /* @ts-ignore */
809
+ ...(ngDevMode ? [{ debugName: "inputClasses" }] : /* istanbul ignore next */ []));
684
810
  /** @internal */
685
- triggerButtonClasses = computed(() => this.variantResult().triggerButton(), ...(ngDevMode ? [{ debugName: "triggerButtonClasses" }] : /* istanbul ignore next */ []));
811
+ triggerButtonClasses = computed(() => this.variantResult().triggerButton(), /* @ts-ignore */
812
+ ...(ngDevMode ? [{ debugName: "triggerButtonClasses" }] : /* istanbul ignore next */ []));
686
813
  /** @internal */
687
- triggerIconClasses = computed(() => this.variantResult().triggerIcon(), ...(ngDevMode ? [{ debugName: "triggerIconClasses" }] : /* istanbul ignore next */ []));
814
+ triggerIconClasses = computed(() => this.variantResult().triggerIcon(), /* @ts-ignore */
815
+ ...(ngDevMode ? [{ debugName: "triggerIconClasses" }] : /* istanbul ignore next */ []));
688
816
  /** @internal */
689
- clearButtonClasses = computed(() => this.variantResult().clearButton(), ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : /* istanbul ignore next */ []));
817
+ clearButtonClasses = computed(() => this.variantResult().clearButton(), /* @ts-ignore */
818
+ ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : /* istanbul ignore next */ []));
690
819
  // ── FormFieldControl impl ──
691
820
  /** @internal */
692
- id = computed(() => this.idInput() ?? this.hostId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
821
+ id = computed(() => this.idInput() ?? this.hostId, /* @ts-ignore */
822
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
693
823
  /** @internal */
694
824
  focused = this.focusedSignal.asReadonly();
695
825
  /** @internal */
@@ -702,7 +832,8 @@ class DatePickerComponent extends FormFieldControl {
702
832
  if (this.requiredInput())
703
833
  return true;
704
834
  return !!this.ngControl?.control?.hasValidator(Validators.required);
705
- }, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
835
+ }, /* @ts-ignore */
836
+ ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
706
837
  /** @internal */
707
838
  errorState = computed(() => {
708
839
  this._ngControlRev();
@@ -714,29 +845,45 @@ class DatePickerComponent extends FormFieldControl {
714
845
  const form = this.parentFormGroup ??
715
846
  this.parentForm;
716
847
  return matcher.isErrorState(this.ngControl?.control ?? null, form);
717
- }, ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
848
+ }, /* @ts-ignore */
849
+ ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
718
850
  /** @internal */
719
851
  controlType = 'date-picker';
720
852
  /** @internal */
721
- userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), ...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
853
+ userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), /* @ts-ignore */
854
+ ...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
722
855
  // ── Constructor ──
723
856
  constructor() {
724
857
  super();
725
- // Wire this component as the NgControl's value accessor without registering
726
- // NG_VALUE_ACCESSOR in providers, which would create a circular DI with the
727
- // `inject(NgControl, { self: true })` above.
728
- if (this.ngControl) {
729
- this.ngControl.valueAccessor = this;
730
- }
858
+ // Re-run validation whenever a constraint input changes, so a consumer
859
+ // moving `minDate` after a value was committed updates `control.errors`
860
+ // instead of leaving a stale verdict. `validatorOnChange` is a plain
861
+ // callback into Angular's validator plumbing, not a signal write, so the
862
+ // effect cannot cycle; `untracked` keeps it that way regardless.
863
+ // Mirrors `date-range-picker.ts` and `calendar.ts`.
864
+ effect(() => {
865
+ this.minDate();
866
+ this.maxDate();
867
+ this.dateFilter();
868
+ untracked(() => this.validatorOnChange());
869
+ });
731
870
  // Sync rawInputText whenever the underlying value changes (programmatic or user).
732
871
  effect(() => {
733
872
  const v = this.internalValue();
734
873
  const fmt = this.effectiveFormat();
735
874
  untracked(() => {
875
+ // An unparseable entry commits `null` precisely *because* it failed to
876
+ // parse, and this effect runs asynchronously afterwards. Syncing here
877
+ // would undo both halves of that: it would clear the error state a
878
+ // moment after it was set, and — because the input is bound to
879
+ // `rawInputText` — blank the very text the user needs to correct,
880
+ // leaving an error border over an empty box. Bail out and let the
881
+ // parse-failure path own the display.
882
+ if (v === null && this.unparseableText() !== null)
883
+ return;
736
884
  const display = v === null ? '' : this.adapter.format(v, fmt);
737
885
  this.rawInputText.set(display);
738
- this.parseError.set(false);
739
- this.rangeError.set(false);
886
+ this.unparseableText.set(null);
740
887
  });
741
888
  });
742
889
  // Mirror the `open` model into the overlay lifecycle.
@@ -836,7 +983,7 @@ class DatePickerComponent extends FormFieldControl {
836
983
  return;
837
984
  const hasLabel = !!this.ariaLabel() ||
838
985
  !!this.ariaLabelledby() ||
839
- !!this.formField?.labelChild();
986
+ !!this.formField?.hasLabel();
840
987
  if (!hasLabel) {
841
988
  console.warn('[tw-date-picker] The date-picker has no accessible name. Set aria-label, aria-labelledby, or wrap the component in a <tw-form-field> with a <label twLabel>.');
842
989
  }
@@ -861,6 +1008,14 @@ class DatePickerComponent extends FormFieldControl {
861
1008
  });
862
1009
  }
863
1010
  ngOnInit() {
1011
+ // Resolve the bound NgControl now that the host's FormControlName / NgModel
1012
+ // is fully constructed (see the field declaration for why this is deferred).
1013
+ // The value accessor itself is registered statically via NG_VALUE_ACCESSOR,
1014
+ // which is what keeps `validate()` on the classic CVA path under v22.
1015
+ this.ngControl = this.injector.get(NgControl, null, { self: true, optional: true });
1016
+ // Anything read off `ngControl` before this point saw `null`; bump the
1017
+ // revision so `isDisabled`, `required` and `errorState` recompute.
1018
+ this._ngControlRev.update((n) => n + 1);
864
1019
  const ctrl = this.ngControl?.control;
865
1020
  if (ctrl) {
866
1021
  const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
@@ -907,8 +1062,7 @@ class DatePickerComponent extends FormFieldControl {
907
1062
  const target = event.target;
908
1063
  const rawText = target.value;
909
1064
  this.rawInputText.set(rawText);
910
- this.parseError.set(false);
911
- this.rangeError.set(false);
1065
+ this.unparseableText.set(null);
912
1066
  // Non-committing parse so the payload's `parsed` field reflects success when achievable.
913
1067
  let parsed = null;
914
1068
  if (rawText.trim()) {
@@ -1036,34 +1190,63 @@ class DatePickerComponent extends FormFieldControl {
1036
1190
  commitFromInput() {
1037
1191
  const raw = this.rawInputText().trim();
1038
1192
  if (!raw) {
1193
+ this.unparseableText.set(null);
1039
1194
  if (this.internalValue() !== null || this.parseError() || this.rangeError()) {
1040
1195
  this.commit(null, 'input');
1041
1196
  }
1042
1197
  else {
1043
- this.parseError.set(false);
1044
- this.rangeError.set(false);
1198
+ this.unparseableText.set(null);
1045
1199
  }
1200
+ this.validatorOnChange();
1046
1201
  return;
1047
1202
  }
1048
1203
  const parsed = this.adapter.parse(raw, this.parseFormat());
1049
1204
  if (!parsed || !this.adapter.isValid(parsed)) {
1050
- this.parseError.set(true);
1051
- this.rangeError.set(false);
1205
+ // Unparseable. Both halves matter: the previously committed date must not
1206
+ // survive in the form behind an error border (that stale value would be
1207
+ // submitted), and the parse failure must be carried into `validate()`
1208
+ // because the `null` we commit is otherwise indistinguishable from an
1209
+ // empty optional field.
1210
+ // Deliberately not routed through `commit()`: that formats the committed
1211
+ // value into the input, which would erase the text the user just typed.
1212
+ // Clear the *form* value so a stale date cannot be submitted behind an
1213
+ // error border, while leaving the raw text on screen to be corrected.
1214
+ //
1215
+ // Keeping that text visible also depends on the bail-out in the
1216
+ // value-sync effect above — without it, this `internalValue.set(null)`
1217
+ // asynchronously blanks the input. Both halves are pinned by the
1218
+ // "keeps unparseable text visible" specs; changing one without the other
1219
+ // reintroduces an error border over an empty box.
1220
+ const previous = this.internalValue();
1221
+ this.internalValue.set(null);
1222
+ this.value.set(null);
1223
+ this.unparseableText.set(raw);
1224
+ this.onChange(null);
1225
+ this.onTouched();
1226
+ this.validatorOnChange();
1227
+ this.dateChange.emit({ value: null, previousValue: previous, source: 'input' });
1052
1228
  return;
1053
1229
  }
1054
1230
  if (!this.isInRange(parsed)) {
1055
- this.parseError.set(false);
1056
- this.rangeError.set(true);
1231
+ // Out of range still commits: the form should hold what the user actually
1232
+ // entered and be *invalid*, not silently keep an older value. The range
1233
+ // codes come from `validate()` reading `control.value`, so no extra state
1234
+ // needs carrying here.
1235
+ this.unparseableText.set(null);
1236
+ this.commit(parsed, 'input');
1237
+ this.unparseableText.set(null);
1238
+ this.validatorOnChange();
1057
1239
  return;
1058
1240
  }
1241
+ this.unparseableText.set(null);
1059
1242
  this.commit(parsed, 'input');
1243
+ this.validatorOnChange();
1060
1244
  }
1061
1245
  commit(next, source) {
1062
1246
  const previous = this.internalValue();
1063
1247
  this.internalValue.set(next);
1064
1248
  this.value.set(next);
1065
- this.parseError.set(false);
1066
- this.rangeError.set(false);
1249
+ this.unparseableText.set(null);
1067
1250
  const display = next === null ? '' : this.adapter.format(next, this.effectiveFormat());
1068
1251
  this.rawInputText.set(display);
1069
1252
  const el = this.inputRef().nativeElement;
@@ -1173,8 +1356,7 @@ class DatePickerComponent extends FormFieldControl {
1173
1356
  this.value.set(previous);
1174
1357
  const display = previous === null ? '' : this.adapter.format(previous, this.effectiveFormat());
1175
1358
  this.rawInputText.set(display);
1176
- this.parseError.set(false);
1177
- this.rangeError.set(false);
1359
+ this.unparseableText.set(null);
1178
1360
  }
1179
1361
  }
1180
1362
  // Fallback chain ensures focus always returns to a visible element even when
@@ -1282,8 +1464,7 @@ class DatePickerComponent extends FormFieldControl {
1282
1464
  if (value === null || value === undefined || value === '') {
1283
1465
  this.internalValue.set(null);
1284
1466
  this.value.set(null);
1285
- this.parseError.set(false);
1286
- this.rangeError.set(false);
1467
+ this.unparseableText.set(null);
1287
1468
  this.rawInputText.set('');
1288
1469
  this.dateChange.emit({
1289
1470
  value: null,
@@ -1297,20 +1478,18 @@ class DatePickerComponent extends FormFieldControl {
1297
1478
  this.internalValue.set(null);
1298
1479
  this.value.set(null);
1299
1480
  this.rawInputText.set('');
1300
- this.parseError.set(false);
1301
- this.rangeError.set(false);
1481
+ this.unparseableText.set(null);
1302
1482
  return;
1303
1483
  }
1304
1484
  if (!this.adapter.isValid(coerced)) {
1305
- this.parseError.set(true);
1485
+ this.unparseableText.set(String(value));
1306
1486
  this.rawInputText.set(String(value));
1307
1487
  return;
1308
1488
  }
1309
1489
  const previous = this.internalValue();
1310
1490
  this.internalValue.set(coerced);
1311
1491
  this.value.set(coerced);
1312
- this.parseError.set(false);
1313
- this.rangeError.set(false);
1492
+ this.unparseableText.set(null);
1314
1493
  this.rawInputText.set(this.adapter.format(coerced, this.effectiveFormat()));
1315
1494
  this.dateChange.emit({ value: coerced, previousValue: previous, source: 'programmatic' });
1316
1495
  }
@@ -1323,6 +1502,33 @@ class DatePickerComponent extends FormFieldControl {
1323
1502
  setDisabledState(isDisabled) {
1324
1503
  this.cvaDisabled.set(isDisabled);
1325
1504
  }
1505
+ // ── Validator ──
1506
+ validatorOnChange = () => { };
1507
+ /** @internal */
1508
+ validate(control) {
1509
+ // Delegate to the shared calendar validator so a `tw-date-picker` reports
1510
+ // the same CalendarErrorCode set as `tw-calendar` and `tw-date-range-picker`
1511
+ // for the same constraint violation.
1512
+ const validator = calendarValidator({
1513
+ mode: 'single',
1514
+ // Carried explicitly rather than inferred from `control.value`: an
1515
+ // unparseable entry commits `null`, and `null` is perfectly valid for a
1516
+ // non-required control, so reading the value alone would report the field
1517
+ // valid while the UI shows an error — the exact bug this fixes.
1518
+ lastInvalidFormValue: this.unparseableText(),
1519
+ constraints: {
1520
+ minDate: this.minDate(),
1521
+ maxDate: this.maxDate(),
1522
+ dateFilter: this.dateFilter(),
1523
+ },
1524
+ adapter: this.adapter,
1525
+ });
1526
+ return validator(control);
1527
+ }
1528
+ /** @internal */
1529
+ registerOnValidatorChange(fn) {
1530
+ this.validatorOnChange = fn;
1531
+ }
1326
1532
  // ── FormFieldControl methods ──
1327
1533
  /** @internal */
1328
1534
  setDescribedByIds(ids) {
@@ -1342,12 +1548,31 @@ class DatePickerComponent extends FormFieldControl {
1342
1548
  inputEl.focus();
1343
1549
  }
1344
1550
  }
1345
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1346
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: DatePickerComponent, isStandalone: true, selector: "tw-date-picker", inputs: { idInput: { classPropertyName: "idInput", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startView: { classPropertyName: "startView", publicName: "startView", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, parseFormat: { classPropertyName: "parseFormat", publicName: "parseFormat", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, requiredInput: { classPropertyName: "requiredInput", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: true, isRequired: false, transformFunction: null }, todayLabel: { classPropertyName: "todayLabel", publicName: "todayLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, applyLabel: { classPropertyName: "applyLabel", publicName: "applyLabel", isSignal: true, isRequired: false, transformFunction: null }, openOnFocus: { classPropertyName: "openOnFocus", publicName: "openOnFocus", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, scrollStrategy: { classPropertyName: "scrollStrategy", publicName: "scrollStrategy", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, triggerAriaLabel: { classPropertyName: "triggerAriaLabel", publicName: "triggerAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, timeConfig: { classPropertyName: "timeConfig", publicName: "timeConfig", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateClass: { classPropertyName: "dateClass", publicName: "dateClass", isSignal: true, isRequired: false, transformFunction: null }, cellTemplate: { classPropertyName: "cellTemplate", publicName: "cellTemplate", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, userAriaDescribedByInput: { classPropertyName: "userAriaDescribedByInput", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", opened: "opened", closed: "closed", dateInput: "dateInput", dateChange: "dateChange", presetSelected: "presetSelected" }, host: { properties: { "class": "rootClasses()" } }, providers: [
1551
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: DatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1552
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: DatePickerComponent, isStandalone: true, selector: "tw-date-picker", inputs: { idInput: { classPropertyName: "idInput", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, dateFilter: { classPropertyName: "dateFilter", publicName: "dateFilter", isSignal: true, isRequired: false, transformFunction: null }, startView: { classPropertyName: "startView", publicName: "startView", isSignal: true, isRequired: false, transformFunction: null }, startAt: { classPropertyName: "startAt", publicName: "startAt", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, parseFormat: { classPropertyName: "parseFormat", publicName: "parseFormat", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, requiredInput: { classPropertyName: "requiredInput", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: true, isRequired: false, transformFunction: null }, todayLabel: { classPropertyName: "todayLabel", publicName: "todayLabel", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, applyLabel: { classPropertyName: "applyLabel", publicName: "applyLabel", isSignal: true, isRequired: false, transformFunction: null }, openOnFocus: { classPropertyName: "openOnFocus", publicName: "openOnFocus", isSignal: true, isRequired: false, transformFunction: null }, panelClass: { classPropertyName: "panelClass", publicName: "panelClass", isSignal: true, isRequired: false, transformFunction: null }, scrollStrategy: { classPropertyName: "scrollStrategy", publicName: "scrollStrategy", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, triggerAriaLabel: { classPropertyName: "triggerAriaLabel", publicName: "triggerAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, timeConfig: { classPropertyName: "timeConfig", publicName: "timeConfig", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateClass: { classPropertyName: "dateClass", publicName: "dateClass", isSignal: true, isRequired: false, transformFunction: null }, cellTemplate: { classPropertyName: "cellTemplate", publicName: "cellTemplate", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, userAriaDescribedByInput: { classPropertyName: "userAriaDescribedByInput", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", opened: "opened", closed: "closed", dateInput: "dateInput", dateChange: "dateChange", presetSelected: "presetSelected" }, host: { properties: { "class": "rootClasses()" } }, providers: [
1347
1553
  {
1348
1554
  provide: TW_FORM_FIELD_CONTROL,
1349
1555
  useExisting: forwardRef(() => DatePickerComponent),
1350
1556
  },
1557
+ {
1558
+ provide: NG_VALIDATORS,
1559
+ useExisting: forwardRef(() => DatePickerComponent),
1560
+ multi: true,
1561
+ },
1562
+ // Angular v22 compiles any component exposing a `value` model() as a
1563
+ // signal-forms custom control, and `FormControlDirective` takes the classic
1564
+ // CVA path — the one that runs `setUpValidators` and composes the
1565
+ // NG_VALIDATORS above onto the control — only if a value accessor is
1566
+ // already visible at directive-creation time. Assigning
1567
+ // `ngControl.valueAccessor` later is too late, so without this static
1568
+ // provider `validate()` is never called and every error code below
1569
+ // disappears silently. See the CVA section of CLAUDE.md; `calendar.ts` and
1570
+ // `date-range-picker.ts` carry the same pair for the same reason.
1571
+ {
1572
+ provide: NG_VALUE_ACCESSOR,
1573
+ useExisting: forwardRef(() => DatePickerComponent),
1574
+ multi: true,
1575
+ },
1351
1576
  PickerOverlayCoordinator,
1352
1577
  ], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["dateInput"], descendants: true, isSignal: true }, { propertyName: "triggerRef", first: true, predicate: ["triggerBtn"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
1353
1578
  <!-- Optional rich-label trigger. When projected (a child carries [slot=trigger]), the default input/clear/trigger chrome is hidden. Clicks anywhere in the projected content open the overlay. -->
@@ -1442,7 +1667,7 @@ class DatePickerComponent extends FormFieldControl {
1442
1667
  }
1443
1668
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1444
1669
  }
1445
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
1670
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: DatePickerComponent, decorators: [{
1446
1671
  type: Component,
1447
1672
  args: [{
1448
1673
  selector: 'tw-date-picker',
@@ -1452,6 +1677,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
1452
1677
  provide: TW_FORM_FIELD_CONTROL,
1453
1678
  useExisting: forwardRef(() => DatePickerComponent),
1454
1679
  },
1680
+ {
1681
+ provide: NG_VALIDATORS,
1682
+ useExisting: forwardRef(() => DatePickerComponent),
1683
+ multi: true,
1684
+ },
1685
+ // Angular v22 compiles any component exposing a `value` model() as a
1686
+ // signal-forms custom control, and `FormControlDirective` takes the classic
1687
+ // CVA path — the one that runs `setUpValidators` and composes the
1688
+ // NG_VALIDATORS above onto the control — only if a value accessor is
1689
+ // already visible at directive-creation time. Assigning
1690
+ // `ngControl.valueAccessor` later is too late, so without this static
1691
+ // provider `validate()` is never called and every error code below
1692
+ // disappears silently. See the CVA section of CLAUDE.md; `calendar.ts` and
1693
+ // `date-range-picker.ts` carry the same pair for the same reason.
1694
+ {
1695
+ provide: NG_VALUE_ACCESSOR,
1696
+ useExisting: forwardRef(() => DatePickerComponent),
1697
+ multi: true,
1698
+ },
1455
1699
  PickerOverlayCoordinator,
1456
1700
  ],
1457
1701
  template: `