stimeo-ui 0.17.0 → 0.18.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 +106 -1
- data/README.md +19 -10
- data/dist/cable/index.js +22 -14
- data/dist/controllers/accordion_controller.js +14 -6
- data/dist/controllers/alert_dialog_controller.js +145 -118
- data/dist/controllers/announcer_controller.js +113 -17
- data/dist/controllers/auto_submit_controller.js +185 -60
- data/dist/controllers/avatar_controller.js +55 -43
- data/dist/controllers/bulk_select_controller.js +64 -50
- data/dist/controllers/calendar_controller.js +55 -53
- data/dist/controllers/carousel_controller.js +124 -86
- data/dist/controllers/character_counter_controller.js +291 -95
- data/dist/controllers/checkbox_controller.js +6 -0
- data/dist/controllers/clipboard_controller.js +118 -27
- data/dist/controllers/collapsible_controller.js +52 -49
- data/dist/controllers/color_picker_controller.js +29 -7
- data/dist/controllers/combobox_controller.js +210 -71
- data/dist/controllers/command_palette_controller.js +289 -140
- data/dist/controllers/conditional_fields_controller.js +145 -58
- data/dist/controllers/confirm_controller.js +145 -118
- data/dist/controllers/context_menu_controller.js +138 -73
- data/dist/controllers/countdown_controller.js +109 -21
- data/dist/controllers/currency_input_controller.js +446 -72
- data/dist/controllers/data_grid_controller.js +134 -28
- data/dist/controllers/date_range_picker_controller.js +93 -72
- data/dist/controllers/dialog_controller.js +145 -118
- data/dist/controllers/direct_upload_controller.js +24 -14
- data/dist/controllers/dirty_form_controller.js +68 -31
- data/dist/controllers/dismissible_controller.js +64 -46
- data/dist/controllers/drawer_controller.js +145 -118
- data/dist/controllers/dropdown_controller.js +76 -63
- data/dist/controllers/editable_controller.js +199 -62
- data/dist/controllers/empty_state_controller.js +13 -6
- data/dist/controllers/file_dropzone_controller.js +55 -48
- data/dist/controllers/filter_controller.js +9 -2
- data/dist/controllers/flash_controller.js +66 -33
- data/dist/controllers/focus_controller.js +39 -14
- data/dist/controllers/form_field_controller.js +52 -43
- data/dist/controllers/form_validation_controller.js +18 -11
- data/dist/controllers/frame_loading_controller.js +55 -48
- data/dist/controllers/hover_card_controller.js +67 -61
- data/dist/controllers/idle_controller.js +10 -6
- data/dist/controllers/input_mask_controller.js +411 -24
- data/dist/controllers/intersection_controller.js +3 -0
- data/dist/controllers/lazy_frame_controller.js +15 -3
- data/dist/controllers/listbox_controller.js +260 -79
- data/dist/controllers/local_time_controller.js +48 -13
- data/dist/controllers/masonry_controller.js +11 -4
- data/dist/controllers/menu_controller.js +138 -82
- data/dist/controllers/menubar_controller.js +65 -19
- data/dist/controllers/meter_controller.js +270 -24
- data/dist/controllers/multi_select_controller.js +251 -94
- data/dist/controllers/navigation_menu_controller.js +67 -19
- data/dist/controllers/nested_form_controller.js +98 -64
- data/dist/controllers/network_status_controller.js +70 -52
- data/dist/controllers/number_input_controller.js +344 -94
- data/dist/controllers/optimistic_controller.js +29 -11
- data/dist/controllers/otp_controller.js +502 -104
- data/dist/controllers/overflow_indicator_controller.js +6 -0
- data/dist/controllers/overflow_menu_controller.js +136 -21
- data/dist/controllers/pagination_controller.js +47 -14
- data/dist/controllers/password_reveal_controller.js +52 -49
- data/dist/controllers/password_strength_controller.js +13 -6
- data/dist/controllers/persist_controller.js +48 -128
- data/dist/controllers/pointer_drag_controller.js +249 -23
- data/dist/controllers/popover_controller.js +82 -65
- data/dist/controllers/portal_controller.js +6 -0
- data/dist/controllers/preview_guard_controller.js +14 -3
- data/dist/controllers/progress_controller.js +271 -27
- data/dist/controllers/radio_group_controller.js +167 -80
- data/dist/controllers/range_slider_controller.js +27 -7
- data/dist/controllers/rating_controller.js +93 -57
- data/dist/controllers/read_more_controller.js +10 -6
- data/dist/controllers/reading_progress_controller.js +22 -5
- data/dist/controllers/relative_time_controller.js +75 -11
- data/dist/controllers/reset_on_restore_controller.js +6 -0
- data/dist/controllers/resizable_controller.js +55 -22
- data/dist/controllers/roving_controller.js +10 -4
- data/dist/controllers/scroll_area_controller.js +129 -88
- data/dist/controllers/scroll_restore_controller.js +14 -2
- data/dist/controllers/scroll_visibility_controller.js +93 -60
- data/dist/controllers/scrollspy_controller.js +11 -5
- data/dist/controllers/separator_controller.js +59 -43
- data/dist/controllers/sidebar_controller.js +150 -122
- data/dist/controllers/skeleton_controller.js +55 -48
- data/dist/controllers/slider_controller.js +27 -6
- data/dist/controllers/smart_sticky_header_controller.js +13 -5
- data/dist/controllers/sortable_controller.js +27 -14
- data/dist/controllers/spinner_controller.js +55 -48
- data/dist/controllers/step_indicator_controller.js +6 -0
- data/dist/controllers/stepper_controller.js +6 -0
- data/dist/controllers/stick_to_bottom_controller.js +19 -8
- data/dist/controllers/sticky_observer_controller.js +49 -43
- data/dist/controllers/submit_once_controller.js +145 -85
- data/dist/controllers/switch_controller.js +172 -28
- data/dist/controllers/tabs_controller.js +6 -1
- data/dist/controllers/tags_input_controller.js +157 -28
- data/dist/controllers/textarea_autosize_controller.js +40 -19
- data/dist/controllers/theme_controller.js +10 -4
- data/dist/controllers/time_picker_controller.js +192 -54
- data/dist/controllers/toast_controller.js +58 -27
- data/dist/controllers/toggle_group_controller.js +158 -56
- data/dist/controllers/toolbar_controller.js +150 -21
- data/dist/controllers/tree_view_controller.js +134 -25
- data/dist/index.js +2451 -1312
- data/dist/positioning/index.js +6 -0
- data/lib/generators/stimeo/install/install_generator.rb +7 -2
- data/lib/generators/stimeo/install/templates/stimeo.js +19 -6
- data/lib/stimeo/ui/version.rb +1 -1
- metadata +2 -2
|
@@ -157,6 +157,9 @@ var MicrotaskCoalescer = class {
|
|
|
157
157
|
this.#queued = false;
|
|
158
158
|
this.#generation += 1;
|
|
159
159
|
}
|
|
160
|
+
get pending() {
|
|
161
|
+
return this.#queued;
|
|
162
|
+
}
|
|
160
163
|
schedule() {
|
|
161
164
|
if (!this.#active || this.#queued) return;
|
|
162
165
|
this.#queued = true;
|
|
@@ -187,6 +190,9 @@ var MorphRenderWatcher = class {
|
|
|
187
190
|
schedule() {
|
|
188
191
|
this.#pass.schedule();
|
|
189
192
|
}
|
|
193
|
+
get pending() {
|
|
194
|
+
return this.#pass.pending;
|
|
195
|
+
}
|
|
190
196
|
disconnect() {
|
|
191
197
|
this.#listeners.dispose();
|
|
192
198
|
this.#pass.cancel();
|
|
@@ -35,6 +35,11 @@ function canTakeFocus(element) {
|
|
|
35
35
|
if (element.disabled) return false;
|
|
36
36
|
return !inheritsFieldsetDisabled(element);
|
|
37
37
|
}
|
|
38
|
+
function deepActiveElement(ownerDocument) {
|
|
39
|
+
let active = ownerDocument.activeElement;
|
|
40
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
41
|
+
return active;
|
|
42
|
+
}
|
|
38
43
|
|
|
39
44
|
var LayoutObserver = class {
|
|
40
45
|
#callback;
|
|
@@ -118,6 +123,9 @@ var MicrotaskCoalescer = class {
|
|
|
118
123
|
this.#queued = false;
|
|
119
124
|
this.#generation += 1;
|
|
120
125
|
}
|
|
126
|
+
get pending() {
|
|
127
|
+
return this.#queued;
|
|
128
|
+
}
|
|
121
129
|
schedule() {
|
|
122
130
|
if (!this.#active || this.#queued) return;
|
|
123
131
|
this.#queued = true;
|
|
@@ -148,6 +156,9 @@ var MorphRenderWatcher = class {
|
|
|
148
156
|
schedule() {
|
|
149
157
|
this.#pass.schedule();
|
|
150
158
|
}
|
|
159
|
+
get pending() {
|
|
160
|
+
return this.#pass.pending;
|
|
161
|
+
}
|
|
151
162
|
disconnect() {
|
|
152
163
|
this.#listeners.dispose();
|
|
153
164
|
this.#pass.cancel();
|
|
@@ -201,6 +212,94 @@ var NumberValueReader = class {
|
|
|
201
212
|
}
|
|
202
213
|
};
|
|
203
214
|
|
|
215
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
216
|
+
if (element.getAttribute(name) === value) return false;
|
|
217
|
+
if (value === null) element.removeAttribute(name);
|
|
218
|
+
else element.setAttribute(name, value);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
var OwnedText = class {
|
|
223
|
+
#attribute;
|
|
224
|
+
#channel;
|
|
225
|
+
#guard;
|
|
226
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
227
|
+
constructor(attribute, channel = "text", guard = () => true) {
|
|
228
|
+
this.#attribute = attribute;
|
|
229
|
+
this.#channel = channel;
|
|
230
|
+
this.#guard = guard;
|
|
231
|
+
}
|
|
232
|
+
hasRecord(element) {
|
|
233
|
+
return element.hasAttribute(this.#attribute);
|
|
234
|
+
}
|
|
235
|
+
read(element, evidence = {}) {
|
|
236
|
+
if (evidence.source === "live") return this.#writes.get(element) ?? null;
|
|
237
|
+
const raw = element.getAttribute(this.#attribute);
|
|
238
|
+
if (raw === null) return null;
|
|
239
|
+
try {
|
|
240
|
+
const record = JSON.parse(raw);
|
|
241
|
+
if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
|
|
242
|
+
return null;
|
|
243
|
+
return { original: record[1], written: record[2] };
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
owns(element, evidence = {}) {
|
|
249
|
+
return this.#ownedRecord(element, evidence) !== null;
|
|
250
|
+
}
|
|
251
|
+
#ownedRecord(element, evidence) {
|
|
252
|
+
const record = this.read(element, evidence);
|
|
253
|
+
if (record === null || !this.#guard(element)) return null;
|
|
254
|
+
if (this.#channel === "text" && element.firstElementChild !== null) return null;
|
|
255
|
+
const current = this.#current(element);
|
|
256
|
+
return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
|
|
257
|
+
}
|
|
258
|
+
write(element, written, original, force = false) {
|
|
259
|
+
const current = this.#current(element);
|
|
260
|
+
if (current === null) return;
|
|
261
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
262
|
+
if (force || current !== written) this.#put(element, written);
|
|
263
|
+
this.remember(element, written, before);
|
|
264
|
+
}
|
|
265
|
+
remember(element, written, original) {
|
|
266
|
+
const current = this.#current(element);
|
|
267
|
+
if (current === null) return;
|
|
268
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
269
|
+
this.#writes.set(element, { original: before, written });
|
|
270
|
+
setAttributeIfChanged(
|
|
271
|
+
element,
|
|
272
|
+
this.#attribute,
|
|
273
|
+
JSON.stringify([this.#channel, before, written])
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
clear(element, evidence = {}) {
|
|
277
|
+
if (!this.owns(element, evidence)) return false;
|
|
278
|
+
if (this.#current(element) !== "") this.#put(element, "");
|
|
279
|
+
this.forget(element);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
restore(element, evidence = {}) {
|
|
283
|
+
const record = this.#ownedRecord(element, evidence);
|
|
284
|
+
if (record === null) return false;
|
|
285
|
+
if (this.#current(element) !== record.original) this.#put(element, record.original);
|
|
286
|
+
this.forget(element);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
forget(element) {
|
|
290
|
+
setAttributeIfChanged(element, this.#attribute, null);
|
|
291
|
+
this.#writes.delete(element);
|
|
292
|
+
}
|
|
293
|
+
#current(element) {
|
|
294
|
+
if (this.#channel === "text") return element.textContent ?? "";
|
|
295
|
+
return element instanceof HTMLInputElement ? element.value : null;
|
|
296
|
+
}
|
|
297
|
+
#put(element, text) {
|
|
298
|
+
if (this.#channel === "text") element.textContent = text;
|
|
299
|
+
else if (element instanceof HTMLInputElement) element.value = text;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
204
303
|
var TimerRegistry = class {
|
|
205
304
|
ids = /* @__PURE__ */ new Set();
|
|
206
305
|
clear(id) {
|
|
@@ -261,6 +360,9 @@ function sharedRegistry(key, create) {
|
|
|
261
360
|
return registry;
|
|
262
361
|
}
|
|
263
362
|
|
|
363
|
+
function holdsFocus(element) {
|
|
364
|
+
return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
|
|
365
|
+
}
|
|
264
366
|
var TabindexLoan = class _TabindexLoan {
|
|
265
367
|
static #registry = sharedRegistry(
|
|
266
368
|
"stimeo-ui.tabindex-loan.registry.v1",
|
|
@@ -268,19 +370,22 @@ var TabindexLoan = class _TabindexLoan {
|
|
|
268
370
|
);
|
|
269
371
|
#value;
|
|
270
372
|
#record;
|
|
373
|
+
#preserveFocus;
|
|
271
374
|
#lent = /* @__PURE__ */ new Set();
|
|
272
|
-
constructor(value, owner) {
|
|
375
|
+
constructor(value, owner, options = {}) {
|
|
273
376
|
this.#value = value;
|
|
274
377
|
this.#record = `data-${owner}-tabindex-loan`;
|
|
378
|
+
this.#preserveFocus = options.preserveFocus === true;
|
|
275
379
|
_TabindexLoan.#registry.names.add(this.#record);
|
|
276
380
|
}
|
|
277
|
-
lend(element) {
|
|
381
|
+
lend(element, options = {}) {
|
|
278
382
|
const stack = this.#stack(element);
|
|
279
383
|
const current = element.getAttribute("tabindex");
|
|
280
384
|
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
281
385
|
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
282
386
|
);
|
|
283
387
|
const linked = stack.at(-1)?.value === current;
|
|
388
|
+
if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
|
|
284
389
|
if (!held && current !== null && !linked) return;
|
|
285
390
|
this.#lent.add(element);
|
|
286
391
|
if (held) held.loan = this;
|
|
@@ -290,19 +395,28 @@ var TabindexLoan = class _TabindexLoan {
|
|
|
290
395
|
element.setAttribute("tabindex", this.#value);
|
|
291
396
|
}
|
|
292
397
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
398
|
+
return(element) {
|
|
399
|
+
const stack = this.#stack(element);
|
|
400
|
+
const hold = stack.find((held) => held.loan === this) ?? stack.find(
|
|
401
|
+
(held) => held.loan === null && held.record === this.#record && held.value === this.#value
|
|
402
|
+
);
|
|
403
|
+
if (!hold) return false;
|
|
404
|
+
if (!this.#release(element, stack, hold, this.#preserveFocus)) {
|
|
405
|
+
hold.loan = this;
|
|
406
|
+
this.#lent.add(element);
|
|
407
|
+
return false;
|
|
298
408
|
}
|
|
299
|
-
this.#lent.
|
|
409
|
+
this.#lent.delete(element);
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
returnAll() {
|
|
413
|
+
for (const element of Array.from(this.#lent)) this.return(element);
|
|
300
414
|
}
|
|
301
415
|
reclaim(element) {
|
|
302
416
|
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
303
417
|
const stack = this.#stack(element);
|
|
304
418
|
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
305
|
-
if (hold && !element
|
|
419
|
+
if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
|
|
306
420
|
}
|
|
307
421
|
reclaimWithin(root) {
|
|
308
422
|
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
@@ -326,26 +440,26 @@ var TabindexLoan = class _TabindexLoan {
|
|
|
326
440
|
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
327
441
|
return holds;
|
|
328
442
|
}
|
|
329
|
-
#release(element, stack, hold) {
|
|
443
|
+
#release(element, stack, hold, preserveFocus = false) {
|
|
330
444
|
const current = element.getAttribute("tabindex");
|
|
331
445
|
const index = stack.indexOf(hold);
|
|
332
446
|
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
447
|
+
if (owns && preserveFocus && holdsFocus(element)) return false;
|
|
333
448
|
stack.splice(index, 1);
|
|
334
449
|
const above = stack[index];
|
|
335
450
|
if (above) above.linked &&= hold.linked;
|
|
336
451
|
this.#mark(element, stack);
|
|
337
|
-
if (!owns) return;
|
|
452
|
+
if (!owns) return true;
|
|
338
453
|
const below = hold.linked ? stack[index - 1] : void 0;
|
|
339
454
|
if (!below) element.removeAttribute("tabindex");
|
|
340
455
|
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
456
|
+
return true;
|
|
341
457
|
}
|
|
342
458
|
#mark(element, stack) {
|
|
343
459
|
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
344
460
|
for (const hold of stack) records.set(hold.record, hold.value);
|
|
345
461
|
for (const [name, recorded] of records) {
|
|
346
|
-
|
|
347
|
-
if (recorded === null) element.removeAttribute(name);
|
|
348
|
-
else element.setAttribute(name, recorded);
|
|
462
|
+
setAttributeIfChanged(element, name, recorded);
|
|
349
463
|
}
|
|
350
464
|
}
|
|
351
465
|
};
|
|
@@ -360,6 +474,11 @@ var MENU_TARGET = "data-stimeo--menu-target";
|
|
|
360
474
|
var BOOKKEEPING = [BANKED, INDEX, SAVED_ROLE, SAVED_TABINDEX, SAVED_MENU_TARGET];
|
|
361
475
|
var OWNED_LABEL = "owns-label";
|
|
362
476
|
var OverflowMenuController = class _OverflowMenuController extends Controller {
|
|
477
|
+
#labelText = new OwnedText(
|
|
478
|
+
this.#attr(OWNED_LABEL),
|
|
479
|
+
"text",
|
|
480
|
+
(trigger) => this.#carriesOnlyText(trigger)
|
|
481
|
+
);
|
|
363
482
|
#numbers = new NumberValueReader();
|
|
364
483
|
#attr(name) {
|
|
365
484
|
return `data-${this.identifier}-${name}`;
|
|
@@ -643,17 +762,13 @@ var OverflowMenuController = class _OverflowMenuController extends Controller {
|
|
|
643
762
|
this.#releaseLabel(trigger);
|
|
644
763
|
return;
|
|
645
764
|
}
|
|
646
|
-
|
|
647
|
-
trigger.textContent = label;
|
|
648
|
-
trigger.setAttribute(this.#attr(OWNED_LABEL), label);
|
|
765
|
+
this.#labelText.write(trigger, label);
|
|
649
766
|
}
|
|
650
767
|
#ownsLabel(trigger) {
|
|
651
|
-
return
|
|
768
|
+
return this.#labelText.owns(trigger);
|
|
652
769
|
}
|
|
653
770
|
#releaseLabel(trigger) {
|
|
654
|
-
|
|
655
|
-
trigger.textContent = "";
|
|
656
|
-
trigger.removeAttribute(this.#attr(OWNED_LABEL));
|
|
771
|
+
this.#labelText.clear(trigger);
|
|
657
772
|
}
|
|
658
773
|
#isBareTrigger(trigger) {
|
|
659
774
|
return (trigger.textContent ?? "").trim() === "" && this.#carriesOnlyText(trigger);
|
|
@@ -49,6 +49,11 @@ function canTakeFocus(element) {
|
|
|
49
49
|
if (element.disabled) return false;
|
|
50
50
|
return !inheritsFieldsetDisabled(element);
|
|
51
51
|
}
|
|
52
|
+
function deepActiveElement(ownerDocument) {
|
|
53
|
+
let active = ownerDocument.activeElement;
|
|
54
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
55
|
+
return active;
|
|
56
|
+
}
|
|
52
57
|
|
|
53
58
|
var ListenerSet = class {
|
|
54
59
|
#abort = new AbortController();
|
|
@@ -77,6 +82,9 @@ var MicrotaskCoalescer = class {
|
|
|
77
82
|
this.#queued = false;
|
|
78
83
|
this.#generation += 1;
|
|
79
84
|
}
|
|
85
|
+
get pending() {
|
|
86
|
+
return this.#queued;
|
|
87
|
+
}
|
|
80
88
|
schedule() {
|
|
81
89
|
if (!this.#active || this.#queued) return;
|
|
82
90
|
this.#queued = true;
|
|
@@ -107,6 +115,9 @@ var MorphRenderWatcher = class {
|
|
|
107
115
|
schedule() {
|
|
108
116
|
this.#pass.schedule();
|
|
109
117
|
}
|
|
118
|
+
get pending() {
|
|
119
|
+
return this.#pass.pending;
|
|
120
|
+
}
|
|
110
121
|
disconnect() {
|
|
111
122
|
this.#listeners.dispose();
|
|
112
123
|
this.#pass.cancel();
|
|
@@ -169,6 +180,13 @@ var NumberValueReader = class {
|
|
|
169
180
|
}
|
|
170
181
|
};
|
|
171
182
|
|
|
183
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
184
|
+
if (element.getAttribute(name) === value) return false;
|
|
185
|
+
if (value === null) element.removeAttribute(name);
|
|
186
|
+
else element.setAttribute(name, value);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
172
190
|
function queryOpenDescendants(root, selector) {
|
|
173
191
|
const matches = [];
|
|
174
192
|
const roots = [root];
|
|
@@ -195,6 +213,9 @@ function sharedRegistry(key, create) {
|
|
|
195
213
|
return registry;
|
|
196
214
|
}
|
|
197
215
|
|
|
216
|
+
function holdsFocus(element) {
|
|
217
|
+
return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
|
|
218
|
+
}
|
|
198
219
|
var TabindexLoan = class _TabindexLoan {
|
|
199
220
|
static #registry = sharedRegistry(
|
|
200
221
|
"stimeo-ui.tabindex-loan.registry.v1",
|
|
@@ -202,19 +223,22 @@ var TabindexLoan = class _TabindexLoan {
|
|
|
202
223
|
);
|
|
203
224
|
#value;
|
|
204
225
|
#record;
|
|
226
|
+
#preserveFocus;
|
|
205
227
|
#lent = /* @__PURE__ */ new Set();
|
|
206
|
-
constructor(value, owner) {
|
|
228
|
+
constructor(value, owner, options = {}) {
|
|
207
229
|
this.#value = value;
|
|
208
230
|
this.#record = `data-${owner}-tabindex-loan`;
|
|
231
|
+
this.#preserveFocus = options.preserveFocus === true;
|
|
209
232
|
_TabindexLoan.#registry.names.add(this.#record);
|
|
210
233
|
}
|
|
211
|
-
lend(element) {
|
|
234
|
+
lend(element, options = {}) {
|
|
212
235
|
const stack = this.#stack(element);
|
|
213
236
|
const current = element.getAttribute("tabindex");
|
|
214
237
|
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
215
238
|
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
216
239
|
);
|
|
217
240
|
const linked = stack.at(-1)?.value === current;
|
|
241
|
+
if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
|
|
218
242
|
if (!held && current !== null && !linked) return;
|
|
219
243
|
this.#lent.add(element);
|
|
220
244
|
if (held) held.loan = this;
|
|
@@ -224,19 +248,28 @@ var TabindexLoan = class _TabindexLoan {
|
|
|
224
248
|
element.setAttribute("tabindex", this.#value);
|
|
225
249
|
}
|
|
226
250
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
251
|
+
return(element) {
|
|
252
|
+
const stack = this.#stack(element);
|
|
253
|
+
const hold = stack.find((held) => held.loan === this) ?? stack.find(
|
|
254
|
+
(held) => held.loan === null && held.record === this.#record && held.value === this.#value
|
|
255
|
+
);
|
|
256
|
+
if (!hold) return false;
|
|
257
|
+
if (!this.#release(element, stack, hold, this.#preserveFocus)) {
|
|
258
|
+
hold.loan = this;
|
|
259
|
+
this.#lent.add(element);
|
|
260
|
+
return false;
|
|
232
261
|
}
|
|
233
|
-
this.#lent.
|
|
262
|
+
this.#lent.delete(element);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
returnAll() {
|
|
266
|
+
for (const element of Array.from(this.#lent)) this.return(element);
|
|
234
267
|
}
|
|
235
268
|
reclaim(element) {
|
|
236
269
|
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
237
270
|
const stack = this.#stack(element);
|
|
238
271
|
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
239
|
-
if (hold && !element
|
|
272
|
+
if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
|
|
240
273
|
}
|
|
241
274
|
reclaimWithin(root) {
|
|
242
275
|
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
@@ -260,26 +293,26 @@ var TabindexLoan = class _TabindexLoan {
|
|
|
260
293
|
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
261
294
|
return holds;
|
|
262
295
|
}
|
|
263
|
-
#release(element, stack, hold) {
|
|
296
|
+
#release(element, stack, hold, preserveFocus = false) {
|
|
264
297
|
const current = element.getAttribute("tabindex");
|
|
265
298
|
const index = stack.indexOf(hold);
|
|
266
299
|
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
300
|
+
if (owns && preserveFocus && holdsFocus(element)) return false;
|
|
267
301
|
stack.splice(index, 1);
|
|
268
302
|
const above = stack[index];
|
|
269
303
|
if (above) above.linked &&= hold.linked;
|
|
270
304
|
this.#mark(element, stack);
|
|
271
|
-
if (!owns) return;
|
|
305
|
+
if (!owns) return true;
|
|
272
306
|
const below = hold.linked ? stack[index - 1] : void 0;
|
|
273
307
|
if (!below) element.removeAttribute("tabindex");
|
|
274
308
|
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
309
|
+
return true;
|
|
275
310
|
}
|
|
276
311
|
#mark(element, stack) {
|
|
277
312
|
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
278
313
|
for (const hold of stack) records.set(hold.record, hold.value);
|
|
279
314
|
for (const [name, recorded] of records) {
|
|
280
|
-
|
|
281
|
-
if (recorded === null) element.removeAttribute(name);
|
|
282
|
-
else element.setAttribute(name, recorded);
|
|
315
|
+
setAttributeIfChanged(element, name, recorded);
|
|
283
316
|
}
|
|
284
317
|
}
|
|
285
318
|
};
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
4
|
+
if (element.getAttribute(name) === value) return false;
|
|
5
|
+
if (value === null) element.removeAttribute(name);
|
|
6
|
+
else element.setAttribute(name, value);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
function sharedRegistry(key, create) {
|
|
4
11
|
const symbol = Symbol.for(key);
|
|
5
12
|
const scope = globalThis;
|
|
6
13
|
const existing = scope[symbol];
|
|
7
14
|
if (existing !== void 0) return existing;
|
|
8
|
-
const
|
|
9
|
-
Object.defineProperty(globalThis, symbol, { value:
|
|
10
|
-
return
|
|
15
|
+
const registry2 = create();
|
|
16
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
17
|
+
return registry2;
|
|
11
18
|
}
|
|
12
19
|
|
|
20
|
+
var registry = sharedRegistry(
|
|
21
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
22
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
23
|
+
);
|
|
13
24
|
function isAttributeValue(value) {
|
|
14
25
|
return value === null || typeof value === "string";
|
|
15
26
|
}
|
|
@@ -26,24 +37,46 @@ function readLeaseRecord(element, record) {
|
|
|
26
37
|
return void 0;
|
|
27
38
|
}
|
|
28
39
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
);
|
|
40
|
+
function attributeStack(element, attribute) {
|
|
41
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
42
|
+
registry.stacks.set(element, byAttribute);
|
|
43
|
+
const known = byAttribute.get(attribute);
|
|
44
|
+
if (known) return known;
|
|
45
|
+
const found = [];
|
|
46
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
47
|
+
const read = readLeaseRecord(element, record);
|
|
48
|
+
if (read) found.push({ ...read, record });
|
|
49
|
+
}
|
|
50
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
51
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
52
|
+
lease: null,
|
|
53
|
+
record,
|
|
54
|
+
written,
|
|
55
|
+
original,
|
|
56
|
+
linked: linked && index > 0
|
|
57
|
+
}));
|
|
58
|
+
byAttribute.set(attribute, stack);
|
|
59
|
+
return stack;
|
|
60
|
+
}
|
|
61
|
+
function findHold(stack, lease, record) {
|
|
62
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
63
|
+
}
|
|
64
|
+
var AttributeLease = class {
|
|
34
65
|
#attribute;
|
|
35
66
|
#record;
|
|
36
67
|
#base;
|
|
68
|
+
#preserveAuthor;
|
|
37
69
|
#held = /* @__PURE__ */ new Set();
|
|
38
70
|
constructor(attribute, owner, options = {}) {
|
|
39
71
|
this.#attribute = attribute;
|
|
40
72
|
this.#record = `data-${owner}-${attribute}-lease`;
|
|
41
73
|
this.#base = options.base === true;
|
|
42
|
-
|
|
74
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
75
|
+
const names = registry.names;
|
|
43
76
|
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
44
77
|
}
|
|
45
78
|
write(element, value) {
|
|
46
|
-
const stack = this.#
|
|
79
|
+
const stack = attributeStack(element, this.#attribute);
|
|
47
80
|
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
48
81
|
this.#held.add(element);
|
|
49
82
|
const shows = !this.#covered(element, stack, hold);
|
|
@@ -53,8 +86,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
53
86
|
}
|
|
54
87
|
return(element) {
|
|
55
88
|
const held = this.#held.delete(element);
|
|
56
|
-
const known =
|
|
57
|
-
const stack = held || known || element.hasAttribute(this.#record) ? this.#
|
|
89
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
90
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
58
91
|
const hold = this.#take(stack);
|
|
59
92
|
if (!hold) return false;
|
|
60
93
|
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
@@ -72,31 +105,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
72
105
|
returnAll() {
|
|
73
106
|
for (const element of Array.from(this.#held)) this.return(element);
|
|
74
107
|
}
|
|
75
|
-
#stack(element) {
|
|
76
|
-
const stacks = _AttributeLease.#registry.stacks;
|
|
77
|
-
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
78
|
-
stacks.set(element, byAttribute);
|
|
79
|
-
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
80
|
-
byAttribute.set(this.#attribute, stack);
|
|
81
|
-
return stack;
|
|
82
|
-
}
|
|
83
|
-
#seed(element) {
|
|
84
|
-
const found = [];
|
|
85
|
-
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
86
|
-
const read = readLeaseRecord(element, record);
|
|
87
|
-
if (read) found.push({ ...read, record });
|
|
88
|
-
}
|
|
89
|
-
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
90
|
-
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
91
|
-
lease: null,
|
|
92
|
-
record,
|
|
93
|
-
written,
|
|
94
|
-
original,
|
|
95
|
-
linked: linked && index > 0
|
|
96
|
-
}));
|
|
97
|
-
}
|
|
98
108
|
#take(stack) {
|
|
99
|
-
const hold = stack
|
|
109
|
+
const hold = findHold(stack, this, this.#record);
|
|
100
110
|
if (hold) hold.lease = this;
|
|
101
111
|
return hold;
|
|
102
112
|
}
|
|
@@ -105,7 +115,7 @@ var AttributeLease = class _AttributeLease {
|
|
|
105
115
|
const top = stack.at(-1);
|
|
106
116
|
const bottom = stack[0];
|
|
107
117
|
const over = top?.written === current ? top : void 0;
|
|
108
|
-
const from = this.#base ? bottom : over;
|
|
118
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
109
119
|
const hold = {
|
|
110
120
|
lease: this,
|
|
111
121
|
record: this.#record,
|
|
@@ -135,15 +145,11 @@ var AttributeLease = class _AttributeLease {
|
|
|
135
145
|
);
|
|
136
146
|
});
|
|
137
147
|
for (const [name, recorded] of records) {
|
|
138
|
-
|
|
139
|
-
if (recorded === null) element.removeAttribute(name);
|
|
140
|
-
else element.setAttribute(name, recorded);
|
|
148
|
+
setAttributeIfChanged(element, name, recorded);
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
#reflect(element, value) {
|
|
144
|
-
|
|
145
|
-
if (value === null) element.removeAttribute(this.#attribute);
|
|
146
|
-
else element.setAttribute(this.#attribute, value);
|
|
152
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
147
153
|
}
|
|
148
154
|
};
|
|
149
155
|
|
|
@@ -278,9 +284,8 @@ var StateRegions = class _StateRegions {
|
|
|
278
284
|
const below = hold.linked ? stack[index - 1] : void 0;
|
|
279
285
|
if (below) {
|
|
280
286
|
if (region.hidden !== below.written) region.hidden = below.written;
|
|
281
|
-
} else
|
|
282
|
-
|
|
283
|
-
else region.setAttribute("hidden", hold.authored);
|
|
287
|
+
} else {
|
|
288
|
+
setAttributeIfChanged(region, "hidden", hold.authored);
|
|
284
289
|
}
|
|
285
290
|
}
|
|
286
291
|
this.#mark(region, stack);
|
|
@@ -355,9 +360,7 @@ var StateRegions = class _StateRegions {
|
|
|
355
360
|
);
|
|
356
361
|
});
|
|
357
362
|
for (const [name, recorded] of records) {
|
|
358
|
-
|
|
359
|
-
if (recorded === null) region.removeAttribute(name);
|
|
360
|
-
else region.setAttribute(name, recorded);
|
|
363
|
+
setAttributeIfChanged(region, name, recorded);
|
|
361
364
|
}
|
|
362
365
|
}
|
|
363
366
|
#recorded(region, name) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
function fillTemplate(template, values) {
|
|
4
|
+
return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
|
|
5
|
+
const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
|
|
6
|
+
return replacement === void 0 ? match : String(replacement);
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
function announce(message, options = {}) {
|
|
4
11
|
const text = message.trim();
|
|
5
12
|
if (text.length === 0) return;
|
|
@@ -9,12 +16,6 @@ function announce(message, options = {}) {
|
|
|
9
16
|
})
|
|
10
17
|
);
|
|
11
18
|
}
|
|
12
|
-
function fillTemplate(template, values) {
|
|
13
|
-
return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
|
|
14
|
-
const replacement = values[name];
|
|
15
|
-
return replacement === void 0 ? match : String(replacement);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
19
|
|
|
19
20
|
var NUMBER_BOUNDS = {
|
|
20
21
|
nonNegative: { finite: true, min: 0 }};
|
|
@@ -69,6 +70,9 @@ var MicrotaskCoalescer = class {
|
|
|
69
70
|
this.#queued = false;
|
|
70
71
|
this.#generation += 1;
|
|
71
72
|
}
|
|
73
|
+
get pending() {
|
|
74
|
+
return this.#queued;
|
|
75
|
+
}
|
|
72
76
|
schedule() {
|
|
73
77
|
if (!this.#active || this.#queued) return;
|
|
74
78
|
this.#queued = true;
|
|
@@ -99,6 +103,9 @@ var MorphRenderWatcher = class {
|
|
|
99
103
|
schedule() {
|
|
100
104
|
this.#pass.schedule();
|
|
101
105
|
}
|
|
106
|
+
get pending() {
|
|
107
|
+
return this.#pass.pending;
|
|
108
|
+
}
|
|
102
109
|
disconnect() {
|
|
103
110
|
this.#listeners.dispose();
|
|
104
111
|
this.#pass.cancel();
|