stimeo-ui 0.16.0 → 0.17.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 +31 -0
- package/README.md +8 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +363 -436
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +166 -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 +862 -221
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +35 -5
- package/dist/controllers/announcer_controller.js +135 -198
- 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 +271 -127
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +171 -105
- 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 +18 -0
- package/dist/controllers/bulk_select_controller.js +262 -52
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +24 -5
- package/dist/controllers/calendar_controller.js +175 -209
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +34 -7
- package/dist/controllers/carousel_controller.js +461 -399
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +17 -1
- package/dist/controllers/character_counter_controller.js +246 -156
- 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 +32 -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 +168 -147
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +16 -4
- package/dist/controllers/collapsible_controller.js +301 -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 +149 -139
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +23 -5
- package/dist/controllers/combobox_controller.js +360 -133
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +45 -14
- package/dist/controllers/command_palette_controller.js +970 -358
- 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 +166 -124
- 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 +939 -248
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +31 -4
- package/dist/controllers/context_menu_controller.js +256 -128
- 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 +45 -1
- package/dist/controllers/countdown_controller.js +183 -148
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +21 -2
- package/dist/controllers/currency_input_controller.js +134 -158
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +15 -4
- package/dist/controllers/data_grid_controller.js +114 -186
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +30 -5
- package/dist/controllers/date_range_picker_controller.js +288 -206
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +13 -1
- package/dist/controllers/dialog_controller.js +858 -223
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +24 -7
- package/dist/controllers/direct_upload_controller.js +73 -153
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.js +14 -48
- 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 +175 -21
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +933 -341
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +312 -82
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +158 -68
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +67 -53
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +60 -18
- package/dist/controllers/file_dropzone_controller.js +311 -207
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +67 -39
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +31 -8
- package/dist/controllers/flash_controller.js +172 -314
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +12 -3
- package/dist/controllers/focus_controller.js +654 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +147 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +13 -86
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +41 -9
- package/dist/controllers/frame_loading_controller.js +264 -213
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +22 -2
- 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 +56 -3
- package/dist/controllers/hover_card_controller.js +309 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +42 -1
- package/dist/controllers/idle_controller.js +267 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.js +71 -72
- 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 +147 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +16 -0
- package/dist/controllers/lazy_frame_controller.js +9 -65
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +34 -2
- package/dist/controllers/listbox_controller.js +306 -204
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +1 -1
- package/dist/controllers/local_time_controller.js +54 -64
- 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 +70 -93
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +32 -7
- package/dist/controllers/menu_controller.js +214 -156
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +4 -4
- package/dist/controllers/menubar_controller.js +170 -329
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +151 -67
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +32 -4
- package/dist/controllers/multi_select_controller.js +593 -358
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +8 -1
- package/dist/controllers/navigation_menu_controller.js +124 -232
- 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 +352 -182
- 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 +242 -54
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -2
- package/dist/controllers/number_input_controller.js +320 -263
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +16 -76
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +20 -4
- package/dist/controllers/otp_controller.js +198 -193
- 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 +116 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +48 -11
- package/dist/controllers/overflow_menu_controller.js +343 -283
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +35 -6
- package/dist/controllers/pagination_controller.js +295 -119
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +45 -8
- package/dist/controllers/password_reveal_controller.js +389 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +14 -5
- package/dist/controllers/password_strength_controller.js +99 -153
- 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 +237 -103
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +19 -7
- package/dist/controllers/pointer_drag_controller.js +185 -165
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +38 -2
- package/dist/controllers/popover_controller.js +236 -97
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +209 -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 +232 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +11 -0
- package/dist/controllers/progress_controller.js +125 -56
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +14 -5
- package/dist/controllers/radio_group_controller.js +117 -137
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +43 -3
- package/dist/controllers/range_slider_controller.js +192 -105
- 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 +418 -207
- 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 +134 -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 +147 -135
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +18 -4
- package/dist/controllers/relative_time_controller.js +145 -110
- 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} +97 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +28 -2
- package/dist/controllers/resizable_controller.js +255 -139
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +39 -84
- 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 +428 -225
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.js +0 -53
- 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 +395 -145
- 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 +181 -242
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +19 -4
- package/dist/controllers/separator_controller.js +267 -140
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +64 -7
- package/dist/controllers/sidebar_controller.js +1023 -386
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +248 -97
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +27 -1
- package/dist/controllers/slider_controller.js +159 -79
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
- package/dist/controllers/smart_sticky_header_controller.js +162 -63
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +0 -112
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +277 -169
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +5 -0
- package/dist/controllers/step_indicator_controller.js +99 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +12 -3
- package/dist/controllers/stepper_controller.js +114 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
- package/dist/controllers/stick_to_bottom_controller.js +262 -138
- 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 +214 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +23 -8
- package/dist/controllers/submit_once_controller.js +294 -205
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -1
- package/dist/controllers/switch_controller.js +33 -60
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +2 -2
- package/dist/controllers/tabs_controller.js +35 -29
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +12 -0
- package/dist/controllers/tags_input_controller.js +136 -145
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
- package/dist/controllers/textarea_autosize_controller.js +71 -58
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +21 -9
- package/dist/controllers/theme_controller.js +130 -112
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +18 -1
- package/dist/controllers/time_picker_controller.js +146 -80
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +36 -11
- package/dist/controllers/toast_controller.js +186 -297
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +12 -3
- package/dist/controllers/toggle_group_controller.js +95 -136
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +68 -98
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +45 -3
- 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 +21 -11
- package/dist/controllers/tree_view_controller.js +150 -345
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6370 -9346
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +23 -3
- package/dist/inspector/cli.js +92 -37
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +92 -56
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +787 -100
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +6 -6
- 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,195 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function sharedRegistry(key, create) {
|
|
4
|
+
const symbol = Symbol.for(key);
|
|
5
|
+
const scope = globalThis;
|
|
6
|
+
const existing = scope[symbol];
|
|
7
|
+
if (existing !== void 0) return existing;
|
|
8
|
+
const registry = create();
|
|
9
|
+
Object.defineProperty(globalThis, symbol, { value: registry });
|
|
10
|
+
return registry;
|
|
11
|
+
}
|
|
4
12
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
function isAttributeValue(value) {
|
|
14
|
+
return value === null || typeof value === "string";
|
|
15
|
+
}
|
|
16
|
+
function readLeaseRecord(element, record) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
19
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
20
|
+
const [original, written, ...rest] = parsed;
|
|
21
|
+
const linked = rest[0] !== false;
|
|
22
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
23
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
24
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
25
|
+
} catch {
|
|
26
|
+
return void 0;
|
|
17
27
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
}
|
|
29
|
+
var AttributeLease = class _AttributeLease {
|
|
30
|
+
static #registry = sharedRegistry(
|
|
31
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
32
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
33
|
+
);
|
|
34
|
+
#attribute;
|
|
35
|
+
#record;
|
|
36
|
+
#base;
|
|
37
|
+
#held = /* @__PURE__ */ new Set();
|
|
38
|
+
constructor(attribute, owner, options = {}) {
|
|
39
|
+
this.#attribute = attribute;
|
|
40
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
41
|
+
this.#base = options.base === true;
|
|
42
|
+
const names = _AttributeLease.#registry.names;
|
|
43
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
44
|
+
}
|
|
45
|
+
write(element, value) {
|
|
46
|
+
const stack = this.#stack(element);
|
|
47
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
48
|
+
this.#held.add(element);
|
|
49
|
+
const shows = !this.#covered(element, stack, hold);
|
|
50
|
+
hold.written = value;
|
|
51
|
+
this.#mark(element, stack);
|
|
52
|
+
if (shows) this.#reflect(element, value);
|
|
53
|
+
}
|
|
54
|
+
return(element) {
|
|
55
|
+
const held = this.#held.delete(element);
|
|
56
|
+
const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
|
|
57
|
+
const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
|
|
58
|
+
const hold = this.#take(stack);
|
|
59
|
+
if (!hold) return false;
|
|
60
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
61
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
62
|
+
const index = stack.indexOf(hold);
|
|
63
|
+
stack.splice(index, 1);
|
|
64
|
+
const above = stack[index];
|
|
65
|
+
if (above) above.linked &&= hold.linked;
|
|
66
|
+
this.#mark(element, stack);
|
|
67
|
+
if (!owns) return true;
|
|
68
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
69
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
returnAll() {
|
|
73
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
74
|
+
}
|
|
75
|
+
#stack(element) {
|
|
76
|
+
const stacks = _AttributeLease.#registry.stacks;
|
|
77
|
+
const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
78
|
+
stacks.set(element, byAttribute);
|
|
79
|
+
const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
|
|
80
|
+
byAttribute.set(this.#attribute, stack);
|
|
81
|
+
return stack;
|
|
82
|
+
}
|
|
83
|
+
#seed(element) {
|
|
84
|
+
const found = [];
|
|
85
|
+
for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
|
|
86
|
+
const read = readLeaseRecord(element, record);
|
|
87
|
+
if (read) found.push({ ...read, record });
|
|
24
88
|
}
|
|
89
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
90
|
+
return 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
|
+
}));
|
|
25
97
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
98
|
+
#take(stack) {
|
|
99
|
+
const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
|
|
100
|
+
if (hold) hold.lease = this;
|
|
101
|
+
return hold;
|
|
102
|
+
}
|
|
103
|
+
#acquire(element, stack) {
|
|
104
|
+
const current = element.getAttribute(this.#attribute);
|
|
105
|
+
const top = stack.at(-1);
|
|
106
|
+
const bottom = stack[0];
|
|
107
|
+
const over = top?.written === current ? top : void 0;
|
|
108
|
+
const from = this.#base ? bottom : over;
|
|
109
|
+
const hold = {
|
|
110
|
+
lease: this,
|
|
111
|
+
record: this.#record,
|
|
112
|
+
written: current,
|
|
113
|
+
original: from ? from.original : current,
|
|
114
|
+
linked: !this.#base && over !== void 0
|
|
115
|
+
};
|
|
116
|
+
if (this.#base && bottom) {
|
|
117
|
+
bottom.linked = true;
|
|
118
|
+
stack.unshift(hold);
|
|
119
|
+
} else stack.push(hold);
|
|
120
|
+
return hold;
|
|
121
|
+
}
|
|
122
|
+
#covered(element, stack, hold) {
|
|
123
|
+
const current = element.getAttribute(this.#attribute);
|
|
124
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
125
|
+
}
|
|
126
|
+
#mark(element, stack) {
|
|
127
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
128
|
+
stack.forEach((hold, index) => {
|
|
129
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
130
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
131
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
132
|
+
records.set(
|
|
133
|
+
hold.record,
|
|
134
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
for (const [name, recorded] of records) {
|
|
138
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
139
|
+
if (recorded === null) element.removeAttribute(name);
|
|
140
|
+
else element.setAttribute(name, recorded);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
#reflect(element, value) {
|
|
144
|
+
if (element.getAttribute(this.#attribute) === value) return;
|
|
145
|
+
if (value === null) element.removeAttribute(this.#attribute);
|
|
146
|
+
else element.setAttribute(this.#attribute, value);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
var DetachGate = class _DetachGate {
|
|
151
|
+
#pending = false;
|
|
152
|
+
get pending() {
|
|
153
|
+
return this.#pending;
|
|
154
|
+
}
|
|
155
|
+
static isDetached(host) {
|
|
156
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
157
|
+
}
|
|
158
|
+
disconnected(host, teardown) {
|
|
159
|
+
const carried = _DetachGate.#carried(host);
|
|
160
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
161
|
+
this.#pending = false;
|
|
162
|
+
teardown();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.#pending = true;
|
|
166
|
+
const probe = () => {
|
|
167
|
+
if (!this.#pending) return;
|
|
168
|
+
this.#pending = false;
|
|
169
|
+
teardown();
|
|
170
|
+
};
|
|
171
|
+
if (carried) setTimeout(probe, 0);
|
|
172
|
+
else queueMicrotask(probe);
|
|
173
|
+
}
|
|
174
|
+
static #carried(host) {
|
|
175
|
+
const element = host.element;
|
|
176
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
177
|
+
}
|
|
178
|
+
static #listed(host) {
|
|
179
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
180
|
+
return tokens.includes(host.identifier);
|
|
181
|
+
}
|
|
182
|
+
cancel() {
|
|
183
|
+
this.#pending = false;
|
|
31
184
|
}
|
|
32
185
|
};
|
|
33
186
|
|
|
34
|
-
// src/utils/escape_layer.ts
|
|
35
187
|
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
|
-
*/
|
|
188
|
+
static #registries = sharedRegistry(
|
|
189
|
+
"stimeo-ui.escape-layer.registry.v1",
|
|
190
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
191
|
+
);
|
|
192
|
+
#active = null;
|
|
47
193
|
activate(ownerDocument = document, options) {
|
|
48
194
|
this.deactivate();
|
|
49
195
|
let registry = _EscapeLayer.#registries.get(ownerDocument);
|
|
@@ -52,44 +198,31 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
52
198
|
_EscapeLayer.#registries.set(ownerDocument, registry);
|
|
53
199
|
ownerDocument.addEventListener("keydown", registry.onKeydown);
|
|
54
200
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.#
|
|
58
|
-
this.#claims = options.claims ?? null;
|
|
201
|
+
const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
|
|
202
|
+
registry.stack.push(entry);
|
|
203
|
+
this.#active = { document: ownerDocument, entry };
|
|
59
204
|
}
|
|
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
205
|
deactivate() {
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
67
|
-
const registry = _EscapeLayer.#registries.get(
|
|
206
|
+
const active = this.#active;
|
|
207
|
+
if (!active) return;
|
|
208
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
68
209
|
if (registry) {
|
|
69
|
-
const index = registry.stack.lastIndexOf(
|
|
210
|
+
const index = registry.stack.lastIndexOf(active.entry);
|
|
70
211
|
if (index >= 0) registry.stack.splice(index, 1);
|
|
71
212
|
if (registry.stack.length === 0) {
|
|
72
|
-
|
|
73
|
-
_EscapeLayer.#registries.delete(
|
|
213
|
+
active.document.removeEventListener("keydown", registry.onKeydown);
|
|
214
|
+
_EscapeLayer.#registries.delete(active.document);
|
|
74
215
|
}
|
|
75
216
|
}
|
|
76
|
-
this.#
|
|
77
|
-
this.#onDismiss = null;
|
|
78
|
-
this.#claims = null;
|
|
217
|
+
this.#active = null;
|
|
79
218
|
}
|
|
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
|
-
*/
|
|
85
219
|
get ownsEscape() {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
const registry = _EscapeLayer.#registries.get(
|
|
220
|
+
const active = this.#active;
|
|
221
|
+
if (!active) return false;
|
|
222
|
+
const registry = _EscapeLayer.#registries.get(active.document);
|
|
89
223
|
if (!registry) return false;
|
|
90
|
-
return _EscapeLayer.#resolveOwner(registry.stack) ===
|
|
224
|
+
return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
|
|
91
225
|
}
|
|
92
|
-
/** Builds a document's registry with its shared resolver listener. */
|
|
93
226
|
static #createRegistry() {
|
|
94
227
|
const registry = {
|
|
95
228
|
stack: [],
|
|
@@ -98,24 +231,22 @@ var EscapeLayer = class _EscapeLayer {
|
|
|
98
231
|
const owner = _EscapeLayer.#resolveOwner(registry.stack);
|
|
99
232
|
if (!owner) return;
|
|
100
233
|
event.preventDefault();
|
|
101
|
-
owner
|
|
234
|
+
owner.onDismiss();
|
|
102
235
|
}
|
|
103
236
|
};
|
|
104
237
|
return registry;
|
|
105
238
|
}
|
|
106
|
-
/** The topmost stack layer whose claims predicate passes, or `null`. */
|
|
107
239
|
static #resolveOwner(stack) {
|
|
108
240
|
for (let index = stack.length - 1; index >= 0; index--) {
|
|
109
241
|
const layer = stack[index];
|
|
110
242
|
if (!layer) continue;
|
|
111
|
-
if (layer
|
|
243
|
+
if (layer.claims && !layer.claims()) continue;
|
|
112
244
|
return layer;
|
|
113
245
|
}
|
|
114
246
|
return null;
|
|
115
247
|
}
|
|
116
248
|
};
|
|
117
249
|
|
|
118
|
-
// src/utils/focus_candidate.ts
|
|
119
250
|
function inheritsFieldsetDisabled(control) {
|
|
120
251
|
let fieldset = control.closest("fieldset[disabled]");
|
|
121
252
|
while (fieldset) {
|
|
@@ -125,8 +256,34 @@ function inheritsFieldsetDisabled(control) {
|
|
|
125
256
|
}
|
|
126
257
|
return false;
|
|
127
258
|
}
|
|
259
|
+
function unslotted(element) {
|
|
260
|
+
return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
|
|
261
|
+
}
|
|
262
|
+
function flatTreeParent(element) {
|
|
263
|
+
if (unslotted(element)) return null;
|
|
264
|
+
const parent = element.assignedSlot ?? element.parentNode;
|
|
265
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
266
|
+
return parent instanceof Element ? parent : null;
|
|
267
|
+
}
|
|
268
|
+
function flatTreeContains(ancestor, element) {
|
|
269
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
270
|
+
if (node === ancestor) return true;
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
function closestInFlatTree(element, selector) {
|
|
275
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
276
|
+
if (node.matches(selector)) return node;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
function flatTreeChildren(element) {
|
|
281
|
+
return ownedScope(element) ?? Array.from(element.children);
|
|
282
|
+
}
|
|
128
283
|
function canTakeFocus(element) {
|
|
129
|
-
|
|
284
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
285
|
+
if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
|
|
286
|
+
}
|
|
130
287
|
if (element instanceof HTMLInputElement && element.type === "hidden") return false;
|
|
131
288
|
if (!("disabled" in element)) return true;
|
|
132
289
|
if (element.disabled) return false;
|
|
@@ -183,189 +340,672 @@ function isTabStop(element) {
|
|
|
183
340
|
if (tabindex !== null) return tabindex >= 0;
|
|
184
341
|
return hasNativeTabStop(element) || hasEditableTabStop(element);
|
|
185
342
|
}
|
|
186
|
-
function
|
|
187
|
-
return
|
|
188
|
-
|
|
343
|
+
function isRadio(element) {
|
|
344
|
+
return element instanceof HTMLInputElement && element.type === "radio";
|
|
345
|
+
}
|
|
346
|
+
function sameRadioGroup(a, b) {
|
|
347
|
+
return a.name !== "" && a.name === b.name && a.form === b.form && a.getRootNode() === b.getRootNode();
|
|
348
|
+
}
|
|
349
|
+
function isInEditableContent(element) {
|
|
350
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
351
|
+
if (node.getAttribute("contenteditable")?.toLowerCase() === "false") return false;
|
|
352
|
+
if (hasEditableTabStop(node)) return true;
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
var XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
|
|
357
|
+
function isSvgLink(element) {
|
|
358
|
+
return element instanceof SVGElement && element.localName === "a" && (element.hasAttribute("href") || element.hasAttributeNS(XLINK_NAMESPACE, "href"));
|
|
359
|
+
}
|
|
360
|
+
function hasRenderedImage(area) {
|
|
361
|
+
const map = area.closest("map");
|
|
362
|
+
const key = map?.name || map?.id;
|
|
363
|
+
if (!map || !key) return false;
|
|
364
|
+
const root = map.getRootNode();
|
|
365
|
+
return Array.from(root.querySelectorAll("img[usemap]")).some(
|
|
366
|
+
(image) => image.getAttribute("usemap") === `#${key}` && isRenderedForFocus(image)
|
|
189
367
|
);
|
|
190
368
|
}
|
|
369
|
+
function isFocusableInOrder(element) {
|
|
370
|
+
const tabindex = authoredTabindex(element);
|
|
371
|
+
return (tabindex === null || tabindex >= 0) && canTakeFocus(element);
|
|
372
|
+
}
|
|
373
|
+
function extraStop(element) {
|
|
374
|
+
if (element instanceof HTMLAreaElement) {
|
|
375
|
+
const focusable = element.hasAttribute("href") || authoredTabindex(element) !== null;
|
|
376
|
+
return focusable && isFocusableInOrder(element) && hasRenderedImage(element);
|
|
377
|
+
}
|
|
378
|
+
if (element instanceof HTMLObjectElement) {
|
|
379
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element) && element.contentWindow !== null;
|
|
380
|
+
}
|
|
381
|
+
if (isSvgLink(element))
|
|
382
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element);
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
function isSequentialStop(element) {
|
|
386
|
+
const stop = extraStop(element) ?? (element.matches(TAB_STOP_CANDIDATE_SELECTOR) && isTabStop(element));
|
|
387
|
+
if (!stop) return false;
|
|
388
|
+
const link = element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement || isSvgLink(element);
|
|
389
|
+
return !(link && authoredTabindex(element) === null && isInEditableContent(element));
|
|
390
|
+
}
|
|
391
|
+
function ownedScope(element) {
|
|
392
|
+
if (element.shadowRoot) return Array.from(element.shadowRoot.children);
|
|
393
|
+
if (element instanceof HTMLSlotElement) {
|
|
394
|
+
const assigned = element.assignedElements();
|
|
395
|
+
return assigned.length > 0 ? assigned : Array.from(element.children);
|
|
396
|
+
}
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
function orderScope(elements, walk) {
|
|
400
|
+
const entries = [];
|
|
401
|
+
const visit = (element) => {
|
|
402
|
+
if (element === walk.from) walk.fromAt = walk.tree.length;
|
|
403
|
+
const tabindex = authoredTabindex(element) ?? 0;
|
|
404
|
+
const stop = (element instanceof HTMLElement || element instanceof SVGElement) && isSequentialStop(element) ? element : null;
|
|
405
|
+
if (stop) walk.tree.push(stop);
|
|
406
|
+
const owned = ownedScope(element);
|
|
407
|
+
if (owned) {
|
|
408
|
+
if (tabindex >= 0) {
|
|
409
|
+
entries.push({ tabindex, stops: [...stop ? [stop] : [], ...orderScope(owned, walk)] });
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (stop) entries.push({ tabindex, stops: [stop] });
|
|
414
|
+
for (const child of Array.from(element.children)) visit(child);
|
|
415
|
+
};
|
|
416
|
+
for (const element of elements) visit(element);
|
|
417
|
+
const positive = entries.filter((entry) => entry.tabindex > 0);
|
|
418
|
+
positive.sort((a, b) => a.tabindex - b.tabindex);
|
|
419
|
+
const rest = entries.filter((entry) => entry.tabindex === 0);
|
|
420
|
+
return [...positive, ...rest].flatMap((entry) => entry.stops);
|
|
421
|
+
}
|
|
422
|
+
function passesRadioRule(element, tree, focusedRadio) {
|
|
423
|
+
if (!isRadio(element) || element.checked) return true;
|
|
424
|
+
const group = tree.filter(
|
|
425
|
+
(stop) => stop === element || isRadio(stop) && sameRadioGroup(stop, element)
|
|
426
|
+
);
|
|
427
|
+
if (group.some((radio) => radio.checked)) return false;
|
|
428
|
+
if (group[0] !== element) return false;
|
|
429
|
+
return focusedRadio === null || !sameRadioGroup(focusedRadio, element);
|
|
430
|
+
}
|
|
431
|
+
function startIndex(order, walk, backward, isStop) {
|
|
432
|
+
if (walk.fromAt < 0) return backward ? order.length - 1 : 0;
|
|
433
|
+
const at = order.indexOf(walk.from);
|
|
434
|
+
if (at >= 0) return backward ? at - 1 : at + 1;
|
|
435
|
+
const neighbour = backward ? walk.tree.slice(0, walk.fromAt).reverse().find(isStop) : walk.tree.slice(walk.fromAt).find(isStop);
|
|
436
|
+
if (neighbour) return order.indexOf(neighbour);
|
|
437
|
+
return backward ? order.length - 1 : 0;
|
|
438
|
+
}
|
|
439
|
+
function sequentialTabStops(root, from = null, backward = false) {
|
|
440
|
+
const walk = { from, tree: [], fromAt: from === root ? 0 : -1 };
|
|
441
|
+
const order = orderScope(ownedScope(root) ?? Array.from(root.children), walk);
|
|
442
|
+
const focusedRadio = walk.fromAt >= 0 && isRadio(from) ? from : null;
|
|
443
|
+
const isStop = (element) => passesRadioRule(element, walk.tree, focusedRadio);
|
|
444
|
+
const start = startIndex(order, walk, backward, isStop);
|
|
445
|
+
const visited = backward ? [...order.slice(0, start + 1).reverse(), ...order.slice(start + 1).reverse()] : [...order.slice(start), ...order.slice(0, start)];
|
|
446
|
+
return visited.filter((element) => element !== from && isStop(element));
|
|
447
|
+
}
|
|
448
|
+
function deepActiveElement(ownerDocument) {
|
|
449
|
+
let active = ownerDocument.activeElement;
|
|
450
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
451
|
+
return active;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
var MicrotaskCoalescer = class {
|
|
455
|
+
#run;
|
|
456
|
+
#queued = false;
|
|
457
|
+
#active = false;
|
|
458
|
+
#generation = 0;
|
|
459
|
+
constructor(run) {
|
|
460
|
+
this.#run = run;
|
|
461
|
+
}
|
|
462
|
+
activate() {
|
|
463
|
+
this.#active = true;
|
|
464
|
+
}
|
|
465
|
+
cancel() {
|
|
466
|
+
this.#active = false;
|
|
467
|
+
this.#queued = false;
|
|
468
|
+
this.#generation += 1;
|
|
469
|
+
}
|
|
470
|
+
schedule() {
|
|
471
|
+
if (!this.#active || this.#queued) return;
|
|
472
|
+
this.#queued = true;
|
|
473
|
+
const generation = this.#generation;
|
|
474
|
+
queueMicrotask(() => {
|
|
475
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
476
|
+
this.#queued = false;
|
|
477
|
+
this.#run();
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
function queryOpenDescendants(root, selector) {
|
|
483
|
+
const matches = [];
|
|
484
|
+
const roots = [root];
|
|
485
|
+
for (const scope of roots) {
|
|
486
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
487
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
488
|
+
const shadow = scope.shadowRoot;
|
|
489
|
+
if (shadow) roots.push(shadow);
|
|
490
|
+
}
|
|
491
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
492
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return matches;
|
|
496
|
+
}
|
|
191
497
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
#
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
498
|
+
var TabindexLoan = class _TabindexLoan {
|
|
499
|
+
static #registry = sharedRegistry(
|
|
500
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
501
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
502
|
+
);
|
|
503
|
+
#value;
|
|
504
|
+
#record;
|
|
505
|
+
#lent = /* @__PURE__ */ new Set();
|
|
506
|
+
constructor(value, owner) {
|
|
507
|
+
this.#value = value;
|
|
508
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
509
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
510
|
+
}
|
|
511
|
+
lend(element) {
|
|
512
|
+
const stack = this.#stack(element);
|
|
513
|
+
const current = element.getAttribute("tabindex");
|
|
514
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
515
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
516
|
+
);
|
|
517
|
+
const linked = stack.at(-1)?.value === current;
|
|
518
|
+
if (!held && current !== null && !linked) return;
|
|
519
|
+
this.#lent.add(element);
|
|
520
|
+
if (held) held.loan = this;
|
|
521
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
522
|
+
this.#mark(element, stack);
|
|
523
|
+
if (current === null || !held && current !== this.#value) {
|
|
524
|
+
element.setAttribute("tabindex", this.#value);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
returnAll() {
|
|
528
|
+
for (const element of this.#lent) {
|
|
529
|
+
const stack = this.#stack(element);
|
|
530
|
+
const hold = stack.find((held) => held.loan === this);
|
|
531
|
+
if (hold) this.#release(element, stack, hold);
|
|
532
|
+
}
|
|
533
|
+
this.#lent.clear();
|
|
534
|
+
}
|
|
535
|
+
reclaim(element) {
|
|
536
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
537
|
+
const stack = this.#stack(element);
|
|
538
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
539
|
+
if (hold && !element.matches(":focus")) this.#release(element, stack, hold);
|
|
540
|
+
}
|
|
541
|
+
reclaimWithin(root) {
|
|
542
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
543
|
+
this.reclaim(element);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
#stack(element) {
|
|
547
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
548
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
549
|
+
stacks.set(element, stack);
|
|
550
|
+
return stack;
|
|
551
|
+
}
|
|
552
|
+
#seed(element) {
|
|
553
|
+
const current = element.getAttribute("tabindex");
|
|
554
|
+
const holds = [];
|
|
555
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
556
|
+
const value = element.getAttribute(record);
|
|
557
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
558
|
+
}
|
|
559
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
560
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
561
|
+
return holds;
|
|
562
|
+
}
|
|
563
|
+
#release(element, stack, hold) {
|
|
564
|
+
const current = element.getAttribute("tabindex");
|
|
565
|
+
const index = stack.indexOf(hold);
|
|
566
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
567
|
+
stack.splice(index, 1);
|
|
568
|
+
const above = stack[index];
|
|
569
|
+
if (above) above.linked &&= hold.linked;
|
|
570
|
+
this.#mark(element, stack);
|
|
571
|
+
if (!owns) return;
|
|
572
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
573
|
+
if (!below) element.removeAttribute("tabindex");
|
|
574
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
575
|
+
}
|
|
576
|
+
#mark(element, stack) {
|
|
577
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
578
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
579
|
+
for (const [name, recorded] of records) {
|
|
580
|
+
if (element.getAttribute(name) === recorded) continue;
|
|
581
|
+
if (recorded === null) element.removeAttribute(name);
|
|
582
|
+
else element.setAttribute(name, recorded);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
var FOCUS_TRAP_OWNER = "stimeo-focus-trap";
|
|
588
|
+
var INERT_MARK = `data-${FOCUS_TRAP_OWNER}-inert`;
|
|
589
|
+
var OVERFLOW_MARK = `data-${FOCUS_TRAP_OWNER}-overflow`;
|
|
590
|
+
var FocusTrap = class _FocusTrap {
|
|
591
|
+
static #registries = sharedRegistry(
|
|
592
|
+
"stimeo-ui.focus-trap.registry.v2",
|
|
593
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
594
|
+
);
|
|
595
|
+
static #TEXT_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
596
|
+
"text",
|
|
597
|
+
"search",
|
|
598
|
+
"email",
|
|
599
|
+
"url",
|
|
600
|
+
"tel",
|
|
601
|
+
"password",
|
|
602
|
+
"number"
|
|
603
|
+
]);
|
|
604
|
+
#openers = [];
|
|
605
|
+
#entry;
|
|
606
|
+
#tabindexLoans = /* @__PURE__ */ new Map();
|
|
203
607
|
#activeState = false;
|
|
204
|
-
/** Registers the trap on the shared Escape stack while active (see {@link EscapeLayer}). */
|
|
205
608
|
#escapeLayer = new EscapeLayer();
|
|
206
|
-
|
|
609
|
+
#gate = new DetachGate();
|
|
207
610
|
#getContainer;
|
|
208
|
-
/** Closing/focus hooks; see {@link FocusTrapOptions}. */
|
|
209
611
|
#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
612
|
constructor(getContainer, options = {}) {
|
|
216
613
|
this.#getContainer = getContainer;
|
|
217
614
|
this.#options = options;
|
|
615
|
+
this.#entry = {
|
|
616
|
+
container: () => this.#readContainer(),
|
|
617
|
+
moveFocus: (backward) => this.#moveFocus(backward),
|
|
618
|
+
refocus: () => this.#refocus(),
|
|
619
|
+
isolated: false,
|
|
620
|
+
lockedBody: null,
|
|
621
|
+
openedFrom: null,
|
|
622
|
+
handedBack: []
|
|
623
|
+
};
|
|
218
624
|
}
|
|
219
|
-
/** Whether the trap is currently active. */
|
|
220
625
|
get active() {
|
|
221
626
|
return this.#activeState;
|
|
222
627
|
}
|
|
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
628
|
activate() {
|
|
229
629
|
if (this.#activeState) return;
|
|
630
|
+
_FocusTrap.#sweep();
|
|
230
631
|
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();
|
|
632
|
+
this.#openers = _FocusTrap.#focusPath();
|
|
633
|
+
this.#entry.openedFrom = _FocusTrap.#trapHolding(this.#openers[0] ?? null);
|
|
634
|
+
if (this.#flag(this.#options.lockScroll, true)) this.#lockScroll();
|
|
635
|
+
this.#entry.isolated = this.#flag(this.#options.isolate, true);
|
|
636
|
+
this.#joinStack();
|
|
637
|
+
_FocusTrap.#reconcileBackground(document);
|
|
241
638
|
const onEscape = this.#options.onEscape;
|
|
242
639
|
if (onEscape) this.#escapeLayer.activate(document, { onDismiss: () => onEscape() });
|
|
243
640
|
if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
|
|
244
641
|
}
|
|
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
642
|
deactivate({ restoreFocus = true } = {}) {
|
|
253
|
-
if (!this.#activeState)
|
|
643
|
+
if (!this.#activeState) {
|
|
644
|
+
this.#returnTabindexes();
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
254
647
|
this.#activeState = false;
|
|
255
648
|
this.#escapeLayer.deactivate();
|
|
256
|
-
|
|
257
|
-
this.#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
649
|
+
const above = this.#leaveStack();
|
|
650
|
+
this.#unlockScroll();
|
|
651
|
+
_FocusTrap.#reconcileBackground(document);
|
|
652
|
+
const wayBack = this.#handOver(above);
|
|
653
|
+
if (restoreFocus) this.#restore(wayBack, above);
|
|
654
|
+
this.#returnTabindexes();
|
|
655
|
+
}
|
|
656
|
+
connect() {
|
|
657
|
+
this.#gate.cancel();
|
|
658
|
+
_FocusTrap.#sweep();
|
|
659
|
+
new TabindexLoan("-1", FOCUS_TRAP_OWNER).reclaimWithin(document);
|
|
660
|
+
}
|
|
661
|
+
disconnect(host) {
|
|
662
|
+
if (this.#activeState) this.deactivate({ restoreFocus: false });
|
|
663
|
+
this.#gate.disconnected(host, () => this.#returnTabindexes());
|
|
664
|
+
}
|
|
665
|
+
refreshIsolation() {
|
|
666
|
+
if (!this.#activeState) return;
|
|
667
|
+
const isolate = this.#flag(this.#options.isolate, true);
|
|
668
|
+
if (isolate === this.#entry.isolated) return;
|
|
669
|
+
this.#entry.isolated = isolate;
|
|
670
|
+
const stranded = _FocusTrap.#reconcileBackground(document);
|
|
671
|
+
const active = deepActiveElement(document);
|
|
672
|
+
if (stranded.some((element) => flatTreeContains(element, active))) this.#focusInitial();
|
|
673
|
+
}
|
|
674
|
+
refreshContainer() {
|
|
675
|
+
if (!this.#activeState) return;
|
|
676
|
+
_FocusTrap.#reconcileBackground(document);
|
|
677
|
+
this.#returnTabindexes();
|
|
678
|
+
if (this.#ownsTab()) this.#refocus();
|
|
267
679
|
}
|
|
268
|
-
/** Resolves a boolean-or-getter option, defaulting when it was not provided. */
|
|
269
680
|
#flag(option, fallback) {
|
|
270
681
|
if (option === void 0) return fallback;
|
|
271
682
|
return typeof option === "function" ? option() : option;
|
|
272
683
|
}
|
|
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;
|
|
684
|
+
#joinStack() {
|
|
685
|
+
const registry = _FocusTrap.#registry(document);
|
|
686
|
+
if (registry.stack.length === 0) {
|
|
687
|
+
document.addEventListener("keydown", registry.onKeydown);
|
|
688
|
+
document.addEventListener("turbo:morph", registry.onMorph);
|
|
689
|
+
document.addEventListener("turbo:morph-element", registry.onMorph);
|
|
296
690
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
691
|
+
registry.stack.push(this.#entry);
|
|
692
|
+
}
|
|
693
|
+
#leaveStack() {
|
|
694
|
+
const registry = _FocusTrap.#registry(document);
|
|
695
|
+
const index = registry.stack.indexOf(this.#entry);
|
|
696
|
+
const above = registry.stack.slice(index + 1);
|
|
697
|
+
registry.stack.splice(index, 1);
|
|
698
|
+
if (registry.stack.length === 0) {
|
|
699
|
+
document.removeEventListener("keydown", registry.onKeydown);
|
|
700
|
+
document.removeEventListener("turbo:morph", registry.onMorph);
|
|
701
|
+
document.removeEventListener("turbo:morph-element", registry.onMorph);
|
|
304
702
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
703
|
+
return above;
|
|
704
|
+
}
|
|
705
|
+
static #focusPath() {
|
|
706
|
+
const path = [];
|
|
707
|
+
for (let active = document.activeElement; active; active = active.shadowRoot?.activeElement ?? null) {
|
|
708
|
+
if (active instanceof HTMLElement && active !== document.body) path.unshift(active);
|
|
709
|
+
}
|
|
710
|
+
return path;
|
|
711
|
+
}
|
|
712
|
+
static #trapHolding(element) {
|
|
713
|
+
for (const trap of [..._FocusTrap.#registry(document).stack].reverse()) {
|
|
714
|
+
const container = trap.container();
|
|
715
|
+
if (container && flatTreeContains(container, element)) return trap;
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
#handOver(above) {
|
|
720
|
+
const entry = this.#entry;
|
|
721
|
+
const wayBack = [
|
|
722
|
+
...this.#openers,
|
|
723
|
+
this.#options.fallbackFocus?.() ?? null,
|
|
724
|
+
...entry.handedBack
|
|
725
|
+
].filter((element) => element !== null);
|
|
726
|
+
for (const trap of above) {
|
|
727
|
+
if (trap.openedFrom !== entry) continue;
|
|
728
|
+
trap.handedBack = [...trap.handedBack, ...wayBack];
|
|
729
|
+
trap.openedFrom = entry.openedFrom;
|
|
730
|
+
}
|
|
731
|
+
this.#openers = [];
|
|
732
|
+
entry.openedFrom = null;
|
|
733
|
+
entry.handedBack = [];
|
|
734
|
+
return wayBack;
|
|
735
|
+
}
|
|
736
|
+
#restore(wayBack, above) {
|
|
737
|
+
const active = deepActiveElement(document);
|
|
738
|
+
const released = this.#readContainer();
|
|
739
|
+
const holds = (trap) => {
|
|
740
|
+
const container = trap.container();
|
|
741
|
+
if (!container || !_FocusTrap.#takesTab(trap) || !flatTreeContains(container, active)) {
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
return !released || !flatTreeContains(released, active) || flatTreeContains(released, container);
|
|
745
|
+
};
|
|
746
|
+
if (above.some(holds)) return;
|
|
747
|
+
for (const element of wayBack) {
|
|
748
|
+
if (this.#activeState) return;
|
|
749
|
+
if (!element.isConnected || !canTakeFocus(element) || !isRenderedForFocus(element)) continue;
|
|
750
|
+
const before = deepActiveElement(document);
|
|
751
|
+
element.focus();
|
|
752
|
+
const after = deepActiveElement(document);
|
|
753
|
+
if (after === element || after !== before) return;
|
|
754
|
+
}
|
|
755
|
+
_FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack)?.refocus();
|
|
756
|
+
}
|
|
757
|
+
#refocus() {
|
|
758
|
+
if (!this.#flag(this.#options.autoFocus, true)) return;
|
|
759
|
+
if (flatTreeContains(this.#getContainer(), deepActiveElement(document))) return;
|
|
760
|
+
this.#focusInitial();
|
|
761
|
+
}
|
|
762
|
+
#lendTabindex(container) {
|
|
763
|
+
const loan = this.#tabindexLoans.get(container) ?? new TabindexLoan("-1", FOCUS_TRAP_OWNER);
|
|
764
|
+
loan.lend(container);
|
|
765
|
+
this.#tabindexLoans.set(container, loan);
|
|
766
|
+
}
|
|
767
|
+
#returnTabindexes() {
|
|
768
|
+
const focused = deepActiveElement(document);
|
|
769
|
+
for (const [element, loan] of [...this.#tabindexLoans]) {
|
|
770
|
+
if (element === focused) continue;
|
|
771
|
+
loan.returnAll();
|
|
772
|
+
this.#tabindexLoans.delete(element);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
#lockScroll() {
|
|
776
|
+
const registry = _FocusTrap.#registry(document);
|
|
777
|
+
const body = document.body;
|
|
778
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
779
|
+
registry.scrollLocks.add(this.#entry);
|
|
780
|
+
this.#entry.lockedBody = body;
|
|
781
|
+
body.style.overflow = "hidden";
|
|
782
|
+
}
|
|
783
|
+
#unlockScroll() {
|
|
784
|
+
const registry = _FocusTrap.#registry(document);
|
|
785
|
+
const body = this.#entry.lockedBody;
|
|
786
|
+
this.#entry.lockedBody = null;
|
|
787
|
+
if (!registry.scrollLocks.delete(this.#entry) || !body) return;
|
|
788
|
+
if (!_FocusTrap.#isLocked(registry, body)) _FocusTrap.#unlock(body);
|
|
789
|
+
}
|
|
790
|
+
static #isLocked(registry, body) {
|
|
791
|
+
return [...registry.scrollLocks].some((trap) => trap.lockedBody === body);
|
|
792
|
+
}
|
|
793
|
+
static #unlock(body) {
|
|
794
|
+
const saved = body.getAttribute(OVERFLOW_MARK);
|
|
795
|
+
if (saved === null) return;
|
|
796
|
+
body.removeAttribute(OVERFLOW_MARK);
|
|
797
|
+
body.style.overflow = saved;
|
|
798
|
+
}
|
|
799
|
+
static #sweep() {
|
|
800
|
+
const registry = _FocusTrap.#registry(document);
|
|
801
|
+
for (const element of queryOpenDescendants(document, `[${INERT_MARK}]`)) {
|
|
802
|
+
if (registry.inerted.has(element)) continue;
|
|
803
|
+
element.removeAttribute(INERT_MARK);
|
|
804
|
+
element.inert = false;
|
|
805
|
+
}
|
|
806
|
+
if (!_FocusTrap.#isLocked(registry, document.body)) _FocusTrap.#unlock(document.body);
|
|
807
|
+
}
|
|
808
|
+
static #registry(ownerDocument) {
|
|
809
|
+
const existing = _FocusTrap.#registries.get(ownerDocument);
|
|
810
|
+
if (existing) return existing;
|
|
811
|
+
const morphs = new MicrotaskCoalescer(() => _FocusTrap.#reapply(registry));
|
|
812
|
+
morphs.activate();
|
|
813
|
+
const registry = {
|
|
814
|
+
stack: [],
|
|
815
|
+
onKeydown: (event) => _FocusTrap.#resolveTab(registry.stack, event),
|
|
816
|
+
onMorph: () => morphs.schedule(),
|
|
817
|
+
inerted: /* @__PURE__ */ new Set(),
|
|
818
|
+
scrollLocks: /* @__PURE__ */ new Set()
|
|
819
|
+
};
|
|
820
|
+
_FocusTrap.#registries.set(ownerDocument, registry);
|
|
821
|
+
return registry;
|
|
822
|
+
}
|
|
823
|
+
static #reapply(registry) {
|
|
824
|
+
_FocusTrap.#reconcileBackground(document);
|
|
825
|
+
const body = document.body;
|
|
826
|
+
if (!_FocusTrap.#isLocked(registry, body)) return;
|
|
827
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
828
|
+
body.style.overflow = "hidden";
|
|
829
|
+
}
|
|
830
|
+
static #reconcileBackground(ownerDocument) {
|
|
831
|
+
const registry = _FocusTrap.#registry(ownerDocument);
|
|
832
|
+
for (const element of registry.inerted) {
|
|
833
|
+
if (!element.hasAttribute(INERT_MARK)) registry.inerted.delete(element);
|
|
834
|
+
}
|
|
835
|
+
const background = _FocusTrap.#backgroundOf(registry);
|
|
836
|
+
for (const element of registry.inerted) {
|
|
837
|
+
if (background.has(element)) continue;
|
|
838
|
+
element.removeAttribute(INERT_MARK);
|
|
839
|
+
element.inert = false;
|
|
840
|
+
registry.inerted.delete(element);
|
|
841
|
+
}
|
|
842
|
+
const added = [];
|
|
843
|
+
for (const element of background) {
|
|
844
|
+
if (registry.inerted.has(element)) continue;
|
|
845
|
+
element.setAttribute(INERT_MARK, "");
|
|
846
|
+
element.inert = true;
|
|
847
|
+
registry.inerted.add(element);
|
|
848
|
+
added.push(element);
|
|
849
|
+
}
|
|
850
|
+
return added;
|
|
851
|
+
}
|
|
852
|
+
static #backgroundOf(registry) {
|
|
853
|
+
const containers = registry.stack.map((trap) => trap.container());
|
|
854
|
+
let top = -1;
|
|
855
|
+
registry.stack.forEach((trap, index) => {
|
|
856
|
+
if (trap.isolated && containers[index]) top = index;
|
|
857
|
+
});
|
|
858
|
+
const background = /* @__PURE__ */ new Set();
|
|
859
|
+
if (top < 0) return background;
|
|
860
|
+
const kept = /* @__PURE__ */ new Set();
|
|
861
|
+
const keep = (path) => {
|
|
862
|
+
for (const node of path) {
|
|
863
|
+
kept.add(node);
|
|
864
|
+
background.delete(node);
|
|
865
|
+
}
|
|
866
|
+
for (const node of path) {
|
|
867
|
+
const parent = flatTreeParent(node);
|
|
868
|
+
for (const sibling of parent ? flatTreeChildren(parent) : []) {
|
|
869
|
+
if (!(sibling instanceof HTMLElement) || kept.has(sibling)) continue;
|
|
870
|
+
if (sibling.inert && !registry.inerted.has(sibling)) continue;
|
|
871
|
+
background.add(sibling);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
containers.slice(top).forEach((container, index) => {
|
|
876
|
+
const path = [];
|
|
877
|
+
for (let node = container; node && node !== document.body && !kept.has(node); ) {
|
|
878
|
+
path.push(node);
|
|
879
|
+
node = flatTreeParent(node);
|
|
335
880
|
}
|
|
336
|
-
node
|
|
881
|
+
if (index === 0 || path.some((node) => background.has(node))) keep(path);
|
|
882
|
+
});
|
|
883
|
+
return background;
|
|
884
|
+
}
|
|
885
|
+
#readContainer() {
|
|
886
|
+
try {
|
|
887
|
+
return this.#getContainer();
|
|
888
|
+
} catch {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
static #resolveTab(stack, event) {
|
|
893
|
+
if (event.key !== "Tab" || event.defaultPrevented) return;
|
|
894
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
895
|
+
if (event.ctrlKey || event.metaKey) return;
|
|
896
|
+
const owner = _FocusTrap.#tabOwner(stack);
|
|
897
|
+
if (!owner) return;
|
|
898
|
+
event.preventDefault();
|
|
899
|
+
owner.moveFocus(event.shiftKey);
|
|
900
|
+
}
|
|
901
|
+
static #tabOwner(stack) {
|
|
902
|
+
for (const trap of [...stack].reverse()) {
|
|
903
|
+
if (_FocusTrap.#takesTab(trap)) return trap;
|
|
337
904
|
}
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
static #takesTab(trap) {
|
|
908
|
+
const container = trap.container();
|
|
909
|
+
if (!container) return false;
|
|
910
|
+
return container.isConnected && !closestInFlatTree(container, "[hidden]") && isRenderedForFocus(container);
|
|
338
911
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
912
|
+
#ownsTab() {
|
|
913
|
+
return _FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack) === this.#entry;
|
|
914
|
+
}
|
|
915
|
+
#moveFocus(backward) {
|
|
916
|
+
const from = deepActiveElement(document);
|
|
917
|
+
for (const stop of sequentialTabStops(this.#getContainer(), from, backward)) {
|
|
918
|
+
if (!this.#ownsTab()) return;
|
|
919
|
+
const before = deepActiveElement(document);
|
|
920
|
+
stop.focus();
|
|
921
|
+
const after = deepActiveElement(document);
|
|
922
|
+
if (after === before) continue;
|
|
923
|
+
if (after === stop) _FocusTrap.#selectOnEntry(stop);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
static #selectOnEntry(stop) {
|
|
928
|
+
if (stop instanceof HTMLInputElement && _FocusTrap.#TEXT_ENTRY_TYPES.has(stop.type)) {
|
|
929
|
+
stop.select();
|
|
343
930
|
}
|
|
344
|
-
this.#inertedSiblings = [];
|
|
345
931
|
}
|
|
346
|
-
/** Moves focus to the initial target, the first focusable, or the container. */
|
|
347
932
|
#focusInitial() {
|
|
348
933
|
const preferred = this.#options.initialFocus?.();
|
|
349
934
|
if (preferred) {
|
|
350
935
|
preferred.focus();
|
|
351
936
|
return;
|
|
352
937
|
}
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
938
|
+
const container = this.#getContainer();
|
|
939
|
+
const [first] = sequentialTabStops(container);
|
|
940
|
+
if (first) {
|
|
941
|
+
first.focus();
|
|
356
942
|
return;
|
|
357
943
|
}
|
|
358
|
-
|
|
359
|
-
container.tabIndex = -1;
|
|
944
|
+
this.#lendTabindex(container);
|
|
360
945
|
container.focus();
|
|
361
946
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
var ListenerSet = class {
|
|
950
|
+
#abort = new AbortController();
|
|
951
|
+
add(target, type, handler, options) {
|
|
952
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
953
|
+
}
|
|
954
|
+
dispose() {
|
|
955
|
+
this.#abort.abort();
|
|
956
|
+
this.#abort = new AbortController();
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
var MorphRenderWatcher = class {
|
|
961
|
+
#listeners = new ListenerSet();
|
|
962
|
+
#pass;
|
|
963
|
+
#element = null;
|
|
964
|
+
#onMorph = () => this.#pass.schedule();
|
|
965
|
+
constructor(run) {
|
|
966
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
967
|
+
}
|
|
968
|
+
observe(element) {
|
|
969
|
+
if (this.#element === element) return;
|
|
970
|
+
this.disconnect();
|
|
971
|
+
this.#element = element;
|
|
972
|
+
this.#pass.activate();
|
|
973
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
974
|
+
}
|
|
975
|
+
schedule() {
|
|
976
|
+
this.#pass.schedule();
|
|
977
|
+
}
|
|
978
|
+
disconnect() {
|
|
979
|
+
this.#listeners.dispose();
|
|
980
|
+
this.#pass.cancel();
|
|
981
|
+
this.#element = null;
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
var LivedMark = class {
|
|
986
|
+
#name;
|
|
987
|
+
#lived = false;
|
|
988
|
+
#element = null;
|
|
989
|
+
#morph = new MorphRenderWatcher(() => this.#write());
|
|
990
|
+
constructor(identifier) {
|
|
991
|
+
this.#name = `data-${identifier}-lived`;
|
|
992
|
+
}
|
|
993
|
+
connect(element) {
|
|
994
|
+
const connection = this.#lived ? "reconnect" : element.hasAttribute(this.#name) ? "restored" : "fresh";
|
|
995
|
+
this.#lived = true;
|
|
996
|
+
this.#element = element;
|
|
997
|
+
this.#write();
|
|
998
|
+
this.#morph.observe(element);
|
|
999
|
+
return connection;
|
|
1000
|
+
}
|
|
1001
|
+
disconnect() {
|
|
1002
|
+
this.#morph.disconnect();
|
|
1003
|
+
}
|
|
1004
|
+
#write() {
|
|
1005
|
+
this.#element?.setAttribute(this.#name, "");
|
|
365
1006
|
}
|
|
366
1007
|
};
|
|
367
1008
|
|
|
368
|
-
// src/utils/state_reason.ts
|
|
369
1009
|
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
370
1010
|
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
371
1011
|
function stateReasonFor(event) {
|
|
@@ -375,43 +1015,24 @@ function stateReasonFor(event) {
|
|
|
375
1015
|
return "user";
|
|
376
1016
|
}
|
|
377
1017
|
|
|
378
|
-
// src/utils/safe_timeout.ts
|
|
379
1018
|
var TimerRegistry = class {
|
|
380
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
381
1019
|
ids = /* @__PURE__ */ new Set();
|
|
382
|
-
/**
|
|
383
|
-
* Cancels a single tracked timer.
|
|
384
|
-
*
|
|
385
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
386
|
-
* registry), so callers can clear defensively without guarding.
|
|
387
|
-
*/
|
|
388
1020
|
clear(id) {
|
|
389
1021
|
if (this.ids.delete(id)) {
|
|
390
1022
|
this.cancel(id);
|
|
391
1023
|
}
|
|
392
1024
|
}
|
|
393
|
-
/**
|
|
394
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
395
|
-
* to guarantee no timer outlives the element.
|
|
396
|
-
*/
|
|
397
1025
|
clearAll() {
|
|
398
1026
|
for (const id of this.ids) {
|
|
399
1027
|
this.cancel(id);
|
|
400
1028
|
}
|
|
401
1029
|
this.ids.clear();
|
|
402
1030
|
}
|
|
403
|
-
/** Number of timers currently tracked (pending). */
|
|
404
1031
|
get size() {
|
|
405
1032
|
return this.ids.size;
|
|
406
1033
|
}
|
|
407
1034
|
};
|
|
408
1035
|
var SafeTimeout = class extends TimerRegistry {
|
|
409
|
-
/**
|
|
410
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
411
|
-
*
|
|
412
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
413
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
414
|
-
*/
|
|
415
1036
|
set(callback, delay) {
|
|
416
1037
|
const id = this.schedule(() => {
|
|
417
1038
|
this.ids.delete(id);
|
|
@@ -428,7 +1049,6 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
428
1049
|
}
|
|
429
1050
|
};
|
|
430
1051
|
|
|
431
|
-
// src/utils/transition_completion.ts
|
|
432
1052
|
function timeMs(value) {
|
|
433
1053
|
const trimmed = value.trim();
|
|
434
1054
|
const amount = Number.parseFloat(trimmed);
|
|
@@ -464,13 +1084,6 @@ var TransitionCompletion = class {
|
|
|
464
1084
|
#complete = null;
|
|
465
1085
|
#pendingProperties = null;
|
|
466
1086
|
#deadline = 0;
|
|
467
|
-
/**
|
|
468
|
-
* Replaces any prior wait and invokes `complete` synchronously for a 0ms
|
|
469
|
-
* transition (including when `getComputedStyle` is unavailable).
|
|
470
|
-
*
|
|
471
|
-
* With a positive `options.timeoutMs` the synchronous fast-path is skipped and
|
|
472
|
-
* the override replaces the auto-computed fallback (see {@link TransitionWaitOptions}).
|
|
473
|
-
*/
|
|
474
1087
|
wait(element, complete, options = {}) {
|
|
475
1088
|
this.cancel();
|
|
476
1089
|
const requested = options.timeoutMs ?? 0;
|
|
@@ -490,18 +1103,10 @@ var TransitionCompletion = class {
|
|
|
490
1103
|
element.addEventListener("transitioncancel", this.#onTerminal);
|
|
491
1104
|
this.#timers.set(() => this.#finish(), override > 0 ? override : maximum + 50);
|
|
492
1105
|
}
|
|
493
|
-
/** Cancels the pending wait without invoking its completion callback. */
|
|
494
1106
|
cancel() {
|
|
495
1107
|
this.#complete = null;
|
|
496
1108
|
this.#teardown();
|
|
497
1109
|
}
|
|
498
|
-
/**
|
|
499
|
-
* Handles terminal events from the observed element only.
|
|
500
|
-
*
|
|
501
|
-
* For explicit property lists, every declared positive-time property must
|
|
502
|
-
* settle. For `all`, no reliable property set exists, so an event can finish
|
|
503
|
-
* only after the computed maximum time; the safety timeout owns the usual path.
|
|
504
|
-
*/
|
|
505
1110
|
#onTerminal = (event) => {
|
|
506
1111
|
if (event.target !== this.#element) return;
|
|
507
1112
|
const transitionEvent = event;
|
|
@@ -521,10 +1126,6 @@ var TransitionCompletion = class {
|
|
|
521
1126
|
}
|
|
522
1127
|
if (Date.now() >= this.#deadline) this.#finish();
|
|
523
1128
|
};
|
|
524
|
-
/**
|
|
525
|
-
* Returns active CSS transition properties expanded to the names reported by
|
|
526
|
-
* terminal events. CSS animations and pseudo-element effects are excluded.
|
|
527
|
-
*/
|
|
528
1129
|
#activeTransitionProperties(element) {
|
|
529
1130
|
if (!element || typeof element.getAnimations !== "function") return /* @__PURE__ */ new Set();
|
|
530
1131
|
try {
|
|
@@ -542,7 +1143,6 @@ var TransitionCompletion = class {
|
|
|
542
1143
|
return /* @__PURE__ */ new Set();
|
|
543
1144
|
}
|
|
544
1145
|
}
|
|
545
|
-
/** Returns explicit positive-time properties, or `null` for the ambiguous `all`. */
|
|
546
1146
|
#explicitPendingProperties(timings) {
|
|
547
1147
|
if (timings.some(({ property }) => property === "all")) return null;
|
|
548
1148
|
const pending = new Set(
|
|
@@ -550,7 +1150,6 @@ var TransitionCompletion = class {
|
|
|
550
1150
|
);
|
|
551
1151
|
return pending.size > 0 ? pending : null;
|
|
552
1152
|
}
|
|
553
|
-
/** Completes exactly once, releasing listeners and the fallback before the callback. */
|
|
554
1153
|
#finish() {
|
|
555
1154
|
const complete = this.#complete;
|
|
556
1155
|
if (!complete) return;
|
|
@@ -558,7 +1157,6 @@ var TransitionCompletion = class {
|
|
|
558
1157
|
this.#teardown();
|
|
559
1158
|
complete();
|
|
560
1159
|
}
|
|
561
|
-
/** Releases the exact element listeners and timer owned by the current wait. */
|
|
562
1160
|
#teardown() {
|
|
563
1161
|
this.#timers.clearAll();
|
|
564
1162
|
this.#element?.removeEventListener("transitionend", this.#onTerminal);
|
|
@@ -569,7 +1167,6 @@ var TransitionCompletion = class {
|
|
|
569
1167
|
}
|
|
570
1168
|
};
|
|
571
1169
|
|
|
572
|
-
// src/controllers/drawer_controller.ts
|
|
573
1170
|
var DrawerController = class extends Controller {
|
|
574
1171
|
static targets = ["trigger", "overlay", "panel"];
|
|
575
1172
|
static values = {
|
|
@@ -578,83 +1175,95 @@ var DrawerController = class extends Controller {
|
|
|
578
1175
|
};
|
|
579
1176
|
static actions = ["close", "closeOnBackdrop", "open"];
|
|
580
1177
|
static events = ["close", "open"];
|
|
581
|
-
/** Exact panel currently owned by the modal lifecycle (survives target churn safely). */
|
|
582
1178
|
#activePanel = null;
|
|
583
|
-
|
|
1179
|
+
#openState = false;
|
|
584
1180
|
#trap = new FocusTrap(() => this.#activePanel ?? this.panelTarget, {
|
|
585
1181
|
onEscape: () => this.#close("escape"),
|
|
586
1182
|
fallbackFocus: () => this.hasTriggerTarget ? this.triggerTarget : null
|
|
587
1183
|
});
|
|
588
|
-
/** Owns the cancellable close-transition wait and its bounded fallback. */
|
|
589
1184
|
#transition = new TransitionCompletion();
|
|
590
|
-
/** Distinguishes dynamic target churn from callbacks around controller teardown. */
|
|
591
1185
|
#connected = false;
|
|
592
|
-
/** Whether state moves are reported: set once `connect()` settled the baseline. */
|
|
593
1186
|
#reporting = false;
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
* rather than being re-derived from the declarative `open` Value (which would
|
|
600
|
-
* close a user-opened drawer). The `open` Value only seeds a genuinely fresh
|
|
601
|
-
* render. We normalize to a clean closed baseline first so
|
|
602
|
-
* {@link DrawerController.open | open} runs its full reveal + trap activation —
|
|
603
|
-
* the `FocusTrap` is inactive after a disconnect and must be re-activated.
|
|
604
|
-
*/
|
|
1187
|
+
#lived = new LivedMark(this.identifier);
|
|
1188
|
+
#morphRender = new MorphRenderWatcher(() => this.#repair());
|
|
1189
|
+
#stateLease = new AttributeLease("data-state", this.identifier);
|
|
1190
|
+
#hiddenLease = new AttributeLease("hidden", this.identifier);
|
|
1191
|
+
#placementLease = new AttributeLease("data-placement", this.identifier);
|
|
605
1192
|
connect() {
|
|
1193
|
+
const restored = this.#lived.connect(this.element) === "restored";
|
|
1194
|
+
this.#trap.connect();
|
|
1195
|
+
this.#morphRender.observe(this.element);
|
|
606
1196
|
this.#connected = true;
|
|
607
1197
|
this.#activePanel = this.hasPanelTarget ? this.panelTarget : null;
|
|
608
1198
|
this.#reflectPlacement();
|
|
609
|
-
const shouldOpen = this.#isOpen || this.openValue;
|
|
1199
|
+
const shouldOpen = !restored && (this.#isOpen || this.openValue);
|
|
610
1200
|
this.#applyClosedState();
|
|
1201
|
+
if (restored) this.openValue = false;
|
|
611
1202
|
if (shouldOpen) this.#open("api");
|
|
612
1203
|
this.#reporting = true;
|
|
613
1204
|
}
|
|
614
|
-
/** Reverts the modal side effects and pending hide if torn down while open. */
|
|
615
1205
|
disconnect() {
|
|
1206
|
+
this.#lived.disconnect();
|
|
1207
|
+
this.#morphRender.disconnect();
|
|
616
1208
|
this.#connected = false;
|
|
617
1209
|
this.#reporting = false;
|
|
618
1210
|
this.#transition.cancel();
|
|
619
|
-
this.#trap.
|
|
1211
|
+
this.#trap.disconnect(this);
|
|
620
1212
|
this.#activePanel = null;
|
|
621
1213
|
}
|
|
622
|
-
/** Adopts a panel target added by a Turbo morph after the controller connected. */
|
|
623
1214
|
panelTargetConnected(panel) {
|
|
624
|
-
if (!this.#connected || this.#activePanel?.isConnected
|
|
1215
|
+
if (!this.#connected || this.#activePanel?.isConnected) return;
|
|
625
1216
|
this.#adoptPanel(panel);
|
|
626
1217
|
}
|
|
627
|
-
/** Closes and releases modal side effects when the actively trapped panel disappears. */
|
|
628
1218
|
panelTargetDisconnected(panel) {
|
|
629
|
-
|
|
1219
|
+
const stays = this.panelTargets.includes(panel);
|
|
1220
|
+
if (!stays) {
|
|
1221
|
+
this.#stateLease.return(panel);
|
|
1222
|
+
this.#hiddenLease.return(panel);
|
|
1223
|
+
this.#placementLease.return(panel);
|
|
1224
|
+
}
|
|
1225
|
+
if (panel !== this.#activePanel || stays && this.panelTarget === panel) return;
|
|
630
1226
|
this.#transition.cancel();
|
|
631
1227
|
this.#activePanel = null;
|
|
632
|
-
if (!this.#connected)
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1228
|
+
if (!this.#connected) {
|
|
1229
|
+
this.#trap.deactivate({ restoreFocus: false });
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
this.#openState = false;
|
|
1233
|
+
if (stays) {
|
|
1234
|
+
this.#writeState(panel, "closed");
|
|
1235
|
+
this.#writeHidden(panel, true);
|
|
638
1236
|
}
|
|
1237
|
+
this.#writeOverlayState("closed");
|
|
1238
|
+
this.#writeOverlayHidden(true);
|
|
639
1239
|
this.openValue = false;
|
|
640
|
-
this.#trap.deactivate();
|
|
641
1240
|
if (this.hasPanelTarget) this.#adoptPanel(this.panelTarget);
|
|
1241
|
+
else this.#trap.deactivate();
|
|
1242
|
+
}
|
|
1243
|
+
overlayTargetConnected() {
|
|
1244
|
+
this.#morphRender.schedule();
|
|
1245
|
+
}
|
|
1246
|
+
overlayTargetDisconnected(overlay) {
|
|
1247
|
+
if (!this.overlayTargets.includes(overlay)) {
|
|
1248
|
+
this.#stateLease.return(overlay);
|
|
1249
|
+
this.#hiddenLease.return(overlay);
|
|
1250
|
+
}
|
|
1251
|
+
this.#morphRender.schedule();
|
|
642
1252
|
}
|
|
643
|
-
/** Keeps `data-placement` in sync if the value changes at runtime. */
|
|
644
1253
|
placementValueChanged() {
|
|
645
|
-
this.#
|
|
1254
|
+
if (this.#connected) this.#repair();
|
|
1255
|
+
else this.#reflectPlacement();
|
|
646
1256
|
}
|
|
647
|
-
/** Opens the drawer: reveals it, syncs `data-state`, traps focus. */
|
|
648
1257
|
open(event) {
|
|
1258
|
+
if (!this.#connected) return;
|
|
649
1259
|
this.#open(stateReasonFor(event));
|
|
650
1260
|
}
|
|
651
|
-
/** Reveals the drawer, syncs `data-state`, reports a move, then traps focus. */
|
|
652
1261
|
#open(reason) {
|
|
653
1262
|
if (!this.hasPanelTarget || this.#isOpen) return;
|
|
654
1263
|
this.#transition.cancel();
|
|
655
1264
|
this.#activePanel = this.panelTarget;
|
|
656
|
-
this.panelTarget
|
|
657
|
-
|
|
1265
|
+
this.#writeHidden(this.panelTarget, false);
|
|
1266
|
+
this.#writeOverlayHidden(false);
|
|
658
1267
|
void this.panelTarget.offsetWidth;
|
|
659
1268
|
this.#setState("open");
|
|
660
1269
|
this.openValue = true;
|
|
@@ -662,21 +1271,14 @@ var DrawerController = class extends Controller {
|
|
|
662
1271
|
if (!this.#isOpen) return;
|
|
663
1272
|
this.#trap.activate();
|
|
664
1273
|
}
|
|
665
|
-
/**
|
|
666
|
-
* Closes the drawer: syncs `data-state` to start the exit transition, then
|
|
667
|
-
* defers both `hidden` *and* the modal teardown (scroll lock / background
|
|
668
|
-
* `inert` / focus restore) until the transition finishes. This keeps the
|
|
669
|
-
* background inert and focus trapped while the drawer is still visually on
|
|
670
|
-
* screen, preserving the modal contract during the exit animation.
|
|
671
|
-
*/
|
|
672
1274
|
close(event) {
|
|
1275
|
+
if (!this.#connected) return;
|
|
673
1276
|
this.#close(stateReasonFor(event));
|
|
674
1277
|
}
|
|
675
|
-
/** Closes only when the overlay itself (not its contents) is clicked. */
|
|
676
1278
|
closeOnBackdrop(event) {
|
|
1279
|
+
if (!this.#connected) return;
|
|
677
1280
|
if (this.hasOverlayTarget && event.target === this.overlayTarget) this.#close("outside");
|
|
678
1281
|
}
|
|
679
|
-
/** Starts the exit transition, reports a move, then defers `hidden` and teardown. */
|
|
680
1282
|
#close(reason) {
|
|
681
1283
|
if (!this.hasPanelTarget || !this.#isOpen) return;
|
|
682
1284
|
this.openValue = false;
|
|
@@ -685,89 +1287,79 @@ var DrawerController = class extends Controller {
|
|
|
685
1287
|
if (this.#isOpen) return;
|
|
686
1288
|
this.#hideAfterTransition();
|
|
687
1289
|
}
|
|
688
|
-
/**
|
|
689
|
-
* Writes `data-placement` from the current `placement` value.
|
|
690
|
-
*
|
|
691
|
-
* @stimeoRenderRoot
|
|
692
|
-
*/
|
|
693
1290
|
#reflectPlacement() {
|
|
694
|
-
if (this.hasPanelTarget) this.panelTarget
|
|
1291
|
+
if (this.hasPanelTarget) this.#placementLease.write(this.panelTarget, this.#placement);
|
|
695
1292
|
}
|
|
696
|
-
/**
|
|
697
|
-
* Reconciles a replacement panel and companion overlay from its explicit DOM state.
|
|
698
|
-
*
|
|
699
|
-
* @stimeoRenderRoot
|
|
700
|
-
*/
|
|
701
1293
|
#adoptPanel(panel) {
|
|
702
1294
|
this.#transition.cancel();
|
|
703
|
-
const trapWasActive = this.#trap.active;
|
|
704
1295
|
this.#activePanel = panel;
|
|
705
|
-
|
|
1296
|
+
this.#placementLease.write(panel, this.#placement);
|
|
706
1297
|
if (panel.getAttribute("data-state") === "open") {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
1298
|
+
this.#openState = true;
|
|
1299
|
+
this.#writeHidden(panel, false);
|
|
1300
|
+
this.#writeOverlayState("open");
|
|
1301
|
+
this.#writeOverlayHidden(false);
|
|
712
1302
|
this.openValue = true;
|
|
713
|
-
if (
|
|
714
|
-
this.#trap.activate();
|
|
1303
|
+
if (this.#trap.active) this.#trap.refreshContainer();
|
|
1304
|
+
else this.#trap.activate();
|
|
715
1305
|
return;
|
|
716
1306
|
}
|
|
717
|
-
|
|
718
|
-
panel
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
1307
|
+
this.#openState = false;
|
|
1308
|
+
this.#writeState(panel, "closed");
|
|
1309
|
+
this.#writeHidden(panel, true);
|
|
1310
|
+
this.#writeOverlayState("closed");
|
|
1311
|
+
this.#writeOverlayHidden(true);
|
|
723
1312
|
this.openValue = false;
|
|
724
1313
|
this.#trap.deactivate();
|
|
725
1314
|
}
|
|
726
|
-
|
|
1315
|
+
#repair() {
|
|
1316
|
+
this.#reflectPlacement();
|
|
1317
|
+
if (!this.hasPanelTarget) return;
|
|
1318
|
+
this.#setState(this.#openState ? "open" : "closed");
|
|
1319
|
+
const hidden = !this.#openState && !this.#trap.active;
|
|
1320
|
+
this.#writeHidden(this.panelTarget, hidden);
|
|
1321
|
+
this.#writeOverlayHidden(hidden);
|
|
1322
|
+
}
|
|
727
1323
|
get #placement() {
|
|
728
1324
|
const value = this.placementValue;
|
|
729
1325
|
return value === "left" || value === "top" || value === "bottom" ? value : "right";
|
|
730
1326
|
}
|
|
731
|
-
/** Syncs `data-state` on the panel and overlay together. */
|
|
732
1327
|
#setState(state) {
|
|
733
|
-
|
|
734
|
-
if (this.
|
|
1328
|
+
this.#openState = state === "open";
|
|
1329
|
+
if (this.hasPanelTarget) this.#writeState(this.panelTarget, state);
|
|
1330
|
+
this.#writeOverlayState(state);
|
|
1331
|
+
}
|
|
1332
|
+
#writeState(element, state) {
|
|
1333
|
+
this.#stateLease.write(element, state);
|
|
1334
|
+
}
|
|
1335
|
+
#writeHidden(element, hidden) {
|
|
1336
|
+
this.#hiddenLease.write(element, hidden ? "" : null);
|
|
1337
|
+
}
|
|
1338
|
+
#writeOverlayState(state) {
|
|
1339
|
+
if (this.hasOverlayTarget) this.#writeState(this.overlayTarget, state);
|
|
1340
|
+
}
|
|
1341
|
+
#writeOverlayHidden(hidden) {
|
|
1342
|
+
if (this.hasOverlayTarget) this.#writeHidden(this.overlayTarget, hidden);
|
|
735
1343
|
}
|
|
736
|
-
/** Fully reflects the closed state up front (used on connect when not open). */
|
|
737
1344
|
#applyClosedState() {
|
|
738
1345
|
this.#setState("closed");
|
|
739
|
-
if (this.hasPanelTarget) this.panelTarget
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
|
-
* Applies `hidden` once the panel's close transition ends, so the exit slide
|
|
744
|
-
* can play. The shared waiter hides synchronously for 0ms transitions and
|
|
745
|
-
* supplies a bounded fallback when the browser emits no terminal event.
|
|
746
|
-
*/
|
|
1346
|
+
if (this.hasPanelTarget) this.#writeHidden(this.panelTarget, true);
|
|
1347
|
+
this.#writeOverlayHidden(true);
|
|
1348
|
+
}
|
|
747
1349
|
#hideAfterTransition() {
|
|
748
1350
|
const panel = this.panelTarget;
|
|
749
1351
|
this.#activePanel = panel;
|
|
750
1352
|
this.#transition.wait(panel, () => this.#applyHidden(panel));
|
|
751
1353
|
}
|
|
752
|
-
/**
|
|
753
|
-
* Runs once the close transition has finished: applies `hidden` to the panel
|
|
754
|
-
* and overlay, then reverts the modal side effects (scroll lock, background
|
|
755
|
-
* `inert`, keydown listener) and restores focus to the opener. Deferring the
|
|
756
|
-
* `FocusTrap` teardown to here — rather than at
|
|
757
|
-
* {@link DrawerController.close | close} time — keeps the background unreachable
|
|
758
|
-
* and focus trapped for the whole exit animation.
|
|
759
|
-
*/
|
|
760
1354
|
#applyHidden(panel) {
|
|
761
|
-
panel
|
|
762
|
-
|
|
1355
|
+
this.#writeHidden(panel, true);
|
|
1356
|
+
this.#writeOverlayHidden(true);
|
|
763
1357
|
this.#trap.deactivate();
|
|
764
1358
|
}
|
|
765
|
-
/** Whether the drawer is open (tracked via `data-state`, not `hidden`). */
|
|
766
1359
|
get #isOpen() {
|
|
767
1360
|
return this.hasPanelTarget && this.panelTarget.getAttribute("data-state") === "open";
|
|
768
1361
|
}
|
|
769
1362
|
};
|
|
770
1363
|
|
|
771
1364
|
export { DrawerController };
|
|
772
|
-
//# sourceMappingURL=drawer_controller.js.map
|
|
773
1365
|
//# sourceMappingURL=drawer_controller.js.map
|