forty-cdk 0.2.0 → 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/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, computed, model, input, numberAttribute, booleanAttribute, output, Directive, ElementRef, effect, signal } from '@angular/core';
|
|
3
|
+
import { secondsOfDay, createDefaults, FormUiControlBase, IdGenerator, assertTimeCapable, injectDateAdapter, injectTextDirection, ListboxOverlayController, timeSentinel, injectHiddenInput, serializeISOTime, FOR_TIME_VALUE_SOURCE, registerHandle, reflectDisabled, injectModalShell, injectOverlayShell, hostId, resolveListNavigation } from 'forty-cdk/core';
|
|
4
|
+
|
|
5
|
+
function timeOfDaySeconds(adapter, d, granularity) {
|
|
6
|
+
const h = adapter.getHours(d);
|
|
7
|
+
if (granularity === 'hour') {
|
|
8
|
+
return h * 3600;
|
|
9
|
+
}
|
|
10
|
+
if (granularity === 'minute') {
|
|
11
|
+
return h * 3600 + adapter.getMinutes(d) * 60;
|
|
12
|
+
}
|
|
13
|
+
return secondsOfDay(adapter, d);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generates the full-day slot list for a {@link ForTimePicker}.
|
|
17
|
+
*
|
|
18
|
+
* Slots span the full 24-hour day (00:00:00 up to but not past 24:00). Each slot's
|
|
19
|
+
* `disabled` flag is `true` when its time-of-day falls outside `[minTime, maxTime]`
|
|
20
|
+
* — slots are never omitted, following the WAI-ARIA option convention for disabled
|
|
21
|
+
* items.
|
|
22
|
+
*
|
|
23
|
+
* Pure function: no signals, no DOM, no side effects.
|
|
24
|
+
*/
|
|
25
|
+
function buildTimeSlots(config) {
|
|
26
|
+
const { adapter, anchor, selected, minTime, maxTime, step, granularity, formatOptions } = config;
|
|
27
|
+
const stepSeconds = Math.max(1, Math.round(step)) * 60;
|
|
28
|
+
const minSec = minTime !== null ? timeOfDaySeconds(adapter, minTime, 'second') : null;
|
|
29
|
+
const maxSec = maxTime !== null ? timeOfDaySeconds(adapter, maxTime, 'second') : null;
|
|
30
|
+
const slots = [];
|
|
31
|
+
for (let t = 0; t < 86400; t += stepSeconds) {
|
|
32
|
+
const h = Math.floor(t / 3600);
|
|
33
|
+
const m = Math.floor((t % 3600) / 60);
|
|
34
|
+
const s = t % 60;
|
|
35
|
+
const value = adapter.setTime(anchor, h, m, s);
|
|
36
|
+
const label = adapter.format(value, formatOptions);
|
|
37
|
+
const slotGranularSec = timeOfDaySeconds(adapter, value, granularity);
|
|
38
|
+
const slotFullSec = h * 3600 + m * 60 + s;
|
|
39
|
+
const isSelected = selected !== null && slotGranularSec === timeOfDaySeconds(adapter, selected, granularity);
|
|
40
|
+
const disabled = (minSec !== null && slotFullSec < minSec) || (maxSec !== null && slotFullSec > maxSec);
|
|
41
|
+
slots.push({ id: `slot-${t}`, value, label, selected: isSelected, disabled });
|
|
42
|
+
}
|
|
43
|
+
return slots;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const FOR_TIME_PICKER_CONTEXT = new InjectionToken('FOR_TIME_PICKER_CONTEXT');
|
|
47
|
+
function injectTimePickerContext(piece) {
|
|
48
|
+
const ctx = inject(FOR_TIME_PICKER_CONTEXT, { optional: true });
|
|
49
|
+
if (!ctx) {
|
|
50
|
+
throw new Error(`[forty-cdk/time-picker] ${piece} must be used inside a [forTimePicker] element. ` +
|
|
51
|
+
"If it is declared inside an ng-template, DI resolves at the template's declaration site — " +
|
|
52
|
+
'not where it is stamped (e.g. via ngTemplateOutlet) — so declare the template inside the ' +
|
|
53
|
+
'[forTimePicker] root.');
|
|
54
|
+
}
|
|
55
|
+
return ctx;
|
|
56
|
+
}
|
|
57
|
+
function injectTimePickerTriggerContext(explicitRoot) {
|
|
58
|
+
const injected = inject(FOR_TIME_PICKER_CONTEXT, { optional: true });
|
|
59
|
+
return computed(() => {
|
|
60
|
+
const explicit = explicitRoot();
|
|
61
|
+
if (explicit !== '') {
|
|
62
|
+
return explicit;
|
|
63
|
+
}
|
|
64
|
+
if (injected) {
|
|
65
|
+
return injected;
|
|
66
|
+
}
|
|
67
|
+
throw new Error('[forty-cdk/time-picker] ForTimePickerTrigger could not resolve its [forTimePicker] root: ' +
|
|
68
|
+
'no FOR_TIME_PICKER_CONTEXT provider is visible and no explicit root reference was passed. ' +
|
|
69
|
+
"If this trigger is declared inside an ng-template, DI resolves at the template's declaration " +
|
|
70
|
+
'site — not where it is stamped — so either declare the template inside the root or pass the ' +
|
|
71
|
+
'root explicitly: [forTimePickerTrigger]="root" with #root="forTimePicker".');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Library fallback for time picker defaults, read at the root injector when no
|
|
77
|
+
* consumer has called `provideForTimePickerDefaults`. Exported for the shared
|
|
78
|
+
* defaults contract spec; not re-exported from the primitive's public entry.
|
|
79
|
+
*/
|
|
80
|
+
const FOR_TIME_PICKER_FALLBACK_DEFAULTS = {
|
|
81
|
+
sideOffset: 4,
|
|
82
|
+
collisionPadding: 8,
|
|
83
|
+
};
|
|
84
|
+
const { token, provideDefaults } = createDefaults('FOR_TIME_PICKER_DEFAULTS', FOR_TIME_PICKER_FALLBACK_DEFAULTS);
|
|
85
|
+
/** Token holding the resolved time picker defaults for the current scope. */
|
|
86
|
+
const FOR_TIME_PICKER_DEFAULTS = token;
|
|
87
|
+
/**
|
|
88
|
+
* Configures forty-cdk time picker defaults for this injector scope. Partial
|
|
89
|
+
* overrides inherit unspecified keys from the parent scope (or library defaults
|
|
90
|
+
* at the root).
|
|
91
|
+
*/
|
|
92
|
+
function provideForTimePickerDefaults(defaults = {}) {
|
|
93
|
+
return provideDefaults(defaults);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Root of the headless time picker, implementing the
|
|
98
|
+
* [WAI-ARIA Listbox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/)
|
|
99
|
+
* for a slot-based time selection. Generates a full-day list of time slots at
|
|
100
|
+
* a configurable `step` (in minutes), displayed in a floating listbox.
|
|
101
|
+
*
|
|
102
|
+
* Implements `FormValueControl<D | null>` from `@angular/forms/signals` for
|
|
103
|
+
* `[formField]` auto-wiring. Requires a time-capable adapter
|
|
104
|
+
* (`provideNativeDateAdapter()` or `provideInternationalizedDateTimeAdapter()`).
|
|
105
|
+
*
|
|
106
|
+
* @typeParam D The adapter's immutable date-time type.
|
|
107
|
+
*/
|
|
108
|
+
class ForTimePicker extends FormUiControlBase {
|
|
109
|
+
#idGen = inject(IdGenerator);
|
|
110
|
+
#defaults = inject(FOR_TIME_PICKER_DEFAULTS);
|
|
111
|
+
/**
|
|
112
|
+
* The active, time-capable date adapter. Throws when the provided adapter is
|
|
113
|
+
* day-only.
|
|
114
|
+
*/
|
|
115
|
+
adapter = assertTimeCapable(injectDateAdapter('ForTimePicker'), 'ForTimePicker');
|
|
116
|
+
/**
|
|
117
|
+
* Two-way bindable selected time, or `null`. Required by
|
|
118
|
+
* `FormValueControl<D | null>`. The `model()` change emitter
|
|
119
|
+
* (`(valueChange)`) fires only on internal selection changes, never on
|
|
120
|
+
* consumer writes via `[(value)]`.
|
|
121
|
+
*/
|
|
122
|
+
value = model(null, /* @ts-ignore */
|
|
123
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
124
|
+
/**
|
|
125
|
+
* Two-way bindable. Whether the listbox is currently shown. The `model()`
|
|
126
|
+
* change emitter (`(openChange)`) fires only on internal transitions, never
|
|
127
|
+
* on consumer writes via `[(open)]`.
|
|
128
|
+
*/
|
|
129
|
+
open = model(false, /* @ts-ignore */
|
|
130
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
131
|
+
/**
|
|
132
|
+
* Earliest selectable time-of-day (inclusive). Slots earlier in the day are
|
|
133
|
+
* disabled. Named `minTime` (not `min`) because `FormUiControl.min` is
|
|
134
|
+
* reserved for a numeric validator.
|
|
135
|
+
*/
|
|
136
|
+
minTime = input(null, /* @ts-ignore */
|
|
137
|
+
...(ngDevMode ? [{ debugName: "minTime" }] : /* istanbul ignore next */ []));
|
|
138
|
+
/**
|
|
139
|
+
* Latest selectable time-of-day (inclusive). Slots later in the day are
|
|
140
|
+
* disabled. Named `maxTime` for the same reason as {@link minTime}.
|
|
141
|
+
*/
|
|
142
|
+
maxTime = input(null, /* @ts-ignore */
|
|
143
|
+
...(ngDevMode ? [{ debugName: "maxTime" }] : /* istanbul ignore next */ []));
|
|
144
|
+
/**
|
|
145
|
+
* Slot interval in whole minutes. Default `30`. Clamped to ≥ 1 internally.
|
|
146
|
+
* Use `60` for hourly slots, `15` for quarter-hour slots.
|
|
147
|
+
*/
|
|
148
|
+
step = input(30, { ...(ngDevMode ? { debugName: "step" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
149
|
+
/**
|
|
150
|
+
* Finest time unit compared when determining if a slot is selected.
|
|
151
|
+
* Default `'minute'`. Use `'second'` when the value carries seconds.
|
|
152
|
+
*/
|
|
153
|
+
granularity = input('minute', /* @ts-ignore */
|
|
154
|
+
...(ngDevMode ? [{ debugName: "granularity" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/**
|
|
156
|
+
* 12- or 24-hour cycle for slot label formatting. When `null` (default) it
|
|
157
|
+
* is derived from the runtime locale.
|
|
158
|
+
*/
|
|
159
|
+
hourCycle = input(null, /* @ts-ignore */
|
|
160
|
+
...(ngDevMode ? [{ debugName: "hourCycle" }] : /* istanbul ignore next */ []));
|
|
161
|
+
/**
|
|
162
|
+
* BCP 47 locale driving slot label formatting. Defaults to the runtime locale.
|
|
163
|
+
*/
|
|
164
|
+
locale = input(null, /* @ts-ignore */
|
|
165
|
+
...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
166
|
+
/**
|
|
167
|
+
* Close the listbox after a slot is selected. Default `true`.
|
|
168
|
+
*/
|
|
169
|
+
closeOnSelect = input(true, { ...(ngDevMode ? { debugName: "closeOnSelect" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
170
|
+
/**
|
|
171
|
+
* When `true`, the listbox mounts as a trapped / inert / scroll-locked modal
|
|
172
|
+
* surface instead of the default anchored popover. Read once when the content
|
|
173
|
+
* mounts.
|
|
174
|
+
*/
|
|
175
|
+
modal = input(false, { ...(ngDevMode ? { debugName: "modal" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
176
|
+
/** When `true` (default), Escape, pointer-down outside, and focus outside close the listbox. */
|
|
177
|
+
dismissible = input(true, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
178
|
+
/** When `true` (default), focus returns to the trigger on close. */
|
|
179
|
+
returnFocus = input(true, { ...(ngDevMode ? { debugName: "returnFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
180
|
+
/** Placeholder shown by `[forTimePickerValue]` when no time is selected. */
|
|
181
|
+
placeholder = input('', /* @ts-ignore */
|
|
182
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
183
|
+
/**
|
|
184
|
+
* `Intl.DateTimeFormat` options driving slot label formatting. When none of
|
|
185
|
+
* `hour` / `minute` / `second` are specified, sensible defaults are filled in
|
|
186
|
+
* automatically based on `granularity` and `hourCycle`.
|
|
187
|
+
*/
|
|
188
|
+
formatOptions = input({}, /* @ts-ignore */
|
|
189
|
+
...(ngDevMode ? [{ debugName: "formatOptions" }] : /* istanbul ignore next */ []));
|
|
190
|
+
/** Manual `aria-label` on `[forTimePickerContent]` when the trigger isn't a meaningful name. */
|
|
191
|
+
ariaLabel = input(null, /* @ts-ignore */
|
|
192
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
193
|
+
/** When `true` (default), keyboard navigation wraps at the ends of the option list. */
|
|
194
|
+
loop = input(true, { ...(ngDevMode ? { debugName: "loop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
195
|
+
/** Orientation of the listbox for keyboard navigation. Default `'vertical'`. */
|
|
196
|
+
orientation = input('vertical', /* @ts-ignore */
|
|
197
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
198
|
+
/**
|
|
199
|
+
* Side the listbox is anchored to. Defaults to `'bottom'`. Ignored in
|
|
200
|
+
* `modal` mode.
|
|
201
|
+
*/
|
|
202
|
+
side = input('bottom', /* @ts-ignore */
|
|
203
|
+
...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
204
|
+
/** Alignment along the chosen `side`. Defaults to `'start'`. */
|
|
205
|
+
align = input('start', /* @ts-ignore */
|
|
206
|
+
...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
207
|
+
/**
|
|
208
|
+
* Gap (px) between trigger and listbox along the main axis. Default `4`.
|
|
209
|
+
* The default is read from `provideForTimePickerDefaults` for the surrounding
|
|
210
|
+
* scope.
|
|
211
|
+
*/
|
|
212
|
+
sideOffset = input(this.#defaults.sideOffset, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
213
|
+
/** Gap (px) along the cross axis. Default `0`. */
|
|
214
|
+
alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
215
|
+
/** When `true` (default), `flip` and `shift` keep the listbox inside the viewport. */
|
|
216
|
+
avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
217
|
+
/**
|
|
218
|
+
* Padding (px) applied uniformly to flip / shift / size. Default `8`.
|
|
219
|
+
* The default is read from `provideForTimePickerDefaults` for the surrounding
|
|
220
|
+
* scope.
|
|
221
|
+
*/
|
|
222
|
+
collisionPadding = input(this.#defaults.collisionPadding, { ...(ngDevMode ? { debugName: "collisionPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
223
|
+
/** Padding (px) for the `arrow` middleware. Default `0`. */
|
|
224
|
+
arrowPadding = input(0, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
225
|
+
/** Stickiness behaviour for `shift`. Default `'partial'`. */
|
|
226
|
+
sticky = input('partial', /* @ts-ignore */
|
|
227
|
+
...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
|
|
228
|
+
/** When `true`, sets `data-detached=""` while the trigger is scrolled off-screen. */
|
|
229
|
+
hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
230
|
+
/**
|
|
231
|
+
* When `true` (default), the content is clipped until floating-ui resolves
|
|
232
|
+
* its first position, preventing a flash at the viewport corner.
|
|
233
|
+
*/
|
|
234
|
+
clipUntilPositioned = input(true, { ...(ngDevMode ? { debugName: "clipUntilPositioned" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
235
|
+
/**
|
|
236
|
+
* Writing direction. When unset (default `null`), the inherited ambient
|
|
237
|
+
* direction is resolved from the nearest ancestor carrying a `dir` attribute
|
|
238
|
+
* (or `<html dir>`), defaulting to `'ltr'`. An explicit `[dir]` always wins.
|
|
239
|
+
*/
|
|
240
|
+
_dirInput = input(null, { ...(ngDevMode ? { debugName: "_dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
241
|
+
dir = injectTextDirection(this._dirInput);
|
|
242
|
+
/**
|
|
243
|
+
* Fires when the user presses Escape while this listbox is the topmost
|
|
244
|
+
* dismissable layer. Call `preventDefault()` on the veto to suppress the
|
|
245
|
+
* automatic close.
|
|
246
|
+
*/
|
|
247
|
+
escapeKeyDown = output();
|
|
248
|
+
/** Fires when a pointer goes down outside the listbox. Vetoable. */
|
|
249
|
+
pointerDownOutside = output();
|
|
250
|
+
/** Fires when focus moves outside the listbox. Vetoable. */
|
|
251
|
+
focusOutside = output();
|
|
252
|
+
/** Composite event: shares veto state with `pointerDownOutside` / `focusOutside`. */
|
|
253
|
+
interactOutside = output();
|
|
254
|
+
/**
|
|
255
|
+
* Fires just before the listbox sends focus to the selected slot (or first
|
|
256
|
+
* enabled) on open. Call `preventDefault()` on the veto to skip the focus
|
|
257
|
+
* move.
|
|
258
|
+
*/
|
|
259
|
+
autoFocusOnOpen = output();
|
|
260
|
+
/**
|
|
261
|
+
* Fires just before focus returns to the trigger on close. Call
|
|
262
|
+
* `preventDefault()` on the veto to suppress the return-focus.
|
|
263
|
+
*/
|
|
264
|
+
autoFocusOnClose = output();
|
|
265
|
+
/**
|
|
266
|
+
* Shared overlay-listbox state machine: option collection, trigger / anchor /
|
|
267
|
+
* content registries + ids, DOM-focus navigation, the open / close machine,
|
|
268
|
+
* the initial-focus / close-reason state, and the dismiss / auto-focus emit
|
|
269
|
+
* forwarders. The value-specific behaviour (`isSelected`, `activate`,
|
|
270
|
+
* `focusSelectedOption`, `commitOnTab`) stays in this root.
|
|
271
|
+
*/
|
|
272
|
+
#controller = new ListboxOverlayController(this.#idGen, {
|
|
273
|
+
idPrefix: 'for-time-picker',
|
|
274
|
+
multipleAnchorsError: '[forty-cdk/time-picker] Multiple [forTimePickerAnchor] inside the same [forTimePicker]; only one is allowed.',
|
|
275
|
+
defaultInitialFocus: 'selected',
|
|
276
|
+
effectiveDisabled: this.effectiveDisabled,
|
|
277
|
+
setOpen: (open) => this.open.set(open),
|
|
278
|
+
isOpen: () => this.open(),
|
|
279
|
+
emit: {
|
|
280
|
+
escapeKeyDown: this.escapeKeyDown,
|
|
281
|
+
pointerDownOutside: this.pointerDownOutside,
|
|
282
|
+
focusOutside: this.focusOutside,
|
|
283
|
+
interactOutside: this.interactOutside,
|
|
284
|
+
autoFocusOnOpen: this.autoFocusOnOpen,
|
|
285
|
+
autoFocusOnClose: this.autoFocusOnClose,
|
|
286
|
+
},
|
|
287
|
+
markTouched: () => this.markTouched(),
|
|
288
|
+
});
|
|
289
|
+
triggerId = this.#controller.triggerId;
|
|
290
|
+
contentId = this.#controller.contentId;
|
|
291
|
+
initialFocus = this.#controller.initialFocus;
|
|
292
|
+
lastCloseReason = this.#controller.lastCloseReason;
|
|
293
|
+
trigger = this.#controller.trigger;
|
|
294
|
+
/**
|
|
295
|
+
* Element floating-ui anchors the listbox against. Prefers an optional
|
|
296
|
+
* `[forTimePickerAnchor]` when registered, otherwise falls back to the
|
|
297
|
+
* trigger so existing pickers without an anchor keep their behavior.
|
|
298
|
+
* Decoupled from `trigger` so the trigger keeps driving `aria-controls`, the
|
|
299
|
+
* click toggle, focus return, and its dismissal exemption regardless of where
|
|
300
|
+
* the listbox paints.
|
|
301
|
+
*/
|
|
302
|
+
anchor = this.#controller.anchor;
|
|
303
|
+
content = this.#controller.content;
|
|
304
|
+
options = this.#controller.options;
|
|
305
|
+
#sentinel = computed(() => timeSentinel(this.adapter), /* @ts-ignore */
|
|
306
|
+
...(ngDevMode ? [{ debugName: "#sentinel" }] : /* istanbul ignore next */ []));
|
|
307
|
+
#effectiveFormatOptions = computed(() => {
|
|
308
|
+
const options = this.formatOptions();
|
|
309
|
+
if (options.hour !== undefined ||
|
|
310
|
+
options.minute !== undefined ||
|
|
311
|
+
options.second !== undefined) {
|
|
312
|
+
return options;
|
|
313
|
+
}
|
|
314
|
+
const cycle = this.hourCycle();
|
|
315
|
+
const granularity = this.granularity();
|
|
316
|
+
return {
|
|
317
|
+
...options,
|
|
318
|
+
hour: 'numeric',
|
|
319
|
+
minute: '2-digit',
|
|
320
|
+
...(granularity === 'second' ? { second: '2-digit' } : {}),
|
|
321
|
+
...(cycle !== null ? { hour12: cycle === 12 } : {}),
|
|
322
|
+
};
|
|
323
|
+
}, /* @ts-ignore */
|
|
324
|
+
...(ngDevMode ? [{ debugName: "#effectiveFormatOptions" }] : /* istanbul ignore next */ []));
|
|
325
|
+
/**
|
|
326
|
+
* The generated list of time slots for the full day. Each slot's `value` is
|
|
327
|
+
* anchored to the current `value()` date (or a sentinel), so selecting a
|
|
328
|
+
* slot preserves the calendar date when used inside a date-time picker.
|
|
329
|
+
*
|
|
330
|
+
* Pure derivation — never written from inside an `effect()`.
|
|
331
|
+
*/
|
|
332
|
+
slots = computed(() => {
|
|
333
|
+
const anchor = this.value() ?? this.#sentinel();
|
|
334
|
+
return buildTimeSlots({
|
|
335
|
+
adapter: this.adapter,
|
|
336
|
+
anchor,
|
|
337
|
+
selected: this.value(),
|
|
338
|
+
minTime: this.minTime(),
|
|
339
|
+
maxTime: this.maxTime(),
|
|
340
|
+
step: this.step(),
|
|
341
|
+
granularity: this.granularity(),
|
|
342
|
+
formatOptions: this.#effectiveFormatOptions(),
|
|
343
|
+
});
|
|
344
|
+
}, /* @ts-ignore */
|
|
345
|
+
...(ngDevMode ? [{ debugName: "slots" }] : /* istanbul ignore next */ []));
|
|
346
|
+
/** Formatted display of the current value, or `null` when empty. */
|
|
347
|
+
formattedValue = computed(() => {
|
|
348
|
+
const v = this.value();
|
|
349
|
+
return v === null ? null : this.adapter.format(v, this.#effectiveFormatOptions());
|
|
350
|
+
}, /* @ts-ignore */
|
|
351
|
+
...(ngDevMode ? [{ debugName: "formattedValue" }] : /* istanbul ignore next */ []));
|
|
352
|
+
fieldLabelledElement() {
|
|
353
|
+
return this.trigger();
|
|
354
|
+
}
|
|
355
|
+
fieldLabelledElementId() {
|
|
356
|
+
return this.triggerId();
|
|
357
|
+
}
|
|
358
|
+
constructor() {
|
|
359
|
+
super();
|
|
360
|
+
injectHiddenInput({
|
|
361
|
+
name: this.name,
|
|
362
|
+
values: computed(() => {
|
|
363
|
+
const current = this.value();
|
|
364
|
+
if (current === null) {
|
|
365
|
+
return [];
|
|
366
|
+
}
|
|
367
|
+
return [serializeISOTime(this.adapter, current, this.granularity())];
|
|
368
|
+
}),
|
|
369
|
+
disabled: this.effectiveDisabled,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
setInitialFocus(target) {
|
|
373
|
+
this.#controller.setInitialFocus(target);
|
|
374
|
+
}
|
|
375
|
+
registerTrigger(el) {
|
|
376
|
+
this.#controller.registerTrigger(el);
|
|
377
|
+
}
|
|
378
|
+
unregisterTrigger(el) {
|
|
379
|
+
this.#controller.unregisterTrigger(el);
|
|
380
|
+
}
|
|
381
|
+
registerAnchor(el) {
|
|
382
|
+
this.#controller.registerAnchor(el);
|
|
383
|
+
}
|
|
384
|
+
unregisterAnchor(el) {
|
|
385
|
+
this.#controller.unregisterAnchor(el);
|
|
386
|
+
}
|
|
387
|
+
registerContent(el) {
|
|
388
|
+
this.#controller.registerContent(el);
|
|
389
|
+
}
|
|
390
|
+
unregisterContent(el) {
|
|
391
|
+
this.#controller.unregisterContent(el);
|
|
392
|
+
}
|
|
393
|
+
registerOption(handle) {
|
|
394
|
+
this.#controller.registerOption(handle);
|
|
395
|
+
}
|
|
396
|
+
unregisterOption(handle) {
|
|
397
|
+
this.#controller.unregisterOption(handle);
|
|
398
|
+
}
|
|
399
|
+
#sameTimeOfDay(a, b) {
|
|
400
|
+
const g = this.granularity();
|
|
401
|
+
const secA = this.adapter.getHours(a) * 3600 +
|
|
402
|
+
(g !== 'hour' ? this.adapter.getMinutes(a) * 60 : 0) +
|
|
403
|
+
(g === 'second' ? this.adapter.getSeconds(a) : 0);
|
|
404
|
+
const secB = this.adapter.getHours(b) * 3600 +
|
|
405
|
+
(g !== 'hour' ? this.adapter.getMinutes(b) * 60 : 0) +
|
|
406
|
+
(g === 'second' ? this.adapter.getSeconds(b) : 0);
|
|
407
|
+
return secA === secB;
|
|
408
|
+
}
|
|
409
|
+
isSelected(v) {
|
|
410
|
+
const current = this.value();
|
|
411
|
+
if (current === null) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
return this.#sameTimeOfDay(v, current);
|
|
415
|
+
}
|
|
416
|
+
activate(v) {
|
|
417
|
+
if (this.effectiveDisabled() || this.readonly()) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
this.value.set(v);
|
|
421
|
+
if (this.closeOnSelect()) {
|
|
422
|
+
this.closeMenu('select');
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
navigate(currentOption, action) {
|
|
426
|
+
this.#controller.navigate(currentOption, action, this.loop());
|
|
427
|
+
}
|
|
428
|
+
focusFirstEnabledOption() {
|
|
429
|
+
return this.#controller.focusFirstEnabledOption();
|
|
430
|
+
}
|
|
431
|
+
focusLastEnabledOption() {
|
|
432
|
+
return this.#controller.focusLastEnabledOption();
|
|
433
|
+
}
|
|
434
|
+
focusSelectedOption() {
|
|
435
|
+
const current = this.value();
|
|
436
|
+
if (current === null) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
const items = this.options();
|
|
440
|
+
const opt = items.find((o) => {
|
|
441
|
+
if (o.disabled()) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const v = o.value();
|
|
445
|
+
return this.#sameTimeOfDay(v, current);
|
|
446
|
+
});
|
|
447
|
+
if (opt) {
|
|
448
|
+
opt.host.focus();
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
toggle(initialFocus = 'selected') {
|
|
454
|
+
this.#controller.toggle(initialFocus);
|
|
455
|
+
}
|
|
456
|
+
openMenu(initialFocus = 'selected') {
|
|
457
|
+
this.#controller.openMenu(initialFocus);
|
|
458
|
+
}
|
|
459
|
+
closeMenu(reason) {
|
|
460
|
+
this.#controller.closeMenu(reason);
|
|
461
|
+
}
|
|
462
|
+
commitOnTab(value) {
|
|
463
|
+
if (this.effectiveDisabled()) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (!this.readonly()) {
|
|
467
|
+
this.value.set(value);
|
|
468
|
+
}
|
|
469
|
+
this.#controller.focusTrigger();
|
|
470
|
+
this.closeMenu('tab');
|
|
471
|
+
}
|
|
472
|
+
emitEscapeKeyDown(event) {
|
|
473
|
+
this.#controller.emitEscapeKeyDown(event, this.dismissible(), 'escape');
|
|
474
|
+
}
|
|
475
|
+
emitPointerDownOutside(veto) {
|
|
476
|
+
this.#controller.emitPointerDownOutside(veto);
|
|
477
|
+
}
|
|
478
|
+
emitFocusOutside(veto) {
|
|
479
|
+
this.#controller.emitFocusOutside(veto);
|
|
480
|
+
}
|
|
481
|
+
emitInteractOutside(veto) {
|
|
482
|
+
this.#controller.emitInteractOutside(veto);
|
|
483
|
+
}
|
|
484
|
+
forwardEscapeKeyDown(veto) {
|
|
485
|
+
this.#controller.forwardEscapeKeyDown(veto);
|
|
486
|
+
}
|
|
487
|
+
requestClose(reason) {
|
|
488
|
+
this.#controller.requestClose(reason);
|
|
489
|
+
}
|
|
490
|
+
emitAutoFocusOnOpen() {
|
|
491
|
+
return this.#controller.emitAutoFocusOnOpen();
|
|
492
|
+
}
|
|
493
|
+
emitAutoFocusOnClose() {
|
|
494
|
+
return this.#controller.emitAutoFocusOnClose();
|
|
495
|
+
}
|
|
496
|
+
markTouched() {
|
|
497
|
+
super.markTouched();
|
|
498
|
+
}
|
|
499
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePicker, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
500
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTimePicker, isStandalone: true, selector: "[forTimePicker]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", 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 }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, granularity: { classPropertyName: "granularity", publicName: "granularity", isSignal: true, isRequired: false, transformFunction: null }, hourCycle: { classPropertyName: "hourCycle", publicName: "hourCycle", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, returnFocus: { classPropertyName: "returnFocus", publicName: "returnFocus", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, formatOptions: { classPropertyName: "formatOptions", publicName: "formatOptions", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, clipUntilPositioned: { classPropertyName: "clipUntilPositioned", publicName: "clipUntilPositioned", isSignal: true, isRequired: false, transformFunction: null }, _dirInput: { classPropertyName: "_dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside", interactOutside: "interactOutside", autoFocusOnOpen: "autoFocusOnOpen", autoFocusOnClose: "autoFocusOnClose" }, host: { properties: { "attr.data-state": "open() ? \"open\" : \"closed\"", "attr.data-disabled": "effectiveDisabled() ? \"\" : null", "attr.dir": "dir()" } }, providers: [
|
|
501
|
+
{ provide: FOR_TIME_PICKER_CONTEXT, useExisting: ForTimePicker },
|
|
502
|
+
{ provide: FOR_TIME_VALUE_SOURCE, useExisting: ForTimePicker },
|
|
503
|
+
], exportAs: ["forTimePicker"], usesInheritance: true, ngImport: i0 });
|
|
504
|
+
}
|
|
505
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePicker, decorators: [{
|
|
506
|
+
type: Directive,
|
|
507
|
+
args: [{
|
|
508
|
+
selector: '[forTimePicker]',
|
|
509
|
+
exportAs: 'forTimePicker',
|
|
510
|
+
host: {
|
|
511
|
+
'[attr.data-state]': 'open() ? "open" : "closed"',
|
|
512
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : null',
|
|
513
|
+
'[attr.dir]': 'dir()',
|
|
514
|
+
},
|
|
515
|
+
providers: [
|
|
516
|
+
{ provide: FOR_TIME_PICKER_CONTEXT, useExisting: ForTimePicker },
|
|
517
|
+
{ provide: FOR_TIME_VALUE_SOURCE, useExisting: ForTimePicker },
|
|
518
|
+
],
|
|
519
|
+
}]
|
|
520
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], minTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTime", required: false }] }], maxTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTime", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], granularity: [{ type: i0.Input, args: [{ isSignal: true, alias: "granularity", required: false }] }], hourCycle: [{ type: i0.Input, args: [{ isSignal: true, alias: "hourCycle", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], returnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnFocus", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], formatOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatOptions", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], sideOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], collisionPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], clipUntilPositioned: [{ type: i0.Input, args: [{ isSignal: true, alias: "clipUntilPositioned", required: false }] }], _dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }], focusOutside: [{ type: i0.Output, args: ["focusOutside"] }], interactOutside: [{ type: i0.Output, args: ["interactOutside"] }], autoFocusOnOpen: [{ type: i0.Output, args: ["autoFocusOnOpen"] }], autoFocusOnClose: [{ type: i0.Output, args: ["autoFocusOnClose"] }] } });
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Optional positioning anchor. When present, `[forTimePickerContent]` is
|
|
524
|
+
* positioned against this element instead of `[forTimePickerTrigger]` — useful
|
|
525
|
+
* when the trigger lives inside a decorated field box (padding, prefix icon,
|
|
526
|
+
* clear / chevron buttons) and the listbox should align to the visible field
|
|
527
|
+
* rather than the inner button.
|
|
528
|
+
*
|
|
529
|
+
* Only positioning changes: the trigger still owns `aria-controls`,
|
|
530
|
+
* `aria-expanded`, the click toggle, focus return on close, and its exemption
|
|
531
|
+
* from outside-pointer dismissal. If no anchor is registered the listbox falls
|
|
532
|
+
* back to anchoring against the trigger, so existing usages are unaffected.
|
|
533
|
+
*
|
|
534
|
+
* At most one `[forTimePickerAnchor]` may be registered per `[forTimePicker]`;
|
|
535
|
+
* a second one throws.
|
|
536
|
+
*
|
|
537
|
+
* ```html
|
|
538
|
+
* <div forTimePicker [(value)]="time">
|
|
539
|
+
* <div forTimePickerAnchor class="field-box">
|
|
540
|
+
* <icon />
|
|
541
|
+
* <button forTimePickerTrigger><span forTimePickerValue>…</span></button>
|
|
542
|
+
* <button class="clear">×</button>
|
|
543
|
+
* </div>
|
|
544
|
+
* @if (open()) {
|
|
545
|
+
* <div forTimePickerContent>…</div>
|
|
546
|
+
* }
|
|
547
|
+
* </div>
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
class ForTimePickerAnchor {
|
|
551
|
+
#ctx = injectTimePickerContext('ForTimePickerAnchor');
|
|
552
|
+
#host = inject(ElementRef);
|
|
553
|
+
constructor() {
|
|
554
|
+
registerHandle(this.#host.nativeElement, (el) => this.#ctx.registerAnchor(el), (el) => this.#ctx.unregisterAnchor(el));
|
|
555
|
+
}
|
|
556
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerAnchor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
557
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTimePickerAnchor, isStandalone: true, selector: "[forTimePickerAnchor]", exportAs: ["forTimePickerAnchor"], ngImport: i0 });
|
|
558
|
+
}
|
|
559
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerAnchor, decorators: [{
|
|
560
|
+
type: Directive,
|
|
561
|
+
args: [{
|
|
562
|
+
selector: '[forTimePickerAnchor]',
|
|
563
|
+
exportAs: 'forTimePickerAnchor',
|
|
564
|
+
}]
|
|
565
|
+
}], ctorParameters: () => [] });
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Combobox button that opens the time picker listbox. Apply on a `<button>` so
|
|
569
|
+
* Space / Enter dispatch native click events that toggle via `(click)`.
|
|
570
|
+
*
|
|
571
|
+
* Wires APG select-only combobox attributes: `role="combobox"`,
|
|
572
|
+
* `aria-haspopup="listbox"`, `aria-expanded`, and `aria-controls` pointing to
|
|
573
|
+
* the listbox. The button is exempt from the listbox's dismissable layer so
|
|
574
|
+
* clicks on it toggle without dismissal racing.
|
|
575
|
+
*
|
|
576
|
+
* Keyboard:
|
|
577
|
+
* - **Click / Enter / Space** — toggle (open focuses the selected slot, or first).
|
|
578
|
+
* - **ArrowDown** — open + focus selected slot (or first).
|
|
579
|
+
* - **ArrowUp** — open + focus selected slot (or last when none selected).
|
|
580
|
+
*/
|
|
581
|
+
class ForTimePickerTrigger {
|
|
582
|
+
#host = inject(ElementRef);
|
|
583
|
+
/**
|
|
584
|
+
* Optional explicit reference to the `[forTimePicker]` root, named after the
|
|
585
|
+
* selector `routerLink`-style. The bare valueless attribute keeps resolving
|
|
586
|
+
* the enclosing root via DI; pass the root explicitly
|
|
587
|
+
* (`[forTimePickerTrigger]="root"`, with `#root="forTimePicker"`) when the
|
|
588
|
+
* trigger is declared in an `ng-template` stamped inside the root.
|
|
589
|
+
*/
|
|
590
|
+
forTimePickerTrigger = input('', /* @ts-ignore */
|
|
591
|
+
...(ngDevMode ? [{ debugName: "forTimePickerTrigger" }] : /* istanbul ignore next */ []));
|
|
592
|
+
ctx = injectTimePickerTriggerContext(this.forTimePickerTrigger);
|
|
593
|
+
constructor() {
|
|
594
|
+
const el = this.#host.nativeElement;
|
|
595
|
+
effect((onCleanup) => {
|
|
596
|
+
const ctx = this.ctx();
|
|
597
|
+
ctx.registerTrigger(el);
|
|
598
|
+
onCleanup(() => ctx.unregisterTrigger(el));
|
|
599
|
+
});
|
|
600
|
+
reflectDisabled(computed(() => this.ctx().effectiveDisabled()));
|
|
601
|
+
}
|
|
602
|
+
onClick() {
|
|
603
|
+
this.ctx().toggle('selected');
|
|
604
|
+
}
|
|
605
|
+
onKeyDown(event) {
|
|
606
|
+
if (this.ctx().effectiveDisabled()) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (event.key === 'ArrowDown') {
|
|
610
|
+
event.preventDefault();
|
|
611
|
+
this.ctx().openMenu('selected');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (event.key === 'ArrowUp') {
|
|
615
|
+
event.preventDefault();
|
|
616
|
+
this.ctx().openMenu(this.ctx().value() !== null ? 'selected' : 'last');
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
onFocusOut(event) {
|
|
620
|
+
const next = event.relatedTarget;
|
|
621
|
+
if (next) {
|
|
622
|
+
const content = this.ctx().content();
|
|
623
|
+
if (content && content.contains(next)) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (this.#host.nativeElement.contains(next)) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
this.ctx().markTouched();
|
|
631
|
+
}
|
|
632
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
633
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTimePickerTrigger, isStandalone: true, selector: "[forTimePickerTrigger]", inputs: { forTimePickerTrigger: { classPropertyName: "forTimePickerTrigger", publicName: "forTimePickerTrigger", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button", "role": "combobox" }, listeners: { "click": "onClick()", "keydown": "onKeyDown($event)", "focusout": "onFocusOut($event)" }, properties: { "id": "ctx().triggerId()", "attr.aria-haspopup": "\"listbox\"", "attr.aria-expanded": "ctx().open() ? \"true\" : \"false\"", "attr.aria-controls": "ctx().open() ? ctx().contentId() : null", "attr.aria-disabled": "ctx().effectiveDisabled() ? \"true\" : null", "attr.aria-readonly": "ctx().readonly() ? \"true\" : null", "attr.aria-required": "ctx().required() ? \"true\" : null", "attr.aria-invalid": "ctx().invalid() ? \"true\" : null", "attr.aria-busy": "ctx().pending() ? \"true\" : null", "attr.data-state": "ctx().open() ? \"open\" : \"closed\"", "attr.data-disabled": "ctx().effectiveDisabled() ? \"\" : null" } }, exportAs: ["forTimePickerTrigger"], ngImport: i0 });
|
|
634
|
+
}
|
|
635
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerTrigger, decorators: [{
|
|
636
|
+
type: Directive,
|
|
637
|
+
args: [{
|
|
638
|
+
selector: '[forTimePickerTrigger]',
|
|
639
|
+
exportAs: 'forTimePickerTrigger',
|
|
640
|
+
host: {
|
|
641
|
+
type: 'button',
|
|
642
|
+
role: 'combobox',
|
|
643
|
+
'[id]': 'ctx().triggerId()',
|
|
644
|
+
'[attr.aria-haspopup]': '"listbox"',
|
|
645
|
+
'[attr.aria-expanded]': 'ctx().open() ? "true" : "false"',
|
|
646
|
+
'[attr.aria-controls]': 'ctx().open() ? ctx().contentId() : null',
|
|
647
|
+
'[attr.aria-disabled]': 'ctx().effectiveDisabled() ? "true" : null',
|
|
648
|
+
'[attr.aria-readonly]': 'ctx().readonly() ? "true" : null',
|
|
649
|
+
'[attr.aria-required]': 'ctx().required() ? "true" : null',
|
|
650
|
+
'[attr.aria-invalid]': 'ctx().invalid() ? "true" : null',
|
|
651
|
+
'[attr.aria-busy]': 'ctx().pending() ? "true" : null',
|
|
652
|
+
'[attr.data-state]': 'ctx().open() ? "open" : "closed"',
|
|
653
|
+
'[attr.data-disabled]': 'ctx().effectiveDisabled() ? "" : null',
|
|
654
|
+
'(click)': 'onClick()',
|
|
655
|
+
'(keydown)': 'onKeyDown($event)',
|
|
656
|
+
'(focusout)': 'onFocusOut($event)',
|
|
657
|
+
},
|
|
658
|
+
}]
|
|
659
|
+
}], ctorParameters: () => [], propDecorators: { forTimePickerTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "forTimePickerTrigger", required: false }] }] } });
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Renders the currently selected time slot's label — or the configured
|
|
663
|
+
* placeholder when nothing is selected — into its host element via
|
|
664
|
+
* `textContent`. Apply on a `<span>` (or any inline element) inside
|
|
665
|
+
* `[forTimePickerTrigger]`:
|
|
666
|
+
*
|
|
667
|
+
* ```html
|
|
668
|
+
* <button forTimePickerTrigger>
|
|
669
|
+
* <span forTimePickerValue placeholder="Select a time…"></span>
|
|
670
|
+
* </button>
|
|
671
|
+
* ```
|
|
672
|
+
*/
|
|
673
|
+
class ForTimePickerValue {
|
|
674
|
+
#ctx = injectTimePickerContext('ForTimePickerValue');
|
|
675
|
+
/** Text shown when nothing is selected. Falls back to `[forTimePicker][placeholder]`. */
|
|
676
|
+
placeholder = input('', /* @ts-ignore */
|
|
677
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
678
|
+
isPlaceholder = computed(() => this.#ctx.value() === null, /* @ts-ignore */
|
|
679
|
+
...(ngDevMode ? [{ debugName: "isPlaceholder" }] : /* istanbul ignore next */ []));
|
|
680
|
+
displayText = computed(() => this.#ctx.formattedValue() ?? (this.placeholder() || this.#ctx.placeholder()), /* @ts-ignore */
|
|
681
|
+
...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
|
|
682
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerValue, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
683
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTimePickerValue, isStandalone: true, selector: "[forTimePickerValue]", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "textContent": "displayText()", "attr.data-placeholder": "isPlaceholder() ? \"\" : null" } }, exportAs: ["forTimePickerValue"], ngImport: i0 });
|
|
684
|
+
}
|
|
685
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerValue, decorators: [{
|
|
686
|
+
type: Directive,
|
|
687
|
+
args: [{
|
|
688
|
+
selector: '[forTimePickerValue]',
|
|
689
|
+
exportAs: 'forTimePickerValue',
|
|
690
|
+
host: {
|
|
691
|
+
'[textContent]': 'displayText()',
|
|
692
|
+
'[attr.data-placeholder]': 'isPlaceholder() ? "" : null',
|
|
693
|
+
},
|
|
694
|
+
}]
|
|
695
|
+
}], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* The listbox surface of the time picker. Carries `role="listbox"`, is
|
|
699
|
+
* portaled to `document.body`, and is positioned by `@floating-ui/dom`
|
|
700
|
+
* against the trigger.
|
|
701
|
+
*
|
|
702
|
+
* Mount/unmount of the visible content is the consumer's responsibility —
|
|
703
|
+
* wrap with `@if (open())` so `animate.enter` / `animate.leave` fire on the
|
|
704
|
+
* natural mount cycle. While mounted, a `DismissableLayer` activates (Escape,
|
|
705
|
+
* pointer-down outside, focus outside); the trigger element is exempt from
|
|
706
|
+
* outside-pointer checks so trigger clicks toggle without dismissal racing.
|
|
707
|
+
*
|
|
708
|
+
* Initial focus is sent to the selected slot (`'selected'`), the first
|
|
709
|
+
* enabled slot (`'first'`), or the last enabled slot (`'last'`) according to
|
|
710
|
+
* the trigger's hint. On destroy, focus returns to the trigger when
|
|
711
|
+
* `returnFocus` is true.
|
|
712
|
+
*/
|
|
713
|
+
class ForTimePickerContent {
|
|
714
|
+
ctx = injectTimePickerContext('ForTimePickerContent');
|
|
715
|
+
#host = inject(ElementRef);
|
|
716
|
+
constructor() {
|
|
717
|
+
const ctx = this.ctx;
|
|
718
|
+
registerHandle(this.#host.nativeElement, (el) => ctx.registerContent(el), (el) => ctx.unregisterContent(el));
|
|
719
|
+
const focusInitial = () => {
|
|
720
|
+
const target = ctx.initialFocus();
|
|
721
|
+
if (target === 'selected') {
|
|
722
|
+
return ctx.focusSelectedOption() || ctx.focusFirstEnabledOption();
|
|
723
|
+
}
|
|
724
|
+
if (target === 'last') {
|
|
725
|
+
return ctx.focusLastEnabledOption();
|
|
726
|
+
}
|
|
727
|
+
return ctx.focusFirstEnabledOption();
|
|
728
|
+
};
|
|
729
|
+
if (ctx.modal()) {
|
|
730
|
+
injectModalShell({
|
|
731
|
+
modal: ctx.modal,
|
|
732
|
+
returnFocus: ctx.returnFocus,
|
|
733
|
+
initialFocus: {
|
|
734
|
+
move: focusInitial,
|
|
735
|
+
veto: () => ctx.emitAutoFocusOnOpen(),
|
|
736
|
+
},
|
|
737
|
+
autoFocusOnClose: () => (event) => {
|
|
738
|
+
if (ctx.emitAutoFocusOnClose()) {
|
|
739
|
+
event.preventDefault();
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
dismiss: {
|
|
743
|
+
dismissible: ctx.dismissible,
|
|
744
|
+
requestClose: (reason) => {
|
|
745
|
+
ctx.markTouched();
|
|
746
|
+
ctx.closeMenu(reason);
|
|
747
|
+
},
|
|
748
|
+
emitEscapeKeyDown: (veto) => ctx.forwardEscapeKeyDown(veto),
|
|
749
|
+
emitPointerDownOutside: (veto) => ctx.emitPointerDownOutside(veto),
|
|
750
|
+
emitFocusOutside: (veto) => ctx.emitFocusOutside(veto),
|
|
751
|
+
emitInteractOutside: (veto) => ctx.emitInteractOutside(veto),
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
injectOverlayShell({
|
|
757
|
+
positioner: {
|
|
758
|
+
kind: 'floating',
|
|
759
|
+
reference: ctx.anchor,
|
|
760
|
+
open: ctx.open,
|
|
761
|
+
side: ctx.side,
|
|
762
|
+
align: ctx.align,
|
|
763
|
+
sideOffset: ctx.sideOffset,
|
|
764
|
+
alignOffset: ctx.alignOffset,
|
|
765
|
+
avoidCollisions: ctx.avoidCollisions,
|
|
766
|
+
collisionPadding: ctx.collisionPadding,
|
|
767
|
+
arrowPadding: ctx.arrowPadding,
|
|
768
|
+
sticky: ctx.sticky,
|
|
769
|
+
hideWhenDetached: ctx.hideWhenDetached,
|
|
770
|
+
clipUntilPositioned: ctx.clipUntilPositioned,
|
|
771
|
+
},
|
|
772
|
+
dismiss: {
|
|
773
|
+
dismissible: ctx.dismissible,
|
|
774
|
+
requestClose: (reason) => {
|
|
775
|
+
ctx.markTouched();
|
|
776
|
+
ctx.closeMenu(reason);
|
|
777
|
+
},
|
|
778
|
+
emitEscapeKeyDown: (event) => ctx.emitEscapeKeyDown(event),
|
|
779
|
+
emitPointerDownOutside: (veto) => ctx.emitPointerDownOutside(veto),
|
|
780
|
+
emitFocusOutside: (veto) => ctx.emitFocusOutside(veto),
|
|
781
|
+
emitInteractOutside: (veto) => ctx.emitInteractOutside(veto),
|
|
782
|
+
exemptElements: () => {
|
|
783
|
+
const t = ctx.trigger();
|
|
784
|
+
return t ? [t] : [];
|
|
785
|
+
},
|
|
786
|
+
},
|
|
787
|
+
initialFocus: {
|
|
788
|
+
move: focusInitial,
|
|
789
|
+
veto: () => ctx.emitAutoFocusOnOpen(),
|
|
790
|
+
},
|
|
791
|
+
returnFocus: {
|
|
792
|
+
enabled: ctx.returnFocus,
|
|
793
|
+
target: () => ctx.trigger(),
|
|
794
|
+
veto: () => ctx.emitAutoFocusOnClose(),
|
|
795
|
+
skip: () => ctx.lastCloseReason() === 'tab',
|
|
796
|
+
},
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
800
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForTimePickerContent, isStandalone: true, selector: "[forTimePickerContent]", host: { attributes: { "role": "listbox", "tabindex": "-1" }, properties: { "id": "ctx.contentId()", "attr.aria-labelledby": "ctx.ariaLabel() ? null : ctx.triggerId()", "attr.aria-label": "ctx.ariaLabel()", "attr.aria-modal": "ctx.modal() ? \"true\" : null", "attr.aria-orientation": "ctx.orientation()", "attr.data-state": "ctx.open() ? \"open\" : \"closed\"", "attr.data-orientation": "ctx.orientation()" } }, exportAs: ["forTimePickerContent"], ngImport: i0 });
|
|
801
|
+
}
|
|
802
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerContent, decorators: [{
|
|
803
|
+
type: Directive,
|
|
804
|
+
args: [{
|
|
805
|
+
selector: '[forTimePickerContent]',
|
|
806
|
+
exportAs: 'forTimePickerContent',
|
|
807
|
+
host: {
|
|
808
|
+
role: 'listbox',
|
|
809
|
+
tabindex: '-1',
|
|
810
|
+
'[id]': 'ctx.contentId()',
|
|
811
|
+
'[attr.aria-labelledby]': 'ctx.ariaLabel() ? null : ctx.triggerId()',
|
|
812
|
+
'[attr.aria-label]': 'ctx.ariaLabel()',
|
|
813
|
+
'[attr.aria-modal]': 'ctx.modal() ? "true" : null',
|
|
814
|
+
'[attr.aria-orientation]': 'ctx.orientation()',
|
|
815
|
+
'[attr.data-state]': 'ctx.open() ? "open" : "closed"',
|
|
816
|
+
'[attr.data-orientation]': 'ctx.orientation()',
|
|
817
|
+
},
|
|
818
|
+
}]
|
|
819
|
+
}], ctorParameters: () => [] });
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* One option inside a `[forTimePickerContent]`. Apply on a `<div>` (or any
|
|
823
|
+
* non-button element) — the option handles Enter/Space activation itself in its
|
|
824
|
+
* keydown handler because a `<div>` has no native keyboard click.
|
|
825
|
+
*
|
|
826
|
+
* @typeParam D The adapter's date-time type (inferred from `[value]`).
|
|
827
|
+
*
|
|
828
|
+
* Keyboard while focused:
|
|
829
|
+
* - **Enter / Space** — activate (select the slot).
|
|
830
|
+
* - **ArrowDown / ArrowUp / Home / End** — move focus inside the listbox.
|
|
831
|
+
* - **Tab / Shift+Tab** — commit the focused slot and let the browser advance
|
|
832
|
+
* focus to the next / previous focusable.
|
|
833
|
+
* - **Escape** — routed through the content's keydown to close the listbox.
|
|
834
|
+
*/
|
|
835
|
+
class ForTimePickerOption {
|
|
836
|
+
#ctx = injectTimePickerContext('ForTimePickerOption');
|
|
837
|
+
#host = inject(ElementRef);
|
|
838
|
+
/**
|
|
839
|
+
* The date-time value this option represents. Provided by the parent
|
|
840
|
+
* `[forTimePicker]`'s `slots()` computed.
|
|
841
|
+
*/
|
|
842
|
+
value = input.required(/* @ts-ignore */
|
|
843
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
844
|
+
/** When `true`, this option cannot be selected. */
|
|
845
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
846
|
+
id = hostId('for-time-picker-option');
|
|
847
|
+
selected = computed(() => this.#ctx.value() !== null && this.#ctx.isSelected(this.value()), /* @ts-ignore */
|
|
848
|
+
...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
|
|
849
|
+
effectiveDisabled = computed(() => this.disabled() || this.#ctx.effectiveDisabled(), /* @ts-ignore */
|
|
850
|
+
...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
851
|
+
#highlighted = signal(false, /* @ts-ignore */
|
|
852
|
+
...(ngDevMode ? [{ debugName: "#highlighted" }] : /* istanbul ignore next */ []));
|
|
853
|
+
/** True while this option has DOM focus. Reflected as `data-highlighted`. */
|
|
854
|
+
highlighted = this.#highlighted.asReadonly();
|
|
855
|
+
constructor() {
|
|
856
|
+
const handle = {
|
|
857
|
+
host: this.#host.nativeElement,
|
|
858
|
+
value: this.value,
|
|
859
|
+
disabled: this.effectiveDisabled,
|
|
860
|
+
};
|
|
861
|
+
registerHandle(handle, (h) => this.#ctx.registerOption(h), (h) => this.#ctx.unregisterOption(h));
|
|
862
|
+
}
|
|
863
|
+
onClick() {
|
|
864
|
+
if (this.effectiveDisabled() || this.#ctx.readonly()) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
this.#ctx.activate(this.value());
|
|
868
|
+
}
|
|
869
|
+
onFocus() {
|
|
870
|
+
this.#highlighted.set(true);
|
|
871
|
+
this.#host.nativeElement.scrollIntoView?.({ block: 'nearest' });
|
|
872
|
+
}
|
|
873
|
+
onBlur() {
|
|
874
|
+
this.#highlighted.set(false);
|
|
875
|
+
}
|
|
876
|
+
onKeyDown(event) {
|
|
877
|
+
if (this.effectiveDisabled()) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
881
|
+
event.preventDefault();
|
|
882
|
+
this.#ctx.activate(this.value());
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (event.key === 'Tab') {
|
|
886
|
+
if (this.#ctx.modal()) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
this.#ctx.commitOnTab(this.value());
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const action = resolveListNavigation(event, {
|
|
893
|
+
orientation: this.#ctx.orientation(),
|
|
894
|
+
dir: this.#ctx.dir(),
|
|
895
|
+
});
|
|
896
|
+
if (action) {
|
|
897
|
+
event.preventDefault();
|
|
898
|
+
this.#ctx.navigate(this.#host.nativeElement, action);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerOption, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
902
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForTimePickerOption, isStandalone: true, selector: "[forTimePickerOption]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option", "tabindex": "-1" }, listeners: { "click": "onClick()", "keydown": "onKeyDown($event)", "focus": "onFocus()", "blur": "onBlur()" }, properties: { "id": "id()", "attr.aria-selected": "selected() ? \"true\" : \"false\"", "attr.aria-disabled": "effectiveDisabled() ? \"true\" : null", "attr.data-state": "selected() ? \"checked\" : \"unchecked\"", "attr.data-disabled": "effectiveDisabled() ? \"\" : null", "attr.data-highlighted": "highlighted() ? \"\" : null" } }, exportAs: ["forTimePickerOption"], ngImport: i0 });
|
|
903
|
+
}
|
|
904
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForTimePickerOption, decorators: [{
|
|
905
|
+
type: Directive,
|
|
906
|
+
args: [{
|
|
907
|
+
selector: '[forTimePickerOption]',
|
|
908
|
+
exportAs: 'forTimePickerOption',
|
|
909
|
+
host: {
|
|
910
|
+
role: 'option',
|
|
911
|
+
tabindex: '-1',
|
|
912
|
+
'[id]': 'id()',
|
|
913
|
+
'[attr.aria-selected]': 'selected() ? "true" : "false"',
|
|
914
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? "true" : null',
|
|
915
|
+
'[attr.data-state]': 'selected() ? "checked" : "unchecked"',
|
|
916
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : null',
|
|
917
|
+
'[attr.data-highlighted]': 'highlighted() ? "" : null',
|
|
918
|
+
'(click)': 'onClick()',
|
|
919
|
+
'(keydown)': 'onKeyDown($event)',
|
|
920
|
+
'(focus)': 'onFocus()',
|
|
921
|
+
'(blur)': 'onBlur()',
|
|
922
|
+
},
|
|
923
|
+
}]
|
|
924
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Exact public names of every `ForTimePicker` input, its models included.
|
|
928
|
+
* Spread it into the `inputs` array of a `hostDirectives` entry so a wrapper
|
|
929
|
+
* component re-exposes the primitive's full surface — the Signal Forms members
|
|
930
|
+
* `[formField]` binds among them — without hand-maintaining the list. Always
|
|
931
|
+
* spread into an inline object literal so the entry is statically analyzable.
|
|
932
|
+
* An anti-drift spec fails when this list no longer matches the directive's
|
|
933
|
+
* actual API.
|
|
934
|
+
*
|
|
935
|
+
* @example
|
|
936
|
+
* ```ts
|
|
937
|
+
* @Component({
|
|
938
|
+
* selector: 'div[myTimePicker]',
|
|
939
|
+
* template: '',
|
|
940
|
+
* hostDirectives: [
|
|
941
|
+
* {
|
|
942
|
+
* directive: ForTimePicker,
|
|
943
|
+
* inputs: [...FOR_TIME_PICKER_HOST_DIRECTIVE_INPUTS],
|
|
944
|
+
* outputs: [...FOR_TIME_PICKER_HOST_DIRECTIVE_OUTPUTS],
|
|
945
|
+
* },
|
|
946
|
+
* ],
|
|
947
|
+
* })
|
|
948
|
+
* export class MyTimePicker {}
|
|
949
|
+
* ```
|
|
950
|
+
*/
|
|
951
|
+
const FOR_TIME_PICKER_HOST_DIRECTIVE_INPUTS = [
|
|
952
|
+
'value',
|
|
953
|
+
'open',
|
|
954
|
+
'align',
|
|
955
|
+
'alignOffset',
|
|
956
|
+
'ariaLabel',
|
|
957
|
+
'arrowPadding',
|
|
958
|
+
'avoidCollisions',
|
|
959
|
+
'clipUntilPositioned',
|
|
960
|
+
'closeOnSelect',
|
|
961
|
+
'collisionPadding',
|
|
962
|
+
'dir',
|
|
963
|
+
'dirty',
|
|
964
|
+
'disabled',
|
|
965
|
+
'dismissible',
|
|
966
|
+
'errors',
|
|
967
|
+
'formatOptions',
|
|
968
|
+
'granularity',
|
|
969
|
+
'hideWhenDetached',
|
|
970
|
+
'hourCycle',
|
|
971
|
+
'invalid',
|
|
972
|
+
'locale',
|
|
973
|
+
'loop',
|
|
974
|
+
'maxTime',
|
|
975
|
+
'minTime',
|
|
976
|
+
'modal',
|
|
977
|
+
'name',
|
|
978
|
+
'orientation',
|
|
979
|
+
'pending',
|
|
980
|
+
'placeholder',
|
|
981
|
+
'readonly',
|
|
982
|
+
'required',
|
|
983
|
+
'returnFocus',
|
|
984
|
+
'side',
|
|
985
|
+
'sideOffset',
|
|
986
|
+
'step',
|
|
987
|
+
'sticky',
|
|
988
|
+
'touched',
|
|
989
|
+
];
|
|
990
|
+
/**
|
|
991
|
+
* Exact public names of every `ForTimePicker` output, the Signal Forms `touch`
|
|
992
|
+
* output included. Spread it into the `outputs` array of the same
|
|
993
|
+
* `hostDirectives` entry as {@link FOR_TIME_PICKER_HOST_DIRECTIVE_INPUTS}.
|
|
994
|
+
*/
|
|
995
|
+
const FOR_TIME_PICKER_HOST_DIRECTIVE_OUTPUTS = [
|
|
996
|
+
'valueChange',
|
|
997
|
+
'openChange',
|
|
998
|
+
'escapeKeyDown',
|
|
999
|
+
'pointerDownOutside',
|
|
1000
|
+
'focusOutside',
|
|
1001
|
+
'interactOutside',
|
|
1002
|
+
'autoFocusOnOpen',
|
|
1003
|
+
'autoFocusOnClose',
|
|
1004
|
+
'touchedChange',
|
|
1005
|
+
'touch',
|
|
1006
|
+
];
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Generated bundle index. Do not edit.
|
|
1010
|
+
*/
|
|
1011
|
+
|
|
1012
|
+
export { FOR_TIME_PICKER_CONTEXT, FOR_TIME_PICKER_DEFAULTS, FOR_TIME_PICKER_HOST_DIRECTIVE_INPUTS, FOR_TIME_PICKER_HOST_DIRECTIVE_OUTPUTS, ForTimePicker, ForTimePickerAnchor, ForTimePickerContent, ForTimePickerOption, ForTimePickerTrigger, ForTimePickerValue, buildTimeSlots, provideForTimePickerDefaults };
|
|
1013
|
+
//# sourceMappingURL=forty-cdk-time-picker.mjs.map
|