@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,12 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, input, booleanAttribute, model, output, inject, ElementRef, DestroyRef, viewChild, signal, linkedSignal, computed, effect, untracked, afterNextRender, isDevMode, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import { NgControl, NgForm, FormGroupDirective, Validators } from '@angular/forms';
2
+ import { Injectable, input, booleanAttribute, model, output, inject, ElementRef, DestroyRef, Injector, viewChild, signal, linkedSignal, computed, effect, untracked, afterNextRender, isDevMode, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgForm, FormGroupDirective, Validators, NgControl, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
4
4
  import { FocusMonitor, LiveAnnouncer } from '@angular/cdk/a11y';
5
5
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
6
  import { merge } from 'rxjs';
7
7
  import { tv } from 'tailwind-variants';
8
8
  import { TW_ERROR_STATE_MATCHER, fieldMin, fieldMax, parseField, padTwo, to12h, appendDigit, isTerminalDigit, stepWithWrap, from12h, timeOfDaySeconds, clamp } from '@cdevhub/ngx-tw/core';
9
- import { FormFieldControl, FormFieldComponent, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
9
+ import { FormFieldControl, TW_FORM_FIELD, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
10
10
  import { DATE_ADAPTER } from '@cdevhub/ngx-tw/calendar';
11
11
 
12
12
  /**
@@ -61,10 +61,10 @@ class TimePickerIntl {
61
61
  }
62
62
  /** Spoken text for an empty hour/minute/second field — used as `aria-valuetext` when the field is blank. */
63
63
  emptyValueText = 'Empty';
64
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TimePickerIntl, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
65
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TimePickerIntl });
64
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: TimePickerIntl, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
65
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: TimePickerIntl });
66
66
  }
67
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TimePickerIntl, decorators: [{
67
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: TimePickerIntl, decorators: [{
68
68
  type: Injectable
69
69
  }] });
70
70
  /**
@@ -96,8 +96,21 @@ const timePickerVariants = tv({
96
96
  fieldGroup: 'inline-flex items-center tabular-nums font-medium',
97
97
  field: 'bg-transparent text-center outline-none border-0 p-0 m-0 text-fg placeholder:text-fg-subtle rounded-md caret-transparent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
98
98
  separator: 'text-fg-subtle select-none px-0.5',
99
- stepperGroup: 'flex flex-col ml-0.5',
100
- stepper: 'inline-flex items-center justify-center text-fg-muted hover:text-fg hover:bg-surface-muted rounded-md transition-colors duration-normal motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500 disabled:opacity-40 disabled:pointer-events-none',
99
+ // Column stretches to the field height; the two buttons split it via
100
+ // `flex-1`. Codified departure from CLAUDE.md "Square interactive targets"
101
+ // (size-6/7/8): stacking two square buttons made the column 2× a button
102
+ // tall — far taller than the digit row — which bloated the field height.
103
+ // So the buttons carry a fixed WIDTH only (per-size below); the chevron
104
+ // glyph's min-content (size-3 = 12px) is the natural floor.
105
+ //
106
+ // WCAG 2.5.8 (target-size, 24px — which slider.ts / badge.ts codify):
107
+ // two stacked buttons cannot each be ≥24px tall inside a compact field
108
+ // without re-bloating it, so each spinner ends up < 24px tall. Accepted
109
+ // as the equivalent-control exception applies — the time is fully editable
110
+ // by typing into the spinbutton fields (and via ↑/↓), so the spinner is a
111
+ // pointer enhancement, never the only path.
112
+ stepperGroup: 'flex flex-col self-stretch ml-0.5',
113
+ stepper: 'flex-1 inline-flex items-center justify-center text-fg-muted hover:text-fg hover:bg-surface-muted rounded-md transition-colors duration-normal motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500 disabled:opacity-40 disabled:pointer-events-none',
101
114
  stepperIcon: '',
102
115
  meridiem: 'inline-flex items-center rounded-md border border-border overflow-hidden ml-2 shrink-0',
103
116
  meridiemButton: 'font-medium text-fg-muted hover:bg-surface-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500 transition-colors duration-normal motion-reduce:transition-none disabled:opacity-40 disabled:pointer-events-none',
@@ -111,7 +124,7 @@ const timePickerVariants = tv({
111
124
  // the standard width scale because the field is content-driven, not a layout primitive
112
125
  // — see CLAUDE.md spacing scale rationale.
113
126
  field: 'w-5 text-xs',
114
- stepper: 'size-6',
127
+ stepper: 'w-6',
115
128
  stepperIcon: 'size-3',
116
129
  meridiemButton: 'px-1.5 py-1 text-2xs',
117
130
  },
@@ -119,7 +132,7 @@ const timePickerVariants = tv({
119
132
  root: 'gap-1 text-sm',
120
133
  // sm-density numeric field — sized to fit a 2-digit value at text-sm; see CLAUDE.md spacing scale rationale.
121
134
  field: 'w-6 text-sm',
122
- stepper: 'size-7',
135
+ stepper: 'w-7',
123
136
  stepperIcon: 'size-3',
124
137
  meridiemButton: 'px-2 py-1 text-xs',
125
138
  },
@@ -127,7 +140,7 @@ const timePickerVariants = tv({
127
140
  root: 'gap-1.5 text-sm',
128
141
  // md-density numeric field — sized to fit a 2-digit value at text-sm; see CLAUDE.md spacing scale rationale.
129
142
  field: 'w-7 text-sm',
130
- stepper: 'size-7',
143
+ stepper: 'w-7',
131
144
  stepperIcon: 'size-3',
132
145
  meridiemButton: 'px-2 py-1 text-xs',
133
146
  },
@@ -135,7 +148,7 @@ const timePickerVariants = tv({
135
148
  root: 'gap-1.5 text-base',
136
149
  // lg-density numeric field — sized to fit a 2-digit value at text-base; see CLAUDE.md spacing scale rationale.
137
150
  field: 'w-8 text-base',
138
- stepper: 'size-8',
151
+ stepper: 'w-8',
139
152
  stepperIcon: 'size-4',
140
153
  meridiemButton: 'px-2.5 py-1.5 text-sm',
141
154
  },
@@ -143,7 +156,7 @@ const timePickerVariants = tv({
143
156
  root: 'gap-2 text-base',
144
157
  // xl-density numeric field — sized to fit a 2-digit value at text-base; see CLAUDE.md spacing scale rationale.
145
158
  field: 'w-9 text-base',
146
- stepper: 'size-8',
159
+ stepper: 'w-8',
147
160
  stepperIcon: 'size-4',
148
161
  meridiemButton: 'px-3 py-1.5 text-sm',
149
162
  },
@@ -207,16 +220,6 @@ const MERIDIEM_ACTIVE_COLOR = {
207
220
  error: 'bg-error-500 text-on-error hover:bg-error-600',
208
221
  };
209
222
  let nextTimePickerId = 0;
210
- /**
211
- * Segmented time editor. Renders `HH : MM` (with an optional `: SS` and/or
212
- * `AM/PM` toggle) and integrates with all three Angular form strategies via
213
- * `ControlValueAccessor`. Also participates in `<tw-form-field>` through
214
- * `FormFieldControl` — wrapping is optional.
215
- *
216
- * Value type is `D | null`. Every time operation flows through the injected
217
- * `DateAdapter<D>`, so the component works with the default native adapter
218
- * (`provideNativeDateAdapter()`) or any custom adapter a consumer ships.
219
- */
220
223
  class TimePickerComponent extends FormFieldControl {
221
224
  // ── Inputs ──
222
225
  /** Id on the time-picker's host element. Auto-generated when not provided. Used by the form-field's `<label for>` attribute. */
@@ -231,39 +234,55 @@ class TimePickerComponent extends FormFieldControl {
231
234
  readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly',
232
235
  transform: booleanAttribute });
