stimeo-ui 0.17.0 → 0.18.1
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 +136 -2
- 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 +290 -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 +66 -46
- data/dist/controllers/drawer_controller.js +145 -118
- data/dist/controllers/dropdown_controller.js +76 -63
- data/dist/controllers/editable_controller.js +200 -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 +434 -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 +272 -95
- 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 +255 -24
- data/dist/controllers/popover_controller.js +91 -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 +2515 -1315
- 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,14 @@ 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
|
+
#connected = false;
|
|
412
|
+
#keyboardFocusSession = null;
|
|
413
|
+
#listeners = new ListenerSet();
|
|
414
|
+
#shadowFocus = new ListenerSet();
|
|
415
|
+
#keyboardFocus = new MicrotaskCoalescer(() => this.#settleKeyboardFocus());
|
|
238
416
|
#followBase = { x: 0, y: 0 };
|
|
239
417
|
#gestureFollows = false;
|
|
240
418
|
#followOutput = null;
|
|
@@ -244,19 +422,30 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
244
422
|
this.#reflectSession();
|
|
245
423
|
});
|
|
246
424
|
connect() {
|
|
425
|
+
this.#connected = true;
|
|
247
426
|
this.#morphRender.observe(this.element);
|
|
248
427
|
if (!this.#pointer && !this.#keyboard) this.#followOutput = null;
|
|
249
428
|
this.#gate.cancel();
|
|
250
429
|
if (!this.#pointer) TRANSIENT_DRAG.reset(this.element);
|
|
251
430
|
if (!this.#keyboard) TRANSIENT_GRAB.reset(this.element);
|
|
252
|
-
this.element
|
|
253
|
-
this.element
|
|
431
|
+
this.#listeners.add(this.element, "pointerdown", this.#onPointerDown);
|
|
432
|
+
this.#listeners.add(this.element, "keydown", this.#onKeydown);
|
|
433
|
+
this.#listeners.add(this.element, "focusout", this.#onFocusout);
|
|
434
|
+
this.#listeners.add(this.element, "focusin", this.#onFocusin);
|
|
435
|
+
this.#keyboardFocus.activate();
|
|
254
436
|
if (!this.hasHandleTarget) this.#prepareHandle(this.element);
|
|
437
|
+
if (this.#keyboard) {
|
|
438
|
+
this.#watchShadowFocus(this.#keyboard.handle);
|
|
439
|
+
this.#checkKeyboardFocus(this.#keyboard);
|
|
440
|
+
}
|
|
255
441
|
}
|
|
256
442
|
disconnect() {
|
|
443
|
+
this.#connected = false;
|
|
257
444
|
this.#morphRender.disconnect();
|
|
258
|
-
this.
|
|
259
|
-
this.
|
|
445
|
+
this.#listeners.dispose();
|
|
446
|
+
this.#shadowFocus.dispose();
|
|
447
|
+
this.#keyboardFocus.cancel();
|
|
448
|
+
this.#keyboardFocusSession = null;
|
|
260
449
|
if (this.#pointer || this.#keyboard) {
|
|
261
450
|
this.#gate.disconnected(this, () => this.#teardown());
|
|
262
451
|
return;
|
|
@@ -264,9 +453,11 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
264
453
|
this.#teardown();
|
|
265
454
|
}
|
|
266
455
|
reset() {
|
|
456
|
+
const follows = this.#pointer || this.#keyboard ? this.#gestureFollows : this.followValue;
|
|
267
457
|
const live = this.#pointer?.started ? this.#pointer.pointerType : this.#keyboard ? "keyboard" : null;
|
|
268
458
|
if (this.#pointer || this.#keyboard) this.#teardownSessions();
|
|
269
459
|
if (live) this.#dispatchCancel(live);
|
|
460
|
+
if (!follows) return;
|
|
270
461
|
this.#followBase = { x: 0, y: 0 };
|
|
271
462
|
this.#applyFollow(0, 0);
|
|
272
463
|
}
|
|
@@ -276,7 +467,9 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
276
467
|
}
|
|
277
468
|
handleTargetDisconnected(handle) {
|
|
278
469
|
this.#restoreHandle(handle);
|
|
279
|
-
if (this
|
|
470
|
+
if (!this.#connected) return;
|
|
471
|
+
if (this.element.contains(handle) && (this.handleTargets.includes(handle) || DetachGate.isDetached(this)))
|
|
472
|
+
return;
|
|
280
473
|
const interrupted = this.#pointer?.handle === handle && this.#pointer.started ? this.#pointer.pointerType : this.#keyboard?.handle === handle ? "keyboard" : null;
|
|
281
474
|
if (this.#pointer?.handle === handle) this.#endPointerSession();
|
|
282
475
|
if (this.#keyboard?.handle === handle) this.#clearKeyboardSession();
|
|
@@ -298,7 +491,8 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
298
491
|
}
|
|
299
492
|
this.#teardownSessions();
|
|
300
493
|
}
|
|
301
|
-
#onPointerDown = (
|
|
494
|
+
#onPointerDown = (rawEvent) => {
|
|
495
|
+
const event = rawEvent;
|
|
302
496
|
if (this.disabledValue || this.#pointer || this.#keyboard || event.button !== 0) return;
|
|
303
497
|
const handle = this.#handleFor(event.target);
|
|
304
498
|
if (!handle) return;
|
|
@@ -353,13 +547,14 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
353
547
|
}
|
|
354
548
|
this.#dispatchCancel(pointerType);
|
|
355
549
|
}
|
|
356
|
-
#onKeydown = (
|
|
550
|
+
#onKeydown = (rawEvent) => {
|
|
551
|
+
const event = rawEvent;
|
|
357
552
|
if (this.disabledValue) return;
|
|
358
553
|
const handle = this.#handleFor(event.target);
|
|
359
554
|
if (!handle) return;
|
|
360
555
|
if (event.defaultPrevented) return;
|
|
361
556
|
if (event.isComposing) return;
|
|
362
|
-
if (this.#ownsNativeKeys(event.target, handle)) return;
|
|
557
|
+
if (this.#ownsNativeKeys(event.composedPath()[0] ?? event.target, handle)) return;
|
|
363
558
|
if (isReservedArrowChord(event)) return;
|
|
364
559
|
if (event.key === "Escape") {
|
|
365
560
|
if (this.#pointer?.started) {
|
|
@@ -383,6 +578,7 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
383
578
|
}
|
|
384
579
|
if (!this.#keyboard) return;
|
|
385
580
|
if (event.key === "Home" || event.key === "End") {
|
|
581
|
+
if (hasModifierChord(event)) return;
|
|
386
582
|
event.preventDefault();
|
|
387
583
|
return;
|
|
388
584
|
}
|
|
@@ -404,9 +600,50 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
404
600
|
}
|
|
405
601
|
});
|
|
406
602
|
};
|
|
603
|
+
#onFocusin = () => {
|
|
604
|
+
const session = this.#keyboard;
|
|
605
|
+
if (session) {
|
|
606
|
+
this.#watchShadowFocus(session.handle);
|
|
607
|
+
this.#checkKeyboardFocus(session);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
#watchShadowFocus(handle) {
|
|
611
|
+
const tree = handle.getRootNode();
|
|
612
|
+
let active = tree.activeElement;
|
|
613
|
+
while (active?.shadowRoot) {
|
|
614
|
+
const shadow = active.shadowRoot;
|
|
615
|
+
this.#shadowFocus.add(shadow, "focusin", this.#onFocusin);
|
|
616
|
+
active = shadow.activeElement;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
#onFocusout = (rawEvent) => {
|
|
620
|
+
const destination = rawEvent.relatedTarget;
|
|
621
|
+
const session = this.#keyboard;
|
|
622
|
+
if (session && this.#handleFor(destination) === session.handle && !this.#ownsNativeKeys(destination, session.handle)) {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.#checkKeyboardFocus(this.#keyboard);
|
|
626
|
+
};
|
|
627
|
+
#checkKeyboardFocus(session) {
|
|
628
|
+
this.#keyboardFocusSession = session;
|
|
629
|
+
this.#keyboardFocus.schedule();
|
|
630
|
+
}
|
|
631
|
+
#settleKeyboardFocus() {
|
|
632
|
+
const session = this.#keyboardFocusSession;
|
|
633
|
+
this.#keyboardFocusSession = null;
|
|
634
|
+
if (!session || this.#keyboard !== session || !this.element.isConnected) return;
|
|
635
|
+
const tree = session.handle.getRootNode();
|
|
636
|
+
const active = tree.activeElement ?? null;
|
|
637
|
+
if (this.#handleFor(active) === session.handle && !this.#ownsNativeKeys(active, session.handle)) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
this.#clearKeyboardSession();
|
|
641
|
+
this.#dispatchCancel("keyboard");
|
|
642
|
+
}
|
|
407
643
|
#grabKeyboard(handle) {
|
|
408
644
|
this.#beginGesture();
|
|
409
645
|
this.#keyboard = { handle, dx: 0, dy: 0 };
|
|
646
|
+
this.#watchShadowFocus(handle);
|
|
410
647
|
this.element.setAttribute("data-grabbed", "true");
|
|
411
648
|
handle.setAttribute("data-grabbed", "true");
|
|
412
649
|
this.dispatch("start", { detail: { x: 0, y: 0, pointerType: "keyboard" } });
|
|
@@ -442,14 +679,17 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
442
679
|
this.#pointer?.pointer?.end();
|
|
443
680
|
}
|
|
444
681
|
#clearKeyboardSession() {
|
|
682
|
+
this.#shadowFocus.dispose();
|
|
445
683
|
this.#keyboard?.handle.removeAttribute("data-grabbed");
|
|
446
684
|
this.#keyboard = null;
|
|
685
|
+
this.#keyboardFocusSession = null;
|
|
447
686
|
this.element.removeAttribute("data-grabbed");
|
|
448
687
|
}
|
|
449
688
|
#teardown() {
|
|
450
689
|
const interrupted = this.#pointer?.started ? this.#pointer.pointerType : this.#keyboard ? "keyboard" : null;
|
|
451
690
|
this.#teardownSessions();
|
|
452
691
|
for (const handle of this.#handles()) this.#restoreHandle(handle);
|
|
692
|
+
this.#tabindex.returnAll();
|
|
453
693
|
if (interrupted) this.#followReset();
|
|
454
694
|
if (interrupted && this.element.isConnected) this.#dispatchCancel(interrupted);
|
|
455
695
|
}
|
|
@@ -530,14 +770,13 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
530
770
|
handle.style.touchAction = lent;
|
|
531
771
|
handle.setAttribute(_PointerDragController.#TOUCH_ACTION_MARKER, "true");
|
|
532
772
|
}
|
|
533
|
-
|
|
534
|
-
handle.setAttribute("tabindex", "0");
|
|
535
|
-
handle.setAttribute(_PointerDragController.#TABINDEX_MARKER, "true");
|
|
536
|
-
}
|
|
773
|
+
this.#tabindex.lend(handle, { onlyIfUnfocusable: true });
|
|
537
774
|
}
|
|
538
775
|
#ownsNativeKeys(target, handle) {
|
|
539
|
-
|
|
540
|
-
|
|
776
|
+
let element = target;
|
|
777
|
+
while (element.shadowRoot?.activeElement) element = element.shadowRoot.activeElement;
|
|
778
|
+
const owner = closestInFlatTree(element, NATIVE_KEY_OWNERS);
|
|
779
|
+
return owner !== null && owner !== handle && flatTreeContains(handle, owner);
|
|
541
780
|
}
|
|
542
781
|
#restoreHandle(handle) {
|
|
543
782
|
if (handle.hasAttribute(_PointerDragController.#TOUCH_ACTION_MARKER)) {
|
|
@@ -546,15 +785,7 @@ var PointerDragController = class _PointerDragController extends Controller {
|
|
|
546
785
|
}
|
|
547
786
|
handle.removeAttribute(_PointerDragController.#TOUCH_ACTION_MARKER);
|
|
548
787
|
}
|
|
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
|
-
}
|
|
788
|
+
this.#tabindex.return(handle);
|
|
558
789
|
}
|
|
559
790
|
get #safeThreshold() {
|
|
560
791
|
return this.#numbers.read(
|