stimeo-ui 0.15.0 → 0.16.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.
- package/CHANGELOG.md +178 -0
- package/README.md +120 -0
- package/dist/cable/index.js +63 -15
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +30 -3
- package/dist/controllers/accordion_controller.js +98 -9
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +8 -7
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +3 -2
- package/dist/controllers/announcer_controller.js +8 -1
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.js +43 -2
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +1 -1
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.d.ts +10 -7
- package/dist/controllers/breadcrumb_controller.js +9 -8
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +4 -4
- package/dist/controllers/bulk_select_controller.js +7 -6
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +85 -23
- package/dist/controllers/calendar_controller.js +323 -123
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +67 -22
- package/dist/controllers/carousel_controller.js +263 -38
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +1 -1
- package/dist/controllers/character_counter_controller.js +40 -2
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.js +54 -10
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +28 -2
- package/dist/controllers/clipboard_controller.js +63 -5
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +25 -1
- package/dist/controllers/collapsible_controller.js +99 -14
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +29 -8
- package/dist/controllers/color_picker_controller.js +80 -34
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +10 -1
- package/dist/controllers/combobox_controller.js +106 -16
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +3 -3
- package/dist/controllers/command_palette_controller.js +35 -3
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.js +56 -11
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.js +3 -0
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +6 -0
- package/dist/controllers/context_menu_controller.js +32 -12
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +30 -1
- package/dist/controllers/countdown_controller.js +129 -26
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +74 -15
- package/dist/controllers/currency_input_controller.js +205 -58
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +63 -18
- package/dist/controllers/data_grid_controller.js +195 -29
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +34 -6
- package/dist/controllers/date_range_picker_controller.js +139 -27
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +10 -3
- package/dist/controllers/dialog_controller.js +35 -8
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.js +22 -4
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +2 -2
- package/dist/controllers/dirty_form_controller.js +14 -1
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.js +1 -0
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +18 -10
- package/dist/controllers/drawer_controller.js +54 -19
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +9 -3
- package/dist/controllers/dropdown_controller.js +36 -9
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.js +34 -0
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.js +144 -51
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.d.ts +10 -4
- package/dist/controllers/filter_controller.js +20 -6
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +26 -6
- package/dist/controllers/flash_controller.js +305 -46
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.js +1 -0
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +7 -5
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +19 -13
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.js +16 -2
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.js +19 -2
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +10 -2
- package/dist/controllers/hover_card_controller.js +40 -14
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +16 -3
- package/dist/controllers/idle_controller.js +90 -5
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +5 -2
- package/dist/controllers/input_mask_controller.js +65 -9
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.js +3 -0
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.js +11 -2
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +50 -8
- package/dist/controllers/listbox_controller.js +203 -45
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.js +0 -2
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +9 -3
- package/dist/controllers/menu_controller.js +45 -16
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +11 -0
- package/dist/controllers/menubar_controller.js +58 -24
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.js +9 -5
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +18 -3
- package/dist/controllers/multi_select_controller.js +278 -104
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +11 -0
- package/dist/controllers/navigation_menu_controller.js +48 -15
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.js +37 -8
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.js +9 -1
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +36 -8
- package/dist/controllers/number_input_controller.js +124 -21
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +14 -1
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +22 -7
- package/dist/controllers/otp_controller.js +198 -55
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +8 -12
- package/dist/controllers/overflow_indicator_controller.js +84 -13
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +26 -6
- package/dist/controllers/overflow_menu_controller.js +112 -41
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +19 -11
- package/dist/controllers/pagination_controller.js +74 -28
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +15 -1
- package/dist/controllers/password_reveal_controller.js +59 -2
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/persist_controller.js +30 -8
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.js +131 -52
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +9 -3
- package/dist/controllers/popover_controller.js +45 -11
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +1 -1
- package/dist/controllers/portal_controller.js +6 -2
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.js +16 -1
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.js +8 -4
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +6 -4
- package/dist/controllers/radio_group_controller.js +42 -17
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +49 -1
- package/dist/controllers/range_slider_controller.js +88 -42
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +14 -3
- package/dist/controllers/rating_controller.js +39 -15
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +24 -2
- package/dist/controllers/read_more_controller.js +69 -2
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/resizable_controller.d.ts +16 -2
- package/dist/controllers/resizable_controller.js +82 -22
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.js +5 -1
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.js +0 -1
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +3 -2
- package/dist/controllers/scrollspy_controller.js +6 -0
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +41 -14
- package/dist/controllers/separator_controller.js +66 -37
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +20 -3
- package/dist/controllers/sidebar_controller.js +77 -18
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.js +6 -1
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +45 -7
- package/dist/controllers/slider_controller.js +82 -47
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.js +11 -1
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +17 -2
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.js +10 -2
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +19 -17
- package/dist/controllers/step_indicator_controller.js +18 -17
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +34 -9
- package/dist/controllers/stepper_controller.js +101 -19
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +19 -0
- package/dist/controllers/stick_to_bottom_controller.js +75 -5
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +3 -2
- package/dist/controllers/submit_once_controller.js +16 -3
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +29 -7
- package/dist/controllers/switch_controller.js +101 -10
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +12 -0
- package/dist/controllers/tabs_controller.js +21 -2
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +15 -3
- package/dist/controllers/tags_input_controller.js +209 -59
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +20 -3
- package/dist/controllers/theme_controller.js +64 -14
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.js +23 -8
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +52 -15
- package/dist/controllers/toast_controller.js +337 -54
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +49 -7
- package/dist/controllers/toggle_group_controller.js +159 -23
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +32 -0
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +8 -0
- package/dist/controllers/tooltip_controller.js +39 -13
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.js +4 -0
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +39 -8
- package/dist/controllers/tree_view_controller.js +169 -16
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +4588 -1535
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +72 -6
- package/dist/inspector/cli.js +262 -51
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +309 -51
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +40 -40
- package/dist/inspector/manifest.json +525 -59
- package/dist/positioning/index.js +2 -0
- package/dist/positioning/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,6 +8,16 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
8
8
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
// src/utils/field_mirror.ts
|
|
12
|
+
function writeField(field, value) {
|
|
13
|
+
if (field.value === value) return false;
|
|
14
|
+
field.value = value;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
function commitField(target) {
|
|
18
|
+
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
// src/utils/microtask_coalescer.ts
|
|
12
22
|
var MicrotaskCoalescer = class {
|
|
13
23
|
#run;
|
|
@@ -165,45 +175,79 @@ function findTypeaheadMatch(items, from, query, label = (item) => typeaheadLabel
|
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
// src/controllers/listbox_controller.ts
|
|
178
|
+
var OBSERVED_ATTRIBUTES = ["aria-selected", "data-value"];
|
|
179
|
+
function setAttributeIfChanged(element, name, value) {
|
|
180
|
+
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
181
|
+
}
|
|
168
182
|
var ListboxController = class extends Controller {
|
|
169
183
|
static targets = ["trigger", "value", "list", "option", "field"];
|
|
184
|
+
static values = {
|
|
185
|
+
placeholder: { type: String, default: "" }
|
|
186
|
+
};
|
|
170
187
|
static actions = ["close", "onTriggerKeydown", "open", "select", "toggle"];
|
|
171
|
-
static events = ["change"];
|
|
188
|
+
static events = ["change", "reconcile"];
|
|
172
189
|
/** Stable ID of the active option; DOM targets are resolved afresh before use. */
|
|
173
190
|
#activeId = null;
|
|
174
191
|
/** Target ID order captured while an option is active, used only for removal fallback. */
|
|
175
192
|
#activeOrder = [];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Collapses the target callbacks, Value changes and observed option writes of
|
|
195
|
+
* one DOM mutation into a single pass; refused before `connect()` and after
|
|
196
|
+
* `disconnect()`.
|
|
197
|
+
*/
|
|
198
|
+
#reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());
|
|
199
|
+
/** Watches the option attributes a page can rewrite in place; set while connected. */
|
|
200
|
+
#observer = null;
|
|
201
|
+
/** The selection last settled: on connect, by the user, or by a reported pass. */
|
|
202
|
+
#settled = { option: null, value: "" };
|
|
179
203
|
/** Accumulated typeahead query and its idle-reset timer. */
|
|
180
204
|
#typeahead = new Typeahead();
|
|
181
|
-
/**
|
|
205
|
+
/**
|
|
206
|
+
* Establishes the ARIA baseline, settles the selection without reporting it,
|
|
207
|
+
* starts closed, and listens for outside clicks.
|
|
208
|
+
*/
|
|
182
209
|
connect() {
|
|
183
210
|
this.#normalizeSelection();
|
|
184
211
|
this.close();
|
|
185
212
|
document.addEventListener("click", this.#onOutsideClick, true);
|
|
186
|
-
this.#
|
|
213
|
+
this.#settled = this.#selection();
|
|
187
214
|
this.#reconcile.activate();
|
|
215
|
+
this.#observeOptions();
|
|
188
216
|
}
|
|
189
217
|
/**
|
|
190
|
-
* Establishes an inactive baseline for a late option, re-
|
|
191
|
-
*
|
|
218
|
+
* Establishes an inactive baseline for a late option, then re-applies the
|
|
219
|
+
* selection baseline and re-resolves active identity once the batch is in.
|
|
192
220
|
*/
|
|
193
221
|
optionTargetConnected(option) {
|
|
194
222
|
option.removeAttribute("data-active");
|
|
195
|
-
|
|
196
|
-
this.#normalizeSelection();
|
|
197
|
-
this.#queueOptionReconciliation();
|
|
223
|
+
this.#reconcile.schedule();
|
|
198
224
|
}
|
|
199
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* Removes controller-owned active state, then settles the selection and the
|
|
227
|
+
* active option among the surviving targets once the batch is in.
|
|
228
|
+
*/
|
|
200
229
|
optionTargetDisconnected(option) {
|
|
201
230
|
option.removeAttribute("data-active");
|
|
202
|
-
|
|
231
|
+
this.#reconcile.schedule();
|
|
232
|
+
}
|
|
233
|
+
/** Fills a form field inserted or replaced at runtime with the current selection. */
|
|
234
|
+
fieldTargetConnected() {
|
|
235
|
+
this.#reconcile.schedule();
|
|
236
|
+
}
|
|
237
|
+
/** Writes the current selection into a trigger value inserted or replaced at runtime. */
|
|
238
|
+
valueTargetConnected() {
|
|
239
|
+
this.#reconcile.schedule();
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Re-renders the empty-selection label when application code (or a Turbo morph)
|
|
243
|
+
* changes `placeholder` at runtime.
|
|
244
|
+
*/
|
|
245
|
+
placeholderValueChanged() {
|
|
246
|
+
this.#reconcile.schedule();
|
|
203
247
|
}
|
|
204
248
|
/**
|
|
205
249
|
* Brings the authored DOM to the shape the APG requires, and derives the state
|
|
206
|
-
* that follows from the
|
|
250
|
+
* that follows from the selection.
|
|
207
251
|
*
|
|
208
252
|
* Three things happen, and only these three — which option is chosen is the
|
|
209
253
|
* author's, and is never changed:
|
|
@@ -212,27 +256,59 @@ var ListboxController = class extends Controller {
|
|
|
212
256
|
* selectable" in ARIA, so a forgotten attribute hides a selectable option
|
|
213
257
|
* from assistive technology.
|
|
214
258
|
* 2. At most one stays `true`. The first in DOM order wins, since that is the
|
|
215
|
-
* only deterministic reading of "which one did the author mean"
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
259
|
+
* only deterministic reading of "which one did the author mean"; an option
|
|
260
|
+
* the page inserts ahead of the selection, already selected, takes it over.
|
|
261
|
+
* 3. The trigger label and the hidden field are derived from that selection,
|
|
262
|
+
* an empty one included. Without this the widget announces a choice it does
|
|
263
|
+
* not submit: the popup says "Banana", the trigger still says "Choose…", and
|
|
264
|
+
* the form posts "".
|
|
219
265
|
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* target
|
|
266
|
+
* Nothing is reported here: `connect()` describes the initial state, and a
|
|
267
|
+
* page-driven pass reports a moved selection itself. The scan is the `option`
|
|
268
|
+
* target set: a `role="option"` without the target is outside the contract and
|
|
269
|
+
* is neither counted nor written.
|
|
223
270
|
*/
|
|
224
271
|
#normalizeSelection() {
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
272
|
+
const selected = this.optionTargets.find(
|
|
273
|
+
(option) => option.getAttribute("aria-selected") === "true"
|
|
274
|
+
);
|
|
275
|
+
this.#applySelection(selected);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The pass the page's changes run: the selection is normalized and mirrored,
|
|
279
|
+
* the active option re-resolved, then a selection that moved is reported. The
|
|
280
|
+
* report comes last, so options a subscriber rewrites are the next pass's to
|
|
281
|
+
* settle.
|
|
282
|
+
*/
|
|
283
|
+
#reconcileOptions() {
|
|
284
|
+
this.#normalizeSelection();
|
|
285
|
+
this.#reconcileActive();
|
|
286
|
+
this.#reportMove();
|
|
231
287
|
}
|
|
232
|
-
/**
|
|
288
|
+
/**
|
|
289
|
+
* Reports the selection as `reconcile` when its option or value differs from the
|
|
290
|
+
* selection last settled. The settled selection is replaced before dispatching,
|
|
291
|
+
* so a move a subscriber makes is measured against what it was told.
|
|
292
|
+
*/
|
|
293
|
+
#reportMove() {
|
|
294
|
+
const { option, value } = this.#selection();
|
|
295
|
+
if (option === this.#settled.option && value === this.#settled.value) return;
|
|
296
|
+
this.#settled = { option, value };
|
|
297
|
+
this.dispatch("reconcile", { detail: { value, option } });
|
|
298
|
+
}
|
|
299
|
+
/** The selected option target, if any, and the value its field submits. */
|
|
300
|
+
#selection() {
|
|
301
|
+
const option = this.optionTargets.find((candidate) => candidate.getAttribute("aria-selected") === "true") ?? null;
|
|
302
|
+
return { option, value: option ? this.#optionValue(option) : "" };
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Releases the outside-click listener and the option observer, drops a queued
|
|
306
|
+
* pass, and clears the typeahead timer.
|
|
307
|
+
*/
|
|
233
308
|
disconnect() {
|
|
234
|
-
this.#connected = false;
|
|
235
309
|
this.#reconcile.cancel();
|
|
310
|
+
this.#observer?.disconnect();
|
|
311
|
+
this.#observer = null;
|
|
236
312
|
document.removeEventListener("click", this.#onOutsideClick, true);
|
|
237
313
|
this.#typeahead.reset();
|
|
238
314
|
}
|
|
@@ -354,27 +430,113 @@ var ListboxController = class extends Controller {
|
|
|
354
430
|
/** Applies selection: `aria-selected`, trigger label, hidden field, `change`. */
|
|
355
431
|
#selectOption(option) {
|
|
356
432
|
const { value, fieldChanged } = this.#applySelection(option);
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
433
|
+
this.#settled = { option, value };
|
|
434
|
+
if (fieldChanged) commitField(this.fieldTarget);
|
|
360
435
|
this.dispatch("change", { detail: { value, option } });
|
|
361
436
|
}
|
|
362
437
|
/**
|
|
363
|
-
* Writes `option`
|
|
364
|
-
*
|
|
365
|
-
*
|
|
438
|
+
* Writes `option` — or, when it is `undefined`, no selection — into
|
|
439
|
+
* `aria-selected`, the trigger label and the hidden field. Emits nothing: the
|
|
440
|
+
* user's commit and a page-driven pass each report it their own way, and
|
|
441
|
+
* `connect()` only describes the state it finds.
|
|
366
442
|
*/
|
|
367
443
|
#applySelection(option) {
|
|
368
444
|
for (const candidate of this.optionTargets) {
|
|
369
|
-
candidate
|
|
445
|
+
setAttributeIfChanged(candidate, "aria-selected", candidate === option ? "true" : "false");
|
|
370
446
|
}
|
|
371
|
-
|
|
372
|
-
const value = option
|
|
373
|
-
|
|
374
|
-
const fieldChanged = this.hasFieldTarget && this.fieldTarget
|
|
375
|
-
if (fieldChanged) this.fieldTarget.value = value;
|
|
447
|
+
this.#dropOwnRecords();
|
|
448
|
+
const value = option ? this.#optionValue(option) : "";
|
|
449
|
+
this.#renderLabel(option);
|
|
450
|
+
const fieldChanged = this.hasFieldTarget && writeField(this.fieldTarget, value);
|
|
376
451
|
return { value, fieldChanged };
|
|
377
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Keeps the trigger value on the selection: the selected option's text, or, with
|
|
455
|
+
* nothing selected, `placeholder` when it is set, else the text the value held
|
|
456
|
+
* before this controller first wrote into it.
|
|
457
|
+
*
|
|
458
|
+
* The value is this controller's only while it holds the text last written into
|
|
459
|
+
* it and nothing else — the `owns-label` marker carries that text — and the text
|
|
460
|
+
* it held before is kept in the `original-label` marker. A value the page
|
|
461
|
+
* rewrote or added an element to after that — the marker is still there but no
|
|
462
|
+
* longer matches — is the page's: with nothing selected it is left exactly as it
|
|
463
|
+
* is, `placeholder` or not, and the next selection remembers its text before
|
|
464
|
+
* replacing it. A value with no marker at all — the author's, or one a morph put
|
|
465
|
+
* back without the markers — is taken over the same way by `placeholder` or by a
|
|
466
|
+
* selection. A value that only ever showed a selection has no text of its own to
|
|
467
|
+
* return to, so it is emptied rather than left naming an option that is not
|
|
468
|
+
* selected.
|
|
469
|
+
*
|
|
470
|
+
* @stimeoRenderRoot
|
|
471
|
+
*/
|
|
472
|
+
#renderLabel(option) {
|
|
473
|
+
if (!this.hasValueTarget) return;
|
|
474
|
+
const label = this.valueTarget;
|
|
475
|
+
const ownsLabel = `data-${this.identifier}-owns-label`;
|
|
476
|
+
const originalLabel = `data-${this.identifier}-original-label`;
|
|
477
|
+
const owned = label.getAttribute(ownsLabel) === label.textContent && label.childElementCount === 0;
|
|
478
|
+
if (option === void 0 && !owned && label.hasAttribute(ownsLabel)) return;
|
|
479
|
+
const text = option ? this.#optionLabel(option) : this.placeholderValue || null;
|
|
480
|
+
if (text === null) {
|
|
481
|
+
if (!owned) return;
|
|
482
|
+
label.textContent = label.getAttribute(originalLabel) ?? "";
|
|
483
|
+
label.removeAttribute(ownsLabel);
|
|
484
|
+
label.removeAttribute(originalLabel);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (!owned) {
|
|
488
|
+
const current = (label.textContent ?? "").trim();
|
|
489
|
+
if (option === void 0 || current !== text) {
|
|
490
|
+
setAttributeIfChanged(label, originalLabel, current);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (label.textContent !== text) label.textContent = text;
|
|
494
|
+
setAttributeIfChanged(label, ownsLabel, text);
|
|
495
|
+
}
|
|
496
|
+
/** An option's visible label: its own text, trimmed. */
|
|
497
|
+
#optionLabel(option) {
|
|
498
|
+
return (option.textContent ?? "").trim();
|
|
499
|
+
}
|
|
500
|
+
/** The value an option submits: its `data-value`, else its label. */
|
|
501
|
+
#optionValue(option) {
|
|
502
|
+
return option.dataset.value ?? this.#optionLabel(option);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Watches the options' `aria-selected` and `data-value`. The listbox's own writes
|
|
506
|
+
* to them are dropped from the queue as they happen, so each record the callback
|
|
507
|
+
* receives is the page's, and only a record on one of the listbox's own options
|
|
508
|
+
* schedules a pass.
|
|
509
|
+
*/
|
|
510
|
+
#observeOptions() {
|
|
511
|
+
const observer = new MutationObserver((records) => {
|
|
512
|
+
if (this.#concernsOptions(records)) this.#reconcile.schedule();
|
|
513
|
+
});
|
|
514
|
+
observer.observe(this.element, {
|
|
515
|
+
subtree: true,
|
|
516
|
+
attributes: true,
|
|
517
|
+
attributeFilter: OBSERVED_ATTRIBUTES
|
|
518
|
+
});
|
|
519
|
+
this.#observer = observer;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Whether any record is about one of this listbox's own options. A widget nested
|
|
523
|
+
* inside it writes the same attributes on its own elements, and those are no
|
|
524
|
+
* reason to reconcile the listbox.
|
|
525
|
+
*/
|
|
526
|
+
#concernsOptions(records) {
|
|
527
|
+
const options = new Set(this.optionTargets);
|
|
528
|
+
return records.some((record) => options.has(record.target));
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Drops the records the listbox's own writes just queued, so the observer never
|
|
532
|
+
* takes them for the page's. A page write queued just before goes with them and
|
|
533
|
+
* owes nothing: every such write leaves every option's `aria-selected` at the
|
|
534
|
+
* value the selection gives it, and the selection is then read back from the DOM,
|
|
535
|
+
* so it already holds what the page wrote.
|
|
536
|
+
*/
|
|
537
|
+
#dropOwnRecords() {
|
|
538
|
+
this.#observer?.takeRecords();
|
|
539
|
+
}
|
|
378
540
|
/**
|
|
379
541
|
* Marks the option at `index` active via `data-active` and the trigger's
|
|
380
542
|
* `aria-activedescendant`. Pass `-1` to clear it (the attribute is removed, not
|
|
@@ -444,10 +606,6 @@ var ListboxController = class extends Controller {
|
|
|
444
606
|
}
|
|
445
607
|
return -1;
|
|
446
608
|
}
|
|
447
|
-
/** Coalesces all target callbacks from one MutationObserver batch. */
|
|
448
|
-
#queueOptionReconciliation() {
|
|
449
|
-
this.#reconcile.schedule();
|
|
450
|
-
}
|
|
451
609
|
/**
|
|
452
610
|
* Advances the typeahead query and activates the next matching option.
|
|
453
611
|
*
|