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.
Files changed (328) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +8 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +363 -436
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +4 -2
  7. package/dist/controllers/accordion_controller.js +166 -55
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +35 -5
  13. package/dist/controllers/announcer_controller.js +135 -198
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +271 -127
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +171 -105
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.js +0 -130
  23. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  24. package/dist/controllers/bulk_select_controller.d.ts +18 -0
  25. package/dist/controllers/bulk_select_controller.js +262 -52
  26. package/dist/controllers/bulk_select_controller.js.map +1 -1
  27. package/dist/controllers/calendar_controller.d.ts +24 -5
  28. package/dist/controllers/calendar_controller.js +175 -209
  29. package/dist/controllers/calendar_controller.js.map +1 -1
  30. package/dist/controllers/carousel_controller.d.ts +34 -7
  31. package/dist/controllers/carousel_controller.js +461 -399
  32. package/dist/controllers/carousel_controller.js.map +1 -1
  33. package/dist/controllers/character_counter_controller.d.ts +17 -1
  34. package/dist/controllers/character_counter_controller.js +246 -156
  35. package/dist/controllers/character_counter_controller.js.map +1 -1
  36. package/dist/controllers/checkbox_controller.d.ts +7 -2
  37. package/dist/controllers/checkbox_controller.js +32 -74
  38. package/dist/controllers/checkbox_controller.js.map +1 -1
  39. package/dist/controllers/clipboard_controller.d.ts +16 -5
  40. package/dist/controllers/clipboard_controller.js +168 -147
  41. package/dist/controllers/clipboard_controller.js.map +1 -1
  42. package/dist/controllers/collapsible_controller.d.ts +16 -4
  43. package/dist/controllers/collapsible_controller.js +301 -120
  44. package/dist/controllers/collapsible_controller.js.map +1 -1
  45. package/dist/controllers/color_picker_controller.d.ts +18 -6
  46. package/dist/controllers/color_picker_controller.js +149 -139
  47. package/dist/controllers/color_picker_controller.js.map +1 -1
  48. package/dist/controllers/combobox_controller.d.ts +23 -5
  49. package/dist/controllers/combobox_controller.js +360 -133
  50. package/dist/controllers/combobox_controller.js.map +1 -1
  51. package/dist/controllers/command_palette_controller.d.ts +45 -14
  52. package/dist/controllers/command_palette_controller.js +970 -358
  53. package/dist/controllers/command_palette_controller.js.map +1 -1
  54. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  55. package/dist/controllers/conditional_fields_controller.js +166 -124
  56. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  57. package/dist/controllers/confirm_controller.d.ts +49 -12
  58. package/dist/controllers/confirm_controller.js +939 -248
  59. package/dist/controllers/confirm_controller.js.map +1 -1
  60. package/dist/controllers/context_menu_controller.d.ts +31 -4
  61. package/dist/controllers/context_menu_controller.js +256 -128
  62. package/dist/controllers/context_menu_controller.js.map +1 -1
  63. package/dist/controllers/count_up_controller.d.ts +13 -1
  64. package/dist/controllers/count_up_controller.js +73 -21
  65. package/dist/controllers/count_up_controller.js.map +1 -1
  66. package/dist/controllers/countdown_controller.d.ts +45 -1
  67. package/dist/controllers/countdown_controller.js +183 -148
  68. package/dist/controllers/countdown_controller.js.map +1 -1
  69. package/dist/controllers/currency_input_controller.d.ts +21 -2
  70. package/dist/controllers/currency_input_controller.js +134 -158
  71. package/dist/controllers/currency_input_controller.js.map +1 -1
  72. package/dist/controllers/data_grid_controller.d.ts +15 -4
  73. package/dist/controllers/data_grid_controller.js +114 -186
  74. package/dist/controllers/data_grid_controller.js.map +1 -1
  75. package/dist/controllers/date_range_picker_controller.d.ts +30 -5
  76. package/dist/controllers/date_range_picker_controller.js +288 -206
  77. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  78. package/dist/controllers/dialog_controller.d.ts +13 -1
  79. package/dist/controllers/dialog_controller.js +858 -223
  80. package/dist/controllers/dialog_controller.js.map +1 -1
  81. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  82. package/dist/controllers/direct_upload_controller.js +73 -153
  83. package/dist/controllers/direct_upload_controller.js.map +1 -1
  84. package/dist/controllers/dirty_form_controller.js +14 -48
  85. package/dist/controllers/dirty_form_controller.js.map +1 -1
  86. package/dist/controllers/dismissible_controller.d.ts +19 -0
  87. package/dist/controllers/dismissible_controller.js +175 -21
  88. package/dist/controllers/dismissible_controller.js.map +1 -1
  89. package/dist/controllers/drawer_controller.d.ts +39 -11
  90. package/dist/controllers/drawer_controller.js +933 -341
  91. package/dist/controllers/drawer_controller.js.map +1 -1
  92. package/dist/controllers/dropdown_controller.d.ts +31 -1
  93. package/dist/controllers/dropdown_controller.js +312 -82
  94. package/dist/controllers/dropdown_controller.js.map +1 -1
  95. package/dist/controllers/editable_controller.d.ts +12 -1
  96. package/dist/controllers/editable_controller.js +158 -68
  97. package/dist/controllers/editable_controller.js.map +1 -1
  98. package/dist/controllers/empty_state_controller.js +67 -53
  99. package/dist/controllers/empty_state_controller.js.map +1 -1
  100. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  101. package/dist/controllers/file_dropzone_controller.js +311 -207
  102. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  103. package/dist/controllers/filter_controller.js +67 -39
  104. package/dist/controllers/filter_controller.js.map +1 -1
  105. package/dist/controllers/flash_controller.d.ts +31 -8
  106. package/dist/controllers/flash_controller.js +172 -314
  107. package/dist/controllers/flash_controller.js.map +1 -1
  108. package/dist/controllers/focus_controller.d.ts +12 -3
  109. package/dist/controllers/focus_controller.js +654 -240
  110. package/dist/controllers/focus_controller.js.map +1 -1
  111. package/dist/controllers/form_field_controller.js +147 -132
  112. package/dist/controllers/form_field_controller.js.map +1 -1
  113. package/dist/controllers/form_validation_controller.js +13 -86
  114. package/dist/controllers/form_validation_controller.js.map +1 -1
  115. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  116. package/dist/controllers/frame_loading_controller.js +264 -213
  117. package/dist/controllers/frame_loading_controller.js.map +1 -1
  118. package/dist/controllers/highlight_controller.d.ts +22 -2
  119. package/dist/controllers/highlight_controller.js +107 -75
  120. package/dist/controllers/highlight_controller.js.map +1 -1
  121. package/dist/controllers/hover_card_controller.d.ts +56 -3
  122. package/dist/controllers/hover_card_controller.js +309 -118
  123. package/dist/controllers/hover_card_controller.js.map +1 -1
  124. package/dist/controllers/idle_controller.d.ts +42 -1
  125. package/dist/controllers/idle_controller.js +267 -101
  126. package/dist/controllers/idle_controller.js.map +1 -1
  127. package/dist/controllers/input_mask_controller.js +71 -72
  128. package/dist/controllers/input_mask_controller.js.map +1 -1
  129. package/dist/controllers/intersection_controller.d.ts +32 -13
  130. package/dist/controllers/intersection_controller.js +147 -106
  131. package/dist/controllers/intersection_controller.js.map +1 -1
  132. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  133. package/dist/controllers/lazy_frame_controller.js +9 -65
  134. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  135. package/dist/controllers/listbox_controller.d.ts +34 -2
  136. package/dist/controllers/listbox_controller.js +306 -204
  137. package/dist/controllers/listbox_controller.js.map +1 -1
  138. package/dist/controllers/local_time_controller.d.ts +1 -1
  139. package/dist/controllers/local_time_controller.js +54 -64
  140. package/dist/controllers/local_time_controller.js.map +1 -1
  141. package/dist/controllers/masonry_controller.d.ts +11 -3
  142. package/dist/controllers/masonry_controller.js +70 -93
  143. package/dist/controllers/masonry_controller.js.map +1 -1
  144. package/dist/controllers/menu_controller.d.ts +32 -7
  145. package/dist/controllers/menu_controller.js +214 -156
  146. package/dist/controllers/menu_controller.js.map +1 -1
  147. package/dist/controllers/menubar_controller.d.ts +4 -4
  148. package/dist/controllers/menubar_controller.js +170 -329
  149. package/dist/controllers/menubar_controller.js.map +1 -1
  150. package/dist/controllers/meter_controller.d.ts +20 -0
  151. package/dist/controllers/meter_controller.js +151 -67
  152. package/dist/controllers/meter_controller.js.map +1 -1
  153. package/dist/controllers/multi_select_controller.d.ts +32 -4
  154. package/dist/controllers/multi_select_controller.js +593 -358
  155. package/dist/controllers/multi_select_controller.js.map +1 -1
  156. package/dist/controllers/navigation_menu_controller.d.ts +8 -1
  157. package/dist/controllers/navigation_menu_controller.js +124 -232
  158. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  159. package/dist/controllers/nested_form_controller.d.ts +10 -0
  160. package/dist/controllers/nested_form_controller.js +352 -182
  161. package/dist/controllers/nested_form_controller.js.map +1 -1
  162. package/dist/controllers/network_status_controller.d.ts +23 -0
  163. package/dist/controllers/network_status_controller.js +242 -54
  164. package/dist/controllers/network_status_controller.js.map +1 -1
  165. package/dist/controllers/number_input_controller.d.ts +26 -2
  166. package/dist/controllers/number_input_controller.js +320 -263
  167. package/dist/controllers/number_input_controller.js.map +1 -1
  168. package/dist/controllers/optimistic_controller.js +16 -76
  169. package/dist/controllers/optimistic_controller.js.map +1 -1
  170. package/dist/controllers/otp_controller.d.ts +20 -4
  171. package/dist/controllers/otp_controller.js +198 -193
  172. package/dist/controllers/otp_controller.js.map +1 -1
  173. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  174. package/dist/controllers/overflow_indicator_controller.js +116 -113
  175. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  176. package/dist/controllers/overflow_menu_controller.d.ts +48 -11
  177. package/dist/controllers/overflow_menu_controller.js +343 -283
  178. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  179. package/dist/controllers/pagination_controller.d.ts +35 -6
  180. package/dist/controllers/pagination_controller.js +295 -119
  181. package/dist/controllers/pagination_controller.js.map +1 -1
  182. package/dist/controllers/password_reveal_controller.d.ts +45 -8
  183. package/dist/controllers/password_reveal_controller.js +389 -121
  184. package/dist/controllers/password_reveal_controller.js.map +1 -1
  185. package/dist/controllers/password_strength_controller.d.ts +14 -5
  186. package/dist/controllers/password_strength_controller.js +99 -153
  187. package/dist/controllers/password_strength_controller.js.map +1 -1
  188. package/dist/controllers/persist_controller.d.ts +11 -4
  189. package/dist/controllers/persist_controller.js +237 -103
  190. package/dist/controllers/persist_controller.js.map +1 -1
  191. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  192. package/dist/controllers/pointer_drag_controller.js +185 -165
  193. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  194. package/dist/controllers/popover_controller.d.ts +38 -2
  195. package/dist/controllers/popover_controller.js +236 -97
  196. package/dist/controllers/popover_controller.js.map +1 -1
  197. package/dist/controllers/portal_controller.d.ts +44 -4
  198. package/dist/controllers/portal_controller.js +209 -76
  199. package/dist/controllers/portal_controller.js.map +1 -1
  200. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  201. package/dist/controllers/preview_guard_controller.js +232 -116
  202. package/dist/controllers/preview_guard_controller.js.map +1 -1
  203. package/dist/controllers/progress_controller.d.ts +11 -0
  204. package/dist/controllers/progress_controller.js +125 -56
  205. package/dist/controllers/progress_controller.js.map +1 -1
  206. package/dist/controllers/radio_group_controller.d.ts +14 -5
  207. package/dist/controllers/radio_group_controller.js +117 -137
  208. package/dist/controllers/radio_group_controller.js.map +1 -1
  209. package/dist/controllers/range_slider_controller.d.ts +43 -3
  210. package/dist/controllers/range_slider_controller.js +192 -105
  211. package/dist/controllers/range_slider_controller.js.map +1 -1
  212. package/dist/controllers/rating_controller.d.ts +20 -4
  213. package/dist/controllers/rating_controller.js +418 -207
  214. package/dist/controllers/rating_controller.js.map +1 -1
  215. package/dist/controllers/read_more_controller.d.ts +2 -0
  216. package/dist/controllers/read_more_controller.js +134 -63
  217. package/dist/controllers/read_more_controller.js.map +1 -1
  218. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  219. package/dist/controllers/reading_progress_controller.js +147 -135
  220. package/dist/controllers/reading_progress_controller.js.map +1 -1
  221. package/dist/controllers/relative_time_controller.d.ts +18 -4
  222. package/dist/controllers/relative_time_controller.js +145 -110
  223. package/dist/controllers/relative_time_controller.js.map +1 -1
  224. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  225. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  226. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  227. package/dist/controllers/resizable_controller.d.ts +28 -2
  228. package/dist/controllers/resizable_controller.js +255 -139
  229. package/dist/controllers/resizable_controller.js.map +1 -1
  230. package/dist/controllers/roving_controller.js +39 -84
  231. package/dist/controllers/roving_controller.js.map +1 -1
  232. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  233. package/dist/controllers/scroll_area_controller.js +428 -225
  234. package/dist/controllers/scroll_area_controller.js.map +1 -1
  235. package/dist/controllers/scroll_restore_controller.js +0 -53
  236. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  237. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  238. package/dist/controllers/scroll_visibility_controller.js +395 -145
  239. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  240. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  241. package/dist/controllers/scrollspy_controller.js +181 -242
  242. package/dist/controllers/scrollspy_controller.js.map +1 -1
  243. package/dist/controllers/separator_controller.d.ts +19 -4
  244. package/dist/controllers/separator_controller.js +267 -140
  245. package/dist/controllers/separator_controller.js.map +1 -1
  246. package/dist/controllers/sidebar_controller.d.ts +64 -7
  247. package/dist/controllers/sidebar_controller.js +1023 -386
  248. package/dist/controllers/sidebar_controller.js.map +1 -1
  249. package/dist/controllers/skeleton_controller.d.ts +23 -0
  250. package/dist/controllers/skeleton_controller.js +248 -97
  251. package/dist/controllers/skeleton_controller.js.map +1 -1
  252. package/dist/controllers/slider_controller.d.ts +27 -1
  253. package/dist/controllers/slider_controller.js +159 -79
  254. package/dist/controllers/slider_controller.js.map +1 -1
  255. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  256. package/dist/controllers/smart_sticky_header_controller.js +162 -63
  257. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  258. package/dist/controllers/sortable_controller.js +0 -112
  259. package/dist/controllers/sortable_controller.js.map +1 -1
  260. package/dist/controllers/spinner_controller.d.ts +39 -4
  261. package/dist/controllers/spinner_controller.js +277 -169
  262. package/dist/controllers/spinner_controller.js.map +1 -1
  263. package/dist/controllers/step_indicator_controller.d.ts +5 -0
  264. package/dist/controllers/step_indicator_controller.js +99 -46
  265. package/dist/controllers/step_indicator_controller.js.map +1 -1
  266. package/dist/controllers/stepper_controller.d.ts +12 -3
  267. package/dist/controllers/stepper_controller.js +114 -73
  268. package/dist/controllers/stepper_controller.js.map +1 -1
  269. package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
  270. package/dist/controllers/stick_to_bottom_controller.js +262 -138
  271. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  272. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  273. package/dist/controllers/sticky_observer_controller.js +214 -49
  274. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  275. package/dist/controllers/submit_once_controller.d.ts +23 -8
  276. package/dist/controllers/submit_once_controller.js +294 -205
  277. package/dist/controllers/submit_once_controller.js.map +1 -1
  278. package/dist/controllers/switch_controller.d.ts +12 -1
  279. package/dist/controllers/switch_controller.js +33 -60
  280. package/dist/controllers/switch_controller.js.map +1 -1
  281. package/dist/controllers/tabs_controller.d.ts +2 -2
  282. package/dist/controllers/tabs_controller.js +35 -29
  283. package/dist/controllers/tabs_controller.js.map +1 -1
  284. package/dist/controllers/tags_input_controller.d.ts +12 -0
  285. package/dist/controllers/tags_input_controller.js +136 -145
  286. package/dist/controllers/tags_input_controller.js.map +1 -1
  287. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  288. package/dist/controllers/textarea_autosize_controller.js +71 -58
  289. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  290. package/dist/controllers/theme_controller.d.ts +21 -9
  291. package/dist/controllers/theme_controller.js +130 -112
  292. package/dist/controllers/theme_controller.js.map +1 -1
  293. package/dist/controllers/time_picker_controller.d.ts +18 -1
  294. package/dist/controllers/time_picker_controller.js +146 -80
  295. package/dist/controllers/time_picker_controller.js.map +1 -1
  296. package/dist/controllers/toast_controller.d.ts +36 -11
  297. package/dist/controllers/toast_controller.js +186 -297
  298. package/dist/controllers/toast_controller.js.map +1 -1
  299. package/dist/controllers/toggle_group_controller.d.ts +12 -3
  300. package/dist/controllers/toggle_group_controller.js +95 -136
  301. package/dist/controllers/toggle_group_controller.js.map +1 -1
  302. package/dist/controllers/toolbar_controller.js +68 -98
  303. package/dist/controllers/toolbar_controller.js.map +1 -1
  304. package/dist/controllers/tooltip_controller.d.ts +45 -3
  305. package/dist/controllers/tooltip_controller.js +158 -124
  306. package/dist/controllers/tooltip_controller.js.map +1 -1
  307. package/dist/controllers/transition_controller.d.ts +22 -4
  308. package/dist/controllers/transition_controller.js +123 -119
  309. package/dist/controllers/transition_controller.js.map +1 -1
  310. package/dist/controllers/tree_view_controller.d.ts +21 -11
  311. package/dist/controllers/tree_view_controller.js +150 -345
  312. package/dist/controllers/tree_view_controller.js.map +1 -1
  313. package/dist/index.d.ts +8 -5
  314. package/dist/index.js +6370 -9346
  315. package/dist/index.js.map +1 -1
  316. package/dist/inspector/cli.d.ts +23 -3
  317. package/dist/inspector/cli.js +92 -37
  318. package/dist/inspector/cli.js.map +1 -1
  319. package/dist/inspector/cli_bin.js +92 -56
  320. package/dist/inspector/cli_bin.js.map +1 -1
  321. package/dist/inspector/examples.json +8 -8
  322. package/dist/inspector/manifest.json +787 -100
  323. package/dist/positioning/index.d.ts +8 -0
  324. package/dist/positioning/index.js +148 -47
  325. package/dist/positioning/index.js.map +1 -1
  326. package/package.json +6 -6
  327. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  328. 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
- // src/controllers/scrollspy_controller.ts
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
- // src/utils/reduced_motion.ts
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
- // src/controllers/scrollspy_controller.ts
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 Number.isFinite(this.offsetValue) ? this.offsetValue : 0;
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
- /** True while a coalesced observation rebuild is queued; see {@link #scheduleResync}. */
223
- #resyncQueued = false;
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.#resyncQueued = false;
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.#resyncQueued = true;
283
- queueMicrotask(this.#drainResync);
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
- * Scrolls to the section the clicked link anchors, honoring `offset` and any
327
- * nested scroll container (a plain fragment jump would scroll the window).
328
- *
329
- * Honors `prefers-reduced-motion` (WCAG 2.2 **2.3.3**) by forcing an instant
330
- * jump independently of the consumer's CSS `scroll-behavior`. With
331
- * `focusSection` enabled it also moves the sequential focus starting point
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.preventDefault();
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.clear();
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)) sections.push(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