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
|
@@ -8,6 +8,9 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
8
8
|
if (!event.key.startsWith("Arrow")) return false;
|
|
9
9
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
10
10
|
}
|
|
11
|
+
function hasModifierChord(event) {
|
|
12
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
13
|
+
}
|
|
11
14
|
|
|
12
15
|
var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
13
16
|
"insertCompositionText",
|
|
@@ -15,63 +18,182 @@ var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
|
15
18
|
"deleteCompositionText",
|
|
16
19
|
"deleteByComposition"
|
|
17
20
|
]);
|
|
21
|
+
function isCompositionInputType(inputType) {
|
|
22
|
+
return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
|
|
23
|
+
}
|
|
24
|
+
var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
|
|
25
|
+
var textOf = (element) => element.value;
|
|
26
|
+
var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
|
|
18
27
|
var CompositionTracker = class {
|
|
19
28
|
#observedTargets = /* @__PURE__ */ new Set();
|
|
20
|
-
#
|
|
29
|
+
#composition = null;
|
|
21
30
|
#onStart;
|
|
22
31
|
#onEnd;
|
|
32
|
+
#onAbandon;
|
|
23
33
|
#confirmedTarget = null;
|
|
34
|
+
#keyComposing = false;
|
|
35
|
+
#connected = false;
|
|
36
|
+
#abandoned = null;
|
|
24
37
|
constructor(options = {}) {
|
|
25
38
|
this.#onStart = options.onStart;
|
|
26
39
|
this.#onEnd = options.onEnd;
|
|
40
|
+
this.#onAbandon = options.onAbandon;
|
|
27
41
|
}
|
|
28
42
|
observe(target) {
|
|
43
|
+
this.#connected = true;
|
|
29
44
|
if (this.#observedTargets.has(target)) return;
|
|
30
45
|
target.addEventListener("compositionstart", this.#handleStart);
|
|
31
46
|
target.addEventListener("compositionend", this.#handleEnd);
|
|
32
|
-
target.addEventListener("keydown", this.#handleKeydown);
|
|
47
|
+
target.addEventListener("keydown", this.#handleKeydown, true);
|
|
48
|
+
target.addEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
49
|
+
target.addEventListener("input", this.#handleInput, true);
|
|
50
|
+
target.addEventListener("blur", this.#handleBlur, true);
|
|
51
|
+
target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
33
52
|
this.#observedTargets.add(target);
|
|
34
53
|
}
|
|
35
54
|
unobserve(target) {
|
|
36
55
|
if (!this.#observedTargets.delete(target)) return;
|
|
37
|
-
|
|
38
|
-
target
|
|
39
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
40
|
-
this.#activeTargets.delete(target);
|
|
56
|
+
this.#release(target);
|
|
57
|
+
if (this.#composition?.target === target) this.#letGo();
|
|
41
58
|
if (this.#confirmedTarget === target) this.#confirmedTarget = null;
|
|
42
59
|
}
|
|
43
60
|
disconnect() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
target.removeEventListener("compositionend", this.#handleEnd);
|
|
47
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
48
|
-
}
|
|
61
|
+
this.#connected = false;
|
|
62
|
+
for (const target of this.#observedTargets) this.#release(target);
|
|
49
63
|
this.#observedTargets.clear();
|
|
50
|
-
this.#
|
|
64
|
+
this.#letGo();
|
|
51
65
|
this.#confirmedTarget = null;
|
|
66
|
+
this.#keyComposing = false;
|
|
67
|
+
this.#abandoned = null;
|
|
52
68
|
}
|
|
53
69
|
isComposing(event) {
|
|
54
|
-
|
|
70
|
+
this.#settle();
|
|
71
|
+
return event?.isComposing === true || this.#composition !== null;
|
|
72
|
+
}
|
|
73
|
+
composingElement() {
|
|
74
|
+
this.#settle();
|
|
75
|
+
return this.#composition?.element ?? null;
|
|
76
|
+
}
|
|
77
|
+
owns(event) {
|
|
78
|
+
const element = this.composingElement();
|
|
79
|
+
return event.isComposing === true || element !== null && element === event.target;
|
|
55
80
|
}
|
|
56
81
|
consumesConfirmedInput(event) {
|
|
57
82
|
const confirmed = this.#confirmedTarget;
|
|
58
83
|
this.#confirmedTarget = null;
|
|
59
84
|
if (confirmed === null || confirmed !== event.target) return false;
|
|
60
85
|
const inputType = event.inputType;
|
|
61
|
-
return !inputType ||
|
|
86
|
+
return !inputType || isCompositionInputType(inputType);
|
|
87
|
+
}
|
|
88
|
+
#release(target) {
|
|
89
|
+
target.removeEventListener("compositionstart", this.#handleStart);
|
|
90
|
+
target.removeEventListener("compositionend", this.#handleEnd);
|
|
91
|
+
target.removeEventListener("keydown", this.#handleKeydown, true);
|
|
92
|
+
target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
93
|
+
target.removeEventListener("input", this.#handleInput, true);
|
|
94
|
+
target.removeEventListener("blur", this.#handleBlur, true);
|
|
95
|
+
target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
96
|
+
}
|
|
97
|
+
#letGo() {
|
|
98
|
+
const composition = this.#composition;
|
|
99
|
+
if (composition === null) return;
|
|
100
|
+
if (!holdsFocus(composition.element)) {
|
|
101
|
+
this.#composition = null;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
queueMicrotask(() => {
|
|
105
|
+
if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.#composition = null;
|
|
109
|
+
if (!this.#connected) return;
|
|
110
|
+
this.#abandoned = { element: composition.element, inPlace: true };
|
|
111
|
+
this.#runAbandon();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
#settle() {
|
|
115
|
+
const composition = this.#composition;
|
|
116
|
+
if (composition === null) return;
|
|
117
|
+
const { target, element, text } = composition;
|
|
118
|
+
if (target instanceof Node && element instanceof Node && !target.contains(element)) {
|
|
119
|
+
this.#composition = null;
|
|
120
|
+
} else if (textOf(element) !== text) {
|
|
121
|
+
this.#abandon(false);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
#abandon(inPlace) {
|
|
125
|
+
this.#owe(inPlace);
|
|
126
|
+
this.#composition = null;
|
|
127
|
+
queueMicrotask(this.#runAbandon);
|
|
128
|
+
}
|
|
129
|
+
#owe(inPlace) {
|
|
130
|
+
const composition = this.#composition;
|
|
131
|
+
if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
|
|
62
132
|
}
|
|
133
|
+
#runAbandon = () => {
|
|
134
|
+
const abandoned = this.#abandoned;
|
|
135
|
+
if (abandoned === null) return;
|
|
136
|
+
this.#abandoned = null;
|
|
137
|
+
this.#onAbandon?.(abandoned.element, abandoned.inPlace);
|
|
138
|
+
};
|
|
63
139
|
#handleStart = (event) => {
|
|
64
140
|
this.#confirmedTarget = null;
|
|
65
|
-
|
|
141
|
+
this.#settle();
|
|
142
|
+
this.#owe(true);
|
|
143
|
+
const target = event.currentTarget;
|
|
144
|
+
const element = event.target;
|
|
145
|
+
this.#composition = { target, element, text: textOf(element) };
|
|
146
|
+
this.#runAbandon();
|
|
66
147
|
this.#onStart?.(event);
|
|
67
148
|
};
|
|
68
149
|
#handleEnd = (event) => {
|
|
69
|
-
|
|
150
|
+
this.#runAbandon();
|
|
151
|
+
if (this.#composition?.target === event.currentTarget) this.#composition = null;
|
|
70
152
|
this.#confirmedTarget = event.target;
|
|
71
153
|
this.#onEnd?.(event);
|
|
72
154
|
};
|
|
73
|
-
#handleKeydown = () => {
|
|
155
|
+
#handleKeydown = (event) => {
|
|
156
|
+
this.#settle();
|
|
157
|
+
this.#runAbandon();
|
|
74
158
|
this.#confirmedTarget = null;
|
|
159
|
+
this.#keyComposing = event.isComposing === true;
|
|
160
|
+
};
|
|
161
|
+
#handleBeforeInput = (event) => {
|
|
162
|
+
const keyComposing = this.#keyComposing;
|
|
163
|
+
this.#keyComposing = false;
|
|
164
|
+
this.#settle();
|
|
165
|
+
const { isComposing, inputType } = event;
|
|
166
|
+
if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
|
|
167
|
+
this.#abandon(true);
|
|
168
|
+
}
|
|
169
|
+
this.#runAbandon();
|
|
170
|
+
};
|
|
171
|
+
#handleInput = (event) => {
|
|
172
|
+
const composition = this.#composition;
|
|
173
|
+
const { isTrusted, isComposing, inputType } = event;
|
|
174
|
+
if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
|
|
175
|
+
composition.text = textOf(composition.element);
|
|
176
|
+
} else {
|
|
177
|
+
this.#settle();
|
|
178
|
+
}
|
|
179
|
+
this.#runAbandon();
|
|
180
|
+
};
|
|
181
|
+
#handleBlur = (event) => {
|
|
182
|
+
this.#settle();
|
|
183
|
+
this.#runAbandon();
|
|
184
|
+
const composition = this.#composition;
|
|
185
|
+
if (composition === null || composition.target === composition.element) return;
|
|
186
|
+
const { element } = composition;
|
|
187
|
+
if (!(element instanceof Node) || event.target !== element) return;
|
|
188
|
+
const root = element.getRootNode();
|
|
189
|
+
if (root.activeElement !== element) this.#composition = null;
|
|
190
|
+
};
|
|
191
|
+
#handleBeforeMorphAttribute = (event) => {
|
|
192
|
+
this.#settle();
|
|
193
|
+
const composition = this.#composition;
|
|
194
|
+
if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
|
|
195
|
+
const detail = event.detail;
|
|
196
|
+
if (detail?.attributeName === "value") event.preventDefault();
|
|
75
197
|
};
|
|
76
198
|
};
|
|
77
199
|
|
|
@@ -117,6 +239,9 @@ var MicrotaskCoalescer = class {
|
|
|
117
239
|
this.#queued = false;
|
|
118
240
|
this.#generation += 1;
|
|
119
241
|
}
|
|
242
|
+
get pending() {
|
|
243
|
+
return this.#queued;
|
|
244
|
+
}
|
|
120
245
|
schedule() {
|
|
121
246
|
if (!this.#active || this.#queued) return;
|
|
122
247
|
this.#queued = true;
|
|
@@ -147,6 +272,9 @@ var MorphRenderWatcher = class {
|
|
|
147
272
|
schedule() {
|
|
148
273
|
this.#pass.schedule();
|
|
149
274
|
}
|
|
275
|
+
get pending() {
|
|
276
|
+
return this.#pass.pending;
|
|
277
|
+
}
|
|
150
278
|
disconnect() {
|
|
151
279
|
this.#listeners.dispose();
|
|
152
280
|
this.#pass.cancel();
|
|
@@ -191,11 +319,12 @@ var ToolbarController = class extends Controller {
|
|
|
191
319
|
#observer = null;
|
|
192
320
|
#connected = false;
|
|
193
321
|
#morph = new MorphRenderWatcher(() => this.#ensureTabStop());
|
|
322
|
+
#listeners = new ListenerSet();
|
|
194
323
|
connect() {
|
|
195
324
|
this.#morph.observe(this.element);
|
|
196
325
|
this.#ensureTabStop();
|
|
197
|
-
this.element
|
|
198
|
-
this.element
|
|
326
|
+
this.#listeners.add(this.element, "keydown", this.#onKeydown);
|
|
327
|
+
this.#listeners.add(this.element, "focusin", this.#onFocusin);
|
|
199
328
|
this.#composition.observe(this.element);
|
|
200
329
|
if (typeof MutationObserver !== "undefined") {
|
|
201
330
|
const observer = new MutationObserver(() => this.#morph.schedule());
|
|
@@ -215,8 +344,7 @@ var ToolbarController = class extends Controller {
|
|
|
215
344
|
disconnect() {
|
|
216
345
|
this.#morph.disconnect();
|
|
217
346
|
this.#connected = false;
|
|
218
|
-
this.
|
|
219
|
-
this.element.removeEventListener("focusin", this.#onFocusin);
|
|
347
|
+
this.#listeners.dispose();
|
|
220
348
|
this.#composition.disconnect();
|
|
221
349
|
this.#observer?.disconnect();
|
|
222
350
|
this.#observer = null;
|
|
@@ -245,6 +373,7 @@ var ToolbarController = class extends Controller {
|
|
|
245
373
|
#handleKeydown(event) {
|
|
246
374
|
if (event.defaultPrevented) return;
|
|
247
375
|
if (isReservedArrowChord(event)) return;
|
|
376
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
248
377
|
if (this.#composition.isComposing(event)) return;
|
|
249
378
|
this.#ensureTabStop();
|
|
250
379
|
const from = this.#indexOf(event.target);
|
|
@@ -30,6 +30,107 @@ 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
|
+
}
|
|
36
|
+
|
|
37
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
38
|
+
if (element.getAttribute(name) === value) return false;
|
|
39
|
+
beforeWrite?.();
|
|
40
|
+
if (value === null) element.removeAttribute(name);
|
|
41
|
+
else element.setAttribute(name, value);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var AuthoredMutationWatcher = class {
|
|
46
|
+
#onAuthored;
|
|
47
|
+
#claims = null;
|
|
48
|
+
#observer = null;
|
|
49
|
+
constructor(onAuthored) {
|
|
50
|
+
this.#onAuthored = onAuthored;
|
|
51
|
+
}
|
|
52
|
+
observe(root, options) {
|
|
53
|
+
this.disconnect();
|
|
54
|
+
const observer = new MutationObserver((records) => {
|
|
55
|
+
if (this.#observer !== observer) return;
|
|
56
|
+
const authored = this.#filter(records);
|
|
57
|
+
if (authored.length > 0) this.#onAuthored(authored);
|
|
58
|
+
});
|
|
59
|
+
this.#observer = observer;
|
|
60
|
+
this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
|
|
61
|
+
observer.observe(root, {
|
|
62
|
+
attributes: true,
|
|
63
|
+
attributeFilter: [...options.attributes],
|
|
64
|
+
subtree: options.subtree ?? false,
|
|
65
|
+
childList: options.childList ?? false
|
|
66
|
+
});
|
|
67
|
+
if (options.ancestorAttributes) {
|
|
68
|
+
let ancestor = root.parentElement;
|
|
69
|
+
while (ancestor) {
|
|
70
|
+
observer.observe(ancestor, {
|
|
71
|
+
attributes: true,
|
|
72
|
+
attributeFilter: [...options.ancestorAttributes]
|
|
73
|
+
});
|
|
74
|
+
ancestor = ancestor.parentElement;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
claim(target, name, value) {
|
|
79
|
+
const claims = this.#claims?.attributes;
|
|
80
|
+
let attributes = claims?.get(target);
|
|
81
|
+
if (!attributes) {
|
|
82
|
+
attributes = /* @__PURE__ */ new Map();
|
|
83
|
+
claims?.set(target, attributes);
|
|
84
|
+
}
|
|
85
|
+
const pending = attributes.get(name);
|
|
86
|
+
attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
|
|
87
|
+
}
|
|
88
|
+
claimCurrent(target, name) {
|
|
89
|
+
this.claim(target, name, target.getAttribute(name));
|
|
90
|
+
}
|
|
91
|
+
claimChildList(target, added, removed) {
|
|
92
|
+
const claims = this.#claims?.children;
|
|
93
|
+
const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
|
|
94
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
|
|
95
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
|
|
96
|
+
claims?.set(target, pending);
|
|
97
|
+
}
|
|
98
|
+
take() {
|
|
99
|
+
return this.#filter(this.#observer?.takeRecords() ?? []);
|
|
100
|
+
}
|
|
101
|
+
disconnect() {
|
|
102
|
+
this.#observer?.disconnect();
|
|
103
|
+
this.#observer = null;
|
|
104
|
+
this.#claims = null;
|
|
105
|
+
}
|
|
106
|
+
#filter(records) {
|
|
107
|
+
const authored = records.filter((record) => !this.#consume(record));
|
|
108
|
+
this.#claims?.attributes.clear();
|
|
109
|
+
this.#claims?.children.clear();
|
|
110
|
+
return authored;
|
|
111
|
+
}
|
|
112
|
+
#consume(record) {
|
|
113
|
+
if (record.type === "attributes") {
|
|
114
|
+
const target = record.target;
|
|
115
|
+
const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
|
|
116
|
+
if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
pending2.count -= 1;
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
const pending = this.#claims?.children.get(record.target);
|
|
123
|
+
if (!pending) return false;
|
|
124
|
+
const added = [...record.addedNodes];
|
|
125
|
+
const removed = [...record.removedNodes];
|
|
126
|
+
if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
|
|
130
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
33
134
|
|
|
34
135
|
function writeField(field, value) {
|
|
35
136
|
if (field.value === value) return false;
|
|
@@ -111,6 +212,9 @@ var MicrotaskCoalescer = class {
|
|
|
111
212
|
this.#queued = false;
|
|
112
213
|
this.#generation += 1;
|
|
113
214
|
}
|
|
215
|
+
get pending() {
|
|
216
|
+
return this.#queued;
|
|
217
|
+
}
|
|
114
218
|
schedule() {
|
|
115
219
|
if (!this.#active || this.#queued) return;
|
|
116
220
|
this.#queued = true;
|
|
@@ -141,6 +245,9 @@ var MorphRenderWatcher = class {
|
|
|
141
245
|
schedule() {
|
|
142
246
|
this.#pass.schedule();
|
|
143
247
|
}
|
|
248
|
+
get pending() {
|
|
249
|
+
return this.#pass.pending;
|
|
250
|
+
}
|
|
144
251
|
disconnect() {
|
|
145
252
|
this.#listeners.dispose();
|
|
146
253
|
this.#pass.cancel();
|
|
@@ -262,19 +369,19 @@ function findTypeaheadMatch(items, from, query, label = (item) => typeaheadLabel
|
|
|
262
369
|
}
|
|
263
370
|
|
|
264
371
|
var OBSERVED_ATTRIBUTES = ["aria-selected", "data-value"];
|
|
265
|
-
function setAttributeIfChanged(element, name, value) {
|
|
266
|
-
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
267
|
-
}
|
|
268
372
|
var NESTED_INTERACTIVE = INTERACTIVE_HOST_SELECTOR;
|
|
269
373
|
var TreeViewController = class extends Controller {
|
|
270
374
|
#moves = new MoveCounter();
|
|
375
|
+
#connectionGeneration = 0;
|
|
271
376
|
static targets = ["item", "group", "field"];
|
|
272
377
|
static actions = ["onKeydown", "select", "toggle"];
|
|
273
378
|
static events = ["select", "toggle", "reconcile"];
|
|
274
379
|
#roving = new RovingTabindex(() => this.itemTargets);
|
|
275
380
|
#typeahead = new Typeahead();
|
|
276
381
|
#reconcile = new MorphRenderWatcher(() => this.#reconcileSelection());
|
|
277
|
-
#observer =
|
|
382
|
+
#observer = new AuthoredMutationWatcher((records) => {
|
|
383
|
+
if (this.#concernsItems(records)) this.#reconcile.schedule();
|
|
384
|
+
});
|
|
278
385
|
#settled = { item: null, value: "" };
|
|
279
386
|
#connected = false;
|
|
280
387
|
#order = [];
|
|
@@ -293,9 +400,10 @@ var TreeViewController = class extends Controller {
|
|
|
293
400
|
if (this.#focused === row && row.isConnected) this.#focused = null;
|
|
294
401
|
});
|
|
295
402
|
};
|
|
403
|
+
#listeners = new ListenerSet();
|
|
296
404
|
connect() {
|
|
297
|
-
this.element
|
|
298
|
-
this.element
|
|
405
|
+
this.#listeners.add(this.element, "focusin", this.#onFocusIn);
|
|
406
|
+
this.#listeners.add(this.element, "focusout", this.#onFocusOut);
|
|
299
407
|
this.#seedFocused();
|
|
300
408
|
this.#reconcileExpansion();
|
|
301
409
|
this.#normalizeSelection();
|
|
@@ -314,12 +422,11 @@ var TreeViewController = class extends Controller {
|
|
|
314
422
|
this.#reconcile.schedule();
|
|
315
423
|
}
|
|
316
424
|
disconnect() {
|
|
317
|
-
this
|
|
318
|
-
this.
|
|
425
|
+
this.#connectionGeneration += 1;
|
|
426
|
+
this.#listeners.dispose();
|
|
319
427
|
this.#connected = false;
|
|
320
428
|
this.#reconcile.disconnect();
|
|
321
|
-
this.#observer
|
|
322
|
-
this.#observer = null;
|
|
429
|
+
this.#observer.disconnect();
|
|
323
430
|
this.#typeahead.reset();
|
|
324
431
|
this.#order = [];
|
|
325
432
|
this.#focused = null;
|
|
@@ -337,7 +444,7 @@ var TreeViewController = class extends Controller {
|
|
|
337
444
|
const items = this.itemTargets;
|
|
338
445
|
const selected = items.find((item) => item.getAttribute("aria-selected") === "true");
|
|
339
446
|
for (const item of items) {
|
|
340
|
-
|
|
447
|
+
this.#writeSelection(item, item === selected ? "true" : "false");
|
|
341
448
|
}
|
|
342
449
|
}
|
|
343
450
|
#mirrorField(notify) {
|
|
@@ -353,7 +460,7 @@ var TreeViewController = class extends Controller {
|
|
|
353
460
|
this.#reconcileExpansion();
|
|
354
461
|
this.#normalizeSelection();
|
|
355
462
|
this.#normalizeTabStop();
|
|
356
|
-
this.#
|
|
463
|
+
this.#flushMutations();
|
|
357
464
|
this.#mirrorField(false);
|
|
358
465
|
this.#reportMove();
|
|
359
466
|
}
|
|
@@ -364,22 +471,17 @@ var TreeViewController = class extends Controller {
|
|
|
364
471
|
this.dispatch("reconcile", { detail: { item: selection.item } });
|
|
365
472
|
}
|
|
366
473
|
#observeItems() {
|
|
367
|
-
|
|
368
|
-
if (this.#concernsItems(records)) this.#reconcile.schedule();
|
|
369
|
-
});
|
|
370
|
-
observer.observe(this.element, {
|
|
474
|
+
this.#observer.observe(this.element, {
|
|
371
475
|
subtree: true,
|
|
372
|
-
attributes:
|
|
373
|
-
attributeFilter: OBSERVED_ATTRIBUTES
|
|
476
|
+
attributes: OBSERVED_ATTRIBUTES
|
|
374
477
|
});
|
|
375
|
-
this.#observer = observer;
|
|
376
478
|
}
|
|
377
479
|
#concernsItems(records) {
|
|
378
480
|
const items = new Set(this.itemTargets);
|
|
379
481
|
return records.some((record) => items.has(record.target));
|
|
380
482
|
}
|
|
381
|
-
#
|
|
382
|
-
this.#observer
|
|
483
|
+
#flushMutations() {
|
|
484
|
+
if (this.#concernsItems(this.#observer.take())) this.#reconcile.schedule();
|
|
383
485
|
}
|
|
384
486
|
itemTargetDisconnected(item) {
|
|
385
487
|
if (!this.#connected || !this.element.isConnected) return;
|
|
@@ -408,6 +510,7 @@ var TreeViewController = class extends Controller {
|
|
|
408
510
|
onKeydown(event) {
|
|
409
511
|
if (event.defaultPrevented) return;
|
|
410
512
|
if (isReservedArrowChord(event)) return;
|
|
513
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
411
514
|
if (event.isComposing) return;
|
|
412
515
|
const item = this.#ownerItem(event);
|
|
413
516
|
if (!item) return;
|
|
@@ -553,10 +656,11 @@ var TreeViewController = class extends Controller {
|
|
|
553
656
|
}
|
|
554
657
|
#select(item, reason = "user") {
|
|
555
658
|
if (this.#isDisabled(item)) return;
|
|
659
|
+
const generation = this.#connectionGeneration;
|
|
556
660
|
for (const candidate of this.itemTargets) {
|
|
557
|
-
|
|
661
|
+
this.#writeSelection(candidate, candidate === item ? "true" : "false");
|
|
558
662
|
}
|
|
559
|
-
this.#
|
|
663
|
+
this.#flushMutations();
|
|
560
664
|
const selection = this.#selection();
|
|
561
665
|
const changed = selection.item !== this.#settled.item || selection.value !== this.#settled.value;
|
|
562
666
|
this.#settled = selection;
|
|
@@ -566,7 +670,7 @@ var TreeViewController = class extends Controller {
|
|
|
566
670
|
}
|
|
567
671
|
const token = this.#moves.record();
|
|
568
672
|
this.#mirrorField(true);
|
|
569
|
-
if (!this.#moves.isLatest(token)) return;
|
|
673
|
+
if (generation !== this.#connectionGeneration || !this.#moves.isLatest(token)) return;
|
|
570
674
|
this.dispatch("select", { detail: { item, reason } });
|
|
571
675
|
}
|
|
572
676
|
#setExpanded(item, group, expanded, reason = "user") {
|
|
@@ -631,7 +735,7 @@ var TreeViewController = class extends Controller {
|
|
|
631
735
|
}
|
|
632
736
|
const selected = visible.find((item) => item.getAttribute("aria-selected") === "true");
|
|
633
737
|
const next = selected ?? visible[0];
|
|
634
|
-
this.#roving.setActive(items.
|
|
738
|
+
this.#roving.setActive(items.indexOf(next));
|
|
635
739
|
}
|
|
636
740
|
get #visibleItems() {
|
|
637
741
|
return this.itemTargets.filter((item) => {
|
|
@@ -664,6 +768,11 @@ var TreeViewController = class extends Controller {
|
|
|
664
768
|
return text;
|
|
665
769
|
});
|
|
666
770
|
}
|
|
771
|
+
#writeSelection(target, value) {
|
|
772
|
+
setAttributeIfChanged(target, "aria-selected", value, () => {
|
|
773
|
+
this.#observer.claim(target, "aria-selected", value);
|
|
774
|
+
});
|
|
775
|
+
}
|
|
667
776
|
};
|
|
668
777
|
|
|
669
778
|
export { TreeViewController };
|