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
|
@@ -5,11 +5,15 @@ function sharedRegistry(key, create) {
|
|
|
5
5
|
const scope = globalThis;
|
|
6
6
|
const existing = scope[symbol];
|
|
7
7
|
if (existing !== void 0) return existing;
|
|
8
|
-
const
|
|
9
|
-
Object.defineProperty(globalThis, symbol, { value:
|
|
10
|
-
return
|
|
8
|
+
const registry2 = create();
|
|
9
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
10
|
+
return registry2;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
sharedRegistry(
|
|
14
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
15
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
16
|
+
);
|
|
13
17
|
function isAttributeValue(value) {
|
|
14
18
|
return value === null || typeof value === "string";
|
|
15
19
|
}
|
|
@@ -35,126 +39,37 @@ function leasedAuthorValue(element, attribute) {
|
|
|
35
39
|
}
|
|
36
40
|
return void 0;
|
|
37
41
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
write(element, value) {
|
|
55
|
-
const stack = this.#stack(element);
|
|
56
|
-
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
57
|
-
this.#held.add(element);
|
|
58
|
-
const shows = !this.#covered(element, stack, hold);
|
|
59
|
-
hold.written = value;
|
|
60
|
-
this.#mark(element, stack);
|
|
61
|
-
if (shows) this.#reflect(element, value);
|
|
62
|
-
}
|
|
63
|
-
return(element) {
|
|
64
|
-
const held = this.#held.delete(element);
|
|
65
|
-
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
66
|
-
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
67
|
-
const hold = this.#take(stack);
|
|
68
|
-
if (!hold) return false;
|
|
69
|
-
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
70
|
-
const owns = carries && !this.#covered(element, stack, hold);
|
|
71
|
-
const index = stack.indexOf(hold);
|
|
72
|
-
stack.splice(index, 1);
|
|
73
|
-
const above = stack[index];
|
|
74
|
-
if (above) above.linked &&= hold.linked;
|
|
75
|
-
this.#mark(element, stack);
|
|
76
|
-
if (!owns) return true;
|
|
77
|
-
const below = hold.linked ? stack[index - 1] : void 0;
|
|
78
|
-
this.#reflect(element, below ? below.written : hold.original);
|
|
79
|
-
return true;
|
|
42
|
+
|
|
43
|
+
var PREFIX = "__stimeo_raw_v1_";
|
|
44
|
+
sharedRegistry(
|
|
45
|
+
"stimeo-ui.form-data-projection.registry.v1",
|
|
46
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
47
|
+
);
|
|
48
|
+
function decodedName(key) {
|
|
49
|
+
if (!key.startsWith(PREFIX)) return null;
|
|
50
|
+
const separator = PREFIX.length + 32;
|
|
51
|
+
if (key[separator] !== ":") return null;
|
|
52
|
+
try {
|
|
53
|
+
const name = JSON.parse(key.slice(separator + 1));
|
|
54
|
+
return typeof name === "string" ? name : null;
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
80
57
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return stack;
|
|
91
|
-
}
|
|
92
|
-
#seed(element) {
|
|
93
|
-
const found = [];
|
|
94
|
-
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
95
|
-
const read = readLeaseRecord(element, record);
|
|
96
|
-
if (read) found.push({ ...read, record });
|
|
97
|
-
}
|
|
98
|
-
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
99
|
-
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
100
|
-
lease: null,
|
|
101
|
-
record,
|
|
102
|
-
written,
|
|
103
|
-
original,
|
|
104
|
-
linked: linked && index > 0
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
#take(stack) {
|
|
108
|
-
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
109
|
-
if (hold) hold.lease = this;
|
|
110
|
-
return hold;
|
|
111
|
-
}
|
|
112
|
-
#acquire(element, stack) {
|
|
113
|
-
const current = element.getAttribute(this.#attribute);
|
|
114
|
-
const top = stack.at(-1);
|
|
115
|
-
const bottom = stack[0];
|
|
116
|
-
const over = top?.written === current ? top : void 0;
|
|
117
|
-
const from = this.#base ? bottom : over;
|
|
118
|
-
const hold = {
|
|
119
|
-
lease: this,
|
|
120
|
-
record: this.#record,
|
|
121
|
-
written: current,
|
|
122
|
-
original: from ? from.original : current,
|
|
123
|
-
linked: !this.#base && over !== void 0
|
|
124
|
-
};
|
|
125
|
-
if (this.#base && bottom) {
|
|
126
|
-
bottom.linked = true;
|
|
127
|
-
stack.unshift(hold);
|
|
128
|
-
} else stack.push(hold);
|
|
129
|
-
return hold;
|
|
130
|
-
}
|
|
131
|
-
#covered(element, stack, hold) {
|
|
132
|
-
const current = element.getAttribute(this.#attribute);
|
|
133
|
-
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
134
|
-
}
|
|
135
|
-
#mark(element, stack) {
|
|
136
|
-
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
137
|
-
stack.forEach((hold, index) => {
|
|
138
|
-
const names = stack.slice(0, index).map((under) => under.record);
|
|
139
|
-
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
140
|
-
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
141
|
-
records.set(
|
|
142
|
-
hold.record,
|
|
143
|
-
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
144
|
-
);
|
|
145
|
-
});
|
|
146
|
-
for (const [name, recorded] of records) {
|
|
147
|
-
if (element.getAttribute(name) === recorded) continue;
|
|
148
|
-
if (recorded === null) element.removeAttribute(name);
|
|
149
|
-
else element.setAttribute(name, recorded);
|
|
150
|
-
}
|
|
58
|
+
}
|
|
59
|
+
function projectedFieldName(name) {
|
|
60
|
+
return decodedName(name) ?? name;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var ListenerSet = class {
|
|
64
|
+
#abort = new AbortController();
|
|
65
|
+
add(target, type, handler, options) {
|
|
66
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
151
67
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
else element.setAttribute(this.#attribute, value);
|
|
68
|
+
dispose() {
|
|
69
|
+
this.#abort.abort();
|
|
70
|
+
this.#abort = new AbortController();
|
|
156
71
|
}
|
|
157
|
-
}
|
|
72
|
+
};
|
|
158
73
|
|
|
159
74
|
var MicrotaskCoalescer = class {
|
|
160
75
|
#run;
|
|
@@ -172,6 +87,9 @@ var MicrotaskCoalescer = class {
|
|
|
172
87
|
this.#queued = false;
|
|
173
88
|
this.#generation += 1;
|
|
174
89
|
}
|
|
90
|
+
get pending() {
|
|
91
|
+
return this.#queued;
|
|
92
|
+
}
|
|
175
93
|
schedule() {
|
|
176
94
|
if (!this.#active || this.#queued) return;
|
|
177
95
|
this.#queued = true;
|
|
@@ -364,6 +282,7 @@ var PersistController = class _PersistController extends Controller {
|
|
|
364
282
|
#logicalKey = null;
|
|
365
283
|
#payload = null;
|
|
366
284
|
#registeredClearOn = null;
|
|
285
|
+
#clearOnListeners = new ListenerSet();
|
|
367
286
|
#excluded = DEFAULT_EXCLUDE;
|
|
368
287
|
#knownFields = /* @__PURE__ */ new WeakSet();
|
|
369
288
|
#forcedRestore = /* @__PURE__ */ new Set();
|
|
@@ -377,13 +296,14 @@ var PersistController = class _PersistController extends Controller {
|
|
|
377
296
|
#onClearEvent = () => {
|
|
378
297
|
this.clear();
|
|
379
298
|
};
|
|
299
|
+
#listeners = new ListenerSet();
|
|
380
300
|
connect() {
|
|
381
301
|
if (this.#connected) return;
|
|
382
302
|
this.#connected = true;
|
|
383
303
|
this.#logicalKey = this.#resolveLogicalKey();
|
|
384
304
|
this.#excluded = parseStringList(this.excludeValue, DEFAULT_EXCLUDE);
|
|
385
|
-
this.element
|
|
386
|
-
this.element
|
|
305
|
+
this.#listeners.add(this.element, "input", this.#onInput);
|
|
306
|
+
this.#listeners.add(this.element, "change", this.#onInput);
|
|
387
307
|
this.#syncClearOn();
|
|
388
308
|
this.#restoreDynamic.activate();
|
|
389
309
|
this.#observer.observe(this.element, {
|
|
@@ -408,8 +328,7 @@ var PersistController = class _PersistController extends Controller {
|
|
|
408
328
|
disconnect() {
|
|
409
329
|
this.#flushPendingSave();
|
|
410
330
|
this.#connected = false;
|
|
411
|
-
this.
|
|
412
|
-
this.element.removeEventListener("change", this.#onInput);
|
|
331
|
+
this.#listeners.dispose();
|
|
413
332
|
this.#unbindClearOn();
|
|
414
333
|
this.#observer.disconnect();
|
|
415
334
|
this.#restoreDynamic.cancel();
|
|
@@ -631,11 +550,12 @@ var PersistController = class _PersistController extends Controller {
|
|
|
631
550
|
if (NON_VALUE_TYPES.has(field.type) || SENSITIVE_TYPES.has(field.type)) return false;
|
|
632
551
|
if (SENSITIVE_TYPES.has(leasedAuthorValue(field, "type") ?? "")) return false;
|
|
633
552
|
if (excluded.includes(field.type)) return false;
|
|
634
|
-
|
|
553
|
+
const name = projectedFieldName(field.name);
|
|
554
|
+
if (name.length > 0 && excluded.includes(name)) return false;
|
|
635
555
|
return true;
|
|
636
556
|
}
|
|
637
557
|
#keyOf(field) {
|
|
638
|
-
return field.name || field.id || null;
|
|
558
|
+
return projectedFieldName(field.name) || field.id || null;
|
|
639
559
|
}
|
|
640
560
|
#ownsField(field) {
|
|
641
561
|
return field.closest(`[data-controller~="${this.identifier}"]`) === this.element;
|
|
@@ -655,12 +575,12 @@ var PersistController = class _PersistController extends Controller {
|
|
|
655
575
|
if (next === this.#registeredClearOn) return;
|
|
656
576
|
this.#unbindClearOn();
|
|
657
577
|
if (next === null) return;
|
|
658
|
-
this.element
|
|
578
|
+
this.#clearOnListeners.add(this.element, next, this.#onClearEvent);
|
|
659
579
|
this.#registeredClearOn = next;
|
|
660
580
|
}
|
|
661
581
|
#unbindClearOn() {
|
|
662
582
|
if (this.#registeredClearOn === null) return;
|
|
663
|
-
this.
|
|
583
|
+
this.#clearOnListeners.dispose();
|
|
664
584
|
this.#registeredClearOn = null;
|
|
665
585
|
}
|
|
666
586
|
#validEventName(value) {
|
|
@@ -4,6 +4,9 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
4
4
|
if (!event.key.startsWith("Arrow")) return false;
|
|
5
5
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
6
6
|
}
|
|
7
|
+
function hasModifierChord(event) {
|
|
8
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
var DetachGate = class _DetachGate {
|
|
9
12
|
#pending = false;
|
|
@@ -50,6 +53,33 @@ function ownerOf(candidates, node) {
|
|
|
50
53
|
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
function unslotted(element) {
|
|
57
|
+
return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
|
|
58
|
+
}
|
|
59
|
+
function flatTreeParent(element) {
|
|
60
|
+
if (unslotted(element)) return null;
|
|
61
|
+
const parent = element.assignedSlot ?? element.parentNode;
|
|
62
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
63
|
+
return parent instanceof Element ? parent : null;
|
|
64
|
+
}
|
|
65
|
+
function flatTreeContains(ancestor, element) {
|
|
66
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
67
|
+
if (node === ancestor) return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
function closestInFlatTree(element, selector) {
|
|
72
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
73
|
+
if (node.matches(selector)) return node;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function deepActiveElement(ownerDocument) {
|
|
78
|
+
let active = ownerDocument.activeElement;
|
|
79
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
80
|
+
return active;
|
|
81
|
+
}
|
|
82
|
+
|
|
53
83
|
var ListenerSet = class {
|
|
54
84
|
#abort = new AbortController();
|
|
55
85
|
add(target, type, handler, options) {
|
|
@@ -77,6 +107,9 @@ var MicrotaskCoalescer = class {
|
|
|
77
107
|
this.#queued = false;
|
|
78
108
|
this.#generation += 1;
|
|
79
109
|
}
|
|
110
|
+
get pending() {
|
|
111
|
+
return this.#queued;
|
|
112
|
+
}
|
|
80
113
|
schedule() {
|
|
81
114
|
if (!this.#active || this.#queued) return;
|
|
82
115
|
this.#queued = true;
|
|
@@ -107,6 +140,9 @@ var MorphRenderWatcher = class {
|
|
|
107
140
|
schedule() {
|
|
108
141
|
this.#pass.schedule();
|
|
109
142
|
}
|
|
143
|
+
get pending() {
|
|
144
|
+
return this.#pass.pending;
|
|
145
|
+
}
|
|
110
146
|
disconnect() {
|
|
111
147
|
this.#listeners.dispose();
|
|
112
148
|
this.#pass.cancel();
|
|
@@ -202,6 +238,143 @@ var OwnedPointerSession = class {
|
|
|
202
238
|
};
|
|
203
239
|
};
|
|
204
240
|
|
|
241
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
242
|
+
if (element.getAttribute(name) === value) return false;
|
|
243
|
+
if (value === null) element.removeAttribute(name);
|
|
244
|
+
else element.setAttribute(name, value);
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function queryOpenDescendants(root, selector) {
|
|
249
|
+
const matches = [];
|
|
250
|
+
const roots = [root];
|
|
251
|
+
for (const scope of roots) {
|
|
252
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
253
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
254
|
+
const shadow = scope.shadowRoot;
|
|
255
|
+
if (shadow) roots.push(shadow);
|
|
256
|
+
}
|
|
257
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
258
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return matches;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function sharedRegistry(key, create) {
|
|
265
|
+
const symbol = Symbol.for(key);
|
|
266
|
+
const scope = globalThis;
|
|
267
|
+
const existing = scope[symbol];
|
|
268
|
+
if (existing !== void 0) return existing;
|
|
269
|
+
const registry = create();
|
|
270
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
271
|
+
return registry;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function holdsFocus(element) {
|
|
275
|
+
return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
|
|
276
|
+
}
|
|
277
|
+
var TabindexLoan = class _TabindexLoan {
|
|
278
|
+
static #registry = sharedRegistry(
|
|
279
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
280
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
281
|
+
);
|
|
282
|
+
#value;
|
|
283
|
+
#record;
|
|
284
|
+
#preserveFocus;
|
|
285
|
+
#lent = /* @__PURE__ */ new Set();
|
|
286
|
+
constructor(value, owner, options = {}) {
|
|
287
|
+
this.#value = value;
|
|
288
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
289
|
+
this.#preserveFocus = options.preserveFocus === true;
|
|
290
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
291
|
+
}
|
|
292
|
+
lend(element, options = {}) {
|
|
293
|
+
const stack = this.#stack(element);
|
|
294
|
+
const current = element.getAttribute("tabindex");
|
|
295
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
296
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
297
|
+
);
|
|
298
|
+
const linked = stack.at(-1)?.value === current;
|
|
299
|
+
if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
|
|
300
|
+
if (!held && current !== null && !linked) return;
|
|
301
|
+
this.#lent.add(element);
|
|
302
|
+
if (held) held.loan = this;
|
|
303
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
304
|
+
this.#mark(element, stack);
|
|
305
|
+
if (current === null || !held && current !== this.#value) {
|
|
306
|
+
element.setAttribute("tabindex", this.#value);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return(element) {
|
|
310
|
+
const stack = this.#stack(element);
|
|
311
|
+
const hold = stack.find((held) => held.loan === this) ?? stack.find(
|
|
312
|
+
(held) => held.loan === null && held.record === this.#record && held.value === this.#value
|
|
313
|
+
);
|
|
314
|
+
if (!hold) return false;
|
|
315
|
+
if (!this.#release(element, stack, hold, this.#preserveFocus)) {
|
|
316
|
+
hold.loan = this;
|
|
317
|
+
this.#lent.add(element);
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
this.#lent.delete(element);
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
returnAll() {
|
|
324
|
+
for (const element of Array.from(this.#lent)) this.return(element);
|
|
325
|
+
}
|
|
326
|
+
reclaim(element) {
|
|
327
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
328
|
+
const stack = this.#stack(element);
|
|
329
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
330
|
+
if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
|
|
331
|
+
}
|
|
332
|
+
reclaimWithin(root) {
|
|
333
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
334
|
+
this.reclaim(element);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
#stack(element) {
|
|
338
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
339
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
340
|
+
stacks.set(element, stack);
|
|
341
|
+
return stack;
|
|
342
|
+
}
|
|
343
|
+
#seed(element) {
|
|
344
|
+
const current = element.getAttribute("tabindex");
|
|
345
|
+
const holds = [];
|
|
346
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
347
|
+
const value = element.getAttribute(record);
|
|
348
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
349
|
+
}
|
|
350
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
351
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
352
|
+
return holds;
|
|
353
|
+
}
|
|
354
|
+
#release(element, stack, hold, preserveFocus = false) {
|
|
355
|
+
const current = element.getAttribute("tabindex");
|
|
356
|
+
const index = stack.indexOf(hold);
|
|
357
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
358
|
+
if (owns && preserveFocus && holdsFocus(element)) return false;
|
|
359
|
+
stack.splice(index, 1);
|
|
360
|
+
const above = stack[index];
|
|
361
|
+
if (above) above.linked &&= hold.linked;
|
|
362
|
+
this.#mark(element, stack);
|
|
363
|
+
if (!owns) return true;
|
|
364
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
365
|
+
if (!below) element.removeAttribute("tabindex");
|
|
366
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
#mark(element, stack) {
|
|
370
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
371
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
372
|
+
for (const [name, recorded] of records) {
|
|
373
|
+
setAttributeIfChanged(element, name, recorded);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
205
378
|
var TransientHooks = class {
|
|
206
379
|
#attributes;
|
|
207
380
|
constructor(options) {
|
|
@@ -232,9 +405,13 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
232
405
|
static actions = ["reset"];
|
|
233
406
|
static events = ["start", "move", "end", "cancel"];
|
|
234
407
|
static #TOUCH_ACTION_MARKER = "data-pointer-drag-touch-action";
|
|
235
|
-
|
|
408
|
+
#tabindex = new TabindexLoan("0", this.identifier, { preserveFocus: true });
|
|
236
409
|
#pointer = null;
|
|
237
410
|
#keyboard = null;
|
|
411
|
+
#keyboardFocusSession = null;
|
|
412
|
+
#listeners = new ListenerSet();
|
|
413
|
+
#shadowFocus = new ListenerSet();
|
|
414
|
+
#keyboardFocus = new MicrotaskCoalescer(() => this.#settleKeyboardFocus());
|
|
238
415
|
#followBase = { x: 0, y: 0 };
|
|
239
416
|
#gestureFollows = false;
|
|
240
417
|
#followOutput = null;
|
|
@@ -249,14 +426,23 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
249
426
|
this.#gate.cancel();
|
|
250
427
|
if (!this.#pointer) TRANSIENT_DRAG.reset(this.element);
|
|
251
428
|
if (!this.#keyboard) TRANSIENT_GRAB.reset(this.element);
|
|
252
|
-
this.element
|
|
253
|
-
this.element
|
|
429
|
+
this.#listeners.add(this.element, "pointerdown", this.#onPointerDown);
|
|
430
|
+
this.#listeners.add(this.element, "keydown", this.#onKeydown);
|
|
431
|
+
this.#listeners.add(this.element, "focusout", this.#onFocusout);
|
|
432
|
+
this.#listeners.add(this.element, "focusin", this.#onFocusin);
|
|
433
|
+
this.#keyboardFocus.activate();
|
|
254
434
|
if (!this.hasHandleTarget) this.#prepareHandle(this.element);
|
|
435
|
+
if (this.#keyboard) {
|
|
436
|
+
this.#watchShadowFocus(this.#keyboard.handle);
|
|
437
|
+
this.#checkKeyboardFocus(this.#keyboard);
|
|
438
|
+
}
|
|
255
439
|
}
|
|
256
440
|
disconnect() {
|
|
257
441
|
this.#morphRender.disconnect();
|
|
258
|
-
this.
|
|
259
|
-
this.
|
|
442
|
+
this.#listeners.dispose();
|
|
443
|
+
this.#shadowFocus.dispose();
|
|
444
|
+
this.#keyboardFocus.cancel();
|
|
445
|
+
this.#keyboardFocusSession = null;
|
|
260
446
|
if (this.#pointer || this.#keyboard) {
|
|
261
447
|
this.#gate.disconnected(this, () => this.#teardown());
|
|
262
448
|
return;
|
|
@@ -264,9 +450,11 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
264
450
|
this.#teardown();
|
|
265
451
|
}
|
|
266
452
|
reset() {
|
|
453
|
+
const follows = this.#pointer || this.#keyboard ? this.#gestureFollows : this.followValue;
|
|
267
454
|
const live = this.#pointer?.started ? this.#pointer.pointerType : this.#keyboard ? "keyboard" : null;
|
|
268
455
|
if (this.#pointer || this.#keyboard) this.#teardownSessions();
|
|
269
456
|
if (live) this.#dispatchCancel(live);
|
|
457
|
+
if (!follows) return;
|
|
270
458
|
this.#followBase = { x: 0, y: 0 };
|
|
271
459
|
this.#applyFollow(0, 0);
|
|
272
460
|
}
|
|
@@ -298,7 +486,8 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
298
486
|
}
|
|
299
487
|
this.#teardownSessions();
|
|
300
488
|
}
|
|
301
|
-
#onPointerDown = (
|
|
489
|
+
#onPointerDown = (rawEvent) => {
|
|
490
|
+
const event = rawEvent;
|
|
302
491
|
if (this.disabledValue || this.#pointer || this.#keyboard || event.button !== 0) return;
|
|
303
492
|
const handle = this.#handleFor(event.target);
|
|
304
493
|
if (!handle) return;
|
|
@@ -353,13 +542,14 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
353
542
|
}
|
|
354
543
|
this.#dispatchCancel(pointerType);
|
|
355
544
|
}
|
|
356
|
-
#onKeydown = (
|
|
545
|
+
#onKeydown = (rawEvent) => {
|
|
546
|
+
const event = rawEvent;
|
|
357
547
|
if (this.disabledValue) return;
|
|
358
548
|
const handle = this.#handleFor(event.target);
|
|
359
549
|
if (!handle) return;
|
|
360
550
|
if (event.defaultPrevented) return;
|
|
361
551
|
if (event.isComposing) return;
|
|
362
|
-
if (this.#ownsNativeKeys(event.target, handle)) return;
|
|
552
|
+
if (this.#ownsNativeKeys(event.composedPath()[0] ?? event.target, handle)) return;
|
|
363
553
|
if (isReservedArrowChord(event)) return;
|
|
364
554
|
if (event.key === "Escape") {
|
|
365
555
|
if (this.#pointer?.started) {
|
|
@@ -383,6 +573,7 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
383
573
|
}
|
|
384
574
|
if (!this.#keyboard) return;
|
|
385
575
|
if (event.key === "Home" || event.key === "End") {
|
|
576
|
+
if (hasModifierChord(event)) return;
|
|
386
577
|
event.preventDefault();
|
|
387
578
|
return;
|
|
388
579
|
}
|
|
@@ -404,9 +595,50 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
404
595
|
}
|
|
405
596
|
});
|
|
406
597
|
};
|
|
598
|
+
#onFocusin = () => {
|
|
599
|
+
const session = this.#keyboard;
|
|
600
|
+
if (session) {
|
|
601
|
+
this.#watchShadowFocus(session.handle);
|
|
602
|
+
this.#checkKeyboardFocus(session);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
#watchShadowFocus(handle) {
|
|
606
|
+
const tree = handle.getRootNode();
|
|
607
|
+
let active = tree.activeElement;
|
|
608
|
+
while (active?.shadowRoot) {
|
|
609
|
+
const shadow = active.shadowRoot;
|
|
610
|
+
this.#shadowFocus.add(shadow, "focusin", this.#onFocusin);
|
|
611
|
+
active = shadow.activeElement;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
#onFocusout = (rawEvent) => {
|
|
615
|
+
const destination = rawEvent.relatedTarget;
|
|
616
|
+
const session = this.#keyboard;
|
|
617
|
+
if (session && this.#handleFor(destination) === session.handle && !this.#ownsNativeKeys(destination, session.handle)) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
this.#checkKeyboardFocus(this.#keyboard);
|
|
621
|
+
};
|
|
622
|
+
#checkKeyboardFocus(session) {
|
|
623
|
+
this.#keyboardFocusSession = session;
|
|
624
|
+
this.#keyboardFocus.schedule();
|
|
625
|
+
}
|
|
626
|
+
#settleKeyboardFocus() {
|
|
627
|
+
const session = this.#keyboardFocusSession;
|
|
628
|
+
this.#keyboardFocusSession = null;
|
|
629
|
+
if (!session || this.#keyboard !== session || !this.element.isConnected) return;
|
|
630
|
+
const tree = session.handle.getRootNode();
|
|
631
|
+
const active = tree.activeElement ?? null;
|
|
632
|
+
if (this.#handleFor(active) === session.handle && !this.#ownsNativeKeys(active, session.handle)) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
this.#clearKeyboardSession();
|
|
636
|
+
this.#dispatchCancel("keyboard");
|
|
637
|
+
}
|
|
407
638
|
#grabKeyboard(handle) {
|
|
408
639
|
this.#beginGesture();
|
|
409
640
|
this.#keyboard = { handle, dx: 0, dy: 0 };
|
|
641
|
+
this.#watchShadowFocus(handle);
|
|
410
642
|
this.element.setAttribute("data-grabbed", "true");
|
|
411
643
|
handle.setAttribute("data-grabbed", "true");
|
|
412
644
|
this.dispatch("start", { detail: { x: 0, y: 0, pointerType: "keyboard" } });
|
|
@@ -442,14 +674,17 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
442
674
|
this.#pointer?.pointer?.end();
|
|
443
675
|
}
|
|
444
676
|
#clearKeyboardSession() {
|
|
677
|
+
this.#shadowFocus.dispose();
|
|
445
678
|
this.#keyboard?.handle.removeAttribute("data-grabbed");
|
|
446
679
|
this.#keyboard = null;
|
|
680
|
+
this.#keyboardFocusSession = null;
|
|
447
681
|
this.element.removeAttribute("data-grabbed");
|
|
448
682
|
}
|
|
449
683
|
#teardown() {
|
|
450
684
|
const interrupted = this.#pointer?.started ? this.#pointer.pointerType : this.#keyboard ? "keyboard" : null;
|
|
451
685
|
this.#teardownSessions();
|
|
452
686
|
for (const handle of this.#handles()) this.#restoreHandle(handle);
|
|
687
|
+
this.#tabindex.returnAll();
|
|
453
688
|
if (interrupted) this.#followReset();
|
|
454
689
|
if (interrupted && this.element.isConnected) this.#dispatchCancel(interrupted);
|
|
455
690
|
}
|
|
@@ -530,14 +765,13 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
530
765
|
handle.style.touchAction = lent;
|
|
531
766
|
handle.setAttribute(_PointerDragController.#TOUCH_ACTION_MARKER, "true");
|
|
532
767
|
}
|
|
533
|
-
|
|
534
|
-
handle.setAttribute("tabindex", "0");
|
|
535
|
-
handle.setAttribute(_PointerDragController.#TABINDEX_MARKER, "true");
|
|
536
|
-
}
|
|
768
|
+
this.#tabindex.lend(handle, { onlyIfUnfocusable: true });
|
|
537
769
|
}
|
|
538
770
|
#ownsNativeKeys(target, handle) {
|
|
539
|
-
|
|
540
|
-
|
|
771
|
+
let element = target;
|
|
772
|
+
while (element.shadowRoot?.activeElement) element = element.shadowRoot.activeElement;
|
|
773
|
+
const owner = closestInFlatTree(element, NATIVE_KEY_OWNERS);
|
|
774
|
+
return owner !== null && owner !== handle && flatTreeContains(handle, owner);
|
|
541
775
|
}
|
|
542
776
|
#restoreHandle(handle) {
|
|
543
777
|
if (handle.hasAttribute(_PointerDragController.#TOUCH_ACTION_MARKER)) {
|
|
@@ -546,15 +780,7 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
546
780
|
}
|
|
547
781
|
handle.removeAttribute(_PointerDragController.#TOUCH_ACTION_MARKER);
|
|
548
782
|
}
|
|
549
|
-
|
|
550
|
-
const ours = handle.getAttribute("tabindex") === "0";
|
|
551
|
-
if (ours && document.activeElement !== handle) {
|
|
552
|
-
handle.removeAttribute("tabindex");
|
|
553
|
-
handle.removeAttribute(_PointerDragController.#TABINDEX_MARKER);
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
if (!ours) handle.removeAttribute(_PointerDragController.#TABINDEX_MARKER);
|
|
557
|
-
}
|
|
783
|
+
this.#tabindex.return(handle);
|
|
558
784
|
}
|
|
559
785
|
get #safeThreshold() {
|
|
560
786
|
return this.#numbers.read(
|