stimeo-ui 0.16.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.
- package/CHANGELOG.md +136 -0
- package/README.md +27 -11
- package/dist/cable/index.d.ts +88 -31
- package/dist/cable/index.js +384 -449
- package/dist/cable/index.js.map +1 -1
- package/dist/coerce-JtZVNG5h.d.ts +20 -0
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +174 -55
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +12 -2
- package/dist/controllers/alert_dialog_controller.js +903 -235
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +37 -5
- package/dist/controllers/announcer_controller.js +238 -205
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.js +0 -10
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +30 -6
- package/dist/controllers/auto_submit_controller.js +414 -145
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +185 -107
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +0 -130
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +21 -3
- package/dist/controllers/bulk_select_controller.js +282 -58
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +38 -16
- package/dist/controllers/calendar_controller.js +228 -260
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +43 -12
- package/dist/controllers/carousel_controller.js +536 -436
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +44 -7
- package/dist/controllers/character_counter_controller.js +493 -207
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +7 -2
- package/dist/controllers/checkbox_controller.js +38 -74
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +16 -5
- package/dist/controllers/clipboard_controller.js +261 -149
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +17 -5
- package/dist/controllers/collapsible_controller.js +304 -120
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +18 -6
- package/dist/controllers/color_picker_controller.js +178 -146
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +24 -6
- package/dist/controllers/combobox_controller.js +522 -156
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +51 -20
- package/dist/controllers/command_palette_controller.js +1156 -395
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +15 -2
- package/dist/controllers/conditional_fields_controller.js +300 -171
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +49 -12
- package/dist/controllers/confirm_controller.js +980 -262
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +80 -18
- package/dist/controllers/context_menu_controller.js +346 -153
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +13 -1
- package/dist/controllers/count_up_controller.js +73 -21
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +47 -3
- package/dist/controllers/countdown_controller.js +281 -158
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +93 -10
- package/dist/controllers/currency_input_controller.js +575 -225
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +25 -12
- package/dist/controllers/data_grid_controller.js +248 -214
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +42 -14
- package/dist/controllers/date_range_picker_controller.js +338 -235
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +17 -6
- package/dist/controllers/dialog_controller.js +899 -237
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +25 -8
- package/dist/controllers/direct_upload_controller.js +97 -167
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.d.ts +6 -1
- package/dist/controllers/dirty_form_controller.js +72 -69
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +19 -0
- package/dist/controllers/dismissible_controller.js +196 -24
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +974 -355
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +339 -96
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +315 -88
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +79 -58
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +63 -18
- package/dist/controllers/file_dropzone_controller.js +322 -211
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +77 -42
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +35 -12
- package/dist/controllers/flash_controller.js +238 -347
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +15 -6
- package/dist/controllers/focus_controller.js +679 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +156 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.d.ts +3 -3
- package/dist/controllers/form_validation_controller.js +31 -97
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +47 -14
- package/dist/controllers/frame_loading_controller.js +275 -217
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +27 -6
- package/dist/controllers/highlight_controller.js +107 -75
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +60 -4
- package/dist/controllers/hover_card_controller.js +327 -130
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +48 -6
- package/dist/controllers/idle_controller.js +271 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.d.ts +30 -13
- package/dist/controllers/input_mask_controller.js +483 -97
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.d.ts +32 -13
- package/dist/controllers/intersection_controller.js +150 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +19 -2
- package/dist/controllers/lazy_frame_controller.js +24 -68
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +44 -9
- package/dist/controllers/listbox_controller.js +523 -240
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +9 -7
- package/dist/controllers/local_time_controller.js +102 -77
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +11 -3
- package/dist/controllers/masonry_controller.js +80 -96
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +55 -11
- package/dist/controllers/menu_controller.js +302 -188
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +51 -9
- package/dist/controllers/menubar_controller.js +232 -345
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +33 -20
- package/dist/controllers/meter_controller.js +417 -87
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +48 -18
- package/dist/controllers/multi_select_controller.js +785 -393
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +27 -6
- package/dist/controllers/navigation_menu_controller.js +190 -250
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +10 -0
- package/dist/controllers/nested_form_controller.js +396 -192
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +23 -0
- package/dist/controllers/network_status_controller.js +268 -62
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +50 -8
- package/dist/controllers/number_input_controller.js +610 -303
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.d.ts +8 -5
- package/dist/controllers/optimistic_controller.js +45 -87
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +55 -8
- package/dist/controllers/otp_controller.js +658 -255
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
- package/dist/controllers/overflow_indicator_controller.js +122 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +58 -20
- package/dist/controllers/overflow_menu_controller.js +470 -295
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +37 -7
- package/dist/controllers/pagination_controller.js +332 -123
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +46 -8
- package/dist/controllers/password_reveal_controller.js +392 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +15 -6
- package/dist/controllers/password_strength_controller.js +111 -158
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +11 -4
- package/dist/controllers/persist_controller.js +165 -111
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +35 -12
- package/dist/controllers/pointer_drag_controller.js +434 -188
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +41 -5
- package/dist/controllers/popover_controller.js +269 -113
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +215 -76
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +243 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +20 -19
- package/dist/controllers/progress_controller.js +391 -78
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +21 -9
- package/dist/controllers/radio_group_controller.js +278 -211
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +47 -6
- package/dist/controllers/range_slider_controller.js +220 -113
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +20 -4
- package/dist/controllers/rating_controller.js +458 -211
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +2 -0
- package/dist/controllers/read_more_controller.js +138 -63
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +1 -1
- package/dist/controllers/reading_progress_controller.js +166 -137
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +20 -6
- package/dist/controllers/relative_time_controller.js +211 -112
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
- package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +31 -2
- package/dist/controllers/resizable_controller.js +299 -150
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.d.ts +3 -3
- package/dist/controllers/roving_controller.js +49 -88
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -3
- package/dist/controllers/scroll_area_controller.js +490 -246
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.d.ts +3 -3
- package/dist/controllers/scroll_restore_controller.js +14 -55
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
- package/dist/controllers/scroll_visibility_controller.js +432 -149
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +6 -1
- package/dist/controllers/scrollspy_controller.js +193 -248
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +20 -5
- package/dist/controllers/separator_controller.js +285 -142
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +65 -8
- package/dist/controllers/sidebar_controller.js +1069 -404
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +259 -101
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +29 -2
- package/dist/controllers/slider_controller.js +187 -86
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
- package/dist/controllers/smart_sticky_header_controller.js +173 -66
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.d.ts +4 -4
- package/dist/controllers/sortable_controller.js +26 -125
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +288 -173
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +9 -4
- package/dist/controllers/step_indicator_controller.js +105 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +14 -6
- package/dist/controllers/stepper_controller.js +120 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
- package/dist/controllers/stick_to_bottom_controller.js +275 -140
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +11 -0
- package/dist/controllers/sticky_observer_controller.js +220 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +24 -8
- package/dist/controllers/submit_once_controller.js +376 -227
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +18 -4
- package/dist/controllers/switch_controller.js +201 -84
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +9 -6
- package/dist/controllers/tabs_controller.js +41 -30
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +16 -3
- package/dist/controllers/tags_input_controller.js +293 -173
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
- package/dist/controllers/textarea_autosize_controller.js +108 -74
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +24 -11
- package/dist/controllers/theme_controller.js +140 -116
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +55 -5
- package/dist/controllers/time_picker_controller.js +331 -127
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +40 -16
- package/dist/controllers/toast_controller.js +244 -324
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +14 -6
- package/dist/controllers/toggle_group_controller.js +252 -191
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.d.ts +8 -6
- package/dist/controllers/toolbar_controller.js +219 -120
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +49 -4
- package/dist/controllers/tooltip_controller.js +158 -124
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +22 -4
- package/dist/controllers/transition_controller.js +123 -119
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +22 -12
- package/dist/controllers/tree_view_controller.js +283 -369
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +8573 -10410
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +38 -14
- package/dist/inspector/cli.js +108 -40
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +145 -64
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +20 -20
- package/dist/inspector/manifest.json +802 -101
- package/dist/positioning/index.d.ts +15 -7
- package/dist/positioning/index.js +154 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +11 -10
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1,14 +1,279 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
4
|
+
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
5
|
+
function stateReasonFor(event) {
|
|
6
|
+
if (!event) return "api";
|
|
7
|
+
if (FOCUS_EVENTS.has(event.type)) return "focus";
|
|
8
|
+
if (POINTER_EVENTS.has(event.type)) return "pointer";
|
|
9
|
+
return "user";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function actionSource(source) {
|
|
13
|
+
if (source instanceof HTMLElement) {
|
|
14
|
+
return { event: null, host: source, origin: source, reason: "api" };
|
|
15
|
+
}
|
|
16
|
+
const event = source ?? null;
|
|
17
|
+
return {
|
|
18
|
+
event,
|
|
19
|
+
host: event?.currentTarget instanceof Element ? event.currentTarget : null,
|
|
20
|
+
origin: event?.target instanceof Element ? event.target : null,
|
|
21
|
+
reason: stateReasonFor(event)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
4
24
|
|
|
5
|
-
// src/utils/arrow_step.ts
|
|
6
25
|
function isReservedArrowChord(event, allow = []) {
|
|
7
26
|
if (!event.key.startsWith("Arrow")) return false;
|
|
8
27
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
9
28
|
}
|
|
29
|
+
function hasModifierChord(event) {
|
|
30
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
34
|
+
if (element.getAttribute(name) === value) return false;
|
|
35
|
+
beforeWrite?.();
|
|
36
|
+
if (value === null) element.removeAttribute(name);
|
|
37
|
+
else element.setAttribute(name, value);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sharedRegistry(key, create) {
|
|
42
|
+
const symbol = Symbol.for(key);
|
|
43
|
+
const scope = globalThis;
|
|
44
|
+
const existing = scope[symbol];
|
|
45
|
+
if (existing !== void 0) return existing;
|
|
46
|
+
const registry2 = create();
|
|
47
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
48
|
+
return registry2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var registry = sharedRegistry(
|
|
52
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
53
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
54
|
+
);
|
|
55
|
+
function isAttributeValue(value) {
|
|
56
|
+
return value === null || typeof value === "string";
|
|
57
|
+
}
|
|
58
|
+
function readLeaseRecord(element, record) {
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
61
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
62
|
+
const [original, written, ...rest] = parsed;
|
|
63
|
+
const linked = rest[0] !== false;
|
|
64
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
65
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
66
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
67
|
+
} catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function attributeStack(element, attribute) {
|
|
72
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
73
|
+
registry.stacks.set(element, byAttribute);
|
|
74
|
+
const known = byAttribute.get(attribute);
|
|
75
|
+
if (known) return known;
|
|
76
|
+
const found = [];
|
|
77
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
78
|
+
const read = readLeaseRecord(element, record);
|
|
79
|
+
if (read) found.push({ ...read, record });
|
|
80
|
+
}
|
|
81
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
82
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
83
|
+
lease: null,
|
|
84
|
+
record,
|
|
85
|
+
written,
|
|
86
|
+
original,
|
|
87
|
+
linked: linked && index > 0
|
|
88
|
+
}));
|
|
89
|
+
byAttribute.set(attribute, stack);
|
|
90
|
+
return stack;
|
|
91
|
+
}
|
|
92
|
+
function findHold(stack, lease, record) {
|
|
93
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
94
|
+
}
|
|
95
|
+
var AttributeLease = class {
|
|
96
|
+
#attribute;
|
|
97
|
+
#record;
|
|
98
|
+
#base;
|
|
99
|
+
#preserveAuthor;
|
|
100
|
+
#held = /* @__PURE__ */ new Set();
|
|
101
|
+
constructor(attribute, owner, options = {}) {
|
|
102
|
+
this.#attribute = attribute;
|
|
103
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
104
|
+
this.#base = options.base === true;
|
|
105
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
106
|
+
const names = registry.names;
|
|
107
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
108
|
+
}
|
|
109
|
+
write(element, value) {
|
|
110
|
+
const stack = attributeStack(element, this.#attribute);
|
|
111
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
112
|
+
this.#held.add(element);
|
|
113
|
+
const shows = !this.#covered(element, stack, hold);
|
|
114
|
+
hold.written = value;
|
|
115
|
+
this.#mark(element, stack);
|
|
116
|
+
if (shows) this.#reflect(element, value);
|
|
117
|
+
}
|
|
118
|
+
return(element) {
|
|
119
|
+
const held = this.#held.delete(element);
|
|
120
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
121
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
122
|
+
const hold = this.#take(stack);
|
|
123
|
+
if (!hold) return false;
|
|
124
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
125
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
126
|
+
const index = stack.indexOf(hold);
|
|
127
|
+
stack.splice(index, 1);
|
|
128
|
+
const above = stack[index];
|
|
129
|
+
if (above) above.linked &&= hold.linked;
|
|
130
|
+
this.#mark(element, stack);
|
|
131
|
+
if (!owns) return true;
|
|
132
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
133
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
returnAll() {
|
|
137
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
138
|
+
}
|
|
139
|
+
#take(stack) {
|
|
140
|
+
const hold = findHold(stack, this, this.#record);
|
|
141
|
+
if (hold) hold.lease = this;
|
|
142
|
+
return hold;
|
|
143
|
+
}
|
|
144
|
+
#acquire(element, stack) {
|
|
145
|
+
const current = element.getAttribute(this.#attribute);
|
|
146
|
+
const top = stack.at(-1);
|
|
147
|
+
const bottom = stack[0];
|
|
148
|
+
const over = top?.written === current ? top : void 0;
|
|
149
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
150
|
+
const hold = {
|
|
151
|
+
lease: this,
|
|
152
|
+
record: this.#record,
|
|
153
|
+
written: current,
|
|
154
|
+
original: from ? from.original : current,
|
|
155
|
+
linked: !this.#base && over !== void 0
|
|
156
|
+
};
|
|
157
|
+
if (this.#base && bottom) {
|
|
158
|
+
bottom.linked = true;
|
|
159
|
+
stack.unshift(hold);
|
|
160
|
+
} else stack.push(hold);
|
|
161
|
+
return hold;
|
|
162
|
+
}
|
|
163
|
+
#covered(element, stack, hold) {
|
|
164
|
+
const current = element.getAttribute(this.#attribute);
|
|
165
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
166
|
+
}
|
|
167
|
+
#mark(element, stack) {
|
|
168
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
169
|
+
stack.forEach((hold, index) => {
|
|
170
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
171
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
172
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
173
|
+
records.set(
|
|
174
|
+
hold.record,
|
|
175
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
for (const [name, recorded] of records) {
|
|
179
|
+
setAttributeIfChanged(element, name, recorded);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
#reflect(element, value) {
|
|
183
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
var AuthoredMutationWatcher = class {
|
|
188
|
+
#onAuthored;
|
|
189
|
+
#claims = null;
|
|
190
|
+
#observer = null;
|
|
191
|
+
constructor(onAuthored) {
|
|
192
|
+
this.#onAuthored = onAuthored;
|
|
193
|
+
}
|
|
194
|
+
observe(root, options) {
|
|
195
|
+
this.disconnect();
|
|
196
|
+
const observer = new MutationObserver((records) => {
|
|
197
|
+
if (this.#observer !== observer) return;
|
|
198
|
+
const authored = this.#filter(records);
|
|
199
|
+
if (authored.length > 0) this.#onAuthored(authored);
|
|
200
|
+
});
|
|
201
|
+
this.#observer = observer;
|
|
202
|
+
this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
|
|
203
|
+
observer.observe(root, {
|
|
204
|
+
attributes: true,
|
|
205
|
+
attributeFilter: [...options.attributes],
|
|
206
|
+
subtree: options.subtree ?? false,
|
|
207
|
+
childList: options.childList ?? false
|
|
208
|
+
});
|
|
209
|
+
if (options.ancestorAttributes) {
|
|
210
|
+
let ancestor = root.parentElement;
|
|
211
|
+
while (ancestor) {
|
|
212
|
+
observer.observe(ancestor, {
|
|
213
|
+
attributes: true,
|
|
214
|
+
attributeFilter: [...options.ancestorAttributes]
|
|
215
|
+
});
|
|
216
|
+
ancestor = ancestor.parentElement;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
claim(target, name, value) {
|
|
221
|
+
const claims = this.#claims?.attributes;
|
|
222
|
+
let attributes = claims?.get(target);
|
|
223
|
+
if (!attributes) {
|
|
224
|
+
attributes = /* @__PURE__ */ new Map();
|
|
225
|
+
claims?.set(target, attributes);
|
|
226
|
+
}
|
|
227
|
+
const pending = attributes.get(name);
|
|
228
|
+
attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
|
|
229
|
+
}
|
|
230
|
+
claimCurrent(target, name) {
|
|
231
|
+
this.claim(target, name, target.getAttribute(name));
|
|
232
|
+
}
|
|
233
|
+
claimChildList(target, added, removed) {
|
|
234
|
+
const claims = this.#claims?.children;
|
|
235
|
+
const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
|
|
236
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
|
|
237
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
|
|
238
|
+
claims?.set(target, pending);
|
|
239
|
+
}
|
|
240
|
+
take() {
|
|
241
|
+
return this.#filter(this.#observer?.takeRecords() ?? []);
|
|
242
|
+
}
|
|
243
|
+
disconnect() {
|
|
244
|
+
this.#observer?.disconnect();
|
|
245
|
+
this.#observer = null;
|
|
246
|
+
this.#claims = null;
|
|
247
|
+
}
|
|
248
|
+
#filter(records) {
|
|
249
|
+
const authored = records.filter((record) => !this.#consume(record));
|
|
250
|
+
this.#claims?.attributes.clear();
|
|
251
|
+
this.#claims?.children.clear();
|
|
252
|
+
return authored;
|
|
253
|
+
}
|
|
254
|
+
#consume(record) {
|
|
255
|
+
if (record.type === "attributes") {
|
|
256
|
+
const target = record.target;
|
|
257
|
+
const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
|
|
258
|
+
if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
pending2.count -= 1;
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
const pending = this.#claims?.children.get(record.target);
|
|
265
|
+
if (!pending) return false;
|
|
266
|
+
const added = [...record.addedNodes];
|
|
267
|
+
const removed = [...record.removedNodes];
|
|
268
|
+
if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
|
|
272
|
+
for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
10
276
|
|
|
11
|
-
// src/utils/field_mirror.ts
|
|
12
277
|
function writeField(field, value) {
|
|
13
278
|
if (field.value === value) return false;
|
|
14
279
|
field.value = value;
|
|
@@ -18,27 +283,36 @@ function commitField(target) {
|
|
|
18
283
|
target.dispatchEvent(new Event("change", { bubbles: true }));
|
|
19
284
|
}
|
|
20
285
|
|
|
21
|
-
|
|
286
|
+
var ListenerSet = class {
|
|
287
|
+
#abort = new AbortController();
|
|
288
|
+
add(target, type, handler, options) {
|
|
289
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
290
|
+
}
|
|
291
|
+
dispose() {
|
|
292
|
+
this.#abort.abort();
|
|
293
|
+
this.#abort = new AbortController();
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
22
297
|
var MicrotaskCoalescer = class {
|
|
23
298
|
#run;
|
|
24
299
|
#queued = false;
|
|
25
300
|
#active = false;
|
|
26
301
|
#generation = 0;
|
|
27
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
28
302
|
constructor(run) {
|
|
29
303
|
this.#run = run;
|
|
30
304
|
}
|
|
31
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
32
305
|
activate() {
|
|
33
306
|
this.#active = true;
|
|
34
307
|
}
|
|
35
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
36
308
|
cancel() {
|
|
37
309
|
this.#active = false;
|
|
38
310
|
this.#queued = false;
|
|
39
311
|
this.#generation += 1;
|
|
40
312
|
}
|
|
41
|
-
|
|
313
|
+
get pending() {
|
|
314
|
+
return this.#queued;
|
|
315
|
+
}
|
|
42
316
|
schedule() {
|
|
43
317
|
if (!this.#active || this.#queued) return;
|
|
44
318
|
this.#queued = true;
|
|
@@ -51,7 +325,45 @@ var MicrotaskCoalescer = class {
|
|
|
51
325
|
}
|
|
52
326
|
};
|
|
53
327
|
|
|
54
|
-
|
|
328
|
+
var MorphRenderWatcher = class {
|
|
329
|
+
#listeners = new ListenerSet();
|
|
330
|
+
#pass;
|
|
331
|
+
#element = null;
|
|
332
|
+
#onMorph = () => this.#pass.schedule();
|
|
333
|
+
constructor(run) {
|
|
334
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
335
|
+
}
|
|
336
|
+
observe(element) {
|
|
337
|
+
if (this.#element === element) return;
|
|
338
|
+
this.disconnect();
|
|
339
|
+
this.#element = element;
|
|
340
|
+
this.#pass.activate();
|
|
341
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
342
|
+
}
|
|
343
|
+
schedule() {
|
|
344
|
+
this.#pass.schedule();
|
|
345
|
+
}
|
|
346
|
+
get pending() {
|
|
347
|
+
return this.#pass.pending;
|
|
348
|
+
}
|
|
349
|
+
disconnect() {
|
|
350
|
+
this.#listeners.dispose();
|
|
351
|
+
this.#pass.cancel();
|
|
352
|
+
this.#element = null;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
var MoveCounter = class {
|
|
357
|
+
#sequence = 0;
|
|
358
|
+
record() {
|
|
359
|
+
this.#sequence += 1;
|
|
360
|
+
return this.#sequence;
|
|
361
|
+
}
|
|
362
|
+
isLatest(token) {
|
|
363
|
+
return token === this.#sequence;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
55
367
|
function scrollOptionIntoView(list, option) {
|
|
56
368
|
if (list.scrollHeight <= list.clientHeight) return;
|
|
57
369
|
const listRect = list.getBoundingClientRect();
|
|
@@ -63,43 +375,109 @@ function scrollOptionIntoView(list, option) {
|
|
|
63
375
|
}
|
|
64
376
|
}
|
|
65
377
|
|
|
66
|
-
|
|
378
|
+
var OwnedText = class {
|
|
379
|
+
#attribute;
|
|
380
|
+
#channel;
|
|
381
|
+
#guard;
|
|
382
|
+
#writes = /* @__PURE__ */ new WeakMap();
|
|
383
|
+
constructor(attribute, channel = "text", guard = () => true) {
|
|
384
|
+
this.#attribute = attribute;
|
|
385
|
+
this.#channel = channel;
|
|
386
|
+
this.#guard = guard;
|
|
387
|
+
}
|
|
388
|
+
hasRecord(element) {
|
|
389
|
+
return element.hasAttribute(this.#attribute);
|
|
390
|
+
}
|
|
391
|
+
read(element, evidence = {}) {
|
|
392
|
+
if (evidence.source === "live") return this.#writes.get(element) ?? null;
|
|
393
|
+
const raw = element.getAttribute(this.#attribute);
|
|
394
|
+
if (raw === null) return null;
|
|
395
|
+
try {
|
|
396
|
+
const record = JSON.parse(raw);
|
|
397
|
+
if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
|
|
398
|
+
return null;
|
|
399
|
+
return { original: record[1], written: record[2] };
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
owns(element, evidence = {}) {
|
|
405
|
+
return this.#ownedRecord(element, evidence) !== null;
|
|
406
|
+
}
|
|
407
|
+
#ownedRecord(element, evidence) {
|
|
408
|
+
const record = this.read(element, evidence);
|
|
409
|
+
if (record === null || !this.#guard(element)) return null;
|
|
410
|
+
if (this.#channel === "text" && element.firstElementChild !== null) return null;
|
|
411
|
+
const current = this.#current(element);
|
|
412
|
+
return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
|
|
413
|
+
}
|
|
414
|
+
write(element, written, original, force = false) {
|
|
415
|
+
const current = this.#current(element);
|
|
416
|
+
if (current === null) return;
|
|
417
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
418
|
+
if (force || current !== written) this.#put(element, written);
|
|
419
|
+
this.remember(element, written, before);
|
|
420
|
+
}
|
|
421
|
+
remember(element, written, original) {
|
|
422
|
+
const current = this.#current(element);
|
|
423
|
+
if (current === null) return;
|
|
424
|
+
const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
|
|
425
|
+
this.#writes.set(element, { original: before, written });
|
|
426
|
+
setAttributeIfChanged(
|
|
427
|
+
element,
|
|
428
|
+
this.#attribute,
|
|
429
|
+
JSON.stringify([this.#channel, before, written])
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
clear(element, evidence = {}) {
|
|
433
|
+
if (!this.owns(element, evidence)) return false;
|
|
434
|
+
if (this.#current(element) !== "") this.#put(element, "");
|
|
435
|
+
this.forget(element);
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
restore(element, evidence = {}) {
|
|
439
|
+
const record = this.#ownedRecord(element, evidence);
|
|
440
|
+
if (record === null) return false;
|
|
441
|
+
if (this.#current(element) !== record.original) this.#put(element, record.original);
|
|
442
|
+
this.forget(element);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
forget(element) {
|
|
446
|
+
setAttributeIfChanged(element, this.#attribute, null);
|
|
447
|
+
this.#writes.delete(element);
|
|
448
|
+
}
|
|
449
|
+
#current(element) {
|
|
450
|
+
if (this.#channel === "text") return element.textContent ?? "";
|
|
451
|
+
return element instanceof HTMLInputElement ? element.value : null;
|
|
452
|
+
}
|
|
453
|
+
#put(element, text) {
|
|
454
|
+
if (this.#channel === "text") element.textContent = text;
|
|
455
|
+
else if (element instanceof HTMLInputElement) element.value = text;
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
function targetSelector(identifier, name) {
|
|
460
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
461
|
+
}
|
|
462
|
+
|
|
67
463
|
var TimerRegistry = class {
|
|
68
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
69
464
|
ids = /* @__PURE__ */ new Set();
|
|
70
|
-
/**
|
|
71
|
-
* Cancels a single tracked timer.
|
|
72
|
-
*
|
|
73
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
74
|
-
* registry), so callers can clear defensively without guarding.
|
|
75
|
-
*/
|
|
76
465
|
clear(id) {
|
|
77
466
|
if (this.ids.delete(id)) {
|
|
78
467
|
this.cancel(id);
|
|
79
468
|
}
|
|
80
469
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
83
|
-
* to guarantee no timer outlives the element.
|
|
84
|
-
*/
|
|
85
470
|
clearAll() {
|
|
86
471
|
for (const id of this.ids) {
|
|
87
472
|
this.cancel(id);
|
|
88
473
|
}
|
|
89
474
|
this.ids.clear();
|
|
90
475
|
}
|
|
91
|
-
/** Number of timers currently tracked (pending). */
|
|
92
476
|
get size() {
|
|
93
477
|
return this.ids.size;
|
|
94
478
|
}
|
|
95
479
|
};
|
|
96
480
|
var SafeTimeout = class extends TimerRegistry {
|
|
97
|
-
/**
|
|
98
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
99
|
-
*
|
|
100
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
101
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
102
|
-
*/
|
|
103
481
|
set(callback, delay) {
|
|
104
482
|
const id = this.schedule(() => {
|
|
105
483
|
this.ids.delete(id);
|
|
@@ -116,29 +494,18 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
116
494
|
}
|
|
117
495
|
};
|
|
118
496
|
|
|
119
|
-
// src/utils/typeahead.ts
|
|
120
497
|
var TYPEAHEAD_RESET_MS = 500;
|
|
121
498
|
var Typeahead = class {
|
|
122
|
-
/** Timer registry for the pending idle reset; private so `reset()` is the only exit. */
|
|
123
499
|
#timers = new SafeTimeout();
|
|
124
|
-
/** Idle window before the query resets, in milliseconds. */
|
|
125
500
|
#resetMs;
|
|
126
|
-
/** The accumulated lowercase query, empty when idle. */
|
|
127
501
|
#query = "";
|
|
128
|
-
/** Id of the pending reset timer, `0` when none is scheduled. */
|
|
129
502
|
#timerId = 0;
|
|
130
|
-
/** @param options - Overrides for the idle window. */
|
|
131
503
|
constructor({ resetMs = TYPEAHEAD_RESET_MS } = {}) {
|
|
132
504
|
this.#resetMs = resetMs;
|
|
133
505
|
}
|
|
134
|
-
/** The query a search would currently run with; empty while idle. */
|
|
135
506
|
get query() {
|
|
136
507
|
return this.#query;
|
|
137
508
|
}
|
|
138
|
-
/**
|
|
139
|
-
* Folds `key` into the query, restarts the idle window, and returns the query to
|
|
140
|
-
* search with. A repeated character collapses the query to that one character.
|
|
141
|
-
*/
|
|
142
509
|
push(key) {
|
|
143
510
|
const char = key.toLowerCase();
|
|
144
511
|
const repeated = this.#query.length > 0 && [...this.#query].every((c) => c === char);
|
|
@@ -147,7 +514,6 @@ var Typeahead = class {
|
|
|
147
514
|
this.#timerId = this.#timers.set(() => this.reset(), this.#resetMs);
|
|
148
515
|
return this.#query;
|
|
149
516
|
}
|
|
150
|
-
/** Clears the query and cancels the pending idle reset. */
|
|
151
517
|
reset() {
|
|
152
518
|
this.#query = "";
|
|
153
519
|
this.#timers.clear(this.#timerId);
|
|
@@ -174,149 +540,131 @@ function findTypeaheadMatch(items, from, query, label = (item) => typeaheadLabel
|
|
|
174
540
|
return -1;
|
|
175
541
|
}
|
|
176
542
|
|
|
177
|
-
// src/controllers/listbox_controller.ts
|
|
178
543
|
var OBSERVED_ATTRIBUTES = ["aria-selected", "data-value"];
|
|
179
|
-
function setAttributeIfChanged(element, name, value) {
|
|
180
|
-
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
181
|
-
}
|
|
182
544
|
var ListboxController = class extends Controller {
|
|
545
|
+
#labelText = new OwnedText(`data-${this.identifier}-owns-label`);
|
|
546
|
+
#moves = new MoveCounter();
|
|
547
|
+
#connectionGeneration = 0;
|
|
183
548
|
static targets = ["trigger", "value", "list", "option", "field"];
|
|
184
549
|
static values = {
|
|
185
550
|
placeholder: { type: String, default: "" }
|
|
186
551
|
};
|
|
187
552
|
static actions = ["close", "onTriggerKeydown", "open", "select", "toggle"];
|
|
188
553
|
static events = ["change", "reconcile"];
|
|
189
|
-
/** Stable ID of the active option; DOM targets are resolved afresh before use. */
|
|
190
554
|
#activeId = null;
|
|
191
|
-
/** Target ID order captured while an option is active, used only for removal fallback. */
|
|
192
555
|
#activeOrder = [];
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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. */
|
|
556
|
+
#reconcile = new MorphRenderWatcher(() => this.#reconcileOptions());
|
|
557
|
+
#observer = new AuthoredMutationWatcher((records) => {
|
|
558
|
+
if (this.#concernsOptions(records)) this.#reconcile.schedule();
|
|
559
|
+
});
|
|
202
560
|
#settled = { option: null, value: "" };
|
|
203
|
-
|
|
561
|
+
#shownList = null;
|
|
562
|
+
#listHidden = new AttributeLease("hidden", this.identifier);
|
|
563
|
+
#expanded = new AttributeLease("aria-expanded", this.identifier);
|
|
564
|
+
#activeDescendant = new AttributeLease(
|
|
565
|
+
"aria-activedescendant",
|
|
566
|
+
this.identifier
|
|
567
|
+
);
|
|
204
568
|
#typeahead = new Typeahead();
|
|
205
|
-
|
|
206
|
-
* Establishes the ARIA baseline, settles the selection without reporting it,
|
|
207
|
-
* starts closed, and listens for outside clicks.
|
|
208
|
-
*/
|
|
569
|
+
#listeners = new ListenerSet();
|
|
209
570
|
connect() {
|
|
210
571
|
this.#normalizeSelection();
|
|
211
572
|
this.close();
|
|
212
|
-
|
|
573
|
+
this.#listeners.add(document, "click", this.#onOutsideClick, { capture: true });
|
|
213
574
|
this.#settled = this.#selection();
|
|
214
|
-
this.#reconcile.
|
|
575
|
+
this.#reconcile.observe(this.element);
|
|
215
576
|
this.#observeOptions();
|
|
216
577
|
}
|
|
217
|
-
/**
|
|
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.
|
|
220
|
-
*/
|
|
221
578
|
optionTargetConnected(option) {
|
|
222
579
|
option.removeAttribute("data-active");
|
|
223
580
|
this.#reconcile.schedule();
|
|
224
581
|
}
|
|
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
|
-
*/
|
|
229
582
|
optionTargetDisconnected(option) {
|
|
230
583
|
option.removeAttribute("data-active");
|
|
231
584
|
this.#reconcile.schedule();
|
|
232
585
|
}
|
|
233
|
-
|
|
586
|
+
triggerTargetConnected() {
|
|
587
|
+
this.#reconcile.schedule();
|
|
588
|
+
}
|
|
589
|
+
triggerTargetDisconnected(trigger) {
|
|
590
|
+
if (!this.triggerTargets.includes(trigger)) {
|
|
591
|
+
this.#expanded.return(trigger);
|
|
592
|
+
this.#activeDescendant.return(trigger);
|
|
593
|
+
}
|
|
594
|
+
this.#reconcile.schedule();
|
|
595
|
+
}
|
|
596
|
+
listTargetConnected() {
|
|
597
|
+
this.#reconcile.schedule();
|
|
598
|
+
}
|
|
599
|
+
listTargetDisconnected(list) {
|
|
600
|
+
if (!this.listTargets.includes(list)) {
|
|
601
|
+
if (list === this.#shownList) this.#adoptPageClose();
|
|
602
|
+
this.#listHidden.return(list);
|
|
603
|
+
}
|
|
604
|
+
this.#reconcile.schedule();
|
|
605
|
+
}
|
|
234
606
|
fieldTargetConnected() {
|
|
235
607
|
this.#reconcile.schedule();
|
|
236
608
|
}
|
|
237
|
-
|
|
609
|
+
fieldTargetDisconnected() {
|
|
610
|
+
this.#reconcile.schedule();
|
|
611
|
+
}
|
|
238
612
|
valueTargetConnected() {
|
|
239
613
|
this.#reconcile.schedule();
|
|
240
614
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
*/
|
|
615
|
+
valueTargetDisconnected() {
|
|
616
|
+
this.#reconcile.schedule();
|
|
617
|
+
}
|
|
245
618
|
placeholderValueChanged() {
|
|
246
619
|
this.#reconcile.schedule();
|
|
247
620
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Brings the authored DOM to the shape the APG requires, and derives the state
|
|
250
|
-
* that follows from the selection.
|
|
251
|
-
*
|
|
252
|
-
* Three things happen, and only these three — which option is chosen is the
|
|
253
|
-
* author's, and is never changed:
|
|
254
|
-
*
|
|
255
|
-
* 1. Every option gets an explicit value. An absent `aria-selected` means "not
|
|
256
|
-
* selectable" in ARIA, so a forgotten attribute hides a selectable option
|
|
257
|
-
* from assistive technology.
|
|
258
|
-
* 2. At most one stays `true`. The first in DOM order wins, since that is the
|
|
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 "".
|
|
265
|
-
*
|
|
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.
|
|
270
|
-
*/
|
|
271
621
|
#normalizeSelection() {
|
|
272
622
|
const selected = this.optionTargets.find(
|
|
273
623
|
(option) => option.getAttribute("aria-selected") === "true"
|
|
274
624
|
);
|
|
275
625
|
this.#applySelection(selected);
|
|
276
626
|
}
|
|
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
627
|
#reconcileOptions() {
|
|
284
628
|
this.#normalizeSelection();
|
|
629
|
+
this.#reconcileOpenState();
|
|
285
630
|
this.#reconcileActive();
|
|
286
631
|
this.#reportMove();
|
|
287
632
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
633
|
+
#reconcileOpenState() {
|
|
634
|
+
if (!this.hasListTarget) {
|
|
635
|
+
this.#shownList = null;
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (this.#shownList && this.listTargets.includes(this.#shownList)) this.#adoptPageClose();
|
|
639
|
+
const list = this.listTarget;
|
|
640
|
+
if (this.#shownList !== null && list !== this.#shownList) {
|
|
641
|
+
this.#listHidden.write(list, null);
|
|
642
|
+
this.#shownList = list;
|
|
643
|
+
}
|
|
644
|
+
if (this.hasTriggerTarget) {
|
|
645
|
+
this.#expanded.write(this.triggerTarget, list.hidden ? "false" : "true");
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
#adoptPageClose() {
|
|
649
|
+
if (this.#shownList?.hidden) this.#shownList = null;
|
|
650
|
+
}
|
|
293
651
|
#reportMove() {
|
|
294
652
|
const { option, value } = this.#selection();
|
|
295
653
|
if (option === this.#settled.option && value === this.#settled.value) return;
|
|
296
654
|
this.#settled = { option, value };
|
|
297
655
|
this.dispatch("reconcile", { detail: { value, option } });
|
|
298
656
|
}
|
|
299
|
-
/** The selected option target, if any, and the value its field submits. */
|
|
300
657
|
#selection() {
|
|
301
658
|
const option = this.optionTargets.find((candidate) => candidate.getAttribute("aria-selected") === "true") ?? null;
|
|
302
659
|
return { option, value: option ? this.#optionValue(option) : "" };
|
|
303
660
|
}
|
|
304
|
-
/**
|
|
305
|
-
* Releases the outside-click listener and the option observer, drops a queued
|
|
306
|
-
* pass, and clears the typeahead timer.
|
|
307
|
-
*/
|
|
308
661
|
disconnect() {
|
|
309
|
-
this.#
|
|
310
|
-
this.#
|
|
311
|
-
this.#observer
|
|
312
|
-
|
|
662
|
+
this.#connectionGeneration += 1;
|
|
663
|
+
this.#reconcile.disconnect();
|
|
664
|
+
this.#observer.disconnect();
|
|
665
|
+
this.#listeners.dispose();
|
|
313
666
|
this.#typeahead.reset();
|
|
314
667
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Toggles the list on a real mouse click. Keyboard activation of the
|
|
317
|
-
* `<button>` also fires a click (`detail === 0`); the keydown handler already
|
|
318
|
-
* drives that, so the synthetic click is ignored to avoid double-toggling.
|
|
319
|
-
*/
|
|
320
668
|
toggle(event) {
|
|
321
669
|
if (event.detail === 0) return;
|
|
322
670
|
if (this.#isClosed) {
|
|
@@ -325,10 +673,10 @@ var ListboxController = class extends Controller {
|
|
|
325
673
|
this.close();
|
|
326
674
|
}
|
|
327
675
|
}
|
|
328
|
-
/** Yields claimed keys; otherwise routes the APG select-only keyboard model. */
|
|
329
676
|
onTriggerKeydown(event) {
|
|
330
677
|
if (event.defaultPrevented) return;
|
|
331
678
|
if (isReservedArrowChord(event)) return;
|
|
679
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
332
680
|
if (!this.#isClosed) this.#reconcileActive();
|
|
333
681
|
const options = this.optionTargets;
|
|
334
682
|
const length = options.length;
|
|
@@ -351,7 +699,7 @@ var ListboxController = class extends Controller {
|
|
|
351
699
|
switch (event.key) {
|
|
352
700
|
case "ArrowDown":
|
|
353
701
|
event.preventDefault();
|
|
354
|
-
this.#setActive(
|
|
702
|
+
this.#setActive((activeIndex + 1) % length);
|
|
355
703
|
break;
|
|
356
704
|
case "ArrowUp":
|
|
357
705
|
event.preventDefault();
|
|
@@ -387,19 +735,24 @@ var ListboxController = class extends Controller {
|
|
|
387
735
|
break;
|
|
388
736
|
}
|
|
389
737
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
738
|
+
select(source) {
|
|
739
|
+
const { event, host, origin, reason } = actionSource(source);
|
|
740
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
741
|
+
return;
|
|
742
|
+
const option = host?.closest(targetSelector(this.identifier, "option"));
|
|
393
743
|
if (!option || !this.optionTargets.includes(option)) return;
|
|
394
|
-
this
|
|
744
|
+
const focus = event !== null || this.element.contains(document.activeElement);
|
|
745
|
+
if (!this.#selectOption(option, reason)) return;
|
|
395
746
|
this.close();
|
|
396
|
-
this.
|
|
747
|
+
if (focus && (event !== null || this.element.contains(document.activeElement))) {
|
|
748
|
+
this.triggerTarget.focus();
|
|
749
|
+
}
|
|
397
750
|
}
|
|
398
|
-
/** Opens the list and activates the selected option (else the first). */
|
|
399
751
|
open() {
|
|
400
752
|
if (!this.hasListTarget) return;
|
|
401
|
-
this.listTarget
|
|
402
|
-
this.
|
|
753
|
+
this.#listHidden.write(this.listTarget, null);
|
|
754
|
+
this.#shownList = this.listTarget;
|
|
755
|
+
this.#expanded.write(this.triggerTarget, "true");
|
|
403
756
|
if (this.optionTargets.length === 0) {
|
|
404
757
|
this.#setActive(-1);
|
|
405
758
|
return;
|
|
@@ -409,142 +762,80 @@ var ListboxController = class extends Controller {
|
|
|
409
762
|
);
|
|
410
763
|
this.#setActive(selected === -1 ? 0 : selected);
|
|
411
764
|
}
|
|
412
|
-
/** Closes the list, clears the active option, and resets the typeahead buffer. */
|
|
413
765
|
close() {
|
|
414
766
|
if (!this.hasListTarget) return;
|
|
415
|
-
this.listTarget
|
|
416
|
-
this
|
|
767
|
+
this.#listHidden.write(this.listTarget, "");
|
|
768
|
+
this.#shownList = null;
|
|
769
|
+
this.#expanded.write(this.triggerTarget, "false");
|
|
417
770
|
this.#setActive(-1);
|
|
418
771
|
this.#typeahead.reset();
|
|
419
772
|
}
|
|
420
|
-
/** Commits the active option (keyboard) and closes, returning focus. */
|
|
421
773
|
#commitActive() {
|
|
422
|
-
this.#reconcileActive();
|
|
423
774
|
const options = this.optionTargets;
|
|
424
775
|
const activeIndex = this.#findActiveIndex(options);
|
|
425
|
-
const option =
|
|
776
|
+
const option = options[activeIndex];
|
|
426
777
|
if (option) this.#selectOption(option);
|
|
427
778
|
this.close();
|
|
428
779
|
this.triggerTarget.focus();
|
|
429
780
|
}
|
|
430
|
-
|
|
431
|
-
|
|
781
|
+
#selectOption(option, reason = "user") {
|
|
782
|
+
const generation = this.#connectionGeneration;
|
|
432
783
|
const { value, fieldChanged } = this.#applySelection(option);
|
|
784
|
+
const changed = option !== this.#settled.option || value !== this.#settled.value;
|
|
433
785
|
this.#settled = { option, value };
|
|
786
|
+
if (!changed) return true;
|
|
787
|
+
const token = this.#moves.record();
|
|
434
788
|
if (fieldChanged) commitField(this.fieldTarget);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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.
|
|
442
|
-
*/
|
|
789
|
+
if (generation !== this.#connectionGeneration || !this.#moves.isLatest(token)) return false;
|
|
790
|
+
this.dispatch("change", { detail: { value, option, reason } });
|
|
791
|
+
return this.#moves.isLatest(token);
|
|
792
|
+
}
|
|
443
793
|
#applySelection(option) {
|
|
444
794
|
for (const candidate of this.optionTargets) {
|
|
445
|
-
|
|
795
|
+
this.#writeSelection(candidate, candidate === option ? "true" : "false");
|
|
446
796
|
}
|
|
447
|
-
this.#
|
|
797
|
+
this.#flushMutations();
|
|
448
798
|
const value = option ? this.#optionValue(option) : "";
|
|
449
799
|
this.#renderLabel(option);
|
|
450
800
|
const fieldChanged = this.hasFieldTarget && writeField(this.fieldTarget, value);
|
|
451
801
|
return { value, fieldChanged };
|
|
452
802
|
}
|
|
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
803
|
#renderLabel(option) {
|
|
473
804
|
if (!this.hasValueTarget) return;
|
|
474
805
|
const label = this.valueTarget;
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
const owned = label.getAttribute(ownsLabel) === label.textContent && label.childElementCount === 0;
|
|
478
|
-
if (option === void 0 && !owned && label.hasAttribute(ownsLabel)) return;
|
|
806
|
+
const owned = this.#labelText.owns(label);
|
|
807
|
+
if (option === void 0 && !owned && this.#labelText.hasRecord(label)) return;
|
|
479
808
|
const text = option ? this.#optionLabel(option) : this.placeholderValue || null;
|
|
480
809
|
if (text === null) {
|
|
481
|
-
|
|
482
|
-
label.textContent = label.getAttribute(originalLabel) ?? "";
|
|
483
|
-
label.removeAttribute(ownsLabel);
|
|
484
|
-
label.removeAttribute(originalLabel);
|
|
810
|
+
this.#labelText.restore(label);
|
|
485
811
|
return;
|
|
486
812
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
setAttributeIfChanged(label, originalLabel, current);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
if (label.textContent !== text) label.textContent = text;
|
|
494
|
-
setAttributeIfChanged(label, ownsLabel, text);
|
|
813
|
+
const current = (label.textContent ?? "").trim();
|
|
814
|
+
const original = owned ? void 0 : option === void 0 || current !== text ? current : this.#labelText.read(label)?.original ?? "";
|
|
815
|
+
this.#labelText.write(label, text, original);
|
|
495
816
|
}
|
|
496
|
-
/** An option's visible label: its own text, trimmed. */
|
|
497
817
|
#optionLabel(option) {
|
|
498
818
|
return (option.textContent ?? "").trim();
|
|
499
819
|
}
|
|
500
|
-
/** The value an option submits: its `data-value`, else its label. */
|
|
501
820
|
#optionValue(option) {
|
|
502
821
|
return option.dataset.value ?? this.#optionLabel(option);
|
|
503
822
|
}
|
|
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
823
|
#observeOptions() {
|
|
511
|
-
|
|
512
|
-
if (this.#concernsOptions(records)) this.#reconcile.schedule();
|
|
513
|
-
});
|
|
514
|
-
observer.observe(this.element, {
|
|
824
|
+
this.#observer.observe(this.element, {
|
|
515
825
|
subtree: true,
|
|
516
|
-
attributes:
|
|
517
|
-
attributeFilter: OBSERVED_ATTRIBUTES
|
|
826
|
+
attributes: OBSERVED_ATTRIBUTES
|
|
518
827
|
});
|
|
519
|
-
this.#observer = observer;
|
|
520
828
|
}
|
|
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
829
|
#concernsOptions(records) {
|
|
527
830
|
const options = new Set(this.optionTargets);
|
|
528
831
|
return records.some((record) => options.has(record.target));
|
|
529
832
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Marks the option at `index` active via `data-active` and the trigger's
|
|
542
|
-
* `aria-activedescendant`. Pass `-1` to clear it (the attribute is removed, not
|
|
543
|
-
* set to empty, per the APG).
|
|
544
|
-
*/
|
|
833
|
+
#flushMutations() {
|
|
834
|
+
if (this.#concernsOptions(this.#observer.take())) this.#reconcile.schedule();
|
|
835
|
+
}
|
|
545
836
|
#setActive(index) {
|
|
546
837
|
const options = this.optionTargets;
|
|
547
|
-
const active =
|
|
838
|
+
const active = options[index] ?? null;
|
|
548
839
|
for (const option of options) {
|
|
549
840
|
const marked = option.hasAttribute("data-active");
|
|
550
841
|
if (option === active) {
|
|
@@ -555,15 +846,14 @@ var ListboxController = class extends Controller {
|
|
|
555
846
|
}
|
|
556
847
|
if (active?.id) {
|
|
557
848
|
this.#activeId = active.id;
|
|
558
|
-
this.triggerTarget
|
|
849
|
+
this.#activeDescendant.write(this.triggerTarget, active.id);
|
|
559
850
|
} else {
|
|
560
851
|
this.#activeId = null;
|
|
561
|
-
this.triggerTarget
|
|
852
|
+
this.#activeDescendant.write(this.triggerTarget, null);
|
|
562
853
|
}
|
|
563
854
|
this.#activeOrder = active ? options.map((option) => option.id).filter(Boolean) : [];
|
|
564
855
|
if (active && this.hasListTarget) scrollOptionIntoView(this.listTarget, active);
|
|
565
856
|
}
|
|
566
|
-
/** Resolves active state against the current target collection. */
|
|
567
857
|
#reconcileActive() {
|
|
568
858
|
if (!this.hasTriggerTarget || this.#isClosed) {
|
|
569
859
|
if (this.hasTriggerTarget) this.#setActive(-1);
|
|
@@ -583,17 +873,15 @@ var ListboxController = class extends Controller {
|
|
|
583
873
|
return;
|
|
584
874
|
}
|
|
585
875
|
const activeId = this.triggerTarget.getAttribute("aria-activedescendant") ?? this.#activeId;
|
|
586
|
-
this.#setActive(
|
|
876
|
+
this.#setActive(this.#findFallbackIndex(options, activeId));
|
|
587
877
|
}
|
|
588
|
-
/** Finds the live target carrying the stable ID, or the active marker for an ID-less option. */
|
|
589
878
|
#findActiveIndex(options) {
|
|
590
879
|
const activeId = (this.hasTriggerTarget ? this.triggerTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
|
|
591
880
|
if (activeId) return options.findIndex((option) => option.id === activeId);
|
|
592
881
|
return options.findIndex((option) => option.hasAttribute("data-active"));
|
|
593
882
|
}
|
|
594
|
-
/** Chooses a surviving former successor, then a former predecessor. */
|
|
595
883
|
#findFallbackIndex(options, activeId) {
|
|
596
|
-
const oldIndex = this.#activeOrder.indexOf(activeId);
|
|
884
|
+
const oldIndex = this.#activeOrder.indexOf(activeId ?? "");
|
|
597
885
|
if (oldIndex < 0) return -1;
|
|
598
886
|
const indexesById = new Map(options.map((option, index) => [option.id, index]));
|
|
599
887
|
for (let index = oldIndex + 1; index < this.#activeOrder.length; index += 1) {
|
|
@@ -606,27 +894,22 @@ var ListboxController = class extends Controller {
|
|
|
606
894
|
}
|
|
607
895
|
return -1;
|
|
608
896
|
}
|
|
609
|
-
/**
|
|
610
|
-
* Advances the typeahead query and activates the next matching option.
|
|
611
|
-
*
|
|
612
|
-
* The search resumes just after the active option so repeating a character
|
|
613
|
-
* cycles through the options starting with it, rather than re-activating the
|
|
614
|
-
* same first match on every press.
|
|
615
|
-
*/
|
|
616
897
|
#typeaheadTo(options, activeIndex, char) {
|
|
617
898
|
const index = findTypeaheadMatch(options, activeIndex, this.#typeahead.push(char));
|
|
618
899
|
if (index !== -1) this.#setActive(index);
|
|
619
900
|
}
|
|
620
|
-
/** Closes on an outside click before an inside handler can detach its target. */
|
|
621
901
|
#onOutsideClick = (event) => {
|
|
622
902
|
if (!this.#isClosed && !this.element.contains(event.target)) this.close();
|
|
623
903
|
};
|
|
624
|
-
/** Whether the list is currently hidden. */
|
|
625
904
|
get #isClosed() {
|
|
626
905
|
return !this.hasListTarget || this.listTarget.hidden !== false;
|
|
627
906
|
}
|
|
907
|
+
#writeSelection(target, value) {
|
|
908
|
+
setAttributeIfChanged(target, "aria-selected", value, () => {
|
|
909
|
+
this.#observer.claim(target, "aria-selected", value);
|
|
910
|
+
});
|
|
911
|
+
}
|
|
628
912
|
};
|
|
629
913
|
|
|
630
914
|
export { ListboxController };
|
|
631
|
-
//# sourceMappingURL=listbox_controller.js.map
|
|
632
915
|
//# sourceMappingURL=listbox_controller.js.map
|