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
|
@@ -22,21 +22,42 @@ function actionSource(source) {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function isAriaDisabled(element) {
|
|
26
|
+
for (let current = element; current; current = current.parentElement) {
|
|
27
|
+
if (current.getAttribute("aria-disabled") === "true") return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
function isReservedArrowChord(event, allow = []) {
|
|
26
33
|
if (!event.key.startsWith("Arrow")) return false;
|
|
27
34
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
28
35
|
}
|
|
36
|
+
function hasModifierChord(event) {
|
|
37
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
41
|
+
if (element.getAttribute(name) === value) return false;
|
|
42
|
+
if (value === null) element.removeAttribute(name);
|
|
43
|
+
else element.setAttribute(name, value);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
29
46
|
|
|
30
47
|
function sharedRegistry(key, create) {
|
|
31
48
|
const symbol = Symbol.for(key);
|
|
32
49
|
const scope = globalThis;
|
|
33
50
|
const existing = scope[symbol];
|
|
34
51
|
if (existing !== void 0) return existing;
|
|
35
|
-
const
|
|
36
|
-
Object.defineProperty(globalThis, symbol, { value:
|
|
37
|
-
return
|
|
52
|
+
const registry2 = create();
|
|
53
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
54
|
+
return registry2;
|
|
38
55
|
}
|
|
39
56
|
|
|
57
|
+
var registry = sharedRegistry(
|
|
58
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
59
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
60
|
+
);
|
|
40
61
|
function isAttributeValue(value) {
|
|
41
62
|
return value === null || typeof value === "string";
|
|
42
63
|
}
|
|
@@ -53,24 +74,46 @@ function readLeaseRecord(element, record) {
|
|
|
53
74
|
return void 0;
|
|
54
75
|
}
|
|
55
76
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
77
|
+
function attributeStack(element, attribute) {
|
|
78
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
79
|
+
registry.stacks.set(element, byAttribute);
|
|
80
|
+
const known = byAttribute.get(attribute);
|
|
81
|
+
if (known) return known;
|
|
82
|
+
const found = [];
|
|
83
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
84
|
+
const read = readLeaseRecord(element, record);
|
|
85
|
+
if (read) found.push({ ...read, record });
|
|
86
|
+
}
|
|
87
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
88
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
89
|
+
lease: null,
|
|
90
|
+
record,
|
|
91
|
+
written,
|
|
92
|
+
original,
|
|
93
|
+
linked: linked && index > 0
|
|
94
|
+
}));
|
|
95
|
+
byAttribute.set(attribute, stack);
|
|
96
|
+
return stack;
|
|
97
|
+
}
|
|
98
|
+
function findHold(stack, lease, record) {
|
|
99
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
100
|
+
}
|
|
101
|
+
var AttributeLease = class {
|
|
61
102
|
#attribute;
|
|
62
103
|
#record;
|
|
63
104
|
#base;
|
|
105
|
+
#preserveAuthor;
|
|
64
106
|
#held = /* @__PURE__ */ new Set();
|
|
65
107
|
constructor(attribute, owner, options = {}) {
|
|
66
108
|
this.#attribute = attribute;
|
|
67
109
|
this.#record = `data-${owner}-${attribute}-lease`;
|
|
68
110
|
this.#base = options.base === true;
|
|
69
|
-
|
|
111
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
112
|
+
const names = registry.names;
|
|
70
113
|
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
71
114
|
}
|
|
72
115
|
write(element, value) {
|
|
73
|
-
const stack = this.#
|
|
116
|
+
const stack = attributeStack(element, this.#attribute);
|
|
74
117
|
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
75
118
|
this.#held.add(element);
|
|
76
119
|
const shows = !this.#covered(element, stack, hold);
|
|
@@ -80,8 +123,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
80
123
|
}
|
|
81
124
|
return(element) {
|
|
82
125
|
const held = this.#held.delete(element);
|
|
83
|
-
const known =
|
|
84
|
-
const stack = held || known || element.hasAttribute(this.#record) ? this.#
|
|
126
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
127
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
85
128
|
const hold = this.#take(stack);
|
|
86
129
|
if (!hold) return false;
|
|
87
130
|
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
@@ -99,31 +142,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
99
142
|
returnAll() {
|
|
100
143
|
for (const element of Array.from(this.#held)) this.return(element);
|
|
101
144
|
}
|
|
102
|
-
#stack(element) {
|
|
103
|
-
const stacks = _AttributeLease.#registry.stacks;
|
|
104
|
-
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
105
|
-
stacks.set(element, byAttribute);
|
|
106
|
-
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
107
|
-
byAttribute.set(this.#attribute, stack);
|
|
108
|
-
return stack;
|
|
109
|
-
}
|
|
110
|
-
#seed(element) {
|
|
111
|
-
const found = [];
|
|
112
|
-
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
113
|
-
const read = readLeaseRecord(element, record);
|
|
114
|
-
if (read) found.push({ ...read, record });
|
|
115
|
-
}
|
|
116
|
-
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
117
|
-
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
118
|
-
lease: null,
|
|
119
|
-
record,
|
|
120
|
-
written,
|
|
121
|
-
original,
|
|
122
|
-
linked: linked && index > 0
|
|
123
|
-
}));
|
|
124
|
-
}
|
|
125
145
|
#take(stack) {
|
|
126
|
-
const hold = stack
|
|
146
|
+
const hold = findHold(stack, this, this.#record);
|
|
127
147
|
if (hold) hold.lease = this;
|
|
128
148
|
return hold;
|
|
129
149
|
}
|
|
@@ -132,7 +152,7 @@ var AttributeLease = class _AttributeLease {
|
|
|
132
152
|
const top = stack.at(-1);
|
|
133
153
|
const bottom = stack[0];
|
|
134
154
|
const over = top?.written === current ? top : void 0;
|
|
135
|
-
const from = this.#base ? bottom : over;
|
|
155
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
136
156
|
const hold = {
|
|
137
157
|
lease: this,
|
|
138
158
|
record: this.#record,
|
|
@@ -162,15 +182,23 @@ var AttributeLease = class _AttributeLease {
|
|
|
162
182
|
);
|
|
163
183
|
});
|
|
164
184
|
for (const [name, recorded] of records) {
|
|
165
|
-
|
|
166
|
-
if (recorded === null) element.removeAttribute(name);
|
|
167
|
-
else element.setAttribute(name, recorded);
|
|
185
|
+
setAttributeIfChanged(element, name, recorded);
|
|
168
186
|
}
|
|
169
187
|
}
|
|
170
188
|
#reflect(element, value) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
189
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
var ClickVerdicts = class {
|
|
194
|
+
#verdicts = /* @__PURE__ */ new WeakMap();
|
|
195
|
+
record(event, entries, refused) {
|
|
196
|
+
this.#verdicts.set(event, { entries, refused });
|
|
197
|
+
}
|
|
198
|
+
for(event, entry) {
|
|
199
|
+
const verdict = event && this.#verdicts.get(event);
|
|
200
|
+
if (!verdict?.entries.includes(entry)) return void 0;
|
|
201
|
+
return verdict.refused;
|
|
174
202
|
}
|
|
175
203
|
};
|
|
176
204
|
|
|
@@ -188,25 +216,25 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
188
216
|
#active = null;
|
|
189
217
|
activate(ownerDocument = document, options) {
|
|
190
218
|
this.deactivate();
|
|
191
|
-
let
|
|
192
|
-
if (!
|
|
193
|
-
|
|
194
|
-
_EscapeLayer.#registries.set(ownerDocument,
|
|
195
|
-
ownerDocument.addEventListener("keydown",
|
|
219
|
+
let registry2 = _EscapeLayer.#registries.get(ownerDocument);
|
|
220
|
+
if (!registry2) {
|
|
221
|
+
registry2 = _EscapeLayer.#createRegistry();
|
|
222
|
+
_EscapeLayer.#registries.set(ownerDocument, registry2);
|
|
223
|
+
ownerDocument.addEventListener("keydown", registry2.onKeydown);
|
|
196
224
|
}
|
|
197
225
|
const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
|
|
198
|
-
|
|
226
|
+
registry2.stack.push(entry);
|
|
199
227
|
this.#active = { document: ownerDocument, entry };
|
|
200
228
|
}
|
|
201
229
|
deactivate() {
|
|
202
230
|
const active = this.#active;
|
|
203
231
|
if (!active) return;
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
206
|
-
const index =
|
|
207
|
-
if (index >= 0)
|
|
208
|
-
if (
|
|
209
|
-
active.document.removeEventListener("keydown",
|
|
232
|
+
const registry2 = _EscapeLayer.#registries.get(active.document);
|
|
233
|
+
if (registry2) {
|
|
234
|
+
const index = registry2.stack.lastIndexOf(active.entry);
|
|
235
|
+
if (index >= 0) registry2.stack.splice(index, 1);
|
|
236
|
+
if (registry2.stack.length === 0) {
|
|
237
|
+
active.document.removeEventListener("keydown", registry2.onKeydown);
|
|
210
238
|
_EscapeLayer.#registries.delete(active.document);
|
|
211
239
|
}
|
|
212
240
|
}
|
|
@@ -215,22 +243,22 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
215
243
|
get ownsEscape() {
|
|
216
244
|
const active = this.#active;
|
|
217
245
|
if (!active) return false;
|
|
218
|
-
const
|
|
219
|
-
if (!
|
|
220
|
-
return _EscapeLayer.#resolveOwner(
|
|
246
|
+
const registry2 = _EscapeLayer.#registries.get(active.document);
|
|
247
|
+
if (!registry2) return false;
|
|
248
|
+
return _EscapeLayer.#resolveOwner(registry2.stack) === active.entry;
|
|
221
249
|
}
|
|
222
250
|
static #createRegistry() {
|
|
223
|
-
const
|
|
251
|
+
const registry2 = {
|
|
224
252
|
stack: [],
|
|
225
253
|
onKeydown: (event) => {
|
|
226
254
|
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
227
|
-
const owner = _EscapeLayer.#resolveOwner(
|
|
255
|
+
const owner = _EscapeLayer.#resolveOwner(registry2.stack);
|
|
228
256
|
if (!owner) return;
|
|
229
257
|
event.preventDefault();
|
|
230
258
|
owner.onDismiss();
|
|
231
259
|
}
|
|
232
260
|
};
|
|
233
|
-
return
|
|
261
|
+
return registry2;
|
|
234
262
|
}
|
|
235
263
|
static #resolveOwner(stack) {
|
|
236
264
|
for (let index = stack.length - 1; index >= 0; index--) {
|
|
@@ -243,6 +271,29 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
243
271
|
}
|
|
244
272
|
};
|
|
245
273
|
|
|
274
|
+
function ownerIndex(candidates, node) {
|
|
275
|
+
if (!(node instanceof Node)) return -1;
|
|
276
|
+
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
277
|
+
}
|
|
278
|
+
function ownerOf(candidates, node) {
|
|
279
|
+
return candidates[ownerIndex(candidates, node)] ?? null;
|
|
280
|
+
}
|
|
281
|
+
function ownersOf(candidates, node) {
|
|
282
|
+
if (!(node instanceof Node)) return [];
|
|
283
|
+
return candidates.filter((candidate) => candidate.contains(node));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
var ListenerSet = class {
|
|
287
|
+
#abort = new AbortController();
|
|
288
|
+
add(target, type, handler, options) {
|
|
289
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
290
|
+
}
|
|
291
|
+
dispose() {
|
|
292
|
+
this.#abort.abort();
|
|
293
|
+
this.#abort = new AbortController();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
246
297
|
var TimerRegistry = class {
|
|
247
298
|
ids = /* @__PURE__ */ new Set();
|
|
248
299
|
clear(id) {
|
|
@@ -289,11 +340,13 @@ var ContextMenuController = class _ContextMenuController extends Controller {
|
|
|
289
340
|
#menu = null;
|
|
290
341
|
#connected = false;
|
|
291
342
|
#reporting = false;
|
|
343
|
+
#clickVerdicts = new ClickVerdicts();
|
|
344
|
+
#listeners = new ListenerSet();
|
|
292
345
|
connect() {
|
|
293
346
|
this.#closeMenu("api");
|
|
294
|
-
this.element
|
|
295
|
-
|
|
296
|
-
|
|
347
|
+
this.#listeners.add(this.element, "click", this.#onItemClickCapture, { capture: true });
|
|
348
|
+
this.#listeners.add(document, "click", this.#onOutsidePointer, { capture: true });
|
|
349
|
+
this.#listeners.add(document, "contextmenu", this.#onOutsidePointer, { capture: true });
|
|
297
350
|
this.#connected = true;
|
|
298
351
|
this.#reporting = true;
|
|
299
352
|
}
|
|
@@ -302,9 +355,7 @@ var ContextMenuController = class _ContextMenuController extends Controller {
|
|
|
302
355
|
this.#reporting = false;
|
|
303
356
|
this.#timers.clearAll();
|
|
304
357
|
this.#escapeLayer.deactivate();
|
|
305
|
-
this.
|
|
306
|
-
document.removeEventListener("click", this.#onOutsidePointer, true);
|
|
307
|
-
document.removeEventListener("contextmenu", this.#onOutsidePointer, true);
|
|
358
|
+
this.#listeners.dispose();
|
|
308
359
|
}
|
|
309
360
|
regionTargetConnected() {
|
|
310
361
|
if (this.#connected) this.#reflectRegion();
|
|
@@ -342,6 +393,7 @@ var ContextMenuController = class _ContextMenuController extends Controller {
|
|
|
342
393
|
onItemKeydown(event) {
|
|
343
394
|
if (event.defaultPrevented) return;
|
|
344
395
|
if (isReservedArrowChord(event)) return;
|
|
396
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
345
397
|
const items = this.#navigableItems;
|
|
346
398
|
const currentIndex = items.indexOf(event.currentTarget);
|
|
347
399
|
switch (event.key) {
|
|
@@ -373,9 +425,18 @@ var ContextMenuController = class _ContextMenuController extends Controller {
|
|
|
373
425
|
break;
|
|
374
426
|
}
|
|
375
427
|
}
|
|
376
|
-
activate() {
|
|
428
|
+
activate(event) {
|
|
429
|
+
const item = ownerOf(this.itemTargets, event?.target);
|
|
430
|
+
if (item !== null && this.#refuses(item, event)) return;
|
|
377
431
|
this.#closeAndRestore("select");
|
|
378
432
|
}
|
|
433
|
+
#refuses(item, event) {
|
|
434
|
+
const recorded = this.#clickVerdicts.for(event, item);
|
|
435
|
+
if (recorded !== void 0) return recorded;
|
|
436
|
+
return ownersOf(this.itemTargets, event?.target).some(
|
|
437
|
+
(owner) => this.#isActivationDisabled(owner)
|
|
438
|
+
);
|
|
439
|
+
}
|
|
379
440
|
#openAt(x, y, reason) {
|
|
380
441
|
if (!this.hasMenuTarget) return;
|
|
381
442
|
this.#timers.clearAll();
|
|
@@ -435,12 +496,13 @@ var ContextMenuController = class _ContextMenuController extends Controller {
|
|
|
435
496
|
if (this.#isOpen && !this.element.contains(event.target)) this.#closeMenu("outside");
|
|
436
497
|
};
|
|
437
498
|
#onItemClickCapture = (event) => {
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
499
|
+
const items = ownersOf(this.itemTargets, event.target);
|
|
500
|
+
this.#clickVerdicts.record(
|
|
501
|
+
event,
|
|
502
|
+
items,
|
|
503
|
+
items.some((item) => this.#isActivationDisabled(item))
|
|
442
504
|
);
|
|
443
|
-
if (!
|
|
505
|
+
if (!items.some((item) => isAriaDisabled(item))) return;
|
|
444
506
|
event.preventDefault();
|
|
445
507
|
event.stopImmediatePropagation();
|
|
446
508
|
};
|
|
@@ -451,6 +513,9 @@ var ContextMenuController = class _ContextMenuController extends Controller {
|
|
|
451
513
|
if (item.hasAttribute("hidden")) return false;
|
|
452
514
|
return !item.disabled;
|
|
453
515
|
}
|
|
516
|
+
#isActivationDisabled(item) {
|
|
517
|
+
return !this.#isNavigable(item) || isAriaDisabled(item);
|
|
518
|
+
}
|
|
454
519
|
get #isOpen() {
|
|
455
520
|
return this.hasMenuTarget && !this.menuTarget.hidden;
|
|
456
521
|
}
|
|
@@ -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 ListenerSet = class {
|
|
20
21
|
#abort = new AbortController();
|
|
@@ -43,6 +44,9 @@ var MicrotaskCoalescer = class {
|
|
|
43
44
|
this.#queued = false;
|
|
44
45
|
this.#generation += 1;
|
|
45
46
|
}
|
|
47
|
+
get pending() {
|
|
48
|
+
return this.#queued;
|
|
49
|
+
}
|
|
46
50
|
schedule() {
|
|
47
51
|
if (!this.#active || this.#queued) return;
|
|
48
52
|
this.#queued = true;
|
|
@@ -73,6 +77,9 @@ var MorphRenderWatcher = class {
|
|
|
73
77
|
schedule() {
|
|
74
78
|
this.#pass.schedule();
|
|
75
79
|
}
|
|
80
|
+
get pending() {
|
|
81
|
+
return this.#pass.pending;
|
|
82
|
+
}
|
|
76
83
|
disconnect() {
|
|
77
84
|
this.#listeners.dispose();
|
|
78
85
|
this.#pass.cancel();
|
|
@@ -127,6 +134,94 @@ var NumberValueReader = class {
|
|
|
127
134
|
}
|
|
128
135
|
};
|
|
129
136
|
|
|
137
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
138
|
+
if (element.getAttribute(name) === value) return false;
|
|
139
|
+
if (value === null) element.removeAttribute(name);
|
|
140
|
+
else element.setAttribute(name, value);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
var OwnedText = class {
|
|
145
|
+
#attribute;
|
|
146
|
+
#channel;
|
|
147
|
+
#guard;
|
|
148
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
149
|
+
constructor(attribute, channel = "text", guard = () => true) {
|
|
150
|
+
this.#attribute = attribute;
|
|
151
|
+
this.#channel = channel;
|
|
152
|
+
this.#guard = guard;
|
|
153
|
+
}
|
|
154
|
+
hasRecord(element) {
|
|
155
|
+
return element.hasAttribute(this.#attribute);
|
|
156
|
+
}
|
|
157
|
+
read(element, evidence = {}) {
|
|
158
|
+
if (evidence.source === "live") return this.#writes.get(element) ?? null;
|
|
159
|
+
const raw = element.getAttribute(this.#attribute);
|
|
160
|
+
if (raw === null) return null;
|
|
161
|
+
try {
|
|
162
|
+
const record = JSON.parse(raw);
|
|
163
|
+
if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
|
|
164
|
+
return null;
|
|
165
|
+
return { original: record[1], written: record[2] };
|
|
166
|
+
} catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
owns(element, evidence = {}) {
|
|
171
|
+
return this.#ownedRecord(element, evidence) !== null;
|
|
172
|
+
}
|
|
173
|
+
#ownedRecord(element, evidence) {
|
|
174
|
+
const record = this.read(element, evidence);
|
|
175
|
+
if (record === null || !this.#guard(element)) return null;
|
|
176
|
+
if (this.#channel === "text" && element.firstElementChild !== null) return null;
|
|
177
|
+
const current = this.#current(element);
|
|
178
|
+
return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
|
|
179
|
+
}
|
|
180
|
+
write(element, written, original, force = false) {
|
|
181
|
+
const current = this.#current(element);
|
|
182
|
+
if (current === null) return;
|
|
183
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
184
|
+
if (force || current !== written) this.#put(element, written);
|
|
185
|
+
this.remember(element, written, before);
|
|
186
|
+
}
|
|
187
|
+
remember(element, written, original) {
|
|
188
|
+
const current = this.#current(element);
|
|
189
|
+
if (current === null) return;
|
|
190
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
191
|
+
this.#writes.set(element, { original: before, written });
|
|
192
|
+
setAttributeIfChanged(
|
|
193
|
+
element,
|
|
194
|
+
this.#attribute,
|
|
195
|
+
JSON.stringify([this.#channel, before, written])
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
clear(element, evidence = {}) {
|
|
199
|
+
if (!this.owns(element, evidence)) return false;
|
|
200
|
+
if (this.#current(element) !== "") this.#put(element, "");
|
|
201
|
+
this.forget(element);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
restore(element, evidence = {}) {
|
|
205
|
+
const record = this.#ownedRecord(element, evidence);
|
|
206
|
+
if (record === null) return false;
|
|
207
|
+
if (this.#current(element) !== record.original) this.#put(element, record.original);
|
|
208
|
+
this.forget(element);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
forget(element) {
|
|
212
|
+
setAttributeIfChanged(element, this.#attribute, null);
|
|
213
|
+
this.#writes.delete(element);
|
|
214
|
+
}
|
|
215
|
+
#current(element) {
|
|
216
|
+
if (this.#channel === "text") return element.textContent ?? "";
|
|
217
|
+
return element instanceof HTMLInputElement ? element.value : null;
|
|
218
|
+
}
|
|
219
|
+
#put(element, text) {
|
|
220
|
+
if (this.#channel === "text") element.textContent = text;
|
|
221
|
+
else if (element instanceof HTMLInputElement) element.value = text;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
130
225
|
var TimerRegistry = class {
|
|
131
226
|
ids = /* @__PURE__ */ new Set();
|
|
132
227
|
clear(id) {
|
|
@@ -189,7 +284,7 @@ var CountdownController = class _CountdownController extends Controller {
|
|
|
189
284
|
#renderedAmount = 0;
|
|
190
285
|
#phase = "paused";
|
|
191
286
|
#valuesDirty = false;
|
|
192
|
-
#
|
|
287
|
+
#statusText = new OwnedText(this.#ownedStatus);
|
|
193
288
|
connect() {
|
|
194
289
|
this.#connected = true;
|
|
195
290
|
this.#resync.observe(this.element);
|
|
@@ -245,8 +340,7 @@ var CountdownController = class _CountdownController extends Controller {
|
|
|
245
340
|
}
|
|
246
341
|
statusTargetDisconnected(status) {
|
|
247
342
|
if (!this.statusTargets.includes(status) && this.#ownsStatus(status)) {
|
|
248
|
-
|
|
249
|
-
this.#statusWrites.delete(status);
|
|
343
|
+
this.#statusText.forget(status);
|
|
250
344
|
}
|
|
251
345
|
this.#resync.schedule();
|
|
252
346
|
}
|
|
@@ -366,25 +460,19 @@ var CountdownController = class _CountdownController extends Controller {
|
|
|
366
460
|
this.#writeStatus(status, label);
|
|
367
461
|
}
|
|
368
462
|
#ownsStatus(status) {
|
|
369
|
-
|
|
370
|
-
if (!status.hasAttribute(this.#ownedStatus) && held && status.firstElementChild === null && (status.textContent === held.text || status.textContent === held.original))
|
|
371
|
-
return true;
|
|
372
|
-
return status.getAttribute(this.#ownedStatus) === status.textContent && status.firstElementChild === null;
|
|
463
|
+
return this.#statusText.owns(status) || !this.#statusText.hasRecord(status) && this.#statusText.owns(status, { source: "live", allowOriginal: true });
|
|
373
464
|
}
|
|
374
465
|
#isEmpty(status) {
|
|
375
466
|
return status.textContent === "" && status.firstElementChild === null;
|
|
376
467
|
}
|
|
377
468
|
#writeStatus(status, text) {
|
|
378
|
-
|
|
379
|
-
this.#statusWrites.set(status, { original, text });
|
|
380
|
-
if (status.textContent !== text) status.textContent = text;
|
|
381
|
-
if (status.getAttribute(this.#ownedStatus) !== text)
|
|
382
|
-
status.setAttribute(this.#ownedStatus, text);
|
|
469
|
+
this.#statusText.write(status, text);
|
|
383
470
|
}
|
|
384
471
|
#releaseStatus(status) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
472
|
+
this.#statusText.clear(
|
|
473
|
+
status,
|
|
474
|
+
this.#statusText.hasRecord(status) ? {} : { source: "live", allowOriginal: true }
|
|
475
|
+
);
|
|
388
476
|
}
|
|
389
477
|
#announceCompletion() {
|
|
390
478
|
announce(fillTemplate(this.announceTextValue, {}));
|