233
236
  /** Controls field height, font size, and stepper density. Defaults to `'md'`. */
234
- size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
237
+ size = input('md', /* @ts-ignore */
238
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
235
239
  /** Semantic color used for the focused border and focus ring. Defaults to `'primary'`. */
236
- color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
240
+ color = input('primary', /* @ts-ignore */
241
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
237
242
  /** Visual style of the chrome. `'default'` draws its own border; `'naked'` strips chrome. Auto-resolves to `'naked'` inside `<tw-form-field>`. */
238
- variant = input(undefined, ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
243
+ variant = input(undefined, /* @ts-ignore */
244
+ ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
239
245
  /** `'12h'` renders 1–12 hours with an AM/PM toggle; `'24h'` renders 00–23. Defaults to `'24h'`. */
240
- format = input('24h', ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
246
+ format = input('24h', /* @ts-ignore */
247
+ ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
241
248
  /** When true, renders a seconds field after minutes. Defaults to `false`. */
242
- showSeconds = input(false, ...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
249
+ showSeconds = input(false, /* @ts-ignore */
250
+ ...(ngDevMode ? [{ debugName: "showSeconds" }] : /* istanbul ignore next */ []));
243
251
  /** Amount to add/subtract when stepping hours. Defaults to `1`. */
244
- hourStep = input(1, ...(ngDevMode ? [{ debugName: "hourStep" }] : /* istanbul ignore next */ []));
252
+ hourStep = input(1, /* @ts-ignore */
253
+ ...(ngDevMode ? [{ debugName: "hourStep" }] : /* istanbul ignore next */ []));
245
254
  /** Amount to add/subtract when stepping minutes. Defaults to `1`. */
246
- minuteStep = input(1, ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
255
+ minuteStep = input(1, /* @ts-ignore */
256
+ ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
247
257
  /** Amount to add/subtract when stepping seconds. Defaults to `1`. */
248
- secondStep = input(1, ...(ngDevMode ? [{ debugName: "secondStep" }] : /* istanbul ignore next */ []));
249
- /** Earliest accepted time-of-day. Values earlier than this set `errorState`. Defaults to `null`. Note: when `showSeconds` is `false`, the seconds component of any value still participates in the range comparison — supply `minTime` with zeroed seconds to match the 2-field display. */
250
- minTime = input(null, ...(ngDevMode ? [{ debugName: "minTime" }] : /* istanbul ignore next */ []));
251
- /** Latest accepted time-of-day. Values later than this set `errorState`. Defaults to `null`. Note: when `showSeconds` is `false`, the seconds component of any value still participates in the range comparison — supply `maxTime` with zeroed seconds to match the 2-field display. */
252
- maxTime = input(null, ...(ngDevMode ? [{ debugName: "maxTime" }] : /* istanbul ignore next */ []));
258
+ secondStep = input(1, /* @ts-ignore */
259
+ ...(ngDevMode ? [{ debugName: "secondStep" }] : /* istanbul ignore next */ []));
260
+ /** Earliest accepted time-of-day. Values earlier than this set `errorState` and mark the bound control invalid with `timePickerMin`. Defaults to `null`. Note: when `showSeconds` is `false`, the seconds component of any value still participates in the range comparison — supply `minTime` with zeroed seconds to match the 2-field display. */
261
+ minTime = input(null, /* @ts-ignore */
262
+ ...(ngDevMode ? [{ debugName: "minTime" }] : /* istanbul ignore next */ []));
263
+ /** Latest accepted time-of-day. Values later than this set `errorState` and mark the bound control invalid with `timePickerMax`. Defaults to `null`. Note: when `showSeconds` is `false`, the seconds component of any value still participates in the range comparison — supply `maxTime` with zeroed seconds to match the 2-field display. */
264
+ maxTime = input(null, /* @ts-ignore */
265
+ ...(ngDevMode ? [{ debugName: "maxTime" }] : /* istanbul ignore next */ []));
253
266
  /** Date portion used when the user types a time while `value` is `null`. Defaults to today. */
254
- referenceDate = input(null, ...(ngDevMode ? [{ debugName: "referenceDate" }] : /* istanbul ignore next */ []));
267
+ referenceDate = input(null, /* @ts-ignore */
268
+ ...(ngDevMode ? [{ debugName: "referenceDate" }] : /* istanbul ignore next */ []));
255
269
  /** Placeholder shown in each field when empty. Defaults to `'--'`. */
256
- placeholder = input(undefined, ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
270
+ placeholder = input(undefined, /* @ts-ignore */
271
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
257
272
  /** Whether to render the up/down stepper column. Defaults to `true`. */
258
273
  // TRUE-default: pointer users expect inline up/down chevrons next to a numeric editor — without them the picker reads as a static display, and consumers must rebuild stepping for every instance.
259
- showSteppers = input(true, ...(ngDevMode ? [{ debugName: "showSteppers" }] : /* istanbul ignore next */ []));
274
+ showSteppers = input(true, /* @ts-ignore */
275
+ ...(ngDevMode ? [{ debugName: "showSteppers" }] : /* istanbul ignore next */ []));
260
276
  /** Whether to render the clear affordance when a value is set. Defaults to `true`. */
261
277
  // TRUE-default: matches `<tw-date-picker>` and `<tw-input>` clear behaviour; without the inline clear, every form has to wire its own reset surface for an obvious affordance.
262
- showClear = input(true, ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
278
+ showClear = input(true, /* @ts-ignore */
279
+ ...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
263
280
  /** Accessible label for the clear button. Defaults to `'Clear time'`. */
264
- clearLabel = input('Clear time', ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
281
+ clearLabel = input('Clear time', /* @ts-ignore */
282
+ ...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
265
283
  /** Per-instance override of the `ErrorStateMatcher`. */
266
- errorStateMatcher = input(undefined, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
284
+ errorStateMatcher = input(undefined, /* @ts-ignore */
285
+ ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
267
286
  /** Accessible name for the fields group. Required when no visible label is supplied. Alias: `aria-label`. */
268
287
  ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
269
288
  /** ID of an external element that labels the group. Alias: `aria-labelledby`. */
@@ -272,7 +291,8 @@ class TimePickerComponent extends FormFieldControl {
272
291
  userAriaDescribedByInput = input(undefined, { ...(ngDevMode ? { debugName: "userAriaDescribedByInput" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
273
292
  // ── Models ──
274
293
  /** Two-way bound current time. `null` when empty. Setting programmatically updates the fields without firing `onChange`. */
275
- value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
294
+ value = model(null, /* @ts-ignore */
295
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
276
296
  // ── Outputs ──
277
297
  /** Fires on every keystroke, stepper press, or AM/PM toggle — before the new value is committed. */
278
298
  timeInput = output();
@@ -284,39 +304,58 @@ class TimePickerComponent extends FormFieldControl {
284
304
  liveAnnouncer = inject(LiveAnnouncer);
285
305
  elementRef = inject((ElementRef));
286
306
  destroyRef = inject(DestroyRef);
287
- formField = inject(FormFieldComponent, { optional: true });
288
- ngControl = inject(NgControl, { optional: true, self: true });
307
+ injector = inject(Injector);
308
+ formField = inject(TW_FORM_FIELD, { optional: true });
309
+ // Resolved lazily in `ngOnInit`: this component self-provides NG_VALIDATORS
310
+ // via `useExisting`, so an eager `inject(NgControl, { self })` would deadlock
311
+ // against its own validator during construction. Mirrors `date-picker.ts`,
312
+ // `date-range-picker.ts` and `calendar.ts`.
313
+ ngControl = null;
289
314
  parentForm = inject(NgForm, { optional: true });
290
315
  parentFormGroup = inject(FormGroupDirective, { optional: true });
291
316
  defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
292
317
  /** @internal — exposed for the template; consumers configure via `provideTimePickerIntl()`. */
293
318
  intl = inject(TimePickerIntl, { optional: true }) ?? new TimePickerIntl();
294
319
  // ── View refs ──
295
- hourInputRef = viewChild.required('hourInput');
296
- minuteInputRef = viewChild.required('minuteInput');
297
- secondInputRef = viewChild('secondInput', ...(ngDevMode ? [{ debugName: "secondInputRef" }] : /* istanbul ignore next */ []));
320
+ hourInputRef = viewChild.required('hourInput', /* @ts-ignore */
321
+ ...(ngDevMode ? [{ debugName: "hourInputRef" }] : /* istanbul ignore next */ []));
322
+ minuteInputRef = viewChild.required('minuteInput', /* @ts-ignore */
323
+ ...(ngDevMode ? [{ debugName: "minuteInputRef" }] : /* istanbul ignore next */ []));
324
+ secondInputRef = viewChild('secondInput', /* @ts-ignore */
325
+ ...(ngDevMode ? [{ debugName: "secondInputRef" }] : /* istanbul ignore next */ []));
298
326
  // ── Identity ──
299
327
  uid = nextTimePickerId++;
300
328
  /** @internal */
301
329
  hostId = `tw-time-picker-${this.uid}`;
302
330
  // ── Internal state ──
303
331
  /** @internal raw display text per field — always the numerals the user sees. */
304
- hourText = signal('', ...(ngDevMode ? [{ debugName: "hourText" }] : /* istanbul ignore next */ []));
332
+ hourText = signal('', /* @ts-ignore */
333
+ ...(ngDevMode ? [{ debugName: "hourText" }] : /* istanbul ignore next */ []));
305
334
  /** @internal */
306
- minuteText = signal('', ...(ngDevMode ? [{ debugName: "minuteText" }] : /* istanbul ignore next */ []));
335
+ minuteText = signal('', /* @ts-ignore */
336
+ ...(ngDevMode ? [{ debugName: "minuteText" }] : /* istanbul ignore next */ []));
307
337
  /** @internal */
308
- secondText = signal('', ...(ngDevMode ? [{ debugName: "secondText" }] : /* istanbul ignore next */ []));
338
+ secondText = signal('', /* @ts-ignore */
339
+ ...(ngDevMode ? [{ debugName: "secondText" }] : /* istanbul ignore next */ []));
309
340
  /** @internal */
310
- meridiem = signal('AM', ...(ngDevMode ? [{ debugName: "meridiem" }] : /* istanbul ignore next */ []));
341
+ meridiem = signal('AM', /* @ts-ignore */
342
+ ...(ngDevMode ? [{ debugName: "meridiem" }] : /* istanbul ignore next */ []));
311
343
  /** @internal */
312
- focusedField = signal(null, ...(ngDevMode ? [{ debugName: "focusedField" }] : /* istanbul ignore next */ []));
344
+ focusedField = signal(null, /* @ts-ignore */
345
+ ...(ngDevMode ? [{ debugName: "focusedField" }] : /* istanbul ignore next */ []));
313
346
  /** @internal */
314
- internalValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
315
- cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
316
- describedByIdsSignal = signal([], ...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
317
- focusedSignal = signal(false, ...(ngDevMode ? [{ debugName: "focusedSignal" }] : /* istanbul ignore next */ []));
318
- _ngControlRev = signal(0, ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
319
- _formSubmitRev = signal(0, ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
347
+ internalValue = linkedSignal(() => this.value(), /* @ts-ignore */
348
+ ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
349
+ cvaDisabled = signal(false, /* @ts-ignore */
350
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
351
+ describedByIdsSignal = signal([], /* @ts-ignore */
352
+ ...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
353
+ focusedSignal = signal(false, /* @ts-ignore */
354
+ ...(ngDevMode ? [{ debugName: "focusedSignal" }] : /* istanbul ignore next */ []));
355
+ _ngControlRev = signal(0, /* @ts-ignore */
356
+ ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
357
+ _formSubmitRev = signal(0, /* @ts-ignore */
358
+ ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
320
359
  /** When true, an effect should not rewrite field text from the external value. */
321
360
  editing = false;
322
361
  onChange = () => { };
@@ -326,22 +365,29 @@ class TimePickerComponent extends FormFieldControl {
326
365
  isDisabled = computed(() => {
327
366
  this._ngControlRev();
328
367
  return this.disabledInput() || this.cvaDisabled() || !!this.ngControl?.disabled;
329
- }, ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
368
+ }, /* @ts-ignore */
369
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
330
370
  /** @internal */
331
- isEmpty = computed(() => this.internalValue() === null, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
371
+ isEmpty = computed(() => this.internalValue() === null, /* @ts-ignore */
372
+ ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
332
373
  /** @internal Effective variant — auto-naked when wrapped in tw-form-field. */
333
- resolvedVariant = computed(() => this.variant() ?? (this.formField ? 'naked' : 'default'), ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
374
+ resolvedVariant = computed(() => this.variant() ?? (this.formField ? 'naked' : 'default'), /* @ts-ignore */
375
+ ...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
334
376
  /** @internal */
335
- hourMin = computed(() => fieldMin('hour', this.format()), ...(ngDevMode ? [{ debugName: "hourMin" }] : /* istanbul ignore next */ []));
377
+ hourMin = computed(() => fieldMin('hour', this.format()), /* @ts-ignore */
378
+ ...(ngDevMode ? [{ debugName: "hourMin" }] : /* istanbul ignore next */ []));
336
379
  /** @internal */
337
- hourMax = computed(() => fieldMax('hour', this.format()), ...(ngDevMode ? [{ debugName: "hourMax" }] : /* istanbul ignore next */ []));
380
+ hourMax = computed(() => fieldMax('hour', this.format()), /* @ts-ignore */
381
+ ...(ngDevMode ? [{ debugName: "hourMax" }] : /* istanbul ignore next */ []));
338
382
  /** @internal */
339
- hourDisplay = computed(() => this.hourText(), ...(ngDevMode ? [{ debugName: "hourDisplay" }] : /* istanbul ignore next */ []));
383
+ hourDisplay = computed(() => this.hourText(), /* @ts-ignore */
384
+ ...(ngDevMode ? [{ debugName: "hourDisplay" }] : /* istanbul ignore next */ []));
340
385
  /** @internal */
341
386
  hourValueNow = computed(() => {
342
387
  const n = parseField(this.hourText());
343
388
  return n === null ? null : n;
344
- }, ...(ngDevMode ? [{ debugName: "hourValueNow" }] : /* istanbul ignore next */ []));
389
+ }, /* @ts-ignore */
390
+ ...(ngDevMode ? [{ debugName: "hourValueNow" }] : /* istanbul ignore next */ []));
345
391
  /** @internal */
346
392
  hourValueText = computed(() => {
347
393
  const n = this.hourValueNow();
@@ -350,23 +396,29 @@ class TimePickerComponent extends FormFieldControl {
350
396
  const meridiemLabel = this.meridiem() === 'AM' ? this.intl.amLabel : this.intl.pmLabel;
351
397
  const suffix = this.format() === '12h' ? ` ${meridiemLabel}` : '';
352
398
  return `${padTwo(n)}${suffix}`;
353
- }, ...(ngDevMode ? [{ debugName: "hourValueText" }] : /* istanbul ignore next */ []));
399
+ }, /* @ts-ignore */
400
+ ...(ngDevMode ? [{ debugName: "hourValueText" }] : /* istanbul ignore next */ []));
354
401
  /** @internal */
355
- minuteValueNow = computed(() => parseField(this.minuteText()), ...(ngDevMode ? [{ debugName: "minuteValueNow" }] : /* istanbul ignore next */ []));
402
+ minuteValueNow = computed(() => parseField(this.minuteText()), /* @ts-ignore */
403
+ ...(ngDevMode ? [{ debugName: "minuteValueNow" }] : /* istanbul ignore next */ []));
356
404
  /** @internal */
357
405
  minuteValueText = computed(() => {
358
406
  const n = this.minuteValueNow();
359
407
  return n === null ? this.intl.emptyValueText : padTwo(n);
360
- }, ...(ngDevMode ? [{ debugName: "minuteValueText" }] : /* istanbul ignore next */ []));
408
+ }, /* @ts-ignore */
409
+ ...(ngDevMode ? [{ debugName: "minuteValueText" }] : /* istanbul ignore next */ []));
361
410
  /** @internal */
362
- secondValueNow = computed(() => parseField(this.secondText()), ...(ngDevMode ? [{ debugName: "secondValueNow" }] : /* istanbul ignore next */ []));
411
+ secondValueNow = computed(() => parseField(this.secondText()), /* @ts-ignore */
412
+ ...(ngDevMode ? [{ debugName: "secondValueNow" }] : /* istanbul ignore next */ []));
363
413
  /** @internal */
364
414
  secondValueText = computed(() => {
365
415
  const n = this.secondValueNow();
366
416
  return n === null ? this.intl.emptyValueText : padTwo(n);
367
- }, ...(ngDevMode ? [{ debugName: "secondValueText" }] : /* istanbul ignore next */ []));
417
+ }, /* @ts-ignore */
418
+ ...(ngDevMode ? [{ debugName: "secondValueText" }] : /* istanbul ignore next */ []));
368
419
  /** @internal */
369
- groupAriaLabel = computed(() => this.ariaLabel() ?? this.intl.groupLabel, ...(ngDevMode ? [{ debugName: "groupAriaLabel" }] : /* istanbul ignore next */ []));
420
+ groupAriaLabel = computed(() => this.ariaLabel() ?? this.intl.groupLabel, /* @ts-ignore */
421
+ ...(ngDevMode ? [{ debugName: "groupAriaLabel" }] : /* istanbul ignore next */ []));
370
422
  /** @internal Label of the currently focused field, used by stepper aria-labels. */
371
423
  focusedFieldLabel = computed(() => {
372
424
  const f = this.focusedField();
@@ -375,7 +427,8 @@ class TimePickerComponent extends FormFieldControl {
375
427
  if (f === 'second')
376
428
  return this.intl.secondsLabel.toLowerCase();
377
429
  return this.intl.hoursLabel.toLowerCase();
378
- }, ...(ngDevMode ? [{ debugName: "focusedFieldLabel" }] : /* istanbul ignore next */ []));
430
+ }, /* @ts-ignore */
431
+ ...(ngDevMode ? [{ debugName: "focusedFieldLabel" }] : /* istanbul ignore next */ []));
379
432
  /** @internal */
380
433
  describedBy = computed(() => {
381
434
  const extra = this.describedByIdsSignal();
@@ -387,9 +440,11 @@ class TimePickerComponent extends FormFieldControl {
387
440
  merged.push(id);
388
441
  }
389
442
  return merged.length ? merged.join(' ') : '';
390
- }, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
443
+ }, /* @ts-ignore */
444
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
391
445
  // ── Range / error state ──
392
- rangeError = signal(false, ...(ngDevMode ? [{ debugName: "rangeError" }] : /* istanbul ignore next */ []));
446
+ rangeError = signal(false, /* @ts-ignore */
447
+ ...(ngDevMode ? [{ debugName: "rangeError" }] : /* istanbul ignore next */ []));
393
448
  // ── tv() output ──
394
449
  variantResult = computed(() => timePickerVariants({
395
450
  size: this.size(),
@@ -398,16 +453,26 @@ class TimePickerComponent extends FormFieldControl {
398
453
  errorState: this.errorState(),
399
454
  focused: this.focusedSignal(),
400
455
  color: this.color(),
401
- }), ...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
402
- /** @internal */ rootClasses = computed(() => this.variantResult().root(), ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
403
- /** @internal */ fieldGroupClasses = computed(() => this.variantResult().fieldGroup(), ...(ngDevMode ? [{ debugName: "fieldGroupClasses" }] : /* istanbul ignore next */ []));
404
- /** @internal */ fieldClasses = computed(() => this.variantResult().field(), ...(ngDevMode ? [{ debugName: "fieldClasses" }] : /* istanbul ignore next */ []));
405
- /** @internal */ separatorClasses = computed(() => this.variantResult().separator(), ...(ngDevMode ? [{ debugName: "separatorClasses" }] : /* istanbul ignore next */ []));
406
- /** @internal */ stepperGroupClasses = computed(() => this.variantResult().stepperGroup(), ...(ngDevMode ? [{ debugName: "stepperGroupClasses" }] : /* istanbul ignore next */ []));
407
- /** @internal */ stepperClasses = computed(() => this.variantResult().stepper(), ...(ngDevMode ? [{ debugName: "stepperClasses" }] : /* istanbul ignore next */ []));
408
- /** @internal */ stepperIconClasses = computed(() => this.variantResult().stepperIcon(), ...(ngDevMode ? [{ debugName: "stepperIconClasses" }] : /* istanbul ignore next */ []));
409
- /** @internal */ meridiemClasses = computed(() => this.variantResult().meridiem(), ...(ngDevMode ? [{ debugName: "meridiemClasses" }] : /* istanbul ignore next */ []));
410
- /** @internal */ clearButtonClasses = computed(() => this.variantResult().clearButton(), ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : /* istanbul ignore next */ []));
456
+ }), /* @ts-ignore */
457
+ ...(ngDevMode ? [{ debugName: "variantResult" }] : /* istanbul ignore next */ []));
458
+ /** @internal */ rootClasses = computed(() => this.variantResult().root(), /* @ts-ignore */
459
+ ...(ngDevMode ? [{ debugName: "rootClasses" }] : /* istanbul ignore next */ []));
460
+ /** @internal */ fieldGroupClasses = computed(() => this.variantResult().fieldGroup(), /* @ts-ignore */
461
+ ...(ngDevMode ? [{ debugName: "fieldGroupClasses" }] : /* istanbul ignore next */ []));
462
+ /** @internal */ fieldClasses = computed(() => this.variantResult().field(), /* @ts-ignore */
463
+ ...(ngDevMode ? [{ debugName: "fieldClasses" }] : /* istanbul ignore next */ []));
464
+ /** @internal */ separatorClasses = computed(() => this.variantResult().separator(), /* @ts-ignore */
465
+ ...(ngDevMode ? [{ debugName: "separatorClasses" }] : /* istanbul ignore next */ []));
466
+ /** @internal */ stepperGroupClasses = computed(() => this.variantResult().stepperGroup(), /* @ts-ignore */
467
+ ...(ngDevMode ? [{ debugName: "stepperGroupClasses" }] : /* istanbul ignore next */ []));
468
+ /** @internal */ stepperClasses = computed(() => this.variantResult().stepper(), /* @ts-ignore */
469
+ ...(ngDevMode ? [{ debugName: "stepperClasses" }] : /* istanbul ignore next */ []));
470
+ /** @internal */ stepperIconClasses = computed(() => this.variantResult().stepperIcon(), /* @ts-ignore */
471
+ ...(ngDevMode ? [{ debugName: "stepperIconClasses" }] : /* istanbul ignore next */ []));
472
+ /** @internal */ meridiemClasses = computed(() => this.variantResult().meridiem(), /* @ts-ignore */
473
+ ...(ngDevMode ? [{ debugName: "meridiemClasses" }] : /* istanbul ignore next */ []));
474
+ /** @internal */ clearButtonClasses = computed(() => this.variantResult().clearButton(), /* @ts-ignore */
475
+ ...(ngDevMode ? [{ debugName: "clearButtonClasses" }] : /* istanbul ignore next */ []));
411
476
  /** @internal */
412
477
  meridiemButtonClasses(which) {
413
478
  const base = this.variantResult().meridiemButton();
@@ -415,7 +480,8 @@ class TimePickerComponent extends FormFieldControl {
415
480
  return active ? `${base} ${MERIDIEM_ACTIVE_COLOR[this.color()]}` : base;
416
481
  }
417
482
  // ── FormFieldControl impl ──
418
- /** @internal */ id = computed(() => this.idInput() ?? this.hostId, ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
483
+ /** @internal */ id = computed(() => this.idInput() ?? this.hostId, /* @ts-ignore */
484
+ ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
419
485
  /** @internal */ focused = this.focusedSignal.asReadonly();
420
486
  /** @internal */ empty = this.isEmpty;
421
487
  /** @internal */ disabled = this.isDisabled;
@@ -424,7 +490,8 @@ class TimePickerComponent extends FormFieldControl {
424
490
  if (this.requiredInput())
425
491
  return true;
426
492
  return !!this.ngControl?.control?.hasValidator(Validators.required);
427
- }, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
493
+ }, /* @ts-ignore */
494
+ ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
428
495
  /** @internal */
429
496
  errorState = computed(() => {
430
497
  this._ngControlRev();
@@ -436,16 +503,24 @@ class TimePickerComponent extends FormFieldControl {
436
503
  const form = this.parentFormGroup ??
437
504
  this.parentForm;
438
505
  return matcher.isErrorState(this.ngControl?.control ?? null, form);
439
- }, ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
506
+ }, /* @ts-ignore */
507
+ ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
440
508
  /** @internal */ controlType = 'time-picker';
441
509
  /** @internal */
442
- userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), ...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
510
+ userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), /* @ts-ignore */
511
+ ...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
443
512
  // ── Constructor ──
444
513
  constructor() {
445
514
  super();
446
- if (this.ngControl) {
447
- this.ngControl.valueAccessor = this;
448
- }
515
+ // Re-run validation when a constraint moves, so `control.errors` reflects
516
+ // the new bound instead of the verdict from when the value was committed.
517
+ // `validatorOnChange` is a plain callback into Angular's validator
518
+ // plumbing, not a signal write, so this cannot cycle.
519
+ effect(() => {
520
+ this.minTime();
521
+ this.maxTime();
522
+ untracked(() => this.validatorOnChange());
523
+ });
449
524
  // Sync the field text whenever the underlying value changes (programmatic).
450
525
  effect(() => {
451
526
  const v = this.internalValue();
@@ -503,7 +578,7 @@ class TimePickerComponent extends FormFieldControl {
503
578
  return;
504
579
  const hasLabel = !!this.ariaLabel() ||
505
580
  !!this.ariaLabelledby() ||
506
- !!this.formField?.labelChild();
581
+ !!this.formField?.hasLabel();
507
582
  if (!hasLabel) {
508
583
  console.warn('[tw-time-picker] The time-picker has no accessible name. Set aria-label, aria-labelledby, or wrap the component in a <tw-form-field> with a <label twLabel>.');
509
584
  }
@@ -542,6 +617,12 @@ class TimePickerComponent extends FormFieldControl {
542
617
  });
543
618
  }
544
619
  ngOnInit() {
620
+ // Deferred NgControl resolution (see the field declaration). The value
621
+ // accessor itself is registered statically via NG_VALUE_ACCESSOR.
622
+ this.ngControl = this.injector.get(NgControl, null, { self: true, optional: true });
623
+ // `isDisabled` / `required` / `errorState` read `ngControl` and saw `null`
624
+ // until now; bump the revision so they recompute.
625
+ this._ngControlRev.update((n) => n + 1);
545
626
  const ctrl = this.ngControl?.control;
546
627
  if (ctrl) {
547
628
  const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
@@ -925,6 +1006,35 @@ class TimePickerComponent extends FormFieldControl {
925
1006
  return fieldMax('hour', this.format());
926
1007
  return 59;
927
1008
  }
1009
+ // ── Validator ──
1010
+ validatorOnChange = () => { };
1011
+ /** @internal */
1012
+ validate(control) {
1013
+ const value = control.value;
1014
+ if (value === null || value === undefined)
1015
+ return null;
1016
+ if (!this.adapter.isValid(value))
1017
+ return null;
1018
+ const errors = {};
1019
+ const actualSecs = this.secondsOf(value);
1020
+ const min = this.minTime();
1021
+ if (min && this.adapter.isValid(min) && actualSecs < this.secondsOf(min)) {
1022
+ errors.timePickerMin = { min, actual: value };
1023
+ }
1024
+ const max = this.maxTime();
1025
+ if (max && this.adapter.isValid(max) && actualSecs > this.secondsOf(max)) {
1026
+ errors.timePickerMax = { max, actual: value };
1027
+ }
1028
+ return Object.keys(errors).length ? errors : null;
1029
+ }
1030
+ /** @internal */
1031
+ registerOnValidatorChange(fn) {
1032
+ this.validatorOnChange = fn;
1033
+ }
1034
+ /** Time-of-day in seconds, ignoring the date part — the same basis `isInRange` uses. */
1035
+ secondsOf(v) {
1036
+ return timeOfDaySeconds(this.adapter.getHours(v), this.adapter.getMinutes(v), this.adapter.getSeconds(v));
1037
+ }
928
1038
  // ── ControlValueAccessor ──
929
1039
  writeValue(value) {
930
1040
  if (value === null || value === undefined || value === '') {
@@ -978,12 +1088,31 @@ class TimePickerComponent extends FormFieldControl {
978
1088
  return;
979
1089
  this.hourInputRef().nativeElement.focus();
980
1090
  }
981
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TimePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
982
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: TimePickerComponent, isStandalone: true, selector: "tw-time-picker", inputs: { idInput: { classPropertyName: "idInput", publicName: "id", 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 }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, hourStep: { classPropertyName: "hourStep", publicName: "hourStep", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, secondStep: { classPropertyName: "secondStep", publicName: "secondStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, referenceDate: { classPropertyName: "referenceDate", publicName: "referenceDate", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showSteppers: { classPropertyName: "showSteppers", publicName: "showSteppers", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", 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 } }, outputs: { value: "valueChange", timeInput: "timeInput", timeChange: "timeChange" }, host: { properties: { "class": "rootClasses()", "id": "hostId", "attr.aria-disabled": "isDisabled() || null", "attr.aria-invalid": "errorState() || null", "attr.data-variant": "resolvedVariant()" } }, providers: [
1091
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: TimePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1092
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: TimePickerComponent, isStandalone: true, selector: "tw-time-picker", inputs: { idInput: { classPropertyName: "idInput", publicName: "id", 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 }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, showSeconds: { classPropertyName: "showSeconds", publicName: "showSeconds", isSignal: true, isRequired: false, transformFunction: null }, hourStep: { classPropertyName: "hourStep", publicName: "hourStep", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, secondStep: { classPropertyName: "secondStep", publicName: "secondStep", isSignal: true, isRequired: false, transformFunction: null }, minTime: { classPropertyName: "minTime", publicName: "minTime", isSignal: true, isRequired: false, transformFunction: null }, maxTime: { classPropertyName: "maxTime", publicName: "maxTime", isSignal: true, isRequired: false, transformFunction: null }, referenceDate: { classPropertyName: "referenceDate", publicName: "referenceDate", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, showSteppers: { classPropertyName: "showSteppers", publicName: "showSteppers", isSignal: true, isRequired: false, transformFunction: null }, showClear: { classPropertyName: "showClear", publicName: "showClear", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", 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 } }, outputs: { value: "valueChange", timeInput: "timeInput", timeChange: "timeChange" }, host: { properties: { "class": "rootClasses()", "id": "hostId", "attr.aria-disabled": "isDisabled() || null", "attr.aria-invalid": "errorState() || null", "attr.data-variant": "resolvedVariant()" } }, providers: [
983
1093
  {
984
1094
  provide: TW_FORM_FIELD_CONTROL,
985
1095
  useExisting: forwardRef(() => TimePickerComponent),
986
1096
  },
1097
+ {
1098
+ provide: NG_VALIDATORS,
1099
+ useExisting: forwardRef(() => TimePickerComponent),
1100
+ multi: true,
1101
+ },
1102
+ // Required, not optional. Angular v22 compiles any component exposing a
1103
+ // `value` model() as a signal-forms custom control; `FormControlDirective`
1104
+ // only takes the classic CVA path — the one that runs `setUpValidators` and
1105
+ // composes the NG_VALIDATORS above — when a value accessor is already
1106
+ // visible at directive-creation time. Assigning `ngControl.valueAccessor`
1107
+ // in a lifecycle hook is too late, and the failure is silent: value and
1108
+ // disabled still round-trip, so only a spec asserting an error code on a
1109
+ // bound FormControl catches it. See CLAUDE.md's ControlValueAccessor
1110
+ // section.
1111
+ {
1112
+ provide: NG_VALUE_ACCESSOR,
1113
+ useExisting: forwardRef(() => TimePickerComponent),
1114
+ multi: true,
1115
+ },
987
1116
  ], viewQueries: [{ propertyName: "hourInputRef", first: true, predicate: ["hourInput"], descendants: true, isSignal: true }, { propertyName: "minuteInputRef", first: true, predicate: ["minuteInput"], descendants: true, isSignal: true }, { propertyName: "secondInputRef", first: true, predicate: ["secondInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
988
1117
  <div [class]="fieldGroupClasses()" role="group" [attr.aria-label]="groupAriaLabel()">
989
1118
  <input
@@ -1150,7 +1279,7 @@ class TimePickerComponent extends FormFieldControl {
1150
1279
  }
1151
1280
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1152
1281
  }
1153
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: TimePickerComponent, decorators: [{
1282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: TimePickerComponent, decorators: [{
1154
1283
  type: Component,
1155
1284
  args: [{
1156
1285
  selector: 'tw-time-picker',
@@ -1160,6 +1289,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
1160
1289
  provide: TW_FORM_FIELD_CONTROL,
1161
1290
  useExisting: forwardRef(() => TimePickerComponent),
1162
1291
  },
1292
+ {
1293
+ provide: NG_VALIDATORS,
1294
+ useExisting: forwardRef(() => TimePickerComponent),
1295
+ multi: true,
1296
+ },
1297
+ // Required, not optional. Angular v22 compiles any component exposing a
1298
+ // `value` model() as a signal-forms custom control; `FormControlDirective`
1299
+ // only takes the classic CVA path — the one that runs `setUpValidators` and
1300
+ // composes the NG_VALIDATORS above — when a value accessor is already
1301
+ // visible at directive-creation time. Assigning `ngControl.valueAccessor`
1302
+ // in a lifecycle hook is too late, and the failure is silent: value and
1303
+ // disabled still round-trip, so only a spec asserting an error code on a
1304
+ // bound FormControl catches it. See CLAUDE.md's ControlValueAccessor
1305
+ // section.
1306
+ {
1307
+ provide: NG_VALUE_ACCESSOR,
1308
+ useExisting: forwardRef(() => TimePickerComponent),
1309
+ multi: true,
1310
+ },
1163
1311
  ],
1164
1312
  template: `
1165
1313
  <div [class]="fieldGroupClasses()" role="group" [attr.aria-label]="groupAriaLabel()">