stimeo-ui 0.16.0 → 0.17.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +8 -1
- data/dist/cable/index.js +363 -436
- data/dist/controllers/accordion_controller.js +166 -55
- data/dist/controllers/alert_dialog_controller.js +862 -221
- data/dist/controllers/announcer_controller.js +135 -198
- data/dist/controllers/aspect_ratio_controller.js +0 -10
- data/dist/controllers/auto_submit_controller.js +271 -127
- data/dist/controllers/avatar_controller.js +171 -105
- data/dist/controllers/breadcrumb_controller.js +0 -130
- data/dist/controllers/bulk_select_controller.js +262 -52
- data/dist/controllers/calendar_controller.js +175 -209
- data/dist/controllers/carousel_controller.js +461 -399
- data/dist/controllers/character_counter_controller.js +246 -156
- data/dist/controllers/checkbox_controller.js +32 -74
- data/dist/controllers/clipboard_controller.js +168 -147
- data/dist/controllers/collapsible_controller.js +301 -120
- data/dist/controllers/color_picker_controller.js +149 -139
- data/dist/controllers/combobox_controller.js +360 -133
- data/dist/controllers/command_palette_controller.js +970 -358
- data/dist/controllers/conditional_fields_controller.js +166 -124
- data/dist/controllers/confirm_controller.js +939 -248
- data/dist/controllers/context_menu_controller.js +256 -128
- data/dist/controllers/count_up_controller.js +73 -21
- data/dist/controllers/countdown_controller.js +183 -148
- data/dist/controllers/currency_input_controller.js +134 -158
- data/dist/controllers/data_grid_controller.js +114 -186
- data/dist/controllers/date_range_picker_controller.js +288 -206
- data/dist/controllers/dialog_controller.js +858 -223
- data/dist/controllers/direct_upload_controller.js +73 -153
- data/dist/controllers/dirty_form_controller.js +14 -48
- data/dist/controllers/dismissible_controller.js +175 -21
- data/dist/controllers/drawer_controller.js +933 -341
- data/dist/controllers/dropdown_controller.js +312 -82
- data/dist/controllers/editable_controller.js +158 -68
- data/dist/controllers/empty_state_controller.js +67 -53
- data/dist/controllers/file_dropzone_controller.js +311 -207
- data/dist/controllers/filter_controller.js +67 -39
- data/dist/controllers/flash_controller.js +172 -314
- data/dist/controllers/focus_controller.js +654 -240
- data/dist/controllers/form_field_controller.js +147 -132
- data/dist/controllers/form_validation_controller.js +13 -86
- data/dist/controllers/frame_loading_controller.js +264 -213
- data/dist/controllers/highlight_controller.js +107 -75
- data/dist/controllers/hover_card_controller.js +309 -118
- data/dist/controllers/idle_controller.js +267 -101
- data/dist/controllers/input_mask_controller.js +71 -72
- data/dist/controllers/intersection_controller.js +147 -106
- data/dist/controllers/lazy_frame_controller.js +9 -65
- data/dist/controllers/listbox_controller.js +306 -204
- data/dist/controllers/local_time_controller.js +54 -64
- data/dist/controllers/masonry_controller.js +70 -93
- data/dist/controllers/menu_controller.js +214 -156
- data/dist/controllers/menubar_controller.js +170 -329
- data/dist/controllers/meter_controller.js +151 -67
- data/dist/controllers/multi_select_controller.js +593 -358
- data/dist/controllers/navigation_menu_controller.js +124 -232
- data/dist/controllers/nested_form_controller.js +352 -182
- data/dist/controllers/network_status_controller.js +242 -54
- data/dist/controllers/number_input_controller.js +320 -263
- data/dist/controllers/optimistic_controller.js +16 -76
- data/dist/controllers/otp_controller.js +198 -193
- data/dist/controllers/overflow_indicator_controller.js +116 -113
- data/dist/controllers/overflow_menu_controller.js +343 -283
- data/dist/controllers/pagination_controller.js +295 -119
- data/dist/controllers/password_reveal_controller.js +389 -121
- data/dist/controllers/password_strength_controller.js +99 -153
- data/dist/controllers/persist_controller.js +237 -103
- data/dist/controllers/pointer_drag_controller.js +185 -165
- data/dist/controllers/popover_controller.js +236 -97
- data/dist/controllers/portal_controller.js +209 -76
- data/dist/controllers/preview_guard_controller.js +232 -116
- data/dist/controllers/progress_controller.js +125 -56
- data/dist/controllers/radio_group_controller.js +117 -137
- data/dist/controllers/range_slider_controller.js +192 -105
- data/dist/controllers/rating_controller.js +418 -207
- data/dist/controllers/read_more_controller.js +134 -63
- data/dist/controllers/reading_progress_controller.js +147 -135
- data/dist/controllers/relative_time_controller.js +145 -110
- data/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- data/dist/controllers/resizable_controller.js +255 -139
- data/dist/controllers/roving_controller.js +39 -84
- data/dist/controllers/scroll_area_controller.js +428 -225
- data/dist/controllers/scroll_restore_controller.js +0 -53
- data/dist/controllers/scroll_visibility_controller.js +395 -145
- data/dist/controllers/scrollspy_controller.js +181 -242
- data/dist/controllers/separator_controller.js +267 -140
- data/dist/controllers/sidebar_controller.js +1023 -386
- data/dist/controllers/skeleton_controller.js +248 -97
- data/dist/controllers/slider_controller.js +159 -79
- data/dist/controllers/smart_sticky_header_controller.js +162 -63
- data/dist/controllers/sortable_controller.js +0 -112
- data/dist/controllers/spinner_controller.js +277 -169
- data/dist/controllers/step_indicator_controller.js +99 -46
- data/dist/controllers/stepper_controller.js +114 -73
- data/dist/controllers/stick_to_bottom_controller.js +262 -138
- data/dist/controllers/sticky_observer_controller.js +214 -49
- data/dist/controllers/submit_once_controller.js +294 -205
- data/dist/controllers/switch_controller.js +33 -60
- data/dist/controllers/tabs_controller.js +35 -29
- data/dist/controllers/tags_input_controller.js +136 -145
- data/dist/controllers/textarea_autosize_controller.js +71 -58
- data/dist/controllers/theme_controller.js +130 -112
- data/dist/controllers/time_picker_controller.js +146 -80
- data/dist/controllers/toast_controller.js +186 -297
- data/dist/controllers/toggle_group_controller.js +95 -136
- data/dist/controllers/toolbar_controller.js +68 -98
- data/dist/controllers/tooltip_controller.js +158 -124
- data/dist/controllers/transition_controller.js +123 -119
- data/dist/controllers/tree_view_controller.js +150 -345
- data/dist/index.js +6370 -9346
- data/dist/positioning/index.js +148 -47
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +3 -3
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
// src/controllers/time_picker_controller.ts
|
|
4
|
-
|
|
5
|
-
// src/utils/logical_scroll.ts
|
|
6
3
|
function isRtl(element) {
|
|
7
4
|
return window.getComputedStyle(element).direction === "rtl";
|
|
8
5
|
}
|
|
9
6
|
|
|
10
|
-
// src/utils/arrow_step.ts
|
|
11
7
|
function logicalArrowKey(key, element) {
|
|
12
8
|
if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
|
|
13
9
|
if (!isRtl(element)) return key;
|
|
@@ -18,7 +14,6 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
18
14
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
// src/utils/field_mirror.ts
|
|
22
17
|
function writeField(field, value) {
|
|
23
18
|
if (field.value === value) return false;
|
|
24
19
|
field.value = value;
|
|
@@ -28,27 +23,33 @@ function commitField(target) {
|
|
|
28
23
|
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
var ListenerSet = class {
|
|
27
|
+
#abort = new AbortController();
|
|
28
|
+
add(target, type, handler, options) {
|
|
29
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
30
|
+
}
|
|
31
|
+
dispose() {
|
|
32
|
+
this.#abort.abort();
|
|
33
|
+
this.#abort = new AbortController();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
32
37
|
var MicrotaskCoalescer = class {
|
|
33
38
|
#run;
|
|
34
39
|
#queued = false;
|
|
35
40
|
#active = false;
|
|
36
41
|
#generation = 0;
|
|
37
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
38
42
|
constructor(run) {
|
|
39
43
|
this.#run = run;
|
|
40
44
|
}
|
|
41
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
42
45
|
activate() {
|
|
43
46
|
this.#active = true;
|
|
44
47
|
}
|
|
45
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
46
48
|
cancel() {
|
|
47
49
|
this.#active = false;
|
|
48
50
|
this.#queued = false;
|
|
49
51
|
this.#generation += 1;
|
|
50
52
|
}
|
|
51
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
52
53
|
schedule() {
|
|
53
54
|
if (!this.#active || this.#queued) return;
|
|
54
55
|
this.#queued = true;
|
|
@@ -61,19 +62,100 @@ var MicrotaskCoalescer = class {
|
|
|
61
62
|
}
|
|
62
63
|
};
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
var MorphRenderWatcher = class {
|
|
66
|
+
#listeners = new ListenerSet();
|
|
67
|
+
#pass;
|
|
68
|
+
#element = null;
|
|
69
|
+
#onMorph = () => this.#pass.schedule();
|
|
70
|
+
constructor(run) {
|
|
71
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
72
|
+
}
|
|
73
|
+
observe(element) {
|
|
74
|
+
if (this.#element === element) return;
|
|
75
|
+
this.disconnect();
|
|
76
|
+
this.#element = element;
|
|
77
|
+
this.#pass.activate();
|
|
78
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
79
|
+
}
|
|
80
|
+
schedule() {
|
|
81
|
+
this.#pass.schedule();
|
|
82
|
+
}
|
|
83
|
+
disconnect() {
|
|
84
|
+
this.#listeners.dispose();
|
|
85
|
+
this.#pass.cancel();
|
|
86
|
+
this.#element = null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
var MoveCounter = class {
|
|
91
|
+
#sequence = 0;
|
|
92
|
+
record() {
|
|
93
|
+
this.#sequence += 1;
|
|
94
|
+
return this.#sequence;
|
|
95
|
+
}
|
|
96
|
+
isLatest(token) {
|
|
97
|
+
return token === this.#sequence;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
var NUMBER_BOUNDS = {
|
|
102
|
+
positive: { finite: true, exclusiveMin: 0 }};
|
|
103
|
+
function matchesNumberBounds(value, bounds) {
|
|
104
|
+
if (!Number.isFinite(value)) {
|
|
105
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
106
|
+
if (direction === null) return false;
|
|
107
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
108
|
+
}
|
|
109
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
110
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
111
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
112
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
113
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readNumber(raw, fallback, bounds) {
|
|
118
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
var NumberValueReader = class {
|
|
122
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
123
|
+
read(owner, name, raw, fallback, bounds) {
|
|
124
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
125
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
126
|
+
this.#lastRejected.delete(name);
|
|
127
|
+
return resolved;
|
|
128
|
+
}
|
|
129
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
130
|
+
const literal = owner.element.getAttribute(attribute);
|
|
131
|
+
if (literal === null) {
|
|
132
|
+
this.#lastRejected.delete(name);
|
|
133
|
+
return resolved;
|
|
134
|
+
}
|
|
135
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
136
|
+
this.#lastRejected.set(name, literal);
|
|
137
|
+
console.warn(
|
|
138
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return resolved;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
65
145
|
function targetSelector(identifier, name) {
|
|
66
146
|
return `[data-${identifier}-target~="${name}"]`;
|
|
67
147
|
}
|
|
68
148
|
|
|
69
|
-
// src/controllers/time_picker_controller.ts
|
|
70
149
|
var AM = 0;
|
|
71
150
|
var PM = 1;
|
|
72
151
|
var SECONDS_PER_MINUTE = 60;
|
|
73
152
|
var SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE;
|
|
74
153
|
var SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
|
|
75
154
|
var hasModifier = (event) => event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
76
|
-
var TimePickerController = class extends Controller {
|
|
155
|
+
var TimePickerController = class _TimePickerController extends Controller {
|
|
156
|
+
#numbers = new NumberValueReader();
|
|
157
|
+
#moves = new MoveCounter();
|
|
158
|
+
#move = 0;
|
|
77
159
|
static targets = ["segment", "field"];
|
|
78
160
|
static values = {
|
|
79
161
|
hourCycle: { type: Number, default: 24 },
|
|
@@ -81,37 +163,32 @@ var TimePickerController = class extends Controller {
|
|
|
81
163
|
seconds: { type: Boolean, default: false },
|
|
82
164
|
wrap: { type: Boolean, default: true }
|
|
83
165
|
};
|
|
166
|
+
static valueConstraints = {
|
|
167
|
+
hourCycle: { finite: true, allowedValues: [12, 24] },
|
|
168
|
+
step: NUMBER_BOUNDS.positive
|
|
169
|
+
};
|
|
84
170
|
static actions = ["onKeydown"];
|
|
85
171
|
static events = ["change", "reconcile"];
|
|
86
|
-
|
|
87
|
-
* Collapses target, Value, and retained-attribute morphs into one render that
|
|
88
|
-
* dispatches no `change`.
|
|
89
|
-
*/
|
|
90
|
-
#reconcile = new MicrotaskCoalescer(() => this.#reconcileDom());
|
|
91
|
-
/** Canonical state; the displayed hour and meridiem are derived from this value. */
|
|
172
|
+
#reconcile = new MorphRenderWatcher(() => this.#reconcileDom());
|
|
92
173
|
#state = { hour: 0, minute: 0, second: 0 };
|
|
93
|
-
/** Direct-entry digit buffer and the segment it belongs to. */
|
|
94
174
|
#typeBuffer = "";
|
|
95
175
|
#typeSegment = null;
|
|
96
|
-
/** Last composed value, used to suppress duplicate user notifications. */
|
|
97
176
|
#lastValue = "";
|
|
98
177
|
#observer = null;
|
|
99
178
|
#connected = false;
|
|
100
179
|
#domDirty = false;
|
|
101
|
-
/** Seeds state from the DOM, renders canonical ARIA, and starts morph observation. */
|
|
102
180
|
connect() {
|
|
103
181
|
this.#connected = true;
|
|
104
|
-
this.#reconcile.
|
|
182
|
+
this.#reconcile.observe(this.element);
|
|
105
183
|
this.element.addEventListener("focusout", this.#onFocusOut);
|
|
106
184
|
this.#domDirty = false;
|
|
107
185
|
this.#adoptDomState();
|
|
108
186
|
this.#render();
|
|
109
187
|
this.#observeMutations();
|
|
110
188
|
}
|
|
111
|
-
/** Releases the observer, listener, queued reconciliation, and transient input state. */
|
|
112
189
|
disconnect() {
|
|
113
190
|
this.#connected = false;
|
|
114
|
-
this.#reconcile.
|
|
191
|
+
this.#reconcile.disconnect();
|
|
115
192
|
this.element.removeEventListener("focusout", this.#onFocusOut);
|
|
116
193
|
this.#observer?.disconnect();
|
|
117
194
|
this.#observer = null;
|
|
@@ -119,35 +196,26 @@ var TimePickerController = class extends Controller {
|
|
|
119
196
|
this.#clearTypeBuffer();
|
|
120
197
|
this.#lastValue = "";
|
|
121
198
|
}
|
|
122
|
-
/** Re-renders the canonical instant when 12/24-hour presentation changes at runtime. */
|
|
123
199
|
hourCycleValueChanged() {
|
|
124
200
|
this.#reconcile.schedule();
|
|
125
201
|
}
|
|
126
|
-
/** Recomposes the optional seconds portion after a runtime Value change. */
|
|
127
202
|
secondsValueChanged() {
|
|
128
203
|
this.#reconcile.schedule();
|
|
129
204
|
}
|
|
130
|
-
/** Adopts a segment inserted or replaced by a Turbo morph. */
|
|
131
205
|
segmentTargetConnected() {
|
|
132
206
|
this.#domDirty = true;
|
|
133
|
-
this.#clearTypeBuffer();
|
|
134
207
|
this.#reconcile.schedule();
|
|
135
208
|
}
|
|
136
|
-
/** Rebuilds state after a segment leaves the retained controller element. */
|
|
137
209
|
segmentTargetDisconnected() {
|
|
138
210
|
this.#domDirty = true;
|
|
139
|
-
this.#clearTypeBuffer();
|
|
140
211
|
this.#reconcile.schedule();
|
|
141
212
|
}
|
|
142
|
-
/** Reflects the current composed value into a field added or replaced at runtime. */
|
|
143
213
|
fieldTargetConnected() {
|
|
144
214
|
this.#reconcile.schedule();
|
|
145
215
|
}
|
|
146
|
-
/** Reconciles silently when the optional field leaves at runtime. */
|
|
147
216
|
fieldTargetDisconnected() {
|
|
148
217
|
this.#reconcile.schedule();
|
|
149
218
|
}
|
|
150
|
-
/** Handles APG stepping, inter-segment focus moves, jumps, and direct entry. */
|
|
151
219
|
onKeydown(event) {
|
|
152
220
|
if (isReservedArrowChord(event)) return;
|
|
153
221
|
const segment = event.target?.closest(
|
|
@@ -158,6 +226,7 @@ var TimePickerController = class extends Controller {
|
|
|
158
226
|
if (!kind) return;
|
|
159
227
|
const key = logicalArrowKey(event.key, this.element);
|
|
160
228
|
if ((key === "Home" || key === "End") && hasModifier(event)) return;
|
|
229
|
+
if (this.#adoptPendingDom()) this.#reconcile.schedule();
|
|
161
230
|
switch (key) {
|
|
162
231
|
case "ArrowUp":
|
|
163
232
|
event.preventDefault();
|
|
@@ -195,7 +264,6 @@ var TimePickerController = class extends Controller {
|
|
|
195
264
|
}
|
|
196
265
|
this.#clearTypeBuffer();
|
|
197
266
|
}
|
|
198
|
-
/** Clears direct-entry state when Tab, Shift+Tab, or pointer focus leaves a segment. */
|
|
199
267
|
#onFocusOut = (event) => {
|
|
200
268
|
const segment = event.target?.closest(
|
|
201
269
|
targetSelector(this.identifier, "segment")
|
|
@@ -203,13 +271,11 @@ var TimePickerController = class extends Controller {
|
|
|
203
271
|
if (!segment || !this.segmentTargets.includes(segment)) return;
|
|
204
272
|
this.#clearTypeBuffer();
|
|
205
273
|
};
|
|
206
|
-
/** Uses the normalized positive-integer minute step; other segments step by one. */
|
|
207
274
|
#delta(kind) {
|
|
208
275
|
if (kind !== "minute") return 1;
|
|
209
|
-
const step = Math.trunc(this
|
|
210
|
-
return
|
|
276
|
+
const step = Math.trunc(this.#safeStep);
|
|
277
|
+
return step > 0 ? step : 1;
|
|
211
278
|
}
|
|
212
|
-
/** Mutates one complete action and then renders/notifies its final value once. */
|
|
213
279
|
#step(kind, delta) {
|
|
214
280
|
if (kind === "meridiem" && this.wrapValue) {
|
|
215
281
|
this.#state.hour = modulo(this.#state.hour + 12, 24);
|
|
@@ -225,7 +291,6 @@ var TimePickerController = class extends Controller {
|
|
|
225
291
|
}
|
|
226
292
|
this.#commitRender();
|
|
227
293
|
}
|
|
228
|
-
/** Accumulates a typed digit, rendering once and advancing after completion. */
|
|
229
294
|
#typeDigit(segment, kind, digit) {
|
|
230
295
|
if (this.#typeSegment !== kind) this.#typeBuffer = "";
|
|
231
296
|
this.#typeSegment = kind;
|
|
@@ -239,27 +304,30 @@ var TimePickerController = class extends Controller {
|
|
|
239
304
|
this.#focusSibling(segment, 1);
|
|
240
305
|
}
|
|
241
306
|
}
|
|
242
|
-
/** Moves focus to the previous/next valid segment, if one exists. */
|
|
243
307
|
#focusSibling(segment, direction) {
|
|
244
308
|
const segments = this.segmentTargets.filter((candidate) => this.#kindOf(candidate) !== null);
|
|
245
309
|
const index = segments.indexOf(segment);
|
|
246
310
|
segments[index + direction]?.focus();
|
|
247
311
|
}
|
|
248
|
-
/** Clears the direct-entry buffer and its owning segment together. */
|
|
249
312
|
#clearTypeBuffer() {
|
|
250
313
|
this.#typeBuffer = "";
|
|
251
314
|
this.#typeSegment = null;
|
|
252
315
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (this
|
|
256
|
-
this.#domDirty =
|
|
257
|
-
this.#clearTypeBuffer();
|
|
258
|
-
this.#adoptDomState();
|
|
316
|
+
#adoptPendingDom() {
|
|
317
|
+
const pending = this.#observer?.takeRecords() ?? [];
|
|
318
|
+
if (pending.some(({ target }) => this.segmentTargets.includes(target))) {
|
|
319
|
+
this.#domDirty = true;
|
|
259
320
|
}
|
|
321
|
+
if (!this.#domDirty) return false;
|
|
322
|
+
this.#domDirty = false;
|
|
323
|
+
this.#clearTypeBuffer();
|
|
324
|
+
this.#adoptDomState();
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
#reconcileDom() {
|
|
328
|
+
this.#adoptPendingDom();
|
|
260
329
|
this.#render();
|
|
261
330
|
}
|
|
262
|
-
/** Rebuilds canonical state from the targets; an absent hour represents midnight. */
|
|
263
331
|
#adoptDomState() {
|
|
264
332
|
let displayedHour = 0;
|
|
265
333
|
let meridiem = AM;
|
|
@@ -275,51 +343,39 @@ var TimePickerController = class extends Controller {
|
|
|
275
343
|
else meridiem = value;
|
|
276
344
|
}
|
|
277
345
|
this.#state = {
|
|
278
|
-
hour: this
|
|
346
|
+
hour: this.#safeHourCycle === 12 ? displayedHour % 12 + (meridiem === PM ? 12 : 0) : displayedHour,
|
|
279
347
|
minute,
|
|
280
348
|
second
|
|
281
349
|
};
|
|
282
350
|
}
|
|
283
|
-
/** Reads and integer-clamps one authored `aria-valuenow`. */
|
|
284
351
|
#authoredValue(segment, kind) {
|
|
285
352
|
const raw = Number(segment.getAttribute("aria-valuenow"));
|
|
286
353
|
const { min, max } = this.#bounds(kind);
|
|
287
354
|
if (!Number.isFinite(raw)) return min;
|
|
288
355
|
return Math.min(max, Math.max(min, Math.trunc(raw)));
|
|
289
356
|
}
|
|
290
|
-
/** Writes a displayed segment value back into canonical state without rendering. */
|
|
291
357
|
#setDisplayed(kind, raw) {
|
|
292
358
|
const { min, max } = this.#bounds(kind);
|
|
293
359
|
const value = Math.min(max, Math.max(min, Math.trunc(raw)));
|
|
294
360
|
if (kind === "hour") {
|
|
295
|
-
this.#state.hour = this
|
|
361
|
+
this.#state.hour = this.#safeHourCycle === 12 ? value % 12 + (this.#state.hour >= 12 ? 12 : 0) : value;
|
|
296
362
|
} else if (kind === "minute" || kind === "second") {
|
|
297
363
|
this.#state[kind] = value;
|
|
298
364
|
} else {
|
|
299
365
|
this.#state.hour = this.#state.hour % 12 + (value === PM ? 12 : 0);
|
|
300
366
|
}
|
|
301
367
|
}
|
|
302
|
-
/** Converts seconds with day wrapping into the canonical three-unit state. */
|
|
303
368
|
#setFromSeconds(raw) {
|
|
304
369
|
const seconds = modulo(raw, SECONDS_PER_DAY);
|
|
305
370
|
this.#state.hour = Math.floor(seconds / SECONDS_PER_HOUR);
|
|
306
371
|
this.#state.minute = Math.floor(seconds % SECONDS_PER_HOUR / SECONDS_PER_MINUTE);
|
|
307
372
|
this.#state.second = seconds % SECONDS_PER_MINUTE;
|
|
308
373
|
}
|
|
309
|
-
/** Reflects canonical state to every segment without dispatching user events. */
|
|
310
374
|
#renderSegments() {
|
|
311
375
|
this.#withoutObservation(() => {
|
|
312
376
|
for (const segment of this.segmentTargets) this.#renderSegment(segment);
|
|
313
377
|
});
|
|
314
378
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Reflects canonical state during connection or DOM reconciliation.
|
|
317
|
-
*
|
|
318
|
-
* No user edit reaches here, so `change` never fires from this path. A pass
|
|
319
|
-
* that moves the committed value reports `reconcile` instead.
|
|
320
|
-
*
|
|
321
|
-
* @stimeoRenderRoot
|
|
322
|
-
*/
|
|
323
379
|
#render() {
|
|
324
380
|
this.#renderSegments();
|
|
325
381
|
const value = this.#composedValue;
|
|
@@ -327,18 +383,21 @@ var TimePickerController = class extends Controller {
|
|
|
327
383
|
this.#writeField(value);
|
|
328
384
|
this.#lastValue = value;
|
|
329
385
|
if (previous !== "" && value !== previous) {
|
|
386
|
+
this.#move = this.#moves.record();
|
|
330
387
|
this.dispatch("reconcile", { detail: { value } });
|
|
331
388
|
}
|
|
332
389
|
}
|
|
333
|
-
/** Reflects one completed user action and dispatches its final change exactly once. */
|
|
334
390
|
#commitRender() {
|
|
335
391
|
this.#renderSegments();
|
|
336
392
|
const value = this.#composedValue;
|
|
337
|
-
|
|
338
|
-
if (value !== this.#lastValue) this.dispatch("change", { detail: { value } });
|
|
393
|
+
const changed = value !== this.#lastValue;
|
|
339
394
|
this.#lastValue = value;
|
|
395
|
+
if (changed) this.#move = this.#moves.record();
|
|
396
|
+
const move = this.#move;
|
|
397
|
+
if (this.#writeField(value)) commitField(this.fieldTarget);
|
|
398
|
+
if (!this.#moves.isLatest(move)) return;
|
|
399
|
+
if (changed) this.dispatch("change", { detail: { value } });
|
|
340
400
|
}
|
|
341
|
-
/** Reflects one segment's value and controller-owned ARIA bounds/text. */
|
|
342
401
|
#renderSegment(segment) {
|
|
343
402
|
const kind = this.#kindOf(segment);
|
|
344
403
|
if (!kind) return;
|
|
@@ -351,29 +410,25 @@ var TimePickerController = class extends Controller {
|
|
|
351
410
|
segment.setAttribute("aria-valuemax", String(max));
|
|
352
411
|
segment.textContent = text;
|
|
353
412
|
}
|
|
354
|
-
/** Writes a composed value to the optional form field and reports whether it changed. */
|
|
355
413
|
#writeField(value) {
|
|
356
414
|
return this.hasFieldTarget && writeField(this.fieldTarget, value);
|
|
357
415
|
}
|
|
358
|
-
/** The canonical form value composed as `HH:MM[:SS]`. */
|
|
359
416
|
get #composedValue() {
|
|
360
417
|
const parts = [pad(this.#state.hour), pad(this.#state.minute)];
|
|
361
418
|
if (this.secondsValue) parts.push(pad(this.#state.second));
|
|
362
419
|
return parts.join(":");
|
|
363
420
|
}
|
|
364
|
-
/** Returns a canonical unit in the presentation form exposed by its segment. */
|
|
365
421
|
#displayValue(kind) {
|
|
366
422
|
if (kind === "hour") {
|
|
367
|
-
return this
|
|
423
|
+
return this.#safeHourCycle === 12 ? this.#state.hour % 12 || 12 : this.#state.hour;
|
|
368
424
|
}
|
|
369
425
|
if (kind === "meridiem") return this.#state.hour >= 12 ? PM : AM;
|
|
370
426
|
return this.#state[kind];
|
|
371
427
|
}
|
|
372
|
-
/** The inclusive controller-owned bounds for a segment kind. */
|
|
373
428
|
#bounds(kind) {
|
|
374
429
|
switch (kind) {
|
|
375
430
|
case "hour":
|
|
376
|
-
return this
|
|
431
|
+
return this.#safeHourCycle === 12 ? { min: 1, max: 12 } : { min: 0, max: 23 };
|
|
377
432
|
case "minute":
|
|
378
433
|
case "second":
|
|
379
434
|
return { min: 0, max: 59 };
|
|
@@ -381,7 +436,6 @@ var TimePickerController = class extends Controller {
|
|
|
381
436
|
return { min: AM, max: PM };
|
|
382
437
|
}
|
|
383
438
|
}
|
|
384
|
-
/** Watches retained segment state while excluding this controller's own reflections. */
|
|
385
439
|
#observeMutations() {
|
|
386
440
|
const observer = new MutationObserver((records) => {
|
|
387
441
|
const changed = records.some(
|
|
@@ -389,13 +443,11 @@ var TimePickerController = class extends Controller {
|
|
|
389
443
|
);
|
|
390
444
|
if (!changed) return;
|
|
391
445
|
this.#domDirty = true;
|
|
392
|
-
this.#clearTypeBuffer();
|
|
393
446
|
this.#reconcile.schedule();
|
|
394
447
|
});
|
|
395
448
|
this.#observer = observer;
|
|
396
449
|
this.#observeWith(observer);
|
|
397
450
|
}
|
|
398
|
-
/** Registers retained attribute observation on the controller subtree. */
|
|
399
451
|
#observeWith(observer) {
|
|
400
452
|
observer.observe(this.element, {
|
|
401
453
|
subtree: true,
|
|
@@ -403,7 +455,6 @@ var TimePickerController = class extends Controller {
|
|
|
403
455
|
attributeFilter: ["aria-valuenow", "data-segment"]
|
|
404
456
|
});
|
|
405
457
|
}
|
|
406
|
-
/** Temporarily pauses observation so derived ARIA writes cannot become DOM input. */
|
|
407
458
|
#withoutObservation(run) {
|
|
408
459
|
const observer = this.#observer;
|
|
409
460
|
observer?.disconnect();
|
|
@@ -413,7 +464,6 @@ var TimePickerController = class extends Controller {
|
|
|
413
464
|
if (observer && this.#connected) this.#observeWith(observer);
|
|
414
465
|
}
|
|
415
466
|
}
|
|
416
|
-
/** Reads a segment's declared kind, or null when absent/invalid. */
|
|
417
467
|
#kindOf(segment) {
|
|
418
468
|
const kind = segment.getAttribute("data-segment");
|
|
419
469
|
if (kind === "hour" || kind === "minute" || kind === "second" || kind === "meridiem") {
|
|
@@ -421,10 +471,27 @@ var TimePickerController = class extends Controller {
|
|
|
421
471
|
}
|
|
422
472
|
return null;
|
|
423
473
|
}
|
|
424
|
-
/** Canonical state as seconds since the start of its nominal day. */
|
|
425
474
|
get #secondsOfDay() {
|
|
426
475
|
return this.#state.hour * SECONDS_PER_HOUR + this.#state.minute * SECONDS_PER_MINUTE + this.#state.second;
|
|
427
476
|
}
|
|
477
|
+
get #safeHourCycle() {
|
|
478
|
+
return this.#numbers.read(
|
|
479
|
+
this,
|
|
480
|
+
"hourCycle",
|
|
481
|
+
this.hourCycleValue,
|
|
482
|
+
_TimePickerController.values.hourCycle.default,
|
|
483
|
+
_TimePickerController.valueConstraints.hourCycle
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
get #safeStep() {
|
|
487
|
+
return this.#numbers.read(
|
|
488
|
+
this,
|
|
489
|
+
"step",
|
|
490
|
+
this.stepValue,
|
|
491
|
+
_TimePickerController.values.step.default,
|
|
492
|
+
_TimePickerController.valueConstraints.step
|
|
493
|
+
);
|
|
494
|
+
}
|
|
428
495
|
};
|
|
429
496
|
function modulo(value, modulus) {
|
|
430
497
|
return (value % modulus + modulus) % modulus;
|
|
@@ -434,5 +501,4 @@ function pad(value) {
|
|
|
434
501
|
}
|
|
435
502
|
|
|
436
503
|
export { TimePickerController };
|
|
437
|
-
//# sourceMappingURL=time_picker_controller.js.map
|
|
438
504
|
//# sourceMappingURL=time_picker_controller.js.map
|