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,49 +1,58 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/** Every subscribed instance, iterated by the one shared document listener. */
|
|
8
|
-
static #subscribers = /* @__PURE__ */ new Set();
|
|
9
|
-
/** The shared listener; installed while at least one instance is subscribed. */
|
|
10
|
-
static #onBeforeCache = () => {
|
|
11
|
-
for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
|
|
12
|
-
};
|
|
13
|
-
#rewind;
|
|
14
|
-
/** @param rewind - the pass that returns this controller's state to its initial form. */
|
|
15
|
-
constructor(rewind) {
|
|
16
|
-
this.#rewind = rewind;
|
|
3
|
+
var DetachGate = class _DetachGate {
|
|
4
|
+
#pending = false;
|
|
5
|
+
get pending() {
|
|
6
|
+
return this.#pending;
|
|
17
7
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
static isDetached(host) {
|
|
9
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
10
|
+
}
|
|
11
|
+
disconnected(host, teardown) {
|
|
12
|
+
const carried = _DetachGate.#carried(host);
|
|
13
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
14
|
+
this.#pending = false;
|
|
15
|
+
teardown();
|
|
16
|
+
return;
|
|
24
17
|
}
|
|
18
|
+
this.#pending = true;
|
|
19
|
+
const probe = () => {
|
|
20
|
+
if (!this.#pending) return;
|
|
21
|
+
this.#pending = false;
|
|
22
|
+
teardown();
|
|
23
|
+
};
|
|
24
|
+
if (carried) setTimeout(probe, 0);
|
|
25
|
+
else queueMicrotask(probe);
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
static #carried(host) {
|
|
28
|
+
const element = host.element;
|
|
29
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
30
|
+
}
|
|
31
|
+
static #listed(host) {
|
|
32
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
33
|
+
return tokens.includes(host.identifier);
|
|
34
|
+
}
|
|
35
|
+
cancel() {
|
|
36
|
+
this.#pending = false;
|
|
31
37
|
}
|
|
32
38
|
};
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
function sharedRegistry(key, create) {
|
|
41
|
+
const symbol = Symbol.for(key);
|
|
42
|
+
const scope = globalThis;
|
|
43
|
+
const existing = scope[symbol];
|
|
44
|
+
if (existing !== void 0) return existing;
|
|
45
|
+
const registry = create();
|
|
46
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
47
|
+
return registry;
|
|
48
|
+
}
|
|
49
|
+
|
|
35
50
|
var EscapeLayer = class _EscapeLayer {
|
|
36
|
-
static #registries =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
#claims = null;
|
|
42
|
-
/**
|
|
43
|
-
* Activates this layer at the top of its document's Escape stack, installing
|
|
44
|
-
* the document's shared resolver listener if this is its first layer.
|
|
45
|
-
* Re-activating an already-active layer moves it to the top.
|
|
46
|
-
*/
|
|
51
|
+
static #registries = sharedRegistry(
|
|
52
|
+
"stimeo-ui.escape-layer.registry.v1",
|
|
53
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
54
|
+
);
|
|
55
|
+
#active = null;
|
|
47
56
|
activate(ownerDocument = document, options) {
|
|
48
57
|
this.deactivate();
|
|
49
58
|
let registry = _EscapeLayer.#registries.get(ownerDocument);
|
|
@@ -52,44 +61,31 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
52
61
|
_EscapeLayer.#registries.set(ownerDocument, registry);
|
|
53
62
|
ownerDocument.addEventListener("keydown", registry.onKeydown);
|
|
54
63
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.#
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Removes this layer from its document's Escape stack, uninstalling the
|
|
62
|
-
* shared listener when the stack empties. Safe to call when inactive.
|
|
63
|
-
*/
|
|
64
|
+
const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
|
|
65
|
+
registry.stack.push(entry);
|
|
66
|
+
this.#active = { document: ownerDocument, entry };
|
|
67
|
+
}
|
|
64
68
|
deactivate() {
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
67
|
-
const registry = _EscapeLayer.#registries.get(
|
|
69
|
+
const active = this.#active;
|
|
70
|
+
if (!active) return;
|
|
71
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
68
72
|
if (registry) {
|
|
69
|
-
const index = registry.stack.lastIndexOf(
|
|
73
|
+
const index = registry.stack.lastIndexOf(active.entry);
|
|
70
74
|
if (index >= 0) registry.stack.splice(index, 1);
|
|
71
75
|
if (registry.stack.length === 0) {
|
|
72
|
-
|
|
73
|
-
_EscapeLayer.#registries.delete(
|
|
76
|
+
active.document.removeEventListener("keydown", registry.onKeydown);
|
|
77
|
+
_EscapeLayer.#registries.delete(active.document);
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
|
-
this.#
|
|
77
|
-
|
|
78
|
-
this.#claims = null;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Whether this active layer would own a press right now: it is the topmost
|
|
82
|
-
* layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
|
|
83
|
-
* and diagnostics — production dismissal goes through the shared listener.
|
|
84
|
-
*/
|
|
80
|
+
this.#active = null;
|
|
81
|
+
}
|
|
85
82
|
get ownsEscape() {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
const registry = _EscapeLayer.#registries.get(
|
|
83
|
+
const active = this.#active;
|
|
84
|
+
if (!active) return false;
|
|
85
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
89
86
|
if (!registry) return false;
|
|
90
|
-
return _EscapeLayer.#resolveOwner(registry.stack) ===
|
|
87
|
+
return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
|
|
91
88
|
}
|
|
92
|
-
/** Builds a document's registry with its shared resolver listener. */
|
|
93
89
|
static #createRegistry() {
|
|
94
90
|
const registry = {
|
|
95
91
|
stack: [],
|
|
@@ -98,24 +94,22 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
98
94
|
const owner = _EscapeLayer.#resolveOwner(registry.stack);
|
|
99
95
|
if (!owner) return;
|
|
100
96
|
event.preventDefault();
|
|
101
|
-
owner
|
|
97
|
+
owner.onDismiss();
|
|
102
98
|
}
|
|
103
99
|
};
|
|
104
100
|
return registry;
|
|
105
101
|
}
|
|
106
|
-
/** The topmost stack layer whose claims predicate passes, or `null`. */
|
|
107
102
|
static #resolveOwner(stack) {
|
|
108
103
|
for (let index = stack.length - 1; index >= 0; index--) {
|
|
109
104
|
const layer = stack[index];
|
|
110
105
|
if (!layer) continue;
|
|
111
|
-
if (layer
|
|
106
|
+
if (layer.claims && !layer.claims()) continue;
|
|
112
107
|
return layer;
|
|
113
108
|
}
|
|
114
109
|
return null;
|
|
115
110
|
}
|
|
116
111
|
};
|
|
117
112
|
|
|
118
|
-
// src/utils/focus_candidate.ts
|
|
119
113
|
function inheritsFieldsetDisabled(control) {
|
|
120
114
|
let fieldset = control.closest("fieldset[disabled]");
|
|
121
115
|
while (fieldset) {
|
|
@@ -125,8 +119,34 @@ function inheritsFieldsetDisabled(control) {
|
|
|
125
119
|
}
|
|
126
120
|
return false;
|
|
127
121
|
}
|
|
122
|
+
function unslotted(element) {
|
|
123
|
+
return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
|
|
124
|
+
}
|
|
125
|
+
function flatTreeParent(element) {
|
|
126
|
+
if (unslotted(element)) return null;
|
|
127
|
+
const parent = element.assignedSlot ?? element.parentNode;
|
|
128
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
129
|
+
return parent instanceof Element ? parent : null;
|
|
130
|
+
}
|
|
131
|
+
function flatTreeContains(ancestor, element) {
|
|
132
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
133
|
+
if (node === ancestor) return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
function closestInFlatTree(element, selector) {
|
|
138
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
139
|
+
if (node.matches(selector)) return node;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
function flatTreeChildren(element) {
|
|
144
|
+
return ownedScope(element) ?? Array.from(element.children);
|
|
145
|
+
}
|
|
128
146
|
function canTakeFocus(element) {
|
|
129
|
-
|
|
147
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
148
|
+
if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
|
|
149
|
+
}
|
|
130
150
|
if (element instanceof HTMLInputElement && element.type === "hidden") return false;
|
|
131
151
|
if (!("disabled" in element)) return true;
|
|
132
152
|
if (element.disabled) return false;
|
|
@@ -183,189 +203,637 @@ function isTabStop(element) {
|
|
|
183
203
|
if (tabindex !== null) return tabindex >= 0;
|
|
184
204
|
return hasNativeTabStop(element) || hasEditableTabStop(element);
|
|
185
205
|
}
|
|
186
|
-
function
|
|
187
|
-
return
|
|
188
|
-
|
|
206
|
+
function isRadio(element) {
|
|
207
|
+
return element instanceof HTMLInputElement && element.type === "radio";
|
|
208
|
+
}
|
|
209
|
+
function sameRadioGroup(a, b) {
|
|
210
|
+
return a.name !== "" && a.name === b.name && a.form === b.form && a.getRootNode() === b.getRootNode();
|
|
211
|
+
}
|
|
212
|
+
function isInEditableContent(element) {
|
|
213
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
214
|
+
if (node.getAttribute("contenteditable")?.toLowerCase() === "false") return false;
|
|
215
|
+
if (hasEditableTabStop(node)) return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
var XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
|
|
220
|
+
function isSvgLink(element) {
|
|
221
|
+
return element instanceof SVGElement && element.localName === "a" && (element.hasAttribute("href") || element.hasAttributeNS(XLINK_NAMESPACE, "href"));
|
|
222
|
+
}
|
|
223
|
+
function hasRenderedImage(area) {
|
|
224
|
+
const map = area.closest("map");
|
|
225
|
+
const key = map?.name || map?.id;
|
|
226
|
+
if (!map || !key) return false;
|
|
227
|
+
const root = map.getRootNode();
|
|
228
|
+
return Array.from(root.querySelectorAll("img[usemap]")).some(
|
|
229
|
+
(image) => image.getAttribute("usemap") === `#${key}` && isRenderedForFocus(image)
|
|
189
230
|
);
|
|
190
231
|
}
|
|
232
|
+
function isFocusableInOrder(element) {
|
|
233
|
+
const tabindex = authoredTabindex(element);
|
|
234
|
+
return (tabindex === null || tabindex >= 0) && canTakeFocus(element);
|
|
235
|
+
}
|
|
236
|
+
function extraStop(element) {
|
|
237
|
+
if (element instanceof HTMLAreaElement) {
|
|
238
|
+
const focusable = element.hasAttribute("href") || authoredTabindex(element) !== null;
|
|
239
|
+
return focusable && isFocusableInOrder(element) && hasRenderedImage(element);
|
|
240
|
+
}
|
|
241
|
+
if (element instanceof HTMLObjectElement) {
|
|
242
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element) && element.contentWindow !== null;
|
|
243
|
+
}
|
|
244
|
+
if (isSvgLink(element))
|
|
245
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
function isSequentialStop(element) {
|
|
249
|
+
const stop = extraStop(element) ?? (element.matches(TAB_STOP_CANDIDATE_SELECTOR) && isTabStop(element));
|
|
250
|
+
if (!stop) return false;
|
|
251
|
+
const link = element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement || isSvgLink(element);
|
|
252
|
+
return !(link && authoredTabindex(element) === null && isInEditableContent(element));
|
|
253
|
+
}
|
|
254
|
+
function ownedScope(element) {
|
|
255
|
+
if (element.shadowRoot) return Array.from(element.shadowRoot.children);
|
|
256
|
+
if (element instanceof HTMLSlotElement) {
|
|
257
|
+
const assigned = element.assignedElements();
|
|
258
|
+
return assigned.length > 0 ? assigned : Array.from(element.children);
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
function orderScope(elements, walk) {
|
|
263
|
+
const entries = [];
|
|
264
|
+
const visit = (element) => {
|
|
265
|
+
if (element === walk.from) walk.fromAt = walk.tree.length;
|
|
266
|
+
const tabindex = authoredTabindex(element) ?? 0;
|
|
267
|
+
const stop = (element instanceof HTMLElement || element instanceof SVGElement) && isSequentialStop(element) ? element : null;
|
|
268
|
+
if (stop) walk.tree.push(stop);
|
|
269
|
+
const owned = ownedScope(element);
|
|
270
|
+
if (owned) {
|
|
271
|
+
if (tabindex >= 0) {
|
|
272
|
+
entries.push({ tabindex, stops: [...stop ? [stop] : [], ...orderScope(owned, walk)] });
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (stop) entries.push({ tabindex, stops: [stop] });
|
|
277
|
+
for (const child of Array.from(element.children)) visit(child);
|
|
278
|
+
};
|
|
279
|
+
for (const element of elements) visit(element);
|
|
280
|
+
const positive = entries.filter((entry) => entry.tabindex > 0);
|
|
281
|
+
positive.sort((a, b) => a.tabindex - b.tabindex);
|
|
282
|
+
const rest = entries.filter((entry) => entry.tabindex === 0);
|
|
283
|
+
return [...positive, ...rest].flatMap((entry) => entry.stops);
|
|
284
|
+
}
|
|
285
|
+
function passesRadioRule(element, tree, focusedRadio) {
|
|
286
|
+
if (!isRadio(element) || element.checked) return true;
|
|
287
|
+
const group = tree.filter(
|
|
288
|
+
(stop) => stop === element || isRadio(stop) && sameRadioGroup(stop, element)
|
|
289
|
+
);
|
|
290
|
+
if (group.some((radio) => radio.checked)) return false;
|
|
291
|
+
if (group[0] !== element) return false;
|
|
292
|
+
return focusedRadio === null || !sameRadioGroup(focusedRadio, element);
|
|
293
|
+
}
|
|
294
|
+
function startIndex(order, walk, backward, isStop) {
|
|
295
|
+
if (walk.fromAt < 0) return backward ? order.length - 1 : 0;
|
|
296
|
+
const at = order.indexOf(walk.from);
|
|
297
|
+
if (at >= 0) return backward ? at - 1 : at + 1;
|
|
298
|
+
const neighbour = backward ? walk.tree.slice(0, walk.fromAt).reverse().find(isStop) : walk.tree.slice(walk.fromAt).find(isStop);
|
|
299
|
+
if (neighbour) return order.indexOf(neighbour);
|
|
300
|
+
return backward ? order.length - 1 : 0;
|
|
301
|
+
}
|
|
302
|
+
function sequentialTabStops(root, from = null, backward = false) {
|
|
303
|
+
const walk = { from, tree: [], fromAt: from === root ? 0 : -1 };
|
|
304
|
+
const order = orderScope(ownedScope(root) ?? Array.from(root.children), walk);
|
|
305
|
+
const focusedRadio = walk.fromAt >= 0 && isRadio(from) ? from : null;
|
|
306
|
+
const isStop = (element) => passesRadioRule(element, walk.tree, focusedRadio);
|
|
307
|
+
const start = startIndex(order, walk, backward, isStop);
|
|
308
|
+
const visited = backward ? [...order.slice(0, start + 1).reverse(), ...order.slice(start + 1).reverse()] : [...order.slice(start), ...order.slice(0, start)];
|
|
309
|
+
return visited.filter((element) => element !== from && isStop(element));
|
|
310
|
+
}
|
|
311
|
+
function deepActiveElement(ownerDocument) {
|
|
312
|
+
let active = ownerDocument.activeElement;
|
|
313
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
314
|
+
return active;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
var MicrotaskCoalescer = class {
|
|
318
|
+
#run;
|
|
319
|
+
#queued = false;
|
|
320
|
+
#active = false;
|
|
321
|
+
#generation = 0;
|
|
322
|
+
constructor(run) {
|
|
323
|
+
this.#run = run;
|
|
324
|
+
}
|
|
325
|
+
activate() {
|
|
326
|
+
this.#active = true;
|
|
327
|
+
}
|
|
328
|
+
cancel() {
|
|
329
|
+
this.#active = false;
|
|
330
|
+
this.#queued = false;
|
|
331
|
+
this.#generation += 1;
|
|
332
|
+
}
|
|
333
|
+
get pending() {
|
|
334
|
+
return this.#queued;
|
|
335
|
+
}
|
|
336
|
+
schedule() {
|
|
337
|
+
if (!this.#active || this.#queued) return;
|
|
338
|
+
this.#queued = true;
|
|
339
|
+
const generation = this.#generation;
|
|
340
|
+
queueMicrotask(() => {
|
|
341
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
342
|
+
this.#queued = false;
|
|
343
|
+
this.#run();
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
function queryOpenDescendants(root, selector) {
|
|
349
|
+
const matches = [];
|
|
350
|
+
const roots = [root];
|
|
351
|
+
for (const scope of roots) {
|
|
352
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
353
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
354
|
+
const shadow = scope.shadowRoot;
|
|
355
|
+
if (shadow) roots.push(shadow);
|
|
356
|
+
}
|
|
357
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
358
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return matches;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
365
|
+
if (element.getAttribute(name) === value) return false;
|
|
366
|
+
if (value === null) element.removeAttribute(name);
|
|
367
|
+
else element.setAttribute(name, value);
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function holdsFocus(element) {
|
|
372
|
+
return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
|
|
373
|
+
}
|
|
374
|
+
var TabindexLoan = class _TabindexLoan {
|
|
375
|
+
static #registry = sharedRegistry(
|
|
376
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
377
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
378
|
+
);
|
|
379
|
+
#value;
|
|
380
|
+
#record;
|
|
381
|
+
#preserveFocus;
|
|
382
|
+
#lent = /* @__PURE__ */ new Set();
|
|
383
|
+
constructor(value, owner, options = {}) {
|
|
384
|
+
this.#value = value;
|
|
385
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
386
|
+
this.#preserveFocus = options.preserveFocus === true;
|
|
387
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
388
|
+
}
|
|
389
|
+
lend(element, options = {}) {
|
|
390
|
+
const stack = this.#stack(element);
|
|
391
|
+
const current = element.getAttribute("tabindex");
|
|
392
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
393
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
394
|
+
);
|
|
395
|
+
const linked = stack.at(-1)?.value === current;
|
|
396
|
+
if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
|
|
397
|
+
if (!held && current !== null && !linked) return;
|
|
398
|
+
this.#lent.add(element);
|
|
399
|
+
if (held) held.loan = this;
|
|
400
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
401
|
+
this.#mark(element, stack);
|
|
402
|
+
if (current === null || !held && current !== this.#value) {
|
|
403
|
+
element.setAttribute("tabindex", this.#value);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return(element) {
|
|
407
|
+
const stack = this.#stack(element);
|
|
408
|
+
const hold = stack.find((held) => held.loan === this) ?? stack.find(
|
|
409
|
+
(held) => held.loan === null && held.record === this.#record && held.value === this.#value
|
|
410
|
+
);
|
|
411
|
+
if (!hold) return false;
|
|
412
|
+
if (!this.#release(element, stack, hold, this.#preserveFocus)) {
|
|
413
|
+
hold.loan = this;
|
|
414
|
+
this.#lent.add(element);
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
this.#lent.delete(element);
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
returnAll() {
|
|
421
|
+
for (const element of Array.from(this.#lent)) this.return(element);
|
|
422
|
+
}
|
|
423
|
+
reclaim(element) {
|
|
424
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
425
|
+
const stack = this.#stack(element);
|
|
426
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
427
|
+
if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
|
|
428
|
+
}
|
|
429
|
+
reclaimWithin(root) {
|
|
430
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
431
|
+
this.reclaim(element);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
#stack(element) {
|
|
435
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
436
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
437
|
+
stacks.set(element, stack);
|
|
438
|
+
return stack;
|
|
439
|
+
}
|
|
440
|
+
#seed(element) {
|
|
441
|
+
const current = element.getAttribute("tabindex");
|
|
442
|
+
const holds = [];
|
|
443
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
444
|
+
const value = element.getAttribute(record);
|
|
445
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
446
|
+
}
|
|
447
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
448
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
449
|
+
return holds;
|
|
450
|
+
}
|
|
451
|
+
#release(element, stack, hold, preserveFocus = false) {
|
|
452
|
+
const current = element.getAttribute("tabindex");
|
|
453
|
+
const index = stack.indexOf(hold);
|
|
454
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
455
|
+
if (owns && preserveFocus && holdsFocus(element)) return false;
|
|
456
|
+
stack.splice(index, 1);
|
|
457
|
+
const above = stack[index];
|
|
458
|
+
if (above) above.linked &&= hold.linked;
|
|
459
|
+
this.#mark(element, stack);
|
|
460
|
+
if (!owns) return true;
|
|
461
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
462
|
+
if (!below) element.removeAttribute("tabindex");
|
|
463
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
#mark(element, stack) {
|
|
467
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
468
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
469
|
+
for (const [name, recorded] of records) {
|
|
470
|
+
setAttributeIfChanged(element, name, recorded);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
191
474
|
|
|
192
|
-
|
|
193
|
-
var
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
475
|
+
var FOCUS_TRAP_OWNER = "stimeo-focus-trap";
|
|
476
|
+
var INERT_MARK = `data-${FOCUS_TRAP_OWNER}-inert`;
|
|
477
|
+
var OVERFLOW_MARK = `data-${FOCUS_TRAP_OWNER}-overflow`;
|
|
478
|
+
var FocusTrap = class _FocusTrap {
|
|
479
|
+
static #registries = sharedRegistry(
|
|
480
|
+
"stimeo-ui.focus-trap.registry.v2",
|
|
481
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
482
|
+
);
|
|
483
|
+
static #TEXT_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
484
|
+
"text",
|
|
485
|
+
"search",
|
|
486
|
+
"email",
|
|
487
|
+
"url",
|
|
488
|
+
"tel",
|
|
489
|
+
"password",
|
|
490
|
+
"number"
|
|
491
|
+
]);
|
|
492
|
+
#openers = [];
|
|
493
|
+
#entry;
|
|
494
|
+
#tabindexLoans = /* @__PURE__ */ new Map();
|
|
203
495
|
#activeState = false;
|
|
204
|
-
/** Registers the trap on the shared Escape stack while active (see {@link EscapeLayer}). */
|
|
205
496
|
#escapeLayer = new EscapeLayer();
|
|
206
|
-
|
|
497
|
+
#gate = new DetachGate();
|
|
207
498
|
#getContainer;
|
|
208
|
-
/** Closing/focus hooks; see {@link FocusTrapOptions}. */
|
|
209
499
|
#options;
|
|
210
|
-
/**
|
|
211
|
-
* @param getContainer - Returns the trapped element. Called on every operation
|
|
212
|
-
* so the live target is always used.
|
|
213
|
-
* @param options - Closing/focus hooks; see {@link FocusTrapOptions}.
|
|
214
|
-
*/
|
|
215
500
|
constructor(getContainer, options = {}) {
|
|
216
501
|
this.#getContainer = getContainer;
|
|
217
502
|
this.#options = options;
|
|
503
|
+
this.#entry = {
|
|
504
|
+
container: () => this.#readContainer(),
|
|
505
|
+
moveFocus: (backward) => this.#moveFocus(backward),
|
|
506
|
+
refocus: () => this.#refocus(),
|
|
507
|
+
isolated: false,
|
|
508
|
+
lockedBody: null,
|
|
509
|
+
openedFrom: null,
|
|
510
|
+
handedBack: []
|
|
511
|
+
};
|
|
218
512
|
}
|
|
219
|
-
/** Whether the trap is currently active. */
|
|
220
513
|
get active() {
|
|
221
514
|
return this.#activeState;
|
|
222
515
|
}
|
|
223
|
-
/**
|
|
224
|
-
* Applies the trap: records the current focus, optionally locks background scroll
|
|
225
|
-
* and makes background siblings `inert`, listens for `Tab`/`Escape`, and (unless
|
|
226
|
-
* `autoFocus` is off) moves focus inside. No-ops if already active.
|
|
227
|
-
*/
|
|
228
516
|
activate() {
|
|
229
517
|
if (this.#activeState) return;
|
|
518
|
+
_FocusTrap.#sweep();
|
|
230
519
|
this.#activeState = true;
|
|
231
|
-
|
|
232
|
-
this.#
|
|
233
|
-
if (this.#flag(this.#options.lockScroll, true))
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (this.#flag(this.#options.isolate, true)) this.#isolateBackground();
|
|
239
|
-
document.addEventListener("keydown", this.#onKeydown);
|
|
240
|
-
this.#beforeCache.activate();
|
|
520
|
+
this.#openers = _FocusTrap.#focusPath();
|
|
521
|
+
this.#entry.openedFrom = _FocusTrap.#trapHolding(this.#openers[0] ?? null);
|
|
522
|
+
if (this.#flag(this.#options.lockScroll, true)) this.#lockScroll();
|
|
523
|
+
this.#entry.isolated = this.#flag(this.#options.isolate, true);
|
|
524
|
+
this.#joinStack();
|
|
525
|
+
_FocusTrap.#reconcileBackground(document);
|
|
241
526
|
const onEscape = this.#options.onEscape;
|
|
242
527
|
if (onEscape) this.#escapeLayer.activate(document, { onDismiss: () => onEscape() });
|
|
243
528
|
if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
|
|
244
529
|
}
|
|
245
|
-
/**
|
|
246
|
-
* Reverts every side effect applied by {@link activate}. No-ops if inactive, so
|
|
247
|
-
* a controller can call it defensively from both `close()` and `disconnect()`.
|
|
248
|
-
*
|
|
249
|
-
* @param restoreFocus - Move focus back to the opener (default `true`). Pass
|
|
250
|
-
* `false` on teardown (`disconnect`), where yanking focus is undesirable.
|
|
251
|
-
*/
|
|
252
530
|
deactivate({ restoreFocus = true } = {}) {
|
|
253
|
-
if (!this.#activeState)
|
|
531
|
+
if (!this.#activeState) {
|
|
532
|
+
this.#returnTabindexes();
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
254
535
|
this.#activeState = false;
|
|
255
536
|
this.#escapeLayer.deactivate();
|
|
256
|
-
|
|
257
|
-
this.#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
537
|
+
const above = this.#leaveStack();
|
|
538
|
+
this.#unlockScroll();
|
|
539
|
+
_FocusTrap.#reconcileBackground(document);
|
|
540
|
+
const wayBack = this.#handOver(above);
|
|
541
|
+
if (restoreFocus) this.#restore(wayBack, above);
|
|
542
|
+
this.#returnTabindexes();
|
|
543
|
+
}
|
|
544
|
+
connect() {
|
|
545
|
+
this.#gate.cancel();
|
|
546
|
+
_FocusTrap.#sweep();
|
|
547
|
+
new TabindexLoan("-1", FOCUS_TRAP_OWNER).reclaimWithin(document);
|
|
548
|
+
}
|
|
549
|
+
disconnect(host) {
|
|
550
|
+
if (this.#activeState) this.deactivate({ restoreFocus: false });
|
|
551
|
+
this.#gate.disconnected(host, () => this.#returnTabindexes());
|
|
552
|
+
}
|
|
553
|
+
refreshIsolation() {
|
|
554
|
+
if (!this.#activeState) return;
|
|
555
|
+
const isolate = this.#flag(this.#options.isolate, true);
|
|
556
|
+
if (isolate === this.#entry.isolated) return;
|
|
557
|
+
this.#entry.isolated = isolate;
|
|
558
|
+
const stranded = _FocusTrap.#reconcileBackground(document);
|
|
559
|
+
const active = deepActiveElement(document);
|
|
560
|
+
if (stranded.some((element) => flatTreeContains(element, active))) this.#focusInitial();
|
|
561
|
+
}
|
|
562
|
+
refreshContainer() {
|
|
563
|
+
if (!this.#activeState) return;
|
|
564
|
+
_FocusTrap.#reconcileBackground(document);
|
|
565
|
+
this.#returnTabindexes();
|
|
566
|
+
if (this.#ownsTab()) this.#refocus();
|
|
267
567
|
}
|
|
268
|
-
/** Resolves a boolean-or-getter option, defaulting when it was not provided. */
|
|
269
568
|
#flag(option, fallback) {
|
|
270
569
|
if (option === void 0) return fallback;
|
|
271
570
|
return typeof option === "function" ? option() : option;
|
|
272
571
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
* is left alone mid-navigation. The listener lives only while active.
|
|
280
|
-
*/
|
|
281
|
-
#beforeCache = new BeforeCacheReset(() => this.deactivate({ restoreFocus: false }));
|
|
282
|
-
/**
|
|
283
|
-
* Handles `Tab` (focus trap) while active. `Escape` dismissal is owned by the
|
|
284
|
-
* shared {@link EscapeLayer} resolver, so Tab trapping stays independent of
|
|
285
|
-
* which layer currently owns Escape.
|
|
286
|
-
*/
|
|
287
|
-
#onKeydown = (event) => {
|
|
288
|
-
if (event.key === "Tab") this.#trapTab(event);
|
|
289
|
-
};
|
|
290
|
-
/** Keeps `Tab` focus cycling within the container's focusable elements. */
|
|
291
|
-
#trapTab(event) {
|
|
292
|
-
const focusable = this.#focusableElements();
|
|
293
|
-
if (focusable.length === 0) {
|
|
294
|
-
event.preventDefault();
|
|
295
|
-
return;
|
|
572
|
+
#joinStack() {
|
|
573
|
+
const registry = _FocusTrap.#registry(document);
|
|
574
|
+
if (registry.stack.length === 0) {
|
|
575
|
+
document.addEventListener("keydown", registry.onKeydown);
|
|
576
|
+
document.addEventListener("turbo:morph", registry.onMorph);
|
|
577
|
+
document.addEventListener("turbo:morph-element", registry.onMorph);
|
|
296
578
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
579
|
+
registry.stack.push(this.#entry);
|
|
580
|
+
}
|
|
581
|
+
#leaveStack() {
|
|
582
|
+
const registry = _FocusTrap.#registry(document);
|
|
583
|
+
const index = registry.stack.indexOf(this.#entry);
|
|
584
|
+
const above = registry.stack.slice(index + 1);
|
|
585
|
+
registry.stack.splice(index, 1);
|
|
586
|
+
if (registry.stack.length === 0) {
|
|
587
|
+
document.removeEventListener("keydown", registry.onKeydown);
|
|
588
|
+
document.removeEventListener("turbo:morph", registry.onMorph);
|
|
589
|
+
document.removeEventListener("turbo:morph-element", registry.onMorph);
|
|
590
|
+
}
|
|
591
|
+
return above;
|
|
592
|
+
}
|
|
593
|
+
static #focusPath() {
|
|
594
|
+
const path = [];
|
|
595
|
+
for (let active = document.activeElement; active; active = active.shadowRoot?.activeElement ?? null) {
|
|
596
|
+
if (active instanceof HTMLElement && active !== document.body) path.unshift(active);
|
|
304
597
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
598
|
+
return path;
|
|
599
|
+
}
|
|
600
|
+
static #trapHolding(element) {
|
|
601
|
+
for (const trap of [..._FocusTrap.#registry(document).stack].reverse()) {
|
|
602
|
+
const container = trap.container();
|
|
603
|
+
if (container && flatTreeContains(container, element)) return trap;
|
|
311
604
|
}
|
|
605
|
+
return null;
|
|
312
606
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
607
|
+
#handOver(above) {
|
|
608
|
+
const entry = this.#entry;
|
|
609
|
+
const wayBack = [
|
|
610
|
+
...this.#openers,
|
|
611
|
+
this.#options.fallbackFocus?.() ?? null,
|
|
612
|
+
...entry.handedBack
|
|
613
|
+
].filter((element) => element !== null);
|
|
614
|
+
for (const trap of above) {
|
|
615
|
+
if (trap.openedFrom !== entry) continue;
|
|
616
|
+
trap.handedBack = [...trap.handedBack, ...wayBack];
|
|
617
|
+
trap.openedFrom = entry.openedFrom;
|
|
618
|
+
}
|
|
619
|
+
this.#openers = [];
|
|
620
|
+
entry.openedFrom = null;
|
|
621
|
+
entry.handedBack = [];
|
|
622
|
+
return wayBack;
|
|
623
|
+
}
|
|
624
|
+
#restore(wayBack, above) {
|
|
625
|
+
const active = deepActiveElement(document);
|
|
626
|
+
const released = this.#readContainer();
|
|
627
|
+
const holds = (trap) => {
|
|
628
|
+
const container = trap.container();
|
|
629
|
+
if (!container || !_FocusTrap.#takesTab(trap) || !flatTreeContains(container, active)) {
|
|
630
|
+
return false;
|
|
335
631
|
}
|
|
336
|
-
|
|
632
|
+
return !released || !flatTreeContains(released, active) || flatTreeContains(released, container);
|
|
633
|
+
};
|
|
634
|
+
if (above.some(holds)) return;
|
|
635
|
+
for (const element of wayBack) {
|
|
636
|
+
if (this.#activeState) return;
|
|
637
|
+
if (!element.isConnected || !canTakeFocus(element) || !isRenderedForFocus(element)) continue;
|
|
638
|
+
const before = deepActiveElement(document);
|
|
639
|
+
element.focus();
|
|
640
|
+
const after = deepActiveElement(document);
|
|
641
|
+
if (after === element || after !== before) return;
|
|
642
|
+
}
|
|
643
|
+
_FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack)?.refocus();
|
|
644
|
+
}
|
|
645
|
+
#refocus() {
|
|
646
|
+
if (!this.#flag(this.#options.autoFocus, true)) return;
|
|
647
|
+
if (flatTreeContains(this.#getContainer(), deepActiveElement(document))) return;
|
|
648
|
+
this.#focusInitial();
|
|
649
|
+
}
|
|
650
|
+
#lendTabindex(container) {
|
|
651
|
+
const loan = this.#tabindexLoans.get(container) ?? new TabindexLoan("-1", FOCUS_TRAP_OWNER);
|
|
652
|
+
loan.lend(container);
|
|
653
|
+
this.#tabindexLoans.set(container, loan);
|
|
654
|
+
}
|
|
655
|
+
#returnTabindexes() {
|
|
656
|
+
const focused = deepActiveElement(document);
|
|
657
|
+
for (const [element, loan] of [...this.#tabindexLoans]) {
|
|
658
|
+
if (element === focused) continue;
|
|
659
|
+
loan.returnAll();
|
|
660
|
+
this.#tabindexLoans.delete(element);
|
|
337
661
|
}
|
|
338
662
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
663
|
+
#lockScroll() {
|
|
664
|
+
const registry = _FocusTrap.#registry(document);
|
|
665
|
+
const body = document.body;
|
|
666
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
667
|
+
registry.scrollLocks.add(this.#entry);
|
|
668
|
+
this.#entry.lockedBody = body;
|
|
669
|
+
body.style.overflow = "hidden";
|
|
670
|
+
}
|
|
671
|
+
#unlockScroll() {
|
|
672
|
+
const registry = _FocusTrap.#registry(document);
|
|
673
|
+
const body = this.#entry.lockedBody;
|
|
674
|
+
this.#entry.lockedBody = null;
|
|
675
|
+
if (!registry.scrollLocks.delete(this.#entry) || !body) return;
|
|
676
|
+
if (!_FocusTrap.#isLocked(registry, body)) _FocusTrap.#unlock(body);
|
|
677
|
+
}
|
|
678
|
+
static #isLocked(registry, body) {
|
|
679
|
+
return [...registry.scrollLocks].some((trap) => trap.lockedBody === body);
|
|
680
|
+
}
|
|
681
|
+
static #unlock(body) {
|
|
682
|
+
const saved = body.getAttribute(OVERFLOW_MARK);
|
|
683
|
+
if (saved === null) return;
|
|
684
|
+
body.removeAttribute(OVERFLOW_MARK);
|
|
685
|
+
body.style.overflow = saved;
|
|
686
|
+
}
|
|
687
|
+
static #sweep() {
|
|
688
|
+
const registry = _FocusTrap.#registry(document);
|
|
689
|
+
for (const element of queryOpenDescendants(document, `[${INERT_MARK}]`)) {
|
|
690
|
+
if (registry.inerted.has(element)) continue;
|
|
691
|
+
element.removeAttribute(INERT_MARK);
|
|
692
|
+
element.inert = false;
|
|
693
|
+
}
|
|
694
|
+
if (!_FocusTrap.#isLocked(registry, document.body)) _FocusTrap.#unlock(document.body);
|
|
695
|
+
}
|
|
696
|
+
static #registry(ownerDocument) {
|
|
697
|
+
const existing = _FocusTrap.#registries.get(ownerDocument);
|
|
698
|
+
if (existing) return existing;
|
|
699
|
+
const morphs = new MicrotaskCoalescer(() => _FocusTrap.#reapply(registry));
|
|
700
|
+
morphs.activate();
|
|
701
|
+
const registry = {
|
|
702
|
+
stack: [],
|
|
703
|
+
onKeydown: (event) => _FocusTrap.#resolveTab(registry.stack, event),
|
|
704
|
+
onMorph: () => morphs.schedule(),
|
|
705
|
+
inerted: /* @__PURE__ */ new Set(),
|
|
706
|
+
scrollLocks: /* @__PURE__ */ new Set()
|
|
707
|
+
};
|
|
708
|
+
_FocusTrap.#registries.set(ownerDocument, registry);
|
|
709
|
+
return registry;
|
|
710
|
+
}
|
|
711
|
+
static #reapply(registry) {
|
|
712
|
+
_FocusTrap.#reconcileBackground(document);
|
|
713
|
+
const body = document.body;
|
|
714
|
+
if (!_FocusTrap.#isLocked(registry, body)) return;
|
|
715
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
716
|
+
body.style.overflow = "hidden";
|
|
717
|
+
}
|
|
718
|
+
static #reconcileBackground(ownerDocument) {
|
|
719
|
+
const registry = _FocusTrap.#registry(ownerDocument);
|
|
720
|
+
for (const element of registry.inerted) {
|
|
721
|
+
if (!element.hasAttribute(INERT_MARK)) registry.inerted.delete(element);
|
|
722
|
+
}
|
|
723
|
+
const background = _FocusTrap.#backgroundOf(registry);
|
|
724
|
+
for (const element of registry.inerted) {
|
|
725
|
+
if (background.has(element)) continue;
|
|
726
|
+
element.removeAttribute(INERT_MARK);
|
|
727
|
+
element.inert = false;
|
|
728
|
+
registry.inerted.delete(element);
|
|
729
|
+
}
|
|
730
|
+
const added = [];
|
|
731
|
+
for (const element of background) {
|
|
732
|
+
if (registry.inerted.has(element)) continue;
|
|
733
|
+
element.setAttribute(INERT_MARK, "");
|
|
734
|
+
element.inert = true;
|
|
735
|
+
registry.inerted.add(element);
|
|
736
|
+
added.push(element);
|
|
737
|
+
}
|
|
738
|
+
return added;
|
|
739
|
+
}
|
|
740
|
+
static #backgroundOf(registry) {
|
|
741
|
+
const containers = registry.stack.map((trap) => trap.container());
|
|
742
|
+
let top = -1;
|
|
743
|
+
registry.stack.forEach((trap, index) => {
|
|
744
|
+
if (trap.isolated && containers[index]) top = index;
|
|
745
|
+
});
|
|
746
|
+
const background = /* @__PURE__ */ new Set();
|
|
747
|
+
if (top < 0) return background;
|
|
748
|
+
const kept = /* @__PURE__ */ new Set();
|
|
749
|
+
const keep = (path) => {
|
|
750
|
+
for (const node of path) {
|
|
751
|
+
kept.add(node);
|
|
752
|
+
background.delete(node);
|
|
753
|
+
}
|
|
754
|
+
for (const node of path) {
|
|
755
|
+
const parent = flatTreeParent(node);
|
|
756
|
+
for (const sibling of parent ? flatTreeChildren(parent) : []) {
|
|
757
|
+
if (!(sibling instanceof HTMLElement) || kept.has(sibling)) continue;
|
|
758
|
+
if (sibling.inert && !registry.inerted.has(sibling)) continue;
|
|
759
|
+
background.add(sibling);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
containers.slice(top).forEach((container, index) => {
|
|
764
|
+
const path = [];
|
|
765
|
+
for (let node = container; node && node !== document.body && !kept.has(node); ) {
|
|
766
|
+
path.push(node);
|
|
767
|
+
node = flatTreeParent(node);
|
|
768
|
+
}
|
|
769
|
+
if (index === 0 || path.some((node) => background.has(node))) keep(path);
|
|
770
|
+
});
|
|
771
|
+
return background;
|
|
772
|
+
}
|
|
773
|
+
#readContainer() {
|
|
774
|
+
try {
|
|
775
|
+
return this.#getContainer();
|
|
776
|
+
} catch {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
static #resolveTab(stack, event) {
|
|
781
|
+
if (event.key !== "Tab" || event.defaultPrevented) return;
|
|
782
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
783
|
+
if (event.ctrlKey || event.metaKey) return;
|
|
784
|
+
const owner = _FocusTrap.#tabOwner(stack);
|
|
785
|
+
if (!owner) return;
|
|
786
|
+
event.preventDefault();
|
|
787
|
+
owner.moveFocus(event.shiftKey);
|
|
788
|
+
}
|
|
789
|
+
static #tabOwner(stack) {
|
|
790
|
+
for (const trap of [...stack].reverse()) {
|
|
791
|
+
if (_FocusTrap.#takesTab(trap)) return trap;
|
|
792
|
+
}
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
static #takesTab(trap) {
|
|
796
|
+
const container = trap.container();
|
|
797
|
+
if (!container) return false;
|
|
798
|
+
return container.isConnected && !closestInFlatTree(container, "[hidden]") && isRenderedForFocus(container);
|
|
799
|
+
}
|
|
800
|
+
#ownsTab() {
|
|
801
|
+
return _FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack) === this.#entry;
|
|
802
|
+
}
|
|
803
|
+
#moveFocus(backward) {
|
|
804
|
+
const from = deepActiveElement(document);
|
|
805
|
+
for (const stop of sequentialTabStops(this.#getContainer(), from, backward)) {
|
|
806
|
+
if (!this.#ownsTab()) return;
|
|
807
|
+
const before = deepActiveElement(document);
|
|
808
|
+
stop.focus();
|
|
809
|
+
const after = deepActiveElement(document);
|
|
810
|
+
if (after === before) continue;
|
|
811
|
+
if (after === stop) _FocusTrap.#selectOnEntry(stop);
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
static #selectOnEntry(stop) {
|
|
816
|
+
if (stop instanceof HTMLInputElement && _FocusTrap.#TEXT_ENTRY_TYPES.has(stop.type)) {
|
|
817
|
+
stop.select();
|
|
343
818
|
}
|
|
344
|
-
this.#inertedSiblings = [];
|
|
345
819
|
}
|
|
346
|
-
/** Moves focus to the initial target, the first focusable, or the container. */
|
|
347
820
|
#focusInitial() {
|
|
348
821
|
const preferred = this.#options.initialFocus?.();
|
|
349
822
|
if (preferred) {
|
|
350
823
|
preferred.focus();
|
|
351
824
|
return;
|
|
352
825
|
}
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
826
|
+
const container = this.#getContainer();
|
|
827
|
+
const [first] = sequentialTabStops(container);
|
|
828
|
+
if (first) {
|
|
829
|
+
first.focus();
|
|
356
830
|
return;
|
|
357
831
|
}
|
|
358
|
-
|
|
359
|
-
container.tabIndex = -1;
|
|
832
|
+
this.#lendTabindex(container);
|
|
360
833
|
container.focus();
|
|
361
834
|
}
|
|
362
|
-
/** Collects the container's currently focusable descendants in DOM order. */
|
|
363
|
-
#focusableElements() {
|
|
364
|
-
return tabStopsWithin(this.#getContainer());
|
|
365
|
-
}
|
|
366
835
|
};
|
|
367
836
|
|
|
368
|
-
// src/controllers/focus_controller.ts
|
|
369
837
|
var FocusController = class extends Controller {
|
|
370
838
|
static targets = ["initial"];
|
|
371
839
|
static values = {
|
|
@@ -377,70 +845,42 @@ var FocusController = class extends Controller {
|
|
|
377
845
|
static actions = ["activate", "deactivate"];
|
|
378
846
|
static events = ["activate", "deactivate"];
|
|
379
847
|
#trap = new FocusTrap(() => this.element, {
|
|
380
|
-
// A focus scope is not a modal: never lock scroll, and only isolate the background
|
|
381
|
-
// when `inert` is requested. `auto` gates the initial focus move; Escape releases.
|
|
382
848
|
lockScroll: false,
|
|
383
849
|
isolate: () => this.inertValue,
|
|
384
850
|
autoFocus: () => this.autoValue,
|
|
385
851
|
initialFocus: () => this.hasInitialTarget ? this.initialTarget : null,
|
|
386
852
|
onEscape: () => this.deactivate()
|
|
387
853
|
});
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
* Held here rather than read back from the trap: the trap releases itself
|
|
392
|
-
* before Turbo caches the page, so its own flag stops answering for the state
|
|
393
|
-
* this controller publishes on the element and in its events.
|
|
394
|
-
*/
|
|
395
|
-
#active = false;
|
|
396
|
-
/**
|
|
397
|
-
* Returns the element to its untrapped form before Turbo copies the page. The
|
|
398
|
-
* trap performs its own release on the same event, so what is left is the hook
|
|
399
|
-
* this controller owns — carried into the snapshot it would describe a scope
|
|
400
|
-
* that is no longer trapping.
|
|
401
|
-
*
|
|
402
|
-
* Silent, like `disconnect()`: the page is about to be frozen, and a release
|
|
403
|
-
* nobody asked for is not a close to report.
|
|
404
|
-
*/
|
|
405
|
-
#beforeCache = new BeforeCacheReset(() => {
|
|
406
|
-
this.#active = false;
|
|
407
|
-
this.element.removeAttribute("data-focus-trapped");
|
|
408
|
-
});
|
|
409
|
-
/** Stimulus drives activation from the `trap` value (also fires on connect). */
|
|
854
|
+
inertValueChanged() {
|
|
855
|
+
this.#trap.refreshIsolation();
|
|
856
|
+
}
|
|
410
857
|
trapValueChanged() {
|
|
411
858
|
if (this.trapValue) this.#activate();
|
|
412
859
|
else this.#deactivate();
|
|
413
860
|
}
|
|
414
861
|
connect() {
|
|
415
|
-
this.#
|
|
862
|
+
this.#trap.connect();
|
|
416
863
|
}
|
|
417
864
|
disconnect() {
|
|
418
|
-
this.#trap.
|
|
419
|
-
this.#beforeCache.deactivate();
|
|
420
|
-
this.#active = false;
|
|
865
|
+
this.#trap.disconnect(this);
|
|
421
866
|
this.element.removeAttribute("data-focus-trapped");
|
|
422
867
|
}
|
|
423
|
-
/** Turns the trap on. Acts synchronously and keeps the `trap` value in sync. */
|
|
424
868
|
activate() {
|
|
425
869
|
this.trapValue = true;
|
|
426
870
|
this.#activate();
|
|
427
871
|
}
|
|
428
|
-
/** Turns the trap off (also wired to Escape). */
|
|
429
872
|
deactivate() {
|
|
430
873
|
this.trapValue = false;
|
|
431
874
|
this.#deactivate();
|
|
432
875
|
}
|
|
433
876
|
#activate() {
|
|
434
|
-
if (this.#active) return;
|
|
435
|
-
this.#active = true;
|
|
877
|
+
if (this.#trap.active) return;
|
|
436
878
|
this.#trap.activate();
|
|
437
879
|
this.element.setAttribute("data-focus-trapped", "true");
|
|
438
880
|
this.dispatch("activate", { detail: {} });
|
|
439
881
|
}
|
|
440
|
-
/** @stimeoRuntimeOnly `restore` decides whether releasing this one trap returns focus. */
|
|
441
882
|
#deactivate() {
|
|
442
|
-
if (!this.#active) return;
|
|
443
|
-
this.#active = false;
|
|
883
|
+
if (!this.#trap.active) return;
|
|
444
884
|
this.#trap.deactivate({ restoreFocus: this.restoreValue });
|
|
445
885
|
this.element.removeAttribute("data-focus-trapped");
|
|
446
886
|
this.dispatch("deactivate", { detail: {} });
|
|
@@ -448,5 +888,4 @@ var FocusController = class extends Controller {
|
|
|
448
888
|
};
|
|
449
889
|
|
|
450
890
|
export { FocusController };
|
|
451
|
-
//# sourceMappingURL=focus_controller.js.map
|
|
452
891
|
//# sourceMappingURL=focus_controller.js.map
|