@cdevhub/ngx-tw 0.5.0 → 0.6.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 +78 -15
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +16 -5
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +26 -15
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +17 -5
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +27 -6
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +23 -11
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +5 -5
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +427 -122
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +21 -11
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +215 -96
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +108 -64
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +18 -6
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +55 -30
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs +218 -0
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +236 -211
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs +200 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +34 -7
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +135 -15
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs +203 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +160 -102
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs +209 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +182 -107
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs +122 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +11 -2
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +57 -67
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +42 -24
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +114 -21
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +16 -6
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +52 -61
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +18 -3
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs +168 -0
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +58 -21
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +34 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +95 -45
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +81 -32
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +131 -98
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +207 -42
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs +165 -0
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +386 -210
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/{cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs → cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs} +3 -3
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs +129 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +23 -7
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +89 -54
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +122 -17
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -19
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +63 -25
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +35 -8
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +54 -48
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +12 -15
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +74 -13
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +84 -30
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs +172 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +107 -71
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +41 -9
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +257 -45
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs +201 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +184 -77
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +110 -27
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/{cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs → cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs} +107 -19
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +21 -13
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +102 -15
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs +219 -0
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +88 -69
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +68 -19
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -1
- package/index.json +1 -1
- package/package.json +63 -2
- package/theme/_base.css +13 -0
- package/theme/_dark.css +110 -36
- package/theme/_high-contrast-dark.css +367 -0
- package/theme/_high-contrast.css +13 -2
- package/theme/_light.css +276 -0
- package/theme/_semantic.css +47 -12
- package/theme/_typography.css +17 -0
- package/theme/index.css +86 -7
- package/types/cdevhub-ngx-tw-accordion.d.ts +12 -4
- package/types/cdevhub-ngx-tw-avatar.d.ts +10 -3
- package/types/cdevhub-ngx-tw-button.d.ts +1 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +2 -2
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +1 -1
- package/types/cdevhub-ngx-tw-calendar.d.ts +179 -73
- package/types/cdevhub-ngx-tw-card.d.ts +11 -3
- package/types/cdevhub-ngx-tw-carousel.d.ts +89 -20
- package/types/cdevhub-ngx-tw-checkbox.d.ts +40 -12
- package/types/cdevhub-ngx-tw-code-block.d.ts +11 -2
- package/types/cdevhub-ngx-tw-collapsible.d.ts +32 -5
- package/types/cdevhub-ngx-tw-combobox-testing.d.ts +157 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +94 -53
- package/types/cdevhub-ngx-tw-command-palette-testing.d.ts +136 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +33 -5
- package/types/cdevhub-ngx-tw-core.d.ts +152 -21
- package/types/cdevhub-ngx-tw-date-picker-testing.d.ts +141 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +37 -29
- package/types/cdevhub-ngx-tw-date-range-picker-testing.d.ts +148 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +36 -29
- package/types/cdevhub-ngx-tw-dialog-testing.d.ts +98 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +12 -3
- package/types/cdevhub-ngx-tw-file-upload-testing.d.ts +120 -0
- package/types/cdevhub-ngx-tw-file-upload.d.ts +8 -10
- package/types/cdevhub-ngx-tw-flip-card.d.ts +18 -11
- package/types/cdevhub-ngx-tw-form-field.d.ts +19 -2
- package/types/cdevhub-ngx-tw-icon.d.ts +3 -3
- package/types/cdevhub-ngx-tw-input.d.ts +5 -8
- package/types/cdevhub-ngx-tw-item.d.ts +2 -0
- package/types/cdevhub-ngx-tw-menu-testing.d.ts +131 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +2 -2
- package/types/cdevhub-ngx-tw-number-input.d.ts +20 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +58 -24
- package/types/cdevhub-ngx-tw-popover.d.ts +41 -10
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +1 -1
- package/types/cdevhub-ngx-tw-radio.d.ts +68 -25
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +79 -16
- package/types/cdevhub-ngx-tw-select-testing.d.ts +114 -0
- package/types/cdevhub-ngx-tw-select.d.ts +104 -33
- package/types/cdevhub-ngx-tw-sheet-testing.d.ts +104 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +19 -3
- package/types/cdevhub-ngx-tw-slider.d.ts +79 -25
- package/types/cdevhub-ngx-tw-sort.d.ts +45 -4
- package/types/cdevhub-ngx-tw-split.d.ts +32 -2
- package/types/cdevhub-ngx-tw-stat.d.ts +14 -4
- package/types/cdevhub-ngx-tw-stepper.d.ts +16 -2
- package/types/cdevhub-ngx-tw-switch.d.ts +35 -13
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +7 -6
- package/types/cdevhub-ngx-tw-table.d.ts +53 -10
- package/types/cdevhub-ngx-tw-tabs.d.ts +41 -10
- package/types/cdevhub-ngx-tw-tags-input-testing.d.ts +135 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +26 -11
- package/types/cdevhub-ngx-tw-textarea.d.ts +3 -2
- package/types/cdevhub-ngx-tw-theme.d.ts +245 -39
- package/types/cdevhub-ngx-tw-time-picker-testing.d.ts +129 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +76 -19
- package/types/cdevhub-ngx-tw-timeline.d.ts +36 -12
- package/types/cdevhub-ngx-tw-toast.d.ts +20 -6
- package/types/cdevhub-ngx-tw-tooltip.d.ts +28 -6
- package/types/cdevhub-ngx-tw-transfer-testing.d.ts +152 -0
- package/types/cdevhub-ngx-tw-transfer.d.ts +21 -8
- package/types/cdevhub-ngx-tw-tree.d.ts +6 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs.map +0 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs.map +0 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
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 {
|
|
3
|
+
import { NgControl, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
4
|
import { FocusMonitor, LiveAnnouncer } from '@angular/cdk/a11y';
|
|
5
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
|
-
import { merge } from 'rxjs';
|
|
7
5
|
import { tv } from 'tailwind-variants';
|
|
8
|
-
import {
|
|
6
|
+
import { wireErrorState, fieldMin, fieldMax, parseField, padTwo, to12h, appendDigit, isTerminalDigit, stepWithWrap, from12h, timeOfDaySeconds, clamp } from '@cdevhub/ngx-tw/core';
|
|
9
7
|
import { FormFieldControl, TW_FORM_FIELD, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
|
|
10
8
|
import { DATE_ADAPTER } from '@cdevhub/ngx-tw/calendar';
|
|
11
9
|
|
|
@@ -81,11 +79,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
81
79
|
* }),
|
|
82
80
|
* ]
|
|
83
81
|
* ```
|
|
82
|
+
*
|
|
83
|
+
* Keys explicitly set to `undefined` are dropped before merging. Root
|
|
84
|
+
* `tsconfig.json` does not set `exactOptionalPropertyTypes`, so
|
|
85
|
+
* `provideTimePickerIntl({ hoursLabel: bundle['time.hours'] })` type-checks
|
|
86
|
+
* even when the lookup misses — and a plain `Object.assign` would then copy
|
|
87
|
+
* that `undefined` over a field `TimePickerIntl` types as `string`. Because
|
|
88
|
+
* this is a bootstrap-level provider, one missing key broke *every*
|
|
89
|
+
* time-picker in the app: `focusedFieldLabel()` calls `.toLowerCase()` on the
|
|
90
|
+
* field labels to build the stepper aria-labels, so the picker threw on first
|
|
91
|
+
* render. Filtering here makes a missing key fall back to the English default.
|
|
84
92
|
*/
|
|
85
93
|
function provideTimePickerIntl(custom) {
|
|
86
94
|
return {
|
|
87
95
|
provide: TimePickerIntl,
|
|
88
|
-
useFactory: () =>
|
|
96
|
+
useFactory: () => {
|
|
97
|
+
const overrides = Object.fromEntries(Object.entries(custom ?? {}).filter(([, value]) => value !== undefined));
|
|
98
|
+
return Object.assign(new TimePickerIntl(), overrides);
|
|
99
|
+
},
|
|
89
100
|
};
|
|
90
101
|
}
|
|
91
102
|
|
|
@@ -109,11 +120,21 @@ const timePickerVariants = tv({
|
|
|
109
120
|
// as the equivalent-control exception applies — the time is fully editable
|
|
110
121
|
// by typing into the spinbutton fields (and via ↑/↓), so the spinner is a
|
|
111
122
|
// pointer enhancement, never the only path.
|
|
123
|
+
//
|
|
124
|
+
// That min-content floor is also why the column is suppressed at
|
|
125
|
+
// `size="xs"` + `variant="default"`: a pinned h-6 root leaves a 22px content
|
|
126
|
+
// box, below the stacked pair's 24px floor. See `steppersVisible` below.
|
|
112
127
|
stepperGroup: 'flex flex-col self-stretch ml-0.5',
|
|
113
128
|
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',
|
|
114
129
|
stepperIcon: '',
|
|
115
130
|
meridiem: 'inline-flex items-center rounded-md border border-border overflow-hidden ml-2 shrink-0',
|
|
116
131
|
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',
|
|
132
|
+
// size-6 (24×24 CSS px) is the WCAG 2.2 SC 2.5.8 minimum interactive target, held flat across
|
|
133
|
+
// every size. It fits the 30/34/42/46px content boxes at sm..xl. At xs the pinned root is h-6
|
|
134
|
+
// (24px border-box) so its content box is only 22px: the button stays 24×24 anyway —
|
|
135
|
+
// docs/vertical-rhythm.md calls 24px a hard floor — and therefore coincides with the root's
|
|
136
|
+
// *border*-box, overlapping the 1px border row top and bottom. The root's height is unaffected
|
|
137
|
+
// (h-6 is a definite height); only the hover fill meets the border at that density.
|
|
117
138
|
clearButton: 'inline-flex items-center justify-center shrink-0 rounded-md text-fg-muted hover:text-fg hover:bg-surface-muted transition-colors duration-normal motion-reduce:transition-none size-6 ml-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
|
|
118
139
|
},
|
|
119
140
|
variants: {
|
|
@@ -126,6 +147,13 @@ const timePickerVariants = tv({
|
|
|
126
147
|
field: 'w-5 text-xs',
|
|
127
148
|
stepper: 'w-6',
|
|
128
149
|
stepperIcon: 'size-3',
|
|
150
|
+
// `border-0` on the AM/PM shell only at xs. With its 1px border the pair
|
|
151
|
+
// measures 26px, which cannot sit inside the 22px content box of a pinned
|
|
152
|
+
// h-6 root. Dropping the shell border lets each radio keep the full 24px
|
|
153
|
+
// WCAG 2.2 SC 2.5.8 target height and makes the pair coincide with the
|
|
154
|
+
// root's border-box. The pair stays legible without the shell because the
|
|
155
|
+
// selected half always carries a solid `bg-{color}-500` fill.
|
|
156
|
+
meridiem: 'border-0',
|
|
129
157
|
meridiemButton: 'px-1.5 py-1 text-2xs',
|
|
130
158
|
},
|
|
131
159
|
sm: {
|
|
@@ -163,7 +191,10 @@ const timePickerVariants = tv({
|
|
|
163
191
|
},
|
|
164
192
|
variant: {
|
|
165
193
|
default: {
|
|
166
|
-
|
|
194
|
+
// No `py-*`: the height is pinned per size in compoundVariants below.
|
|
195
|
+
// See docs/vertical-rhythm.md §3 — padding and a pinned height fight,
|
|
196
|
+
// and under border-box the taller one silently wins.
|
|
197
|
+
root: 'rounded-md border border-border bg-surface px-3 hover:border-border-strong',
|
|
167
198
|
},
|
|
168
199
|
naked: {
|
|
169
200
|
root: 'bg-transparent border-0 rounded-none p-0 focus-within:outline-none',
|
|
@@ -187,6 +218,17 @@ const timePickerVariants = tv({
|
|
|
187
218
|
},
|
|
188
219
|
},
|
|
189
220
|
compoundVariants: [
|
|
221
|
+
// ── Pinned control height (docs/vertical-rhythm.md §1) ──
|
|
222
|
+
// Only the `default` variant carries a height. `naked` means somebody else
|
|
223
|
+
// owns the box — `<tw-form-field>` auto-nakeds this picker and supplies its
|
|
224
|
+
// own bordered, padded controlWrapper, and both `<tw-date-picker>` and
|
|
225
|
+
// `<tw-date-range-picker>` embed a naked time-picker in their overlay footer.
|
|
226
|
+
// Pinning naked would stack 36px on top of those hosts' own padding.
|
|
227
|
+
{ variant: 'default', size: 'xs', class: { root: 'h-6' } },
|
|
228
|
+
{ variant: 'default', size: 'sm', class: { root: 'h-8' } },
|
|
229
|
+
{ variant: 'default', size: 'md', class: { root: 'h-9' } },
|
|
230
|
+
{ variant: 'default', size: 'lg', class: { root: 'h-11' } },
|
|
231
|
+
{ variant: 'default', size: 'xl', class: { root: 'h-12' } },
|
|
190
232
|
{ variant: 'default', focused: true, color: 'primary', class: { root: 'border-primary-500' } },
|
|
191
233
|
{ variant: 'default', focused: true, color: 'secondary', class: { root: 'border-secondary-500' } },
|
|
192
234
|
{ variant: 'default', focused: true, color: 'accent', class: { root: 'border-accent-500' } },
|
|
@@ -209,20 +251,30 @@ const timePickerVariants = tv({
|
|
|
209
251
|
// ── Static active-state color lookup for the AM/PM meridiem button ─────────────
|
|
210
252
|
// Each entry is written out so Tailwind v4's static scanner picks the classes up.
|
|
211
253
|
// Mirrors the lookup-table pattern used by `checkbox.ts` and `radio.ts`.
|
|
254
|
+
//
|
|
255
|
+
// Every entry uses the `{role}-solid` / `-solid-fg` / `-solid-hover` slot trio,
|
|
256
|
+
// which the theme layer picks specifically so the label reaches AA on the fill
|
|
257
|
+
// (see the light-mode contrast table in `theme/_semantic.css`). The previous
|
|
258
|
+
// `bg-{role}-500 text-on-{role}` pairing was off that contract: `primary-500`
|
|
259
|
+
// is blue-500, and white on blue-500 measures 3.76:1 in light / 3.92:1 in dark
|
|
260
|
+
// against a 4.5:1 bar for this 12px label. `primary-solid` is blue-600 in light
|
|
261
|
+
// (≈6:1 on white) and blue-400 in dark (≈5.6:1 on blue-950). `--color-on-{role}`
|
|
262
|
+
// is additionally a legacy alias the theme marks for removal — new code takes
|
|
263
|
+
// the slot token directly.
|
|
212
264
|
const MERIDIEM_ACTIVE_COLOR = {
|
|
213
|
-
primary: 'bg-primary-
|
|
214
|
-
secondary: 'bg-secondary-
|
|
215
|
-
accent: 'bg-accent-
|
|
216
|
-
neutral: 'bg-
|
|
217
|
-
info: 'bg-info-
|
|
218
|
-
success: 'bg-success-
|
|
219
|
-
warning: 'bg-warning-
|
|
220
|
-
error: 'bg-error-
|
|
265
|
+
primary: 'bg-primary-solid text-primary-solid-fg hover:bg-primary-solid-hover',
|
|
266
|
+
secondary: 'bg-secondary-solid text-secondary-solid-fg hover:bg-secondary-solid-hover',
|
|
267
|
+
accent: 'bg-accent-solid text-accent-solid-fg hover:bg-accent-solid-hover',
|
|
268
|
+
neutral: 'bg-neutral-solid text-neutral-solid-fg hover:bg-neutral-solid-hover',
|
|
269
|
+
info: 'bg-info-solid text-info-solid-fg hover:bg-info-solid-hover',
|
|
270
|
+
success: 'bg-success-solid text-success-solid-fg hover:bg-success-solid-hover',
|
|
271
|
+
warning: 'bg-warning-solid text-warning-solid-fg hover:bg-warning-solid-hover',
|
|
272
|
+
error: 'bg-error-solid text-error-solid-fg hover:bg-error-solid-hover',
|
|
221
273
|
};
|
|
222
274
|
let nextTimePickerId = 0;
|
|
223
275
|
class TimePickerComponent extends FormFieldControl {
|
|
224
276
|
// ── Inputs ──
|
|
225
|
-
/** Id on the time-picker's host element. Auto-generated when not provided.
|
|
277
|
+
/** Id on the time-picker's host element. Auto-generated when not provided. The accessible name inside a `<tw-form-field>` comes from `aria-labelledby` on the inner `role="group"` wrapper, not from the field's `<label for>` — the host is a custom element, which `for` cannot target. */
|
|
226
278
|
idInput = input(undefined, { ...(ngDevMode ? { debugName: "idInput" } : /* istanbul ignore next */ {}), alias: 'id' });
|
|
227
279
|
/** When true, the whole component is disabled and every field sets `aria-disabled="true"`. Defaults to `false`. */
|
|
228
280
|
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled',
|
|
@@ -230,7 +282,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
230
282
|
/** When true, exposes `aria-required="true"`. Validators.required on a bound NgControl is also honoured. Defaults to `false`. */
|
|
231
283
|
requiredInput = input(false, { ...(ngDevMode ? { debugName: "requiredInput" } : /* istanbul ignore next */ {}), alias: 'required',
|
|
232
284
|
transform: booleanAttribute });
|
|
233
|
-
/** When true, blocks typing, stepping, and the AM/PM toggle — the value is still read-only visible. Defaults to `false`. */
|
|
285
|
+
/** When true, blocks typing, stepping, and the AM/PM toggle — the value is still read-only visible. Defaults to `false`. Alias: `readonly`. */
|
|
234
286
|
readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly',
|
|
235
287
|
transform: booleanAttribute });
|
|
236
288
|
/** Controls field height, font size, and stepper density. Defaults to `'md'`. */
|
|
@@ -242,7 +294,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
242
294
|
/** Visual style of the chrome. `'default'` draws its own border; `'naked'` strips chrome. Auto-resolves to `'naked'` inside `<tw-form-field>`. */
|
|
243
295
|
variant = input(undefined, /* @ts-ignore */
|
|
244
296
|
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
245
|
-
/** `'12h'` renders 1–12 hours with an AM/PM toggle; `'24h'` renders 00–23. Defaults to `'24h'`. */
|
|
297
|
+
/** `'12h'` renders 1–12 hours with an AM/PM toggle; `'24h'` renders 00–23. The same axis is spelled `timeFormat` on `tw-date-range-picker` and lives under `timeConfig.format` on `tw-date-picker`, where `format` instead means the display-format descriptor. Defaults to `'24h'`. */
|
|
246
298
|
format = input('24h', /* @ts-ignore */
|
|
247
299
|
...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
|
|
248
300
|
/** When true, renders a seconds field after minutes. Defaults to `false`. */
|
|
@@ -269,28 +321,26 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
269
321
|
/** Placeholder shown in each field when empty. Defaults to `'--'`. */
|
|
270
322
|
placeholder = input(undefined, /* @ts-ignore */
|
|
271
323
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
272
|
-
/** Whether to render the up/down stepper column. Defaults to `true`. */
|
|
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.
|
|
324
|
+
/** Whether to render the up/down stepper column. Defaults to `true`. 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. Ignored at `size="xs"` on the bordered `default` variant, where the 24px control leaves no room for a stacked pair — use ↑/↓ on a field instead. */
|
|
274
325
|
showSteppers = input(true, /* @ts-ignore */
|
|
275
326
|
...(ngDevMode ? [{ debugName: "showSteppers" }] : /* istanbul ignore next */ []));
|
|
276
|
-
/** Whether to render the clear affordance when a value is set. Defaults to `true
|
|
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.
|
|
327
|
+
/** Whether to render the clear affordance when a value is set. Defaults to `true` — it 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. */
|
|
278
328
|
showClear = input(true, /* @ts-ignore */
|
|
279
329
|
...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
|
|
280
|
-
/** Accessible label for the clear button. Defaults to `'Clear time'`. */
|
|
330
|
+
/** Accessible label for the clear button. Note the name differs in meaning from the date pickers, which spell this `clearAriaLabel` and reserve `clearLabel` for the overlay action bar's visible button text. Defaults to `'Clear time'`. */
|
|
281
331
|
clearLabel = input('Clear time', /* @ts-ignore */
|
|
282
332
|
...(ngDevMode ? [{ debugName: "clearLabel" }] : /* istanbul ignore next */ []));
|
|
283
|
-
/** Per-instance override of the `ErrorStateMatcher`. */
|
|
333
|
+
/** Per-instance override of the `ErrorStateMatcher`. Defaults to `undefined`. */
|
|
284
334
|
errorStateMatcher = input(undefined, /* @ts-ignore */
|
|
285
335
|
...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
286
|
-
/** Accessible name for the fields group. Required when no visible label is supplied. Alias: `aria-label`. */
|
|
336
|
+
/** Accessible name for the fields group. Required when no visible label is supplied. Alias: `aria-label`. Defaults to `undefined`. */
|
|
287
337
|
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
288
|
-
/** ID of an external element that labels the group. Alias: `aria-labelledby`. */
|
|
338
|
+
/** ID of an external element that labels the group. Alias: `aria-labelledby`. Defaults to `undefined`. */
|
|
289
339
|
ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
290
|
-
/** Consumer-supplied `aria-describedby` ids. The form-field preserves these when merging hint/error ids. Alias: `aria-describedby`. */
|
|
340
|
+
/** Consumer-supplied `aria-describedby` ids. The form-field preserves these when merging hint/error ids. Alias: `aria-describedby`. Defaults to `undefined`. */
|
|
291
341
|
userAriaDescribedByInput = input(undefined, { ...(ngDevMode ? { debugName: "userAriaDescribedByInput" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
292
342
|
// ── Models ──
|
|
293
|
-
/** Two-way bound current time. `null` when empty. Setting programmatically updates the fields without firing `onChange`. */
|
|
343
|
+
/** Two-way bound current time. `null` when empty. Setting programmatically updates the fields without firing `onChange`. Defaults to `null`. */
|
|
294
344
|
value = model(null, /* @ts-ignore */
|
|
295
345
|
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
296
346
|
// ── Outputs ──
|
|
@@ -311,9 +361,6 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
311
361
|
// against its own validator during construction. Mirrors `date-picker.ts`,
|
|
312
362
|
// `date-range-picker.ts` and `calendar.ts`.
|
|
313
363
|
ngControl = null;
|
|
314
|
-
parentForm = inject(NgForm, { optional: true });
|
|
315
|
-
parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
316
|
-
defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
|
|
317
364
|
/** @internal — exposed for the template; consumers configure via `provideTimePickerIntl()`. */
|
|
318
365
|
intl = inject(TimePickerIntl, { optional: true }) ?? new TimePickerIntl();
|
|
319
366
|
// ── View refs ──
|
|
@@ -350,12 +397,19 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
350
397
|
...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
351
398
|
describedByIdsSignal = signal([], /* @ts-ignore */
|
|
352
399
|
...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
|
|
400
|
+
labelledByIdsSignal = signal([], /* @ts-ignore */
|
|
401
|
+
...(ngDevMode ? [{ debugName: "labelledByIdsSignal" }] : /* istanbul ignore next */ []));
|
|
353
402
|
focusedSignal = signal(false, /* @ts-ignore */
|
|
354
403
|
...(ngDevMode ? [{ debugName: "focusedSignal" }] : /* istanbul ignore next */ []));
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
404
|
+
/** @internal Shared `errorState` / `required` / `errors` derivation — see `wireErrorState`. */
|
|
405
|
+
errorWiring = wireErrorState({
|
|
406
|
+
ngControl: () => this.ngControl,
|
|
407
|
+
matcher: () => this.errorStateMatcher(),
|
|
408
|
+
required: () => this.requiredInput(),
|
|
409
|
+
track: [() => this.focusedSignal()],
|
|
410
|
+
// An out-of-range time is an error regardless of validator status.
|
|
411
|
+
errorStateOverride: () => (this.rangeError() ? true : undefined),
|
|
412
|
+
});
|
|
359
413
|
/** When true, an effect should not rewrite field text from the external value. */
|
|
360
414
|
editing = false;
|
|
361
415
|
onChange = () => { };
|
|
@@ -363,7 +417,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
363
417
|
// ── Derived state ──
|
|
364
418
|
/** @internal */
|
|
365
419
|
isDisabled = computed(() => {
|
|
366
|
-
this.
|
|
420
|
+
this.errorWiring.rev();
|
|
367
421
|
return this.disabledInput() || this.cvaDisabled() || !!this.ngControl?.disabled;
|
|
368
422
|
}, /* @ts-ignore */
|
|
369
423
|
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
@@ -373,6 +427,28 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
373
427
|
/** @internal Effective variant — auto-naked when wrapped in tw-form-field. */
|
|
374
428
|
resolvedVariant = computed(() => this.variant() ?? (this.formField ? 'naked' : 'default'), /* @ts-ignore */
|
|
375
429
|
...(ngDevMode ? [{ debugName: "resolvedVariant" }] : /* istanbul ignore next */ []));
|
|
430
|
+
/**
|
|
431
|
+
* @internal Whether the stepper column actually renders.
|
|
432
|
+
*
|
|
433
|
+
* `showSteppers` stays the consumer's switch, but the column is geometrically
|
|
434
|
+
* impossible at `size="xs"` on the bordered `default` variant: the pinned h-6
|
|
435
|
+
* root is 24px border-box, leaving a 22px content box, while a stacked pair of
|
|
436
|
+
* chevron buttons has a 24px min-content height (2 × size-3). Rendering it
|
|
437
|
+
* anyway would either overflow the shell or squeeze each button to 11px — far
|
|
438
|
+
* under any usable pointer target. The `naked` variant keeps its steppers: it
|
|
439
|
+
* draws no border, so in the date-picker / date-range-picker overlay footers a
|
|
440
|
+
* 24px row leaves a full 24px content box and the pair fits exactly. (Inside
|
|
441
|
+
* `<tw-form-field>` the row is governed by the field's own padding and its
|
|
442
|
+
* `min-h-*` floor, which is a minimum — the row grows rather than clipping.)
|
|
443
|
+
* Stepping stays available everywhere via ↑/↓ on a focused field, so nothing
|
|
444
|
+
* becomes unreachable.
|
|
445
|
+
*/
|
|
446
|
+
steppersVisible = computed(() => {
|
|
447
|
+
if (!this.showSteppers())
|
|
448
|
+
return false;
|
|
449
|
+
return !(this.size() === 'xs' && this.resolvedVariant() === 'default');
|
|
450
|
+
}, /* @ts-ignore */
|
|
451
|
+
...(ngDevMode ? [{ debugName: "steppersVisible" }] : /* istanbul ignore next */ []));
|
|
376
452
|
/** @internal */
|
|
377
453
|
hourMin = computed(() => fieldMin('hour', this.format()), /* @ts-ignore */
|
|
378
454
|
...(ngDevMode ? [{ debugName: "hourMin" }] : /* istanbul ignore next */ []));
|
|
@@ -416,8 +492,34 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
416
492
|
return n === null ? this.intl.emptyValueText : padTwo(n);
|
|
417
493
|
}, /* @ts-ignore */
|
|
418
494
|
...(ngDevMode ? [{ debugName: "secondValueText" }] : /* istanbul ignore next */ []));
|
|
419
|
-
/**
|
|
420
|
-
|
|
495
|
+
/**
|
|
496
|
+
* @internal Merged `aria-labelledby` for the `role="group"` wrapper: the ids a
|
|
497
|
+
* wrapping `<tw-form-field>` pushes down (its projected `<label twLabel>` plus
|
|
498
|
+
* anything the consumer supplied) or, standalone, the consumer's own
|
|
499
|
+
* `aria-labelledby`. The host is `<tw-time-picker>`, a custom element the
|
|
500
|
+
* field's `<label for>` cannot target, so this attribute is the only route
|
|
501
|
+
* from the visible label to the composite. Returns `null` when nothing labels
|
|
502
|
+
* the group by reference.
|
|
503
|
+
*/
|
|
504
|
+
groupLabelledBy = computed(() => {
|
|
505
|
+
const pushed = this.labelledByIdsSignal();
|
|
506
|
+
if (pushed.length)
|
|
507
|
+
return pushed.join(' ');
|
|
508
|
+
return this.ariaLabelledby() ?? null;
|
|
509
|
+
}, /* @ts-ignore */
|
|
510
|
+
...(ngDevMode ? [{ debugName: "groupLabelledBy" }] : /* istanbul ignore next */ []));
|
|
511
|
+
/**
|
|
512
|
+
* @internal `aria-label` for the group. An explicit `aria-label` always wins;
|
|
513
|
+
* otherwise the generic intl fallback is suppressed whenever `groupLabelledBy`
|
|
514
|
+
* already names the group, so a real label is not shadowed by "Time" in the
|
|
515
|
+
* accessibility tree inspector.
|
|
516
|
+
*/
|
|
517
|
+
groupAriaLabel = computed(() => {
|
|
518
|
+
const explicit = this.ariaLabel();
|
|
519
|
+
if (explicit)
|
|
520
|
+
return explicit;
|
|
521
|
+
return this.groupLabelledBy() ? null : this.intl.groupLabel;
|
|
522
|
+
}, /* @ts-ignore */
|
|
421
523
|
...(ngDevMode ? [{ debugName: "groupAriaLabel" }] : /* istanbul ignore next */ []));
|
|
422
524
|
/** @internal Label of the currently focused field, used by stepper aria-labels. */
|
|
423
525
|
focusedFieldLabel = computed(() => {
|
|
@@ -485,30 +587,29 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
485
587
|
/** @internal */ focused = this.focusedSignal.asReadonly();
|
|
486
588
|
/** @internal */ empty = this.isEmpty;
|
|
487
589
|
/** @internal */ disabled = this.isDisabled;
|
|
488
|
-
/**
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
590
|
+
/**
|
|
591
|
+
* Whether the control is required — the `required` input OR `Validators.required`
|
|
592
|
+
* on a bound `NgControl`. Bound to `aria-required` on each spinbutton field, not on
|
|
593
|
+
* the `role="group"` wrapper, which does not permit the attribute.
|
|
594
|
+
*/
|
|
595
|
+
required = this.errorWiring.required;
|
|
495
596
|
/** @internal */
|
|
496
|
-
errorState =
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
return matcher.isErrorState(this.ngControl?.control ?? null, form);
|
|
506
|
-
}, /* @ts-ignore */
|
|
507
|
-
...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
597
|
+
errorState = this.errorWiring.errorState;
|
|
598
|
+
/**
|
|
599
|
+
* @internal Active validation errors map from the bound `NgControl` (or `null` when the
|
|
600
|
+
* control reports none / is unbound). This is what `[twError match="…"]` filters on inside a
|
|
601
|
+
* `<tw-form-field>`; without it every `match`-targeted message — including the
|
|
602
|
+
* `timePickerMin` / `timePickerMax` codes this component's `NG_VALIDATORS` apparatus exists
|
|
603
|
+
* to produce — stays permanently hidden.
|
|
604
|
+
*/
|
|
605
|
+
errors = this.errorWiring.errors;
|
|
508
606
|
/** @internal */ controlType = 'time-picker';
|
|
509
607
|
/** @internal */
|
|
510
608
|
userAriaDescribedBy = computed(() => this.userAriaDescribedByInput(), /* @ts-ignore */
|
|
511
609
|
...(ngDevMode ? [{ debugName: "userAriaDescribedBy" }] : /* istanbul ignore next */ []));
|
|
610
|
+
/** @internal Consumer-supplied `aria-labelledby`, surfaced so the wrapping form-field merges it into the ids it pushes back rather than replacing it. */
|
|
611
|
+
userAriaLabelledby = computed(() => this.ariaLabelledby(), /* @ts-ignore */
|
|
612
|
+
...(ngDevMode ? [{ debugName: "userAriaLabelledby" }] : /* istanbul ignore next */ []));
|
|
512
613
|
// ── Constructor ──
|
|
513
614
|
constructor() {
|
|
514
615
|
super();
|
|
@@ -608,7 +709,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
608
709
|
this.editing = false;
|
|
609
710
|
this.normalizeEmptyFields();
|
|
610
711
|
this.onTouched();
|
|
611
|
-
this.
|
|
712
|
+
this.errorWiring.bump();
|
|
612
713
|
}
|
|
613
714
|
});
|
|
614
715
|
this.destroyRef.onDestroy(() => {
|
|
@@ -622,22 +723,8 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
622
723
|
this.ngControl = this.injector.get(NgControl, null, { self: true, optional: true });
|
|
623
724
|
// `isDisabled` / `required` / `errorState` read `ngControl` and saw `null`
|
|
624
725
|
// until now; bump the revision so they recompute.
|
|
625
|
-
this.
|
|
626
|
-
|
|
627
|
-
if (ctrl) {
|
|
628
|
-
const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
|
|
629
|
-
if (streams.length) {
|
|
630
|
-
merge(...streams)
|
|
631
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
632
|
-
.subscribe(() => this._ngControlRev.update((n) => n + 1));
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
|
|
636
|
-
if (submit) {
|
|
637
|
-
submit
|
|
638
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
639
|
-
.subscribe(() => this._formSubmitRev.update((n) => n + 1));
|
|
640
|
-
}
|
|
726
|
+
this.errorWiring.bump();
|
|
727
|
+
this.errorWiring.connect();
|
|
641
728
|
}
|
|
642
729
|
// ── Public imperative API ──
|
|
643
730
|
/** Moves focus to the hours field. */
|
|
@@ -1078,6 +1165,10 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
1078
1165
|
setDescribedByIds(ids) {
|
|
1079
1166
|
this.describedByIdsSignal.set([...ids]);
|
|
1080
1167
|
}
|
|
1168
|
+
/** @internal Receives the merged `aria-labelledby` ids from the wrapping form-field and applies them to the `role="group"` wrapper. Required because the host is a custom element rather than a labelable control, so the field's `<label for>` never reaches it. */
|
|
1169
|
+
setLabelledByIds(ids) {
|
|
1170
|
+
this.labelledByIdsSignal.set([...ids]);
|
|
1171
|
+
}
|
|
1081
1172
|
/** @internal */
|
|
1082
1173
|
onContainerClick(event) {
|
|
1083
1174
|
if (this.isDisabled())
|
|
@@ -1114,7 +1205,12 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
1114
1205
|
multi: true,
|
|
1115
1206
|
},
|
|
1116
1207
|
], 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: `
|
|
1117
|
-
<div
|
|
1208
|
+
<div
|
|
1209
|
+
[class]="fieldGroupClasses()"
|
|
1210
|
+
role="group"
|
|
1211
|
+
[attr.aria-label]="groupAriaLabel()"
|
|
1212
|
+
[attr.aria-labelledby]="groupLabelledBy()"
|
|
1213
|
+
>
|
|
1118
1214
|
<input
|
|
1119
1215
|
#hourInput
|
|
1120
1216
|
type="text"
|
|
@@ -1133,6 +1229,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
1133
1229
|
[attr.aria-valuenow]="hourValueNow()"
|
|
1134
1230
|
[attr.aria-valuetext]="hourValueText()"
|
|
1135
1231
|
[attr.aria-invalid]="errorState() || null"
|
|
1232
|
+
[attr.aria-required]="required() || null"
|
|
1136
1233
|
[attr.aria-disabled]="isDisabled() || null"
|
|
1137
1234
|
(keydown)="onFieldKeydown($event, 'hour')"
|
|
1138
1235
|
(beforeinput)="onBeforeInput($event, 'hour')"
|
|
@@ -1161,6 +1258,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
1161
1258
|
[attr.aria-valuenow]="minuteValueNow()"
|
|
1162
1259
|
[attr.aria-valuetext]="minuteValueText()"
|
|
1163
1260
|
[attr.aria-invalid]="errorState() || null"
|
|
1261
|
+
[attr.aria-required]="required() || null"
|
|
1164
1262
|
[attr.aria-disabled]="isDisabled() || null"
|
|
1165
1263
|
(keydown)="onFieldKeydown($event, 'minute')"
|
|
1166
1264
|
(beforeinput)="onBeforeInput($event, 'minute')"
|
|
@@ -1190,6 +1288,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
1190
1288
|
[attr.aria-valuenow]="secondValueNow()"
|
|
1191
1289
|
[attr.aria-valuetext]="secondValueText()"
|
|
1192
1290
|
[attr.aria-invalid]="errorState() || null"
|
|
1291
|
+
[attr.aria-required]="required() || null"
|
|
1193
1292
|
[attr.aria-disabled]="isDisabled() || null"
|
|
1194
1293
|
(keydown)="onFieldKeydown($event, 'second')"
|
|
1195
1294
|
(beforeinput)="onBeforeInput($event, 'second')"
|
|
@@ -1200,7 +1299,7 @@ class TimePickerComponent extends FormFieldControl {
|
|
|
1200
1299
|
}
|
|
1201
1300
|
</div>
|
|
1202
1301
|
|
|
1203
|
-
@if (
|
|
1302
|
+
@if (steppersVisible()) {
|
|
1204
1303
|
<div [class]="stepperGroupClasses()">
|
|
1205
1304
|
<button
|
|
1206
1305
|
type="button"
|
|
@@ -1310,7 +1409,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1310
1409
|
},
|
|
1311
1410
|
],
|
|
1312
1411
|
template: `
|
|
1313
|
-
<div
|
|
1412
|
+
<div
|
|
1413
|
+
[class]="fieldGroupClasses()"
|
|
1414
|
+
role="group"
|
|
1415
|
+
[attr.aria-label]="groupAriaLabel()"
|
|
1416
|
+
[attr.aria-labelledby]="groupLabelledBy()"
|
|
1417
|
+
>
|
|
1314
1418
|
<input
|
|
1315
1419
|
#hourInput
|
|
1316
1420
|
type="text"
|
|
@@ -1329,6 +1433,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1329
1433
|
[attr.aria-valuenow]="hourValueNow()"
|
|
1330
1434
|
[attr.aria-valuetext]="hourValueText()"
|
|
1331
1435
|
[attr.aria-invalid]="errorState() || null"
|
|
1436
|
+
[attr.aria-required]="required() || null"
|
|
1332
1437
|
[attr.aria-disabled]="isDisabled() || null"
|
|
1333
1438
|
(keydown)="onFieldKeydown($event, 'hour')"
|
|
1334
1439
|
(beforeinput)="onBeforeInput($event, 'hour')"
|
|
@@ -1357,6 +1462,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1357
1462
|
[attr.aria-valuenow]="minuteValueNow()"
|
|
1358
1463
|
[attr.aria-valuetext]="minuteValueText()"
|
|
1359
1464
|
[attr.aria-invalid]="errorState() || null"
|
|
1465
|
+
[attr.aria-required]="required() || null"
|
|
1360
1466
|
[attr.aria-disabled]="isDisabled() || null"
|
|
1361
1467
|
(keydown)="onFieldKeydown($event, 'minute')"
|
|
1362
1468
|
(beforeinput)="onBeforeInput($event, 'minute')"
|
|
@@ -1386,6 +1492,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1386
1492
|
[attr.aria-valuenow]="secondValueNow()"
|
|
1387
1493
|
[attr.aria-valuetext]="secondValueText()"
|
|
1388
1494
|
[attr.aria-invalid]="errorState() || null"
|
|
1495
|
+
[attr.aria-required]="required() || null"
|
|
1389
1496
|
[attr.aria-disabled]="isDisabled() || null"
|
|
1390
1497
|
(keydown)="onFieldKeydown($event, 'second')"
|
|
1391
1498
|
(beforeinput)="onBeforeInput($event, 'second')"
|
|
@@ -1396,7 +1503,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1396
1503
|
}
|
|
1397
1504
|
</div>
|
|
1398
1505
|
|
|
1399
|
-
@if (
|
|
1506
|
+
@if (steppersVisible()) {
|
|
1400
1507
|
<div [class]="stepperGroupClasses()">
|
|
1401
1508
|
<button
|
|
1402
1509
|
type="button"
|