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
|
@@ -1,5 +1,103 @@
|
|
|
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
|
+
beforeWrite?.();
|
|
6
|
+
if (value === null) element.removeAttribute(name);
|
|
7
|
+
else element.setAttribute(name, value);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var AuthoredMutationWatcher = class {
|
|
12
|
+
#onAuthored;
|
|
13
|
+
#claims = null;
|
|
14
|
+
#observer = null;
|
|
15
|
+
constructor(onAuthored) {
|
|
16
|
+
this.#onAuthored = onAuthored;
|
|
17
|
+
}
|
|
18
|
+
observe(root, options) {
|
|
19
|
+
this.disconnect();
|
|
20
|
+
const observer = new MutationObserver((records) => {
|
|
21
|
+
if (this.#observer !== observer) return;
|
|
22
|
+
const authored = this.#filter(records);
|
|
23
|
+
if (authored.length > 0) this.#onAuthored(authored);
|
|
24
|
+
});
|
|
25
|
+
this.#observer = observer;
|
|
26
|
+
this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
|
|
27
|
+
observer.observe(root, {
|
|
28
|
+
attributes: true,
|
|
29
|
+
attributeFilter: [...options.attributes],
|
|
30
|
+
subtree: options.subtree ?? false,
|
|
31
|
+
childList: options.childList ?? false
|
|
32
|
+
});
|
|
33
|
+
if (options.ancestorAttributes) {
|
|
34
|
+
let ancestor = root.parentElement;
|
|
35
|
+
while (ancestor) {
|
|
36
|
+
observer.observe(ancestor, {
|
|
37
|
+
attributes: true,
|
|
38
|
+
attributeFilter: [...options.ancestorAttributes]
|
|
39
|
+
});
|
|
40
|
+
ancestor = ancestor.parentElement;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
claim(target, name, value) {
|
|
45
|
+
const claims = this.#claims?.attributes;
|
|
46
|
+
let attributes = claims?.get(target);
|
|
47
|
+
if (!attributes) {
|
|
48
|
+
attributes = /* @__PURE__ */ new Map();
|
|
49
|
+
claims?.set(target, attributes);
|
|
50
|
+
}
|
|
51
|
+
const pending = attributes.get(name);
|
|
52
|
+
attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
|
|
53
|
+
}
|
|
54
|
+
claimCurrent(target, name) {
|
|
55
|
+
this.claim(target, name, target.getAttribute(name));
|
|
56
|
+
}
|
|
57
|
+
claimChildList(target, added, removed) {
|
|
58
|
+
const claims = this.#claims?.children;
|
|
59
|
+
const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
|
|
60
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
|
|
61
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
|
|
62
|
+
claims?.set(target, pending);
|
|
63
|
+
}
|
|
64
|
+
take() {
|
|
65
|
+
return this.#filter(this.#observer?.takeRecords() ?? []);
|
|
66
|
+
}
|
|
67
|
+
disconnect() {
|
|
68
|
+
this.#observer?.disconnect();
|
|
69
|
+
this.#observer = null;
|
|
70
|
+
this.#claims = null;
|
|
71
|
+
}
|
|
72
|
+
#filter(records) {
|
|
73
|
+
const authored = records.filter((record) => !this.#consume(record));
|
|
74
|
+
this.#claims?.attributes.clear();
|
|
75
|
+
this.#claims?.children.clear();
|
|
76
|
+
return authored;
|
|
77
|
+
}
|
|
78
|
+
#consume(record) {
|
|
79
|
+
if (record.type === "attributes") {
|
|
80
|
+
const target = record.target;
|
|
81
|
+
const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
|
|
82
|
+
if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
pending2.count -= 1;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
const pending = this.#claims?.children.get(record.target);
|
|
89
|
+
if (!pending) return false;
|
|
90
|
+
const added = [...record.addedNodes];
|
|
91
|
+
const removed = [...record.removedNodes];
|
|
92
|
+
if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
|
|
96
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
3
101
|
function setDefaultAttribute(element, name, value) {
|
|
4
102
|
if (element.hasAttribute(name)) return false;
|
|
5
103
|
element.setAttribute(name, value);
|
|
@@ -41,6 +139,17 @@ function isInteractiveHost(element) {
|
|
|
41
139
|
return false;
|
|
42
140
|
}
|
|
43
141
|
|
|
142
|
+
var ListenerSet = class {
|
|
143
|
+
#abort = new AbortController();
|
|
144
|
+
add(target, type, handler, options) {
|
|
145
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
146
|
+
}
|
|
147
|
+
dispose() {
|
|
148
|
+
this.#abort.abort();
|
|
149
|
+
this.#abort = new AbortController();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
44
153
|
var MicrotaskCoalescer = class {
|
|
45
154
|
#run;
|
|
46
155
|
#queued = false;
|
|
@@ -57,6 +166,9 @@ var MicrotaskCoalescer = class {
|
|
|
57
166
|
this.#queued = false;
|
|
58
167
|
this.#generation += 1;
|
|
59
168
|
}
|
|
169
|
+
get pending() {
|
|
170
|
+
return this.#queued;
|
|
171
|
+
}
|
|
60
172
|
schedule() {
|
|
61
173
|
if (!this.#active || this.#queued) return;
|
|
62
174
|
this.#queued = true;
|
|
@@ -69,6 +181,34 @@ var MicrotaskCoalescer = class {
|
|
|
69
181
|
}
|
|
70
182
|
};
|
|
71
183
|
|
|
184
|
+
var MorphRenderWatcher = class {
|
|
185
|
+
#listeners = new ListenerSet();
|
|
186
|
+
#pass;
|
|
187
|
+
#element = null;
|
|
188
|
+
#onMorph = () => this.#pass.schedule();
|
|
189
|
+
constructor(run) {
|
|
190
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
191
|
+
}
|
|
192
|
+
observe(element) {
|
|
193
|
+
if (this.#element === element) return;
|
|
194
|
+
this.disconnect();
|
|
195
|
+
this.#element = element;
|
|
196
|
+
this.#pass.activate();
|
|
197
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
198
|
+
}
|
|
199
|
+
schedule() {
|
|
200
|
+
this.#pass.schedule();
|
|
201
|
+
}
|
|
202
|
+
get pending() {
|
|
203
|
+
return this.#pass.pending;
|
|
204
|
+
}
|
|
205
|
+
disconnect() {
|
|
206
|
+
this.#listeners.dispose();
|
|
207
|
+
this.#pass.cancel();
|
|
208
|
+
this.#element = null;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
72
212
|
var MoveCounter = class {
|
|
73
213
|
#sequence = 0;
|
|
74
214
|
record() {
|
|
@@ -92,14 +232,14 @@ var OBSERVED_ATTRIBUTES = [
|
|
|
92
232
|
var OBSERVED_ANCESTOR_ATTRIBUTES = ["contenteditable"];
|
|
93
233
|
var SwitchController = class extends Controller {
|
|
94
234
|
#moves = new MoveCounter();
|
|
235
|
+
#connectionGeneration = 0;
|
|
95
236
|
static targets = ["field"];
|
|
96
237
|
static actions = ["onKeydown", "toggle"];
|
|
97
238
|
static events = ["change", "reconcile"];
|
|
98
239
|
#ownedDefaults = /* @__PURE__ */ new Map();
|
|
99
|
-
#
|
|
100
|
-
#pass = new MicrotaskCoalescer(() => this.#reconcile());
|
|
240
|
+
#pass = new MorphRenderWatcher(() => this.#reconcile());
|
|
101
241
|
#committedChecked = false;
|
|
102
|
-
#attributeObserver = new
|
|
242
|
+
#attributeObserver = new AuthoredMutationWatcher((records) => {
|
|
103
243
|
this.#releaseAuthoredDefaults(records);
|
|
104
244
|
this.#pass.schedule();
|
|
105
245
|
});
|
|
@@ -108,13 +248,14 @@ var SwitchController = class extends Controller {
|
|
|
108
248
|
event.preventDefault();
|
|
109
249
|
event.stopImmediatePropagation();
|
|
110
250
|
};
|
|
251
|
+
#listeners = new ListenerSet();
|
|
111
252
|
connect() {
|
|
112
253
|
this.#releaseDefaultsRewrittenWhileAway();
|
|
113
254
|
this.#reconcileDefaults();
|
|
114
255
|
this.#mirrorField(false);
|
|
115
256
|
this.#committedChecked = this.#checked;
|
|
116
|
-
this.element
|
|
117
|
-
this.#pass.
|
|
257
|
+
this.#listeners.add(this.element, "click", this.#onClickCapture, { capture: true });
|
|
258
|
+
this.#pass.observe(this.element);
|
|
118
259
|
this.#observeAttributes();
|
|
119
260
|
}
|
|
120
261
|
fieldTargetConnected() {
|
|
@@ -124,10 +265,10 @@ var SwitchController = class extends Controller {
|
|
|
124
265
|
this.#pass.schedule();
|
|
125
266
|
}
|
|
126
267
|
disconnect() {
|
|
127
|
-
this.#
|
|
128
|
-
this.
|
|
268
|
+
this.#connectionGeneration += 1;
|
|
269
|
+
this.#pass.disconnect();
|
|
270
|
+
this.#listeners.dispose();
|
|
129
271
|
this.#attributeObserver.disconnect();
|
|
130
|
-
this.#internalAttributeValues.clear();
|
|
131
272
|
}
|
|
132
273
|
toggle() {
|
|
133
274
|
if (!this.#isSupportedHost || this.#isActivationDisabled) return;
|
|
@@ -151,11 +292,12 @@ var SwitchController = class extends Controller {
|
|
|
151
292
|
this.toggle();
|
|
152
293
|
}
|
|
153
294
|
#reconcile() {
|
|
154
|
-
this.#releaseAuthoredDefaults(this.#attributeObserver.
|
|
155
|
-
this.#attributeObserver.disconnect();
|
|
295
|
+
this.#releaseAuthoredDefaults(this.#attributeObserver.take());
|
|
156
296
|
this.#reconcileDefaults();
|
|
157
297
|
this.#mirrorField(false);
|
|
158
|
-
this.#
|
|
298
|
+
const authored = this.#attributeObserver.take();
|
|
299
|
+
this.#releaseAuthoredDefaults(authored);
|
|
300
|
+
if (authored.length > 0) this.#pass.schedule();
|
|
159
301
|
this.#reportMove();
|
|
160
302
|
}
|
|
161
303
|
#reportMove() {
|
|
@@ -181,7 +323,10 @@ var SwitchController = class extends Controller {
|
|
|
181
323
|
if (moved && notify) commitField(this.fieldTarget);
|
|
182
324
|
}
|
|
183
325
|
#setOwnedDefault(name, value) {
|
|
184
|
-
if (setDefaultAttribute(this.element, name, value))
|
|
326
|
+
if (setDefaultAttribute(this.element, name, value)) {
|
|
327
|
+
this.#ownedDefaults.set(name, value);
|
|
328
|
+
this.#attributeObserver.claimCurrent(this.element, name);
|
|
329
|
+
}
|
|
185
330
|
}
|
|
186
331
|
#releaseDefaultsRewrittenWhileAway() {
|
|
187
332
|
for (const [name, written] of this.#ownedDefaults) {
|
|
@@ -195,31 +340,22 @@ var SwitchController = class extends Controller {
|
|
|
195
340
|
const name = record.attributeName;
|
|
196
341
|
if (name && this.#ownedDefaults.has(name)) {
|
|
197
342
|
const value = this.element.getAttribute(name);
|
|
198
|
-
if (value !== null
|
|
343
|
+
if (value !== null) {
|
|
199
344
|
this.#ownedDefaults.delete(name);
|
|
200
345
|
}
|
|
201
346
|
}
|
|
202
347
|
}
|
|
203
348
|
}
|
|
204
|
-
this.#internalAttributeValues.clear();
|
|
205
349
|
}
|
|
206
350
|
#removeOwnedDefaults() {
|
|
207
|
-
for (const name of this.#ownedDefaults.keys()) this
|
|
351
|
+
for (const name of this.#ownedDefaults.keys()) this.#writeAttribute(name, null);
|
|
208
352
|
this.#ownedDefaults.clear();
|
|
209
353
|
}
|
|
210
354
|
#observeAttributes() {
|
|
211
355
|
this.#attributeObserver.observe(this.element, {
|
|
212
|
-
attributes:
|
|
213
|
-
|
|
356
|
+
attributes: OBSERVED_ATTRIBUTES,
|
|
357
|
+
ancestorAttributes: OBSERVED_ANCESTOR_ATTRIBUTES
|
|
214
358
|
});
|
|
215
|
-
let ancestor = this.element.parentElement;
|
|
216
|
-
while (ancestor) {
|
|
217
|
-
this.#attributeObserver.observe(ancestor, {
|
|
218
|
-
attributes: true,
|
|
219
|
-
attributeFilter: OBSERVED_ANCESTOR_ATTRIBUTES
|
|
220
|
-
});
|
|
221
|
-
ancestor = ancestor.parentElement;
|
|
222
|
-
}
|
|
223
359
|
}
|
|
224
360
|
get #isSupportedHost() {
|
|
225
361
|
if (this.element instanceof HTMLButtonElement) return this.element.type === "button";
|
|
@@ -238,10 +374,13 @@ var SwitchController = class extends Controller {
|
|
|
238
374
|
return this.element.getAttribute("aria-checked") === "true";
|
|
239
375
|
}
|
|
240
376
|
#commit(value) {
|
|
377
|
+
const generation = this.#connectionGeneration;
|
|
241
378
|
const reflected = value ? "true" : "false";
|
|
242
|
-
this.#
|
|
379
|
+
const authored = this.#attributeObserver.take();
|
|
380
|
+
this.#releaseAuthoredDefaults(authored);
|
|
381
|
+
if (authored.length > 0) this.#pass.schedule();
|
|
243
382
|
if (this.#ownedDefaults.has("aria-checked")) this.#ownedDefaults.set("aria-checked", reflected);
|
|
244
|
-
this
|
|
383
|
+
this.#writeAttribute("aria-checked", reflected);
|
|
245
384
|
const changed = value !== this.#committedChecked;
|
|
246
385
|
this.#committedChecked = value;
|
|
247
386
|
if (!changed) {
|
|
@@ -250,9 +389,14 @@ var SwitchController = class extends Controller {
|
|
|
250
389
|
}
|
|
251
390
|
const token = this.#moves.record();
|
|
252
391
|
this.#mirrorField(true);
|
|
253
|
-
if (!this.#moves.isLatest(token)) return;
|
|
392
|
+
if (generation !== this.#connectionGeneration || !this.#moves.isLatest(token)) return;
|
|
254
393
|
this.dispatch("change", { detail: { checked: value } });
|
|
255
394
|
}
|
|
395
|
+
#writeAttribute(name, value) {
|
|
396
|
+
setAttributeIfChanged(this.element, name, value, () => {
|
|
397
|
+
this.#attributeObserver.claim(this.element, name, value);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
256
400
|
};
|
|
257
401
|
|
|
258
402
|
export { SwitchController };
|
|
@@ -30,6 +30,9 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
30
30
|
if (!event.key.startsWith("Arrow")) return false;
|
|
31
31
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
32
32
|
}
|
|
33
|
+
function hasModifierChord(event) {
|
|
34
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
35
|
+
}
|
|
33
36
|
|
|
34
37
|
function targetSelector(identifier, name) {
|
|
35
38
|
return `[data-${identifier}-target~="${name}"]`;
|
|
@@ -62,11 +65,13 @@ var TabsController = class extends Controller {
|
|
|
62
65
|
onKeydown(event) {
|
|
63
66
|
if (event.defaultPrevented) return;
|
|
64
67
|
if (isReservedArrowChord(event)) return;
|
|
68
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
65
69
|
const tabs = this.tabTargets;
|
|
66
70
|
const currentIndex = tabs.indexOf(event.currentTarget);
|
|
67
71
|
if (currentIndex === -1) return;
|
|
68
72
|
let nextIndex = null;
|
|
69
|
-
const
|
|
73
|
+
const directionElement = this.hasListTarget ? this.listTarget : this.element;
|
|
74
|
+
const step = isRtl(directionElement) ? -1 : 1;
|
|
70
75
|
switch (event.key) {
|
|
71
76
|
case "ArrowRight":
|
|
72
77
|
nextIndex = (currentIndex + step + tabs.length) % tabs.length;
|
|
@@ -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
|
function isRtl(element) {
|
|
20
21
|
return window.getComputedStyle(element).direction === "rtl";
|
|
@@ -67,7 +68,7 @@ var RovingTabindex = class {
|
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
var ChipRow = class {
|
|
70
|
-
#
|
|
71
|
+
#getDirectionElement;
|
|
71
72
|
#getItems;
|
|
72
73
|
#getButton;
|
|
73
74
|
#onRemove;
|
|
@@ -75,7 +76,7 @@ var ChipRow = class {
|
|
|
75
76
|
#roving = new RovingTabindex(() => this.buttons);
|
|
76
77
|
#container = null;
|
|
77
78
|
constructor(options) {
|
|
78
|
-
this.#
|
|
79
|
+
this.#getDirectionElement = options.getDirectionElement;
|
|
79
80
|
this.#getItems = options.getItems;
|
|
80
81
|
this.#getButton = options.getButton ?? ((item) => matchingPart(item, "button"));
|
|
81
82
|
this.#onRemove = options.onRemove;
|
|
@@ -156,7 +157,7 @@ var ChipRow = class {
|
|
|
156
157
|
if (!entry) return;
|
|
157
158
|
const buttons = entry.buttons;
|
|
158
159
|
const index = entry.buttonIndex;
|
|
159
|
-
switch (logicalArrowKey(event.key, this.#
|
|
160
|
+
switch (logicalArrowKey(event.key, this.#getDirectionElement())) {
|
|
160
161
|
case "ArrowLeft":
|
|
161
162
|
if (index > 0) {
|
|
162
163
|
event.preventDefault();
|
|
@@ -186,63 +187,182 @@ var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
|
186
187
|
"deleteCompositionText",
|
|
187
188
|
"deleteByComposition"
|
|
188
189
|
]);
|
|
190
|
+
function isCompositionInputType(inputType) {
|
|
191
|
+
return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
|
|
192
|
+
}
|
|
193
|
+
var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
|
|
194
|
+
var textOf = (element) => element.value;
|
|
195
|
+
var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
|
|
189
196
|
var CompositionTracker = class {
|
|
190
197
|
#observedTargets = /* @__PURE__ */ new Set();
|
|
191
|
-
#
|
|
198
|
+
#composition = null;
|
|
192
199
|
#onStart;
|
|
193
200
|
#onEnd;
|
|
201
|
+
#onAbandon;
|
|
194
202
|
#confirmedTarget = null;
|
|
203
|
+
#keyComposing = false;
|
|
204
|
+
#connected = false;
|
|
205
|
+
#abandoned = null;
|
|
195
206
|
constructor(options = {}) {
|
|
196
207
|
this.#onStart = options.onStart;
|
|
197
208
|
this.#onEnd = options.onEnd;
|
|
209
|
+
this.#onAbandon = options.onAbandon;
|
|
198
210
|
}
|
|
199
211
|
observe(target) {
|
|
212
|
+
this.#connected = true;
|
|
200
213
|
if (this.#observedTargets.has(target)) return;
|
|
201
214
|
target.addEventListener("compositionstart", this.#handleStart);
|
|
202
215
|
target.addEventListener("compositionend", this.#handleEnd);
|
|
203
|
-
target.addEventListener("keydown", this.#handleKeydown);
|
|
216
|
+
target.addEventListener("keydown", this.#handleKeydown, true);
|
|
217
|
+
target.addEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
218
|
+
target.addEventListener("input", this.#handleInput, true);
|
|
219
|
+
target.addEventListener("blur", this.#handleBlur, true);
|
|
220
|
+
target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
204
221
|
this.#observedTargets.add(target);
|
|
205
222
|
}
|
|
206
223
|
unobserve(target) {
|
|
207
224
|
if (!this.#observedTargets.delete(target)) return;
|
|
208
|
-
|
|
209
|
-
target
|
|
210
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
211
|
-
this.#activeTargets.delete(target);
|
|
225
|
+
this.#release(target);
|
|
226
|
+
if (this.#composition?.target === target) this.#letGo();
|
|
212
227
|
if (this.#confirmedTarget === target) this.#confirmedTarget = null;
|
|
213
228
|
}
|
|
214
229
|
disconnect() {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
target.removeEventListener("compositionend", this.#handleEnd);
|
|
218
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
219
|
-
}
|
|
230
|
+
this.#connected = false;
|
|
231
|
+
for (const target of this.#observedTargets) this.#release(target);
|
|
220
232
|
this.#observedTargets.clear();
|
|
221
|
-
this.#
|
|
233
|
+
this.#letGo();
|
|
222
234
|
this.#confirmedTarget = null;
|
|
235
|
+
this.#keyComposing = false;
|
|
236
|
+
this.#abandoned = null;
|
|
223
237
|
}
|
|
224
238
|
isComposing(event) {
|
|
225
|
-
|
|
239
|
+
this.#settle();
|
|
240
|
+
return event?.isComposing === true || this.#composition !== null;
|
|
241
|
+
}
|
|
242
|
+
composingElement() {
|
|
243
|
+
this.#settle();
|
|
244
|
+
return this.#composition?.element ?? null;
|
|
245
|
+
}
|
|
246
|
+
owns(event) {
|
|
247
|
+
const element = this.composingElement();
|
|
248
|
+
return event.isComposing === true || element !== null && element === event.target;
|
|
226
249
|
}
|
|
227
250
|
consumesConfirmedInput(event) {
|
|
228
251
|
const confirmed = this.#confirmedTarget;
|
|
229
252
|
this.#confirmedTarget = null;
|
|
230
253
|
if (confirmed === null || confirmed !== event.target) return false;
|
|
231
254
|
const inputType = event.inputType;
|
|
232
|
-
return !inputType ||
|
|
255
|
+
return !inputType || isCompositionInputType(inputType);
|
|
233
256
|
}
|
|
257
|
+
#release(target) {
|
|
258
|
+
target.removeEventListener("compositionstart", this.#handleStart);
|
|
259
|
+
target.removeEventListener("compositionend", this.#handleEnd);
|
|
260
|
+
target.removeEventListener("keydown", this.#handleKeydown, true);
|
|
261
|
+
target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
262
|
+
target.removeEventListener("input", this.#handleInput, true);
|
|
263
|
+
target.removeEventListener("blur", this.#handleBlur, true);
|
|
264
|
+
target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
265
|
+
}
|
|
266
|
+
#letGo() {
|
|
267
|
+
const composition = this.#composition;
|
|
268
|
+
if (composition === null) return;
|
|
269
|
+
if (!holdsFocus(composition.element)) {
|
|
270
|
+
this.#composition = null;
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
queueMicrotask(() => {
|
|
274
|
+
if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.#composition = null;
|
|
278
|
+
if (!this.#connected) return;
|
|
279
|
+
this.#abandoned = { element: composition.element, inPlace: true };
|
|
280
|
+
this.#runAbandon();
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
#settle() {
|
|
284
|
+
const composition = this.#composition;
|
|
285
|
+
if (composition === null) return;
|
|
286
|
+
const { target, element, text } = composition;
|
|
287
|
+
if (target instanceof Node && element instanceof Node && !target.contains(element)) {
|
|
288
|
+
this.#composition = null;
|
|
289
|
+
} else if (textOf(element) !== text) {
|
|
290
|
+
this.#abandon(false);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
#abandon(inPlace) {
|
|
294
|
+
this.#owe(inPlace);
|
|
295
|
+
this.#composition = null;
|
|
296
|
+
queueMicrotask(this.#runAbandon);
|
|
297
|
+
}
|
|
298
|
+
#owe(inPlace) {
|
|
299
|
+
const composition = this.#composition;
|
|
300
|
+
if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
|
|
301
|
+
}
|
|
302
|
+
#runAbandon = () => {
|
|
303
|
+
const abandoned = this.#abandoned;
|
|
304
|
+
if (abandoned === null) return;
|
|
305
|
+
this.#abandoned = null;
|
|
306
|
+
this.#onAbandon?.(abandoned.element, abandoned.inPlace);
|
|
307
|
+
};
|
|
234
308
|
#handleStart = (event) => {
|
|
235
309
|
this.#confirmedTarget = null;
|
|
236
|
-
|
|
310
|
+
this.#settle();
|
|
311
|
+
this.#owe(true);
|
|
312
|
+
const target = event.currentTarget;
|
|
313
|
+
const element = event.target;
|
|
314
|
+
this.#composition = { target, element, text: textOf(element) };
|
|
315
|
+
this.#runAbandon();
|
|
237
316
|
this.#onStart?.(event);
|
|
238
317
|
};
|
|
239
318
|
#handleEnd = (event) => {
|
|
240
|
-
|
|
319
|
+
this.#runAbandon();
|
|
320
|
+
if (this.#composition?.target === event.currentTarget) this.#composition = null;
|
|
241
321
|
this.#confirmedTarget = event.target;
|
|
242
322
|
this.#onEnd?.(event);
|
|
243
323
|
};
|
|
244
|
-
#handleKeydown = () => {
|
|
324
|
+
#handleKeydown = (event) => {
|
|
325
|
+
this.#settle();
|
|
326
|
+
this.#runAbandon();
|
|
245
327
|
this.#confirmedTarget = null;
|
|
328
|
+
this.#keyComposing = event.isComposing === true;
|
|
329
|
+
};
|
|
330
|
+
#handleBeforeInput = (event) => {
|
|
331
|
+
const keyComposing = this.#keyComposing;
|
|
332
|
+
this.#keyComposing = false;
|
|
333
|
+
this.#settle();
|
|
334
|
+
const { isComposing, inputType } = event;
|
|
335
|
+
if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
|
|
336
|
+
this.#abandon(true);
|
|
337
|
+
}
|
|
338
|
+
this.#runAbandon();
|
|
339
|
+
};
|
|
340
|
+
#handleInput = (event) => {
|
|
341
|
+
const composition = this.#composition;
|
|
342
|
+
const { isTrusted, isComposing, inputType } = event;
|
|
343
|
+
if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
|
|
344
|
+
composition.text = textOf(composition.element);
|
|
345
|
+
} else {
|
|
346
|
+
this.#settle();
|
|
347
|
+
}
|
|
348
|
+
this.#runAbandon();
|
|
349
|
+
};
|
|
350
|
+
#handleBlur = (event) => {
|
|
351
|
+
this.#settle();
|
|
352
|
+
this.#runAbandon();
|
|
353
|
+
const composition = this.#composition;
|
|
354
|
+
if (composition === null || composition.target === composition.element) return;
|
|
355
|
+
const { element } = composition;
|
|
356
|
+
if (!(element instanceof Node) || event.target !== element) return;
|
|
357
|
+
const root = element.getRootNode();
|
|
358
|
+
if (root.activeElement !== element) this.#composition = null;
|
|
359
|
+
};
|
|
360
|
+
#handleBeforeMorphAttribute = (event) => {
|
|
361
|
+
this.#settle();
|
|
362
|
+
const composition = this.#composition;
|
|
363
|
+
if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
|
|
364
|
+
const detail = event.detail;
|
|
365
|
+
if (detail?.attributeName === "value") event.preventDefault();
|
|
246
366
|
};
|
|
247
367
|
};
|
|
248
368
|
|
|
@@ -295,6 +415,9 @@ var MicrotaskCoalescer = class {
|
|
|
295
415
|
this.#queued = false;
|
|
296
416
|
this.#generation += 1;
|
|
297
417
|
}
|
|
418
|
+
get pending() {
|
|
419
|
+
return this.#queued;
|
|
420
|
+
}
|
|
298
421
|
schedule() {
|
|
299
422
|
if (!this.#active || this.#queued) return;
|
|
300
423
|
this.#queued = true;
|
|
@@ -325,6 +448,9 @@ var MorphRenderWatcher = class {
|
|
|
325
448
|
schedule() {
|
|
326
449
|
this.#pass.schedule();
|
|
327
450
|
}
|
|
451
|
+
get pending() {
|
|
452
|
+
return this.#pass.pending;
|
|
453
|
+
}
|
|
328
454
|
disconnect() {
|
|
329
455
|
this.#listeners.dispose();
|
|
330
456
|
this.#pass.cancel();
|
|
@@ -481,7 +607,7 @@ var TagsInputController = class _TagsInputController extends Controller {
|
|
|
481
607
|
});
|
|
482
608
|
#reconcile = new MorphRenderWatcher(() => this.#reconcileTags());
|
|
483
609
|
#chipRow = new ChipRow({
|
|
484
|
-
|
|
610
|
+
getDirectionElement: () => this.#chipDirectionElement,
|
|
485
611
|
getItems: () => this.tagTargets,
|
|
486
612
|
getButton: (tag) => matchingPart(tag, `button${this.#rows.selector("remove")}`),
|
|
487
613
|
onRemove: (index) => this.#removeAt(index),
|
|
@@ -554,7 +680,7 @@ var TagsInputController = class _TagsInputController extends Controller {
|
|
|
554
680
|
}
|
|
555
681
|
return;
|
|
556
682
|
}
|
|
557
|
-
if (logicalArrowKey(event.key, this
|
|
683
|
+
if (logicalArrowKey(event.key, this.#chipDirectionElement) === "ArrowLeft" && this.inputTarget.value === "") {
|
|
558
684
|
if (this.#chipRow.length > 0) {
|
|
559
685
|
event.preventDefault();
|
|
560
686
|
this.#chipRow.focusLast();
|
|
@@ -607,6 +733,9 @@ var TagsInputController = class _TagsInputController extends Controller {
|
|
|
607
733
|
}
|
|
608
734
|
if (!this.#chipRow.focusAfterRemoval(index)) this.#focusInput();
|
|
609
735
|
}
|
|
736
|
+
get #chipDirectionElement() {
|
|
737
|
+
return this.hasTagsTarget ? this.tagsTarget : this.element;
|
|
738
|
+
}
|
|
610
739
|
#focusInput() {
|
|
611
740
|
if (this.hasInputTarget) this.inputTarget.focus();
|
|
612
741
|
}
|