stimeo-ui 0.5.0 → 0.7.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/CHANGELOG.md +178 -0
- package/dist/controllers/alert_dialog_controller.d.ts +2 -0
- package/dist/controllers/alert_dialog_controller.js +75 -4
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.d.ts +1 -3
- package/dist/controllers/aspect_ratio_controller.js +19 -11
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +2 -0
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.d.ts +36 -15
- package/dist/controllers/avatar_controller.js +237 -40
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +2 -0
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +2 -0
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +2 -0
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +12 -3
- package/dist/controllers/carousel_controller.js +85 -9
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +52 -20
- package/dist/controllers/character_counter_controller.js +338 -63
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +34 -5
- package/dist/controllers/checkbox_controller.js +136 -25
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +10 -1
- package/dist/controllers/color_picker_controller.js +35 -9
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +2 -0
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +2 -0
- package/dist/controllers/command_palette_controller.js +75 -4
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +42 -14
- package/dist/controllers/conditional_fields_controller.js +345 -51
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +2 -0
- package/dist/controllers/confirm_controller.js +75 -4
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +2 -0
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +4 -0
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +2 -0
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +3 -0
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +23 -4
- package/dist/controllers/date_range_picker_controller.js +157 -30
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.js +75 -4
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +56 -24
- package/dist/controllers/direct_upload_controller.js +201 -45
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +14 -6
- package/dist/controllers/dirty_form_controller.js +192 -29
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +2 -0
- package/dist/controllers/dismissible_controller.js +83 -18
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.js +75 -4
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.d.ts +2 -0
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +9 -1
- package/dist/controllers/file_dropzone_controller.js +26 -3
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +2 -0
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +4 -0
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +4 -3
- package/dist/controllers/focus_controller.js +75 -4
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.d.ts +50 -10
- package/dist/controllers/form_field_controller.js +280 -62
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +10 -8
- package/dist/controllers/form_validation_controller.js +208 -83
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +2 -0
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +2 -0
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +2 -2
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +5 -3
- package/dist/controllers/idle_controller.js +27 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +2 -0
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +2 -0
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +2 -0
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +2 -0
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +2 -0
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.js +5 -3
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +2 -0
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +48 -12
- package/dist/controllers/multi_select_controller.js +460 -151
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +2 -0
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +2 -0
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -6
- package/dist/controllers/number_input_controller.js +317 -51
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +3 -0
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +2 -0
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +2 -0
- package/dist/controllers/overflow_menu_controller.js +6 -1
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +2 -0
- package/dist/controllers/pagination_controller.js +35 -1
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +2 -0
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +5 -3
- package/dist/controllers/password_strength_controller.js +20 -2
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +33 -19
- package/dist/controllers/persist_controller.js +449 -120
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +3 -0
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +2 -2
- package/dist/controllers/popover_controller.js +77 -4
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +3 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +2 -0
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +3 -0
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +44 -15
- package/dist/controllers/radio_group_controller.js +540 -56
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +38 -31
- package/dist/controllers/rating_controller.js +276 -89
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +2 -0
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +2 -0
- package/dist/controllers/resizable_controller.js +33 -0
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +6 -0
- package/dist/controllers/roving_controller.js +60 -5
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +25 -11
- package/dist/controllers/scroll_area_controller.js +557 -125
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +2 -0
- package/dist/controllers/scroll_visibility_controller.js +33 -0
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +2 -0
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +54 -11
- package/dist/controllers/separator_controller.js +354 -38
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.js +83 -10
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +2 -0
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +2 -0
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +2 -0
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +2 -0
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +2 -0
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +2 -0
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +2 -0
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +2 -0
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +94 -38
- package/dist/controllers/submit_once_controller.js +399 -121
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +2 -0
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +43 -11
- package/dist/controllers/tags_input_controller.js +356 -120
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +2 -0
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +2 -0
- package/dist/controllers/theme_controller.js +8 -6
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +44 -13
- package/dist/controllers/time_picker_controller.js +296 -107
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +4 -0
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +41 -13
- package/dist/controllers/toggle_group_controller.js +378 -55
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +5 -3
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +2 -2
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +4 -3
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +2 -0
- package/dist/controllers/tree_view_controller.js +7 -4
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.js +4963 -1581
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +123 -6
- package/dist/inspector/cli.js +212 -18
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +274 -50
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +15 -15
- package/dist/inspector/manifest.json +602 -80
- package/dist/positioning/index.d.ts +4 -2
- package/dist/positioning/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -18,9 +18,46 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
18
18
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// src/utils/microtask_coalescer.ts
|
|
22
|
+
var MicrotaskCoalescer = class {
|
|
23
|
+
#run;
|
|
24
|
+
#queued = false;
|
|
25
|
+
#active = false;
|
|
26
|
+
#generation = 0;
|
|
27
|
+
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
28
|
+
constructor(run) {
|
|
29
|
+
this.#run = run;
|
|
30
|
+
}
|
|
31
|
+
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
32
|
+
activate() {
|
|
33
|
+
this.#active = true;
|
|
34
|
+
}
|
|
35
|
+
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
36
|
+
cancel() {
|
|
37
|
+
this.#active = false;
|
|
38
|
+
this.#queued = false;
|
|
39
|
+
this.#generation += 1;
|
|
40
|
+
}
|
|
41
|
+
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
42
|
+
schedule() {
|
|
43
|
+
if (!this.#active || this.#queued) return;
|
|
44
|
+
this.#queued = true;
|
|
45
|
+
const generation = this.#generation;
|
|
46
|
+
queueMicrotask(() => {
|
|
47
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
48
|
+
this.#queued = false;
|
|
49
|
+
this.#run();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
21
54
|
// src/controllers/time_picker_controller.ts
|
|
22
55
|
var AM = 0;
|
|
23
56
|
var PM = 1;
|
|
57
|
+
var SECONDS_PER_MINUTE = 60;
|
|
58
|
+
var SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
|
|
59
|
+
var SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
|
|
60
|
+
var hasModifier = (event) => event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
24
61
|
var TimePickerController = class extends Controller {
|
|
25
62
|
static targets = ["segment", "field"];
|
|
26
63
|
static values = {
|
|
@@ -30,35 +67,80 @@ var TimePickerController = class extends Controller {
|
|
|
30
67
|
wrap: { type: Boolean, default: true }
|
|
31
68
|
};
|
|
32
69
|
static actions = ["onKeydown"];
|
|
33
|
-
static events = ["change"];
|
|
34
|
-
/**
|
|
35
|
-
#
|
|
70
|
+
static events = ["change", "reconcile"];
|
|
71
|
+
/** Collapses target, Value, and retained-attribute morphs into one silent render. */
|
|
72
|
+
#reconcile = new MicrotaskCoalescer(() => this.#reconcileDom());
|
|
73
|
+
/** Canonical state; the displayed hour and meridiem are derived from this value. */
|
|
74
|
+
#state = { hour: 0, minute: 0, second: 0 };
|
|
36
75
|
/** Direct-entry digit buffer and the segment it belongs to. */
|
|
37
76
|
#typeBuffer = "";
|
|
38
77
|
#typeSegment = null;
|
|
39
|
-
/** Last composed
|
|
78
|
+
/** Last composed value, used to suppress duplicate user notifications. */
|
|
40
79
|
#lastValue = "";
|
|
41
|
-
|
|
80
|
+
#observer = null;
|
|
81
|
+
#connected = false;
|
|
82
|
+
#domDirty = false;
|
|
83
|
+
/** Seeds state from the DOM, renders canonical ARIA, and starts morph observation. */
|
|
42
84
|
connect() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
85
|
+
this.#connected = true;
|
|
86
|
+
this.#reconcile.activate();
|
|
87
|
+
this.element.addEventListener("focusout", this.#onFocusOut);
|
|
88
|
+
this.#domDirty = false;
|
|
89
|
+
this.#adoptDomState();
|
|
90
|
+
this.#render();
|
|
91
|
+
this.#observeMutations();
|
|
92
|
+
}
|
|
93
|
+
/** Releases the observer, listener, queued reconciliation, and transient input state. */
|
|
94
|
+
disconnect() {
|
|
95
|
+
this.#connected = false;
|
|
96
|
+
this.#reconcile.cancel();
|
|
97
|
+
this.element.removeEventListener("focusout", this.#onFocusOut);
|
|
98
|
+
this.#observer?.disconnect();
|
|
99
|
+
this.#observer = null;
|
|
100
|
+
this.#domDirty = false;
|
|
101
|
+
this.#clearTypeBuffer();
|
|
102
|
+
this.#lastValue = "";
|
|
103
|
+
}
|
|
104
|
+
/** Re-renders the canonical instant when 12/24-hour presentation changes at runtime. */
|
|
105
|
+
hourCycleValueChanged() {
|
|
106
|
+
this.#reconcile.schedule();
|
|
107
|
+
}
|
|
108
|
+
/** Recomposes the optional seconds portion after a runtime Value change. */
|
|
109
|
+
secondsValueChanged() {
|
|
110
|
+
this.#reconcile.schedule();
|
|
111
|
+
}
|
|
112
|
+
/** Adopts a segment inserted or replaced by a Turbo morph. */
|
|
113
|
+
segmentTargetConnected() {
|
|
114
|
+
this.#domDirty = true;
|
|
115
|
+
this.#clearTypeBuffer();
|
|
116
|
+
this.#reconcile.schedule();
|
|
117
|
+
}
|
|
118
|
+
/** Rebuilds state after a segment leaves the retained controller element. */
|
|
119
|
+
segmentTargetDisconnected() {
|
|
120
|
+
this.#domDirty = true;
|
|
121
|
+
this.#clearTypeBuffer();
|
|
122
|
+
this.#reconcile.schedule();
|
|
123
|
+
}
|
|
124
|
+
/** Reflects the current composed value into a field added or replaced at runtime. */
|
|
125
|
+
fieldTargetConnected() {
|
|
126
|
+
this.#reconcile.schedule();
|
|
127
|
+
}
|
|
128
|
+
/** Reconciles silently when the optional field leaves at runtime. */
|
|
129
|
+
fieldTargetDisconnected() {
|
|
130
|
+
this.#reconcile.schedule();
|
|
52
131
|
}
|
|
53
|
-
/** Handles stepping, inter-segment focus moves, jumps, and direct entry. */
|
|
132
|
+
/** Handles APG stepping, inter-segment focus moves, jumps, and direct entry. */
|
|
54
133
|
onKeydown(event) {
|
|
55
134
|
if (isReservedArrowChord(event)) return;
|
|
56
135
|
const segment = event.target?.closest(
|
|
57
|
-
"[data-stimeo--time-picker-target
|
|
136
|
+
"[data-stimeo--time-picker-target~='segment']"
|
|
58
137
|
);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
138
|
+
if (!segment || !this.segmentTargets.includes(segment)) return;
|
|
139
|
+
const kind = this.#kindOf(segment);
|
|
140
|
+
if (!kind) return;
|
|
141
|
+
const key = logicalArrowKey(event.key, this.element);
|
|
142
|
+
if ((key === "Home" || key === "End") && hasModifier(event)) return;
|
|
143
|
+
switch (key) {
|
|
62
144
|
case "ArrowUp":
|
|
63
145
|
event.preventDefault();
|
|
64
146
|
this.#step(kind, this.#delta(kind));
|
|
@@ -77,134 +159,202 @@ var TimePickerController = class extends Controller {
|
|
|
77
159
|
break;
|
|
78
160
|
case "Home":
|
|
79
161
|
event.preventDefault();
|
|
80
|
-
this.#
|
|
162
|
+
this.#setDisplayed(kind, this.#bounds(kind).min);
|
|
163
|
+
this.#commitRender();
|
|
81
164
|
break;
|
|
82
165
|
case "End":
|
|
83
166
|
event.preventDefault();
|
|
84
|
-
this.#
|
|
167
|
+
this.#setDisplayed(kind, this.#bounds(kind).max);
|
|
168
|
+
this.#commitRender();
|
|
85
169
|
break;
|
|
86
170
|
default:
|
|
87
|
-
if (/^[0-9]$/.test(event.key)) {
|
|
171
|
+
if (kind !== "meridiem" && /^[0-9]$/.test(event.key) && !hasModifier(event)) {
|
|
88
172
|
event.preventDefault();
|
|
89
173
|
this.#typeDigit(segment, kind, event.key);
|
|
174
|
+
return;
|
|
90
175
|
}
|
|
91
176
|
return;
|
|
92
177
|
}
|
|
93
|
-
this.#
|
|
94
|
-
this.#typeSegment = null;
|
|
178
|
+
this.#clearTypeBuffer();
|
|
95
179
|
}
|
|
96
|
-
/**
|
|
180
|
+
/** Clears direct-entry state when Tab, Shift+Tab, or pointer focus leaves a segment. */
|
|
181
|
+
#onFocusOut = (event) => {
|
|
182
|
+
const segment = event.target?.closest(
|
|
183
|
+
"[data-stimeo--time-picker-target~='segment']"
|
|
184
|
+
);
|
|
185
|
+
if (!segment || !this.segmentTargets.includes(segment)) return;
|
|
186
|
+
this.#clearTypeBuffer();
|
|
187
|
+
};
|
|
188
|
+
/** Uses the normalized positive-integer minute step; other segments step by one. */
|
|
97
189
|
#delta(kind) {
|
|
98
|
-
|
|
190
|
+
if (kind !== "minute") return 1;
|
|
191
|
+
const step = Math.trunc(this.stepValue);
|
|
192
|
+
return Number.isFinite(step) && step > 0 ? step : 1;
|
|
99
193
|
}
|
|
100
|
-
/**
|
|
194
|
+
/** Mutates one complete action and then renders/notifies its final value once. */
|
|
101
195
|
#step(kind, delta) {
|
|
102
|
-
if (kind === "meridiem") {
|
|
103
|
-
this.#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
const wrapped = ((raw - min) % span + span) % span + min;
|
|
115
|
-
const carry = Math.floor((raw - min) / span);
|
|
116
|
-
this.#set(kind, wrapped);
|
|
117
|
-
this.#carry(kind, carry);
|
|
118
|
-
return;
|
|
196
|
+
if (kind === "meridiem" && this.wrapValue) {
|
|
197
|
+
this.#state.hour = modulo(this.#state.hour + 12, 24);
|
|
198
|
+
} else if (this.wrapValue) {
|
|
199
|
+
const scale = kind === "hour" ? SECONDS_PER_HOUR : kind === "minute" ? SECONDS_PER_MINUTE : 1;
|
|
200
|
+
this.#setFromSeconds(this.#secondsOfDay + delta * scale);
|
|
201
|
+
} else if (kind === "hour") {
|
|
202
|
+
this.#state.hour = Math.min(23, Math.max(0, this.#state.hour + delta));
|
|
203
|
+
} else {
|
|
204
|
+
const { min, max } = this.#bounds(kind);
|
|
205
|
+
const displayed = this.#displayValue(kind);
|
|
206
|
+
this.#setDisplayed(kind, Math.min(max, Math.max(min, displayed + delta)));
|
|
119
207
|
}
|
|
120
|
-
this.#
|
|
121
|
-
}
|
|
122
|
-
/** Propagates a wrap carry from `kind` into the next larger segment. */
|
|
123
|
-
#carry(kind, amount) {
|
|
124
|
-
if (kind === "second") this.#step("minute", amount);
|
|
125
|
-
else if (kind === "minute") this.#step("hour", amount);
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Sets a segment's value (clamped to its `[min, max]` bounds), re-renders it,
|
|
129
|
-
* and resyncs the composed field. Clamping here guards the direct-entry path:
|
|
130
|
-
* typing `0` into a 12-hour hour (min 1) must not commit an out-of-range
|
|
131
|
-
* `aria-valuenow="0"`. The stepping path already passes in-bounds values, so
|
|
132
|
-
* the clamp is a no-op there.
|
|
133
|
-
*/
|
|
134
|
-
#set(kind, value) {
|
|
135
|
-
const { min, max } = this.#bounds(kind);
|
|
136
|
-
this.#state[kind] = Math.min(max, Math.max(min, value));
|
|
137
|
-
const segment = this.segmentTargets.find((s) => this.#kindOf(s) === kind);
|
|
138
|
-
if (segment) this.#renderSegment(segment);
|
|
139
|
-
this.#syncField(true);
|
|
208
|
+
this.#commitRender();
|
|
140
209
|
}
|
|
141
|
-
/** Accumulates a typed digit,
|
|
210
|
+
/** Accumulates a typed digit, rendering once and advancing after completion. */
|
|
142
211
|
#typeDigit(segment, kind, digit) {
|
|
143
|
-
if (kind === "meridiem") return;
|
|
144
212
|
if (this.#typeSegment !== kind) this.#typeBuffer = "";
|
|
145
213
|
this.#typeSegment = kind;
|
|
146
214
|
const { max } = this.#bounds(kind);
|
|
147
215
|
const candidate = Number(`${this.#typeBuffer}${digit}`);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this.#
|
|
216
|
+
this.#typeBuffer = candidate <= max ? `${this.#typeBuffer}${digit}` : digit;
|
|
217
|
+
this.#setDisplayed(kind, Number(this.#typeBuffer));
|
|
218
|
+
this.#commitRender();
|
|
151
219
|
if (this.#typeBuffer.length >= 2 || Number(this.#typeBuffer) * 10 > max) {
|
|
152
|
-
this.#
|
|
153
|
-
this.#typeSegment = null;
|
|
220
|
+
this.#clearTypeBuffer();
|
|
154
221
|
this.#focusSibling(segment, 1);
|
|
155
222
|
}
|
|
156
223
|
}
|
|
157
|
-
/** Moves focus to the previous/next segment, if one exists. */
|
|
224
|
+
/** Moves focus to the previous/next valid segment, if one exists. */
|
|
158
225
|
#focusSibling(segment, direction) {
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
226
|
+
const segments = this.segmentTargets.filter((candidate) => this.#kindOf(candidate) !== null);
|
|
227
|
+
const index = segments.indexOf(segment);
|
|
228
|
+
segments[index + direction]?.focus();
|
|
229
|
+
}
|
|
230
|
+
/** Clears the direct-entry buffer and its owning segment together. */
|
|
231
|
+
#clearTypeBuffer() {
|
|
232
|
+
this.#typeBuffer = "";
|
|
233
|
+
this.#typeSegment = null;
|
|
234
|
+
}
|
|
235
|
+
/** Reconciles one coalesced target, Value, or retained-attribute mutation batch. */
|
|
236
|
+
#reconcileDom() {
|
|
237
|
+
if (this.#domDirty) {
|
|
238
|
+
this.#domDirty = false;
|
|
239
|
+
this.#clearTypeBuffer();
|
|
240
|
+
this.#adoptDomState();
|
|
241
|
+
}
|
|
242
|
+
this.#render();
|
|
243
|
+
}
|
|
244
|
+
/** Rebuilds canonical state from the targets; an absent hour represents midnight. */
|
|
245
|
+
#adoptDomState() {
|
|
246
|
+
let displayedHour = 0;
|
|
247
|
+
let meridiem = AM;
|
|
248
|
+
let minute = 0;
|
|
249
|
+
let second = 0;
|
|
250
|
+
for (const segment of this.segmentTargets) {
|
|
251
|
+
const kind = this.#kindOf(segment);
|
|
252
|
+
if (!kind) continue;
|
|
253
|
+
const value = this.#authoredValue(segment, kind);
|
|
254
|
+
if (kind === "hour") displayedHour = value;
|
|
255
|
+
else if (kind === "minute") minute = value;
|
|
256
|
+
else if (kind === "second") second = value;
|
|
257
|
+
else meridiem = value;
|
|
258
|
+
}
|
|
259
|
+
this.#state = {
|
|
260
|
+
hour: this.hourCycleValue === 12 ? displayedHour % 12 + (meridiem === PM ? 12 : 0) : displayedHour,
|
|
261
|
+
minute,
|
|
262
|
+
second
|
|
263
|
+
};
|
|
162
264
|
}
|
|
163
|
-
/**
|
|
265
|
+
/** Reads and integer-clamps one authored `aria-valuenow`. */
|
|
266
|
+
#authoredValue(segment, kind) {
|
|
267
|
+
const raw = Number(segment.getAttribute("aria-valuenow"));
|
|
268
|
+
const { min, max } = this.#bounds(kind);
|
|
269
|
+
if (!Number.isFinite(raw)) return min;
|
|
270
|
+
return Math.min(max, Math.max(min, Math.trunc(raw)));
|
|
271
|
+
}
|
|
272
|
+
/** Writes a displayed segment value back into canonical state without rendering. */
|
|
273
|
+
#setDisplayed(kind, raw) {
|
|
274
|
+
const { min, max } = this.#bounds(kind);
|
|
275
|
+
const value = Math.min(max, Math.max(min, Math.trunc(raw)));
|
|
276
|
+
if (kind === "hour") {
|
|
277
|
+
this.#state.hour = this.hourCycleValue === 12 ? value % 12 + (this.#state.hour >= 12 ? 12 : 0) : value;
|
|
278
|
+
} else if (kind === "minute" || kind === "second") {
|
|
279
|
+
this.#state[kind] = value;
|
|
280
|
+
} else {
|
|
281
|
+
this.#state.hour = this.#state.hour % 12 + (value === PM ? 12 : 0);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** Converts seconds with day wrapping into the canonical three-unit state. */
|
|
285
|
+
#setFromSeconds(raw) {
|
|
286
|
+
const seconds = modulo(raw, SECONDS_PER_DAY);
|
|
287
|
+
this.#state.hour = Math.floor(seconds / SECONDS_PER_HOUR);
|
|
288
|
+
this.#state.minute = Math.floor(seconds % SECONDS_PER_HOUR / SECONDS_PER_MINUTE);
|
|
289
|
+
this.#state.second = seconds % SECONDS_PER_MINUTE;
|
|
290
|
+
}
|
|
291
|
+
/** Reflects canonical state to every segment without dispatching user events. */
|
|
292
|
+
#renderSegments() {
|
|
293
|
+
this.#withoutObservation(() => {
|
|
294
|
+
for (const segment of this.segmentTargets) this.#renderSegment(segment);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Reflects canonical state during connection or DOM reconciliation.
|
|
299
|
+
*
|
|
300
|
+
* No user edit reaches here, so `change` never fires from this path. A pass
|
|
301
|
+
* that moves the committed value reports `reconcile` instead.
|
|
302
|
+
*
|
|
303
|
+
* @stimeoRenderRoot
|
|
304
|
+
*/
|
|
305
|
+
#render() {
|
|
306
|
+
this.#renderSegments();
|
|
307
|
+
const value = this.#composedValue;
|
|
308
|
+
const previous = this.#lastValue;
|
|
309
|
+
this.#writeField(value);
|
|
310
|
+
this.#lastValue = value;
|
|
311
|
+
if (previous !== "" && value !== previous) {
|
|
312
|
+
this.dispatch("reconcile", { detail: { value } });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Reflects one completed user action and dispatches its final change exactly once. */
|
|
316
|
+
#commitRender() {
|
|
317
|
+
this.#renderSegments();
|
|
318
|
+
const value = this.#composedValue;
|
|
319
|
+
const fieldChanged = this.#writeField(value);
|
|
320
|
+
if (fieldChanged) this.fieldTarget.dispatchEvent(new Event("change", { bubbles: true }));
|
|
321
|
+
if (value !== this.#lastValue) this.dispatch("change", { detail: { value } });
|
|
322
|
+
this.#lastValue = value;
|
|
323
|
+
}
|
|
324
|
+
/** Reflects one segment's value and controller-owned ARIA bounds/text. */
|
|
164
325
|
#renderSegment(segment) {
|
|
165
326
|
const kind = this.#kindOf(segment);
|
|
166
327
|
if (!kind) return;
|
|
167
|
-
const value = this.#
|
|
168
|
-
|
|
169
|
-
const text2 = value === PM ? "PM" : "AM";
|
|
170
|
-
segment.setAttribute("aria-valuenow", String(value));
|
|
171
|
-
segment.setAttribute("aria-valuetext", text2);
|
|
172
|
-
segment.setAttribute("aria-valuemin", String(AM));
|
|
173
|
-
segment.setAttribute("aria-valuemax", String(PM));
|
|
174
|
-
segment.textContent = text2;
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
328
|
+
const value = this.#displayValue(kind);
|
|
329
|
+
const text = kind === "meridiem" ? value === PM ? "PM" : "AM" : pad(value);
|
|
177
330
|
const { min, max } = this.#bounds(kind);
|
|
178
|
-
const text = String(value).padStart(2, "0");
|
|
179
331
|
segment.setAttribute("aria-valuenow", String(value));
|
|
180
332
|
segment.setAttribute("aria-valuetext", text);
|
|
181
333
|
segment.setAttribute("aria-valuemin", String(min));
|
|
182
334
|
segment.setAttribute("aria-valuemax", String(max));
|
|
183
335
|
segment.textContent = text;
|
|
184
336
|
}
|
|
185
|
-
/**
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
337
|
+
/** Writes a composed value to the optional form field and reports whether it changed. */
|
|
338
|
+
#writeField(value) {
|
|
339
|
+
if (!this.hasFieldTarget || this.fieldTarget.value === value) return false;
|
|
340
|
+
this.fieldTarget.value = value;
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
/** The canonical form value composed as `HH:MM[:SS]`. */
|
|
344
|
+
get #composedValue() {
|
|
345
|
+
const parts = [pad(this.#state.hour), pad(this.#state.minute)];
|
|
189
346
|
if (this.secondsValue) parts.push(pad(this.#state.second));
|
|
190
|
-
|
|
191
|
-
const fieldChanged = this.hasFieldTarget && this.fieldTarget.value !== value;
|
|
192
|
-
if (fieldChanged) {
|
|
193
|
-
this.fieldTarget.value = value;
|
|
194
|
-
}
|
|
195
|
-
if (notify && fieldChanged) {
|
|
196
|
-
this.fieldTarget.dispatchEvent(new Event("change", { bubbles: true }));
|
|
197
|
-
}
|
|
198
|
-
if (notify && value !== this.#lastValue) this.dispatch("change", { detail: { value } });
|
|
199
|
-
this.#lastValue = value;
|
|
347
|
+
return parts.join(":");
|
|
200
348
|
}
|
|
201
|
-
/**
|
|
202
|
-
#
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
349
|
+
/** Returns a canonical unit in the presentation form exposed by its segment. */
|
|
350
|
+
#displayValue(kind) {
|
|
351
|
+
if (kind === "hour") {
|
|
352
|
+
return this.hourCycleValue === 12 ? this.#state.hour % 12 || 12 : this.#state.hour;
|
|
353
|
+
}
|
|
354
|
+
if (kind === "meridiem") return this.#state.hour >= 12 ? PM : AM;
|
|
355
|
+
return this.#state[kind];
|
|
206
356
|
}
|
|
207
|
-
/** The inclusive
|
|
357
|
+
/** The inclusive controller-owned bounds for a segment kind. */
|
|
208
358
|
#bounds(kind) {
|
|
209
359
|
switch (kind) {
|
|
210
360
|
case "hour":
|
|
@@ -216,6 +366,38 @@ var TimePickerController = class extends Controller {
|
|
|
216
366
|
return { min: AM, max: PM };
|
|
217
367
|
}
|
|
218
368
|
}
|
|
369
|
+
/** Watches retained segment state while excluding this controller's own reflections. */
|
|
370
|
+
#observeMutations() {
|
|
371
|
+
const observer = new MutationObserver((records) => {
|
|
372
|
+
const changed = records.some(
|
|
373
|
+
({ target }) => target instanceof HTMLElement && this.segmentTargets.includes(target)
|
|
374
|
+
);
|
|
375
|
+
if (!changed) return;
|
|
376
|
+
this.#domDirty = true;
|
|
377
|
+
this.#clearTypeBuffer();
|
|
378
|
+
this.#reconcile.schedule();
|
|
379
|
+
});
|
|
380
|
+
this.#observer = observer;
|
|
381
|
+
this.#observeWith(observer);
|
|
382
|
+
}
|
|
383
|
+
/** Registers retained attribute observation on the controller subtree. */
|
|
384
|
+
#observeWith(observer) {
|
|
385
|
+
observer.observe(this.element, {
|
|
386
|
+
subtree: true,
|
|
387
|
+
attributes: true,
|
|
388
|
+
attributeFilter: ["aria-valuenow", "data-segment"]
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/** Temporarily pauses observation so derived ARIA writes cannot become DOM input. */
|
|
392
|
+
#withoutObservation(run) {
|
|
393
|
+
const observer = this.#observer;
|
|
394
|
+
observer?.disconnect();
|
|
395
|
+
try {
|
|
396
|
+
run();
|
|
397
|
+
} finally {
|
|
398
|
+
if (observer && this.#connected) this.#observeWith(observer);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
219
401
|
/** Reads a segment's declared kind, or null when absent/invalid. */
|
|
220
402
|
#kindOf(segment) {
|
|
221
403
|
const kind = segment.getAttribute("data-segment");
|
|
@@ -224,7 +406,14 @@ var TimePickerController = class extends Controller {
|
|
|
224
406
|
}
|
|
225
407
|
return null;
|
|
226
408
|
}
|
|
409
|
+
/** Canonical state as seconds since the start of its nominal day. */
|
|
410
|
+
get #secondsOfDay() {
|
|
411
|
+
return this.#state.hour * SECONDS_PER_HOUR + this.#state.minute * SECONDS_PER_MINUTE + this.#state.second;
|
|
412
|
+
}
|
|
227
413
|
};
|
|
414
|
+
function modulo(value, modulus) {
|
|
415
|
+
return (value % modulus + modulus) % modulus;
|
|
416
|
+
}
|
|
228
417
|
function pad(value) {
|
|
229
418
|
return String(value).padStart(2, "0");
|
|
230
419
|
}
|