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,218 +1,482 @@
|
|
|
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
|
+
}
|
|
24
|
+
|
|
25
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
26
|
+
if (element.getAttribute(name) === value) return false;
|
|
27
|
+
if (value === null) element.removeAttribute(name);
|
|
28
|
+
else element.setAttribute(name, value);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
4
31
|
|
|
5
|
-
// src/utils/active_option.ts
|
|
6
32
|
function syncActiveOption(options, active) {
|
|
7
33
|
let written = 0;
|
|
8
34
|
for (const option of options) {
|
|
9
35
|
const next = option === active ? "true" : "false";
|
|
10
|
-
if (option
|
|
11
|
-
option.setAttribute("aria-selected", next);
|
|
12
|
-
written += 1;
|
|
36
|
+
if (setAttributeIfChanged(option, "aria-selected", next)) written += 1;
|
|
13
37
|
}
|
|
14
38
|
return written;
|
|
15
39
|
}
|
|
16
40
|
|
|
17
|
-
// src/utils/arrow_step.ts
|
|
18
41
|
function isReservedArrowChord(event, allow = []) {
|
|
19
42
|
if (!event.key.startsWith("Arrow")) return false;
|
|
20
43
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
21
44
|
}
|
|
45
|
+
function hasModifierChord(event) {
|
|
46
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sharedRegistry(key, create) {
|
|
50
|
+
const symbol = Symbol.for(key);
|
|
51
|
+
const scope = globalThis;
|
|
52
|
+
const existing = scope[symbol];
|
|
53
|
+
if (existing !== void 0) return existing;
|
|
54
|
+
const registry2 = create();
|
|
55
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
56
|
+
return registry2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var registry = sharedRegistry(
|
|
60
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
61
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
62
|
+
);
|
|
63
|
+
function isAttributeValue(value) {
|
|
64
|
+
return value === null || typeof value === "string";
|
|
65
|
+
}
|
|
66
|
+
function readLeaseRecord(element, record) {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
69
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
70
|
+
const [original, written, ...rest] = parsed;
|
|
71
|
+
const linked = rest[0] !== false;
|
|
72
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
73
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
74
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
75
|
+
} catch {
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function attributeStack(element, attribute) {
|
|
80
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
81
|
+
registry.stacks.set(element, byAttribute);
|
|
82
|
+
const known = byAttribute.get(attribute);
|
|
83
|
+
if (known) return known;
|
|
84
|
+
const found = [];
|
|
85
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
86
|
+
const read = readLeaseRecord(element, record);
|
|
87
|
+
if (read) found.push({ ...read, record });
|
|
88
|
+
}
|
|
89
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
90
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
91
|
+
lease: null,
|
|
92
|
+
record,
|
|
93
|
+
written,
|
|
94
|
+
original,
|
|
95
|
+
linked: linked && index > 0
|
|
96
|
+
}));
|
|
97
|
+
byAttribute.set(attribute, stack);
|
|
98
|
+
return stack;
|
|
99
|
+
}
|
|
100
|
+
function findHold(stack, lease, record) {
|
|
101
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
102
|
+
}
|
|
103
|
+
var AttributeLease = class {
|
|
104
|
+
#attribute;
|
|
105
|
+
#record;
|
|
106
|
+
#base;
|
|
107
|
+
#preserveAuthor;
|
|
108
|
+
#held = /* @__PURE__ */ new Set();
|
|
109
|
+
constructor(attribute, owner, options = {}) {
|
|
110
|
+
this.#attribute = attribute;
|
|
111
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
112
|
+
this.#base = options.base === true;
|
|
113
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
114
|
+
const names = registry.names;
|
|
115
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
116
|
+
}
|
|
117
|
+
write(element, value) {
|
|
118
|
+
const stack = attributeStack(element, this.#attribute);
|
|
119
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
120
|
+
this.#held.add(element);
|
|
121
|
+
const shows = !this.#covered(element, stack, hold);
|
|
122
|
+
hold.written = value;
|
|
123
|
+
this.#mark(element, stack);
|
|
124
|
+
if (shows) this.#reflect(element, value);
|
|
125
|
+
}
|
|
126
|
+
return(element) {
|
|
127
|
+
const held = this.#held.delete(element);
|
|
128
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
129
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
130
|
+
const hold = this.#take(stack);
|
|
131
|
+
if (!hold) return false;
|
|
132
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
133
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
134
|
+
const index = stack.indexOf(hold);
|
|
135
|
+
stack.splice(index, 1);
|
|
136
|
+
const above = stack[index];
|
|
137
|
+
if (above) above.linked &&= hold.linked;
|
|
138
|
+
this.#mark(element, stack);
|
|
139
|
+
if (!owns) return true;
|
|
140
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
141
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
returnAll() {
|
|
145
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
146
|
+
}
|
|
147
|
+
#take(stack) {
|
|
148
|
+
const hold = findHold(stack, this, this.#record);
|
|
149
|
+
if (hold) hold.lease = this;
|
|
150
|
+
return hold;
|
|
151
|
+
}
|
|
152
|
+
#acquire(element, stack) {
|
|
153
|
+
const current = element.getAttribute(this.#attribute);
|
|
154
|
+
const top = stack.at(-1);
|
|
155
|
+
const bottom = stack[0];
|
|
156
|
+
const over = top?.written === current ? top : void 0;
|
|
157
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
158
|
+
const hold = {
|
|
159
|
+
lease: this,
|
|
160
|
+
record: this.#record,
|
|
161
|
+
written: current,
|
|
162
|
+
original: from ? from.original : current,
|
|
163
|
+
linked: !this.#base && over !== void 0
|
|
164
|
+
};
|
|
165
|
+
if (this.#base && bottom) {
|
|
166
|
+
bottom.linked = true;
|
|
167
|
+
stack.unshift(hold);
|
|
168
|
+
} else stack.push(hold);
|
|
169
|
+
return hold;
|
|
170
|
+
}
|
|
171
|
+
#covered(element, stack, hold) {
|
|
172
|
+
const current = element.getAttribute(this.#attribute);
|
|
173
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
174
|
+
}
|
|
175
|
+
#mark(element, stack) {
|
|
176
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
177
|
+
stack.forEach((hold, index) => {
|
|
178
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
179
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
180
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
181
|
+
records.set(
|
|
182
|
+
hold.record,
|
|
183
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
for (const [name, recorded] of records) {
|
|
187
|
+
setAttributeIfChanged(element, name, recorded);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
#reflect(element, value) {
|
|
191
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
22
194
|
|
|
23
|
-
// src/utils/composition_tracker.ts
|
|
24
195
|
var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
25
196
|
"insertCompositionText",
|
|
26
197
|
"insertFromComposition",
|
|
27
198
|
"deleteCompositionText",
|
|
28
199
|
"deleteByComposition"
|
|
29
200
|
]);
|
|
201
|
+
function isCompositionInputType(inputType) {
|
|
202
|
+
return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
|
|
203
|
+
}
|
|
204
|
+
var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
|
|
205
|
+
var textOf = (element) => element.value;
|
|
206
|
+
var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
|
|
30
207
|
var CompositionTracker = class {
|
|
31
208
|
#observedTargets = /* @__PURE__ */ new Set();
|
|
32
|
-
#
|
|
209
|
+
#composition = null;
|
|
33
210
|
#onStart;
|
|
34
211
|
#onEnd;
|
|
35
|
-
|
|
212
|
+
#onAbandon;
|
|
36
213
|
#confirmedTarget = null;
|
|
214
|
+
#keyComposing = false;
|
|
215
|
+
#connected = false;
|
|
216
|
+
#abandoned = null;
|
|
37
217
|
constructor(options = {}) {
|
|
38
218
|
this.#onStart = options.onStart;
|
|
39
219
|
this.#onEnd = options.onEnd;
|
|
220
|
+
this.#onAbandon = options.onAbandon;
|
|
40
221
|
}
|
|
41
|
-
/** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
|
|
42
222
|
observe(target) {
|
|
223
|
+
this.#connected = true;
|
|
43
224
|
if (this.#observedTargets.has(target)) return;
|
|
44
225
|
target.addEventListener("compositionstart", this.#handleStart);
|
|
45
226
|
target.addEventListener("compositionend", this.#handleEnd);
|
|
46
|
-
target.addEventListener("keydown", this.#handleKeydown);
|
|
227
|
+
target.addEventListener("keydown", this.#handleKeydown, true);
|
|
228
|
+
target.addEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
229
|
+
target.addEventListener("input", this.#handleInput, true);
|
|
230
|
+
target.addEventListener("blur", this.#handleBlur, true);
|
|
231
|
+
target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
47
232
|
this.#observedTargets.add(target);
|
|
48
233
|
}
|
|
49
|
-
/** Stops tracking one target and clears any active composition it owned. */
|
|
50
234
|
unobserve(target) {
|
|
51
235
|
if (!this.#observedTargets.delete(target)) return;
|
|
52
|
-
|
|
53
|
-
target
|
|
54
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
55
|
-
this.#activeTargets.delete(target);
|
|
236
|
+
this.#release(target);
|
|
237
|
+
if (this.#composition?.target === target) this.#letGo();
|
|
56
238
|
if (this.#confirmedTarget === target) this.#confirmedTarget = null;
|
|
57
239
|
}
|
|
58
|
-
/** Releases every listener and clears state so reconnect starts cleanly. */
|
|
59
240
|
disconnect() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
target.removeEventListener("compositionend", this.#handleEnd);
|
|
63
|
-
target.removeEventListener("keydown", this.#handleKeydown);
|
|
64
|
-
}
|
|
241
|
+
this.#connected = false;
|
|
242
|
+
for (const target of this.#observedTargets) this.#release(target);
|
|
65
243
|
this.#observedTargets.clear();
|
|
66
|
-
this.#
|
|
244
|
+
this.#letGo();
|
|
67
245
|
this.#confirmedTarget = null;
|
|
246
|
+
this.#keyComposing = false;
|
|
247
|
+
this.#abandoned = null;
|
|
68
248
|
}
|
|
69
|
-
/** True when lifecycle tracking or the current event reports composition. */
|
|
70
249
|
isComposing(event) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
250
|
+
this.#settle();
|
|
251
|
+
return event?.isComposing === true || this.#composition !== null;
|
|
252
|
+
}
|
|
253
|
+
composingElement() {
|
|
254
|
+
this.#settle();
|
|
255
|
+
return this.#composition?.element ?? null;
|
|
256
|
+
}
|
|
257
|
+
owns(event) {
|
|
258
|
+
const element = this.composingElement();
|
|
259
|
+
return event.isComposing === true || element !== null && element === event.target;
|
|
260
|
+
}
|
|
79
261
|
consumesConfirmedInput(event) {
|
|
80
262
|
const confirmed = this.#confirmedTarget;
|
|
81
263
|
this.#confirmedTarget = null;
|
|
82
264
|
if (confirmed === null || confirmed !== event.target) return false;
|
|
83
265
|
const inputType = event.inputType;
|
|
84
|
-
return !inputType ||
|
|
266
|
+
return !inputType || isCompositionInputType(inputType);
|
|
267
|
+
}
|
|
268
|
+
#release(target) {
|
|
269
|
+
target.removeEventListener("compositionstart", this.#handleStart);
|
|
270
|
+
target.removeEventListener("compositionend", this.#handleEnd);
|
|
271
|
+
target.removeEventListener("keydown", this.#handleKeydown, true);
|
|
272
|
+
target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
|
|
273
|
+
target.removeEventListener("input", this.#handleInput, true);
|
|
274
|
+
target.removeEventListener("blur", this.#handleBlur, true);
|
|
275
|
+
target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
|
|
276
|
+
}
|
|
277
|
+
#letGo() {
|
|
278
|
+
const composition = this.#composition;
|
|
279
|
+
if (composition === null) return;
|
|
280
|
+
if (!holdsFocus(composition.element)) {
|
|
281
|
+
this.#composition = null;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
queueMicrotask(() => {
|
|
285
|
+
if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.#composition = null;
|
|
289
|
+
if (!this.#connected) return;
|
|
290
|
+
this.#abandoned = { element: composition.element, inPlace: true };
|
|
291
|
+
this.#runAbandon();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
#settle() {
|
|
295
|
+
const composition = this.#composition;
|
|
296
|
+
if (composition === null) return;
|
|
297
|
+
const { target, element, text } = composition;
|
|
298
|
+
if (target instanceof Node && element instanceof Node && !target.contains(element)) {
|
|
299
|
+
this.#composition = null;
|
|
300
|
+
} else if (textOf(element) !== text) {
|
|
301
|
+
this.#abandon(false);
|
|
302
|
+
}
|
|
85
303
|
}
|
|
304
|
+
#abandon(inPlace) {
|
|
305
|
+
this.#owe(inPlace);
|
|
306
|
+
this.#composition = null;
|
|
307
|
+
queueMicrotask(this.#runAbandon);
|
|
308
|
+
}
|
|
309
|
+
#owe(inPlace) {
|
|
310
|
+
const composition = this.#composition;
|
|
311
|
+
if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
|
|
312
|
+
}
|
|
313
|
+
#runAbandon = () => {
|
|
314
|
+
const abandoned = this.#abandoned;
|
|
315
|
+
if (abandoned === null) return;
|
|
316
|
+
this.#abandoned = null;
|
|
317
|
+
this.#onAbandon?.(abandoned.element, abandoned.inPlace);
|
|
318
|
+
};
|
|
86
319
|
#handleStart = (event) => {
|
|
87
320
|
this.#confirmedTarget = null;
|
|
88
|
-
|
|
321
|
+
this.#settle();
|
|
322
|
+
this.#owe(true);
|
|
323
|
+
const target = event.currentTarget;
|
|
324
|
+
const element = event.target;
|
|
325
|
+
this.#composition = { target, element, text: textOf(element) };
|
|
326
|
+
this.#runAbandon();
|
|
89
327
|
this.#onStart?.(event);
|
|
90
328
|
};
|
|
91
329
|
#handleEnd = (event) => {
|
|
92
|
-
|
|
330
|
+
this.#runAbandon();
|
|
331
|
+
if (this.#composition?.target === event.currentTarget) this.#composition = null;
|
|
93
332
|
this.#confirmedTarget = event.target;
|
|
94
333
|
this.#onEnd?.(event);
|
|
95
334
|
};
|
|
96
|
-
|
|
97
|
-
|
|
335
|
+
#handleKeydown = (event) => {
|
|
336
|
+
this.#settle();
|
|
337
|
+
this.#runAbandon();
|
|
98
338
|
this.#confirmedTarget = null;
|
|
339
|
+
this.#keyComposing = event.isComposing === true;
|
|
340
|
+
};
|
|
341
|
+
#handleBeforeInput = (event) => {
|
|
342
|
+
const keyComposing = this.#keyComposing;
|
|
343
|
+
this.#keyComposing = false;
|
|
344
|
+
this.#settle();
|
|
345
|
+
const { isComposing, inputType } = event;
|
|
346
|
+
if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
|
|
347
|
+
this.#abandon(true);
|
|
348
|
+
}
|
|
349
|
+
this.#runAbandon();
|
|
350
|
+
};
|
|
351
|
+
#handleInput = (event) => {
|
|
352
|
+
const composition = this.#composition;
|
|
353
|
+
const { isTrusted, isComposing, inputType } = event;
|
|
354
|
+
if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
|
|
355
|
+
composition.text = textOf(composition.element);
|
|
356
|
+
} else {
|
|
357
|
+
this.#settle();
|
|
358
|
+
}
|
|
359
|
+
this.#runAbandon();
|
|
360
|
+
};
|
|
361
|
+
#handleBlur = (event) => {
|
|
362
|
+
this.#settle();
|
|
363
|
+
this.#runAbandon();
|
|
364
|
+
const composition = this.#composition;
|
|
365
|
+
if (composition === null || composition.target === composition.element) return;
|
|
366
|
+
const { element } = composition;
|
|
367
|
+
if (!(element instanceof Node) || event.target !== element) return;
|
|
368
|
+
const root = element.getRootNode();
|
|
369
|
+
if (root.activeElement !== element) this.#composition = null;
|
|
370
|
+
};
|
|
371
|
+
#handleBeforeMorphAttribute = (event) => {
|
|
372
|
+
this.#settle();
|
|
373
|
+
const composition = this.#composition;
|
|
374
|
+
if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
|
|
375
|
+
const detail = event.detail;
|
|
376
|
+
if (detail?.attributeName === "value") event.preventDefault();
|
|
99
377
|
};
|
|
100
378
|
};
|
|
101
379
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
static
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
activate() {
|
|
117
|
-
const first = _BeforeCacheReset.#subscribers.size === 0;
|
|
118
|
-
_BeforeCacheReset.#subscribers.add(this);
|
|
119
|
-
if (first) {
|
|
120
|
-
document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
380
|
+
var DetachGate = class _DetachGate {
|
|
381
|
+
#pending = false;
|
|
382
|
+
get pending() {
|
|
383
|
+
return this.#pending;
|
|
384
|
+
}
|
|
385
|
+
static isDetached(host) {
|
|
386
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
387
|
+
}
|
|
388
|
+
disconnected(host, teardown) {
|
|
389
|
+
const carried = _DetachGate.#carried(host);
|
|
390
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
391
|
+
this.#pending = false;
|
|
392
|
+
teardown();
|
|
393
|
+
return;
|
|
121
394
|
}
|
|
395
|
+
this.#pending = true;
|
|
396
|
+
const probe = () => {
|
|
397
|
+
if (!this.#pending) return;
|
|
398
|
+
this.#pending = false;
|
|
399
|
+
teardown();
|
|
400
|
+
};
|
|
401
|
+
if (carried) setTimeout(probe, 0);
|
|
402
|
+
else queueMicrotask(probe);
|
|
122
403
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
404
|
+
static #carried(host) {
|
|
405
|
+
const element = host.element;
|
|
406
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
407
|
+
}
|
|
408
|
+
static #listed(host) {
|
|
409
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
410
|
+
return tokens.includes(host.identifier);
|
|
411
|
+
}
|
|
412
|
+
cancel() {
|
|
413
|
+
this.#pending = false;
|
|
128
414
|
}
|
|
129
415
|
};
|
|
130
416
|
|
|
131
|
-
// src/utils/escape_layer.ts
|
|
132
417
|
var EscapeLayer = class _EscapeLayer {
|
|
133
|
-
static #registries =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#claims = null;
|
|
139
|
-
/**
|
|
140
|
-
* Activates this layer at the top of its document's Escape stack, installing
|
|
141
|
-
* the document's shared resolver listener if this is its first layer.
|
|
142
|
-
* Re-activating an already-active layer moves it to the top.
|
|
143
|
-
*/
|
|
418
|
+
static #registries = sharedRegistry(
|
|
419
|
+
"stimeo-ui.escape-layer.registry.v1",
|
|
420
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
421
|
+
);
|
|
422
|
+
#active = null;
|
|
144
423
|
activate(ownerDocument = document, options) {
|
|
145
424
|
this.deactivate();
|
|
146
|
-
let
|
|
147
|
-
if (!
|
|
148
|
-
|
|
149
|
-
_EscapeLayer.#registries.set(ownerDocument,
|
|
150
|
-
ownerDocument.addEventListener("keydown",
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.#
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Removes this layer from its document's Escape stack, uninstalling the
|
|
159
|
-
* shared listener when the stack empties. Safe to call when inactive.
|
|
160
|
-
*/
|
|
425
|
+
let registry2 = _EscapeLayer.#registries.get(ownerDocument);
|
|
426
|
+
if (!registry2) {
|
|
427
|
+
registry2 = _EscapeLayer.#createRegistry();
|
|
428
|
+
_EscapeLayer.#registries.set(ownerDocument, registry2);
|
|
429
|
+
ownerDocument.addEventListener("keydown", registry2.onKeydown);
|
|
430
|
+
}
|
|
431
|
+
const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
|
|
432
|
+
registry2.stack.push(entry);
|
|
433
|
+
this.#active = { document: ownerDocument, entry };
|
|
434
|
+
}
|
|
161
435
|
deactivate() {
|
|
162
|
-
const
|
|
163
|
-
if (!
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
const index =
|
|
167
|
-
if (index >= 0)
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
_EscapeLayer.#registries.delete(
|
|
436
|
+
const active = this.#active;
|
|
437
|
+
if (!active) return;
|
|
438
|
+
const registry2 = _EscapeLayer.#registries.get(active.document);
|
|
439
|
+
if (registry2) {
|
|
440
|
+
const index = registry2.stack.lastIndexOf(active.entry);
|
|
441
|
+
if (index >= 0) registry2.stack.splice(index, 1);
|
|
442
|
+
if (registry2.stack.length === 0) {
|
|
443
|
+
active.document.removeEventListener("keydown", registry2.onKeydown);
|
|
444
|
+
_EscapeLayer.#registries.delete(active.document);
|
|
171
445
|
}
|
|
172
446
|
}
|
|
173
|
-
this.#
|
|
174
|
-
this.#onDismiss = null;
|
|
175
|
-
this.#claims = null;
|
|
447
|
+
this.#active = null;
|
|
176
448
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Whether this active layer would own a press right now: it is the topmost
|
|
179
|
-
* layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
|
|
180
|
-
* and diagnostics — production dismissal goes through the shared listener.
|
|
181
|
-
*/
|
|
182
449
|
get ownsEscape() {
|
|
183
|
-
const
|
|
184
|
-
if (!
|
|
185
|
-
const
|
|
186
|
-
if (!
|
|
187
|
-
return _EscapeLayer.#resolveOwner(
|
|
450
|
+
const active = this.#active;
|
|
451
|
+
if (!active) return false;
|
|
452
|
+
const registry2 = _EscapeLayer.#registries.get(active.document);
|
|
453
|
+
if (!registry2) return false;
|
|
454
|
+
return _EscapeLayer.#resolveOwner(registry2.stack) === active.entry;
|
|
188
455
|
}
|
|
189
|
-
/** Builds a document's registry with its shared resolver listener. */
|
|
190
456
|
static #createRegistry() {
|
|
191
|
-
const
|
|
457
|
+
const registry2 = {
|
|
192
458
|
stack: [],
|
|
193
459
|
onKeydown: (event) => {
|
|
194
460
|
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
195
|
-
const owner = _EscapeLayer.#resolveOwner(
|
|
461
|
+
const owner = _EscapeLayer.#resolveOwner(registry2.stack);
|
|
196
462
|
if (!owner) return;
|
|
197
463
|
event.preventDefault();
|
|
198
|
-
owner
|
|
464
|
+
owner.onDismiss();
|
|
199
465
|
}
|
|
200
466
|
};
|
|
201
|
-
return
|
|
467
|
+
return registry2;
|
|
202
468
|
}
|
|
203
|
-
/** The topmost stack layer whose claims predicate passes, or `null`. */
|
|
204
469
|
static #resolveOwner(stack) {
|
|
205
470
|
for (let index = stack.length - 1; index >= 0; index--) {
|
|
206
471
|
const layer = stack[index];
|
|
207
472
|
if (!layer) continue;
|
|
208
|
-
if (layer
|
|
473
|
+
if (layer.claims && !layer.claims()) continue;
|
|
209
474
|
return layer;
|
|
210
475
|
}
|
|
211
476
|
return null;
|
|
212
477
|
}
|
|
213
478
|
};
|
|
214
479
|
|
|
215
|
-
// src/utils/focus_candidate.ts
|
|
216
480
|
function inheritsFieldsetDisabled(control) {
|
|
217
481
|
let fieldset = control.closest("fieldset[disabled]");
|
|
218
482
|
while (fieldset) {
|
|
@@ -222,8 +486,34 @@ function inheritsFieldsetDisabled(control) {
|
|
|
222
486
|
}
|
|
223
487
|
return false;
|
|
224
488
|
}
|
|
489
|
+
function unslotted(element) {
|
|
490
|
+
return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
|
|
491
|
+
}
|
|
492
|
+
function flatTreeParent(element) {
|
|
493
|
+
if (unslotted(element)) return null;
|
|
494
|
+
const parent = element.assignedSlot ?? element.parentNode;
|
|
495
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
496
|
+
return parent instanceof Element ? parent : null;
|
|
497
|
+
}
|
|
498
|
+
function flatTreeContains(ancestor, element) {
|
|
499
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
500
|
+
if (node === ancestor) return true;
|
|
501
|
+
}
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
function closestInFlatTree(element, selector) {
|
|
505
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
506
|
+
if (node.matches(selector)) return node;
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
function flatTreeChildren(element) {
|
|
511
|
+
return ownedScope(element) ?? Array.from(element.children);
|
|
512
|
+
}
|
|
225
513
|
function canTakeFocus(element) {
|
|
226
|
-
|
|
514
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
515
|
+
if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
|
|
516
|
+
}
|
|
227
517
|
if (element instanceof HTMLInputElement && element.type === "hidden") return false;
|
|
228
518
|
if (!("disabled" in element)) return true;
|
|
229
519
|
if (element.disabled) return false;
|
|
@@ -280,222 +570,697 @@ function isTabStop(element) {
|
|
|
280
570
|
if (tabindex !== null) return tabindex >= 0;
|
|
281
571
|
return hasNativeTabStop(element) || hasEditableTabStop(element);
|
|
282
572
|
}
|
|
283
|
-
function
|
|
284
|
-
return
|
|
285
|
-
|
|
573
|
+
function isRadio(element) {
|
|
574
|
+
return element instanceof HTMLInputElement && element.type === "radio";
|
|
575
|
+
}
|
|
576
|
+
function sameRadioGroup(a, b) {
|
|
577
|
+
return a.name !== "" && a.name === b.name && a.form === b.form && a.getRootNode() === b.getRootNode();
|
|
578
|
+
}
|
|
579
|
+
function isInEditableContent(element) {
|
|
580
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
581
|
+
if (node.getAttribute("contenteditable")?.toLowerCase() === "false") return false;
|
|
582
|
+
if (hasEditableTabStop(node)) return true;
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
var XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
|
|
587
|
+
function isSvgLink(element) {
|
|
588
|
+
return element instanceof SVGElement && element.localName === "a" && (element.hasAttribute("href") || element.hasAttributeNS(XLINK_NAMESPACE, "href"));
|
|
589
|
+
}
|
|
590
|
+
function hasRenderedImage(area) {
|
|
591
|
+
const map = area.closest("map");
|
|
592
|
+
const key = map?.name || map?.id;
|
|
593
|
+
if (!map || !key) return false;
|
|
594
|
+
const root = map.getRootNode();
|
|
595
|
+
return Array.from(root.querySelectorAll("img[usemap]")).some(
|
|
596
|
+
(image) => image.getAttribute("usemap") === `#${key}` && isRenderedForFocus(image)
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
function isFocusableInOrder(element) {
|
|
600
|
+
const tabindex = authoredTabindex(element);
|
|
601
|
+
return (tabindex === null || tabindex >= 0) && canTakeFocus(element);
|
|
602
|
+
}
|
|
603
|
+
function extraStop(element) {
|
|
604
|
+
if (element instanceof HTMLAreaElement) {
|
|
605
|
+
const focusable = element.hasAttribute("href") || authoredTabindex(element) !== null;
|
|
606
|
+
return focusable && isFocusableInOrder(element) && hasRenderedImage(element);
|
|
607
|
+
}
|
|
608
|
+
if (element instanceof HTMLObjectElement) {
|
|
609
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element) && element.contentWindow !== null;
|
|
610
|
+
}
|
|
611
|
+
if (isSvgLink(element))
|
|
612
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element);
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
function isSequentialStop(element) {
|
|
616
|
+
const stop = extraStop(element) ?? (element.matches(TAB_STOP_CANDIDATE_SELECTOR) && isTabStop(element));
|
|
617
|
+
if (!stop) return false;
|
|
618
|
+
const link = element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement || isSvgLink(element);
|
|
619
|
+
return !(link && authoredTabindex(element) === null && isInEditableContent(element));
|
|
620
|
+
}
|
|
621
|
+
function ownedScope(element) {
|
|
622
|
+
if (element.shadowRoot) return Array.from(element.shadowRoot.children);
|
|
623
|
+
if (element instanceof HTMLSlotElement) {
|
|
624
|
+
const assigned = element.assignedElements();
|
|
625
|
+
return assigned.length > 0 ? assigned : Array.from(element.children);
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
function orderScope(elements, walk) {
|
|
630
|
+
const entries = [];
|
|
631
|
+
const visit = (element) => {
|
|
632
|
+
if (element === walk.from) walk.fromAt = walk.tree.length;
|
|
633
|
+
const tabindex = authoredTabindex(element) ?? 0;
|
|
634
|
+
const stop = (element instanceof HTMLElement || element instanceof SVGElement) && isSequentialStop(element) ? element : null;
|
|
635
|
+
if (stop) walk.tree.push(stop);
|
|
636
|
+
const owned = ownedScope(element);
|
|
637
|
+
if (owned) {
|
|
638
|
+
if (tabindex >= 0) {
|
|
639
|
+
entries.push({ tabindex, stops: [...stop ? [stop] : [], ...orderScope(owned, walk)] });
|
|
640
|
+
}
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (stop) entries.push({ tabindex, stops: [stop] });
|
|
644
|
+
for (const child of Array.from(element.children)) visit(child);
|
|
645
|
+
};
|
|
646
|
+
for (const element of elements) visit(element);
|
|
647
|
+
const positive = entries.filter((entry) => entry.tabindex > 0);
|
|
648
|
+
positive.sort((a, b) => a.tabindex - b.tabindex);
|
|
649
|
+
const rest = entries.filter((entry) => entry.tabindex === 0);
|
|
650
|
+
return [...positive, ...rest].flatMap((entry) => entry.stops);
|
|
651
|
+
}
|
|
652
|
+
function passesRadioRule(element, tree, focusedRadio) {
|
|
653
|
+
if (!isRadio(element) || element.checked) return true;
|
|
654
|
+
const group = tree.filter(
|
|
655
|
+
(stop) => stop === element || isRadio(stop) && sameRadioGroup(stop, element)
|
|
286
656
|
);
|
|
657
|
+
if (group.some((radio) => radio.checked)) return false;
|
|
658
|
+
if (group[0] !== element) return false;
|
|
659
|
+
return focusedRadio === null || !sameRadioGroup(focusedRadio, element);
|
|
660
|
+
}
|
|
661
|
+
function startIndex(order, walk, backward, isStop) {
|
|
662
|
+
if (walk.fromAt < 0) return backward ? order.length - 1 : 0;
|
|
663
|
+
const at = order.indexOf(walk.from);
|
|
664
|
+
if (at >= 0) return backward ? at - 1 : at + 1;
|
|
665
|
+
const neighbour = backward ? walk.tree.slice(0, walk.fromAt).reverse().find(isStop) : walk.tree.slice(walk.fromAt).find(isStop);
|
|
666
|
+
if (neighbour) return order.indexOf(neighbour);
|
|
667
|
+
return backward ? order.length - 1 : 0;
|
|
668
|
+
}
|
|
669
|
+
function sequentialTabStops(root, from = null, backward = false) {
|
|
670
|
+
const walk = { from, tree: [], fromAt: from === root ? 0 : -1 };
|
|
671
|
+
const order = orderScope(ownedScope(root) ?? Array.from(root.children), walk);
|
|
672
|
+
const focusedRadio = walk.fromAt >= 0 && isRadio(from) ? from : null;
|
|
673
|
+
const isStop = (element) => passesRadioRule(element, walk.tree, focusedRadio);
|
|
674
|
+
const start = startIndex(order, walk, backward, isStop);
|
|
675
|
+
const visited = backward ? [...order.slice(0, start + 1).reverse(), ...order.slice(start + 1).reverse()] : [...order.slice(start), ...order.slice(0, start)];
|
|
676
|
+
return visited.filter((element) => element !== from && isStop(element));
|
|
677
|
+
}
|
|
678
|
+
function deepActiveElement(ownerDocument) {
|
|
679
|
+
let active = ownerDocument.activeElement;
|
|
680
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
681
|
+
return active;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
var MicrotaskCoalescer = class {
|
|
685
|
+
#run;
|
|
686
|
+
#queued = false;
|
|
687
|
+
#active = false;
|
|
688
|
+
#generation = 0;
|
|
689
|
+
constructor(run) {
|
|
690
|
+
this.#run = run;
|
|
691
|
+
}
|
|
692
|
+
activate() {
|
|
693
|
+
this.#active = true;
|
|
694
|
+
}
|
|
695
|
+
cancel() {
|
|
696
|
+
this.#active = false;
|
|
697
|
+
this.#queued = false;
|
|
698
|
+
this.#generation += 1;
|
|
699
|
+
}
|
|
700
|
+
get pending() {
|
|
701
|
+
return this.#queued;
|
|
702
|
+
}
|
|
703
|
+
schedule() {
|
|
704
|
+
if (!this.#active || this.#queued) return;
|
|
705
|
+
this.#queued = true;
|
|
706
|
+
const generation = this.#generation;
|
|
707
|
+
queueMicrotask(() => {
|
|
708
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
709
|
+
this.#queued = false;
|
|
710
|
+
this.#run();
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
function queryOpenDescendants(root, selector) {
|
|
716
|
+
const matches = [];
|
|
717
|
+
const roots = [root];
|
|
718
|
+
for (const scope of roots) {
|
|
719
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
720
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
721
|
+
const shadow = scope.shadowRoot;
|
|
722
|
+
if (shadow) roots.push(shadow);
|
|
723
|
+
}
|
|
724
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
725
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return matches;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function holdsFocus2(element) {
|
|
732
|
+
return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
|
|
287
733
|
}
|
|
734
|
+
var TabindexLoan = class _TabindexLoan {
|
|
735
|
+
static #registry = sharedRegistry(
|
|
736
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
737
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
738
|
+
);
|
|
739
|
+
#value;
|
|
740
|
+
#record;
|
|
741
|
+
#preserveFocus;
|
|
742
|
+
#lent = /* @__PURE__ */ new Set();
|
|
743
|
+
constructor(value, owner, options = {}) {
|
|
744
|
+
this.#value = value;
|
|
745
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
746
|
+
this.#preserveFocus = options.preserveFocus === true;
|
|
747
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
748
|
+
}
|
|
749
|
+
lend(element, options = {}) {
|
|
750
|
+
const stack = this.#stack(element);
|
|
751
|
+
const current = element.getAttribute("tabindex");
|
|
752
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
753
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
754
|
+
);
|
|
755
|
+
const linked = stack.at(-1)?.value === current;
|
|
756
|
+
if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
|
|
757
|
+
if (!held && current !== null && !linked) return;
|
|
758
|
+
this.#lent.add(element);
|
|
759
|
+
if (held) held.loan = this;
|
|
760
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
761
|
+
this.#mark(element, stack);
|
|
762
|
+
if (current === null || !held && current !== this.#value) {
|
|
763
|
+
element.setAttribute("tabindex", this.#value);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return(element) {
|
|
767
|
+
const stack = this.#stack(element);
|
|
768
|
+
const hold = stack.find((held) => held.loan === this) ?? stack.find(
|
|
769
|
+
(held) => held.loan === null && held.record === this.#record && held.value === this.#value
|
|
770
|
+
);
|
|
771
|
+
if (!hold) return false;
|
|
772
|
+
if (!this.#release(element, stack, hold, this.#preserveFocus)) {
|
|
773
|
+
hold.loan = this;
|
|
774
|
+
this.#lent.add(element);
|
|
775
|
+
return false;
|
|
776
|
+
}
|
|
777
|
+
this.#lent.delete(element);
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
returnAll() {
|
|
781
|
+
for (const element of Array.from(this.#lent)) this.return(element);
|
|
782
|
+
}
|
|
783
|
+
reclaim(element) {
|
|
784
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
785
|
+
const stack = this.#stack(element);
|
|
786
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
787
|
+
if (hold && !holdsFocus2(element)) this.#release(element, stack, hold);
|
|
788
|
+
}
|
|
789
|
+
reclaimWithin(root) {
|
|
790
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
791
|
+
this.reclaim(element);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
#stack(element) {
|
|
795
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
796
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
797
|
+
stacks.set(element, stack);
|
|
798
|
+
return stack;
|
|
799
|
+
}
|
|
800
|
+
#seed(element) {
|
|
801
|
+
const current = element.getAttribute("tabindex");
|
|
802
|
+
const holds = [];
|
|
803
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
804
|
+
const value = element.getAttribute(record);
|
|
805
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
806
|
+
}
|
|
807
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
808
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
809
|
+
return holds;
|
|
810
|
+
}
|
|
811
|
+
#release(element, stack, hold, preserveFocus = false) {
|
|
812
|
+
const current = element.getAttribute("tabindex");
|
|
813
|
+
const index = stack.indexOf(hold);
|
|
814
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
815
|
+
if (owns && preserveFocus && holdsFocus2(element)) return false;
|
|
816
|
+
stack.splice(index, 1);
|
|
817
|
+
const above = stack[index];
|
|
818
|
+
if (above) above.linked &&= hold.linked;
|
|
819
|
+
this.#mark(element, stack);
|
|
820
|
+
if (!owns) return true;
|
|
821
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
822
|
+
if (!below) element.removeAttribute("tabindex");
|
|
823
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
824
|
+
return true;
|
|
825
|
+
}
|
|
826
|
+
#mark(element, stack) {
|
|
827
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
828
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
829
|
+
for (const [name, recorded] of records) {
|
|
830
|
+
setAttributeIfChanged(element, name, recorded);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
};
|
|
288
834
|
|
|
289
|
-
|
|
290
|
-
var
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
835
|
+
var FOCUS_TRAP_OWNER = "stimeo-focus-trap";
|
|
836
|
+
var INERT_MARK = `data-${FOCUS_TRAP_OWNER}-inert`;
|
|
837
|
+
var OVERFLOW_MARK = `data-${FOCUS_TRAP_OWNER}-overflow`;
|
|
838
|
+
var FocusTrap = class _FocusTrap {
|
|
839
|
+
static #registries = sharedRegistry(
|
|
840
|
+
"stimeo-ui.focus-trap.registry.v2",
|
|
841
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
842
|
+
);
|
|
843
|
+
static #TEXT_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
844
|
+
"text",
|
|
845
|
+
"search",
|
|
846
|
+
"email",
|
|
847
|
+
"url",
|
|
848
|
+
"tel",
|
|
849
|
+
"password",
|
|
850
|
+
"number"
|
|
851
|
+
]);
|
|
852
|
+
#openers = [];
|
|
853
|
+
#entry;
|
|
854
|
+
#tabindexLoans = /* @__PURE__ */ new Map();
|
|
300
855
|
#activeState = false;
|
|
301
|
-
/** Registers the trap on the shared Escape stack while active (see {@link EscapeLayer}). */
|
|
302
856
|
#escapeLayer = new EscapeLayer();
|
|
303
|
-
|
|
857
|
+
#gate = new DetachGate();
|
|
304
858
|
#getContainer;
|
|
305
|
-
/** Closing/focus hooks; see {@link FocusTrapOptions}. */
|
|
306
859
|
#options;
|
|
307
|
-
/**
|
|
308
|
-
* @param getContainer - Returns the trapped element. Called on every operation
|
|
309
|
-
* so the live target is always used.
|
|
310
|
-
* @param options - Closing/focus hooks; see {@link FocusTrapOptions}.
|
|
311
|
-
*/
|
|
312
860
|
constructor(getContainer, options = {}) {
|
|
313
861
|
this.#getContainer = getContainer;
|
|
314
862
|
this.#options = options;
|
|
863
|
+
this.#entry = {
|
|
864
|
+
container: () => this.#readContainer(),
|
|
865
|
+
moveFocus: (backward) => this.#moveFocus(backward),
|
|
866
|
+
refocus: () => this.#refocus(),
|
|
867
|
+
isolated: false,
|
|
868
|
+
lockedBody: null,
|
|
869
|
+
openedFrom: null,
|
|
870
|
+
handedBack: []
|
|
871
|
+
};
|
|
315
872
|
}
|
|
316
|
-
/** Whether the trap is currently active. */
|
|
317
873
|
get active() {
|
|
318
874
|
return this.#activeState;
|
|
319
875
|
}
|
|
320
|
-
/**
|
|
321
|
-
* Applies the trap: records the current focus, optionally locks background scroll
|
|
322
|
-
* and makes background siblings `inert`, listens for `Tab`/`Escape`, and (unless
|
|
323
|
-
* `autoFocus` is off) moves focus inside. No-ops if already active.
|
|
324
|
-
*/
|
|
325
876
|
activate() {
|
|
326
877
|
if (this.#activeState) return;
|
|
878
|
+
_FocusTrap.#sweep();
|
|
327
879
|
this.#activeState = true;
|
|
328
|
-
|
|
329
|
-
this.#
|
|
330
|
-
if (this.#flag(this.#options.lockScroll, true))
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
if (this.#flag(this.#options.isolate, true)) this.#isolateBackground();
|
|
336
|
-
document.addEventListener("keydown", this.#onKeydown);
|
|
337
|
-
this.#beforeCache.activate();
|
|
880
|
+
this.#openers = _FocusTrap.#focusPath();
|
|
881
|
+
this.#entry.openedFrom = _FocusTrap.#trapHolding(this.#openers[0] ?? null);
|
|
882
|
+
if (this.#flag(this.#options.lockScroll, true)) this.#lockScroll();
|
|
883
|
+
this.#entry.isolated = this.#flag(this.#options.isolate, true);
|
|
884
|
+
this.#joinStack();
|
|
885
|
+
_FocusTrap.#reconcileBackground(document);
|
|
338
886
|
const onEscape = this.#options.onEscape;
|
|
339
887
|
if (onEscape) this.#escapeLayer.activate(document, { onDismiss: () => onEscape() });
|
|
340
888
|
if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
|
|
341
889
|
}
|
|
342
|
-
/**
|
|
343
|
-
* Reverts every side effect applied by {@link activate}. No-ops if inactive, so
|
|
344
|
-
* a controller can call it defensively from both `close()` and `disconnect()`.
|
|
345
|
-
*
|
|
346
|
-
* @param restoreFocus - Move focus back to the opener (default `true`). Pass
|
|
347
|
-
* `false` on teardown (`disconnect`), where yanking focus is undesirable.
|
|
348
|
-
*/
|
|
349
890
|
deactivate({ restoreFocus = true } = {}) {
|
|
350
|
-
if (!this.#activeState)
|
|
891
|
+
if (!this.#activeState) {
|
|
892
|
+
this.#returnTabindexes();
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
351
895
|
this.#activeState = false;
|
|
352
896
|
this.#escapeLayer.deactivate();
|
|
353
|
-
|
|
354
|
-
this.#
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
897
|
+
const above = this.#leaveStack();
|
|
898
|
+
this.#unlockScroll();
|
|
899
|
+
_FocusTrap.#reconcileBackground(document);
|
|
900
|
+
const wayBack = this.#handOver(above);
|
|
901
|
+
if (restoreFocus) this.#restore(wayBack, above);
|
|
902
|
+
this.#returnTabindexes();
|
|
903
|
+
}
|
|
904
|
+
connect() {
|
|
905
|
+
this.#gate.cancel();
|
|
906
|
+
_FocusTrap.#sweep();
|
|
907
|
+
new TabindexLoan("-1", FOCUS_TRAP_OWNER).reclaimWithin(document);
|
|
908
|
+
}
|
|
909
|
+
disconnect(host) {
|
|
910
|
+
if (this.#activeState) this.deactivate({ restoreFocus: false });
|
|
911
|
+
this.#gate.disconnected(host, () => this.#returnTabindexes());
|
|
912
|
+
}
|
|
913
|
+
refreshIsolation() {
|
|
914
|
+
if (!this.#activeState) return;
|
|
915
|
+
const isolate = this.#flag(this.#options.isolate, true);
|
|
916
|
+
if (isolate === this.#entry.isolated) return;
|
|
917
|
+
this.#entry.isolated = isolate;
|
|
918
|
+
const stranded = _FocusTrap.#reconcileBackground(document);
|
|
919
|
+
const active = deepActiveElement(document);
|
|
920
|
+
if (stranded.some((element) => flatTreeContains(element, active))) this.#focusInitial();
|
|
921
|
+
}
|
|
922
|
+
refreshContainer() {
|
|
923
|
+
if (!this.#activeState) return;
|
|
924
|
+
_FocusTrap.#reconcileBackground(document);
|
|
925
|
+
this.#returnTabindexes();
|
|
926
|
+
if (this.#ownsTab()) this.#refocus();
|
|
364
927
|
}
|
|
365
|
-
/** Resolves a boolean-or-getter option, defaulting when it was not provided. */
|
|
366
928
|
#flag(option, fallback) {
|
|
367
929
|
if (option === void 0) return fallback;
|
|
368
930
|
return typeof option === "function" ? option() : option;
|
|
369
931
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
* is left alone mid-navigation. The listener lives only while active.
|
|
377
|
-
*/
|
|
378
|
-
#beforeCache = new BeforeCacheReset(() => this.deactivate({ restoreFocus: false }));
|
|
379
|
-
/**
|
|
380
|
-
* Handles `Tab` (focus trap) while active. `Escape` dismissal is owned by the
|
|
381
|
-
* shared {@link EscapeLayer} resolver, so Tab trapping stays independent of
|
|
382
|
-
* which layer currently owns Escape.
|
|
383
|
-
*/
|
|
384
|
-
#onKeydown = (event) => {
|
|
385
|
-
if (event.key === "Tab") this.#trapTab(event);
|
|
386
|
-
};
|
|
387
|
-
/** Keeps `Tab` focus cycling within the container's focusable elements. */
|
|
388
|
-
#trapTab(event) {
|
|
389
|
-
const focusable = this.#focusableElements();
|
|
390
|
-
if (focusable.length === 0) {
|
|
391
|
-
event.preventDefault();
|
|
392
|
-
return;
|
|
932
|
+
#joinStack() {
|
|
933
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
934
|
+
if (registry2.stack.length === 0) {
|
|
935
|
+
document.addEventListener("keydown", registry2.onKeydown);
|
|
936
|
+
document.addEventListener("turbo:morph", registry2.onMorph);
|
|
937
|
+
document.addEventListener("turbo:morph-element", registry2.onMorph);
|
|
393
938
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
939
|
+
registry2.stack.push(this.#entry);
|
|
940
|
+
}
|
|
941
|
+
#leaveStack() {
|
|
942
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
943
|
+
const index = registry2.stack.indexOf(this.#entry);
|
|
944
|
+
const above = registry2.stack.slice(index + 1);
|
|
945
|
+
registry2.stack.splice(index, 1);
|
|
946
|
+
if (registry2.stack.length === 0) {
|
|
947
|
+
document.removeEventListener("keydown", registry2.onKeydown);
|
|
948
|
+
document.removeEventListener("turbo:morph", registry2.onMorph);
|
|
949
|
+
document.removeEventListener("turbo:morph-element", registry2.onMorph);
|
|
401
950
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
951
|
+
return above;
|
|
952
|
+
}
|
|
953
|
+
static #focusPath() {
|
|
954
|
+
const path = [];
|
|
955
|
+
for (let active = document.activeElement; active; active = active.shadowRoot?.activeElement ?? null) {
|
|
956
|
+
if (active instanceof HTMLElement && active !== document.body) path.unshift(active);
|
|
957
|
+
}
|
|
958
|
+
return path;
|
|
959
|
+
}
|
|
960
|
+
static #trapHolding(element) {
|
|
961
|
+
for (const trap of [..._FocusTrap.#registry(document).stack].reverse()) {
|
|
962
|
+
const container = trap.container();
|
|
963
|
+
if (container && flatTreeContains(container, element)) return trap;
|
|
964
|
+
}
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
#handOver(above) {
|
|
968
|
+
const entry = this.#entry;
|
|
969
|
+
const wayBack = [
|
|
970
|
+
...this.#openers,
|
|
971
|
+
this.#options.fallbackFocus?.() ?? null,
|
|
972
|
+
...entry.handedBack
|
|
973
|
+
].filter((element) => element !== null);
|
|
974
|
+
for (const trap of above) {
|
|
975
|
+
if (trap.openedFrom !== entry) continue;
|
|
976
|
+
trap.handedBack = [...trap.handedBack, ...wayBack];
|
|
977
|
+
trap.openedFrom = entry.openedFrom;
|
|
978
|
+
}
|
|
979
|
+
this.#openers = [];
|
|
980
|
+
entry.openedFrom = null;
|
|
981
|
+
entry.handedBack = [];
|
|
982
|
+
return wayBack;
|
|
983
|
+
}
|
|
984
|
+
#restore(wayBack, above) {
|
|
985
|
+
const active = deepActiveElement(document);
|
|
986
|
+
const released = this.#readContainer();
|
|
987
|
+
const holds = (trap) => {
|
|
988
|
+
const container = trap.container();
|
|
989
|
+
if (!container || !_FocusTrap.#takesTab(trap) || !flatTreeContains(container, active)) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
return !released || !flatTreeContains(released, active) || flatTreeContains(released, container);
|
|
993
|
+
};
|
|
994
|
+
if (above.some(holds)) return;
|
|
995
|
+
for (const element of wayBack) {
|
|
996
|
+
if (this.#activeState) return;
|
|
997
|
+
if (!element.isConnected || !canTakeFocus(element) || !isRenderedForFocus(element)) continue;
|
|
998
|
+
const before = deepActiveElement(document);
|
|
999
|
+
element.focus();
|
|
1000
|
+
const after = deepActiveElement(document);
|
|
1001
|
+
if (after === element || after !== before) return;
|
|
1002
|
+
}
|
|
1003
|
+
_FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack)?.refocus();
|
|
1004
|
+
}
|
|
1005
|
+
#refocus() {
|
|
1006
|
+
if (!this.#flag(this.#options.autoFocus, true)) return;
|
|
1007
|
+
if (flatTreeContains(this.#getContainer(), deepActiveElement(document))) return;
|
|
1008
|
+
this.#focusInitial();
|
|
1009
|
+
}
|
|
1010
|
+
#lendTabindex(container) {
|
|
1011
|
+
const loan = this.#tabindexLoans.get(container) ?? new TabindexLoan("-1", FOCUS_TRAP_OWNER);
|
|
1012
|
+
loan.lend(container);
|
|
1013
|
+
this.#tabindexLoans.set(container, loan);
|
|
1014
|
+
}
|
|
1015
|
+
#returnTabindexes() {
|
|
1016
|
+
const focused = deepActiveElement(document);
|
|
1017
|
+
for (const [element, loan] of [...this.#tabindexLoans]) {
|
|
1018
|
+
if (element === focused) continue;
|
|
1019
|
+
loan.returnAll();
|
|
1020
|
+
this.#tabindexLoans.delete(element);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
#lockScroll() {
|
|
1024
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
1025
|
+
const body = document.body;
|
|
1026
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
1027
|
+
registry2.scrollLocks.add(this.#entry);
|
|
1028
|
+
this.#entry.lockedBody = body;
|
|
1029
|
+
body.style.overflow = "hidden";
|
|
1030
|
+
}
|
|
1031
|
+
#unlockScroll() {
|
|
1032
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
1033
|
+
const body = this.#entry.lockedBody;
|
|
1034
|
+
this.#entry.lockedBody = null;
|
|
1035
|
+
if (!registry2.scrollLocks.delete(this.#entry) || !body) return;
|
|
1036
|
+
if (!_FocusTrap.#isLocked(registry2, body)) _FocusTrap.#unlock(body);
|
|
1037
|
+
}
|
|
1038
|
+
static #isLocked(registry2, body) {
|
|
1039
|
+
return [...registry2.scrollLocks].some((trap) => trap.lockedBody === body);
|
|
1040
|
+
}
|
|
1041
|
+
static #unlock(body) {
|
|
1042
|
+
const saved = body.getAttribute(OVERFLOW_MARK);
|
|
1043
|
+
if (saved === null) return;
|
|
1044
|
+
body.removeAttribute(OVERFLOW_MARK);
|
|
1045
|
+
body.style.overflow = saved;
|
|
1046
|
+
}
|
|
1047
|
+
static #sweep() {
|
|
1048
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
1049
|
+
for (const element of queryOpenDescendants(document, `[${INERT_MARK}]`)) {
|
|
1050
|
+
if (registry2.inerted.has(element)) continue;
|
|
1051
|
+
element.removeAttribute(INERT_MARK);
|
|
1052
|
+
element.inert = false;
|
|
1053
|
+
}
|
|
1054
|
+
if (!_FocusTrap.#isLocked(registry2, document.body)) _FocusTrap.#unlock(document.body);
|
|
1055
|
+
}
|
|
1056
|
+
static #registry(ownerDocument) {
|
|
1057
|
+
const existing = _FocusTrap.#registries.get(ownerDocument);
|
|
1058
|
+
if (existing) return existing;
|
|
1059
|
+
const morphs = new MicrotaskCoalescer(() => _FocusTrap.#reapply(registry2));
|
|
1060
|
+
morphs.activate();
|
|
1061
|
+
const registry2 = {
|
|
1062
|
+
stack: [],
|
|
1063
|
+
onKeydown: (event) => _FocusTrap.#resolveTab(registry2.stack, event),
|
|
1064
|
+
onMorph: () => morphs.schedule(),
|
|
1065
|
+
inerted: /* @__PURE__ */ new Set(),
|
|
1066
|
+
scrollLocks: /* @__PURE__ */ new Set()
|
|
1067
|
+
};
|
|
1068
|
+
_FocusTrap.#registries.set(ownerDocument, registry2);
|
|
1069
|
+
return registry2;
|
|
1070
|
+
}
|
|
1071
|
+
static #reapply(registry2) {
|
|
1072
|
+
_FocusTrap.#reconcileBackground(document);
|
|
1073
|
+
const body = document.body;
|
|
1074
|
+
if (!_FocusTrap.#isLocked(registry2, body)) return;
|
|
1075
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
1076
|
+
body.style.overflow = "hidden";
|
|
1077
|
+
}
|
|
1078
|
+
static #reconcileBackground(ownerDocument) {
|
|
1079
|
+
const registry2 = _FocusTrap.#registry(ownerDocument);
|
|
1080
|
+
for (const element of registry2.inerted) {
|
|
1081
|
+
if (!element.hasAttribute(INERT_MARK)) registry2.inerted.delete(element);
|
|
1082
|
+
}
|
|
1083
|
+
const background = _FocusTrap.#backgroundOf(registry2);
|
|
1084
|
+
for (const element of registry2.inerted) {
|
|
1085
|
+
if (background.has(element)) continue;
|
|
1086
|
+
element.removeAttribute(INERT_MARK);
|
|
1087
|
+
element.inert = false;
|
|
1088
|
+
registry2.inerted.delete(element);
|
|
1089
|
+
}
|
|
1090
|
+
const added = [];
|
|
1091
|
+
for (const element of background) {
|
|
1092
|
+
if (registry2.inerted.has(element)) continue;
|
|
1093
|
+
element.setAttribute(INERT_MARK, "");
|
|
1094
|
+
element.inert = true;
|
|
1095
|
+
registry2.inerted.add(element);
|
|
1096
|
+
added.push(element);
|
|
1097
|
+
}
|
|
1098
|
+
return added;
|
|
1099
|
+
}
|
|
1100
|
+
static #backgroundOf(registry2) {
|
|
1101
|
+
const containers = registry2.stack.map((trap) => trap.container());
|
|
1102
|
+
let top = -1;
|
|
1103
|
+
registry2.stack.forEach((trap, index) => {
|
|
1104
|
+
if (trap.isolated && containers[index]) top = index;
|
|
1105
|
+
});
|
|
1106
|
+
const background = /* @__PURE__ */ new Set();
|
|
1107
|
+
if (top < 0) return background;
|
|
1108
|
+
const kept = /* @__PURE__ */ new Set();
|
|
1109
|
+
const keep = (path) => {
|
|
1110
|
+
for (const node of path) {
|
|
1111
|
+
kept.add(node);
|
|
1112
|
+
background.delete(node);
|
|
432
1113
|
}
|
|
433
|
-
node
|
|
1114
|
+
for (const node of path) {
|
|
1115
|
+
const parent = flatTreeParent(node);
|
|
1116
|
+
for (const sibling of parent ? flatTreeChildren(parent) : []) {
|
|
1117
|
+
if (!(sibling instanceof HTMLElement) || kept.has(sibling)) continue;
|
|
1118
|
+
if (sibling.inert && !registry2.inerted.has(sibling)) continue;
|
|
1119
|
+
background.add(sibling);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
containers.slice(top).forEach((container, index) => {
|
|
1124
|
+
const path = [];
|
|
1125
|
+
for (let node = container; node && node !== document.body && !kept.has(node); ) {
|
|
1126
|
+
path.push(node);
|
|
1127
|
+
node = flatTreeParent(node);
|
|
1128
|
+
}
|
|
1129
|
+
if (index === 0 || path.some((node) => background.has(node))) keep(path);
|
|
1130
|
+
});
|
|
1131
|
+
return background;
|
|
1132
|
+
}
|
|
1133
|
+
#readContainer() {
|
|
1134
|
+
try {
|
|
1135
|
+
return this.#getContainer();
|
|
1136
|
+
} catch {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
static #resolveTab(stack, event) {
|
|
1141
|
+
if (event.key !== "Tab" || event.defaultPrevented) return;
|
|
1142
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
1143
|
+
if (event.ctrlKey || event.metaKey) return;
|
|
1144
|
+
const owner = _FocusTrap.#tabOwner(stack);
|
|
1145
|
+
if (!owner) return;
|
|
1146
|
+
event.preventDefault();
|
|
1147
|
+
owner.moveFocus(event.shiftKey);
|
|
1148
|
+
}
|
|
1149
|
+
static #tabOwner(stack) {
|
|
1150
|
+
for (const trap of [...stack].reverse()) {
|
|
1151
|
+
if (_FocusTrap.#takesTab(trap)) return trap;
|
|
1152
|
+
}
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
static #takesTab(trap) {
|
|
1156
|
+
const container = trap.container();
|
|
1157
|
+
if (!container) return false;
|
|
1158
|
+
return container.isConnected && !closestInFlatTree(container, "[hidden]") && isRenderedForFocus(container);
|
|
1159
|
+
}
|
|
1160
|
+
#ownsTab() {
|
|
1161
|
+
return _FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack) === this.#entry;
|
|
1162
|
+
}
|
|
1163
|
+
#moveFocus(backward) {
|
|
1164
|
+
const from = deepActiveElement(document);
|
|
1165
|
+
for (const stop of sequentialTabStops(this.#getContainer(), from, backward)) {
|
|
1166
|
+
if (!this.#ownsTab()) return;
|
|
1167
|
+
const before = deepActiveElement(document);
|
|
1168
|
+
stop.focus();
|
|
1169
|
+
const after = deepActiveElement(document);
|
|
1170
|
+
if (after === before) continue;
|
|
1171
|
+
if (after === stop) _FocusTrap.#selectOnEntry(stop);
|
|
1172
|
+
return;
|
|
434
1173
|
}
|
|
435
1174
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
sibling.inert = false;
|
|
1175
|
+
static #selectOnEntry(stop) {
|
|
1176
|
+
if (stop instanceof HTMLInputElement && _FocusTrap.#TEXT_ENTRY_TYPES.has(stop.type)) {
|
|
1177
|
+
stop.select();
|
|
440
1178
|
}
|
|
441
|
-
this.#inertedSiblings = [];
|
|
442
1179
|
}
|
|
443
|
-
/** Moves focus to the initial target, the first focusable, or the container. */
|
|
444
1180
|
#focusInitial() {
|
|
445
1181
|
const preferred = this.#options.initialFocus?.();
|
|
446
1182
|
if (preferred) {
|
|
447
1183
|
preferred.focus();
|
|
448
1184
|
return;
|
|
449
1185
|
}
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
1186
|
+
const container = this.#getContainer();
|
|
1187
|
+
const [first] = sequentialTabStops(container);
|
|
1188
|
+
if (first) {
|
|
1189
|
+
first.focus();
|
|
453
1190
|
return;
|
|
454
1191
|
}
|
|
455
|
-
|
|
456
|
-
container.tabIndex = -1;
|
|
1192
|
+
this.#lendTabindex(container);
|
|
457
1193
|
container.focus();
|
|
458
1194
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
var ListenerSet = class {
|
|
1198
|
+
#abort = new AbortController();
|
|
1199
|
+
add(target, type, handler, options) {
|
|
1200
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
1201
|
+
}
|
|
1202
|
+
dispose() {
|
|
1203
|
+
this.#abort.abort();
|
|
1204
|
+
this.#abort = new AbortController();
|
|
462
1205
|
}
|
|
463
1206
|
};
|
|
464
1207
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
#
|
|
468
|
-
#
|
|
469
|
-
#
|
|
470
|
-
#generation = 0;
|
|
471
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
1208
|
+
var MorphRenderWatcher = class {
|
|
1209
|
+
#listeners = new ListenerSet();
|
|
1210
|
+
#pass;
|
|
1211
|
+
#element = null;
|
|
1212
|
+
#onMorph = () => this.#pass.schedule();
|
|
472
1213
|
constructor(run) {
|
|
473
|
-
this.#
|
|
1214
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
474
1215
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
this
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
this.#active = false;
|
|
482
|
-
this.#queued = false;
|
|
483
|
-
this.#generation += 1;
|
|
1216
|
+
observe(element) {
|
|
1217
|
+
if (this.#element === element) return;
|
|
1218
|
+
this.disconnect();
|
|
1219
|
+
this.#element = element;
|
|
1220
|
+
this.#pass.activate();
|
|
1221
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
484
1222
|
}
|
|
485
|
-
/** Requests one pass after the batch settles. Idempotent; inert outside the window. */
|
|
486
1223
|
schedule() {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
1224
|
+
this.#pass.schedule();
|
|
1225
|
+
}
|
|
1226
|
+
get pending() {
|
|
1227
|
+
return this.#pass.pending;
|
|
1228
|
+
}
|
|
1229
|
+
disconnect() {
|
|
1230
|
+
this.#listeners.dispose();
|
|
1231
|
+
this.#pass.cancel();
|
|
1232
|
+
this.#element = null;
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
var LivedMark = class {
|
|
1237
|
+
#name;
|
|
1238
|
+
#lived = false;
|
|
1239
|
+
#element = null;
|
|
1240
|
+
#morph = new MorphRenderWatcher(() => this.#write());
|
|
1241
|
+
constructor(identifier) {
|
|
1242
|
+
this.#name = `data-${identifier}-lived`;
|
|
1243
|
+
}
|
|
1244
|
+
connect(element) {
|
|
1245
|
+
const connection = this.#lived ? "reconnect" : element.hasAttribute(this.#name) ? "restored" : "fresh";
|
|
1246
|
+
this.#lived = true;
|
|
1247
|
+
this.#element = element;
|
|
1248
|
+
this.#write();
|
|
1249
|
+
this.#morph.observe(element);
|
|
1250
|
+
return connection;
|
|
1251
|
+
}
|
|
1252
|
+
disconnect() {
|
|
1253
|
+
this.#morph.disconnect();
|
|
1254
|
+
}
|
|
1255
|
+
#write() {
|
|
1256
|
+
this.#element?.setAttribute(this.#name, "");
|
|
495
1257
|
}
|
|
496
1258
|
};
|
|
497
1259
|
|
|
498
|
-
|
|
1260
|
+
function targetSelector(identifier, name) {
|
|
1261
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
499
1264
|
var CommandPaletteController = class _CommandPaletteController extends Controller {
|
|
500
1265
|
static #ORIGINAL_ARIA_DISABLED = "data-command-palette-original-aria-disabled";
|
|
501
1266
|
static #ABSENT_ARIA_DISABLED = "absent";
|
|
@@ -510,85 +1275,104 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
510
1275
|
"filter",
|
|
511
1276
|
"onKeydown",
|
|
512
1277
|
"open",
|
|
513
|
-
"
|
|
1278
|
+
"select",
|
|
514
1279
|
"toggle"
|
|
515
1280
|
];
|
|
516
1281
|
static events = ["select"];
|
|
517
|
-
/** Stable ID of the active option; the live target is resolved before every use. */
|
|
518
1282
|
#activeId = null;
|
|
519
|
-
/** Visible/selectable target ID order captured for removal fallback. */
|
|
520
1283
|
#activeOrder = [];
|
|
521
1284
|
#connected = false;
|
|
522
|
-
|
|
523
|
-
#
|
|
524
|
-
|
|
1285
|
+
#lived = new LivedMark(this.identifier);
|
|
1286
|
+
#dialog = null;
|
|
1287
|
+
#hidden = new AttributeLease("hidden", this.identifier);
|
|
1288
|
+
#expanded = new AttributeLease("aria-expanded", this.identifier);
|
|
1289
|
+
#activeDescendant = new AttributeLease(
|
|
1290
|
+
"aria-activedescendant",
|
|
1291
|
+
this.identifier
|
|
1292
|
+
);
|
|
1293
|
+
#reconcile = new MicrotaskCoalescer(() => {
|
|
1294
|
+
if (this.hasInputTarget) this.#expanded.write(this.inputTarget, String(this.#isOpen));
|
|
1295
|
+
this.#reconcileActive();
|
|
1296
|
+
});
|
|
1297
|
+
#morphRender = new MorphRenderWatcher(() => this.#repair());
|
|
525
1298
|
#composition = new CompositionTracker({
|
|
526
1299
|
onEnd: () => {
|
|
527
1300
|
if (this.#isOpen) this.filter();
|
|
528
1301
|
}
|
|
529
1302
|
});
|
|
530
|
-
/**
|
|
531
|
-
* Owns the modal side effects (focus trap, scroll lock, background `inert`, focus
|
|
532
|
-
* restore). Escape closes; focus on open goes to the input, and is restored to
|
|
533
|
-
* whatever opened the palette on close.
|
|
534
|
-
*/
|
|
535
1303
|
#trap = new FocusTrap(() => this.dialogTarget, {
|
|
536
1304
|
onEscape: () => this.close(),
|
|
537
|
-
initialFocus: () =>
|
|
1305
|
+
initialFocus: () => {
|
|
1306
|
+
const dialog = this.dialogTarget;
|
|
1307
|
+
return this.inputTargets.find((input) => dialog.contains(input)) ?? null;
|
|
1308
|
+
}
|
|
538
1309
|
});
|
|
539
|
-
|
|
540
|
-
* Initializes the global hotkey handler and establishes the initial open state.
|
|
541
|
-
*
|
|
542
|
-
* The DOM is the source of truth on reconnect (Turbo cache restore / morph): if
|
|
543
|
-
* the restored snapshot already shows the dialog open, honor that rather than
|
|
544
|
-
* re-deriving from the declarative `open` Value (which would slam a user-opened
|
|
545
|
-
* palette shut). The `open` Value only seeds the initial state of a genuinely
|
|
546
|
-
* fresh render. We normalize to a clean closed baseline first so
|
|
547
|
-
* {@link CommandPaletteController.open | open} runs its full setup — the
|
|
548
|
-
* `FocusTrap` is a fresh instance after a reconnect and must be re-activated.
|
|
549
|
-
*/
|
|
1310
|
+
#listeners = new ListenerSet();
|
|
550
1311
|
connect() {
|
|
551
|
-
|
|
1312
|
+
const connection = this.#lived.connect(this.element);
|
|
1313
|
+
this.#trap.connect();
|
|
1314
|
+
this.#listeners.add(document, "keydown", this.#onGlobalKeydown);
|
|
552
1315
|
if (this.hasInputTarget) this.#composition.observe(this.inputTarget);
|
|
553
1316
|
this.#syncOptionSemantics();
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
1317
|
+
if (connection === "reconnect" && this.#isOpen) {
|
|
1318
|
+
this.#trap.activate();
|
|
1319
|
+
} else {
|
|
1320
|
+
const shouldOpen = connection !== "restored" && (this.#isOpen || this.openValue);
|
|
1321
|
+
this.#resetToClosedState();
|
|
1322
|
+
if (shouldOpen) this.open();
|
|
1323
|
+
}
|
|
1324
|
+
this.#dialog = this.hasDialogTarget ? this.dialogTarget : null;
|
|
557
1325
|
this.#connected = true;
|
|
558
1326
|
this.#reconcile.activate();
|
|
1327
|
+
this.#morphRender.observe(this.element);
|
|
559
1328
|
}
|
|
560
|
-
/** Tears down the global hotkey listener and reverts the modal side effects. */
|
|
561
1329
|
disconnect() {
|
|
562
1330
|
this.#connected = false;
|
|
1331
|
+
this.#lived.disconnect();
|
|
1332
|
+
this.#morphRender.disconnect();
|
|
563
1333
|
this.#reconcile.cancel();
|
|
564
|
-
|
|
1334
|
+
this.#listeners.dispose();
|
|
565
1335
|
this.#composition.disconnect();
|
|
566
|
-
this.#trap.
|
|
567
|
-
this.#resetToClosedState();
|
|
1336
|
+
this.#trap.disconnect(this);
|
|
568
1337
|
}
|
|
569
|
-
/** Initializes semantics for an option added before or after the controller connects. */
|
|
570
1338
|
optionTargetConnected(option) {
|
|
571
1339
|
this.#syncOptionSemanticsFor(option);
|
|
572
1340
|
option.setAttribute("aria-selected", "false");
|
|
573
1341
|
option.removeAttribute("data-active");
|
|
574
1342
|
if (this.#connected) this.#queueOptionReconciliation();
|
|
575
1343
|
}
|
|
576
|
-
/** Clears active residue on the old node and reconciles the surviving targets. */
|
|
577
1344
|
optionTargetDisconnected(option) {
|
|
578
1345
|
option.setAttribute("aria-selected", "false");
|
|
579
1346
|
option.removeAttribute("data-active");
|
|
580
1347
|
if (this.#connected) this.#queueOptionReconciliation();
|
|
581
1348
|
}
|
|
582
|
-
/** Tracks an input added initially or after connect without extra consumer actions. */
|
|
583
1349
|
inputTargetConnected(input) {
|
|
584
1350
|
this.#composition.observe(input);
|
|
585
1351
|
if (this.#connected) this.#queueOptionReconciliation();
|
|
586
1352
|
}
|
|
587
|
-
/** Removes controller-owned listeners when the input target is replaced or removed. */
|
|
588
1353
|
inputTargetDisconnected(input) {
|
|
589
1354
|
this.#composition.unobserve(input);
|
|
1355
|
+
if (!this.inputTargets.includes(input)) {
|
|
1356
|
+
this.#expanded.return(input);
|
|
1357
|
+
this.#activeDescendant.return(input);
|
|
1358
|
+
}
|
|
1359
|
+
this.#queueOptionReconciliation();
|
|
1360
|
+
}
|
|
1361
|
+
dialogTargetConnected() {
|
|
1362
|
+
if (this.#connected) this.#adoptDialog();
|
|
1363
|
+
}
|
|
1364
|
+
dialogTargetDisconnected(dialog) {
|
|
1365
|
+
if (this.dialogTargets.includes(dialog)) return;
|
|
1366
|
+
this.#hidden.return(dialog);
|
|
1367
|
+
if (this.#connected) this.#adoptDialog();
|
|
1368
|
+
}
|
|
1369
|
+
emptyTargetConnected() {
|
|
1370
|
+
this.#queueOptionReconciliation();
|
|
1371
|
+
}
|
|
1372
|
+
emptyTargetDisconnected(empty) {
|
|
1373
|
+
if (!this.emptyTargets.includes(empty)) this.#hidden.return(empty);
|
|
1374
|
+
this.#queueOptionReconciliation();
|
|
590
1375
|
}
|
|
591
|
-
/** Toggles the open state of the command palette. */
|
|
592
1376
|
toggle() {
|
|
593
1377
|
if (this.#isOpen) {
|
|
594
1378
|
this.close();
|
|
@@ -596,25 +1380,43 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
596
1380
|
this.open();
|
|
597
1381
|
}
|
|
598
1382
|
}
|
|
599
|
-
/** Opens the palette, traps focus, and shifts focus to the input. */
|
|
600
1383
|
open() {
|
|
601
1384
|
if (!this.hasDialogTarget || this.#isOpen) return;
|
|
602
|
-
this.dialogTarget
|
|
1385
|
+
this.#hidden.write(this.dialogTarget, null);
|
|
603
1386
|
this.openValue = true;
|
|
604
|
-
if (this.hasInputTarget) this.inputTarget
|
|
1387
|
+
if (this.hasInputTarget) this.#expanded.write(this.inputTarget, "true");
|
|
605
1388
|
this.#resetFilter();
|
|
606
1389
|
this.#trap.activate();
|
|
607
1390
|
}
|
|
608
|
-
/** Closes the palette and restores focus back to the opener. */
|
|
609
1391
|
close() {
|
|
1392
|
+
this.#close(true);
|
|
1393
|
+
}
|
|
1394
|
+
#adoptDialog() {
|
|
1395
|
+
const dialog = this.hasDialogTarget ? this.dialogTarget : null;
|
|
1396
|
+
if (dialog === this.#dialog) return;
|
|
1397
|
+
this.#dialog = dialog;
|
|
1398
|
+
if (!this.#trap.active) {
|
|
1399
|
+
if (dialog) this.#hidden.write(dialog, "");
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (!dialog) {
|
|
1403
|
+
this.#resetToClosedState();
|
|
1404
|
+
this.#trap.deactivate();
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
this.#hidden.write(dialog, null);
|
|
1408
|
+
this.#trap.refreshContainer();
|
|
1409
|
+
}
|
|
1410
|
+
#close(restoreFocus) {
|
|
610
1411
|
if (!this.hasDialogTarget || !this.#isOpen) return;
|
|
611
1412
|
this.#resetToClosedState();
|
|
612
|
-
this.#trap.deactivate();
|
|
1413
|
+
this.#trap.deactivate({ restoreFocus });
|
|
613
1414
|
}
|
|
614
|
-
/** Filters option elements in-memory matching the input value. Bound to input target. */
|
|
615
1415
|
filter(event) {
|
|
616
1416
|
if (!this.hasInputTarget || this.#composition.isComposing(event)) return;
|
|
617
|
-
this.#
|
|
1417
|
+
this.#setActiveIndex(this.#applyQuery() ? 0 : -1);
|
|
1418
|
+
}
|
|
1419
|
+
#applyQuery() {
|
|
618
1420
|
const query = this.inputTarget.value.trim().toLowerCase();
|
|
619
1421
|
let hasSelectableMatch = false;
|
|
620
1422
|
for (const option of this.optionTargets) {
|
|
@@ -628,34 +1430,39 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
628
1430
|
}
|
|
629
1431
|
}
|
|
630
1432
|
if (this.hasEmptyTarget) {
|
|
631
|
-
this.emptyTarget
|
|
1433
|
+
this.#hidden.write(this.emptyTarget, hasSelectableMatch ? "" : null);
|
|
632
1434
|
}
|
|
633
|
-
|
|
1435
|
+
return hasSelectableMatch;
|
|
634
1436
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
1437
|
+
#repair() {
|
|
1438
|
+
if (!this.hasDialogTarget) return;
|
|
1439
|
+
const open = this.#trap.active;
|
|
1440
|
+
this.#hidden.write(this.dialogTarget, open ? null : "");
|
|
1441
|
+
if (open && this.hasInputTarget) this.#applyQuery();
|
|
1442
|
+
this.#reconcile.schedule();
|
|
1443
|
+
}
|
|
1444
|
+
select(source) {
|
|
1445
|
+
const { event, host, origin, reason } = actionSource(source);
|
|
1446
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
1447
|
+
return;
|
|
1448
|
+
const option = host?.closest(targetSelector(this.identifier, "option"));
|
|
640
1449
|
if (!option || !this.optionTargets.includes(option)) return;
|
|
641
1450
|
this.#syncOptionSemanticsFor(option);
|
|
642
1451
|
if (!option.hasAttribute("hidden") && !this.#isDisabled(option)) {
|
|
643
|
-
this.#confirmSelection(
|
|
1452
|
+
this.#confirmSelection(
|
|
1453
|
+
option,
|
|
1454
|
+
reason,
|
|
1455
|
+
event !== null || this.element.contains(document.activeElement)
|
|
1456
|
+
);
|
|
644
1457
|
}
|
|
645
1458
|
}
|
|
646
|
-
/** Closes when the backdrop (the dialog target itself) is clicked, ignoring inner clicks. */
|
|
647
1459
|
closeOnBackdrop(event) {
|
|
648
1460
|
if (event.target === this.dialogTarget) this.close();
|
|
649
1461
|
}
|
|
650
|
-
/**
|
|
651
|
-
* Combobox navigation keys (arrows / Home / End / Enter), bound to the input.
|
|
652
|
-
* `Tab` (focus trap) and `Escape` (close) are owned by the shared `FocusTrap` at
|
|
653
|
-
* the document level, so they work no matter which element inside the dialog has
|
|
654
|
-
* focus — not only the input.
|
|
655
|
-
*/
|
|
656
1462
|
onKeydown(event) {
|
|
657
1463
|
if (event.defaultPrevented) return;
|
|
658
1464
|
if (isReservedArrowChord(event)) return;
|
|
1465
|
+
if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
|
|
659
1466
|
if (this.#composition.isComposing(event) || !this.#isOpen) return;
|
|
660
1467
|
this.#reconcileActive();
|
|
661
1468
|
switch (event.key) {
|
|
@@ -708,14 +1515,9 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
708
1515
|
this.#activeId = activeOption?.id || null;
|
|
709
1516
|
this.#activeOrder = activeOption ? visible.map((option) => option.id).filter(Boolean) : [];
|
|
710
1517
|
if (this.hasInputTarget) {
|
|
711
|
-
|
|
712
|
-
this.inputTarget.setAttribute("aria-activedescendant", activeOption.id);
|
|
713
|
-
} else {
|
|
714
|
-
this.inputTarget.removeAttribute("aria-activedescendant");
|
|
715
|
-
}
|
|
1518
|
+
this.#activeDescendant.write(this.inputTarget, activeOption?.id || null);
|
|
716
1519
|
}
|
|
717
1520
|
}
|
|
718
|
-
/** Re-resolves active identity and component-specific fallback against live targets. */
|
|
719
1521
|
#reconcileActive() {
|
|
720
1522
|
if (!this.#isOpen) {
|
|
721
1523
|
this.#setActiveIndex(-1);
|
|
@@ -742,7 +1544,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
742
1544
|
this.#setActiveIndex(activeId ? this.#findFallbackIndex(visible, activeId) : -1);
|
|
743
1545
|
this.#reflectEmptyState();
|
|
744
1546
|
}
|
|
745
|
-
/** Resolves stable ID first, then active markers for the existing ID-less case. */
|
|
746
1547
|
#findActiveIndex(options) {
|
|
747
1548
|
const activeId = (this.hasInputTarget ? this.inputTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
|
|
748
1549
|
if (activeId) return options.findIndex((option) => option.id === activeId);
|
|
@@ -750,7 +1551,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
750
1551
|
(option) => option.hasAttribute("data-active") || option.getAttribute("aria-selected") === "true"
|
|
751
1552
|
);
|
|
752
1553
|
}
|
|
753
|
-
/** Chooses a surviving former successor, then predecessor, from selectable targets only. */
|
|
754
1554
|
#findFallbackIndex(options, activeId) {
|
|
755
1555
|
const oldIndex = this.#activeOrder.indexOf(activeId);
|
|
756
1556
|
if (oldIndex < 0) return -1;
|
|
@@ -765,28 +1565,21 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
765
1565
|
}
|
|
766
1566
|
return -1;
|
|
767
1567
|
}
|
|
768
|
-
/** Reflects whether any visible, navigable command remains. */
|
|
769
1568
|
#reflectEmptyState() {
|
|
770
|
-
if (this.hasEmptyTarget)
|
|
1569
|
+
if (this.hasEmptyTarget) {
|
|
1570
|
+
this.#hidden.write(this.emptyTarget, this.#visibleOptions.length > 0 ? "" : null);
|
|
1571
|
+
}
|
|
771
1572
|
}
|
|
772
|
-
/** Coalesces all target callbacks from one MutationObserver batch. */
|
|
773
1573
|
#queueOptionReconciliation() {
|
|
774
1574
|
this.#reconcile.schedule();
|
|
775
1575
|
}
|
|
776
|
-
|
|
777
|
-
* Runs `option`: dispatches `select` and closes.
|
|
778
|
-
*
|
|
779
|
-
* **Activation is refused here, not at the call sites.** Every path that runs a
|
|
780
|
-
* command funnels through this method, so one check covers keyboard, pointer,
|
|
781
|
-
* and anything added later. Per-call-site guards would leave `aria-disabled`
|
|
782
|
-
* options — which stay within the keyboard's reach — runnable from whichever
|
|
783
|
-
* path forgets to consult the predicate.
|
|
784
|
-
*/
|
|
785
|
-
#confirmSelection(option) {
|
|
1576
|
+
#confirmSelection(option, reason = "user", restoreFocus = true) {
|
|
786
1577
|
if (this.#isDisabled(option)) return;
|
|
787
1578
|
const value = option.dataset.value || option.textContent || "";
|
|
788
|
-
this.dispatch("select", { detail: { value, option } });
|
|
789
|
-
this
|
|
1579
|
+
this.dispatch("select", { detail: { value, option, reason } });
|
|
1580
|
+
this.#close(
|
|
1581
|
+
restoreFocus && (reason !== "api" || this.element.contains(document.activeElement))
|
|
1582
|
+
);
|
|
790
1583
|
}
|
|
791
1584
|
#resetFilter() {
|
|
792
1585
|
if (this.hasInputTarget) this.inputTarget.value = "";
|
|
@@ -802,43 +1595,15 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
802
1595
|
(option) => !option.hasAttribute("hidden") && this.#isNavigable(option)
|
|
803
1596
|
);
|
|
804
1597
|
}
|
|
805
|
-
/**
|
|
806
|
-
* Whether virtual focus may land on `option`.
|
|
807
|
-
*
|
|
808
|
-
* Only `data-disabled` disqualifies. That attribute is this controller's own —
|
|
809
|
-
* authors reach for it to mark a row that is not a destination at all, such as
|
|
810
|
-
* a group heading — so it can mean "skip entirely" without contradicting ARIA.
|
|
811
|
-
*
|
|
812
|
-
* **`aria-disabled` does not disqualify.** It marks a command that must stay
|
|
813
|
-
* *discoverable*: `aria-activedescendant` names it, so a reader announces it as
|
|
814
|
-
* unavailable rather than hiding its existence. Virtual focus does
|
|
815
|
-
* not change that — pointing `aria-activedescendant` at a disabled option is
|
|
816
|
-
* ordinary ARIA. Keeping the two attributes distinct is what leaves both
|
|
817
|
-
* meanings expressible; collapsing them would take "show it, say it is
|
|
818
|
-
* unavailable, do not run it" away from consumers.
|
|
819
|
-
*/
|
|
820
1598
|
#isNavigable(option) {
|
|
821
1599
|
return option.dataset.disabled !== "true";
|
|
822
1600
|
}
|
|
823
|
-
/** Whether activating `option` may run. Suppressed for either disabled marker. */
|
|
824
1601
|
#isDisabled(option) {
|
|
825
1602
|
return option.dataset.disabled === "true" || option.getAttribute("aria-disabled") === "true";
|
|
826
1603
|
}
|
|
827
|
-
/** Synchronizes every option's controller-owned selection and disabled semantics. */
|
|
828
1604
|
#syncOptionSemantics() {
|
|
829
1605
|
for (const option of this.optionTargets) this.#syncOptionSemanticsFor(option);
|
|
830
1606
|
}
|
|
831
|
-
/**
|
|
832
|
-
* Reflects `data-disabled` to ARIA without losing a pre-existing authored value,
|
|
833
|
-
* and puts `aria-selected` back to its baseline.
|
|
834
|
-
*
|
|
835
|
-
* Here `aria-selected` marks the *active* option, not a committed choice — the
|
|
836
|
-
* palette runs a command and keeps nothing selected — so an authored value is
|
|
837
|
-
* meaningless and is overwritten rather than preserved. Every caller either
|
|
838
|
-
* establishes the active option straight after (`filter`, `#setActiveIndex`) or
|
|
839
|
-
* is handling an option that cannot be the active one yet
|
|
840
|
-
* (`optionTargetConnected`).
|
|
841
|
-
*/
|
|
842
1607
|
#syncOptionSemanticsFor(option) {
|
|
843
1608
|
const originalAttribute = _CommandPaletteController.#ORIGINAL_ARIA_DISABLED;
|
|
844
1609
|
const originalValue = option.getAttribute(originalAttribute);
|
|
@@ -868,7 +1633,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
868
1633
|
event.preventDefault();
|
|
869
1634
|
this.toggle();
|
|
870
1635
|
};
|
|
871
|
-
/** Matches the configured `mod+key` or bare-key hotkey without extra modifiers. */
|
|
872
1636
|
#matchesHotkey(event) {
|
|
873
1637
|
const hotkey = this.#parseHotkey();
|
|
874
1638
|
if (!hotkey || event.altKey || event.shiftKey) return false;
|
|
@@ -880,7 +1644,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
880
1644
|
}
|
|
881
1645
|
return event.key.toLowerCase() === hotkey.key;
|
|
882
1646
|
}
|
|
883
|
-
/** Parses the intentionally small public hotkey grammar. */
|
|
884
1647
|
#parseHotkey() {
|
|
885
1648
|
const parts = this.hotkeyValue.toLowerCase().split("+");
|
|
886
1649
|
if (parts.length === 1 && parts[0] && parts[0] !== "mod") {
|
|
@@ -891,7 +1654,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
891
1654
|
}
|
|
892
1655
|
return null;
|
|
893
1656
|
}
|
|
894
|
-
/** Resets transient open state so reconnect starts from a predictable closed snapshot. */
|
|
895
1657
|
#resetToClosedState() {
|
|
896
1658
|
this.#activeId = null;
|
|
897
1659
|
this.#activeOrder = [];
|
|
@@ -901,15 +1663,14 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
|
|
|
901
1663
|
option.removeAttribute("data-active");
|
|
902
1664
|
}
|
|
903
1665
|
if (this.hasDialogTarget) {
|
|
904
|
-
this.dialogTarget
|
|
1666
|
+
this.#hidden.write(this.dialogTarget, "");
|
|
905
1667
|
}
|
|
906
1668
|
if (this.hasInputTarget) {
|
|
907
|
-
this.inputTarget
|
|
908
|
-
this.inputTarget
|
|
1669
|
+
this.#expanded.write(this.inputTarget, "false");
|
|
1670
|
+
this.#activeDescendant.write(this.inputTarget, null);
|
|
909
1671
|
}
|
|
910
1672
|
}
|
|
911
1673
|
};
|
|
912
1674
|
|
|
913
1675
|
export { CommandPaletteController };
|
|
914
|
-
//# sourceMappingURL=command_palette_controller.js.map
|
|
915
1676
|
//# sourceMappingURL=command_palette_controller.js.map
|