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
|
@@ -26,17 +26,32 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
26
26
|
if (!event.key.startsWith("Arrow")) return false;
|
|
27
27
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
28
28
|
}
|
|
29
|
+
function hasModifierChord(event) {
|
|
30
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
34
|
+
if (element.getAttribute(name) === value) return false;
|
|
35
|
+
beforeWrite?.();
|
|
36
|
+
if (value === null) element.removeAttribute(name);
|
|
37
|
+
else element.setAttribute(name, value);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
29
40
|
|
|
30
41
|
function sharedRegistry(key, create) {
|
|
31
42
|
const symbol = Symbol.for(key);
|
|
32
43
|
const scope = globalThis;
|
|
33
44
|
const existing = scope[symbol];
|
|
34
45
|
if (existing !== void 0) return existing;
|
|
35
|
-
const
|
|
36
|
-
Object.defineProperty(globalThis, symbol, { value:
|
|
37
|
-
return
|
|
46
|
+
const registry2 = create();
|
|
47
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
48
|
+
return registry2;
|
|
38
49
|
}
|
|
39
50
|
|
|
51
|
+
var registry = sharedRegistry(
|
|
52
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
53
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
54
|
+
);
|
|
40
55
|
function isAttributeValue(value) {
|
|
41
56
|
return value === null || typeof value === "string";
|
|
42
57
|
}
|
|
@@ -53,24 +68,46 @@ function readLeaseRecord(element, record) {
|
|
|
53
68
|
return void 0;
|
|
54
69
|
}
|
|
55
70
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
71
|
+
function attributeStack(element, attribute) {
|
|
72
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
73
|
+
registry.stacks.set(element, byAttribute);
|
|
74
|
+
const known = byAttribute.get(attribute);
|
|
75
|
+
if (known) return known;
|
|
76
|
+
const found = [];
|
|
77
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
78
|
+
const read = readLeaseRecord(element, record);
|
|
79
|
+
if (read) found.push({ ...read, record });
|
|
80
|
+
}
|
|
81
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
82
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
83
|
+
lease: null,
|
|
84
|
+
record,
|
|
85
|
+
written,
|
|
86
|
+
original,
|
|
87
|
+
linked: linked && index > 0
|
|
88
|
+
}));
|
|
89
|
+
byAttribute.set(attribute, stack);
|
|
90
|
+
return stack;
|
|
91
|
+
}
|
|
92
|
+
function findHold(stack, lease, record) {
|
|
93
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
94
|
+
}
|
|
95
|
+
var AttributeLease = class {
|
|
61
96
|
#attribute;
|
|
62
97
|
#record;
|
|
63
98
|
#base;
|
|
99
|
+
#preserveAuthor;
|
|
64
100
|
#held = /* @__PURE__ */ new Set();
|
|
65
101
|
constructor(attribute, owner, options = {}) {
|
|
66
102
|
this.#attribute = attribute;
|
|
67
103
|
this.#record = `data-${owner}-${attribute}-lease`;
|
|
68
104
|
this.#base = options.base === true;
|
|
69
|
-
|
|
105
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
106
|
+
const names = registry.names;
|
|
70
107
|
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
71
108
|
}
|
|
72
109
|
write(element, value) {
|
|
73
|
-
const stack = this.#
|
|
110
|
+
const stack = attributeStack(element, this.#attribute);
|
|
74
111
|
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
75
112
|
this.#held.add(element);
|
|
76
113
|
const shows = !this.#covered(element, stack, hold);
|
|
@@ -80,8 +117,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
80
117
|
}
|
|
81
118
|
return(element) {
|
|
82
119
|
const held = this.#held.delete(element);
|
|
83
|
-
const known =
|
|
84
|
-
const stack = held || known || element.hasAttribute(this.#record) ? this.#
|
|
120
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
121
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
85
122
|
const hold = this.#take(stack);
|
|
86
123
|
if (!hold) return false;
|
|
87
124
|
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
@@ -99,31 +136,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
99
136
|
returnAll() {
|
|
100
137
|
for (const element of Array.from(this.#held)) this.return(element);
|
|
101
138
|
}
|
|
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
139
|
#take(stack) {
|
|
126
|
-
const hold = stack
|
|
140
|
+
const hold = findHold(stack, this, this.#record);
|
|
127
141
|
if (hold) hold.lease = this;
|
|
128
142
|
return hold;
|
|
129
143
|
}
|
|
@@ -132,7 +146,7 @@ var AttributeLease = class _AttributeLease {
|
|
|
132
146
|
const top = stack.at(-1);
|
|
133
147
|
const bottom = stack[0];
|
|
134
148
|
const over = top?.written === current ? top : void 0;
|
|
135
|
-
const from = this.#base ? bottom : over;
|
|
149
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
136
150
|
const hold = {
|
|
137
151
|
lease: this,
|
|
138
152
|
record: this.#record,
|
|
@@ -162,15 +176,101 @@ var AttributeLease = class _AttributeLease {
|
|
|
162
176
|
);
|
|
163
177
|
});
|
|
164
178
|
for (const [name, recorded] of records) {
|
|
165
|
-
|
|
166
|
-
if (recorded === null) element.removeAttribute(name);
|
|
167
|
-
else element.setAttribute(name, recorded);
|
|
179
|
+
setAttributeIfChanged(element, name, recorded);
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
#reflect(element, value) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
var AuthoredMutationWatcher = class {
|
|
188
|
+
#onAuthored;
|
|
189
|
+
#claims = null;
|
|
190
|
+
#observer = null;
|
|
191
|
+
constructor(onAuthored) {
|
|
192
|
+
this.#onAuthored = onAuthored;
|
|
193
|
+
}
|
|
194
|
+
observe(root, options) {
|
|
195
|
+
this.disconnect();
|
|
196
|
+
const observer = new MutationObserver((records) => {
|
|
197
|
+
if (this.#observer !== observer) return;
|
|
198
|
+
const authored = this.#filter(records);
|
|
199
|
+
if (authored.length > 0) this.#onAuthored(authored);
|
|
200
|
+
});
|
|
201
|
+
this.#observer = observer;
|
|
202
|
+
this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
|
|
203
|
+
observer.observe(root, {
|
|
204
|
+
attributes: true,
|
|
205
|
+
attributeFilter: [...options.attributes],
|
|
206
|
+
subtree: options.subtree ?? false,
|
|
207
|
+
childList: options.childList ?? false
|
|
208
|
+
});
|
|
209
|
+
if (options.ancestorAttributes) {
|
|
210
|
+
let ancestor = root.parentElement;
|
|
211
|
+
while (ancestor) {
|
|
212
|
+
observer.observe(ancestor, {
|
|
213
|
+
attributes: true,
|
|
214
|
+
attributeFilter: [...options.ancestorAttributes]
|
|
215
|
+
});
|
|
216
|
+
ancestor = ancestor.parentElement;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
claim(target, name, value) {
|
|
221
|
+
const claims = this.#claims?.attributes;
|
|
222
|
+
let attributes = claims?.get(target);
|
|
223
|
+
if (!attributes) {
|
|
224
|
+
attributes = /* @__PURE__ */ new Map();
|
|
225
|
+
claims?.set(target, attributes);
|
|
226
|
+
}
|
|
227
|
+
const pending = attributes.get(name);
|
|
228
|
+
attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
|
|
229
|
+
}
|
|
230
|
+
claimCurrent(target, name) {
|
|
231
|
+
this.claim(target, name, target.getAttribute(name));
|
|
232
|
+
}
|
|
233
|
+
claimChildList(target, added, removed) {
|
|
234
|
+
const claims = this.#claims?.children;
|
|
235
|
+
const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
|
|
236
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
|
|
237
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
|
|
238
|
+
claims?.set(target, pending);
|
|
239
|
+
}
|
|
240
|
+
take() {
|
|
241
|
+
return this.#filter(this.#observer?.takeRecords() ?? []);
|
|
242
|
+
}
|
|
243
|
+
disconnect() {
|
|
244
|
+
this.#observer?.disconnect();
|
|
245
|
+
this.#observer = null;
|
|
246
|
+
this.#claims = null;
|
|
247
|
+
}
|
|
248
|
+
#filter(records) {
|
|
249
|
+
const authored = records.filter((record) => !this.#consume(record));
|
|
250
|
+
this.#claims?.attributes.clear();
|
|
251
|
+
this.#claims?.children.clear();
|
|
252
|
+
return authored;
|
|
253
|
+
}
|
|
254
|
+
#consume(record) {
|
|
255
|
+
if (record.type === "attributes") {
|
|
256
|
+
const target = record.target;
|
|
257
|
+
const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
|
|
258
|
+
if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
pending2.count -= 1;
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
const pending = this.#claims?.children.get(record.target);
|
|
265
|
+
if (!pending) return false;
|
|
266
|
+
const added = [...record.addedNodes];
|
|
267
|
+
const removed = [...record.removedNodes];
|
|
268
|
+
if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
|
|
272
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
|
|
273
|
+
return true;
|
|
174
274
|
}
|
|
175
275
|
};
|
|
176
276
|
|
|
@@ -210,6 +310,9 @@ var MicrotaskCoalescer = class {
|
|
|
210
310
|
this.#queued = false;
|
|
211
311
|
this.#generation += 1;
|
|
212
312
|
}
|
|
313
|
+
get pending() {
|
|
314
|
+
return this.#queued;
|
|
315
|
+
}
|
|
213
316
|
schedule() {
|
|
214
317
|
if (!this.#active || this.#queued) return;
|
|
215
318
|
this.#queued = true;
|
|
@@ -240,6 +343,9 @@ var MorphRenderWatcher = class {
|
|
|
240
343
|
schedule() {
|
|
241
344
|
this.#pass.schedule();
|
|
242
345
|
}
|
|
346
|
+
get pending() {
|
|
347
|
+
return this.#pass.pending;
|
|
348
|
+
}
|
|
243
349
|
disconnect() {
|
|
244
350
|
this.#listeners.dispose();
|
|
245
351
|
this.#pass.cancel();
|
|
@@ -269,6 +375,87 @@ function scrollOptionIntoView(list, option) {
|
|
|
269
375
|
}
|
|
270
376
|
}
|
|
271
377
|
|
|
378
|
+
var OwnedText = class {
|
|
379
|
+
#attribute;
|
|
380
|
+
#channel;
|
|
381
|
+
#guard;
|
|
382
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
383
|
+
constructor(attribute, channel = "text", guard = () => true) {
|
|
384
|
+
this.#attribute = attribute;
|
|
385
|
+
this.#channel = channel;
|
|
386
|
+
this.#guard = guard;
|
|
387
|
+
}
|
|
388
|
+
hasRecord(element) {
|
|
389
|
+
return element.hasAttribute(this.#attribute);
|
|
390
|
+
}
|
|
391
|
+
read(element, evidence = {}) {
|
|
392
|
+
if (evidence.source === "live") return this.#writes.get(element) ?? null;
|
|
393
|
+
const raw = element.getAttribute(this.#attribute);
|
|
394
|
+
if (raw === null) return null;
|
|
395
|
+
try {
|
|
396
|
+
const record = JSON.parse(raw);
|
|
397
|
+
if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
|
|
398
|
+
return null;
|
|
399
|
+
return { original: record[1], written: record[2] };
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
owns(element, evidence = {}) {
|
|
405
|
+
return this.#ownedRecord(element, evidence) !== null;
|
|
406
|
+
}
|
|
407
|
+
#ownedRecord(element, evidence) {
|
|
408
|
+
const record = this.read(element, evidence);
|
|
409
|
+
if (record === null || !this.#guard(element)) return null;
|
|
410
|
+
if (this.#channel === "text" && element.firstElementChild !== null) return null;
|
|
411
|
+
const current = this.#current(element);
|
|
412
|
+
return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
|
|
413
|
+
}
|
|
414
|
+
write(element, written, original, force = false) {
|
|
415
|
+
const current = this.#current(element);
|
|
416
|
+
if (current === null) return;
|
|
417
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
418
|
+
if (force || current !== written) this.#put(element, written);
|
|
419
|
+
this.remember(element, written, before);
|
|
420
|
+
}
|
|
421
|
+
remember(element, written, original) {
|
|
422
|
+
const current = this.#current(element);
|
|
423
|
+
if (current === null) return;
|
|
424
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
425
|
+
this.#writes.set(element, { original: before, written });
|
|
426
|
+
setAttributeIfChanged(
|
|
427
|
+
element,
|
|
428
|
+
this.#attribute,
|
|
429
|
+
JSON.stringify([this.#channel, before, written])
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
clear(element, evidence = {}) {
|
|
433
|
+
if (!this.owns(element, evidence)) return false;
|
|
434
|
+
if (this.#current(element) !== "") this.#put(element, "");
|
|
435
|
+
this.forget(element);
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
restore(element, evidence = {}) {
|
|
439
|
+
const record = this.#ownedRecord(element, evidence);
|
|
440
|
+
if (record === null) return false;
|
|
441
|
+
if (this.#current(element) !== record.original) this.#put(element, record.original);
|
|
442
|
+
this.forget(element);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
forget(element) {
|
|
446
|
+
setAttributeIfChanged(element, this.#attribute, null);
|
|
447
|
+
this.#writes.delete(element);
|
|
448
|
+
}
|
|
449
|
+
#current(element) {
|
|
450
|
+
if (this.#channel === "text") return element.textContent ?? "";
|
|
451
|
+
return element instanceof HTMLInputElement ? element.value : null;
|
|
452
|
+
}
|
|
453
|
+
#put(element, text) {
|
|
454
|
+
if (this.#channel === "text") element.textContent = text;
|
|
455
|
+
else if (element instanceof HTMLInputElement) element.value = text;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
272
459
|
function targetSelector(identifier, name) {
|
|
273
460
|
return `[data-${identifier}-target~="${name}"]`;
|
|
274
461
|
}
|
|
@@ -354,11 +541,10 @@ function findTypeaheadMatch(items, from, query, label = (item) => typeaheadLabel
|
|
|
354
541
|
}
|
|
355
542
|
|
|
356
543
|
var OBSERVED_ATTRIBUTES = ["aria-selected", "data-value"];
|
|
357
|
-
function setAttributeIfChanged(element, name, value) {
|
|
358
|
-
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
359
|
-
}
|
|
360
544
|
var ListboxController = class extends Controller {
|
|
545
|
+
#labelText = new OwnedText(`data-${this.identifier}-owns-label`);
|
|
361
546
|
#moves = new MoveCounter();
|
|
547
|
+
#connectionGeneration = 0;
|
|
362
548
|
static targets = ["trigger", "value", "list", "option", "field"];
|
|
363
549
|
static values = {
|
|
364
550
|
placeholder: { type: String, default: "" }
|
|
@@ -368,7 +554,9 @@ var ListboxController = class extends Controller {
|
|
|
368
554
|
#activeId = null;
|
|
369
555
|
#activeOrder = [];
|
|
370
556
|
#reconcile = new MorphRenderWatcher(() => this.#reconcileOptions());
|
|
371
|
-
#observer =
|
|
557
|
+
#observer = new AuthoredMutationWatcher((records) => {
|
|
558
|
+
if (this.#concernsOptions(records)) this.#reconcile.schedule();
|
|
559
|
+
});
|
|
372
560
|
#settled = { option: null, value: "" };
|
|
373
561
|
#shownList = null;
|
|
374
562
|
#listHidden = new AttributeLease("hidden", this.identifier);
|
|
@@ -378,10 +566,11 @@ var ListboxController = class extends Controller {
|
|
|
378
566
|
this.identifier
|
|
379
567
|
);
|
|
380
568
|
#typeahead = new Typeahead();
|
|
569
|
+
#listeners = new ListenerSet();
|
|
381
570
|
connect() {
|
|
382
571
|
this.#normalizeSelection();
|
|
383
572
|
this.close();
|
|
384
|
-
|
|
573
|
+
this.#listeners.add(document, "click", this.#onOutsideClick, { capture: true });
|
|
385
574
|
this.#settled = this.#selection();
|
|
386
575
|
this.#reconcile.observe(this.element);
|
|
387
576
|
this.#observeOptions();
|
|
@@ -470,10 +659,10 @@ var ListboxController = class extends Controller {
|
|
|
470
659
|
return { option, value: option ? this.#optionValue(option) : "" };
|
|
471
660
|
}
|
|
472
661
|
disconnect() {
|
|
662
|
+
this.#connectionGeneration += 1;
|
|
473
663
|
this.#reconcile.disconnect();
|
|
474
|
-
this.#observer
|
|
475
|
-
this.#
|
|
476
|
-
document.removeEventListener("click", this.#onOutsideClick, true);
|
|
664
|
+
this.#observer.disconnect();
|
|
665
|
+
this.#listeners.dispose();
|
|
477
666
|
this.#typeahead.reset();
|
|
478
667
|
}
|
|
479
668
|
toggle(event) {
|
|
@@ -487,6 +676,7 @@ var ListboxController = class extends Controller {
|
|
|
487
676
|
onTriggerKeydown(event) {
|
|
488
677
|
if (event.defaultPrevented) return;
|
|
489
678
|
if (isReservedArrowChord(event)) return;
|
|
679
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
490
680
|
if (!this.#isClosed) this.#reconcileActive();
|
|
491
681
|
const options = this.optionTargets;
|
|
492
682
|
const length = options.length;
|
|
@@ -589,21 +779,22 @@ var ListboxController = class extends Controller {
|
|
|
589
779
|
this.triggerTarget.focus();
|
|
590
780
|
}
|
|
591
781
|
#selectOption(option, reason = "user") {
|
|
782
|
+
const generation = this.#connectionGeneration;
|
|
592
783
|
const { value, fieldChanged } = this.#applySelection(option);
|
|
593
784
|
const changed = option !== this.#settled.option || value !== this.#settled.value;
|
|
594
785
|
this.#settled = { option, value };
|
|
595
786
|
if (!changed) return true;
|
|
596
787
|
const token = this.#moves.record();
|
|
597
788
|
if (fieldChanged) commitField(this.fieldTarget);
|
|
598
|
-
if (!this.#moves.isLatest(token)) return false;
|
|
789
|
+
if (generation !== this.#connectionGeneration || !this.#moves.isLatest(token)) return false;
|
|
599
790
|
this.dispatch("change", { detail: { value, option, reason } });
|
|
600
791
|
return this.#moves.isLatest(token);
|
|
601
792
|
}
|
|
602
793
|
#applySelection(option) {
|
|
603
794
|
for (const candidate of this.optionTargets) {
|
|
604
|
-
|
|
795
|
+
this.#writeSelection(candidate, candidate === option ? "true" : "false");
|
|
605
796
|
}
|
|
606
|
-
this.#
|
|
797
|
+
this.#flushMutations();
|
|
607
798
|
const value = option ? this.#optionValue(option) : "";
|
|
608
799
|
this.#renderLabel(option);
|
|
609
800
|
const fieldChanged = this.hasFieldTarget && writeField(this.fieldTarget, value);
|
|
@@ -612,26 +803,16 @@ var ListboxController = class extends Controller {
|
|
|
612
803
|
#renderLabel(option) {
|
|
613
804
|
if (!this.hasValueTarget) return;
|
|
614
805
|
const label = this.valueTarget;
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
const owned = label.getAttribute(ownsLabel) === label.textContent && label.childElementCount === 0;
|
|
618
|
-
if (option === void 0 && !owned && label.hasAttribute(ownsLabel)) return;
|
|
806
|
+
const owned = this.#labelText.owns(label);
|
|
807
|
+
if (option === void 0 && !owned && this.#labelText.hasRecord(label)) return;
|
|
619
808
|
const text = option ? this.#optionLabel(option) : this.placeholderValue || null;
|
|
620
809
|
if (text === null) {
|
|
621
|
-
|
|
622
|
-
label.textContent = label.getAttribute(originalLabel) ?? "";
|
|
623
|
-
label.removeAttribute(ownsLabel);
|
|
624
|
-
label.removeAttribute(originalLabel);
|
|
810
|
+
this.#labelText.restore(label);
|
|
625
811
|
return;
|
|
626
812
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
setAttributeIfChanged(label, originalLabel, current);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
if (label.textContent !== text) label.textContent = text;
|
|
634
|
-
setAttributeIfChanged(label, ownsLabel, text);
|
|
813
|
+
const current = (label.textContent ?? "").trim();
|
|
814
|
+
const original = owned ? void 0 : option === void 0 || current !== text ? current : this.#labelText.read(label)?.original ?? "";
|
|
815
|
+
this.#labelText.write(label, text, original);
|
|
635
816
|
}
|
|
636
817
|
#optionLabel(option) {
|
|
637
818
|
return (option.textContent ?? "").trim();
|
|
@@ -640,22 +821,17 @@ var ListboxController = class extends Controller {
|
|
|
640
821
|
return option.dataset.value ?? this.#optionLabel(option);
|
|
641
822
|
}
|
|
642
823
|
#observeOptions() {
|
|
643
|
-
|
|
644
|
-
if (this.#concernsOptions(records)) this.#reconcile.schedule();
|
|
645
|
-
});
|
|
646
|
-
observer.observe(this.element, {
|
|
824
|
+
this.#observer.observe(this.element, {
|
|
647
825
|
subtree: true,
|
|
648
|
-
attributes:
|
|
649
|
-
attributeFilter: OBSERVED_ATTRIBUTES
|
|
826
|
+
attributes: OBSERVED_ATTRIBUTES
|
|
650
827
|
});
|
|
651
|
-
this.#observer = observer;
|
|
652
828
|
}
|
|
653
829
|
#concernsOptions(records) {
|
|
654
830
|
const options = new Set(this.optionTargets);
|
|
655
831
|
return records.some((record) => options.has(record.target));
|
|
656
832
|
}
|
|
657
|
-
#
|
|
658
|
-
this.#observer
|
|
833
|
+
#flushMutations() {
|
|
834
|
+
if (this.#concernsOptions(this.#observer.take())) this.#reconcile.schedule();
|
|
659
835
|
}
|
|
660
836
|
#setActive(index) {
|
|
661
837
|
const options = this.optionTargets;
|
|
@@ -728,6 +904,11 @@ var ListboxController = class extends Controller {
|
|
|
728
904
|
get #isClosed() {
|
|
729
905
|
return !this.hasListTarget || this.listTarget.hidden !== false;
|
|
730
906
|
}
|
|
907
|
+
#writeSelection(target, value) {
|
|
908
|
+
setAttributeIfChanged(target, "aria-selected", value, () => {
|
|
909
|
+
this.#observer.claim(target, "aria-selected", value);
|
|
910
|
+
});
|
|
911
|
+
}
|
|
731
912
|
};
|
|
732
913
|
|
|
733
914
|
export { ListboxController };
|
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
var DATETIME = /^([+-]\d{6}|\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
4
|
+
function readDatetime(element) {
|
|
5
|
+
const raw = element.getAttribute("datetime");
|
|
6
|
+
if (raw === null) return null;
|
|
7
|
+
const match = DATETIME.exec(raw.trim());
|
|
8
|
+
if (match === null) return null;
|
|
9
|
+
const [
|
|
10
|
+
,
|
|
11
|
+
yearText,
|
|
12
|
+
monthText,
|
|
13
|
+
dayText,
|
|
14
|
+
hourText = "00",
|
|
15
|
+
minuteText = "00",
|
|
16
|
+
secondText = "00",
|
|
17
|
+
fraction = "",
|
|
18
|
+
zone = "Z"
|
|
19
|
+
] = match;
|
|
20
|
+
const year = Number(yearText);
|
|
21
|
+
const month = Number(monthText);
|
|
22
|
+
const day = Number(dayText);
|
|
23
|
+
const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
|
|
24
|
+
const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
25
|
+
if (month < 1 || month > 12 || day < 1 || day > (days[month - 1] ?? 0)) return null;
|
|
26
|
+
const hour = Number(hourText);
|
|
27
|
+
const minute = Number(minuteText);
|
|
28
|
+
const second = Number(secondText);
|
|
29
|
+
if (hour === 24 && (minute !== 0 || second !== 0 || /[1-9]/.test(fraction))) return null;
|
|
30
|
+
let offset = zone;
|
|
31
|
+
if (zone !== "Z") {
|
|
32
|
+
const digits = zone.slice(1).replace(":", "");
|
|
33
|
+
const offsetHour = digits.slice(0, 2);
|
|
34
|
+
const offsetMinute = digits.slice(2);
|
|
35
|
+
offset = `${zone[0]}${offsetHour}:${offsetMinute}`;
|
|
36
|
+
}
|
|
37
|
+
const milliseconds = fraction.slice(0, 3).padEnd(3, "0");
|
|
38
|
+
const canonical = `${yearText}-${monthText}-${dayText}T${hourText}:${minuteText}:${secondText}.${milliseconds}${offset}`;
|
|
39
|
+
const instant = Date.parse(canonical);
|
|
40
|
+
return Number.isNaN(instant) ? null : instant;
|
|
41
|
+
}
|
|
42
|
+
|
|
3
43
|
var formatters = /* @__PURE__ */ new WeakMap();
|
|
4
44
|
function intlFormatter(factory, locale, options, fallbackLocale) {
|
|
5
45
|
const cache = cacheFor(factory);
|
|
@@ -59,6 +99,9 @@ var MicrotaskCoalescer = class {
|
|
|
59
99
|
this.#queued = false;
|
|
60
100
|
this.#generation += 1;
|
|
61
101
|
}
|
|
102
|
+
get pending() {
|
|
103
|
+
return this.#queued;
|
|
104
|
+
}
|
|
62
105
|
schedule() {
|
|
63
106
|
if (!this.#active || this.#queued) return;
|
|
64
107
|
this.#queued = true;
|
|
@@ -89,6 +132,9 @@ var MorphRenderWatcher = class {
|
|
|
89
132
|
schedule() {
|
|
90
133
|
this.#pass.schedule();
|
|
91
134
|
}
|
|
135
|
+
get pending() {
|
|
136
|
+
return this.#pass.pending;
|
|
137
|
+
}
|
|
92
138
|
disconnect() {
|
|
93
139
|
this.#listeners.dispose();
|
|
94
140
|
this.#pass.cancel();
|
|
@@ -161,19 +207,8 @@ var LocalTimeController = class extends Controller {
|
|
|
161
207
|
if (notify) this.dispatch("format", { detail: { formatted } });
|
|
162
208
|
}
|
|
163
209
|
#parse() {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
const ms = Date.parse(this.#asUtc(raw.trim()));
|
|
167
|
-
return Number.isNaN(ms) ? null : new Date(ms);
|
|
168
|
-
}
|
|
169
|
-
#asUtc(value) {
|
|
170
|
-
const isoLike = value.replace(
|
|
171
|
-
/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)$/,
|
|
172
|
-
"$1T$2"
|
|
173
|
-
);
|
|
174
|
-
const hasTime = /T\d{2}:\d{2}/.test(isoLike);
|
|
175
|
-
const hasZone = /(Z|[+-]\d{2}:?\d{2})$/.test(isoLike);
|
|
176
|
-
return hasTime && !hasZone ? `${isoLike}Z` : isoLike;
|
|
210
|
+
const instant = readDatetime(this.element);
|
|
211
|
+
return instant === null ? null : new Date(instant);
|
|
177
212
|
}
|
|
178
213
|
#title(date) {
|
|
179
214
|
if (this.titleFormatValue.length === 0) return null;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
4
|
+
if (element.getAttribute(name) === value) return false;
|
|
5
|
+
if (value === null) element.removeAttribute(name);
|
|
6
|
+
else element.setAttribute(name, value);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
var LayoutObserver = class {
|
|
4
11
|
#callback;
|
|
5
12
|
#resizeObserverFactory;
|
|
@@ -71,6 +78,9 @@ var MicrotaskCoalescer = class {
|
|
|
71
78
|
this.#queued = false;
|
|
72
79
|
this.#generation += 1;
|
|
73
80
|
}
|
|
81
|
+
get pending() {
|
|
82
|
+
return this.#queued;
|
|
83
|
+
}
|
|
74
84
|
schedule() {
|
|
75
85
|
if (!this.#active || this.#queued) return;
|
|
76
86
|
this.#queued = true;
|
|
@@ -205,10 +215,7 @@ var MasonryController = class _MasonryController extends Controller {
|
|
|
205
215
|
if ((heights[col] ?? 0) < (heights[shortest] ?? 0)) shortest = col;
|
|
206
216
|
}
|
|
207
217
|
const assigned = String(shortest);
|
|
208
|
-
if (item
|
|
209
|
-
item.setAttribute("data-column", assigned);
|
|
210
|
-
changed = true;
|
|
211
|
-
}
|
|
218
|
+
if (setAttributeIfChanged(item, "data-column", assigned)) changed = true;
|
|
212
219
|
heights[shortest] = (heights[shortest] ?? 0) + (boxes[index] ?? 0) + this.#gap;
|
|
213
220
|
});
|
|
214
221
|
this.element.style.setProperty(COLUMNS_PROPERTY, String(columns));
|