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,13 +1,31 @@
|
|
|
1
1
|
import { Controller } from '@hotwired/stimulus';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
|
|
4
|
+
var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
|
|
5
|
+
function stateReasonFor(event) {
|
|
6
|
+
if (!event) return "api";
|
|
7
|
+
if (FOCUS_EVENTS.has(event.type)) return "focus";
|
|
8
|
+
if (POINTER_EVENTS.has(event.type)) return "pointer";
|
|
9
|
+
return "user";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function actionSource(source) {
|
|
13
|
+
if (source instanceof HTMLElement) {
|
|
14
|
+
return { event: null, host: source, origin: source, reason: "api" };
|
|
15
|
+
}
|
|
16
|
+
const event = source ?? null;
|
|
17
|
+
return {
|
|
18
|
+
event,
|
|
19
|
+
host: event?.currentTarget instanceof Element ? event.currentTarget : null,
|
|
20
|
+
origin: event?.target instanceof Element ? event.target : null,
|
|
21
|
+
reason: stateReasonFor(event)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
4
24
|
|
|
5
|
-
// src/utils/logical_scroll.ts
|
|
6
25
|
function isRtl(element) {
|
|
7
26
|
return window.getComputedStyle(element).direction === "rtl";
|
|
8
27
|
}
|
|
9
28
|
|
|
10
|
-
// src/utils/arrow_step.ts
|
|
11
29
|
function logicalArrowStep(key, element) {
|
|
12
30
|
if (key === "ArrowDown") return 1;
|
|
13
31
|
if (key === "ArrowUp") return -1;
|
|
@@ -19,183 +37,277 @@ function isReservedArrowChord(event, allow = []) {
|
|
|
19
37
|
if (!event.key.startsWith("Arrow")) return false;
|
|
20
38
|
return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
|
|
21
39
|
}
|
|
40
|
+
function hasModifierChord(event) {
|
|
41
|
+
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function setAttributeIfChanged(element, name, value, beforeWrite) {
|
|
45
|
+
if (element.getAttribute(name) === value) return false;
|
|
46
|
+
if (value === null) element.removeAttribute(name);
|
|
47
|
+
else element.setAttribute(name, value);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
22
50
|
|
|
23
|
-
|
|
51
|
+
function sharedRegistry(key, create) {
|
|
52
|
+
const symbol = Symbol.for(key);
|
|
53
|
+
const scope = globalThis;
|
|
54
|
+
const existing = scope[symbol];
|
|
55
|
+
if (existing !== void 0) return existing;
|
|
56
|
+
const registry2 = create();
|
|
57
|
+
Object.defineProperty(globalThis, symbol, { value: registry2 });
|
|
58
|
+
return registry2;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
var registry = sharedRegistry(
|
|
62
|
+
"stimeo-ui.attribute-lease.registry.v1",
|
|
63
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
|
|
64
|
+
);
|
|
65
|
+
function isAttributeValue(value) {
|
|
66
|
+
return value === null || typeof value === "string";
|
|
67
|
+
}
|
|
68
|
+
function readLeaseRecord(element, record) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(element.getAttribute(record) ?? "null");
|
|
71
|
+
if (!Array.isArray(parsed)) return void 0;
|
|
72
|
+
const [original, written, ...rest] = parsed;
|
|
73
|
+
const linked = rest[0] !== false;
|
|
74
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
75
|
+
if (!beneath.every((under) => typeof under === "string")) return void 0;
|
|
76
|
+
return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
|
|
77
|
+
} catch {
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function attributeStack(element, attribute) {
|
|
82
|
+
const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
|
|
83
|
+
registry.stacks.set(element, byAttribute);
|
|
84
|
+
const known = byAttribute.get(attribute);
|
|
85
|
+
if (known) return known;
|
|
86
|
+
const found = [];
|
|
87
|
+
for (const record of registry.names.get(attribute) ?? []) {
|
|
88
|
+
const read = readLeaseRecord(element, record);
|
|
89
|
+
if (read) found.push({ ...read, record });
|
|
90
|
+
}
|
|
91
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
92
|
+
const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
|
|
93
|
+
lease: null,
|
|
94
|
+
record,
|
|
95
|
+
written,
|
|
96
|
+
original,
|
|
97
|
+
linked: linked && index > 0
|
|
98
|
+
}));
|
|
99
|
+
byAttribute.set(attribute, stack);
|
|
100
|
+
return stack;
|
|
101
|
+
}
|
|
102
|
+
function findHold(stack, lease, record) {
|
|
103
|
+
return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
|
|
104
|
+
}
|
|
24
105
|
var AttributeLease = class {
|
|
25
106
|
#attribute;
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
107
|
+
#record;
|
|
108
|
+
#base;
|
|
109
|
+
#preserveAuthor;
|
|
110
|
+
#held = /* @__PURE__ */ new Set();
|
|
111
|
+
constructor(attribute, owner, options = {}) {
|
|
29
112
|
this.#attribute = attribute;
|
|
113
|
+
this.#record = `data-${owner}-${attribute}-lease`;
|
|
114
|
+
this.#base = options.base === true;
|
|
115
|
+
this.#preserveAuthor = options.preserveAuthor === true;
|
|
116
|
+
const names = registry.names;
|
|
117
|
+
names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
|
|
30
118
|
}
|
|
31
|
-
/** Writes or removes the leased attribute while preserving its authored value. */
|
|
32
119
|
write(element, value) {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
this.#reflect(element, value);
|
|
120
|
+
const stack = attributeStack(element, this.#attribute);
|
|
121
|
+
const hold = this.#take(stack) ?? this.#acquire(element, stack);
|
|
122
|
+
this.#held.add(element);
|
|
123
|
+
const shows = !this.#covered(element, stack, hold);
|
|
124
|
+
hold.written = value;
|
|
125
|
+
this.#mark(element, stack);
|
|
126
|
+
if (shows) this.#reflect(element, value);
|
|
43
127
|
}
|
|
44
|
-
/** Returns one lease without overwriting a value subsequently authored by a consumer. */
|
|
45
128
|
return(element) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
this.#
|
|
49
|
-
const
|
|
50
|
-
if (
|
|
129
|
+
const held = this.#held.delete(element);
|
|
130
|
+
const known = registry.stacks.get(element)?.has(this.#attribute);
|
|
131
|
+
const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
|
|
132
|
+
const hold = this.#take(stack);
|
|
133
|
+
if (!hold) return false;
|
|
134
|
+
const carries = element.getAttribute(this.#attribute) === hold.written;
|
|
135
|
+
const owns = carries && !this.#covered(element, stack, hold);
|
|
136
|
+
const index = stack.indexOf(hold);
|
|
137
|
+
stack.splice(index, 1);
|
|
138
|
+
const above = stack[index];
|
|
139
|
+
if (above) above.linked &&= hold.linked;
|
|
140
|
+
this.#mark(element, stack);
|
|
141
|
+
if (!owns) return true;
|
|
142
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
143
|
+
this.#reflect(element, below ? below.written : hold.original);
|
|
144
|
+
return true;
|
|
51
145
|
}
|
|
52
|
-
/** Reflects only a real value transition, avoiding self-triggered mutation work. */
|
|
53
|
-
#reflect(element, value) {
|
|
54
|
-
if (element.getAttribute(this.#attribute) === value) return;
|
|
55
|
-
if (value === null) element.removeAttribute(this.#attribute);
|
|
56
|
-
else element.setAttribute(this.#attribute, value);
|
|
57
|
-
}
|
|
58
|
-
/** Returns every outstanding lease using the same ownership check as {@link return}. */
|
|
59
146
|
returnAll() {
|
|
60
|
-
for (const element of Array.from(this.#
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
147
|
+
for (const element of Array.from(this.#held)) this.return(element);
|
|
148
|
+
}
|
|
149
|
+
#take(stack) {
|
|
150
|
+
const hold = findHold(stack, this, this.#record);
|
|
151
|
+
if (hold) hold.lease = this;
|
|
152
|
+
return hold;
|
|
153
|
+
}
|
|
154
|
+
#acquire(element, stack) {
|
|
155
|
+
const current = element.getAttribute(this.#attribute);
|
|
156
|
+
const top = stack.at(-1);
|
|
157
|
+
const bottom = stack[0];
|
|
158
|
+
const over = top?.written === current ? top : void 0;
|
|
159
|
+
const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
|
|
160
|
+
const hold = {
|
|
161
|
+
lease: this,
|
|
162
|
+
record: this.#record,
|
|
163
|
+
written: current,
|
|
164
|
+
original: from ? from.original : current,
|
|
165
|
+
linked: !this.#base && over !== void 0
|
|
166
|
+
};
|
|
167
|
+
if (this.#base && bottom) {
|
|
168
|
+
bottom.linked = true;
|
|
169
|
+
stack.unshift(hold);
|
|
170
|
+
} else stack.push(hold);
|
|
171
|
+
return hold;
|
|
172
|
+
}
|
|
173
|
+
#covered(element, stack, hold) {
|
|
174
|
+
const current = element.getAttribute(this.#attribute);
|
|
175
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
|
|
176
|
+
}
|
|
177
|
+
#mark(element, stack) {
|
|
178
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
179
|
+
stack.forEach((hold, index) => {
|
|
180
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
181
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
182
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
183
|
+
records.set(
|
|
184
|
+
hold.record,
|
|
185
|
+
hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
for (const [name, recorded] of records) {
|
|
189
|
+
setAttributeIfChanged(element, name, recorded);
|
|
83
190
|
}
|
|
84
191
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
_BeforeCacheReset.#subscribers.delete(this);
|
|
88
|
-
if (_BeforeCacheReset.#subscribers.size > 0) return;
|
|
89
|
-
document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
|
|
192
|
+
#reflect(element, value) {
|
|
193
|
+
setAttributeIfChanged(element, this.#attribute, value);
|
|
90
194
|
}
|
|
91
195
|
};
|
|
92
196
|
|
|
93
|
-
// src/utils/detach_gate.ts
|
|
94
197
|
var DetachGate = class _DetachGate {
|
|
95
|
-
/** Set while a probe is queued, waiting for a reconnect to cancel it. */
|
|
96
198
|
#pending = false;
|
|
97
|
-
/**
|
|
98
|
-
* True while a probe is queued — the last disconnect was ambiguous and no
|
|
99
|
-
* reconnect has cancelled it yet. Read it from `connect()` to tell the
|
|
100
|
-
* reconnect half of an in-page move from a first connect: a controller whose
|
|
101
|
-
* initialisation restarts a measurement (a min-duration floor, an elapsed
|
|
102
|
-
* counter) must skip it for the move, where nothing actually restarted.
|
|
103
|
-
*/
|
|
104
199
|
get pending() {
|
|
105
200
|
return this.#pending;
|
|
106
201
|
}
|
|
107
|
-
/**
|
|
108
|
-
* True when the disconnect is definitely a real detach — the element left
|
|
109
|
-
* the document, or `data-controller` no longer lists the identifier. False
|
|
110
|
-
* means ambiguous (in-page move or observed-root exit), NOT "alive".
|
|
111
|
-
*/
|
|
112
202
|
static isDetached(host) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return !tokens.includes(host.identifier);
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Call from `disconnect()`: runs `teardown` synchronously on a definite
|
|
119
|
-
* detach (fast path), otherwise defers it one microtask — a reconnect
|
|
120
|
-
* ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.
|
|
121
|
-
* One microtask is the whole probe window: Stimulus reconnects a moved
|
|
122
|
-
* element within the same mutation batch, before the checkpoint drains.
|
|
123
|
-
*/
|
|
203
|
+
return !host.element.isConnected || !_DetachGate.#listed(host);
|
|
204
|
+
}
|
|
124
205
|
disconnected(host, teardown) {
|
|
125
|
-
|
|
206
|
+
const carried = _DetachGate.#carried(host);
|
|
207
|
+
if (!carried && _DetachGate.isDetached(host)) {
|
|
126
208
|
this.#pending = false;
|
|
127
209
|
teardown();
|
|
128
210
|
return;
|
|
129
211
|
}
|
|
130
212
|
this.#pending = true;
|
|
131
|
-
|
|
213
|
+
const probe = () => {
|
|
132
214
|
if (!this.#pending) return;
|
|
133
215
|
this.#pending = false;
|
|
134
216
|
teardown();
|
|
135
|
-
}
|
|
217
|
+
};
|
|
218
|
+
if (carried) setTimeout(probe, 0);
|
|
219
|
+
else queueMicrotask(probe);
|
|
220
|
+
}
|
|
221
|
+
static #carried(host) {
|
|
222
|
+
const element = host.element;
|
|
223
|
+
return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
|
|
224
|
+
}
|
|
225
|
+
static #listed(host) {
|
|
226
|
+
const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
|
|
227
|
+
return tokens.includes(host.identifier);
|
|
136
228
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Disarms a pending probe. Call from `connect()` (the reconnect that proves
|
|
139
|
-
* an in-page move) and from the head of any teardown path not routed through
|
|
140
|
-
* {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can
|
|
141
|
-
* never run the teardown a second time.
|
|
142
|
-
*/
|
|
143
229
|
cancel() {
|
|
144
230
|
this.#pending = false;
|
|
145
231
|
}
|
|
146
232
|
};
|
|
147
233
|
|
|
148
|
-
// src/utils/event_owner.ts
|
|
149
234
|
function ownerIndex(candidates, node) {
|
|
150
235
|
if (!(node instanceof Node)) return -1;
|
|
151
236
|
return candidates.findIndex((candidate) => candidate.contains(node));
|
|
152
237
|
}
|
|
153
238
|
|
|
154
|
-
// src/utils/listener_set.ts
|
|
155
239
|
var ListenerSet = class {
|
|
156
|
-
/** The generation every `add` joins until the next `dispose()`. */
|
|
157
240
|
#abort = new AbortController();
|
|
158
|
-
/**
|
|
159
|
-
* Attaches `handler` to the open generation, exactly as the caller spelled it.
|
|
160
|
-
*
|
|
161
|
-
* The set supplies the signal, so `options` carries everything else the DOM
|
|
162
|
-
* accepts — `capture` included, which has to match at release time and no
|
|
163
|
-
* longer has a second place to drift from.
|
|
164
|
-
*/
|
|
165
241
|
add(target, type, handler, options) {
|
|
166
242
|
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
167
243
|
}
|
|
168
|
-
/**
|
|
169
|
-
* Releases every listener of the open generation, synchronously, and opens the
|
|
170
|
-
* next one. Idempotent, and safe before anything has been added.
|
|
171
|
-
*/
|
|
172
244
|
dispose() {
|
|
173
245
|
this.#abort.abort();
|
|
174
246
|
this.#abort = new AbortController();
|
|
175
247
|
}
|
|
176
248
|
};
|
|
177
249
|
|
|
178
|
-
|
|
250
|
+
var HoverRecheck = class {
|
|
251
|
+
#document;
|
|
252
|
+
#release;
|
|
253
|
+
#listeners = new ListenerSet();
|
|
254
|
+
#pending = /* @__PURE__ */ new Set();
|
|
255
|
+
#tickets = /* @__PURE__ */ new WeakMap();
|
|
256
|
+
constructor(document2, release) {
|
|
257
|
+
this.#document = document2;
|
|
258
|
+
this.#release = release;
|
|
259
|
+
}
|
|
260
|
+
wait(element) {
|
|
261
|
+
this.#pending.add(element);
|
|
262
|
+
this.#tickets.set(element, /* @__PURE__ */ Symbol());
|
|
263
|
+
this.#listeners.add(this.#document, "pointermove", this.#onPointerMove, {
|
|
264
|
+
capture: true,
|
|
265
|
+
passive: true
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
cancel(element) {
|
|
269
|
+
this.#tickets.delete(element);
|
|
270
|
+
this.#pending.delete(element);
|
|
271
|
+
if (this.#pending.size === 0) this.#listeners.dispose();
|
|
272
|
+
}
|
|
273
|
+
dispose() {
|
|
274
|
+
this.#listeners.dispose();
|
|
275
|
+
this.#pending.clear();
|
|
276
|
+
this.#tickets = /* @__PURE__ */ new WeakMap();
|
|
277
|
+
}
|
|
278
|
+
#onPointerMove = () => {
|
|
279
|
+
this.#listeners.dispose();
|
|
280
|
+
const waiting = [...this.#pending].map(
|
|
281
|
+
(element) => [element, this.#tickets.get(element)]
|
|
282
|
+
);
|
|
283
|
+
this.#pending.clear();
|
|
284
|
+
for (const [element, ticket] of waiting) {
|
|
285
|
+
if (this.#tickets.get(element) !== ticket) continue;
|
|
286
|
+
if (element.matches(":hover")) continue;
|
|
287
|
+
this.#release(element);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
|
|
179
292
|
var MicrotaskCoalescer = class {
|
|
180
293
|
#run;
|
|
181
294
|
#queued = false;
|
|
182
295
|
#active = false;
|
|
183
296
|
#generation = 0;
|
|
184
|
-
/** @param run - the single reconciliation pass, invoked at most once per batch. */
|
|
185
297
|
constructor(run) {
|
|
186
298
|
this.#run = run;
|
|
187
299
|
}
|
|
188
|
-
/** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
|
|
189
300
|
activate() {
|
|
190
301
|
this.#active = true;
|
|
191
302
|
}
|
|
192
|
-
/** Closes the window and drops any pending pass; call from `disconnect()`. */
|
|
193
303
|
cancel() {
|
|
194
304
|
this.#active = false;
|
|
195
305
|
this.#queued = false;
|
|
196
306
|
this.#generation += 1;
|
|
197
307
|
}
|
|
198
|
-
|
|
308
|
+
get pending() {
|
|
309
|
+
return this.#queued;
|
|
310
|
+
}
|
|
199
311
|
schedule() {
|
|
200
312
|
if (!this.#active || this.#queued) return;
|
|
201
313
|
this.#queued = true;
|
|
@@ -208,36 +320,104 @@ var MicrotaskCoalescer = class {
|
|
|
208
320
|
}
|
|
209
321
|
};
|
|
210
322
|
|
|
211
|
-
|
|
323
|
+
var MorphRenderWatcher = class {
|
|
324
|
+
#listeners = new ListenerSet();
|
|
325
|
+
#pass;
|
|
326
|
+
#element = null;
|
|
327
|
+
#onMorph = () => this.#pass.schedule();
|
|
328
|
+
constructor(run) {
|
|
329
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
330
|
+
}
|
|
331
|
+
observe(element) {
|
|
332
|
+
if (this.#element === element) return;
|
|
333
|
+
this.disconnect();
|
|
334
|
+
this.#element = element;
|
|
335
|
+
this.#pass.activate();
|
|
336
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
337
|
+
}
|
|
338
|
+
schedule() {
|
|
339
|
+
this.#pass.schedule();
|
|
340
|
+
}
|
|
341
|
+
get pending() {
|
|
342
|
+
return this.#pass.pending;
|
|
343
|
+
}
|
|
344
|
+
disconnect() {
|
|
345
|
+
this.#listeners.dispose();
|
|
346
|
+
this.#pass.cancel();
|
|
347
|
+
this.#element = null;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
var MoveCounter = class {
|
|
352
|
+
#sequence = 0;
|
|
353
|
+
record() {
|
|
354
|
+
this.#sequence += 1;
|
|
355
|
+
return this.#sequence;
|
|
356
|
+
}
|
|
357
|
+
isLatest(token) {
|
|
358
|
+
return token === this.#sequence;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
363
|
+
|
|
364
|
+
var NUMBER_BOUNDS = {
|
|
365
|
+
positiveTimer: { finite: true, exclusiveMin: 0, max: MAX_TIMER_DELAY_MS }
|
|
366
|
+
};
|
|
367
|
+
function matchesNumberBounds(value, bounds) {
|
|
368
|
+
if (!Number.isFinite(value)) {
|
|
369
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
370
|
+
if (direction === null) return false;
|
|
371
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
372
|
+
}
|
|
373
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
374
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
375
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
376
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
377
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function readNumber(raw, fallback, bounds) {
|
|
382
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
var NumberValueReader = class {
|
|
386
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
387
|
+
read(owner, name, raw, fallback, bounds) {
|
|
388
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
389
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
390
|
+
this.#lastRejected.delete(name);
|
|
391
|
+
return resolved;
|
|
392
|
+
}
|
|
393
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
394
|
+
const literal = owner.element.getAttribute(attribute);
|
|
395
|
+
if (literal === null) {
|
|
396
|
+
this.#lastRejected.delete(name);
|
|
397
|
+
return resolved;
|
|
398
|
+
}
|
|
399
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
400
|
+
this.#lastRejected.set(name, literal);
|
|
401
|
+
console.warn(
|
|
402
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
return resolved;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
212
409
|
function prefersReducedMotion() {
|
|
213
410
|
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
214
411
|
}
|
|
215
412
|
|
|
216
|
-
// src/utils/roving_tabindex.ts
|
|
217
413
|
var RovingTabindex = class {
|
|
218
|
-
/** Returns the current ordered item elements; called on every operation. */
|
|
219
414
|
#getItems;
|
|
220
|
-
/**
|
|
221
|
-
* @param getItems - Returns the current ordered item elements. Called on every
|
|
222
|
-
* operation so the live target list is always used.
|
|
223
|
-
*/
|
|
224
415
|
constructor(getItems) {
|
|
225
416
|
this.#getItems = getItems;
|
|
226
417
|
}
|
|
227
|
-
/** Index of the currently tabbable item (`tabindex="0"`), or `-1` if none. */
|
|
228
418
|
get activeIndex() {
|
|
229
419
|
return this.#getItems().findIndex((item) => item.tabIndex === 0);
|
|
230
420
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Makes exactly the item at `index` tabbable (`tabindex="0"`) and removes every
|
|
233
|
-
* other item from the Tab sequence (`tabindex="-1"`). An out-of-range `index`
|
|
234
|
-
* (e.g. `-1`) leaves all items at `-1`, which a controller can use to express
|
|
235
|
-
* "nothing is currently tabbable".
|
|
236
|
-
*
|
|
237
|
-
* @param index - Position of the item to make tabbable.
|
|
238
|
-
* @param options - Pass `{ focus: true }` to also move DOM focus to that item,
|
|
239
|
-
* and `items` to reuse an event-scoped collection snapshot.
|
|
240
|
-
*/
|
|
241
421
|
setActive(index, options = {}) {
|
|
242
422
|
const { focus = false } = options;
|
|
243
423
|
const items = options.items ?? this.#getItems();
|
|
@@ -253,38 +433,24 @@ function rovingMove(current, length, delta, wrap) {
|
|
|
253
433
|
return (next + length) % length;
|
|
254
434
|
}
|
|
255
435
|
|
|
256
|
-
// src/utils/safe_timeout.ts
|
|
257
436
|
var TimerRegistry = class {
|
|
258
|
-
/** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
|
|
259
437
|
ids = /* @__PURE__ */ new Set();
|
|
260
|
-
/**
|
|
261
|
-
* Cancels a single tracked timer.
|
|
262
|
-
*
|
|
263
|
-
* No-ops if the id is unknown (already cleared, fired, or never owned by this
|
|
264
|
-
* registry), so callers can clear defensively without guarding.
|
|
265
|
-
*/
|
|
266
438
|
clear(id) {
|
|
267
439
|
if (this.ids.delete(id)) {
|
|
268
440
|
this.cancel(id);
|
|
269
441
|
}
|
|
270
442
|
}
|
|
271
|
-
/**
|
|
272
|
-
* Cancels every tracked timer. Call this from a controller's `disconnect()`
|
|
273
|
-
* to guarantee no timer outlives the element.
|
|
274
|
-
*/
|
|
275
443
|
clearAll() {
|
|
276
444
|
for (const id of this.ids) {
|
|
277
445
|
this.cancel(id);
|
|
278
446
|
}
|
|
279
447
|
this.ids.clear();
|
|
280
448
|
}
|
|
281
|
-
/** Number of timers currently tracked (pending). */
|
|
282
449
|
get size() {
|
|
283
450
|
return this.ids.size;
|
|
284
451
|
}
|
|
285
452
|
};
|
|
286
453
|
var SafeInterval = class extends TimerRegistry {
|
|
287
|
-
/** Schedules a repeating `callback` every `delay` ms and returns the timer id. */
|
|
288
454
|
set(callback, delay) {
|
|
289
455
|
const id = this.schedule(callback, delay);
|
|
290
456
|
this.ids.add(id);
|
|
@@ -298,16 +464,20 @@ var SafeInterval = class extends TimerRegistry {
|
|
|
298
464
|
}
|
|
299
465
|
};
|
|
300
466
|
|
|
301
|
-
|
|
302
|
-
|
|
467
|
+
var StateRegions = class _StateRegions {
|
|
468
|
+
static #registry = sharedRegistry(
|
|
469
|
+
"stimeo-ui.state-regions.registry.v1",
|
|
470
|
+
() => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
|
|
471
|
+
);
|
|
303
472
|
#whenTrue;
|
|
304
473
|
#whenFalse;
|
|
305
|
-
#
|
|
306
|
-
constructor(options) {
|
|
474
|
+
#record;
|
|
475
|
+
constructor(options, owner) {
|
|
307
476
|
this.#whenTrue = options.whenTrue;
|
|
308
477
|
this.#whenFalse = options.whenFalse ?? null;
|
|
478
|
+
this.#record = `data-${owner}-hidden-region`;
|
|
479
|
+
_StateRegions.#registry.names.add(this.#record);
|
|
309
480
|
}
|
|
310
|
-
/** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
|
|
311
481
|
reflect(host, isTrue) {
|
|
312
482
|
const shown = this.#inside(host, this.#whenTrue());
|
|
313
483
|
if (!this.#whenFalse) {
|
|
@@ -323,32 +493,135 @@ var StateRegions = class {
|
|
|
323
493
|
for (const region of shown) this.#write(region, !isTrue);
|
|
324
494
|
for (const region of hiddenSide) this.#write(region, isTrue);
|
|
325
495
|
}
|
|
326
|
-
|
|
496
|
+
release(host) {
|
|
497
|
+
for (const region of [host, ...host.querySelectorAll("*")]) {
|
|
498
|
+
if (!(region instanceof HTMLElement)) continue;
|
|
499
|
+
const stack = this.#held(region);
|
|
500
|
+
const hold = stack && this.#take(stack);
|
|
501
|
+
if (!stack || !hold) continue;
|
|
502
|
+
const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);
|
|
503
|
+
const index = stack.indexOf(hold);
|
|
504
|
+
stack.splice(index, 1);
|
|
505
|
+
const above = stack[index];
|
|
506
|
+
if (above) above.linked &&= hold.linked;
|
|
507
|
+
if (owns) {
|
|
508
|
+
const below = hold.linked ? stack[index - 1] : void 0;
|
|
509
|
+
if (below) {
|
|
510
|
+
if (region.hidden !== below.written) region.hidden = below.written;
|
|
511
|
+
} else {
|
|
512
|
+
setAttributeIfChanged(region, "hidden", hold.authored);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
this.#mark(region, stack);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
327
518
|
#write(region, hidden) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
519
|
+
const stack = this.#stack(region);
|
|
520
|
+
const hold = this.#take(stack) ?? this.#acquire(region, stack);
|
|
521
|
+
const shows = !this.#covered(region, stack, hold);
|
|
522
|
+
hold.written = hidden;
|
|
523
|
+
if (shows && region.hidden !== hidden) region.hidden = hidden;
|
|
524
|
+
this.#mark(region, stack);
|
|
331
525
|
}
|
|
332
|
-
/** Returns a region this instance hid; one it never hid keeps what it carries. */
|
|
333
526
|
#give(region) {
|
|
334
|
-
|
|
527
|
+
const stack = this.#held(region);
|
|
528
|
+
if (stack && this.#take(stack)?.written) this.#write(region, false);
|
|
529
|
+
}
|
|
530
|
+
#held(region) {
|
|
531
|
+
return _StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record) ? this.#stack(region) : void 0;
|
|
532
|
+
}
|
|
533
|
+
#stack(region) {
|
|
534
|
+
const stacks = _StateRegions.#registry.stacks;
|
|
535
|
+
const stack = stacks.get(region) ?? this.#seed(region);
|
|
536
|
+
stacks.set(region, stack);
|
|
537
|
+
return stack;
|
|
538
|
+
}
|
|
539
|
+
#seed(region) {
|
|
540
|
+
const found = [];
|
|
541
|
+
for (const record of _StateRegions.#registry.names) {
|
|
542
|
+
const read = this.#recorded(region, record);
|
|
543
|
+
if (read) found.push({ ...read, record });
|
|
544
|
+
}
|
|
545
|
+
const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
|
|
546
|
+
return found.sort((one, other) => depth(one) - depth(other)).map(({ record, authored, written, linked }, index) => ({
|
|
547
|
+
instance: null,
|
|
548
|
+
record,
|
|
549
|
+
authored,
|
|
550
|
+
written,
|
|
551
|
+
linked: linked && index > 0
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
#take(stack) {
|
|
555
|
+
const hold = stack.find((held) => held.instance === this) ?? stack.find((held) => held.instance === null && held.record === this.#record);
|
|
556
|
+
if (hold) hold.instance = this;
|
|
557
|
+
return hold;
|
|
558
|
+
}
|
|
559
|
+
#covered(region, stack, hold) {
|
|
560
|
+
return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);
|
|
561
|
+
}
|
|
562
|
+
#acquire(region, stack) {
|
|
563
|
+
const top = stack.at(-1);
|
|
564
|
+
const over = top && region.hidden === top.written ? top : void 0;
|
|
565
|
+
const hold = {
|
|
566
|
+
instance: this,
|
|
567
|
+
record: this.#record,
|
|
568
|
+
authored: over ? over.authored : region.getAttribute("hidden"),
|
|
569
|
+
written: false,
|
|
570
|
+
linked: over !== void 0
|
|
571
|
+
};
|
|
572
|
+
stack.push(hold);
|
|
573
|
+
return hold;
|
|
574
|
+
}
|
|
575
|
+
#mark(region, stack) {
|
|
576
|
+
const records = /* @__PURE__ */ new Map([[this.#record, null]]);
|
|
577
|
+
stack.forEach((hold, index) => {
|
|
578
|
+
const names = stack.slice(0, index).map((under) => under.record);
|
|
579
|
+
const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
|
|
580
|
+
const marker = hold.linked || beneath.length === 0 ? [] : [false];
|
|
581
|
+
records.set(
|
|
582
|
+
hold.record,
|
|
583
|
+
region.getAttribute("hidden") === hold.authored && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath])
|
|
584
|
+
);
|
|
585
|
+
});
|
|
586
|
+
for (const [name, recorded] of records) {
|
|
587
|
+
setAttributeIfChanged(region, name, recorded);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
#recorded(region, name) {
|
|
591
|
+
try {
|
|
592
|
+
const parsed = JSON.parse(region.getAttribute(name) ?? "null");
|
|
593
|
+
if (!Array.isArray(parsed)) return null;
|
|
594
|
+
const [authored, written, ...rest] = parsed;
|
|
595
|
+
if (authored !== null && typeof authored !== "string") return null;
|
|
596
|
+
const linked = rest[0] !== false;
|
|
597
|
+
const beneath = linked ? rest : rest.slice(1);
|
|
598
|
+
if (!beneath.every((under) => typeof under === "string")) return null;
|
|
599
|
+
return typeof written === "boolean" ? { authored, written, linked, beneath } : null;
|
|
600
|
+
} catch {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
335
603
|
}
|
|
336
|
-
/** The declared regions that sit within `host`, which may be the host itself. */
|
|
337
604
|
#inside(host, regions) {
|
|
338
605
|
return regions.filter((region) => host.contains(region));
|
|
339
606
|
}
|
|
340
607
|
};
|
|
341
608
|
|
|
342
|
-
|
|
609
|
+
function targetSelector(identifier, name) {
|
|
610
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
611
|
+
}
|
|
612
|
+
|
|
343
613
|
var DEFAULT_INTERVAL = 5e3;
|
|
344
614
|
var OBSERVED_ATTRIBUTES = ["aria-selected", "data-state", "hidden"];
|
|
345
|
-
var CarouselController = class extends Controller {
|
|
615
|
+
var CarouselController = class _CarouselController extends Controller {
|
|
616
|
+
#numbers = new NumberValueReader();
|
|
617
|
+
#moves = new MoveCounter();
|
|
346
618
|
static targets = [
|
|
347
619
|
"slide",
|
|
348
620
|
"viewport",
|
|
349
621
|
"prev",
|
|
350
622
|
"next",
|
|
351
623
|
"picker",
|
|
624
|
+
"pickerList",
|
|
352
625
|
"playToggle",
|
|
353
626
|
"onLabel",
|
|
354
627
|
"offLabel"
|
|
@@ -358,6 +631,9 @@ var CarouselController = class extends Controller {
|
|
|
358
631
|
interval: { type: Number, default: DEFAULT_INTERVAL },
|
|
359
632
|
loop: { type: Boolean, default: true }
|
|
360
633
|
};
|
|
634
|
+
static valueConstraints = {
|
|
635
|
+
interval: NUMBER_BOUNDS.positiveTimer
|
|
636
|
+
};
|
|
361
637
|
static actions = [
|
|
362
638
|
"goto",
|
|
363
639
|
"next",
|
|
@@ -369,84 +645,46 @@ var CarouselController = class extends Controller {
|
|
|
369
645
|
];
|
|
370
646
|
static events = ["change", "pause", "play", "reconcile"];
|
|
371
647
|
#roving = new RovingTabindex(() => this.pickerTargets);
|
|
372
|
-
#reconcileTargets = new
|
|
648
|
+
#reconcileTargets = new MorphRenderWatcher(() => this.#reconcileTargetSet());
|
|
373
649
|
#intervals = new SafeInterval();
|
|
374
|
-
|
|
375
|
-
#
|
|
376
|
-
|
|
377
|
-
#
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
#beforeCache = new BeforeCacheReset(() => this.#returnLeases());
|
|
386
|
-
/** Tells an in-page move, which keeps the rotation, from a real detach. */
|
|
650
|
+
#ariaDisabled = new AttributeLease("aria-disabled", this.identifier);
|
|
651
|
+
#ariaPressed = new AttributeLease("aria-pressed", this.identifier);
|
|
652
|
+
#ariaLive = new AttributeLease("aria-live", this.identifier);
|
|
653
|
+
#ariaAtomic = new AttributeLease("aria-atomic", this.identifier);
|
|
654
|
+
#labels = new StateRegions(
|
|
655
|
+
{
|
|
656
|
+
whenTrue: () => this.onLabelTargets,
|
|
657
|
+
whenFalse: () => this.offLabelTargets
|
|
658
|
+
},
|
|
659
|
+
this.identifier
|
|
660
|
+
);
|
|
387
661
|
#gate = new DetachGate();
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
* later — it sits on the controller element, above every control — so it can
|
|
393
|
-
* consume the mark and stand down, letting the two wirings coexist without
|
|
394
|
-
* handling one interaction twice.
|
|
395
|
-
*/
|
|
662
|
+
#pointer = new HoverRecheck(this.element.ownerDocument, () => {
|
|
663
|
+
this.#pointerPaused = false;
|
|
664
|
+
this.#syncTimer();
|
|
665
|
+
});
|
|
396
666
|
#handledEvents = /* @__PURE__ */ new WeakSet();
|
|
397
|
-
/**
|
|
398
|
-
* Whether `connect()` has run. Scheduling is already inert outside that window
|
|
399
|
-
* (`MicrotaskCoalescer`), so this gates the Value callbacks Stimulus
|
|
400
|
-
* delivers ahead of `connect()`.
|
|
401
|
-
*/
|
|
402
667
|
#connected = false;
|
|
403
|
-
/** Index of the visible slide. */
|
|
404
668
|
#index = 0;
|
|
405
|
-
/** The visible slide element, which identifies it across a changing target set. */
|
|
406
669
|
#activeSlide = null;
|
|
407
|
-
/** Slide count at the last resolved state, so a changed total is reportable. */
|
|
408
670
|
#total = 0;
|
|
409
|
-
/**
|
|
410
|
-
* Pointer rests on the carousel: a suspension that lifts on `mouseleave`, or on the
|
|
411
|
-
* pointer movement that confirms a suspension a move or a connect left unconfirmed.
|
|
412
|
-
*/
|
|
413
671
|
#pointerPaused = false;
|
|
414
|
-
/** Focus is inside the carousel: a suspension that lifts when it leaves. */
|
|
415
672
|
#focusPaused = false;
|
|
416
|
-
/** The tab is in the background: a suspension that lifts when it returns. */
|
|
417
673
|
#hiddenPaused = false;
|
|
418
|
-
/** Reduced motion holds back the intent declared at connect, until a press or a new one. */
|
|
419
674
|
#motionHold = false;
|
|
420
|
-
/** Id of the live autoplay interval, or null when stopped. */
|
|
421
675
|
#timerId = null;
|
|
422
|
-
/** Delay the live interval was armed with, so an `interval` change re-arms it. */
|
|
423
676
|
#timerInterval = 0;
|
|
424
|
-
/** Follows state-attribute rewrites on retained slides and pickers. */
|
|
425
677
|
#observer = null;
|
|
426
|
-
/** Keeps a hidden tab from advancing behind the user's back. */
|
|
427
678
|
#onVisibilityChange = () => {
|
|
428
679
|
this.#hiddenPaused = document.visibilityState === "hidden";
|
|
429
680
|
this.#syncTimer();
|
|
430
681
|
};
|
|
431
|
-
|
|
432
|
-
* Renders the initial slide, wires the delegated listeners, and starts autoplay
|
|
433
|
-
* when requested.
|
|
434
|
-
*
|
|
435
|
-
* Focus, the tab's visibility and the reduced-motion hold are read afresh. The
|
|
436
|
-
* pointer is not something the document can answer yet: no `mouseleave` reaches a
|
|
437
|
-
* node that moved, and `:hover` can answer from before the move until the pointer
|
|
438
|
-
* moves again. So an in-page move — which Stimulus delivers to the *same* controller
|
|
439
|
-
* instance as `disconnect()` then `connect()` — keeps the pointer suspension it had,
|
|
440
|
-
* `:hover` reading true adds one, and either waits for the next pointer movement to
|
|
441
|
-
* be confirmed or lifted. A rotation the move carried is still running, so the edges
|
|
442
|
-
* are judged against it. The attribute observer starts after the first render so the
|
|
443
|
-
* controller's own opening writes are not fed back to it.
|
|
444
|
-
*/
|
|
682
|
+
#listeners = new ListenerSet();
|
|
445
683
|
connect() {
|
|
446
684
|
const moved = this.#gate.pending;
|
|
447
685
|
this.#gate.cancel();
|
|
448
686
|
this.#pointerPaused = moved && this.#pointerPaused || this.element.matches(":hover");
|
|
449
|
-
if (this.#pointerPaused) this.#
|
|
687
|
+
if (this.#pointerPaused) this.#pointer.wait(this.element);
|
|
450
688
|
this.#focusPaused = this.element.contains(document.activeElement);
|
|
451
689
|
this.#hiddenPaused = document.visibilityState === "hidden";
|
|
452
690
|
this.#motionHold = prefersReducedMotion();
|
|
@@ -455,15 +693,14 @@ var CarouselController = class extends Controller {
|
|
|
455
693
|
this.#total = this.slideTargets.length;
|
|
456
694
|
this.#syncTimer();
|
|
457
695
|
this.#connected = true;
|
|
458
|
-
this.#reconcileTargets.
|
|
459
|
-
this.#
|
|
460
|
-
this.element
|
|
461
|
-
this.element
|
|
462
|
-
this.element
|
|
463
|
-
this.element
|
|
464
|
-
this.element
|
|
465
|
-
this.
|
|
466
|
-
document.addEventListener("visibilitychange", this.#onVisibilityChange);
|
|
696
|
+
this.#reconcileTargets.observe(this.element);
|
|
697
|
+
this.#listeners.add(this.element, "click", this.#onClick);
|
|
698
|
+
this.#listeners.add(this.element, "keydown", this.#onKeydown);
|
|
699
|
+
this.#listeners.add(this.element, "focusin", this.#onFocusin);
|
|
700
|
+
this.#listeners.add(this.element, "focusout", this.#onFocusout);
|
|
701
|
+
this.#listeners.add(this.element, "mouseenter", this.#onMouseenter);
|
|
702
|
+
this.#listeners.add(this.element, "mouseleave", this.#onMouseleave);
|
|
703
|
+
this.#listeners.add(document, "visibilitychange", this.#onVisibilityChange);
|
|
467
704
|
this.#observer = new MutationObserver(this.#onAttributeMutations);
|
|
468
705
|
this.#observer.observe(this.element, {
|
|
469
706
|
subtree: true,
|
|
@@ -471,147 +708,105 @@ var CarouselController = class extends Controller {
|
|
|
471
708
|
attributeFilter: OBSERVED_ATTRIBUTES
|
|
472
709
|
});
|
|
473
710
|
}
|
|
474
|
-
/**
|
|
475
|
-
* Releases every listener and observer, returns the leased ARIA, drops the focus
|
|
476
|
-
* and tab suspensions. The rotation goes through the detach gate: a reconnect in
|
|
477
|
-
* the same batch — an in-page move — keeps it, and the pointer suspension with it;
|
|
478
|
-
* anything else ends it.
|
|
479
|
-
*/
|
|
480
711
|
disconnect() {
|
|
481
712
|
this.#connected = false;
|
|
482
713
|
this.#pointer.dispose();
|
|
483
|
-
this.
|
|
484
|
-
this.element.removeEventListener("keydown", this.#onKeydown);
|
|
485
|
-
this.element.removeEventListener("focusin", this.#onFocusin);
|
|
486
|
-
this.element.removeEventListener("focusout", this.#onFocusout);
|
|
487
|
-
this.element.removeEventListener("mouseenter", this.#onMouseenter);
|
|
488
|
-
this.element.removeEventListener("mouseleave", this.#onMouseleave);
|
|
489
|
-
document.removeEventListener("visibilitychange", this.#onVisibilityChange);
|
|
714
|
+
this.#listeners.dispose();
|
|
490
715
|
this.#observer?.disconnect();
|
|
491
716
|
this.#observer = null;
|
|
492
|
-
this.#reconcileTargets.
|
|
717
|
+
this.#reconcileTargets.disconnect();
|
|
493
718
|
this.#focusPaused = false;
|
|
494
719
|
this.#hiddenPaused = false;
|
|
495
720
|
this.#activeSlide = null;
|
|
496
721
|
this.#returnLeases();
|
|
497
|
-
this.#beforeCache.deactivate();
|
|
498
722
|
this.#gate.disconnected(this, () => this.#endRun());
|
|
499
723
|
}
|
|
500
|
-
/**
|
|
501
|
-
* Re-establishes the single selected picker when one is added after connect.
|
|
502
|
-
*
|
|
503
|
-
* Without this an appended picker that arrives `aria-selected="true"` leaves two
|
|
504
|
-
* marked at once — the authored pre-selection is only read on connect, so
|
|
505
|
-
* nothing else ever resolves the conflict. The current slide is kept: the
|
|
506
|
-
* repaint re-derives every picker from `#index`, so a late arrival never steals
|
|
507
|
-
* the selection.
|
|
508
|
-
*/
|
|
509
724
|
pickerTargetConnected(picker) {
|
|
510
725
|
picker.tabIndex = -1;
|
|
511
726
|
this.#reconcileTargets.schedule();
|
|
512
727
|
}
|
|
513
|
-
/** Repairs selection and roving after a picker leaves a retained carousel. */
|
|
514
728
|
pickerTargetDisconnected() {
|
|
515
729
|
this.#reconcileTargets.schedule();
|
|
516
730
|
}
|
|
517
|
-
/** Reconciles a slide added in the same DOM batch as its picker. */
|
|
518
731
|
slideTargetConnected() {
|
|
519
732
|
this.#reconcileTargets.schedule();
|
|
520
733
|
}
|
|
521
|
-
/** Re-clamps the active index after a slide is removed. */
|
|
522
734
|
slideTargetDisconnected() {
|
|
523
735
|
this.#reconcileTargets.schedule();
|
|
524
736
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
737
|
+
viewportTargetConnected() {
|
|
738
|
+
this.#reconcileTargets.schedule();
|
|
739
|
+
}
|
|
740
|
+
viewportTargetDisconnected(viewport) {
|
|
741
|
+
if (!this.viewportTargets.includes(viewport)) {
|
|
742
|
+
this.#ariaLive.return(viewport);
|
|
743
|
+
this.#ariaAtomic.return(viewport);
|
|
744
|
+
}
|
|
745
|
+
this.#reconcileTargets.schedule();
|
|
746
|
+
}
|
|
747
|
+
playToggleTargetDisconnected(toggle) {
|
|
748
|
+
if (this.playToggleTargets.includes(toggle)) return;
|
|
749
|
+
this.#labels.release(toggle);
|
|
750
|
+
this.#ariaDisabled.return(toggle);
|
|
751
|
+
this.#ariaPressed.return(toggle);
|
|
752
|
+
}
|
|
753
|
+
prevTargetDisconnected(button) {
|
|
754
|
+
if (!this.prevTargets.includes(button)) this.#ariaDisabled.return(button);
|
|
755
|
+
}
|
|
756
|
+
nextTargetDisconnected(button) {
|
|
757
|
+
if (!this.nextTargets.includes(button)) this.#ariaDisabled.return(button);
|
|
758
|
+
}
|
|
532
759
|
playToggleTargetConnected() {
|
|
533
760
|
if (this.#connected) this.#syncTimer();
|
|
534
761
|
}
|
|
535
|
-
/**
|
|
536
|
-
* Publishes the rotation state on a label half that joins a connected toggle.
|
|
537
|
-
*
|
|
538
|
-
* A half arriving changes the toggle's subtree, not its attributes, so the
|
|
539
|
-
* attribute observer never sees it; where the arrival completes a pair that was
|
|
540
|
-
* standing alone, this is what makes that pair whole and reflectable.
|
|
541
|
-
*/
|
|
542
762
|
onLabelTargetConnected() {
|
|
543
763
|
if (this.#connected) this.#syncTimer();
|
|
544
764
|
}
|
|
545
|
-
/** Publishes the rotation state on a label half that joins a connected toggle. */
|
|
546
765
|
offLabelTargetConnected() {
|
|
547
766
|
if (this.#connected) this.#syncTimer();
|
|
548
767
|
}
|
|
549
|
-
/** Follows a rotation intent the page changed at runtime, lifting the reduced-motion hold. */
|
|
550
768
|
autoplayValueChanged() {
|
|
551
769
|
if (!this.#connected) return;
|
|
552
770
|
this.#motionHold = false;
|
|
553
771
|
this.#syncTimer();
|
|
554
772
|
}
|
|
555
|
-
/** Re-arms the live interval at the new delay without reporting a state change. */
|
|
556
773
|
intervalValueChanged() {
|
|
557
774
|
if (this.#connected) this.#syncTimer();
|
|
558
775
|
}
|
|
559
|
-
/**
|
|
560
|
-
* Re-publishes the step controls and re-evaluates the non-looping end through the
|
|
561
|
-
* reconcile pass, which reports a move only when the position changed.
|
|
562
|
-
*/
|
|
563
776
|
loopValueChanged() {
|
|
564
777
|
this.#reconcileTargets.schedule();
|
|
565
778
|
}
|
|
566
|
-
/** Advances to the next slide. Delegated; `data-action` wiring is optional. */
|
|
567
779
|
next(event) {
|
|
568
780
|
if (event?.defaultPrevented) return;
|
|
569
781
|
this.#markHandled(event);
|
|
570
|
-
this.#select(this.#step(1), { focus: false });
|
|
782
|
+
this.#select(this.#step(1), { focus: false }, stateReasonFor(event));
|
|
571
783
|
}
|
|
572
|
-
/** Returns to the previous slide. Delegated; `data-action` wiring is optional. */
|
|
573
784
|
prev(event) {
|
|
574
785
|
if (event?.defaultPrevented) return;
|
|
575
786
|
this.#markHandled(event);
|
|
576
|
-
this.#select(this.#step(-1), { focus: false });
|
|
787
|
+
this.#select(this.#step(-1), { focus: false }, stateReasonFor(event));
|
|
788
|
+
}
|
|
789
|
+
goto(source) {
|
|
790
|
+
const { event, host, origin, reason } = actionSource(source);
|
|
791
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
792
|
+
return;
|
|
793
|
+
if (event?.defaultPrevented) return;
|
|
794
|
+
const picker = host?.closest(targetSelector(this.identifier, "picker"));
|
|
795
|
+
if (!picker || !this.pickerTargets.includes(picker)) return;
|
|
796
|
+
this.#markHandled(event ?? void 0);
|
|
797
|
+
this.#select(this.pickerTargets.indexOf(picker), { focus: false }, reason);
|
|
577
798
|
}
|
|
578
|
-
/** Jumps to the slide whose picker was activated (click / Enter / Space). */
|
|
579
|
-
goto(event) {
|
|
580
|
-
if (event.defaultPrevented) return;
|
|
581
|
-
this.#markHandled(event);
|
|
582
|
-
const index = this.#pickerIndexFor(event.currentTarget);
|
|
583
|
-
if (index !== -1) this.#select(index, { focus: false });
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Flips the rotation intent the toggle shows, on the user's explicit request.
|
|
587
|
-
*
|
|
588
|
-
* The result is written to the `autoplay` Value, which is where every other path
|
|
589
|
-
* reads it from. A carousel with nothing to advance to has no intent to flip: the
|
|
590
|
-
* toggle is marked `aria-disabled` and does nothing.
|
|
591
|
-
*/
|
|
592
799
|
togglePlay(event) {
|
|
593
800
|
if (event?.defaultPrevented) return;
|
|
594
801
|
this.#markHandled(event);
|
|
595
802
|
this.#togglePlay();
|
|
596
803
|
}
|
|
597
|
-
/**
|
|
598
|
-
* Suspends rotation. A `focus`-family event records that focus is inside the
|
|
599
|
-
* carousel; anything else — hover, or a bare programmatic call — records the
|
|
600
|
-
* pointer suspension. Both lift through the matching {@link resume}, and
|
|
601
|
-
* neither touches the rotation intent.
|
|
602
|
-
*/
|
|
603
804
|
pause(event) {
|
|
604
805
|
this.#markHandled(event);
|
|
605
806
|
if (isFocusEvent(event)) this.#focusPaused = true;
|
|
606
807
|
else this.#setPointerPaused(true);
|
|
607
808
|
this.#syncTimer();
|
|
608
809
|
}
|
|
609
|
-
/**
|
|
610
|
-
* Lifts the matching suspension. A `focusout` whose `relatedTarget` is still
|
|
611
|
-
* inside the carousel is focus moving between its own controls, which leaves the
|
|
612
|
-
* focus suspension in place — releasing it there would stop and restart the
|
|
613
|
-
* interval on every Tab press.
|
|
614
|
-
*/
|
|
615
810
|
resume(event) {
|
|
616
811
|
this.#markHandled(event);
|
|
617
812
|
if (isFocusEvent(event)) {
|
|
@@ -622,14 +817,12 @@ var CarouselController = class extends Controller {
|
|
|
622
817
|
}
|
|
623
818
|
this.#syncTimer();
|
|
624
819
|
}
|
|
625
|
-
/** Picker roving for authored bindings; the delegated path is `#onKeydown`. */
|
|
626
820
|
onPickerKeydown(event) {
|
|
627
821
|
const index = this.#pickerIndexFor(event.currentTarget);
|
|
628
822
|
if (index === -1) return;
|
|
629
823
|
this.#markHandled(event);
|
|
630
824
|
this.#handlePickerKeydown(event, index);
|
|
631
825
|
}
|
|
632
|
-
/** Delegated activation for pickers and step controls without authored actions. */
|
|
633
826
|
#onClick = (event) => this.#delegate(event, () => {
|
|
634
827
|
const index = this.#pickerIndexFor(event.target);
|
|
635
828
|
if (index !== -1) {
|
|
@@ -642,91 +835,48 @@ var CarouselController = class extends Controller {
|
|
|
642
835
|
this.#togglePlay();
|
|
643
836
|
}
|
|
644
837
|
});
|
|
645
|
-
/** Delegated picker roving for pickers without authored actions. */
|
|
646
838
|
#onKeydown = (event) => this.#delegate(event, () => {
|
|
647
839
|
const index = this.#pickerIndexFor(event.target);
|
|
648
840
|
if (index !== -1) this.#handlePickerKeydown(event, index);
|
|
649
841
|
});
|
|
650
|
-
/** Focus arriving anywhere inside suspends the rotation. */
|
|
651
842
|
#onFocusin = (event) => this.#delegate(event, () => {
|
|
652
843
|
this.#focusPaused = true;
|
|
653
844
|
this.#syncTimer();
|
|
654
845
|
});
|
|
655
|
-
/** Focus genuinely leaving lifts the suspension; moves between own controls do not. */
|
|
656
846
|
#onFocusout = (event) => this.#delegate(event, () => {
|
|
657
847
|
if (this.#focusStaysInside(event)) return;
|
|
658
848
|
this.#focusPaused = false;
|
|
659
849
|
this.#syncTimer();
|
|
660
850
|
});
|
|
661
|
-
/** Pointer entry suspends the rotation. */
|
|
662
851
|
#onMouseenter = (event) => this.#delegate(event, () => {
|
|
663
852
|
this.#setPointerPaused(true);
|
|
664
853
|
this.#syncTimer();
|
|
665
854
|
});
|
|
666
|
-
/** Pointer exit lifts the suspension. */
|
|
667
855
|
#onMouseleave = (event) => this.#delegate(event, () => {
|
|
668
856
|
this.#setPointerPaused(false);
|
|
669
857
|
this.#syncTimer();
|
|
670
858
|
});
|
|
671
|
-
/**
|
|
672
|
-
* Reads `:hover` once the pointer has moved, when the answer is current, and lifts
|
|
673
|
-
* the pointer suspension if the pointer is not over the carousel. Listens once.
|
|
674
|
-
*/
|
|
675
|
-
#onPointerMove = () => {
|
|
676
|
-
this.#pointer.dispose();
|
|
677
|
-
if (this.element.matches(":hover")) return;
|
|
678
|
-
this.#pointerPaused = false;
|
|
679
|
-
this.#syncTimer();
|
|
680
|
-
};
|
|
681
|
-
/** Leaves the pointer suspension to be confirmed or lifted on the next pointer movement. */
|
|
682
|
-
#awaitPointer() {
|
|
683
|
-
this.#pointer.add(document, "pointermove", this.#onPointerMove, {
|
|
684
|
-
capture: true,
|
|
685
|
-
passive: true
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
/** Records the pointer suspension; a newer answer ends any wait on the next movement. */
|
|
689
859
|
#setPointerPaused(paused) {
|
|
690
860
|
this.#pointer.dispose();
|
|
691
861
|
this.#pointerPaused = paused;
|
|
692
862
|
}
|
|
693
|
-
/**
|
|
694
|
-
* Schedules one reconciliation when a state attribute is rewritten in place —
|
|
695
|
-
* the only shape of change no target callback reports.
|
|
696
|
-
*
|
|
697
|
-
* The filter is the `attributeFilter` alone: those three attributes belong to
|
|
698
|
-
* the slides and pickers, and a coalesced pass over an unrelated one costs a
|
|
699
|
-
* repaint that writes nothing. Narrowing further here would add a branch no
|
|
700
|
-
* test could distinguish.
|
|
701
|
-
*/
|
|
702
863
|
#onAttributeMutations = () => {
|
|
703
864
|
this.#reconcileTargets.schedule();
|
|
704
865
|
};
|
|
705
|
-
/**
|
|
706
|
-
* Records that an action binding took this event.
|
|
707
|
-
*
|
|
708
|
-
* An authored binding sits on the control, so it runs while the event is still
|
|
709
|
-
* below the controller element and always precedes the delegated listener.
|
|
710
|
-
* Marking is the one signal that keeps the two wirings from both acting.
|
|
711
|
-
*/
|
|
712
866
|
#markHandled(event) {
|
|
713
867
|
if (event) this.#handledEvents.add(event);
|
|
714
868
|
}
|
|
715
|
-
/** Runs a delegated handler unless an action binding, or a descendant, took the event. */
|
|
716
869
|
#delegate(event, run) {
|
|
717
870
|
if (this.#handledEvents.delete(event) || event.defaultPrevented) return;
|
|
718
871
|
run();
|
|
719
872
|
}
|
|
720
|
-
/** Position of the picker that is or contains `node`, or `-1` when none does. */
|
|
721
873
|
#pickerIndexFor(node) {
|
|
722
874
|
return ownerIndex(this.pickerTargets, node);
|
|
723
875
|
}
|
|
724
|
-
/** Whether a focus transition lands on another control of this same carousel. */
|
|
725
876
|
#focusStaysInside(event) {
|
|
726
877
|
const next = event.relatedTarget;
|
|
727
878
|
return next instanceof Node && this.element.contains(next);
|
|
728
879
|
}
|
|
729
|
-
/** Flips the rotation intent unless there is nothing to rotate to; lifts the motion hold. */
|
|
730
880
|
#togglePlay() {
|
|
731
881
|
if (!this.#canAutoplay()) return;
|
|
732
882
|
const next = !this.#intent;
|
|
@@ -734,7 +884,6 @@ var CarouselController = class extends Controller {
|
|
|
734
884
|
this.autoplayValue = next;
|
|
735
885
|
this.#syncTimer();
|
|
736
886
|
}
|
|
737
|
-
/** Applies the APG picker key map: arrows, Home, and End all move focus only. */
|
|
738
887
|
#handlePickerKeydown(event, current) {
|
|
739
888
|
if (event.defaultPrevented) return;
|
|
740
889
|
if (isReservedArrowChord(event)) return;
|
|
@@ -745,72 +894,49 @@ var CarouselController = class extends Controller {
|
|
|
745
894
|
case "ArrowLeft":
|
|
746
895
|
case "ArrowUp": {
|
|
747
896
|
event.preventDefault();
|
|
748
|
-
const step = logicalArrowStep(
|
|
897
|
+
const step = logicalArrowStep(
|
|
898
|
+
event.key,
|
|
899
|
+
this.hasPickerListTarget ? this.pickerListTarget : this.element
|
|
900
|
+
);
|
|
749
901
|
this.#roving.setActive(rovingMove(current, length, step), { focus: true });
|
|
750
902
|
return;
|
|
751
903
|
}
|
|
752
|
-
// A chorded Home/End is a document-level shortcut (`Control+Home` scrolls
|
|
753
|
-
// the page) that belongs to the browser, exactly as a chorded arrow does.
|
|
754
904
|
case "Home":
|
|
755
|
-
if (
|
|
905
|
+
if (hasModifierChord(event)) return;
|
|
756
906
|
event.preventDefault();
|
|
757
907
|
this.#roving.setActive(0, { focus: true });
|
|
758
908
|
return;
|
|
759
909
|
case "End":
|
|
760
|
-
if (
|
|
910
|
+
if (hasModifierChord(event)) return;
|
|
761
911
|
event.preventDefault();
|
|
762
912
|
this.#roving.setActive(length - 1, { focus: true });
|
|
763
913
|
return;
|
|
764
914
|
}
|
|
765
915
|
}
|
|
766
|
-
/**
|
|
767
|
-
* Resolves the index one step away from the current one, honoring `loop`.
|
|
768
|
-
*
|
|
769
|
-
* Bounding is {@link CarouselController.#clampToSlides}'s job alone, so an
|
|
770
|
-
* empty set is allowed to fall out of the arithmetic here rather than being
|
|
771
|
-
* special-cased in two places that could disagree.
|
|
772
|
-
*/
|
|
773
916
|
#step(delta) {
|
|
774
917
|
const total = this.slideTargets.length;
|
|
775
|
-
const next = this.#
|
|
918
|
+
const next = this.#resolveIndex() + delta;
|
|
776
919
|
if (this.loopValue) return (next + total) % total;
|
|
777
920
|
return Math.min(total - 1, Math.max(0, next));
|
|
778
921
|
}
|
|
779
|
-
/**
|
|
780
|
-
* Confines an index to the slide range, so no index can hide every slide.
|
|
781
|
-
*
|
|
782
|
-
* The single place an index is bounded: an empty set collapses to the first
|
|
783
|
-
* position, which is also where a non-numeric step from that empty set lands.
|
|
784
|
-
*/
|
|
785
922
|
#clampToSlides(index) {
|
|
786
923
|
const last = this.slideTargets.length - 1;
|
|
787
924
|
if (last < 0) return 0;
|
|
788
925
|
return Math.min(last, Math.max(0, index));
|
|
789
926
|
}
|
|
790
|
-
|
|
791
|
-
* Changes the active slide, updates state hooks, and emits `change` — but only
|
|
792
|
-
* when the index actually changes, so a `next`/`prev` clamped at the end (or an
|
|
793
|
-
* autoplay tick at a non-looping boundary) re-renders without a spurious event.
|
|
794
|
-
*/
|
|
795
|
-
#select(index, { focus }) {
|
|
927
|
+
#select(index, { focus }, reason = "user") {
|
|
796
928
|
const target = this.#clampToSlides(index);
|
|
797
|
-
const
|
|
929
|
+
const total = this.slideTargets.length;
|
|
930
|
+
const changed = target !== this.#index || total !== this.#total;
|
|
798
931
|
this.#index = target;
|
|
932
|
+
this.#total = total;
|
|
933
|
+
const token = changed ? this.#moves.record() : null;
|
|
799
934
|
this.#render({ focus });
|
|
800
935
|
this.#syncTimer();
|
|
801
|
-
if (
|
|
802
|
-
this.dispatch("change", { detail: { index: target, total
|
|
936
|
+
if (token !== null && this.#moves.isLatest(token)) {
|
|
937
|
+
this.dispatch("change", { detail: { index: target, total, reason } });
|
|
803
938
|
}
|
|
804
939
|
}
|
|
805
|
-
/**
|
|
806
|
-
* Reflects `this.#index` onto the slides, the pickers, and the step controls.
|
|
807
|
-
*
|
|
808
|
-
* Every observed attribute is written only when its value actually changes: the
|
|
809
|
-
* same writes are watched by {@link CarouselController.#onAttributeMutations},
|
|
810
|
-
* and an unconditional rewrite would feed the controller its own output.
|
|
811
|
-
*
|
|
812
|
-
* @stimeoRenderRoot
|
|
813
|
-
*/
|
|
814
940
|
#render({ focus }) {
|
|
815
941
|
this.#activeSlide = this.slideTargets[this.#index] ?? null;
|
|
816
942
|
this.slideTargets.forEach((slide, i) => {
|
|
@@ -823,10 +949,9 @@ var CarouselController = class extends Controller {
|
|
|
823
949
|
this.pickerTargets.forEach((picker, i) => {
|
|
824
950
|
setAttributeIfChanged(picker, "aria-selected", i === pickerIndex ? "true" : "false");
|
|
825
951
|
});
|
|
826
|
-
this.#roving.setActive(pickerIndex, { focus });
|
|
827
952
|
this.#syncStepControls();
|
|
953
|
+
this.#roving.setActive(pickerIndex, { focus });
|
|
828
954
|
}
|
|
829
|
-
/** Marks the step control a non-looping carousel has no slide left to reach. */
|
|
830
955
|
#syncStepControls() {
|
|
831
956
|
const last = this.slideTargets.length - 1;
|
|
832
957
|
const atStart = !this.loopValue && this.#index <= 0;
|
|
@@ -838,16 +963,6 @@ var CarouselController = class extends Controller {
|
|
|
838
963
|
this.#ariaDisabled.write(button, atEnd ? "true" : null);
|
|
839
964
|
}
|
|
840
965
|
}
|
|
841
|
-
/**
|
|
842
|
-
* Resolves which slide is current from the strongest evidence available.
|
|
843
|
-
*
|
|
844
|
-
* A single `data-state="active"` is unambiguous and wins, which is what restores
|
|
845
|
-
* the visible slide after a Turbo cache restore even with no pickers present.
|
|
846
|
-
* Competing claims — a slide inserted already marked active — are settled by the
|
|
847
|
-
* element the last render actually showed, so a newcomer never displaces what the
|
|
848
|
-
* reader is looking at. With no claim at all the authored picker selection
|
|
849
|
-
* decides, first in DOM order; failing that the previous position is kept.
|
|
850
|
-
*/
|
|
851
966
|
#resolveIndex() {
|
|
852
967
|
const slides = this.slideTargets;
|
|
853
968
|
const claims = [];
|
|
@@ -865,45 +980,30 @@ var CarouselController = class extends Controller {
|
|
|
865
980
|
);
|
|
866
981
|
return this.#clampToSlides(selected === -1 ? this.#index : selected);
|
|
867
982
|
}
|
|
868
|
-
/** Re-resolves the active slide after the target set changed and reports the move. */
|
|
869
983
|
#reconcileTargetSet() {
|
|
870
984
|
const previousIndex = this.#index;
|
|
871
985
|
const previousTotal = this.#total;
|
|
872
986
|
this.#index = this.#resolveIndex();
|
|
873
|
-
this.#render({ focus: false });
|
|
874
987
|
this.#total = this.slideTargets.length;
|
|
988
|
+
const changed = this.#index !== previousIndex || this.#total !== previousTotal;
|
|
989
|
+
const token = changed ? this.#moves.record() : null;
|
|
990
|
+
this.#render({ focus: false });
|
|
875
991
|
this.#syncTimer();
|
|
876
|
-
if (
|
|
992
|
+
if (token !== null && this.#moves.isLatest(token)) {
|
|
877
993
|
this.dispatch("reconcile", { detail: { index: this.#index, total: this.#total } });
|
|
878
994
|
}
|
|
879
995
|
}
|
|
880
|
-
/** The rotation intent in effect: `autoplay`, unless reduced motion holds it back. */
|
|
881
996
|
get #intent() {
|
|
882
997
|
return this.autoplayValue && !this.#motionHold;
|
|
883
998
|
}
|
|
884
|
-
/** Whether autoplay has anywhere left to advance to. */
|
|
885
999
|
#canAutoplay() {
|
|
886
1000
|
const total = this.slideTargets.length;
|
|
887
1001
|
if (total <= 1) return false;
|
|
888
1002
|
return this.loopValue || this.#index < total - 1;
|
|
889
1003
|
}
|
|
890
|
-
/** The advance delay; a non-finite or non-positive declaration falls back. */
|
|
891
1004
|
get #interval() {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Drives the autoplay interval toward the desired state and publishes it.
|
|
897
|
-
*
|
|
898
|
-
* Rotation runs when the intent is on, nothing suspends it, and a slide is left
|
|
899
|
-
* to advance to. The toggle shows the intent only while a slide is left, so
|
|
900
|
-
* `aria-pressed` never claims a rotation that cannot happen. This body reads
|
|
901
|
-
* `autoplay` and never writes it, so the declared intent returns once a slide
|
|
902
|
-
* lies ahead again. Crossing the run/stop boundary emits `play`/`pause`;
|
|
903
|
-
* re-arming at a new `interval` is the same state and stays silent.
|
|
904
|
-
*
|
|
905
|
-
* @stimeoRenderRoot
|
|
906
|
-
*/
|
|
1005
|
+
return this.#safeInterval;
|
|
1006
|
+
}
|
|
907
1007
|
#syncTimer() {
|
|
908
1008
|
const canAutoplay = this.#canAutoplay();
|
|
909
1009
|
const pressed = this.#intent && canAutoplay;
|
|
@@ -916,15 +1016,14 @@ var CarouselController = class extends Controller {
|
|
|
916
1016
|
}
|
|
917
1017
|
if (shouldRun && this.#timerId === null) {
|
|
918
1018
|
this.#timerInterval = interval;
|
|
919
|
-
this.#timerId = this.#intervals.set(
|
|
1019
|
+
this.#timerId = this.#intervals.set(
|
|
1020
|
+
() => this.#select(this.#step(1), { focus: false }, "timeout"),
|
|
1021
|
+
interval
|
|
1022
|
+
);
|
|
920
1023
|
}
|
|
921
1024
|
setAttributeIfChanged(this.element, "data-state", shouldRun ? "playing" : "paused");
|
|
922
|
-
if (shouldRun !== wasRunning) {
|
|
923
|
-
if (shouldRun) this.dispatch("play");
|
|
924
|
-
else this.dispatch("pause");
|
|
925
|
-
}
|
|
926
1025
|
for (const toggle of this.playToggleTargets) {
|
|
927
|
-
|
|
1026
|
+
this.#ariaPressed.write(toggle, pressed ? "true" : "false");
|
|
928
1027
|
this.#ariaDisabled.write(toggle, canAutoplay ? null : "true");
|
|
929
1028
|
this.#labels.reflect(toggle, pressed);
|
|
930
1029
|
}
|
|
@@ -932,12 +1031,11 @@ var CarouselController = class extends Controller {
|
|
|
932
1031
|
this.#ariaLive.write(this.viewportTarget, shouldRun ? "off" : "polite");
|
|
933
1032
|
this.#ariaAtomic.write(this.viewportTarget, "false");
|
|
934
1033
|
}
|
|
1034
|
+
if (shouldRun !== wasRunning) {
|
|
1035
|
+
if (shouldRun) this.dispatch("play");
|
|
1036
|
+
else this.dispatch("pause");
|
|
1037
|
+
}
|
|
935
1038
|
}
|
|
936
|
-
/**
|
|
937
|
-
* Ends the rotation once the controller has really left. A carousel still in
|
|
938
|
-
* the document hears the end — `pause`, and `data-state="paused"` — so no `play`
|
|
939
|
-
* it reported is left without one; a detached tree keeps the markup it had.
|
|
940
|
-
*/
|
|
941
1039
|
#endRun() {
|
|
942
1040
|
const wasRunning = this.#timerId !== null;
|
|
943
1041
|
this.#intervals.clearAll();
|
|
@@ -946,26 +1044,28 @@ var CarouselController = class extends Controller {
|
|
|
946
1044
|
setAttributeIfChanged(this.element, "data-state", "paused");
|
|
947
1045
|
this.dispatch("pause");
|
|
948
1046
|
}
|
|
949
|
-
/** Hands every leased attribute back to the value the consumer authored. */
|
|
950
1047
|
#returnLeases() {
|
|
951
1048
|
this.#ariaDisabled.returnAll();
|
|
1049
|
+
this.#ariaPressed.returnAll();
|
|
952
1050
|
this.#ariaLive.returnAll();
|
|
953
1051
|
this.#ariaAtomic.returnAll();
|
|
954
1052
|
}
|
|
1053
|
+
get #safeInterval() {
|
|
1054
|
+
return this.#numbers.read(
|
|
1055
|
+
this,
|
|
1056
|
+
"interval",
|
|
1057
|
+
this.intervalValue,
|
|
1058
|
+
_CarouselController.values.interval.default,
|
|
1059
|
+
_CarouselController.valueConstraints.interval
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
955
1062
|
};
|
|
956
1063
|
function isFocusEvent(event) {
|
|
957
1064
|
return event?.type.startsWith("focus") === true;
|
|
958
1065
|
}
|
|
959
|
-
function hasModifier(event) {
|
|
960
|
-
return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
|
|
961
|
-
}
|
|
962
1066
|
function hits(elements, node) {
|
|
963
1067
|
return ownerIndex(elements, node) !== -1;
|
|
964
1068
|
}
|
|
965
|
-
function setAttributeIfChanged(element, name, value) {
|
|
966
|
-
if (element.getAttribute(name) !== value) element.setAttribute(name, value);
|
|
967
|
-
}
|
|
968
1069
|
|
|
969
1070
|
export { CarouselController };
|
|
970
|
-
//# sourceMappingURL=carousel_controller.js.map
|
|
971
1071
|
//# sourceMappingURL=carousel_controller.js.map
|