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
|
@@ -97,6 +97,17 @@ var KeyedTimers = class {
|
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
var ListenerSet = class {
|
|
101
|
+
#abort = new AbortController();
|
|
102
|
+
add(target, type, handler, options) {
|
|
103
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
104
|
+
}
|
|
105
|
+
dispose() {
|
|
106
|
+
this.#abort.abort();
|
|
107
|
+
this.#abort = new AbortController();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
100
111
|
var MicrotaskCoalescer = class {
|
|
101
112
|
#run;
|
|
102
113
|
#queued = false;
|
|
@@ -113,6 +124,9 @@ var MicrotaskCoalescer = class {
|
|
|
113
124
|
this.#queued = false;
|
|
114
125
|
this.#generation += 1;
|
|
115
126
|
}
|
|
127
|
+
get pending() {
|
|
128
|
+
return this.#queued;
|
|
129
|
+
}
|
|
116
130
|
schedule() {
|
|
117
131
|
if (!this.#active || this.#queued) return;
|
|
118
132
|
this.#queued = true;
|
|
@@ -169,6 +183,94 @@ var NumberValueReader = class {
|
|
|
169
183
|
}
|
|
170
184
|
};
|
|
171
185
|
|
|
186
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
187
|
+
if (element.getAttribute(name) === value) return false;
|
|
188
|
+
if (value === null) element.removeAttribute(name);
|
|
189
|
+
else element.setAttribute(name, value);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var OwnedText = class {
|
|
194
|
+
#attribute;
|
|
195
|
+
#channel;
|
|
196
|
+
#guard;
|
|
197
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
198
|
+
constructor(attribute, channel = "text", guard = () => true) {
|
|
199
|
+
this.#attribute = attribute;
|
|
200
|
+
this.#channel = channel;
|
|
201
|
+
this.#guard = guard;
|
|
202
|
+
}
|
|
203
|
+
hasRecord(element) {
|
|
204
|
+
return element.hasAttribute(this.#attribute);
|
|
205
|
+
}
|
|
206
|
+
read(element, evidence = {}) {
|
|
207
|
+
if (evidence.source === "live") return this.#writes.get(element) ?? null;
|
|
208
|
+
const raw = element.getAttribute(this.#attribute);
|
|
209
|
+
if (raw === null) return null;
|
|
210
|
+
try {
|
|
211
|
+
const record = JSON.parse(raw);
|
|
212
|
+
if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
|
|
213
|
+
return null;
|
|
214
|
+
return { original: record[1], written: record[2] };
|
|
215
|
+
} catch {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
owns(element, evidence = {}) {
|
|
220
|
+
return this.#ownedRecord(element, evidence) !== null;
|
|
221
|
+
}
|
|
222
|
+
#ownedRecord(element, evidence) {
|
|
223
|
+
const record = this.read(element, evidence);
|
|
224
|
+
if (record === null || !this.#guard(element)) return null;
|
|
225
|
+
if (this.#channel === "text" && element.firstElementChild !== null) return null;
|
|
226
|
+
const current = this.#current(element);
|
|
227
|
+
return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
|
|
228
|
+
}
|
|
229
|
+
write(element, written, original, force = false) {
|
|
230
|
+
const current = this.#current(element);
|
|
231
|
+
if (current === null) return;
|
|
232
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
233
|
+
if (force || current !== written) this.#put(element, written);
|
|
234
|
+
this.remember(element, written, before);
|
|
235
|
+
}
|
|
236
|
+
remember(element, written, original) {
|
|
237
|
+
const current = this.#current(element);
|
|
238
|
+
if (current === null) return;
|
|
239
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
240
|
+
this.#writes.set(element, { original: before, written });
|
|
241
|
+
setAttributeIfChanged(
|
|
242
|
+
element,
|
|
243
|
+
this.#attribute,
|
|
244
|
+
JSON.stringify([this.#channel, before, written])
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
clear(element, evidence = {}) {
|
|
248
|
+
if (!this.owns(element, evidence)) return false;
|
|
249
|
+
if (this.#current(element) !== "") this.#put(element, "");
|
|
250
|
+
this.forget(element);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
restore(element, evidence = {}) {
|
|
254
|
+
const record = this.#ownedRecord(element, evidence);
|
|
255
|
+
if (record === null) return false;
|
|
256
|
+
if (this.#current(element) !== record.original) this.#put(element, record.original);
|
|
257
|
+
this.forget(element);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
forget(element) {
|
|
261
|
+
setAttributeIfChanged(element, this.#attribute, null);
|
|
262
|
+
this.#writes.delete(element);
|
|
263
|
+
}
|
|
264
|
+
#current(element) {
|
|
265
|
+
if (this.#channel === "text") return element.textContent ?? "";
|
|
266
|
+
return element instanceof HTMLInputElement ? element.value : null;
|
|
267
|
+
}
|
|
268
|
+
#put(element, text) {
|
|
269
|
+
if (this.#channel === "text") element.textContent = text;
|
|
270
|
+
else if (element instanceof HTMLInputElement) element.value = text;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
172
274
|
var LEVELS = ["polite", "assertive"];
|
|
173
275
|
var STAND_IN = "stand-in";
|
|
174
276
|
var ANNOUNCED = "announced";
|
|
@@ -185,14 +287,13 @@ var AnnouncerController = class _AnnouncerController extends Controller {
|
|
|
185
287
|
static actions = ["announce"];
|
|
186
288
|
#timers = new SafeTimeout();
|
|
187
289
|
#gate = new DetachGate();
|
|
290
|
+
#listeners = new ListenerSet();
|
|
188
291
|
#regionTimers = new KeyedTimers();
|
|
189
292
|
#generated = /* @__PURE__ */ new Map();
|
|
190
293
|
get #standIn() {
|
|
191
294
|
return `data-${this.identifier}-${STAND_IN}`;
|
|
192
295
|
}
|
|
193
|
-
|
|
194
|
-
return `data-${this.identifier}-${ANNOUNCED}`;
|
|
195
|
-
}
|
|
296
|
+
#messageText = new OwnedText(`data-${this.identifier}-${ANNOUNCED}`);
|
|
196
297
|
#queues = /* @__PURE__ */ new Map();
|
|
197
298
|
#draining = /* @__PURE__ */ new Set();
|
|
198
299
|
#reconcile = new MicrotaskCoalescer(() => this.#reconcileRegions());
|
|
@@ -217,8 +318,8 @@ var AnnouncerController = class _AnnouncerController extends Controller {
|
|
|
217
318
|
this.#emptyInheritedMessages();
|
|
218
319
|
this.#reconcileRegions();
|
|
219
320
|
this.#hostWatch.observe(this.element, { childList: true });
|
|
220
|
-
this.element
|
|
221
|
-
|
|
321
|
+
this.#listeners.add(this.element, "stimeo--announcer:announce", this.#onAnnounceEvent);
|
|
322
|
+
this.#listeners.add(window, "stimeo--announcer:announce", this.#onAnnounceEvent);
|
|
222
323
|
}
|
|
223
324
|
disconnect() {
|
|
224
325
|
this.#gate.disconnected(this, () => this.#teardown());
|
|
@@ -226,8 +327,7 @@ var AnnouncerController = class _AnnouncerController extends Controller {
|
|
|
226
327
|
#teardown() {
|
|
227
328
|
this.#reconcile.cancel();
|
|
228
329
|
this.#hostWatch.disconnect();
|
|
229
|
-
this.
|
|
230
|
-
window.removeEventListener("stimeo--announcer:announce", this.#onAnnounceEvent);
|
|
330
|
+
this.#listeners.dispose();
|
|
231
331
|
this.#timers.clearAll();
|
|
232
332
|
this.#regionTimers.clearAll();
|
|
233
333
|
this.#queues.clear();
|
|
@@ -279,9 +379,7 @@ var AnnouncerController = class _AnnouncerController extends Controller {
|
|
|
279
379
|
}
|
|
280
380
|
#emptyInheritedMessages() {
|
|
281
381
|
for (const region of [...this.politeTargets, ...this.assertiveTargets]) {
|
|
282
|
-
|
|
283
|
-
region.replaceChildren();
|
|
284
|
-
region.removeAttribute(this.#announced);
|
|
382
|
+
this.#messageText.clear(region);
|
|
285
383
|
}
|
|
286
384
|
}
|
|
287
385
|
#removeGenerated() {
|
|
@@ -325,24 +423,22 @@ var AnnouncerController = class _AnnouncerController extends Controller {
|
|
|
325
423
|
}
|
|
326
424
|
if (this.dedupeReannounceValue && region.textContent === message) {
|
|
327
425
|
region.textContent = "";
|
|
426
|
+
this.#messageText.forget(region);
|
|
328
427
|
this.#scheduleDrain(level);
|
|
329
428
|
return;
|
|
330
429
|
}
|
|
331
430
|
queue.shift();
|
|
332
431
|
this.#regionTimers.clear(region);
|
|
333
|
-
region
|
|
334
|
-
|
|
335
|
-
this.#scheduleClear(region, message);
|
|
432
|
+
this.#messageText.write(region, message, void 0, true);
|
|
433
|
+
this.#scheduleClear(region);
|
|
336
434
|
if (queue.length > 0) this.#scheduleDrain(level);
|
|
337
435
|
}
|
|
338
|
-
#scheduleClear(region
|
|
436
|
+
#scheduleClear(region) {
|
|
339
437
|
if (this.#safeClearAfter <= 0) return;
|
|
340
438
|
this.#regionTimers.set(
|
|
341
439
|
region,
|
|
342
440
|
() => {
|
|
343
|
-
|
|
344
|
-
region.textContent = "";
|
|
345
|
-
region.removeAttribute(this.#announced);
|
|
441
|
+
this.#messageText.clear(region);
|
|
346
442
|
},
|
|
347
443
|
this.#safeClearAfter
|
|
348
444
|
);
|
|
@@ -10,16 +10,27 @@ function announce(message, options = {}) {
|
|
|
10
10
|
);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
14
|
+
if (element.getAttribute(name) === value) return false;
|
|
15
|
+
if (value === null) element.removeAttribute(name);
|
|
16
|
+
else element.setAttribute(name, value);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
function sharedRegistry(key, create) {
|
|
14
21
|
const symbol = Symbol.for(key);
|
|
15
22
|
const scope = globalThis;
|
|
16
23
|
const existing = scope[symbol];
|
|
17
24
|
if (existing !== void 0) return existing;
|
|
18
|
-
const
|
|
19
|
-
Object.defineProperty(globalThis, symbol, { value:
|
|
20
|
-
return
|
|
25
|
+
const registry2 = create();
|
|
26
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
27
|
+
return registry2;
|
|
21
28
|
}
|
|
22
29
|
|
|
30
|
+
var registry = sharedRegistry(
|
|
31
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
32
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
33
|
+
);
|
|
23
34
|
function isAttributeValue(value) {
|
|
24
35
|
return value === null || typeof value === "string";
|
|
25
36
|
}
|
|
@@ -36,24 +47,46 @@ function readLeaseRecord(element, record) {
|
|
|
36
47
|
return void 0;
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
);
|
|
50
|
+
function attributeStack(element, attribute) {
|
|
51
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
52
|
+
registry.stacks.set(element, byAttribute);
|
|
53
|
+
const known = byAttribute.get(attribute);
|
|
54
|
+
if (known) return known;
|
|
55
|
+
const found = [];
|
|
56
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
57
|
+
const read = readLeaseRecord(element, record);
|
|
58
|
+
if (read) found.push({ ...read, record });
|
|
59
|
+
}
|
|
60
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
61
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
62
|
+
lease: null,
|
|
63
|
+
record,
|
|
64
|
+
written,
|
|
65
|
+
original,
|
|
66
|
+
linked: linked && index > 0
|
|
67
|
+
}));
|
|
68
|
+
byAttribute.set(attribute, stack);
|
|
69
|
+
return stack;
|
|
70
|
+
}
|
|
71
|
+
function findHold(stack, lease, record) {
|
|
72
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
73
|
+
}
|
|
74
|
+
var AttributeLease = class {
|
|
44
75
|
#attribute;
|
|
45
76
|
#record;
|
|
46
77
|
#base;
|
|
78
|
+
#preserveAuthor;
|
|
47
79
|
#held = /* @__PURE__ */ new Set();
|
|
48
80
|
constructor(attribute, owner, options = {}) {
|
|
49
81
|
this.#attribute = attribute;
|
|
50
82
|
this.#record = `data-${owner}-${attribute}-lease`;
|
|
51
83
|
this.#base = options.base === true;
|
|
52
|
-
|
|
84
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
85
|
+
const names = registry.names;
|
|
53
86
|
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
54
87
|
}
|
|
55
88
|
write(element, value) {
|
|
56
|
-
const stack = this.#
|
|
89
|
+
const stack = attributeStack(element, this.#attribute);
|
|
57
90
|
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
58
91
|
this.#held.add(element);
|
|
59
92
|
const shows = !this.#covered(element, stack, hold);
|
|
@@ -63,8 +96,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
63
96
|
}
|
|
64
97
|
return(element) {
|
|
65
98
|
const held = this.#held.delete(element);
|
|
66
|
-
const known =
|
|
67
|
-
const stack = held || known || element.hasAttribute(this.#record) ? this.#
|
|
99
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
100
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
68
101
|
const hold = this.#take(stack);
|
|
69
102
|
if (!hold) return false;
|
|
70
103
|
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
@@ -82,31 +115,8 @@ var AttributeLease = class _AttributeLease {
|
|
|
82
115
|
returnAll() {
|
|
83
116
|
for (const element of Array.from(this.#held)) this.return(element);
|
|
84
117
|
}
|
|
85
|
-
#stack(element) {
|
|
86
|
-
const stacks = _AttributeLease.#registry.stacks;
|
|
87
|
-
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
88
|
-
stacks.set(element, byAttribute);
|
|
89
|
-
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
90
|
-
byAttribute.set(this.#attribute, stack);
|
|
91
|
-
return stack;
|
|
92
|
-
}
|
|
93
|
-
#seed(element) {
|
|
94
|
-
const found = [];
|
|
95
|
-
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
96
|
-
const read = readLeaseRecord(element, record);
|
|
97
|
-
if (read) found.push({ ...read, record });
|
|
98
|
-
}
|
|
99
|
-
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
100
|
-
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
101
|
-
lease: null,
|
|
102
|
-
record,
|
|
103
|
-
written,
|
|
104
|
-
original,
|
|
105
|
-
linked: linked && index > 0
|
|
106
|
-
}));
|
|
107
|
-
}
|
|
108
118
|
#take(stack) {
|
|
109
|
-
const hold = stack
|
|
119
|
+
const hold = findHold(stack, this, this.#record);
|
|
110
120
|
if (hold) hold.lease = this;
|
|
111
121
|
return hold;
|
|
112
122
|
}
|
|
@@ -115,7 +125,7 @@ var AttributeLease = class _AttributeLease {
|
|
|
115
125
|
const top = stack.at(-1);
|
|
116
126
|
const bottom = stack[0];
|
|
117
127
|
const over = top?.written === current ? top : void 0;
|
|
118
|
-
const from = this.#base ? bottom : over;
|
|
128
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
119
129
|
const hold = {
|
|
120
130
|
lease: this,
|
|
121
131
|
record: this.#record,
|
|
@@ -145,15 +155,11 @@ var AttributeLease = class _AttributeLease {
|
|
|
145
155
|
);
|
|
146
156
|
});
|
|
147
157
|
for (const [name, recorded] of records) {
|
|
148
|
-
|
|
149
|
-
if (recorded === null) element.removeAttribute(name);
|
|
150
|
-
else element.setAttribute(name, recorded);
|
|
158
|
+
setAttributeIfChanged(element, name, recorded);
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
161
|
#reflect(element, value) {
|
|
154
|
-
|
|
155
|
-
if (value === null) element.removeAttribute(this.#attribute);
|
|
156
|
-
else element.setAttribute(this.#attribute, value);
|
|
162
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
157
163
|
}
|
|
158
164
|
};
|
|
159
165
|
|
|
@@ -163,63 +169,182 @@ var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
|
163
169
|
"deleteCompositionText",
|
|
164
170
|
"deleteByComposition"
|
|
165
171
|
]);
|
|
172
|
+
function isCompositionInputType(inputType) {
|
|
173
|
+
return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
|
|
174
|
+
}
|
|
175
|
+
var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
|
|
176
|
+
var textOf = (element) => element.value;
|
|
177
|
+
var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
|
|
166
178
|
var CompositionTracker = class {
|
|
167
179
|
#observedTargets = /* @__PURE__ */ new Set();
|
|
168
|
-
#
|
|
180
|
+
#composition = null;
|
|
169
181
|
#onStart;
|
|
170
182
|
#onEnd;
|
|
183
|
+
#onAbandon;
|
|
171
184
|
#confirmedTarget = null;
|
|
185
|
+
#keyComposing = false;
|
|
186
|
+
#connected = false;
|
|
187
|
+
#abandoned = null;
|
|
172
188
|
constructor(options = {}) {
|
|
173
189
|
this.#onStart = options.onStart;
|
|
174
190
|
this.#onEnd = options.onEnd;
|
|
191
|
+
this.#onAbandon = options.onAbandon;
|
|
175
192
|
}
|
|
176
193
|
observe(target) {
|
|
194
|
+
this.#connected = true;
|
|
177
195
|
if (this.#observedTargets.has(target)) return;
|
|
178
196
|
target.addEventListener("compositionstart", this.#handleStart);
|
|
179
197
|
target.addEventListener("compositionend", this.#handleEnd);
|
|
180
|
-
target.addEventListener("keydown", this.#handleKeydown);
|
|
198
|
+
target.addEventListener("keydown", this.#handleKeydown, true);
|
|
199
|
+
target.addEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
200
|
+
target.addEventListener("input", this.#handleInput, true);
|
|
201
|
+
target.addEventListener("blur", this.#handleBlur, true);
|
|
202
|
+
target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
181
203
|
this.#observedTargets.add(target);
|
|
182
204
|
}
|
|
183
205
|
unobserve(target) {
|
|
184
206
|
if (!this.#observedTargets.delete(target)) return;
|
|
185
|
-
|
|
186
|
-
target
|
|
187
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
188
|
-
this.#activeTargets.delete(target);
|
|
207
|
+
this.#release(target);
|
|
208
|
+
if (this.#composition?.target === target) this.#letGo();
|
|
189
209
|
if (this.#confirmedTarget === target) this.#confirmedTarget = null;
|
|
190
210
|
}
|
|
191
211
|
disconnect() {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
target.removeEventListener("compositionend", this.#handleEnd);
|
|
195
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
196
|
-
}
|
|
212
|
+
this.#connected = false;
|
|
213
|
+
for (const target of this.#observedTargets) this.#release(target);
|
|
197
214
|
this.#observedTargets.clear();
|
|
198
|
-
this.#
|
|
215
|
+
this.#letGo();
|
|
199
216
|
this.#confirmedTarget = null;
|
|
217
|
+
this.#keyComposing = false;
|
|
218
|
+
this.#abandoned = null;
|
|
200
219
|
}
|
|
201
220
|
isComposing(event) {
|
|
202
|
-
|
|
221
|
+
this.#settle();
|
|
222
|
+
return event?.isComposing === true || this.#composition !== null;
|
|
223
|
+
}
|
|
224
|
+
composingElement() {
|
|
225
|
+
this.#settle();
|
|
226
|
+
return this.#composition?.element ?? null;
|
|
227
|
+
}
|
|
228
|
+
owns(event) {
|
|
229
|
+
const element = this.composingElement();
|
|
230
|
+
return event.isComposing === true || element !== null && element === event.target;
|
|
203
231
|
}
|
|
204
232
|
consumesConfirmedInput(event) {
|
|
205
233
|
const confirmed = this.#confirmedTarget;
|
|
206
234
|
this.#confirmedTarget = null;
|
|
207
235
|
if (confirmed === null || confirmed !== event.target) return false;
|
|
208
236
|
const inputType = event.inputType;
|
|
209
|
-
return !inputType ||
|
|
237
|
+
return !inputType || isCompositionInputType(inputType);
|
|
238
|
+
}
|
|
239
|
+
#release(target) {
|
|
240
|
+
target.removeEventListener("compositionstart", this.#handleStart);
|
|
241
|
+
target.removeEventListener("compositionend", this.#handleEnd);
|
|
242
|
+
target.removeEventListener("keydown", this.#handleKeydown, true);
|
|
243
|
+
target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
244
|
+
target.removeEventListener("input", this.#handleInput, true);
|
|
245
|
+
target.removeEventListener("blur", this.#handleBlur, true);
|
|
246
|
+
target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
247
|
+
}
|
|
248
|
+
#letGo() {
|
|
249
|
+
const composition = this.#composition;
|
|
250
|
+
if (composition === null) return;
|
|
251
|
+
if (!holdsFocus(composition.element)) {
|
|
252
|
+
this.#composition = null;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
queueMicrotask(() => {
|
|
256
|
+
if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
this.#composition = null;
|
|
260
|
+
if (!this.#connected) return;
|
|
261
|
+
this.#abandoned = { element: composition.element, inPlace: true };
|
|
262
|
+
this.#runAbandon();
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
#settle() {
|
|
266
|
+
const composition = this.#composition;
|
|
267
|
+
if (composition === null) return;
|
|
268
|
+
const { target, element, text } = composition;
|
|
269
|
+
if (target instanceof Node && element instanceof Node && !target.contains(element)) {
|
|
270
|
+
this.#composition = null;
|
|
271
|
+
} else if (textOf(element) !== text) {
|
|
272
|
+
this.#abandon(false);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
#abandon(inPlace) {
|
|
276
|
+
this.#owe(inPlace);
|
|
277
|
+
this.#composition = null;
|
|
278
|
+
queueMicrotask(this.#runAbandon);
|
|
210
279
|
}
|
|
280
|
+
#owe(inPlace) {
|
|
281
|
+
const composition = this.#composition;
|
|
282
|
+
if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
|
|
283
|
+
}
|
|
284
|
+
#runAbandon = () => {
|
|
285
|
+
const abandoned = this.#abandoned;
|
|
286
|
+
if (abandoned === null) return;
|
|
287
|
+
this.#abandoned = null;
|
|
288
|
+
this.#onAbandon?.(abandoned.element, abandoned.inPlace);
|
|
289
|
+
};
|
|
211
290
|
#handleStart = (event) => {
|
|
212
291
|
this.#confirmedTarget = null;
|
|
213
|
-
|
|
292
|
+
this.#settle();
|
|
293
|
+
this.#owe(true);
|
|
294
|
+
const target = event.currentTarget;
|
|
295
|
+
const element = event.target;
|
|
296
|
+
this.#composition = { target, element, text: textOf(element) };
|
|
297
|
+
this.#runAbandon();
|
|
214
298
|
this.#onStart?.(event);
|
|
215
299
|
};
|
|
216
300
|
#handleEnd = (event) => {
|
|
217
|
-
|
|
301
|
+
this.#runAbandon();
|
|
302
|
+
if (this.#composition?.target === event.currentTarget) this.#composition = null;
|
|
218
303
|
this.#confirmedTarget = event.target;
|
|
219
304
|
this.#onEnd?.(event);
|
|
220
305
|
};
|
|
221
|
-
#handleKeydown = () => {
|
|
306
|
+
#handleKeydown = (event) => {
|
|
307
|
+
this.#settle();
|
|
308
|
+
this.#runAbandon();
|
|
222
309
|
this.#confirmedTarget = null;
|
|
310
|
+
this.#keyComposing = event.isComposing === true;
|
|
311
|
+
};
|
|
312
|
+
#handleBeforeInput = (event) => {
|
|
313
|
+
const keyComposing = this.#keyComposing;
|
|
314
|
+
this.#keyComposing = false;
|
|
315
|
+
this.#settle();
|
|
316
|
+
const { isComposing, inputType } = event;
|
|
317
|
+
if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
|
|
318
|
+
this.#abandon(true);
|
|
319
|
+
}
|
|
320
|
+
this.#runAbandon();
|
|
321
|
+
};
|
|
322
|
+
#handleInput = (event) => {
|
|
323
|
+
const composition = this.#composition;
|
|
324
|
+
const { isTrusted, isComposing, inputType } = event;
|
|
325
|
+
if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
|
|
326
|
+
composition.text = textOf(composition.element);
|
|
327
|
+
} else {
|
|
328
|
+
this.#settle();
|
|
329
|
+
}
|
|
330
|
+
this.#runAbandon();
|
|
331
|
+
};
|
|
332
|
+
#handleBlur = (event) => {
|
|
333
|
+
this.#settle();
|
|
334
|
+
this.#runAbandon();
|
|
335
|
+
const composition = this.#composition;
|
|
336
|
+
if (composition === null || composition.target === composition.element) return;
|
|
337
|
+
const { element } = composition;
|
|
338
|
+
if (!(element instanceof Node) || event.target !== element) return;
|
|
339
|
+
const root = element.getRootNode();
|
|
340
|
+
if (root.activeElement !== element) this.#composition = null;
|
|
341
|
+
};
|
|
342
|
+
#handleBeforeMorphAttribute = (event) => {
|
|
343
|
+
this.#settle();
|
|
344
|
+
const composition = this.#composition;
|
|
345
|
+
if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
|
|
346
|
+
const detail = event.detail;
|
|
347
|
+
if (detail?.attributeName === "value") event.preventDefault();
|
|
223
348
|
};
|
|
224
349
|
};
|
|
225
350
|
|