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