stimeo-ui 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +8 -1
- package/dist/cable/index.d.ts +79 -23
- package/dist/cable/index.js +363 -436
- package/dist/cable/index.js.map +1 -1
- package/dist/controllers/accordion_controller.d.ts +4 -2
- package/dist/controllers/accordion_controller.js +166 -55
- package/dist/controllers/accordion_controller.js.map +1 -1
- package/dist/controllers/alert_dialog_controller.d.ts +12 -2
- package/dist/controllers/alert_dialog_controller.js +862 -221
- package/dist/controllers/alert_dialog_controller.js.map +1 -1
- package/dist/controllers/announcer_controller.d.ts +35 -5
- package/dist/controllers/announcer_controller.js +135 -198
- package/dist/controllers/announcer_controller.js.map +1 -1
- package/dist/controllers/aspect_ratio_controller.js +0 -10
- package/dist/controllers/aspect_ratio_controller.js.map +1 -1
- package/dist/controllers/auto_submit_controller.d.ts +30 -6
- package/dist/controllers/auto_submit_controller.js +271 -127
- package/dist/controllers/auto_submit_controller.js.map +1 -1
- package/dist/controllers/avatar_controller.js +171 -105
- package/dist/controllers/avatar_controller.js.map +1 -1
- package/dist/controllers/breadcrumb_controller.js +0 -130
- package/dist/controllers/breadcrumb_controller.js.map +1 -1
- package/dist/controllers/bulk_select_controller.d.ts +18 -0
- package/dist/controllers/bulk_select_controller.js +262 -52
- package/dist/controllers/bulk_select_controller.js.map +1 -1
- package/dist/controllers/calendar_controller.d.ts +24 -5
- package/dist/controllers/calendar_controller.js +175 -209
- package/dist/controllers/calendar_controller.js.map +1 -1
- package/dist/controllers/carousel_controller.d.ts +34 -7
- package/dist/controllers/carousel_controller.js +461 -399
- package/dist/controllers/carousel_controller.js.map +1 -1
- package/dist/controllers/character_counter_controller.d.ts +17 -1
- package/dist/controllers/character_counter_controller.js +246 -156
- package/dist/controllers/character_counter_controller.js.map +1 -1
- package/dist/controllers/checkbox_controller.d.ts +7 -2
- package/dist/controllers/checkbox_controller.js +32 -74
- package/dist/controllers/checkbox_controller.js.map +1 -1
- package/dist/controllers/clipboard_controller.d.ts +16 -5
- package/dist/controllers/clipboard_controller.js +168 -147
- package/dist/controllers/clipboard_controller.js.map +1 -1
- package/dist/controllers/collapsible_controller.d.ts +16 -4
- package/dist/controllers/collapsible_controller.js +301 -120
- package/dist/controllers/collapsible_controller.js.map +1 -1
- package/dist/controllers/color_picker_controller.d.ts +18 -6
- package/dist/controllers/color_picker_controller.js +149 -139
- package/dist/controllers/color_picker_controller.js.map +1 -1
- package/dist/controllers/combobox_controller.d.ts +23 -5
- package/dist/controllers/combobox_controller.js +360 -133
- package/dist/controllers/combobox_controller.js.map +1 -1
- package/dist/controllers/command_palette_controller.d.ts +45 -14
- package/dist/controllers/command_palette_controller.js +970 -358
- package/dist/controllers/command_palette_controller.js.map +1 -1
- package/dist/controllers/conditional_fields_controller.d.ts +15 -2
- package/dist/controllers/conditional_fields_controller.js +166 -124
- package/dist/controllers/conditional_fields_controller.js.map +1 -1
- package/dist/controllers/confirm_controller.d.ts +49 -12
- package/dist/controllers/confirm_controller.js +939 -248
- package/dist/controllers/confirm_controller.js.map +1 -1
- package/dist/controllers/context_menu_controller.d.ts +31 -4
- package/dist/controllers/context_menu_controller.js +256 -128
- package/dist/controllers/context_menu_controller.js.map +1 -1
- package/dist/controllers/count_up_controller.d.ts +13 -1
- package/dist/controllers/count_up_controller.js +73 -21
- package/dist/controllers/count_up_controller.js.map +1 -1
- package/dist/controllers/countdown_controller.d.ts +45 -1
- package/dist/controllers/countdown_controller.js +183 -148
- package/dist/controllers/countdown_controller.js.map +1 -1
- package/dist/controllers/currency_input_controller.d.ts +21 -2
- package/dist/controllers/currency_input_controller.js +134 -158
- package/dist/controllers/currency_input_controller.js.map +1 -1
- package/dist/controllers/data_grid_controller.d.ts +15 -4
- package/dist/controllers/data_grid_controller.js +114 -186
- package/dist/controllers/data_grid_controller.js.map +1 -1
- package/dist/controllers/date_range_picker_controller.d.ts +30 -5
- package/dist/controllers/date_range_picker_controller.js +288 -206
- package/dist/controllers/date_range_picker_controller.js.map +1 -1
- package/dist/controllers/dialog_controller.d.ts +13 -1
- package/dist/controllers/dialog_controller.js +858 -223
- package/dist/controllers/dialog_controller.js.map +1 -1
- package/dist/controllers/direct_upload_controller.d.ts +24 -7
- package/dist/controllers/direct_upload_controller.js +73 -153
- package/dist/controllers/direct_upload_controller.js.map +1 -1
- package/dist/controllers/dirty_form_controller.js +14 -48
- package/dist/controllers/dirty_form_controller.js.map +1 -1
- package/dist/controllers/dismissible_controller.d.ts +19 -0
- package/dist/controllers/dismissible_controller.js +175 -21
- package/dist/controllers/dismissible_controller.js.map +1 -1
- package/dist/controllers/drawer_controller.d.ts +39 -11
- package/dist/controllers/drawer_controller.js +933 -341
- package/dist/controllers/drawer_controller.js.map +1 -1
- package/dist/controllers/dropdown_controller.d.ts +31 -1
- package/dist/controllers/dropdown_controller.js +312 -82
- package/dist/controllers/dropdown_controller.js.map +1 -1
- package/dist/controllers/editable_controller.d.ts +12 -1
- package/dist/controllers/editable_controller.js +158 -68
- package/dist/controllers/editable_controller.js.map +1 -1
- package/dist/controllers/empty_state_controller.js +67 -53
- package/dist/controllers/empty_state_controller.js.map +1 -1
- package/dist/controllers/file_dropzone_controller.d.ts +60 -18
- package/dist/controllers/file_dropzone_controller.js +311 -207
- package/dist/controllers/file_dropzone_controller.js.map +1 -1
- package/dist/controllers/filter_controller.js +67 -39
- package/dist/controllers/filter_controller.js.map +1 -1
- package/dist/controllers/flash_controller.d.ts +31 -8
- package/dist/controllers/flash_controller.js +172 -314
- package/dist/controllers/flash_controller.js.map +1 -1
- package/dist/controllers/focus_controller.d.ts +12 -3
- package/dist/controllers/focus_controller.js +654 -240
- package/dist/controllers/focus_controller.js.map +1 -1
- package/dist/controllers/form_field_controller.js +147 -132
- package/dist/controllers/form_field_controller.js.map +1 -1
- package/dist/controllers/form_validation_controller.js +13 -86
- package/dist/controllers/form_validation_controller.js.map +1 -1
- package/dist/controllers/frame_loading_controller.d.ts +41 -9
- package/dist/controllers/frame_loading_controller.js +264 -213
- package/dist/controllers/frame_loading_controller.js.map +1 -1
- package/dist/controllers/highlight_controller.d.ts +22 -2
- package/dist/controllers/highlight_controller.js +107 -75
- package/dist/controllers/highlight_controller.js.map +1 -1
- package/dist/controllers/hover_card_controller.d.ts +56 -3
- package/dist/controllers/hover_card_controller.js +309 -118
- package/dist/controllers/hover_card_controller.js.map +1 -1
- package/dist/controllers/idle_controller.d.ts +42 -1
- package/dist/controllers/idle_controller.js +267 -101
- package/dist/controllers/idle_controller.js.map +1 -1
- package/dist/controllers/input_mask_controller.js +71 -72
- package/dist/controllers/input_mask_controller.js.map +1 -1
- package/dist/controllers/intersection_controller.d.ts +32 -13
- package/dist/controllers/intersection_controller.js +147 -106
- package/dist/controllers/intersection_controller.js.map +1 -1
- package/dist/controllers/lazy_frame_controller.d.ts +16 -0
- package/dist/controllers/lazy_frame_controller.js +9 -65
- package/dist/controllers/lazy_frame_controller.js.map +1 -1
- package/dist/controllers/listbox_controller.d.ts +34 -2
- package/dist/controllers/listbox_controller.js +306 -204
- package/dist/controllers/listbox_controller.js.map +1 -1
- package/dist/controllers/local_time_controller.d.ts +1 -1
- package/dist/controllers/local_time_controller.js +54 -64
- package/dist/controllers/local_time_controller.js.map +1 -1
- package/dist/controllers/masonry_controller.d.ts +11 -3
- package/dist/controllers/masonry_controller.js +70 -93
- package/dist/controllers/masonry_controller.js.map +1 -1
- package/dist/controllers/menu_controller.d.ts +32 -7
- package/dist/controllers/menu_controller.js +214 -156
- package/dist/controllers/menu_controller.js.map +1 -1
- package/dist/controllers/menubar_controller.d.ts +4 -4
- package/dist/controllers/menubar_controller.js +170 -329
- package/dist/controllers/menubar_controller.js.map +1 -1
- package/dist/controllers/meter_controller.d.ts +20 -0
- package/dist/controllers/meter_controller.js +151 -67
- package/dist/controllers/meter_controller.js.map +1 -1
- package/dist/controllers/multi_select_controller.d.ts +32 -4
- package/dist/controllers/multi_select_controller.js +593 -358
- package/dist/controllers/multi_select_controller.js.map +1 -1
- package/dist/controllers/navigation_menu_controller.d.ts +8 -1
- package/dist/controllers/navigation_menu_controller.js +124 -232
- package/dist/controllers/navigation_menu_controller.js.map +1 -1
- package/dist/controllers/nested_form_controller.d.ts +10 -0
- package/dist/controllers/nested_form_controller.js +352 -182
- package/dist/controllers/nested_form_controller.js.map +1 -1
- package/dist/controllers/network_status_controller.d.ts +23 -0
- package/dist/controllers/network_status_controller.js +242 -54
- package/dist/controllers/network_status_controller.js.map +1 -1
- package/dist/controllers/number_input_controller.d.ts +26 -2
- package/dist/controllers/number_input_controller.js +320 -263
- package/dist/controllers/number_input_controller.js.map +1 -1
- package/dist/controllers/optimistic_controller.js +16 -76
- package/dist/controllers/optimistic_controller.js.map +1 -1
- package/dist/controllers/otp_controller.d.ts +20 -4
- package/dist/controllers/otp_controller.js +198 -193
- package/dist/controllers/otp_controller.js.map +1 -1
- package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
- package/dist/controllers/overflow_indicator_controller.js +116 -113
- package/dist/controllers/overflow_indicator_controller.js.map +1 -1
- package/dist/controllers/overflow_menu_controller.d.ts +48 -11
- package/dist/controllers/overflow_menu_controller.js +343 -283
- package/dist/controllers/overflow_menu_controller.js.map +1 -1
- package/dist/controllers/pagination_controller.d.ts +35 -6
- package/dist/controllers/pagination_controller.js +295 -119
- package/dist/controllers/pagination_controller.js.map +1 -1
- package/dist/controllers/password_reveal_controller.d.ts +45 -8
- package/dist/controllers/password_reveal_controller.js +389 -121
- package/dist/controllers/password_reveal_controller.js.map +1 -1
- package/dist/controllers/password_strength_controller.d.ts +14 -5
- package/dist/controllers/password_strength_controller.js +99 -153
- package/dist/controllers/password_strength_controller.js.map +1 -1
- package/dist/controllers/persist_controller.d.ts +11 -4
- package/dist/controllers/persist_controller.js +237 -103
- package/dist/controllers/persist_controller.js.map +1 -1
- package/dist/controllers/pointer_drag_controller.d.ts +19 -7
- package/dist/controllers/pointer_drag_controller.js +185 -165
- package/dist/controllers/pointer_drag_controller.js.map +1 -1
- package/dist/controllers/popover_controller.d.ts +38 -2
- package/dist/controllers/popover_controller.js +236 -97
- package/dist/controllers/popover_controller.js.map +1 -1
- package/dist/controllers/portal_controller.d.ts +44 -4
- package/dist/controllers/portal_controller.js +209 -76
- package/dist/controllers/portal_controller.js.map +1 -1
- package/dist/controllers/preview_guard_controller.d.ts +10 -4
- package/dist/controllers/preview_guard_controller.js +232 -116
- package/dist/controllers/preview_guard_controller.js.map +1 -1
- package/dist/controllers/progress_controller.d.ts +11 -0
- package/dist/controllers/progress_controller.js +125 -56
- package/dist/controllers/progress_controller.js.map +1 -1
- package/dist/controllers/radio_group_controller.d.ts +14 -5
- package/dist/controllers/radio_group_controller.js +117 -137
- package/dist/controllers/radio_group_controller.js.map +1 -1
- package/dist/controllers/range_slider_controller.d.ts +43 -3
- package/dist/controllers/range_slider_controller.js +192 -105
- package/dist/controllers/range_slider_controller.js.map +1 -1
- package/dist/controllers/rating_controller.d.ts +20 -4
- package/dist/controllers/rating_controller.js +418 -207
- package/dist/controllers/rating_controller.js.map +1 -1
- package/dist/controllers/read_more_controller.d.ts +2 -0
- package/dist/controllers/read_more_controller.js +134 -63
- package/dist/controllers/read_more_controller.js.map +1 -1
- package/dist/controllers/reading_progress_controller.d.ts +1 -1
- package/dist/controllers/reading_progress_controller.js +147 -135
- package/dist/controllers/reading_progress_controller.js.map +1 -1
- package/dist/controllers/relative_time_controller.d.ts +18 -4
- package/dist/controllers/relative_time_controller.js +145 -110
- package/dist/controllers/relative_time_controller.js.map +1 -1
- package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
- package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
- package/dist/controllers/reset_on_restore_controller.js.map +1 -0
- package/dist/controllers/resizable_controller.d.ts +28 -2
- package/dist/controllers/resizable_controller.js +255 -139
- package/dist/controllers/resizable_controller.js.map +1 -1
- package/dist/controllers/roving_controller.js +39 -84
- package/dist/controllers/roving_controller.js.map +1 -1
- package/dist/controllers/scroll_area_controller.d.ts +7 -3
- package/dist/controllers/scroll_area_controller.js +428 -225
- package/dist/controllers/scroll_area_controller.js.map +1 -1
- package/dist/controllers/scroll_restore_controller.js +0 -53
- package/dist/controllers/scroll_restore_controller.js.map +1 -1
- package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
- package/dist/controllers/scroll_visibility_controller.js +395 -145
- package/dist/controllers/scroll_visibility_controller.js.map +1 -1
- package/dist/controllers/scrollspy_controller.d.ts +6 -1
- package/dist/controllers/scrollspy_controller.js +181 -242
- package/dist/controllers/scrollspy_controller.js.map +1 -1
- package/dist/controllers/separator_controller.d.ts +19 -4
- package/dist/controllers/separator_controller.js +267 -140
- package/dist/controllers/separator_controller.js.map +1 -1
- package/dist/controllers/sidebar_controller.d.ts +64 -7
- package/dist/controllers/sidebar_controller.js +1023 -386
- package/dist/controllers/sidebar_controller.js.map +1 -1
- package/dist/controllers/skeleton_controller.d.ts +23 -0
- package/dist/controllers/skeleton_controller.js +248 -97
- package/dist/controllers/skeleton_controller.js.map +1 -1
- package/dist/controllers/slider_controller.d.ts +27 -1
- package/dist/controllers/slider_controller.js +159 -79
- package/dist/controllers/slider_controller.js.map +1 -1
- package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
- package/dist/controllers/smart_sticky_header_controller.js +162 -63
- package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
- package/dist/controllers/sortable_controller.js +0 -112
- package/dist/controllers/sortable_controller.js.map +1 -1
- package/dist/controllers/spinner_controller.d.ts +39 -4
- package/dist/controllers/spinner_controller.js +277 -169
- package/dist/controllers/spinner_controller.js.map +1 -1
- package/dist/controllers/step_indicator_controller.d.ts +5 -0
- package/dist/controllers/step_indicator_controller.js +99 -46
- package/dist/controllers/step_indicator_controller.js.map +1 -1
- package/dist/controllers/stepper_controller.d.ts +12 -3
- package/dist/controllers/stepper_controller.js +114 -73
- package/dist/controllers/stepper_controller.js.map +1 -1
- package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
- package/dist/controllers/stick_to_bottom_controller.js +262 -138
- package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
- package/dist/controllers/sticky_observer_controller.d.ts +11 -0
- package/dist/controllers/sticky_observer_controller.js +214 -49
- package/dist/controllers/sticky_observer_controller.js.map +1 -1
- package/dist/controllers/submit_once_controller.d.ts +23 -8
- package/dist/controllers/submit_once_controller.js +294 -205
- package/dist/controllers/submit_once_controller.js.map +1 -1
- package/dist/controllers/switch_controller.d.ts +12 -1
- package/dist/controllers/switch_controller.js +33 -60
- package/dist/controllers/switch_controller.js.map +1 -1
- package/dist/controllers/tabs_controller.d.ts +2 -2
- package/dist/controllers/tabs_controller.js +35 -29
- package/dist/controllers/tabs_controller.js.map +1 -1
- package/dist/controllers/tags_input_controller.d.ts +12 -0
- package/dist/controllers/tags_input_controller.js +136 -145
- package/dist/controllers/tags_input_controller.js.map +1 -1
- package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
- package/dist/controllers/textarea_autosize_controller.js +71 -58
- package/dist/controllers/textarea_autosize_controller.js.map +1 -1
- package/dist/controllers/theme_controller.d.ts +21 -9
- package/dist/controllers/theme_controller.js +130 -112
- package/dist/controllers/theme_controller.js.map +1 -1
- package/dist/controllers/time_picker_controller.d.ts +18 -1
- package/dist/controllers/time_picker_controller.js +146 -80
- package/dist/controllers/time_picker_controller.js.map +1 -1
- package/dist/controllers/toast_controller.d.ts +36 -11
- package/dist/controllers/toast_controller.js +186 -297
- package/dist/controllers/toast_controller.js.map +1 -1
- package/dist/controllers/toggle_group_controller.d.ts +12 -3
- package/dist/controllers/toggle_group_controller.js +95 -136
- package/dist/controllers/toggle_group_controller.js.map +1 -1
- package/dist/controllers/toolbar_controller.js +68 -98
- package/dist/controllers/toolbar_controller.js.map +1 -1
- package/dist/controllers/tooltip_controller.d.ts +45 -3
- package/dist/controllers/tooltip_controller.js +158 -124
- package/dist/controllers/tooltip_controller.js.map +1 -1
- package/dist/controllers/transition_controller.d.ts +22 -4
- package/dist/controllers/transition_controller.js +123 -119
- package/dist/controllers/transition_controller.js.map +1 -1
- package/dist/controllers/tree_view_controller.d.ts +21 -11
- package/dist/controllers/tree_view_controller.js +150 -345
- package/dist/controllers/tree_view_controller.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6370 -9346
- package/dist/index.js.map +1 -1
- package/dist/inspector/cli.d.ts +23 -3
- package/dist/inspector/cli.js +92 -37
- package/dist/inspector/cli.js.map +1 -1
- package/dist/inspector/cli_bin.js +92 -56
- package/dist/inspector/cli_bin.js.map +1 -1
- package/dist/inspector/examples.json +8 -8
- package/dist/inspector/manifest.json +787 -100
- package/dist/positioning/index.d.ts +8 -0
- package/dist/positioning/index.js +148 -47
- package/dist/positioning/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
- package/dist/controllers/reset_before_cache_controller.js.map +0 -1
|
@@ -1,8 +1,27 @@
|
|
|
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/declared_value.ts
|
|
6
25
|
function parseDeclared(raw, parse, fallback) {
|
|
7
26
|
try {
|
|
8
27
|
return parse(raw);
|
|
@@ -22,14 +41,8 @@ function validSelector(element, raw, fallback) {
|
|
|
22
41
|
);
|
|
23
42
|
}
|
|
24
43
|
|
|
25
|
-
// src/utils/frame_coalescer.ts
|
|
26
44
|
var FrameCoalescer = class {
|
|
27
45
|
#frame = null;
|
|
28
|
-
/**
|
|
29
|
-
* Runs `run` on the next frame, unless a frame is already pending — the first
|
|
30
|
-
* request of a burst wins and the rest are dropped. The pending frame is
|
|
31
|
-
* released before `run`, so `run` may request the next one.
|
|
32
|
-
*/
|
|
33
46
|
schedule(run) {
|
|
34
47
|
if (this.#frame !== null) return;
|
|
35
48
|
this.#frame = requestAnimationFrame(() => {
|
|
@@ -37,14 +50,6 @@ var FrameCoalescer = class {
|
|
|
37
50
|
run();
|
|
38
51
|
});
|
|
39
52
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Drops the pending frame, and reaches the platform only when there is one.
|
|
42
|
-
*
|
|
43
|
-
* There is no handle value that stands for "nothing pending":
|
|
44
|
-
* `cancelAnimationFrame` takes an `unsigned long`, so a negative placeholder
|
|
45
|
-
* arrives as a large positive number that the same allocator can hand out, and
|
|
46
|
-
* an idle cancel would then drop a frame belonging to someone else.
|
|
47
|
-
*/
|
|
48
53
|
cancel() {
|
|
49
54
|
if (this.#frame === null) return;
|
|
50
55
|
cancelAnimationFrame(this.#frame);
|
|
@@ -52,7 +57,6 @@ var FrameCoalescer = class {
|
|
|
52
57
|
}
|
|
53
58
|
};
|
|
54
59
|
|
|
55
|
-
// src/utils/intersection_watcher.ts
|
|
56
60
|
function queryRoot(selector) {
|
|
57
61
|
if (!selector) return null;
|
|
58
62
|
return parseDeclared(selector, (raw) => document.querySelector(raw), null);
|
|
@@ -65,30 +69,12 @@ var IntersectionWatcher = class {
|
|
|
65
69
|
constructor(onEntries) {
|
|
66
70
|
this.#onEntries = onEntries;
|
|
67
71
|
}
|
|
68
|
-
/** Whether an observer is live (started, `IntersectionObserver` supported). */
|
|
69
72
|
get active() {
|
|
70
73
|
return this.#active;
|
|
71
74
|
}
|
|
72
|
-
/** Whether the live observer discarded configured options after construction failed. */
|
|
73
75
|
get usingPlatformDefaults() {
|
|
74
76
|
return this.#usingPlatformDefaults;
|
|
75
77
|
}
|
|
76
|
-
/**
|
|
77
|
-
* (Re)creates the observer and observes `targets`. Returns `false` — leaving
|
|
78
|
-
* the watcher inert — without `IntersectionObserver` support (very old
|
|
79
|
-
* browsers; the caller's no-JS fallback stays in charge) or with no targets.
|
|
80
|
-
* If initial construction with the configured options fails, the watcher
|
|
81
|
-
* warns and retries once with the same root and platform defaults. A
|
|
82
|
-
* `rootSelector` that does not parse resolves to the viewport (see
|
|
83
|
-
* {@link IntersectionWatchOptions.rootSelector}), so a typo never fails the
|
|
84
|
-
* call.
|
|
85
|
-
*
|
|
86
|
-
* @throws The fallback constructor error if both construction attempts fail,
|
|
87
|
-
* or whatever the platform throws from `observe()`. The exception is passed
|
|
88
|
-
* through unchanged, but the watcher rolls back first: every target observed
|
|
89
|
-
* so far is released and `active` stays `false`, so a caller that retries
|
|
90
|
-
* starts from a clean slate.
|
|
91
|
-
*/
|
|
92
78
|
start(targets, options = {}) {
|
|
93
79
|
this.stop();
|
|
94
80
|
if (typeof IntersectionObserver === "undefined") return false;
|
|
@@ -126,14 +112,6 @@ var IntersectionWatcher = class {
|
|
|
126
112
|
throw error;
|
|
127
113
|
}
|
|
128
114
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Re-delivers `target`'s CURRENT intersection state: `IntersectionObserver`
|
|
131
|
-
* only reports *changes*, but `observe()` always reports the present state,
|
|
132
|
-
* so unobserve→observe turns "still intersecting" into a fresh callback.
|
|
133
|
-
*
|
|
134
|
-
* @throws Whatever `unobserve()`/`observe()` throws. The watcher is stopped
|
|
135
|
-
* first, so it never stays live with a half-rearmed target.
|
|
136
|
-
*/
|
|
137
115
|
rearm(target) {
|
|
138
116
|
if (!this.#observer) return;
|
|
139
117
|
try {
|
|
@@ -144,7 +122,6 @@ var IntersectionWatcher = class {
|
|
|
144
122
|
throw error;
|
|
145
123
|
}
|
|
146
124
|
}
|
|
147
|
-
/** Severs the observer; late queued callbacks become no-ops via the guard. */
|
|
148
125
|
stop() {
|
|
149
126
|
this.#active = false;
|
|
150
127
|
this.#observer?.disconnect();
|
|
@@ -153,12 +130,118 @@ var IntersectionWatcher = class {
|
|
|
153
130
|
}
|
|
154
131
|
};
|
|
155
132
|
|
|
156
|
-
|
|
133
|
+
var ListenerSet = class {
|
|
134
|
+
#abort = new AbortController();
|
|
135
|
+
add(target, type, handler, options) {
|
|
136
|
+
target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
|
|
137
|
+
}
|
|
138
|
+
dispose() {
|
|
139
|
+
this.#abort.abort();
|
|
140
|
+
this.#abort = new AbortController();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
var MicrotaskCoalescer = class {
|
|
145
|
+
#run;
|
|
146
|
+
#queued = false;
|
|
147
|
+
#active = false;
|
|
148
|
+
#generation = 0;
|
|
149
|
+
constructor(run) {
|
|
150
|
+
this.#run = run;
|
|
151
|
+
}
|
|
152
|
+
activate() {
|
|
153
|
+
this.#active = true;
|
|
154
|
+
}
|
|
155
|
+
cancel() {
|
|
156
|
+
this.#active = false;
|
|
157
|
+
this.#queued = false;
|
|
158
|
+
this.#generation += 1;
|
|
159
|
+
}
|
|
160
|
+
schedule() {
|
|
161
|
+
if (!this.#active || this.#queued) return;
|
|
162
|
+
this.#queued = true;
|
|
163
|
+
const generation = this.#generation;
|
|
164
|
+
queueMicrotask(() => {
|
|
165
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
166
|
+
this.#queued = false;
|
|
167
|
+
this.#run();
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
var MorphRenderWatcher = class {
|
|
173
|
+
#listeners = new ListenerSet();
|
|
174
|
+
#pass;
|
|
175
|
+
#element = null;
|
|
176
|
+
#onMorph = () => this.#pass.schedule();
|
|
177
|
+
constructor(run) {
|
|
178
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
179
|
+
}
|
|
180
|
+
observe(element) {
|
|
181
|
+
if (this.#element === element) return;
|
|
182
|
+
this.disconnect();
|
|
183
|
+
this.#element = element;
|
|
184
|
+
this.#pass.activate();
|
|
185
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
186
|
+
}
|
|
187
|
+
schedule() {
|
|
188
|
+
this.#pass.schedule();
|
|
189
|
+
}
|
|
190
|
+
disconnect() {
|
|
191
|
+
this.#listeners.dispose();
|
|
192
|
+
this.#pass.cancel();
|
|
193
|
+
this.#element = null;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
var NUMBER_BOUNDS = {
|
|
198
|
+
finite: { finite: true }};
|
|
199
|
+
function matchesNumberBounds(value, bounds) {
|
|
200
|
+
if (!Number.isFinite(value)) {
|
|
201
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
202
|
+
if (direction === null) return false;
|
|
203
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
204
|
+
}
|
|
205
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
206
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
207
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
208
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
209
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function readNumber(raw, fallback, bounds) {
|
|
214
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
var NumberValueReader = class {
|
|
218
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
219
|
+
read(owner, name, raw, fallback, bounds) {
|
|
220
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
221
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
222
|
+
this.#lastRejected.delete(name);
|
|
223
|
+
return resolved;
|
|
224
|
+
}
|
|
225
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
226
|
+
const literal = owner.element.getAttribute(attribute);
|
|
227
|
+
if (literal === null) {
|
|
228
|
+
this.#lastRejected.delete(name);
|
|
229
|
+
return resolved;
|
|
230
|
+
}
|
|
231
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
232
|
+
this.#lastRejected.set(name, literal);
|
|
233
|
+
console.warn(
|
|
234
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return resolved;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
157
241
|
function prefersReducedMotion() {
|
|
158
242
|
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
159
243
|
}
|
|
160
244
|
|
|
161
|
-
// src/utils/scroll_source.ts
|
|
162
245
|
function resolveScrollContainer(selector) {
|
|
163
246
|
const match = selector ? document.querySelector(selector) : null;
|
|
164
247
|
return match instanceof HTMLElement ? match : null;
|
|
@@ -167,9 +250,13 @@ function scrollOffset(source) {
|
|
|
167
250
|
return source === window ? window.scrollY ?? window.pageYOffset ?? 0 : source.scrollTop;
|
|
168
251
|
}
|
|
169
252
|
|
|
170
|
-
|
|
253
|
+
function targetSelector(identifier, name) {
|
|
254
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
255
|
+
}
|
|
256
|
+
|
|
171
257
|
var ANCHOR_ATTRIBUTES = ["href", "data-href"];
|
|
172
|
-
var ScrollspyController = class extends Controller {
|
|
258
|
+
var ScrollspyController = class _ScrollspyController extends Controller {
|
|
259
|
+
#numbers = new NumberValueReader();
|
|
173
260
|
static targets = ["link"];
|
|
174
261
|
static values = {
|
|
175
262
|
offset: { type: Number, default: 0 },
|
|
@@ -177,70 +264,48 @@ var ScrollspyController = class extends Controller {
|
|
|
177
264
|
rootSelector: { type: String, default: "" },
|
|
178
265
|
focusSection: { type: Boolean, default: false }
|
|
179
266
|
};
|
|
267
|
+
static valueConstraints = {
|
|
268
|
+
offset: NUMBER_BOUNDS.finite
|
|
269
|
+
};
|
|
180
270
|
static actions = ["scrollTo"];
|
|
181
271
|
static events = ["change"];
|
|
182
|
-
/**
|
|
183
|
-
* The one offset shared by observation, active-section selection, and
|
|
184
|
-
* scrolling. Stimulus parses a malformed Number Value as `NaN`; degrading it
|
|
185
|
-
* to the declared default keeps every path aligned instead of only repairing
|
|
186
|
-
* the observer margin while selection and scrolling still receive `NaN`.
|
|
187
|
-
*/
|
|
188
272
|
get #offset() {
|
|
189
|
-
return
|
|
273
|
+
return this.#safeOffset;
|
|
190
274
|
}
|
|
191
|
-
/** Shared IO plumbing (support guard, active guard, teardown). */
|
|
192
275
|
#watcher = new IntersectionWatcher((entries) => this.#onIntersection(entries));
|
|
193
276
|
#isConnected = false;
|
|
194
|
-
/**
|
|
195
|
-
* Sections currently tracked, by id. Only the *latest reported* intersection
|
|
196
|
-
* flag is stored — never a coordinate. Positions are re-measured when the
|
|
197
|
-
* active section is evaluated, so a section observed several batches ago is
|
|
198
|
-
* never compared against a trigger line computed now.
|
|
199
|
-
*/
|
|
200
277
|
#intersectionStates = /* @__PURE__ */ new Map();
|
|
201
|
-
/** Current active section ID; comparing it suppresses duplicate `change` events. */
|
|
202
278
|
#activeSectionId = "";
|
|
203
|
-
/**
|
|
204
|
-
* Scroll root resolved when the observer was (re)built; `null` = viewport.
|
|
205
|
-
* Cached so a click and every intersection batch reuse the element the
|
|
206
|
-
* observer is actually watching instead of re-querying the document.
|
|
207
|
-
*/
|
|
208
279
|
#rootElement = null;
|
|
209
|
-
/**
|
|
210
|
-
* The source the `scroll` listener is currently attached to — the resolved
|
|
211
|
-
* root, or the window while the viewport is spied. `null` means "nothing
|
|
212
|
-
* attached", so teardown always detaches from the source it attached to
|
|
213
|
-
* rather than from whatever the selector resolves to now.
|
|
214
|
-
*/
|
|
215
280
|
#scrollSource = null;
|
|
216
|
-
/** Coalesces a scroll burst into one measurement per frame. */
|
|
217
281
|
#frames = new FrameCoalescer();
|
|
218
|
-
/** The validated `rootSelector`, or `""` when the declaration cannot be parsed. */
|
|
219
282
|
#rootSelector = "";
|
|
220
|
-
/** Watches the link targets' anchor attributes for an in-place morph rewrite. */
|
|
221
283
|
#anchorObserver = null;
|
|
222
|
-
|
|
223
|
-
#
|
|
284
|
+
#resyncInputsChanged = false;
|
|
285
|
+
#resync = new MorphRenderWatcher(() => {
|
|
286
|
+
const retainStates = !this.#resyncInputsChanged;
|
|
287
|
+
this.#resyncInputsChanged = false;
|
|
288
|
+
this.#initializeObserver(retainStates);
|
|
289
|
+
this.#evaluateActiveSection();
|
|
290
|
+
});
|
|
224
291
|
connect() {
|
|
292
|
+
this.#resync.observe(this.element);
|
|
225
293
|
this.#isConnected = true;
|
|
226
294
|
this.#observeAnchorAttributes();
|
|
227
295
|
this.#initializeObserver();
|
|
228
296
|
}
|
|
229
297
|
disconnect() {
|
|
230
298
|
this.#isConnected = false;
|
|
231
|
-
this.#
|
|
299
|
+
this.#resync.disconnect();
|
|
300
|
+
this.#resyncInputsChanged = false;
|
|
232
301
|
this.#watcher.stop();
|
|
233
302
|
this.#anchorObserver?.disconnect();
|
|
234
303
|
this.#anchorObserver = null;
|
|
235
304
|
this.#detachScrollListener();
|
|
236
305
|
this.#frames.cancel();
|
|
237
|
-
this.#intersectionStates.clear();
|
|
238
306
|
this.#activeSectionId = "";
|
|
239
307
|
this.#rootElement = null;
|
|
240
308
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Re-initializes the observer if the offset or rootMargin values change dynamically.
|
|
243
|
-
*/
|
|
244
309
|
offsetValueChanged() {
|
|
245
310
|
if (!this.#isConnected) return;
|
|
246
311
|
this.#initializeObserver();
|
|
@@ -254,50 +319,16 @@ var ScrollspyController = class extends Controller {
|
|
|
254
319
|
if (!this.#isConnected) return;
|
|
255
320
|
this.#initializeObserver();
|
|
256
321
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Re-syncs the observation set when a Turbo Stream/morph swaps the table of
|
|
259
|
-
* contents. Stimulus fires these before `connect()` for the links already in
|
|
260
|
-
* the markup, hence the guard: the initial observer is built exactly once, by
|
|
261
|
-
* `connect()`.
|
|
262
|
-
*/
|
|
263
322
|
linkTargetConnected() {
|
|
264
323
|
if (this.#isConnected) this.#scheduleResync();
|
|
265
324
|
}
|
|
266
325
|
linkTargetDisconnected() {
|
|
267
326
|
if (this.#isConnected) this.#scheduleResync();
|
|
268
327
|
}
|
|
269
|
-
/**
|
|
270
|
-
* Queues **one** observation rebuild for the current mutation batch.
|
|
271
|
-
*
|
|
272
|
-
* A single Turbo morph can append one link, drop another, and rewrite a
|
|
273
|
-
* third's `href`, and those arrive through two independent channels —
|
|
274
|
-
* Stimulus's target callbacks and {@link #anchorObserver}. Each channel just
|
|
275
|
-
* raises the flag and queues a drain; the first drain to run does the work and
|
|
276
|
-
* clears it, so every later drain in the same batch finds nothing to do and
|
|
277
|
-
* the set is rebuilt once instead of three times. `disconnect()` clears the
|
|
278
|
-
* same flag, which is how a queued rebuild is dropped rather than run against
|
|
279
|
-
* a detached controller.
|
|
280
|
-
*/
|
|
281
328
|
#scheduleResync() {
|
|
282
|
-
this.#
|
|
283
|
-
|
|
329
|
+
this.#resyncInputsChanged = true;
|
|
330
|
+
this.#resync.schedule();
|
|
284
331
|
}
|
|
285
|
-
#drainResync = () => {
|
|
286
|
-
if (!this.#resyncQueued) return;
|
|
287
|
-
this.#resyncQueued = false;
|
|
288
|
-
this.#initializeObserver();
|
|
289
|
-
};
|
|
290
|
-
/**
|
|
291
|
-
* Watches the link targets' anchor attributes so an in-place rewrite re-syncs.
|
|
292
|
-
*
|
|
293
|
-
* A Turbo 8 morph keeps the element **and** its `data-*-target` marker and
|
|
294
|
-
* only rewrites attributes, so Stimulus fires no target callback: without
|
|
295
|
-
* this, a link re-pointed from `#intro` to `#faq` would keep the controller
|
|
296
|
-
* observing `#intro` for the rest of the page's life. The filter is exactly
|
|
297
|
-
* `href` and `data-href`; guarding on `MutationObserver` keeps the
|
|
298
|
-
* controller usable where the API is absent, matching `IntersectionWatcher`'s
|
|
299
|
-
* own support guard.
|
|
300
|
-
*/
|
|
301
332
|
#observeAnchorAttributes() {
|
|
302
333
|
if (typeof MutationObserver !== "undefined") {
|
|
303
334
|
this.#anchorObserver = new MutationObserver(this.#onAnchorMutation);
|
|
@@ -308,12 +339,6 @@ var ScrollspyController = class extends Controller {
|
|
|
308
339
|
});
|
|
309
340
|
}
|
|
310
341
|
}
|
|
311
|
-
/**
|
|
312
|
-
* Rebuilds only for a rewrite on a *current* link target. The observer is
|
|
313
|
-
* scoped to this controller's element, but that subtree also holds links the
|
|
314
|
-
* author never marked as targets (a "back to top" anchor, a nested nav), and
|
|
315
|
-
* those anchor nothing here.
|
|
316
|
-
*/
|
|
317
342
|
#onAnchorMutation = (records) => {
|
|
318
343
|
const links = this.linkTargets;
|
|
319
344
|
for (const record of records) {
|
|
@@ -322,20 +347,16 @@ var ScrollspyController = class extends Controller {
|
|
|
322
347
|
return;
|
|
323
348
|
}
|
|
324
349
|
};
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
* into the destination; the URL fragment is deliberately not touched.
|
|
333
|
-
*/
|
|
334
|
-
scrollTo(event) {
|
|
335
|
-
const link = event.currentTarget;
|
|
350
|
+
scrollTo(source) {
|
|
351
|
+
const { event, host, origin } = actionSource(source);
|
|
352
|
+
const link = host?.closest(targetSelector(this.identifier, "link"));
|
|
353
|
+
if (!link || !this.linkTargets.includes(link)) return;
|
|
354
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
355
|
+
return;
|
|
356
|
+
const moveFocus = event !== null || this.element.contains(document.activeElement);
|
|
336
357
|
const id = this.#getAnchorId(link);
|
|
337
358
|
if (!id) return;
|
|
338
|
-
event
|
|
359
|
+
event?.preventDefault();
|
|
339
360
|
const targetElement = document.getElementById(id);
|
|
340
361
|
if (!targetElement) return;
|
|
341
362
|
const behavior = prefersReducedMotion() ? "instant" : "smooth";
|
|
@@ -350,69 +371,22 @@ var ScrollspyController = class extends Controller {
|
|
|
350
371
|
const scrollPosition = scrollOffset(window) + targetRect.top - offset;
|
|
351
372
|
window.scrollTo({ top: scrollPosition, behavior });
|
|
352
373
|
}
|
|
353
|
-
if (this.focusSectionValue) this.#focusSection(targetElement);
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Moves the sequential focus starting point into the section `scrollTo` just
|
|
357
|
-
* jumped to, so the next Tab continues *inside* the destination instead of
|
|
358
|
-
* resuming in the table of contents (`preventDefault()` alone would leave the
|
|
359
|
-
* starting point on the link). Opt-in through `focusSection`, because moving
|
|
360
|
-
* focus is a decision only the consuming page can make.
|
|
361
|
-
*
|
|
362
|
-
* `tabindex="-1"` is established only when the section is not already
|
|
363
|
-
* focusable and is never removed, so an author-owned tabindex is left alone.
|
|
364
|
-
* `preventScroll` keeps the focus call from cancelling the smooth scroll
|
|
365
|
-
* started just above.
|
|
366
|
-
*/
|
|
374
|
+
if (this.focusSectionValue && moveFocus) this.#focusSection(targetElement);
|
|
375
|
+
}
|
|
367
376
|
#focusSection(section) {
|
|
368
377
|
if (!section.hasAttribute("tabindex")) section.setAttribute("tabindex", "-1");
|
|
369
378
|
section.focus({ preventScroll: true });
|
|
370
379
|
}
|
|
371
|
-
/**
|
|
372
|
-
* The cached scroll root, re-resolved when the cached element has left the
|
|
373
|
-
* document (a Turbo morph replaced the container) so `scrollTo` never
|
|
374
|
-
* scrolls a detached node.
|
|
375
|
-
*/
|
|
376
380
|
#scrollRoot() {
|
|
377
381
|
if (this.#rootElement && !this.#rootElement.isConnected) {
|
|
378
382
|
this.#rootElement = this.#queryRootElement();
|
|
379
383
|
}
|
|
380
384
|
return this.#rootElement;
|
|
381
385
|
}
|
|
382
|
-
/**
|
|
383
|
-
* Resolves `rootSelector` to a scrollable element.
|
|
384
|
-
*
|
|
385
|
-
* @returns The container, or `null` meaning "spy the viewport" when the
|
|
386
|
-
* declaration is empty, matches nothing, or matches a non-HTML element. A typo
|
|
387
|
-
* reads as empty: the declaration is validated once when it changes, so a
|
|
388
|
-
* selector that cannot be parsed degrades to viewport spying rather than
|
|
389
|
-
* leaving the controller inert.
|
|
390
|
-
*/
|
|
391
386
|
#queryRootElement() {
|
|
392
387
|
return resolveScrollContainer(this.#rootSelector);
|
|
393
388
|
}
|
|
394
|
-
/**
|
|
395
|
-
* Re-evaluates once per frame while the reader scrolls.
|
|
396
|
-
*
|
|
397
|
-
* `IntersectionObserver` reports **threshold crossings**, not positions, so a
|
|
398
|
-
* reader moving inside one long section — or across a gap wider than the
|
|
399
|
-
* observation band — produces no batch at all and the highlight would stay
|
|
400
|
-
* frozen at whatever the last crossing decided. That would make the current
|
|
401
|
-
* location depend on the *route* to a position instead of the position: a
|
|
402
|
-
* stepwise scroll crosses thresholds an instant jump to the same offset never
|
|
403
|
-
* does. Listening to the scroll source closes the gap, and because both paths
|
|
404
|
-
* end in {@link #evaluateActiveSection} — which measures section rects and the
|
|
405
|
-
* root's top edge at that instant — they converge on the same answer.
|
|
406
|
-
*/
|
|
407
389
|
#onScroll = () => this.#frames.schedule(() => this.#evaluateActiveSection());
|
|
408
|
-
/**
|
|
409
|
-
* Points the `scroll` listener at whatever the reader actually scrolls: the
|
|
410
|
-
* resolved root, or the window when the viewport is spied.
|
|
411
|
-
*
|
|
412
|
-
* The detach is unconditional, so rebuilding the observation set (a Value
|
|
413
|
-
* change, a morph) **moves** the listener rather than stacking a second one on
|
|
414
|
-
* a container the reader has stopped scrolling.
|
|
415
|
-
*/
|
|
416
390
|
#syncScrollListener() {
|
|
417
391
|
this.#detachScrollListener();
|
|
418
392
|
this.#scrollSource = this.#rootElement ?? window;
|
|
@@ -422,13 +396,11 @@ var ScrollspyController = class extends Controller {
|
|
|
422
396
|
this.#scrollSource?.removeEventListener("scroll", this.#onScroll);
|
|
423
397
|
this.#scrollSource = null;
|
|
424
398
|
}
|
|
425
|
-
|
|
426
|
-
* @stimeoRuntimeOnly `rootMargin` wires the observer this call installs; the active section it
|
|
427
|
-
* republishes is the one already recorded.
|
|
428
|
-
*/
|
|
429
|
-
#initializeObserver() {
|
|
399
|
+
#initializeObserver(retainStates = false) {
|
|
430
400
|
this.#watcher.stop();
|
|
431
|
-
this.#intersectionStates
|
|
401
|
+
const previousStates = retainStates ? this.#intersectionStates : null;
|
|
402
|
+
const previousRoot = this.#rootElement;
|
|
403
|
+
this.#intersectionStates = /* @__PURE__ */ new Map();
|
|
432
404
|
this.#activeSectionId = this.#activeSectionId || this.#activeIdFromDom();
|
|
433
405
|
this.#rootElement = this.#queryRootElement();
|
|
434
406
|
this.#syncScrollListener();
|
|
@@ -439,13 +411,18 @@ var ScrollspyController = class extends Controller {
|
|
|
439
411
|
const id = this.#getAnchorId(link);
|
|
440
412
|
if (!id) continue;
|
|
441
413
|
const section = document.getElementById(id);
|
|
442
|
-
if (section && !sections.includes(section))
|
|
414
|
+
if (section && !sections.includes(section)) {
|
|
415
|
+
sections.push(section);
|
|
416
|
+
const previous = previousStates?.get(id);
|
|
417
|
+
if (previous?.element === section && previousRoot === this.#rootElement) {
|
|
418
|
+
this.#intersectionStates.set(id, previous);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
443
421
|
}
|
|
444
422
|
this.#watcher.start(sections, {
|
|
445
423
|
root: this.#rootElement,
|
|
446
424
|
rootMargin: margin,
|
|
447
425
|
threshold: [0, 0.2, 0.4, 0.6, 0.8, 1]
|
|
448
|
-
// Multiple thresholds handle large sections safely
|
|
449
426
|
});
|
|
450
427
|
this.#syncActiveStates(false);
|
|
451
428
|
}
|
|
@@ -462,18 +439,6 @@ var ScrollspyController = class extends Controller {
|
|
|
462
439
|
}
|
|
463
440
|
this.#evaluateActiveSection();
|
|
464
441
|
};
|
|
465
|
-
/**
|
|
466
|
-
* Picks the section closest to the trigger line and publishes it.
|
|
467
|
-
*
|
|
468
|
-
* Every coordinate is read **now**: the section rects *and* the root's top
|
|
469
|
-
* edge share one measurement instant. Comparing an entry's recorded
|
|
470
|
-
* `boundingClientRect.top` (captured whenever that section last crossed a
|
|
471
|
-
* threshold) against a freshly computed trigger line mixes two moments in
|
|
472
|
-
* time, which would make the result depend on how the reader arrived at a
|
|
473
|
-
* position — a smooth scroll and an instant jump to the same offset disagree.
|
|
474
|
-
*
|
|
475
|
-
* @stimeoRenderRoot
|
|
476
|
-
*/
|
|
477
442
|
#evaluateActiveSection() {
|
|
478
443
|
const rootEl = this.#scrollRoot();
|
|
479
444
|
const triggerLine = (rootEl ? rootEl.getBoundingClientRect().top : 0) + this.#offset;
|
|
@@ -500,19 +465,6 @@ var ScrollspyController = class extends Controller {
|
|
|
500
465
|
this.#syncActiveStates(true);
|
|
501
466
|
}
|
|
502
467
|
}
|
|
503
|
-
/**
|
|
504
|
-
* Writes `aria-current` across the current link set, optionally announcing.
|
|
505
|
-
*
|
|
506
|
-
* The two halves are separate because they answer different questions.
|
|
507
|
-
* *Attributes* must be idempotently re-established whenever the link set
|
|
508
|
-
* changes, even though the reader has not moved — otherwise a link that
|
|
509
|
-
* appears (or is re-anchored) while its section is already current never gets
|
|
510
|
-
* marked. *The `change` event* announces that the reader moved, so it fires
|
|
511
|
-
* only from the evaluation path; re-publishing over a new link set is not
|
|
512
|
-
* news, and dispatching there would make a rebuild look like navigation.
|
|
513
|
-
*
|
|
514
|
-
* @param announce Whether this sync represents a change of current section.
|
|
515
|
-
*/
|
|
516
468
|
#syncActiveStates(announce) {
|
|
517
469
|
const activeLinks = this.linkTargets.filter(
|
|
518
470
|
(link) => this.#getAnchorId(link) === this.#activeSectionId
|
|
@@ -530,11 +482,6 @@ var ScrollspyController = class extends Controller {
|
|
|
530
482
|
this.dispatch("change", { detail: { id: this.#activeSectionId, link: primaryLink } });
|
|
531
483
|
}
|
|
532
484
|
}
|
|
533
|
-
/**
|
|
534
|
-
* The current location already encoded in the DOM, i.e. the section anchored
|
|
535
|
-
* by the first link carrying this controller's `aria-current="location"`.
|
|
536
|
-
* Empty when no link claims it (a genuinely fresh render).
|
|
537
|
-
*/
|
|
538
485
|
#activeIdFromDom() {
|
|
539
486
|
for (const link of this.linkTargets) {
|
|
540
487
|
if (link.getAttribute("aria-current") !== "location") continue;
|
|
@@ -543,31 +490,23 @@ var ScrollspyController = class extends Controller {
|
|
|
543
490
|
}
|
|
544
491
|
return "";
|
|
545
492
|
}
|
|
546
|
-
/**
|
|
547
|
-
* The section id a link anchors, resolved in a fixed order:
|
|
548
|
-
*
|
|
549
|
-
* 1. `href`, when it is a non-empty same-document fragment — the contract.
|
|
550
|
-
* 2. otherwise `data-href`'s fragment — the fallback for a link whose `href`
|
|
551
|
-
* must stay a real URL (a server-rendered permalink) or that is not an
|
|
552
|
-
* `<a>` at all.
|
|
553
|
-
* 3. otherwise `null`: the link anchors nothing here and is not observed.
|
|
554
|
-
*
|
|
555
|
-
* Step 2 is reached whenever `href` yields nothing usable — absent, `"#"`, or
|
|
556
|
-
* a real URL — which is the whole point of the fallback: `href="/guide/usage"`
|
|
557
|
-
* with `data-href="#usage"` is a permalink that also spies, and picking the
|
|
558
|
-
* first *present* attribute instead of the first *usable* one would silently
|
|
559
|
-
* exclude exactly that markup. When both are valid fragments `href` wins.
|
|
560
|
-
*/
|
|
561
493
|
#getAnchorId(link) {
|
|
562
494
|
return this.#fragmentId(link.getAttribute("href")) ?? this.#fragmentId(link.getAttribute("data-href"));
|
|
563
495
|
}
|
|
564
|
-
/** The id in a `#fragment` value; `null` for absent, empty, or non-fragment. */
|
|
565
496
|
#fragmentId(value) {
|
|
566
497
|
if (!value?.startsWith("#")) return null;
|
|
567
498
|
return value.substring(1) || null;
|
|
568
499
|
}
|
|
500
|
+
get #safeOffset() {
|
|
501
|
+
return this.#numbers.read(
|
|
502
|
+
this,
|
|
503
|
+
"offset",
|
|
504
|
+
this.offsetValue,
|
|
505
|
+
_ScrollspyController.values.offset.default,
|
|
506
|
+
_ScrollspyController.valueConstraints.offset
|
|
507
|
+
);
|
|
508
|
+
}
|
|
569
509
|
};
|
|
570
510
|
|
|
571
511
|
export { ScrollspyController };
|
|
572
|
-
//# sourceMappingURL=scrollspy_controller.js.map
|
|
573
512
|
//# sourceMappingURL=scrollspy_controller.js.map
|