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,121 +1,258 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
4
|
+
if (element.getAttribute(name) === value) return false;
|
|
5
|
+
if (value === null) element.removeAttribute(name);
|
|
6
|
+
else element.setAttribute(name, value);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
function sharedRegistry(key, create) {
|
|
11
|
+
const symbol = Symbol.for(key);
|
|
12
|
+
const scope = globalThis;
|
|
13
|
+
const existing = scope[symbol];
|
|
14
|
+
if (existing !== void 0) return existing;
|
|
15
|
+
const registry2 = create();
|
|
16
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
17
|
+
return registry2;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
var registry = sharedRegistry(
|
|
21
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
22
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
23
|
+
);
|
|
24
|
+
function isAttributeValue(value) {
|
|
25
|
+
return value === null || typeof value === "string";
|
|
26
|
+
}
|
|
27
|
+
function readLeaseRecord(element, record) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
30
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
31
|
+
const [original, written, ...rest] = parsed;
|
|
32
|
+
const linked = rest[0] !== false;
|
|
33
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
34
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
35
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
36
|
+
} catch {
|
|
37
|
+
return void 0;
|
|
17
38
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
}
|
|
40
|
+
function attributeStack(element, attribute) {
|
|
41
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
42
|
+
registry.stacks.set(element, byAttribute);
|
|
43
|
+
const known = byAttribute.get(attribute);
|
|
44
|
+
if (known) return known;
|
|
45
|
+
const found = [];
|
|
46
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
47
|
+
const read = readLeaseRecord(element, record);
|
|
48
|
+
if (read) found.push({ ...read, record });
|
|
49
|
+
}
|
|
50
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
51
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
52
|
+
lease: null,
|
|
53
|
+
record,
|
|
54
|
+
written,
|
|
55
|
+
original,
|
|
56
|
+
linked: linked && index > 0
|
|
57
|
+
}));
|
|
58
|
+
byAttribute.set(attribute, stack);
|
|
59
|
+
return stack;
|
|
60
|
+
}
|
|
61
|
+
function findHold(stack, lease, record) {
|
|
62
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
63
|
+
}
|
|
64
|
+
var AttributeLease = class {
|
|
65
|
+
#attribute;
|
|
66
|
+
#record;
|
|
67
|
+
#base;
|
|
68
|
+
#preserveAuthor;
|
|
69
|
+
#held = /* @__PURE__ */ new Set();
|
|
70
|
+
constructor(attribute, owner, options = {}) {
|
|
71
|
+
this.#attribute = attribute;
|
|
72
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
73
|
+
this.#base = options.base === true;
|
|
74
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
75
|
+
const names = registry.names;
|
|
76
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
77
|
+
}
|
|
78
|
+
write(element, value) {
|
|
79
|
+
const stack = attributeStack(element, this.#attribute);
|
|
80
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
81
|
+
this.#held.add(element);
|
|
82
|
+
const shows = !this.#covered(element, stack, hold);
|
|
83
|
+
hold.written = value;
|
|
84
|
+
this.#mark(element, stack);
|
|
85
|
+
if (shows) this.#reflect(element, value);
|
|
86
|
+
}
|
|
87
|
+
return(element) {
|
|
88
|
+
const held = this.#held.delete(element);
|
|
89
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
90
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
91
|
+
const hold = this.#take(stack);
|
|
92
|
+
if (!hold) return false;
|
|
93
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
94
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
95
|
+
const index = stack.indexOf(hold);
|
|
96
|
+
stack.splice(index, 1);
|
|
97
|
+
const above = stack[index];
|
|
98
|
+
if (above) above.linked &&= hold.linked;
|
|
99
|
+
this.#mark(element, stack);
|
|
100
|
+
if (!owns) return true;
|
|
101
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
102
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
returnAll() {
|
|
106
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
107
|
+
}
|
|
108
|
+
#take(stack) {
|
|
109
|
+
const hold = findHold(stack, this, this.#record);
|
|
110
|
+
if (hold) hold.lease = this;
|
|
111
|
+
return hold;
|
|
112
|
+
}
|
|
113
|
+
#acquire(element, stack) {
|
|
114
|
+
const current = element.getAttribute(this.#attribute);
|
|
115
|
+
const top = stack.at(-1);
|
|
116
|
+
const bottom = stack[0];
|
|
117
|
+
const over = top?.written === current ? top : void 0;
|
|
118
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
119
|
+
const hold = {
|
|
120
|
+
lease: this,
|
|
121
|
+
record: this.#record,
|
|
122
|
+
written: current,
|
|
123
|
+
original: from ? from.original : current,
|
|
124
|
+
linked: !this.#base && over !== void 0
|
|
125
|
+
};
|
|
126
|
+
if (this.#base && bottom) {
|
|
127
|
+
bottom.linked = true;
|
|
128
|
+
stack.unshift(hold);
|
|
129
|
+
} else stack.push(hold);
|
|
130
|
+
return hold;
|
|
131
|
+
}
|
|
132
|
+
#covered(element, stack, hold) {
|
|
133
|
+
const current = element.getAttribute(this.#attribute);
|
|
134
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
135
|
+
}
|
|
136
|
+
#mark(element, stack) {
|
|
137
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
138
|
+
stack.forEach((hold, index) => {
|
|
139
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
140
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
141
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
142
|
+
records.set(
|
|
143
|
+
hold.record,
|
|
144
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
for (const [name, recorded] of records) {
|
|
148
|
+
setAttributeIfChanged(element, name, recorded);
|
|
24
149
|
}
|
|
25
150
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
151
|
+
#reflect(element, value) {
|
|
152
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
var DetachGate = class _DetachGate {
|
|
157
|
+
#pending = false;
|
|
158
|
+
get pending() {
|
|
159
|
+
return this.#pending;
|
|
160
|
+
}
|
|
161
|
+
static isDetached(host) {
|
|
162
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
163
|
+
}
|
|
164
|
+
disconnected(host, teardown) {
|
|
165
|
+
const carried = _DetachGate.#carried(host);
|
|
166
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
167
|
+
this.#pending = false;
|
|
168
|
+
teardown();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.#pending = true;
|
|
172
|
+
const probe = () => {
|
|
173
|
+
if (!this.#pending) return;
|
|
174
|
+
this.#pending = false;
|
|
175
|
+
teardown();
|
|
176
|
+
};
|
|
177
|
+
if (carried) setTimeout(probe, 0);
|
|
178
|
+
else queueMicrotask(probe);
|
|
179
|
+
}
|
|
180
|
+
static #carried(host) {
|
|
181
|
+
const element = host.element;
|
|
182
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
183
|
+
}
|
|
184
|
+
static #listed(host) {
|
|
185
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
186
|
+
return tokens.includes(host.identifier);
|
|
187
|
+
}
|
|
188
|
+
cancel() {
|
|
189
|
+
this.#pending = false;
|
|
31
190
|
}
|
|
32
191
|
};
|
|
33
192
|
|
|
34
|
-
// src/utils/escape_layer.ts
|
|
35
193
|
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
|
-
*/
|
|
194
|
+
static #registries = sharedRegistry(
|
|
195
|
+
"stimeo-ui.escape-layer.registry.v1",
|
|
196
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
197
|
+
);
|
|
198
|
+
#active = null;
|
|
47
199
|
activate(ownerDocument = document, options) {
|
|
48
200
|
this.deactivate();
|
|
49
|
-
let
|
|
50
|
-
if (!
|
|
51
|
-
|
|
52
|
-
_EscapeLayer.#registries.set(ownerDocument,
|
|
53
|
-
ownerDocument.addEventListener("keydown",
|
|
54
|
-
}
|
|
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
|
-
*/
|
|
201
|
+
let registry2 = _EscapeLayer.#registries.get(ownerDocument);
|
|
202
|
+
if (!registry2) {
|
|
203
|
+
registry2 = _EscapeLayer.#createRegistry();
|
|
204
|
+
_EscapeLayer.#registries.set(ownerDocument, registry2);
|
|
205
|
+
ownerDocument.addEventListener("keydown", registry2.onKeydown);
|
|
206
|
+
}
|
|
207
|
+
const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
|
|
208
|
+
registry2.stack.push(entry);
|
|
209
|
+
this.#active = { document: ownerDocument, entry };
|
|
210
|
+
}
|
|
64
211
|
deactivate() {
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
const index =
|
|
70
|
-
if (index >= 0)
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
_EscapeLayer.#registries.delete(
|
|
212
|
+
const active = this.#active;
|
|
213
|
+
if (!active) return;
|
|
214
|
+
const registry2 = _EscapeLayer.#registries.get(active.document);
|
|
215
|
+
if (registry2) {
|
|
216
|
+
const index = registry2.stack.lastIndexOf(active.entry);
|
|
217
|
+
if (index >= 0) registry2.stack.splice(index, 1);
|
|
218
|
+
if (registry2.stack.length === 0) {
|
|
219
|
+
active.document.removeEventListener("keydown", registry2.onKeydown);
|
|
220
|
+
_EscapeLayer.#registries.delete(active.document);
|
|
74
221
|
}
|
|
75
222
|
}
|
|
76
|
-
this.#
|
|
77
|
-
this.#onDismiss = null;
|
|
78
|
-
this.#claims = null;
|
|
223
|
+
this.#active = null;
|
|
79
224
|
}
|
|
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
225
|
get ownsEscape() {
|
|
86
|
-
const
|
|
87
|
-
if (!
|
|
88
|
-
const
|
|
89
|
-
if (!
|
|
90
|
-
return _EscapeLayer.#resolveOwner(
|
|
226
|
+
const active = this.#active;
|
|
227
|
+
if (!active) return false;
|
|
228
|
+
const registry2 = _EscapeLayer.#registries.get(active.document);
|
|
229
|
+
if (!registry2) return false;
|
|
230
|
+
return _EscapeLayer.#resolveOwner(registry2.stack) === active.entry;
|
|
91
231
|
}
|
|
92
|
-
/** Builds a document's registry with its shared resolver listener. */
|
|
93
232
|
static #createRegistry() {
|
|
94
|
-
const
|
|
233
|
+
const registry2 = {
|
|
95
234
|
stack: [],
|
|
96
235
|
onKeydown: (event) => {
|
|
97
236
|
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
98
|
-
const owner = _EscapeLayer.#resolveOwner(
|
|
237
|
+
const owner = _EscapeLayer.#resolveOwner(registry2.stack);
|
|
99
238
|
if (!owner) return;
|
|
100
239
|
event.preventDefault();
|
|
101
|
-
owner
|
|
240
|
+
owner.onDismiss();
|
|
102
241
|
}
|
|
103
242
|
};
|
|
104
|
-
return
|
|
243
|
+
return registry2;
|
|
105
244
|
}
|
|
106
|
-
/** The topmost stack layer whose claims predicate passes, or `null`. */
|
|
107
245
|
static #resolveOwner(stack) {
|
|
108
246
|
for (let index = stack.length - 1; index >= 0; index--) {
|
|
109
247
|
const layer = stack[index];
|
|
110
248
|
if (!layer) continue;
|
|
111
|
-
if (layer
|
|
249
|
+
if (layer.claims && !layer.claims()) continue;
|
|
112
250
|
return layer;
|
|
113
251
|
}
|
|
114
252
|
return null;
|
|
115
253
|
}
|
|
116
254
|
};
|
|
117
255
|
|
|
118
|
-
// src/utils/focus_candidate.ts
|
|
119
256
|
function inheritsFieldsetDisabled(control) {
|
|
120
257
|
let fieldset = control.closest("fieldset[disabled]");
|
|
121
258
|
while (fieldset) {
|
|
@@ -125,8 +262,34 @@ function inheritsFieldsetDisabled(control) {
|
|
|
125
262
|
}
|
|
126
263
|
return false;
|
|
127
264
|
}
|
|
265
|
+
function unslotted(element) {
|
|
266
|
+
return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
|
|
267
|
+
}
|
|
268
|
+
function flatTreeParent(element) {
|
|
269
|
+
if (unslotted(element)) return null;
|
|
270
|
+
const parent = element.assignedSlot ?? element.parentNode;
|
|
271
|
+
if (parent instanceof ShadowRoot) return parent.host;
|
|
272
|
+
return parent instanceof Element ? parent : null;
|
|
273
|
+
}
|
|
274
|
+
function flatTreeContains(ancestor, element) {
|
|
275
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
276
|
+
if (node === ancestor) return true;
|
|
277
|
+
}
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
function closestInFlatTree(element, selector) {
|
|
281
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
282
|
+
if (node.matches(selector)) return node;
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
function flatTreeChildren(element) {
|
|
287
|
+
return ownedScope(element) ?? Array.from(element.children);
|
|
288
|
+
}
|
|
128
289
|
function canTakeFocus(element) {
|
|
129
|
-
|
|
290
|
+
for (let node = element; node; node = flatTreeParent(node)) {
|
|
291
|
+
if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
|
|
292
|
+
}
|
|
130
293
|
if (element instanceof HTMLInputElement && element.type === "hidden") return false;
|
|
131
294
|
if (!("disabled" in element)) return true;
|
|
132
295
|
if (element.disabled) return false;
|
|
@@ -183,189 +346,713 @@ function isTabStop(element) {
|
|
|
183
346
|
if (tabindex !== null) return tabindex >= 0;
|
|
184
347
|
return hasNativeTabStop(element) || hasEditableTabStop(element);
|
|
185
348
|
}
|
|
186
|
-
function
|
|
187
|
-
return
|
|
188
|
-
|
|
349
|
+
function isRadio(element) {
|
|
350
|
+
return element instanceof HTMLInputElement && element.type === "radio";
|
|
351
|
+
}
|
|
352
|
+
function sameRadioGroup(a, b) {
|
|
353
|
+
return a.name !== "" && a.name === b.name && a.form === b.form && a.getRootNode() === b.getRootNode();
|
|
354
|
+
}
|
|
355
|
+
function isInEditableContent(element) {
|
|
356
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
357
|
+
if (node.getAttribute("contenteditable")?.toLowerCase() === "false") return false;
|
|
358
|
+
if (hasEditableTabStop(node)) return true;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
var XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
|
|
363
|
+
function isSvgLink(element) {
|
|
364
|
+
return element instanceof SVGElement && element.localName === "a" && (element.hasAttribute("href") || element.hasAttributeNS(XLINK_NAMESPACE, "href"));
|
|
365
|
+
}
|
|
366
|
+
function hasRenderedImage(area) {
|
|
367
|
+
const map = area.closest("map");
|
|
368
|
+
const key = map?.name || map?.id;
|
|
369
|
+
if (!map || !key) return false;
|
|
370
|
+
const root = map.getRootNode();
|
|
371
|
+
return Array.from(root.querySelectorAll("img[usemap]")).some(
|
|
372
|
+
(image) => image.getAttribute("usemap") === `#${key}` && isRenderedForFocus(image)
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
function isFocusableInOrder(element) {
|
|
376
|
+
const tabindex = authoredTabindex(element);
|
|
377
|
+
return (tabindex === null || tabindex >= 0) && canTakeFocus(element);
|
|
378
|
+
}
|
|
379
|
+
function extraStop(element) {
|
|
380
|
+
if (element instanceof HTMLAreaElement) {
|
|
381
|
+
const focusable = element.hasAttribute("href") || authoredTabindex(element) !== null;
|
|
382
|
+
return focusable && isFocusableInOrder(element) && hasRenderedImage(element);
|
|
383
|
+
}
|
|
384
|
+
if (element instanceof HTMLObjectElement) {
|
|
385
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element) && element.contentWindow !== null;
|
|
386
|
+
}
|
|
387
|
+
if (isSvgLink(element))
|
|
388
|
+
return isFocusableInOrder(element) && isRenderedForFocus(element);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
function isSequentialStop(element) {
|
|
392
|
+
const stop = extraStop(element) ?? (element.matches(TAB_STOP_CANDIDATE_SELECTOR) && isTabStop(element));
|
|
393
|
+
if (!stop) return false;
|
|
394
|
+
const link = element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement || isSvgLink(element);
|
|
395
|
+
return !(link && authoredTabindex(element) === null && isInEditableContent(element));
|
|
396
|
+
}
|
|
397
|
+
function ownedScope(element) {
|
|
398
|
+
if (element.shadowRoot) return Array.from(element.shadowRoot.children);
|
|
399
|
+
if (element instanceof HTMLSlotElement) {
|
|
400
|
+
const assigned = element.assignedElements();
|
|
401
|
+
return assigned.length > 0 ? assigned : Array.from(element.children);
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
function orderScope(elements, walk) {
|
|
406
|
+
const entries = [];
|
|
407
|
+
const visit = (element) => {
|
|
408
|
+
if (element === walk.from) walk.fromAt = walk.tree.length;
|
|
409
|
+
const tabindex = authoredTabindex(element) ?? 0;
|
|
410
|
+
const stop = (element instanceof HTMLElement || element instanceof SVGElement) && isSequentialStop(element) ? element : null;
|
|
411
|
+
if (stop) walk.tree.push(stop);
|
|
412
|
+
const owned = ownedScope(element);
|
|
413
|
+
if (owned) {
|
|
414
|
+
if (tabindex >= 0) {
|
|
415
|
+
entries.push({ tabindex, stops: [...stop ? [stop] : [], ...orderScope(owned, walk)] });
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (stop) entries.push({ tabindex, stops: [stop] });
|
|
420
|
+
for (const child of Array.from(element.children)) visit(child);
|
|
421
|
+
};
|
|
422
|
+
for (const element of elements) visit(element);
|
|
423
|
+
const positive = entries.filter((entry) => entry.tabindex > 0);
|
|
424
|
+
positive.sort((a, b) => a.tabindex - b.tabindex);
|
|
425
|
+
const rest = entries.filter((entry) => entry.tabindex === 0);
|
|
426
|
+
return [...positive, ...rest].flatMap((entry) => entry.stops);
|
|
427
|
+
}
|
|
428
|
+
function passesRadioRule(element, tree, focusedRadio) {
|
|
429
|
+
if (!isRadio(element) || element.checked) return true;
|
|
430
|
+
const group = tree.filter(
|
|
431
|
+
(stop) => stop === element || isRadio(stop) && sameRadioGroup(stop, element)
|
|
189
432
|
);
|
|
433
|
+
if (group.some((radio) => radio.checked)) return false;
|
|
434
|
+
if (group[0] !== element) return false;
|
|
435
|
+
return focusedRadio === null || !sameRadioGroup(focusedRadio, element);
|
|
436
|
+
}
|
|
437
|
+
function startIndex(order, walk, backward, isStop) {
|
|
438
|
+
if (walk.fromAt < 0) return backward ? order.length - 1 : 0;
|
|
439
|
+
const at = order.indexOf(walk.from);
|
|
440
|
+
if (at >= 0) return backward ? at - 1 : at + 1;
|
|
441
|
+
const neighbour = backward ? walk.tree.slice(0, walk.fromAt).reverse().find(isStop) : walk.tree.slice(walk.fromAt).find(isStop);
|
|
442
|
+
if (neighbour) return order.indexOf(neighbour);
|
|
443
|
+
return backward ? order.length - 1 : 0;
|
|
444
|
+
}
|
|
445
|
+
function sequentialTabStops(root, from = null, backward = false) {
|
|
446
|
+
const walk = { from, tree: [], fromAt: from === root ? 0 : -1 };
|
|
447
|
+
const order = orderScope(ownedScope(root) ?? Array.from(root.children), walk);
|
|
448
|
+
const focusedRadio = walk.fromAt >= 0 && isRadio(from) ? from : null;
|
|
449
|
+
const isStop = (element) => passesRadioRule(element, walk.tree, focusedRadio);
|
|
450
|
+
const start = startIndex(order, walk, backward, isStop);
|
|
451
|
+
const visited = backward ? [...order.slice(0, start + 1).reverse(), ...order.slice(start + 1).reverse()] : [...order.slice(start), ...order.slice(0, start)];
|
|
452
|
+
return visited.filter((element) => element !== from && isStop(element));
|
|
453
|
+
}
|
|
454
|
+
function deepActiveElement(ownerDocument) {
|
|
455
|
+
let active = ownerDocument.activeElement;
|
|
456
|
+
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
457
|
+
return active;
|
|
190
458
|
}
|
|
191
459
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
#
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
460
|
+
var MicrotaskCoalescer = class {
|
|
461
|
+
#run;
|
|
462
|
+
#queued = false;
|
|
463
|
+
#active = false;
|
|
464
|
+
#generation = 0;
|
|
465
|
+
constructor(run) {
|
|
466
|
+
this.#run = run;
|
|
467
|
+
}
|
|
468
|
+
activate() {
|
|
469
|
+
this.#active = true;
|
|
470
|
+
}
|
|
471
|
+
cancel() {
|
|
472
|
+
this.#active = false;
|
|
473
|
+
this.#queued = false;
|
|
474
|
+
this.#generation += 1;
|
|
475
|
+
}
|
|
476
|
+
get pending() {
|
|
477
|
+
return this.#queued;
|
|
478
|
+
}
|
|
479
|
+
schedule() {
|
|
480
|
+
if (!this.#active || this.#queued) return;
|
|
481
|
+
this.#queued = true;
|
|
482
|
+
const generation = this.#generation;
|
|
483
|
+
queueMicrotask(() => {
|
|
484
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
485
|
+
this.#queued = false;
|
|
486
|
+
this.#run();
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
function queryOpenDescendants(root, selector) {
|
|
492
|
+
const matches = [];
|
|
493
|
+
const roots = [root];
|
|
494
|
+
for (const scope of roots) {
|
|
495
|
+
matches.push(...scope.querySelectorAll(selector));
|
|
496
|
+
if (scope.nodeType === Node.ELEMENT_NODE) {
|
|
497
|
+
const shadow = scope.shadowRoot;
|
|
498
|
+
if (shadow) roots.push(shadow);
|
|
499
|
+
}
|
|
500
|
+
for (const element of scope.querySelectorAll("*")) {
|
|
501
|
+
if (element.shadowRoot) roots.push(element.shadowRoot);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return matches;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function holdsFocus(element) {
|
|
508
|
+
return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
|
|
509
|
+
}
|
|
510
|
+
var TabindexLoan = class _TabindexLoan {
|
|
511
|
+
static #registry = sharedRegistry(
|
|
512
|
+
"stimeo-ui.tabindex-loan.registry.v1",
|
|
513
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
514
|
+
);
|
|
515
|
+
#value;
|
|
516
|
+
#record;
|
|
517
|
+
#preserveFocus;
|
|
518
|
+
#lent = /* @__PURE__ */ new Set();
|
|
519
|
+
constructor(value, owner, options = {}) {
|
|
520
|
+
this.#value = value;
|
|
521
|
+
this.#record = `data-${owner}-tabindex-loan`;
|
|
522
|
+
this.#preserveFocus = options.preserveFocus === true;
|
|
523
|
+
_TabindexLoan.#registry.names.add(this.#record);
|
|
524
|
+
}
|
|
525
|
+
lend(element, options = {}) {
|
|
526
|
+
const stack = this.#stack(element);
|
|
527
|
+
const current = element.getAttribute("tabindex");
|
|
528
|
+
const held = stack.find((hold) => hold.loan === this) ?? stack.find(
|
|
529
|
+
(hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
|
|
530
|
+
);
|
|
531
|
+
const linked = stack.at(-1)?.value === current;
|
|
532
|
+
if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
|
|
533
|
+
if (!held && current !== null && !linked) return;
|
|
534
|
+
this.#lent.add(element);
|
|
535
|
+
if (held) held.loan = this;
|
|
536
|
+
else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
|
|
537
|
+
this.#mark(element, stack);
|
|
538
|
+
if (current === null || !held && current !== this.#value) {
|
|
539
|
+
element.setAttribute("tabindex", this.#value);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return(element) {
|
|
543
|
+
const stack = this.#stack(element);
|
|
544
|
+
const hold = stack.find((held) => held.loan === this) ?? stack.find(
|
|
545
|
+
(held) => held.loan === null && held.record === this.#record && held.value === this.#value
|
|
546
|
+
);
|
|
547
|
+
if (!hold) return false;
|
|
548
|
+
if (!this.#release(element, stack, hold, this.#preserveFocus)) {
|
|
549
|
+
hold.loan = this;
|
|
550
|
+
this.#lent.add(element);
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
this.#lent.delete(element);
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
returnAll() {
|
|
557
|
+
for (const element of Array.from(this.#lent)) this.return(element);
|
|
558
|
+
}
|
|
559
|
+
reclaim(element) {
|
|
560
|
+
if (element.getAttribute(this.#record) !== this.#value) return;
|
|
561
|
+
const stack = this.#stack(element);
|
|
562
|
+
const hold = stack.find((held) => held.loan === null && held.record === this.#record);
|
|
563
|
+
if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
|
|
564
|
+
}
|
|
565
|
+
reclaimWithin(root) {
|
|
566
|
+
for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
|
|
567
|
+
this.reclaim(element);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
#stack(element) {
|
|
571
|
+
const stacks = _TabindexLoan.#registry.stacks;
|
|
572
|
+
const stack = stacks.get(element) ?? this.#seed(element);
|
|
573
|
+
stacks.set(element, stack);
|
|
574
|
+
return stack;
|
|
575
|
+
}
|
|
576
|
+
#seed(element) {
|
|
577
|
+
const current = element.getAttribute("tabindex");
|
|
578
|
+
const holds = [];
|
|
579
|
+
for (const record of _TabindexLoan.#registry.names) {
|
|
580
|
+
const value = element.getAttribute(record);
|
|
581
|
+
if (value !== null) holds.push({ loan: null, record, value, linked: true });
|
|
582
|
+
}
|
|
583
|
+
holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
|
|
584
|
+
for (const [index, hold] of holds.entries()) hold.linked = index > 0;
|
|
585
|
+
return holds;
|
|
586
|
+
}
|
|
587
|
+
#release(element, stack, hold, preserveFocus = false) {
|
|
588
|
+
const current = element.getAttribute("tabindex");
|
|
589
|
+
const index = stack.indexOf(hold);
|
|
590
|
+
const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
|
|
591
|
+
if (owns && preserveFocus && holdsFocus(element)) return false;
|
|
592
|
+
stack.splice(index, 1);
|
|
593
|
+
const above = stack[index];
|
|
594
|
+
if (above) above.linked &&= hold.linked;
|
|
595
|
+
this.#mark(element, stack);
|
|
596
|
+
if (!owns) return true;
|
|
597
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
598
|
+
if (!below) element.removeAttribute("tabindex");
|
|
599
|
+
else if (below.value !== current) element.setAttribute("tabindex", below.value);
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
#mark(element, stack) {
|
|
603
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
604
|
+
for (const hold of stack) records.set(hold.record, hold.value);
|
|
605
|
+
for (const [name, recorded] of records) {
|
|
606
|
+
setAttributeIfChanged(element, name, recorded);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
var FOCUS_TRAP_OWNER = "stimeo-focus-trap";
|
|
612
|
+
var INERT_MARK = `data-${FOCUS_TRAP_OWNER}-inert`;
|
|
613
|
+
var OVERFLOW_MARK = `data-${FOCUS_TRAP_OWNER}-overflow`;
|
|
614
|
+
var FocusTrap = class _FocusTrap {
|
|
615
|
+
static #registries = sharedRegistry(
|
|
616
|
+
"stimeo-ui.focus-trap.registry.v2",
|
|
617
|
+
() => /* @__PURE__ */ new WeakMap()
|
|
618
|
+
);
|
|
619
|
+
static #TEXT_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
620
|
+
"text",
|
|
621
|
+
"search",
|
|
622
|
+
"email",
|
|
623
|
+
"url",
|
|
624
|
+
"tel",
|
|
625
|
+
"password",
|
|
626
|
+
"number"
|
|
627
|
+
]);
|
|
628
|
+
#openers = [];
|
|
629
|
+
#entry;
|
|
630
|
+
#tabindexLoans = /* @__PURE__ */ new Map();
|
|
203
631
|
#activeState = false;
|
|
204
|
-
/** Registers the trap on the shared Escape stack while active (see {@link EscapeLayer}). */
|
|
205
632
|
#escapeLayer = new EscapeLayer();
|
|
206
|
-
|
|
633
|
+
#gate = new DetachGate();
|
|
207
634
|
#getContainer;
|
|
208
|
-
/** Closing/focus hooks; see {@link FocusTrapOptions}. */
|
|
209
635
|
#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
636
|
constructor(getContainer, options = {}) {
|
|
216
637
|
this.#getContainer = getContainer;
|
|
217
638
|
this.#options = options;
|
|
639
|
+
this.#entry = {
|
|
640
|
+
container: () => this.#readContainer(),
|
|
641
|
+
moveFocus: (backward) => this.#moveFocus(backward),
|
|
642
|
+
refocus: () => this.#refocus(),
|
|
643
|
+
isolated: false,
|
|
644
|
+
lockedBody: null,
|
|
645
|
+
openedFrom: null,
|
|
646
|
+
handedBack: []
|
|
647
|
+
};
|
|
218
648
|
}
|
|
219
|
-
/** Whether the trap is currently active. */
|
|
220
649
|
get active() {
|
|
221
650
|
return this.#activeState;
|
|
222
651
|
}
|
|
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
652
|
activate() {
|
|
229
653
|
if (this.#activeState) return;
|
|
654
|
+
_FocusTrap.#sweep();
|
|
230
655
|
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();
|
|
656
|
+
this.#openers = _FocusTrap.#focusPath();
|
|
657
|
+
this.#entry.openedFrom = _FocusTrap.#trapHolding(this.#openers[0] ?? null);
|
|
658
|
+
if (this.#flag(this.#options.lockScroll, true)) this.#lockScroll();
|
|
659
|
+
this.#entry.isolated = this.#flag(this.#options.isolate, true);
|
|
660
|
+
this.#joinStack();
|
|
661
|
+
_FocusTrap.#reconcileBackground(document);
|
|
241
662
|
const onEscape = this.#options.onEscape;
|
|
242
663
|
if (onEscape) this.#escapeLayer.activate(document, { onDismiss: () => onEscape() });
|
|
243
664
|
if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
|
|
244
665
|
}
|
|
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
666
|
deactivate({ restoreFocus = true } = {}) {
|
|
253
|
-
if (!this.#activeState)
|
|
667
|
+
if (!this.#activeState) {
|
|
668
|
+
this.#returnTabindexes();
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
254
671
|
this.#activeState = false;
|
|
255
672
|
this.#escapeLayer.deactivate();
|
|
256
|
-
|
|
257
|
-
this.#
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
673
|
+
const above = this.#leaveStack();
|
|
674
|
+
this.#unlockScroll();
|
|
675
|
+
_FocusTrap.#reconcileBackground(document);
|
|
676
|
+
const wayBack = this.#handOver(above);
|
|
677
|
+
if (restoreFocus) this.#restore(wayBack, above);
|
|
678
|
+
this.#returnTabindexes();
|
|
679
|
+
}
|
|
680
|
+
connect() {
|
|
681
|
+
this.#gate.cancel();
|
|
682
|
+
_FocusTrap.#sweep();
|
|
683
|
+
new TabindexLoan("-1", FOCUS_TRAP_OWNER).reclaimWithin(document);
|
|
684
|
+
}
|
|
685
|
+
disconnect(host) {
|
|
686
|
+
if (this.#activeState) this.deactivate({ restoreFocus: false });
|
|
687
|
+
this.#gate.disconnected(host, () => this.#returnTabindexes());
|
|
688
|
+
}
|
|
689
|
+
refreshIsolation() {
|
|
690
|
+
if (!this.#activeState) return;
|
|
691
|
+
const isolate = this.#flag(this.#options.isolate, true);
|
|
692
|
+
if (isolate === this.#entry.isolated) return;
|
|
693
|
+
this.#entry.isolated = isolate;
|
|
694
|
+
const stranded = _FocusTrap.#reconcileBackground(document);
|
|
695
|
+
const active = deepActiveElement(document);
|
|
696
|
+
if (stranded.some((element) => flatTreeContains(element, active))) this.#focusInitial();
|
|
697
|
+
}
|
|
698
|
+
refreshContainer() {
|
|
699
|
+
if (!this.#activeState) return;
|
|
700
|
+
_FocusTrap.#reconcileBackground(document);
|
|
701
|
+
this.#returnTabindexes();
|
|
702
|
+
if (this.#ownsTab()) this.#refocus();
|
|
267
703
|
}
|
|
268
|
-
/** Resolves a boolean-or-getter option, defaulting when it was not provided. */
|
|
269
704
|
#flag(option, fallback) {
|
|
270
705
|
if (option === void 0) return fallback;
|
|
271
706
|
return typeof option === "function" ? option() : option;
|
|
272
707
|
}
|
|
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;
|
|
708
|
+
#joinStack() {
|
|
709
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
710
|
+
if (registry2.stack.length === 0) {
|
|
711
|
+
document.addEventListener("keydown", registry2.onKeydown);
|
|
712
|
+
document.addEventListener("turbo:morph", registry2.onMorph);
|
|
713
|
+
document.addEventListener("turbo:morph-element", registry2.onMorph);
|
|
296
714
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
715
|
+
registry2.stack.push(this.#entry);
|
|
716
|
+
}
|
|
717
|
+
#leaveStack() {
|
|
718
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
719
|
+
const index = registry2.stack.indexOf(this.#entry);
|
|
720
|
+
const above = registry2.stack.slice(index + 1);
|
|
721
|
+
registry2.stack.splice(index, 1);
|
|
722
|
+
if (registry2.stack.length === 0) {
|
|
723
|
+
document.removeEventListener("keydown", registry2.onKeydown);
|
|
724
|
+
document.removeEventListener("turbo:morph", registry2.onMorph);
|
|
725
|
+
document.removeEventListener("turbo:morph-element", registry2.onMorph);
|
|
304
726
|
}
|
|
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
|
-
|
|
727
|
+
return above;
|
|
728
|
+
}
|
|
729
|
+
static #focusPath() {
|
|
730
|
+
const path = [];
|
|
731
|
+
for (let active = document.activeElement; active; active = active.shadowRoot?.activeElement ?? null) {
|
|
732
|
+
if (active instanceof HTMLElement && active !== document.body) path.unshift(active);
|
|
733
|
+
}
|
|
734
|
+
return path;
|
|
735
|
+
}
|
|
736
|
+
static #trapHolding(element) {
|
|
737
|
+
for (const trap of [..._FocusTrap.#registry(document).stack].reverse()) {
|
|
738
|
+
const container = trap.container();
|
|
739
|
+
if (container && flatTreeContains(container, element)) return trap;
|
|
740
|
+
}
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
#handOver(above) {
|
|
744
|
+
const entry = this.#entry;
|
|
745
|
+
const wayBack = [
|
|
746
|
+
...this.#openers,
|
|
747
|
+
this.#options.fallbackFocus?.() ?? null,
|
|
748
|
+
...entry.handedBack
|
|
749
|
+
].filter((element) => element !== null);
|
|
750
|
+
for (const trap of above) {
|
|
751
|
+
if (trap.openedFrom !== entry) continue;
|
|
752
|
+
trap.handedBack = [...trap.handedBack, ...wayBack];
|
|
753
|
+
trap.openedFrom = entry.openedFrom;
|
|
754
|
+
}
|
|
755
|
+
this.#openers = [];
|
|
756
|
+
entry.openedFrom = null;
|
|
757
|
+
entry.handedBack = [];
|
|
758
|
+
return wayBack;
|
|
759
|
+
}
|
|
760
|
+
#restore(wayBack, above) {
|
|
761
|
+
const active = deepActiveElement(document);
|
|
762
|
+
const released = this.#readContainer();
|
|
763
|
+
const holds = (trap) => {
|
|
764
|
+
const container = trap.container();
|
|
765
|
+
if (!container || !_FocusTrap.#takesTab(trap) || !flatTreeContains(container, active)) {
|
|
766
|
+
return false;
|
|
335
767
|
}
|
|
336
|
-
|
|
768
|
+
return !released || !flatTreeContains(released, active) || flatTreeContains(released, container);
|
|
769
|
+
};
|
|
770
|
+
if (above.some(holds)) return;
|
|
771
|
+
for (const element of wayBack) {
|
|
772
|
+
if (this.#activeState) return;
|
|
773
|
+
if (!element.isConnected || !canTakeFocus(element) || !isRenderedForFocus(element)) continue;
|
|
774
|
+
const before = deepActiveElement(document);
|
|
775
|
+
element.focus();
|
|
776
|
+
const after = deepActiveElement(document);
|
|
777
|
+
if (after === element || after !== before) return;
|
|
778
|
+
}
|
|
779
|
+
_FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack)?.refocus();
|
|
780
|
+
}
|
|
781
|
+
#refocus() {
|
|
782
|
+
if (!this.#flag(this.#options.autoFocus, true)) return;
|
|
783
|
+
if (flatTreeContains(this.#getContainer(), deepActiveElement(document))) return;
|
|
784
|
+
this.#focusInitial();
|
|
785
|
+
}
|
|
786
|
+
#lendTabindex(container) {
|
|
787
|
+
const loan = this.#tabindexLoans.get(container) ?? new TabindexLoan("-1", FOCUS_TRAP_OWNER);
|
|
788
|
+
loan.lend(container);
|
|
789
|
+
this.#tabindexLoans.set(container, loan);
|
|
790
|
+
}
|
|
791
|
+
#returnTabindexes() {
|
|
792
|
+
const focused = deepActiveElement(document);
|
|
793
|
+
for (const [element, loan] of [...this.#tabindexLoans]) {
|
|
794
|
+
if (element === focused) continue;
|
|
795
|
+
loan.returnAll();
|
|
796
|
+
this.#tabindexLoans.delete(element);
|
|
337
797
|
}
|
|
338
798
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
799
|
+
#lockScroll() {
|
|
800
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
801
|
+
const body = document.body;
|
|
802
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
803
|
+
registry2.scrollLocks.add(this.#entry);
|
|
804
|
+
this.#entry.lockedBody = body;
|
|
805
|
+
body.style.overflow = "hidden";
|
|
806
|
+
}
|
|
807
|
+
#unlockScroll() {
|
|
808
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
809
|
+
const body = this.#entry.lockedBody;
|
|
810
|
+
this.#entry.lockedBody = null;
|
|
811
|
+
if (!registry2.scrollLocks.delete(this.#entry) || !body) return;
|
|
812
|
+
if (!_FocusTrap.#isLocked(registry2, body)) _FocusTrap.#unlock(body);
|
|
813
|
+
}
|
|
814
|
+
static #isLocked(registry2, body) {
|
|
815
|
+
return [...registry2.scrollLocks].some((trap) => trap.lockedBody === body);
|
|
816
|
+
}
|
|
817
|
+
static #unlock(body) {
|
|
818
|
+
const saved = body.getAttribute(OVERFLOW_MARK);
|
|
819
|
+
if (saved === null) return;
|
|
820
|
+
body.removeAttribute(OVERFLOW_MARK);
|
|
821
|
+
body.style.overflow = saved;
|
|
822
|
+
}
|
|
823
|
+
static #sweep() {
|
|
824
|
+
const registry2 = _FocusTrap.#registry(document);
|
|
825
|
+
for (const element of queryOpenDescendants(document, `[${INERT_MARK}]`)) {
|
|
826
|
+
if (registry2.inerted.has(element)) continue;
|
|
827
|
+
element.removeAttribute(INERT_MARK);
|
|
828
|
+
element.inert = false;
|
|
829
|
+
}
|
|
830
|
+
if (!_FocusTrap.#isLocked(registry2, document.body)) _FocusTrap.#unlock(document.body);
|
|
831
|
+
}
|
|
832
|
+
static #registry(ownerDocument) {
|
|
833
|
+
const existing = _FocusTrap.#registries.get(ownerDocument);
|
|
834
|
+
if (existing) return existing;
|
|
835
|
+
const morphs = new MicrotaskCoalescer(() => _FocusTrap.#reapply(registry2));
|
|
836
|
+
morphs.activate();
|
|
837
|
+
const registry2 = {
|
|
838
|
+
stack: [],
|
|
839
|
+
onKeydown: (event) => _FocusTrap.#resolveTab(registry2.stack, event),
|
|
840
|
+
onMorph: () => morphs.schedule(),
|
|
841
|
+
inerted: /* @__PURE__ */ new Set(),
|
|
842
|
+
scrollLocks: /* @__PURE__ */ new Set()
|
|
843
|
+
};
|
|
844
|
+
_FocusTrap.#registries.set(ownerDocument, registry2);
|
|
845
|
+
return registry2;
|
|
846
|
+
}
|
|
847
|
+
static #reapply(registry2) {
|
|
848
|
+
_FocusTrap.#reconcileBackground(document);
|
|
849
|
+
const body = document.body;
|
|
850
|
+
if (!_FocusTrap.#isLocked(registry2, body)) return;
|
|
851
|
+
if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
|
|
852
|
+
body.style.overflow = "hidden";
|
|
853
|
+
}
|
|
854
|
+
static #reconcileBackground(ownerDocument) {
|
|
855
|
+
const registry2 = _FocusTrap.#registry(ownerDocument);
|
|
856
|
+
for (const element of registry2.inerted) {
|
|
857
|
+
if (!element.hasAttribute(INERT_MARK)) registry2.inerted.delete(element);
|
|
858
|
+
}
|
|
859
|
+
const background = _FocusTrap.#backgroundOf(registry2);
|
|
860
|
+
for (const element of registry2.inerted) {
|
|
861
|
+
if (background.has(element)) continue;
|
|
862
|
+
element.removeAttribute(INERT_MARK);
|
|
863
|
+
element.inert = false;
|
|
864
|
+
registry2.inerted.delete(element);
|
|
865
|
+
}
|
|
866
|
+
const added = [];
|
|
867
|
+
for (const element of background) {
|
|
868
|
+
if (registry2.inerted.has(element)) continue;
|
|
869
|
+
element.setAttribute(INERT_MARK, "");
|
|
870
|
+
element.inert = true;
|
|
871
|
+
registry2.inerted.add(element);
|
|
872
|
+
added.push(element);
|
|
873
|
+
}
|
|
874
|
+
return added;
|
|
875
|
+
}
|
|
876
|
+
static #backgroundOf(registry2) {
|
|
877
|
+
const containers = registry2.stack.map((trap) => trap.container());
|
|
878
|
+
let top = -1;
|
|
879
|
+
registry2.stack.forEach((trap, index) => {
|
|
880
|
+
if (trap.isolated && containers[index]) top = index;
|
|
881
|
+
});
|
|
882
|
+
const background = /* @__PURE__ */ new Set();
|
|
883
|
+
if (top < 0) return background;
|
|
884
|
+
const kept = /* @__PURE__ */ new Set();
|
|
885
|
+
const keep = (path) => {
|
|
886
|
+
for (const node of path) {
|
|
887
|
+
kept.add(node);
|
|
888
|
+
background.delete(node);
|
|
889
|
+
}
|
|
890
|
+
for (const node of path) {
|
|
891
|
+
const parent = flatTreeParent(node);
|
|
892
|
+
for (const sibling of parent ? flatTreeChildren(parent) : []) {
|
|
893
|
+
if (!(sibling instanceof HTMLElement) || kept.has(sibling)) continue;
|
|
894
|
+
if (sibling.inert && !registry2.inerted.has(sibling)) continue;
|
|
895
|
+
background.add(sibling);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
containers.slice(top).forEach((container, index) => {
|
|
900
|
+
const path = [];
|
|
901
|
+
for (let node = container; node && node !== document.body && !kept.has(node); ) {
|
|
902
|
+
path.push(node);
|
|
903
|
+
node = flatTreeParent(node);
|
|
904
|
+
}
|
|
905
|
+
if (index === 0 || path.some((node) => background.has(node))) keep(path);
|
|
906
|
+
});
|
|
907
|
+
return background;
|
|
908
|
+
}
|
|
909
|
+
#readContainer() {
|
|
910
|
+
try {
|
|
911
|
+
return this.#getContainer();
|
|
912
|
+
} catch {
|
|
913
|
+
return null;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
static #resolveTab(stack, event) {
|
|
917
|
+
if (event.key !== "Tab" || event.defaultPrevented) return;
|
|
918
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
919
|
+
if (event.ctrlKey || event.metaKey) return;
|
|
920
|
+
const owner = _FocusTrap.#tabOwner(stack);
|
|
921
|
+
if (!owner) return;
|
|
922
|
+
event.preventDefault();
|
|
923
|
+
owner.moveFocus(event.shiftKey);
|
|
924
|
+
}
|
|
925
|
+
static #tabOwner(stack) {
|
|
926
|
+
for (const trap of [...stack].reverse()) {
|
|
927
|
+
if (_FocusTrap.#takesTab(trap)) return trap;
|
|
928
|
+
}
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
static #takesTab(trap) {
|
|
932
|
+
const container = trap.container();
|
|
933
|
+
if (!container) return false;
|
|
934
|
+
return container.isConnected && !closestInFlatTree(container, "[hidden]") && isRenderedForFocus(container);
|
|
935
|
+
}
|
|
936
|
+
#ownsTab() {
|
|
937
|
+
return _FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack) === this.#entry;
|
|
938
|
+
}
|
|
939
|
+
#moveFocus(backward) {
|
|
940
|
+
const from = deepActiveElement(document);
|
|
941
|
+
for (const stop of sequentialTabStops(this.#getContainer(), from, backward)) {
|
|
942
|
+
if (!this.#ownsTab()) return;
|
|
943
|
+
const before = deepActiveElement(document);
|
|
944
|
+
stop.focus();
|
|
945
|
+
const after = deepActiveElement(document);
|
|
946
|
+
if (after === before) continue;
|
|
947
|
+
if (after === stop) _FocusTrap.#selectOnEntry(stop);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
static #selectOnEntry(stop) {
|
|
952
|
+
if (stop instanceof HTMLInputElement && _FocusTrap.#TEXT_ENTRY_TYPES.has(stop.type)) {
|
|
953
|
+
stop.select();
|
|
343
954
|
}
|
|
344
|
-
this.#inertedSiblings = [];
|
|
345
955
|
}
|
|
346
|
-
/** Moves focus to the initial target, the first focusable, or the container. */
|
|
347
956
|
#focusInitial() {
|
|
348
957
|
const preferred = this.#options.initialFocus?.();
|
|
349
958
|
if (preferred) {
|
|
350
959
|
preferred.focus();
|
|
351
960
|
return;
|
|
352
961
|
}
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
962
|
+
const container = this.#getContainer();
|
|
963
|
+
const [first] = sequentialTabStops(container);
|
|
964
|
+
if (first) {
|
|
965
|
+
first.focus();
|
|
356
966
|
return;
|
|
357
967
|
}
|
|
358
|
-
|
|
359
|
-
container.tabIndex = -1;
|
|
968
|
+
this.#lendTabindex(container);
|
|
360
969
|
container.focus();
|
|
361
970
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
var ListenerSet = class {
|
|
974
|
+
#abort = new AbortController();
|
|
975
|
+
add(target, type, handler, options) {
|
|
976
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
977
|
+
}
|
|
978
|
+
dispose() {
|
|
979
|
+
this.#abort.abort();
|
|
980
|
+
this.#abort = new AbortController();
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
var MorphRenderWatcher = class {
|
|
985
|
+
#listeners = new ListenerSet();
|
|
986
|
+
#pass;
|
|
987
|
+
#element = null;
|
|
988
|
+
#onMorph = () => this.#pass.schedule();
|
|
989
|
+
constructor(run) {
|
|
990
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
991
|
+
}
|
|
992
|
+
observe(element) {
|
|
993
|
+
if (this.#element === element) return;
|
|
994
|
+
this.disconnect();
|
|
995
|
+
this.#element = element;
|
|
996
|
+
this.#pass.activate();
|
|
997
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
998
|
+
}
|
|
999
|
+
schedule() {
|
|
1000
|
+
this.#pass.schedule();
|
|
1001
|
+
}
|
|
1002
|
+
get pending() {
|
|
1003
|
+
return this.#pass.pending;
|
|
1004
|
+
}
|
|
1005
|
+
disconnect() {
|
|
1006
|
+
this.#listeners.dispose();
|
|
1007
|
+
this.#pass.cancel();
|
|
1008
|
+
this.#element = null;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
var NUMBER_BOUNDS = {
|
|
1013
|
+
nonNegative: { finite: true, min: 0 }};
|
|
1014
|
+
function matchesNumberBounds(value, bounds) {
|
|
1015
|
+
if (!Number.isFinite(value)) {
|
|
1016
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
1017
|
+
if (direction === null) return false;
|
|
1018
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
1019
|
+
}
|
|
1020
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
1021
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
1022
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
1023
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
1024
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
function readNumber(raw, fallback, bounds) {
|
|
1029
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
var NumberValueReader = class {
|
|
1033
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
1034
|
+
read(owner, name, raw, fallback, bounds) {
|
|
1035
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
1036
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
1037
|
+
this.#lastRejected.delete(name);
|
|
1038
|
+
return resolved;
|
|
1039
|
+
}
|
|
1040
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
1041
|
+
const literal = owner.element.getAttribute(attribute);
|
|
1042
|
+
if (literal === null) {
|
|
1043
|
+
this.#lastRejected.delete(name);
|
|
1044
|
+
return resolved;
|
|
1045
|
+
}
|
|
1046
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
1047
|
+
this.#lastRejected.set(name, literal);
|
|
1048
|
+
console.warn(
|
|
1049
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
return resolved;
|
|
365
1053
|
}
|
|
366
1054
|
};
|
|
367
1055
|
|
|
368
|
-
// src/utils/safe_storage.ts
|
|
369
1056
|
function readLocalStorage(key) {
|
|
370
1057
|
try {
|
|
371
1058
|
return { ok: true, value: window.localStorage.getItem(key) };
|
|
@@ -382,7 +1069,6 @@ function writeLocalStorage(key, value) {
|
|
|
382
1069
|
}
|
|
383
1070
|
}
|
|
384
1071
|
|
|
385
|
-
// src/utils/state_reason.ts
|
|
386
1072
|
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
387
1073
|
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
388
1074
|
function stateReasonFor(event) {
|
|
@@ -392,43 +1078,24 @@ function stateReasonFor(event) {
|
|
|
392
1078
|
return "user";
|
|
393
1079
|
}
|
|
394
1080
|
|
|
395
|
-
// src/utils/safe_timeout.ts
|
|
396
1081
|
var TimerRegistry = class {
|
|
397
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
398
1082
|
ids = /* @__PURE__ */ new Set();
|
|
399
|
-
/**
|
|
400
|
-
* Cancels a single tracked timer.
|
|
401
|
-
*
|
|
402
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
403
|
-
* registry), so callers can clear defensively without guarding.
|
|
404
|
-
*/
|
|
405
1083
|
clear(id) {
|
|
406
1084
|
if (this.ids.delete(id)) {
|
|
407
1085
|
this.cancel(id);
|
|
408
1086
|
}
|
|
409
1087
|
}
|
|
410
|
-
/**
|
|
411
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
412
|
-
* to guarantee no timer outlives the element.
|
|
413
|
-
*/
|
|
414
1088
|
clearAll() {
|
|
415
1089
|
for (const id of this.ids) {
|
|
416
1090
|
this.cancel(id);
|
|
417
1091
|
}
|
|
418
1092
|
this.ids.clear();
|
|
419
1093
|
}
|
|
420
|
-
/** Number of timers currently tracked (pending). */
|
|
421
1094
|
get size() {
|
|
422
1095
|
return this.ids.size;
|
|
423
1096
|
}
|
|
424
1097
|
};
|
|
425
1098
|
var SafeTimeout = class extends TimerRegistry {
|
|
426
|
-
/**
|
|
427
|
-
* Schedules `callback` after `delay` ms and returns the timer id.
|
|
428
|
-
*
|
|
429
|
-
* The id is removed from the registry automatically when the timeout fires,
|
|
430
|
-
* so {@link TimerRegistry.size | size} reflects only still-pending timers.
|
|
431
|
-
*/
|
|
432
1099
|
set(callback, delay) {
|
|
433
1100
|
const id = this.schedule(() => {
|
|
434
1101
|
this.ids.delete(id);
|
|
@@ -445,7 +1112,6 @@ var SafeTimeout = class extends TimerRegistry {
|
|
|
445
1112
|
}
|
|
446
1113
|
};
|
|
447
1114
|
|
|
448
|
-
// src/utils/transition_completion.ts
|
|
449
1115
|
function timeMs(value) {
|
|
450
1116
|
const trimmed = value.trim();
|
|
451
1117
|
const amount = Number.parseFloat(trimmed);
|
|
@@ -481,13 +1147,6 @@ var TransitionCompletion = class {
|
|
|
481
1147
|
#complete = null;
|
|
482
1148
|
#pendingProperties = null;
|
|
483
1149
|
#deadline = 0;
|
|
484
|
-
/**
|
|
485
|
-
* Replaces any prior wait and invokes `complete` synchronously for a 0ms
|
|
486
|
-
* transition (including when `getComputedStyle` is unavailable).
|
|
487
|
-
*
|
|
488
|
-
* With a positive `options.timeoutMs` the synchronous fast-path is skipped and
|
|
489
|
-
* the override replaces the auto-computed fallback (see {@link TransitionWaitOptions}).
|
|
490
|
-
*/
|
|
491
1150
|
wait(element, complete, options = {}) {
|
|
492
1151
|
this.cancel();
|
|
493
1152
|
const requested = options.timeoutMs ?? 0;
|
|
@@ -507,18 +1166,10 @@ var TransitionCompletion = class {
|
|
|
507
1166
|
element.addEventListener("transitioncancel", this.#onTerminal);
|
|
508
1167
|
this.#timers.set(() => this.#finish(), override > 0 ? override : maximum + 50);
|
|
509
1168
|
}
|
|
510
|
-
/** Cancels the pending wait without invoking its completion callback. */
|
|
511
1169
|
cancel() {
|
|
512
1170
|
this.#complete = null;
|
|
513
1171
|
this.#teardown();
|
|
514
1172
|
}
|
|
515
|
-
/**
|
|
516
|
-
* Handles terminal events from the observed element only.
|
|
517
|
-
*
|
|
518
|
-
* For explicit property lists, every declared positive-time property must
|
|
519
|
-
* settle. For `all`, no reliable property set exists, so an event can finish
|
|
520
|
-
* only after the computed maximum time; the safety timeout owns the usual path.
|
|
521
|
-
*/
|
|
522
1173
|
#onTerminal = (event) => {
|
|
523
1174
|
if (event.target !== this.#element) return;
|
|
524
1175
|
const transitionEvent = event;
|
|
@@ -538,10 +1189,6 @@ var TransitionCompletion = class {
|
|
|
538
1189
|
}
|
|
539
1190
|
if (Date.now() >= this.#deadline) this.#finish();
|
|
540
1191
|
};
|
|
541
|
-
/**
|
|
542
|
-
* Returns active CSS transition properties expanded to the names reported by
|
|
543
|
-
* terminal events. CSS animations and pseudo-element effects are excluded.
|
|
544
|
-
*/
|
|
545
1192
|
#activeTransitionProperties(element) {
|
|
546
1193
|
if (!element || typeof element.getAnimations !== "function") return /* @__PURE__ */ new Set();
|
|
547
1194
|
try {
|
|
@@ -559,7 +1206,6 @@ var TransitionCompletion = class {
|
|
|
559
1206
|
return /* @__PURE__ */ new Set();
|
|
560
1207
|
}
|
|
561
1208
|
}
|
|
562
|
-
/** Returns explicit positive-time properties, or `null` for the ambiguous `all`. */
|
|
563
1209
|
#explicitPendingProperties(timings) {
|
|
564
1210
|
if (timings.some(({ property }) => property === "all")) return null;
|
|
565
1211
|
const pending = new Set(
|
|
@@ -567,7 +1213,6 @@ var TransitionCompletion = class {
|
|
|
567
1213
|
);
|
|
568
1214
|
return pending.size > 0 ? pending : null;
|
|
569
1215
|
}
|
|
570
|
-
/** Completes exactly once, releasing listeners and the fallback before the callback. */
|
|
571
1216
|
#finish() {
|
|
572
1217
|
const complete = this.#complete;
|
|
573
1218
|
if (!complete) return;
|
|
@@ -575,7 +1220,6 @@ var TransitionCompletion = class {
|
|
|
575
1220
|
this.#teardown();
|
|
576
1221
|
complete();
|
|
577
1222
|
}
|
|
578
|
-
/** Releases the exact element listeners and timer owned by the current wait. */
|
|
579
1223
|
#teardown() {
|
|
580
1224
|
this.#timers.clearAll();
|
|
581
1225
|
this.#element?.removeEventListener("transitionend", this.#onTerminal);
|
|
@@ -586,111 +1230,138 @@ var TransitionCompletion = class {
|
|
|
586
1230
|
}
|
|
587
1231
|
};
|
|
588
1232
|
|
|
589
|
-
|
|
590
|
-
|
|
1233
|
+
var SidebarController = class _SidebarController extends Controller {
|
|
1234
|
+
#numbers = new NumberValueReader();
|
|
591
1235
|
static targets = ["trigger", "panel", "backdrop"];
|
|
592
1236
|
static values = {
|
|
593
1237
|
breakpoint: { type: Number, default: 768 },
|
|
594
1238
|
key: { type: String, default: "" },
|
|
595
1239
|
collapsed: { type: Boolean, default: false }
|
|
596
1240
|
};
|
|
1241
|
+
static valueConstraints = {
|
|
1242
|
+
breakpoint: NUMBER_BOUNDS.nonNegative
|
|
1243
|
+
};
|
|
597
1244
|
static actions = ["close", "open", "toggle"];
|
|
598
1245
|
static events = ["close", "open", "reconcile"];
|
|
599
|
-
/** Exact panel currently owned by the modal lifecycle (survives target churn safely). */
|
|
600
1246
|
#activePanel = null;
|
|
601
|
-
/** Owns the overlay modal side effects; Escape closes, focus falls to trigger. */
|
|
602
1247
|
#trap = new FocusTrap(() => this.#activePanel ?? this.panelTarget, {
|
|
603
1248
|
onEscape: () => this.#closeOverlay("escape"),
|
|
604
1249
|
fallbackFocus: () => this.hasTriggerTarget ? this.triggerTarget : null
|
|
605
1250
|
});
|
|
606
|
-
/** Current responsive mode. */
|
|
607
1251
|
#mode = "inline";
|
|
608
|
-
/** Persisted inline preference: whether the rail is collapsed. */
|
|
609
1252
|
#collapsed = false;
|
|
610
|
-
/** The matched media query (`min-width: breakpoint`), watched for mode changes. */
|
|
611
1253
|
#mql = null;
|
|
612
|
-
|
|
1254
|
+
#mediaListeners = new ListenerSet();
|
|
613
1255
|
#mqlQuery = null;
|
|
614
|
-
/** Owns the cancellable close-transition wait and its bounded fallback. */
|
|
615
1256
|
#transition = new TransitionCompletion();
|
|
616
|
-
/** Distinguishes dynamic target churn from callbacks around controller teardown. */
|
|
617
1257
|
#connected = false;
|
|
618
|
-
|
|
619
|
-
#
|
|
1258
|
+
#storageKeyRead = null;
|
|
1259
|
+
#expanded = false;
|
|
1260
|
+
#backdrop = {
|
|
1261
|
+
state: "closed",
|
|
1262
|
+
hidden: true
|
|
1263
|
+
};
|
|
1264
|
+
#expandedLease = new AttributeLease("aria-expanded", this.identifier);
|
|
1265
|
+
#stateLease = new AttributeLease("data-state", this.identifier);
|
|
1266
|
+
#hiddenLease = new AttributeLease("hidden", this.identifier);
|
|
1267
|
+
#modeLease = new AttributeLease("data-mode", this.identifier);
|
|
1268
|
+
#morphRender = new MorphRenderWatcher(() => this.#repair());
|
|
1269
|
+
#lived = false;
|
|
620
1270
|
connect() {
|
|
1271
|
+
this.#trap.connect();
|
|
621
1272
|
this.#connected = true;
|
|
622
|
-
this.#beforeCache.activate();
|
|
623
1273
|
this.#activePanel = this.hasPanelTarget ? this.panelTarget : null;
|
|
1274
|
+
this.#storageKeyRead = this.#storageKey;
|
|
624
1275
|
this.#collapsed = this.#restoreCollapsed();
|
|
625
1276
|
this.#mqlQuery = this.#breakpointQuery;
|
|
626
1277
|
this.#mql = this.#matchBreakpoint(this.#mqlQuery);
|
|
627
|
-
this.#mql
|
|
628
|
-
this.#
|
|
629
|
-
this.#
|
|
1278
|
+
if (this.#mql) this.#mediaListeners.add(this.#mql, "change", this.#onMediaChange);
|
|
1279
|
+
const mode = this.#computeMode();
|
|
1280
|
+
const resume = this.#lived && mode === "overlay" && this.#isOverlayOpen;
|
|
1281
|
+
this.#lived = true;
|
|
1282
|
+
if (resume) this.#trap.activate();
|
|
1283
|
+
else this.#applyMode(mode, false);
|
|
1284
|
+
this.#morphRender.observe(this.element);
|
|
630
1285
|
}
|
|
631
1286
|
disconnect() {
|
|
632
1287
|
this.#connected = false;
|
|
633
|
-
this.#
|
|
634
|
-
this.#
|
|
635
|
-
this.#mql?.removeEventListener("change", this.#onMediaChange);
|
|
1288
|
+
this.#morphRender.disconnect();
|
|
1289
|
+
this.#mediaListeners.dispose();
|
|
636
1290
|
this.#mql = null;
|
|
637
1291
|
this.#mqlQuery = null;
|
|
638
1292
|
this.#transition.cancel();
|
|
639
|
-
this.#trap.
|
|
1293
|
+
this.#trap.disconnect(this);
|
|
640
1294
|
this.#activePanel = null;
|
|
641
1295
|
}
|
|
642
|
-
/** Adopts a panel target added by a Turbo morph after the controller connected. */
|
|
643
1296
|
panelTargetConnected(panel) {
|
|
644
|
-
if (!this.#connected || this.#activePanel?.isConnected
|
|
1297
|
+
if (!this.#connected || this.#activePanel?.isConnected) return;
|
|
645
1298
|
this.#adoptPanel(panel);
|
|
646
1299
|
}
|
|
647
|
-
/** Closes and releases overlay side effects when the actively trapped panel disappears. */
|
|
648
1300
|
panelTargetDisconnected(panel) {
|
|
649
|
-
|
|
1301
|
+
const stays = this.panelTargets.includes(panel);
|
|
1302
|
+
if (!stays) {
|
|
1303
|
+
this.#modeLease.return(panel);
|
|
1304
|
+
this.#stateLease.return(panel);
|
|
1305
|
+
this.#hiddenLease.return(panel);
|
|
1306
|
+
}
|
|
1307
|
+
if (panel !== this.#activePanel || stays && this.panelTarget === panel) return;
|
|
650
1308
|
this.#transition.cancel();
|
|
651
1309
|
this.#activePanel = null;
|
|
652
|
-
if (
|
|
653
|
-
|
|
654
|
-
|
|
1310
|
+
if (stays) {
|
|
1311
|
+
this.#writeState(panel, "closed");
|
|
1312
|
+
this.#writeHidden(panel, true);
|
|
1313
|
+
}
|
|
655
1314
|
this.#hideBackdrop();
|
|
656
1315
|
this.#setExpandedAttr(false);
|
|
657
|
-
this.#
|
|
1316
|
+
if (!this.#connected) {
|
|
1317
|
+
this.#trap.deactivate({ restoreFocus: false });
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
658
1320
|
if (this.hasPanelTarget) this.#adoptPanel(this.panelTarget);
|
|
1321
|
+
else this.#trap.deactivate();
|
|
1322
|
+
}
|
|
1323
|
+
triggerTargetConnected() {
|
|
1324
|
+
if (this.#connected) this.#reflectExpanded();
|
|
1325
|
+
}
|
|
1326
|
+
triggerTargetDisconnected(trigger) {
|
|
1327
|
+
if (!this.triggerTargets.includes(trigger)) this.#expandedLease.return(trigger);
|
|
1328
|
+
if (this.#connected) this.#reflectExpanded();
|
|
1329
|
+
}
|
|
1330
|
+
backdropTargetConnected() {
|
|
1331
|
+
if (this.#connected) this.#reflectBackdrop();
|
|
1332
|
+
}
|
|
1333
|
+
backdropTargetDisconnected(backdrop) {
|
|
1334
|
+
if (!this.backdropTargets.includes(backdrop)) {
|
|
1335
|
+
this.#stateLease.return(backdrop);
|
|
1336
|
+
this.#hiddenLease.return(backdrop);
|
|
1337
|
+
}
|
|
1338
|
+
if (this.#connected) this.#reflectBackdrop();
|
|
659
1339
|
}
|
|
660
|
-
/**
|
|
661
|
-
* Rebinds responsive observation when `breakpoint` changes at runtime.
|
|
662
|
-
*
|
|
663
|
-
* Stimulus calls value callbacks before `connect()`, so the connected guard
|
|
664
|
-
* prevents an eager subscription. Equivalent normalized queries retain the
|
|
665
|
-
* existing listener instead of allocating duplicate `MediaQueryList` objects.
|
|
666
|
-
*/
|
|
667
1340
|
breakpointValueChanged() {
|
|
668
1341
|
if (!this.#connected) return;
|
|
669
1342
|
const query = this.#breakpointQuery;
|
|
670
1343
|
if (this.#mqlQuery === query) return;
|
|
671
|
-
this.#
|
|
1344
|
+
this.#mediaListeners.dispose();
|
|
672
1345
|
this.#mqlQuery = query;
|
|
673
1346
|
this.#mql = this.#matchBreakpoint(query);
|
|
674
|
-
this.#mql
|
|
1347
|
+
if (this.#mql) this.#mediaListeners.add(this.#mql, "change", this.#onMediaChange);
|
|
675
1348
|
const next = this.#computeMode();
|
|
676
1349
|
if (next !== this.#mode) this.#applyMode(next, true);
|
|
677
1350
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
this.#
|
|
689
|
-
|
|
690
|
-
this.#setOverlayClosedImmediate();
|
|
691
|
-
});
|
|
692
|
-
/** Toggles the panel: inline flips collapsed/expanded, overlay flips open/closed. */
|
|
1351
|
+
keyValueChanged() {
|
|
1352
|
+
if (!this.#connected) return;
|
|
1353
|
+
const key = this.#storageKey;
|
|
1354
|
+
if (key === this.#storageKeyRead) return;
|
|
1355
|
+
this.#storageKeyRead = key;
|
|
1356
|
+
const stored = this.#readStoredCollapsed(key);
|
|
1357
|
+
if (stored === null || stored === this.#collapsed) return;
|
|
1358
|
+
this.#collapsed = stored;
|
|
1359
|
+
if (this.#isOverlay) return;
|
|
1360
|
+
this.#applyInlineState(stored);
|
|
1361
|
+
this.#reportReconcile();
|
|
1362
|
+
}
|
|
693
1363
|
toggle(event) {
|
|
1364
|
+
if (!this.#connected) return;
|
|
694
1365
|
const reason = stateReasonFor(event);
|
|
695
1366
|
if (this.#isOverlay) {
|
|
696
1367
|
this.#isOverlayOpen ? this.#closeOverlay(reason) : this.#openOverlay(reason);
|
|
@@ -698,83 +1369,78 @@ var SidebarController = class extends Controller {
|
|
|
698
1369
|
this.#setCollapsed(!this.#collapsed, reason);
|
|
699
1370
|
}
|
|
700
1371
|
}
|
|
701
|
-
/** Shows the panel (inline: expand; overlay: open). */
|
|
702
1372
|
open(event) {
|
|
1373
|
+
if (!this.#connected) return;
|
|
703
1374
|
const reason = stateReasonFor(event);
|
|
704
1375
|
if (this.#isOverlay) this.#openOverlay(reason);
|
|
705
1376
|
else this.#setCollapsed(false, reason);
|
|
706
1377
|
}
|
|
707
|
-
/** Hides the panel (inline: collapse; overlay: close). */
|
|
708
1378
|
close(event) {
|
|
1379
|
+
if (!this.#connected) return;
|
|
709
1380
|
const reason = this.#closeReason(event);
|
|
710
1381
|
if (this.#isOverlay) this.#closeOverlay(reason);
|
|
711
1382
|
else this.#setCollapsed(true, reason);
|
|
712
1383
|
}
|
|
713
|
-
/**
|
|
714
|
-
* Reads why a close happened. The backdrop has no action of its own — the
|
|
715
|
-
* consumer wires this one onto it — so the shared `"outside"` vocabulary is
|
|
716
|
-
* only reachable by recognising that target here.
|
|
717
|
-
*/
|
|
718
1384
|
#closeReason(event) {
|
|
719
1385
|
if (this.hasBackdropTarget && event?.currentTarget === this.backdropTarget) return "outside";
|
|
720
1386
|
return stateReasonFor(event);
|
|
721
1387
|
}
|
|
722
|
-
// --- Mode handling ---------------------------------------------------------
|
|
723
|
-
/**
|
|
724
|
-
* Re-renders the closed/default state for `mode` and records it.
|
|
725
|
-
*
|
|
726
|
-
* @param report - Whether the mode change is one the consumer can observe: a
|
|
727
|
-
* viewport change re-derives the state under them, the connect-time baseline
|
|
728
|
-
* does not.
|
|
729
|
-
*/
|
|
730
1388
|
#applyMode(mode, report) {
|
|
731
1389
|
this.#mode = mode;
|
|
732
|
-
if (this.hasPanelTarget) this.panelTarget
|
|
1390
|
+
if (this.hasPanelTarget) this.#modeLease.write(this.panelTarget, mode);
|
|
733
1391
|
if (mode === "inline") {
|
|
734
1392
|
this.#transition.cancel();
|
|
735
|
-
this.#trap.deactivate({ restoreFocus: false });
|
|
736
|
-
if (this.hasPanelTarget) this.panelTarget
|
|
1393
|
+
if (report) this.#trap.deactivate({ restoreFocus: false });
|
|
1394
|
+
if (this.hasPanelTarget) this.#writeHidden(this.panelTarget, false);
|
|
737
1395
|
this.#hideBackdrop();
|
|
738
1396
|
this.#applyInlineState(this.#collapsed);
|
|
739
1397
|
} else {
|
|
740
|
-
this.#transition.cancel();
|
|
741
|
-
this.#trap.deactivate({ restoreFocus: false });
|
|
742
1398
|
this.#setOverlayClosedImmediate();
|
|
743
1399
|
}
|
|
744
|
-
if (report
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1400
|
+
if (report) this.#reportReconcile();
|
|
1401
|
+
}
|
|
1402
|
+
#repair() {
|
|
1403
|
+
if (!this.hasPanelTarget) return;
|
|
1404
|
+
if (this.#mode === "inline") {
|
|
1405
|
+
this.#applyMode("inline", false);
|
|
1406
|
+
return;
|
|
749
1407
|
}
|
|
1408
|
+
const panel = this.panelTarget;
|
|
1409
|
+
this.#modeLease.write(panel, "overlay");
|
|
1410
|
+
this.#setOverlayState(this.#expanded ? "open" : "closed");
|
|
1411
|
+
const hidden = !this.#expanded && !this.#trap.active;
|
|
1412
|
+
this.#writeHidden(panel, hidden);
|
|
1413
|
+
this.#writeBackdropHidden(hidden);
|
|
1414
|
+
this.#setExpandedAttr(this.#expanded);
|
|
1415
|
+
}
|
|
1416
|
+
#reportReconcile() {
|
|
1417
|
+
this.dispatch("reconcile", {
|
|
1418
|
+
detail: { mode: this.#mode, open: this.#isExpanded },
|
|
1419
|
+
cancelable: false
|
|
1420
|
+
});
|
|
750
1421
|
}
|
|
751
|
-
/** Reconciles a replacement panel with the current responsive mode and DOM state. */
|
|
752
1422
|
#adoptPanel(panel) {
|
|
753
1423
|
this.#transition.cancel();
|
|
754
|
-
const trapWasActive = this.#trap.active;
|
|
755
1424
|
this.#activePanel = panel;
|
|
756
|
-
|
|
1425
|
+
this.#modeLease.write(panel, this.#mode);
|
|
757
1426
|
if (this.#mode === "inline") {
|
|
758
|
-
panel
|
|
759
|
-
panel
|
|
1427
|
+
this.#writeHidden(panel, false);
|
|
1428
|
+
this.#writeState(panel, this.#collapsed ? "collapsed" : "expanded");
|
|
760
1429
|
this.#hideBackdrop();
|
|
761
1430
|
this.#setExpandedAttr(!this.#collapsed);
|
|
762
|
-
this.#trap.deactivate({ restoreFocus: false });
|
|
763
1431
|
return;
|
|
764
1432
|
}
|
|
765
1433
|
if (panel.getAttribute("data-state") === "open") {
|
|
766
|
-
panel
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
this.backdropTarget.hidden = false;
|
|
770
|
-
}
|
|
1434
|
+
this.#writeHidden(panel, false);
|
|
1435
|
+
this.#writeBackdropState("open");
|
|
1436
|
+
this.#writeBackdropHidden(false);
|
|
771
1437
|
this.#setExpandedAttr(true);
|
|
772
|
-
if (
|
|
773
|
-
this.#trap.activate();
|
|
1438
|
+
if (this.#trap.active) this.#trap.refreshContainer();
|
|
1439
|
+
else this.#trap.activate();
|
|
774
1440
|
return;
|
|
775
1441
|
}
|
|
776
|
-
panel
|
|
777
|
-
panel
|
|
1442
|
+
this.#writeState(panel, "closed");
|
|
1443
|
+
this.#writeHidden(panel, true);
|
|
778
1444
|
this.#hideBackdrop();
|
|
779
1445
|
this.#setExpandedAttr(false);
|
|
780
1446
|
this.#trap.deactivate();
|
|
@@ -790,8 +1456,6 @@ var SidebarController = class extends Controller {
|
|
|
790
1456
|
if (typeof window.matchMedia !== "function") return null;
|
|
791
1457
|
return window.matchMedia(query);
|
|
792
1458
|
}
|
|
793
|
-
// --- Inline (rail) ---------------------------------------------------------
|
|
794
|
-
/** Sets, reflects, and persists the inline collapsed preference. */
|
|
795
1459
|
#setCollapsed(collapsed, reason) {
|
|
796
1460
|
if (collapsed === this.#collapsed) return;
|
|
797
1461
|
this.#collapsed = collapsed;
|
|
@@ -799,21 +1463,18 @@ var SidebarController = class extends Controller {
|
|
|
799
1463
|
this.#report(!collapsed, reason);
|
|
800
1464
|
this.#persistCollapsed(collapsed);
|
|
801
1465
|
}
|
|
802
|
-
/** Reflects the inline rail state onto the panel and trigger. */
|
|
803
1466
|
#applyInlineState(collapsed) {
|
|
804
1467
|
if (this.hasPanelTarget) {
|
|
805
|
-
this.panelTarget
|
|
1468
|
+
this.#writeState(this.panelTarget, collapsed ? "collapsed" : "expanded");
|
|
806
1469
|
}
|
|
807
1470
|
this.#setExpandedAttr(!collapsed);
|
|
808
1471
|
}
|
|
809
|
-
// --- Overlay (off-canvas modal) -------------------------------------------
|
|
810
|
-
/** Opens the overlay: reveal it, commit a starting frame, then trap focus. */
|
|
811
1472
|
#openOverlay(reason) {
|
|
812
1473
|
if (!this.hasPanelTarget || this.#isOverlayOpen) return;
|
|
813
1474
|
this.#transition.cancel();
|
|
814
1475
|
this.#activePanel = this.panelTarget;
|
|
815
|
-
this.panelTarget
|
|
816
|
-
|
|
1476
|
+
this.#writeHidden(this.panelTarget, false);
|
|
1477
|
+
this.#writeBackdropHidden(false);
|
|
817
1478
|
void this.panelTarget.offsetWidth;
|
|
818
1479
|
this.#setOverlayState("open");
|
|
819
1480
|
this.#setExpandedAttr(true);
|
|
@@ -821,7 +1482,6 @@ var SidebarController = class extends Controller {
|
|
|
821
1482
|
if (!this.#isOverlayOpen) return;
|
|
822
1483
|
this.#trap.activate();
|
|
823
1484
|
}
|
|
824
|
-
/** Closes the overlay: start the exit transition, defer hide + trap teardown. */
|
|
825
1485
|
#closeOverlay(reason) {
|
|
826
1486
|
if (!this.hasPanelTarget || !this.#isOverlayOpen) return;
|
|
827
1487
|
this.#setOverlayState("closed");
|
|
@@ -830,93 +1490,90 @@ var SidebarController = class extends Controller {
|
|
|
830
1490
|
if (this.#isOverlayOpen) return;
|
|
831
1491
|
this.#hideAfterTransition();
|
|
832
1492
|
}
|
|
833
|
-
/** Renders the overlay closed state up front (used when entering overlay mode). */
|
|
834
1493
|
#setOverlayClosedImmediate() {
|
|
835
1494
|
this.#setOverlayState("closed");
|
|
836
1495
|
this.#setExpandedAttr(false);
|
|
837
|
-
if (this.hasPanelTarget) this.panelTarget
|
|
1496
|
+
if (this.hasPanelTarget) this.#writeHidden(this.panelTarget, true);
|
|
838
1497
|
this.#hideBackdrop();
|
|
839
1498
|
}
|
|
840
|
-
/** Syncs `data-state` on the panel and backdrop together (overlay). */
|
|
841
1499
|
#setOverlayState(state) {
|
|
842
|
-
if (this.hasPanelTarget) this.panelTarget
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* Applies `hidden` once the close transition ends so the exit slide can play,
|
|
847
|
-
* then reverts the modal side effects. The shared waiter completes
|
|
848
|
-
* synchronously for 0ms transitions and supplies a bounded fallback when the
|
|
849
|
-
* browser emits no terminal event.
|
|
850
|
-
*/
|
|
1500
|
+
if (this.hasPanelTarget) this.#writeState(this.panelTarget, state);
|
|
1501
|
+
this.#writeBackdropState(state);
|
|
1502
|
+
}
|
|
851
1503
|
#hideAfterTransition() {
|
|
852
1504
|
const panel = this.panelTarget;
|
|
853
1505
|
this.#activePanel = panel;
|
|
854
1506
|
this.#transition.wait(panel, () => this.#applyOverlayHidden(panel));
|
|
855
1507
|
}
|
|
856
|
-
/** Hides the panel/backdrop and tears down the trap after the exit transition. */
|
|
857
1508
|
#applyOverlayHidden(panel) {
|
|
858
|
-
panel
|
|
1509
|
+
this.#writeHidden(panel, true);
|
|
859
1510
|
this.#hideBackdrop();
|
|
860
1511
|
this.#trap.deactivate();
|
|
861
1512
|
}
|
|
862
1513
|
#hideBackdrop() {
|
|
863
|
-
|
|
864
|
-
this
|
|
865
|
-
|
|
1514
|
+
this.#writeBackdropState("closed");
|
|
1515
|
+
this.#writeBackdropHidden(true);
|
|
1516
|
+
}
|
|
1517
|
+
#writeState(element, state) {
|
|
1518
|
+
this.#stateLease.write(element, state);
|
|
1519
|
+
}
|
|
1520
|
+
#writeHidden(element, hidden) {
|
|
1521
|
+
this.#hiddenLease.write(element, hidden ? "" : null);
|
|
1522
|
+
}
|
|
1523
|
+
#writeBackdropState(state) {
|
|
1524
|
+
this.#backdrop.state = state;
|
|
1525
|
+
if (this.hasBackdropTarget) this.#writeState(this.backdropTarget, state);
|
|
1526
|
+
}
|
|
1527
|
+
#writeBackdropHidden(hidden) {
|
|
1528
|
+
this.#backdrop.hidden = hidden;
|
|
1529
|
+
if (this.hasBackdropTarget) this.#writeHidden(this.backdropTarget, hidden);
|
|
1530
|
+
}
|
|
1531
|
+
#reflectBackdrop() {
|
|
1532
|
+
this.#writeBackdropState(this.#backdrop.state);
|
|
1533
|
+
this.#writeBackdropHidden(this.#backdrop.hidden);
|
|
866
1534
|
}
|
|
867
|
-
// --- Shared helpers --------------------------------------------------------
|
|
868
1535
|
#setExpandedAttr(expanded) {
|
|
1536
|
+
this.#expanded = expanded;
|
|
1537
|
+
this.#reflectExpanded();
|
|
1538
|
+
}
|
|
1539
|
+
#reflectExpanded() {
|
|
869
1540
|
if (this.hasTriggerTarget) {
|
|
870
|
-
this.triggerTarget
|
|
1541
|
+
this.#expandedLease.write(this.triggerTarget, this.#expanded ? "true" : "false");
|
|
871
1542
|
}
|
|
872
1543
|
}
|
|
873
|
-
/** Reports a move of the expanded state, naming the mode it happened in. */
|
|
874
1544
|
#report(open, reason) {
|
|
875
|
-
if (!this.#reporting) return;
|
|
876
1545
|
const detail = { reason, mode: this.#mode };
|
|
877
1546
|
if (open) this.dispatch("open", { detail, cancelable: false });
|
|
878
1547
|
else this.dispatch("close", { detail, cancelable: false });
|
|
879
1548
|
}
|
|
880
|
-
/** Whether the panel currently counts as expanded in the mode it is in. */
|
|
881
1549
|
get #isExpanded() {
|
|
882
1550
|
return this.#isOverlay ? this.#isOverlayOpen : !this.#collapsed;
|
|
883
1551
|
}
|
|
884
|
-
/**
|
|
885
|
-
* Resolves the inline collapsed preference, in priority order:
|
|
886
|
-
* 1. the persisted value (when a `key` is set and storage is readable),
|
|
887
|
-
* 2. the current DOM `data-state` — so a Turbo cache restore / morph that
|
|
888
|
-
* reconnects over already-rendered markup keeps the live state (the DOM is
|
|
889
|
-
* the source of truth) even when no `key` / localStorage is available,
|
|
890
|
-
* 3. the declared `collapsed` value.
|
|
891
|
-
*/
|
|
892
1552
|
#restoreCollapsed() {
|
|
893
|
-
const
|
|
894
|
-
if (
|
|
895
|
-
const result = readLocalStorage(key);
|
|
896
|
-
if (result.ok && result.value !== null) return result.value === "1";
|
|
897
|
-
}
|
|
1553
|
+
const stored = this.#readStoredCollapsed(this.#storageKey);
|
|
1554
|
+
if (stored !== null) return stored;
|
|
898
1555
|
const domState = this.hasPanelTarget ? this.panelTarget.getAttribute("data-state") : null;
|
|
899
1556
|
if (domState === "collapsed") return true;
|
|
900
1557
|
if (domState === "expanded") return false;
|
|
901
1558
|
return this.collapsedValue;
|
|
902
1559
|
}
|
|
903
|
-
|
|
1560
|
+
#readStoredCollapsed(key) {
|
|
1561
|
+
if (!key) return null;
|
|
1562
|
+
const result = readLocalStorage(key);
|
|
1563
|
+
if (!result.ok || result.value === null) return null;
|
|
1564
|
+
return result.value === "1";
|
|
1565
|
+
}
|
|
904
1566
|
#persistCollapsed(collapsed) {
|
|
905
1567
|
const key = this.#storageKey;
|
|
906
1568
|
if (!key) return;
|
|
907
1569
|
writeLocalStorage(key, collapsed ? "1" : "0");
|
|
908
1570
|
}
|
|
909
|
-
/**
|
|
910
|
-
* @stimeoRuntimeOnly `key` names the storage slot the collapsed state is saved under; the state
|
|
911
|
-
* itself comes from the user.
|
|
912
|
-
*/
|
|
913
1571
|
get #storageKey() {
|
|
914
1572
|
return this.keyValue ? `stimeo--sidebar:${this.keyValue}` : "";
|
|
915
1573
|
}
|
|
916
|
-
/** Valid breakpoint CSS query, defaulting malformed or negative values to 768px. */
|
|
917
1574
|
get #breakpointQuery() {
|
|
918
|
-
const value = this
|
|
919
|
-
const breakpoint =
|
|
1575
|
+
const value = this.#safeBreakpoint;
|
|
1576
|
+
const breakpoint = value;
|
|
920
1577
|
return `(min-width: ${breakpoint}px)`;
|
|
921
1578
|
}
|
|
922
1579
|
get #isOverlay() {
|
|
@@ -925,8 +1582,16 @@ var SidebarController = class extends Controller {
|
|
|
925
1582
|
get #isOverlayOpen() {
|
|
926
1583
|
return this.#isOverlay && this.hasPanelTarget && this.panelTarget.getAttribute("data-state") === "open";
|
|
927
1584
|
}
|
|
1585
|
+
get #safeBreakpoint() {
|
|
1586
|
+
return this.#numbers.read(
|
|
1587
|
+
this,
|
|
1588
|
+
"breakpoint",
|
|
1589
|
+
this.breakpointValue,
|
|
1590
|
+
_SidebarController.values.breakpoint.default,
|
|
1591
|
+
_SidebarController.valueConstraints.breakpoint
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
928
1594
|
};
|
|
929
1595
|
|
|
930
1596
|
export { SidebarController };
|
|
931
|
-
//# sourceMappingURL=sidebar_controller.js.map
|
|
932
1597
|
//# sourceMappingURL=sidebar_controller.js.map
|