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,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,124 @@ 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
|
+
get pending() {
|
|
161
|
+
return this.#queued;
|
|
162
|
+
}
|
|
163
|
+
schedule() {
|
|
164
|
+
if (!this.#active || this.#queued) return;
|
|
165
|
+
this.#queued = true;
|
|
166
|
+
const generation = this.#generation;
|
|
167
|
+
queueMicrotask(() => {
|
|
168
|
+
if (generation !== this.#generation || !this.#queued || !this.#active) return;
|
|
169
|
+
this.#queued = false;
|
|
170
|
+
this.#run();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
var MorphRenderWatcher = class {
|
|
176
|
+
#listeners = new ListenerSet();
|
|
177
|
+
#pass;
|
|
178
|
+
#element = null;
|
|
179
|
+
#onMorph = () => this.#pass.schedule();
|
|
180
|
+
constructor(run) {
|
|
181
|
+
this.#pass = new MicrotaskCoalescer(run);
|
|
182
|
+
}
|
|
183
|
+
observe(element) {
|
|
184
|
+
if (this.#element === element) return;
|
|
185
|
+
this.disconnect();
|
|
186
|
+
this.#element = element;
|
|
187
|
+
this.#pass.activate();
|
|
188
|
+
this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
|
|
189
|
+
}
|
|
190
|
+
schedule() {
|
|
191
|
+
this.#pass.schedule();
|
|
192
|
+
}
|
|
193
|
+
get pending() {
|
|
194
|
+
return this.#pass.pending;
|
|
195
|
+
}
|
|
196
|
+
disconnect() {
|
|
197
|
+
this.#listeners.dispose();
|
|
198
|
+
this.#pass.cancel();
|
|
199
|
+
this.#element = null;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
var NUMBER_BOUNDS = {
|
|
204
|
+
finite: { finite: true }};
|
|
205
|
+
function matchesNumberBounds(value, bounds) {
|
|
206
|
+
if (!Number.isFinite(value)) {
|
|
207
|
+
const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
|
|
208
|
+
if (direction === null) return false;
|
|
209
|
+
if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
|
|
210
|
+
}
|
|
211
|
+
if (bounds.min !== void 0 && value < bounds.min) return false;
|
|
212
|
+
if (bounds.max !== void 0 && value > bounds.max) return false;
|
|
213
|
+
if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
|
|
214
|
+
if (bounds.integer && !Number.isInteger(value)) return false;
|
|
215
|
+
if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function readNumber(raw, fallback, bounds) {
|
|
220
|
+
return matchesNumberBounds(raw, bounds) ? raw : fallback;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
var NumberValueReader = class {
|
|
224
|
+
#lastRejected = /* @__PURE__ */ new Map();
|
|
225
|
+
read(owner, name, raw, fallback, bounds) {
|
|
226
|
+
const resolved = readNumber(raw, fallback, bounds);
|
|
227
|
+
if (matchesNumberBounds(raw, bounds)) {
|
|
228
|
+
this.#lastRejected.delete(name);
|
|
229
|
+
return resolved;
|
|
230
|
+
}
|
|
231
|
+
const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
|
|
232
|
+
const literal = owner.element.getAttribute(attribute);
|
|
233
|
+
if (literal === null) {
|
|
234
|
+
this.#lastRejected.delete(name);
|
|
235
|
+
return resolved;
|
|
236
|
+
}
|
|
237
|
+
if (this.#lastRejected.get(name) !== literal) {
|
|
238
|
+
this.#lastRejected.set(name, literal);
|
|
239
|
+
console.warn(
|
|
240
|
+
`Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return resolved;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
157
247
|
function prefersReducedMotion() {
|
|
158
248
|
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
159
249
|
}
|
|
160
250
|
|
|
161
|
-
// src/utils/scroll_source.ts
|
|
162
251
|
function resolveScrollContainer(selector) {
|
|
163
252
|
const match = selector ? document.querySelector(selector) : null;
|
|
164
253
|
return match instanceof HTMLElement ? match : null;
|
|
@@ -167,9 +256,13 @@ function scrollOffset(source) {
|
|
|
167
256
|
return source === window ? window.scrollY ?? window.pageYOffset ?? 0 : source.scrollTop;
|
|
168
257
|
}
|
|
169
258
|
|
|
170
|
-
|
|
259
|
+
function targetSelector(identifier, name) {
|
|
260
|
+
return `[data-${identifier}-target~="${name}"]`;
|
|
261
|
+
}
|
|
262
|
+
|
|
171
263
|
var ANCHOR_ATTRIBUTES = ["href", "data-href"];
|
|
172
|
-
var ScrollspyController = class extends Controller {
|
|
264
|
+
var ScrollspyController = class _ScrollspyController extends Controller {
|
|
265
|
+
#numbers = new NumberValueReader();
|
|
173
266
|
static targets = ["link"];
|
|
174
267
|
static values = {
|
|
175
268
|
offset: { type: Number, default: 0 },
|
|
@@ -177,70 +270,48 @@ var ScrollspyController = class extends Controller {
|
|
|
177
270
|
rootSelector: { type: String, default: "" },
|
|
178
271
|
focusSection: { type: Boolean, default: false }
|
|
179
272
|
};
|
|
273
|
+
static valueConstraints = {
|
|
274
|
+
offset: NUMBER_BOUNDS.finite
|
|
275
|
+
};
|
|
180
276
|
static actions = ["scrollTo"];
|
|
181
277
|
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
278
|
get #offset() {
|
|
189
|
-
return
|
|
279
|
+
return this.#safeOffset;
|
|
190
280
|
}
|
|
191
|
-
/** Shared IO plumbing (support guard, active guard, teardown). */
|
|
192
281
|
#watcher = new IntersectionWatcher((entries) => this.#onIntersection(entries));
|
|
193
282
|
#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
283
|
#intersectionStates = /* @__PURE__ */ new Map();
|
|
201
|
-
/** Current active section ID; comparing it suppresses duplicate `change` events. */
|
|
202
284
|
#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
285
|
#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
|
-
#scrollSource = null;
|
|
216
|
-
/** Coalesces a scroll burst into one measurement per frame. */
|
|
286
|
+
#scrollListeners = new ListenerSet();
|
|
217
287
|
#frames = new FrameCoalescer();
|
|
218
|
-
/** The validated `rootSelector`, or `""` when the declaration cannot be parsed. */
|
|
219
288
|
#rootSelector = "";
|
|
220
|
-
/** Watches the link targets' anchor attributes for an in-place morph rewrite. */
|
|
221
289
|
#anchorObserver = null;
|
|
222
|
-
|
|
223
|
-
#
|
|
290
|
+
#resyncInputsChanged = false;
|
|
291
|
+
#resync = new MorphRenderWatcher(() => {
|
|
292
|
+
const retainStates = !this.#resyncInputsChanged;
|
|
293
|
+
this.#resyncInputsChanged = false;
|
|
294
|
+
this.#initializeObserver(retainStates);
|
|
295
|
+
this.#evaluateActiveSection();
|
|
296
|
+
});
|
|
224
297
|
connect() {
|
|
298
|
+
this.#resync.observe(this.element);
|
|
225
299
|
this.#isConnected = true;
|
|
226
300
|
this.#observeAnchorAttributes();
|
|
227
301
|
this.#initializeObserver();
|
|
228
302
|
}
|
|
229
303
|
disconnect() {
|
|
230
304
|
this.#isConnected = false;
|
|
231
|
-
this.#
|
|
305
|
+
this.#resync.disconnect();
|
|
306
|
+
this.#resyncInputsChanged = false;
|
|
232
307
|
this.#watcher.stop();
|
|
233
308
|
this.#anchorObserver?.disconnect();
|
|
234
309
|
this.#anchorObserver = null;
|
|
235
310
|
this.#detachScrollListener();
|
|
236
311
|
this.#frames.cancel();
|
|
237
|
-
this.#intersectionStates.clear();
|
|
238
312
|
this.#activeSectionId = "";
|
|
239
313
|
this.#rootElement = null;
|
|
240
314
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Re-initializes the observer if the offset or rootMargin values change dynamically.
|
|
243
|
-
*/
|
|
244
315
|
offsetValueChanged() {
|
|
245
316
|
if (!this.#isConnected) return;
|
|
246
317
|
this.#initializeObserver();
|
|
@@ -254,50 +325,16 @@ var ScrollspyController = class extends Controller {
|
|
|
254
325
|
if (!this.#isConnected) return;
|
|
255
326
|
this.#initializeObserver();
|
|
256
327
|
}
|
|
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
328
|
linkTargetConnected() {
|
|
264
329
|
if (this.#isConnected) this.#scheduleResync();
|
|
265
330
|
}
|
|
266
331
|
linkTargetDisconnected() {
|
|
267
332
|
if (this.#isConnected) this.#scheduleResync();
|
|
268
333
|
}
|
|
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
334
|
#scheduleResync() {
|
|
282
|
-
this.#
|
|
283
|
-
|
|
335
|
+
this.#resyncInputsChanged = true;
|
|
336
|
+
this.#resync.schedule();
|
|
284
337
|
}
|
|
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
338
|
#observeAnchorAttributes() {
|
|
302
339
|
if (typeof MutationObserver !== "undefined") {
|
|
303
340
|
this.#anchorObserver = new MutationObserver(this.#onAnchorMutation);
|
|
@@ -308,12 +345,6 @@ var ScrollspyController = class extends Controller {
|
|
|
308
345
|
});
|
|
309
346
|
}
|
|
310
347
|
}
|
|
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
348
|
#onAnchorMutation = (records) => {
|
|
318
349
|
const links = this.linkTargets;
|
|
319
350
|
for (const record of records) {
|
|
@@ -322,20 +353,16 @@ var ScrollspyController = class extends Controller {
|
|
|
322
353
|
return;
|
|
323
354
|
}
|
|
324
355
|
};
|
|
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;
|
|
356
|
+
scrollTo(source) {
|
|
357
|
+
const { event, host, origin } = actionSource(source);
|
|
358
|
+
const link = host?.closest(targetSelector(this.identifier, "link"));
|
|
359
|
+
if (!link || !this.linkTargets.includes(link)) return;
|
|
360
|
+
if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
|
|
361
|
+
return;
|
|
362
|
+
const moveFocus = event !== null || this.element.contains(document.activeElement);
|
|
336
363
|
const id = this.#getAnchorId(link);
|
|
337
364
|
if (!id) return;
|
|
338
|
-
event
|
|
365
|
+
event?.preventDefault();
|
|
339
366
|
const targetElement = document.getElementById(id);
|
|
340
367
|
if (!targetElement) return;
|
|
341
368
|
const behavior = prefersReducedMotion() ? "instant" : "smooth";
|
|
@@ -350,85 +377,36 @@ var ScrollspyController = class extends Controller {
|
|
|
350
377
|
const scrollPosition = scrollOffset(window) + targetRect.top - offset;
|
|
351
378
|
window.scrollTo({ top: scrollPosition, behavior });
|
|
352
379
|
}
|
|
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
|
-
*/
|
|
380
|
+
if (this.focusSectionValue && moveFocus) this.#focusSection(targetElement);
|
|
381
|
+
}
|
|
367
382
|
#focusSection(section) {
|
|
368
383
|
if (!section.hasAttribute("tabindex")) section.setAttribute("tabindex", "-1");
|
|
369
384
|
section.focus({ preventScroll: true });
|
|
370
385
|
}
|
|
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
386
|
#scrollRoot() {
|
|
377
387
|
if (this.#rootElement && !this.#rootElement.isConnected) {
|
|
378
388
|
this.#rootElement = this.#queryRootElement();
|
|
379
389
|
}
|
|
380
390
|
return this.#rootElement;
|
|
381
391
|
}
|
|
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
392
|
#queryRootElement() {
|
|
392
393
|
return resolveScrollContainer(this.#rootSelector);
|
|
393
394
|
}
|
|
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
395
|
#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
396
|
#syncScrollListener() {
|
|
417
397
|
this.#detachScrollListener();
|
|
418
|
-
this.#
|
|
419
|
-
|
|
398
|
+
this.#scrollListeners.add(this.#rootElement ?? window, "scroll", this.#onScroll, {
|
|
399
|
+
passive: true
|
|
400
|
+
});
|
|
420
401
|
}
|
|
421
402
|
#detachScrollListener() {
|
|
422
|
-
this.#
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* @stimeoRuntimeOnly `rootMargin` wires the observer this call installs; the active section it
|
|
427
|
-
* republishes is the one already recorded.
|
|
428
|
-
*/
|
|
429
|
-
#initializeObserver() {
|
|
403
|
+
this.#scrollListeners.dispose();
|
|
404
|
+
}
|
|
405
|
+
#initializeObserver(retainStates = false) {
|
|
430
406
|
this.#watcher.stop();
|
|
431
|
-
this.#intersectionStates
|
|
407
|
+
const previousStates = retainStates ? this.#intersectionStates : null;
|
|
408
|
+
const previousRoot = this.#rootElement;
|
|
409
|
+
this.#intersectionStates = /* @__PURE__ */ new Map();
|
|
432
410
|
this.#activeSectionId = this.#activeSectionId || this.#activeIdFromDom();
|
|
433
411
|
this.#rootElement = this.#queryRootElement();
|
|
434
412
|
this.#syncScrollListener();
|
|
@@ -439,13 +417,18 @@ var ScrollspyController = class extends Controller {
|
|
|
439
417
|
const id = this.#getAnchorId(link);
|
|
440
418
|
if (!id) continue;
|
|
441
419
|
const section = document.getElementById(id);
|
|
442
|
-
if (section && !sections.includes(section))
|
|
420
|
+
if (section && !sections.includes(section)) {
|
|
421
|
+
sections.push(section);
|
|
422
|
+
const previous = previousStates?.get(id);
|
|
423
|
+
if (previous?.element === section && previousRoot === this.#rootElement) {
|
|
424
|
+
this.#intersectionStates.set(id, previous);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
443
427
|
}
|
|
444
428
|
this.#watcher.start(sections, {
|
|
445
429
|
root: this.#rootElement,
|
|
446
430
|
rootMargin: margin,
|
|
447
431
|
threshold: [0, 0.2, 0.4, 0.6, 0.8, 1]
|
|
448
|
-
// Multiple thresholds handle large sections safely
|
|
449
432
|
});
|
|
450
433
|
this.#syncActiveStates(false);
|
|
451
434
|
}
|
|
@@ -462,18 +445,6 @@ var ScrollspyController = class extends Controller {
|
|
|
462
445
|
}
|
|
463
446
|
this.#evaluateActiveSection();
|
|
464
447
|
};
|
|
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
448
|
#evaluateActiveSection() {
|
|
478
449
|
const rootEl = this.#scrollRoot();
|
|
479
450
|
const triggerLine = (rootEl ? rootEl.getBoundingClientRect().top : 0) + this.#offset;
|
|
@@ -500,19 +471,6 @@ var ScrollspyController = class extends Controller {
|
|
|
500
471
|
this.#syncActiveStates(true);
|
|
501
472
|
}
|
|
502
473
|
}
|
|
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
474
|
#syncActiveStates(announce) {
|
|
517
475
|
const activeLinks = this.linkTargets.filter(
|
|
518
476
|
(link) => this.#getAnchorId(link) === this.#activeSectionId
|
|
@@ -530,11 +488,6 @@ var ScrollspyController = class extends Controller {
|
|
|
530
488
|
this.dispatch("change", { detail: { id: this.#activeSectionId, link: primaryLink } });
|
|
531
489
|
}
|
|
532
490
|
}
|
|
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
491
|
#activeIdFromDom() {
|
|
539
492
|
for (const link of this.linkTargets) {
|
|
540
493
|
if (link.getAttribute("aria-current") !== "location") continue;
|
|
@@ -543,31 +496,23 @@ var ScrollspyController = class extends Controller {
|
|
|
543
496
|
}
|
|
544
497
|
return "";
|
|
545
498
|
}
|
|
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
499
|
#getAnchorId(link) {
|
|
562
500
|
return this.#fragmentId(link.getAttribute("href")) ?? this.#fragmentId(link.getAttribute("data-href"));
|
|
563
501
|
}
|
|
564
|
-
/** The id in a `#fragment` value; `null` for absent, empty, or non-fragment. */
|
|
565
502
|
#fragmentId(value) {
|
|
566
503
|
if (!value?.startsWith("#")) return null;
|
|
567
504
|
return value.substring(1) || null;
|
|
568
505
|
}
|
|
506
|
+
get #safeOffset() {
|
|
507
|
+
return this.#numbers.read(
|
|
508
|
+
this,
|
|
509
|
+
"offset",
|
|
510
|
+
this.offsetValue,
|
|
511
|
+
_ScrollspyController.values.offset.default,
|
|
512
|
+
_ScrollspyController.valueConstraints.offset
|
|
513
|
+
);
|
|
514
|
+
}
|
|
569
515
|
};
|
|
570
516
|
|
|
571
517
|
export { ScrollspyController };
|
|
572
|
-
//# sourceMappingURL=scrollspy_controller.js.map
|
|
573
518
|
//# sourceMappingURL=scrollspy_controller.js.map
|