@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.
- package/README.md +264 -49
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +11 -7
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-alert.mjs +43 -29
- package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs +103 -0
- package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +56 -31
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +61 -35
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +154 -93
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +24 -15
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +408 -215
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +33 -24
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +144 -79
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +99 -47
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +34 -20
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +46 -29
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +195 -110
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +140 -73
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +16 -11
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +393 -149
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +305 -150
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +56 -43
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +51 -33
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +905 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +49 -26
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +133 -74
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +2 -2
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +28 -17
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +37 -21
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +33 -24
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +63 -48
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +622 -0
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +137 -71
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +101 -46
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +57 -30
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +108 -57
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +46 -26
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select.mjs +217 -122
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-separator.mjs +21 -12
- package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +58 -44
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +25 -14
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +155 -79
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +44 -25
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs +29 -16
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -42
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +107 -64
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +113 -47
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +61 -32
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +88 -42
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +266 -118
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +101 -51
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +798 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +11 -7
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +22 -14
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +243 -95
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +149 -82
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +89 -55
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +50 -28
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +1129 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +554 -0
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw.mjs +7 -0
- package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -1
- package/package.json +45 -20
- package/theme/index.css +10 -2
- package/types/cdevhub-ngx-tw-aspect-ratio.d.ts +40 -0
- package/types/cdevhub-ngx-tw-calendar.d.ts +39 -8
- package/types/cdevhub-ngx-tw-command-palette.d.ts +7 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +36 -10
- package/types/cdevhub-ngx-tw-file-upload.d.ts +250 -0
- package/types/cdevhub-ngx-tw-form-field.d.ts +22 -3
- package/types/cdevhub-ngx-tw-icon-lucide.d.ts +21 -3
- package/types/cdevhub-ngx-tw-number-input.d.ts +130 -0
- package/types/cdevhub-ngx-tw-popover.d.ts +8 -0
- package/types/cdevhub-ngx-tw-stepper.d.ts +30 -2
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +8 -0
- package/types/cdevhub-ngx-tw-table.d.ts +30 -1
- package/types/cdevhub-ngx-tw-tabs.d.ts +8 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +217 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +31 -6
- package/types/cdevhub-ngx-tw-transfer.d.ts +330 -0
- package/types/cdevhub-ngx-tw-tree.d.ts +189 -0
- 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 {
|
|
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,
|
|
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: "
|
|
65
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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: "
|
|
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
|
-
|
|
100
|
-
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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',
|
|
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',
|
|
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,
|
|
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',
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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',
|
|
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,
|
|
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,
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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('',
|
|
332
|
+
hourText = signal('', /* @ts-ignore */
|
|
333
|
+
...(ngDevMode ? [{ debugName: "hourText" }] : /* istanbul ignore next */ []));
|
|
305
334
|
/** @internal */
|
|
306
|
-
minuteText = signal('',
|
|
335
|
+
minuteText = signal('', /* @ts-ignore */
|
|
336
|
+
...(ngDevMode ? [{ debugName: "minuteText" }] : /* istanbul ignore next */ []));
|
|
307
337
|
/** @internal */
|
|
308
|
-
secondText = signal('',
|
|
338
|
+
secondText = signal('', /* @ts-ignore */
|
|
339
|
+
...(ngDevMode ? [{ debugName: "secondText" }] : /* istanbul ignore next */ []));
|
|
309
340
|
/** @internal */
|
|
310
|
-
meridiem = signal('AM',
|
|
341
|
+
meridiem = signal('AM', /* @ts-ignore */
|
|
342
|
+
...(ngDevMode ? [{ debugName: "meridiem" }] : /* istanbul ignore next */ []));
|
|
311
343
|
/** @internal */
|
|
312
|
-
focusedField = signal(null,
|
|
344
|
+
focusedField = signal(null, /* @ts-ignore */
|
|
345
|
+
...(ngDevMode ? [{ debugName: "focusedField" }] : /* istanbul ignore next */ []));
|
|
313
346
|
/** @internal */
|
|
314
|
-
internalValue = linkedSignal(() => this.value(),
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
},
|
|
368
|
+
}, /* @ts-ignore */
|
|
369
|
+
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
330
370
|
/** @internal */
|
|
331
|
-
isEmpty = computed(() => this.internalValue() === null,
|
|
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'),
|
|
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()),
|
|
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()),
|
|
380
|
+
hourMax = computed(() => fieldMax('hour', this.format()), /* @ts-ignore */
|
|
381
|
+
...(ngDevMode ? [{ debugName: "hourMax" }] : /* istanbul ignore next */ []));
|
|
338
382
|
/** @internal */
|
|
339
|
-
hourDisplay = computed(() => this.hourText(),
|
|
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
|
-
},
|
|
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
|
-
},
|
|
399
|
+
}, /* @ts-ignore */
|
|
400
|
+
...(ngDevMode ? [{ debugName: "hourValueText" }] : /* istanbul ignore next */ []));
|
|
354
401
|
/** @internal */
|
|
355
|
-
minuteValueNow = computed(() => parseField(this.minuteText()),
|
|
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
|
-
},
|
|
408
|
+
}, /* @ts-ignore */
|
|
409
|
+
...(ngDevMode ? [{ debugName: "minuteValueText" }] : /* istanbul ignore next */ []));
|
|
361
410
|
/** @internal */
|
|
362
|
-
secondValueNow = computed(() => parseField(this.secondText()),
|
|
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
|
-
},
|
|
417
|
+
}, /* @ts-ignore */
|
|
418
|
+
...(ngDevMode ? [{ debugName: "secondValueText" }] : /* istanbul ignore next */ []));
|
|
368
419
|
/** @internal */
|
|
369
|
-
groupAriaLabel = computed(() => this.ariaLabel() ?? this.intl.groupLabel,
|
|
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
|
-
},
|
|
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
|
-
},
|
|
443
|
+
}, /* @ts-ignore */
|
|
444
|
+
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
391
445
|
// ── Range / error state ──
|
|
392
|
-
rangeError = signal(false,
|
|
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
|
-
}),
|
|
402
|
-
|
|
403
|
-
/** @internal */
|
|
404
|
-
|
|
405
|
-
/** @internal */
|
|
406
|
-
|
|
407
|
-
/** @internal */
|
|
408
|
-
|
|
409
|
-
/** @internal */
|
|
410
|
-
|
|
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,
|
|
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
|
-
},
|
|
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
|
-
},
|
|
506
|
+
}, /* @ts-ignore */
|
|
507
|
+
...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
440
508
|
/** @internal */ controlType = 'time-picker';
|
|
441
509
|
/** @internal */
|
|
442
|
-
userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(),
|
|
510
|
+
userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), /* @ts-ignore */
|
|
511
|
+
...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
443
512
|
// ── Constructor ──
|
|
444
513
|
constructor() {
|
|
445
514
|
super();
|
|
446
|
-
|
|
447
|
-
|
|
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?.
|
|
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: "
|
|
982
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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()">
|