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.
Files changed (337) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +27 -11
  3. package/dist/cable/index.d.ts +88 -31
  4. package/dist/cable/index.js +384 -449
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.d.ts +4 -2
  8. package/dist/controllers/accordion_controller.js +174 -55
  9. package/dist/controllers/accordion_controller.js.map +1 -1
  10. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  11. package/dist/controllers/alert_dialog_controller.js +903 -235
  12. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  13. package/dist/controllers/announcer_controller.d.ts +37 -5
  14. package/dist/controllers/announcer_controller.js +238 -205
  15. package/dist/controllers/announcer_controller.js.map +1 -1
  16. package/dist/controllers/aspect_ratio_controller.js +0 -10
  17. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  18. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  19. package/dist/controllers/auto_submit_controller.js +414 -145
  20. package/dist/controllers/auto_submit_controller.js.map +1 -1
  21. package/dist/controllers/avatar_controller.js +185 -107
  22. package/dist/controllers/avatar_controller.js.map +1 -1
  23. package/dist/controllers/breadcrumb_controller.js +0 -130
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +21 -3
  26. package/dist/controllers/bulk_select_controller.js +282 -58
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +38 -16
  29. package/dist/controllers/calendar_controller.js +228 -260
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +43 -12
  32. package/dist/controllers/carousel_controller.js +536 -436
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +44 -7
  35. package/dist/controllers/character_counter_controller.js +493 -207
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +38 -74
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +16 -5
  41. package/dist/controllers/clipboard_controller.js +261 -149
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +17 -5
  44. package/dist/controllers/collapsible_controller.js +304 -120
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +18 -6
  47. package/dist/controllers/color_picker_controller.js +178 -146
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +24 -6
  50. package/dist/controllers/combobox_controller.js +522 -156
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +51 -20
  53. package/dist/controllers/command_palette_controller.js +1156 -395
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +300 -171
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +980 -262
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +80 -18
  62. package/dist/controllers/context_menu_controller.js +346 -153
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +47 -3
  68. package/dist/controllers/countdown_controller.js +281 -158
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -10
  71. package/dist/controllers/currency_input_controller.js +575 -225
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +25 -12
  74. package/dist/controllers/data_grid_controller.js +248 -214
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +42 -14
  77. package/dist/controllers/date_range_picker_controller.js +338 -235
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +17 -6
  80. package/dist/controllers/dialog_controller.js +899 -237
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +25 -8
  83. package/dist/controllers/direct_upload_controller.js +97 -167
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  86. package/dist/controllers/dirty_form_controller.js +72 -69
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +196 -24
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +39 -11
  92. package/dist/controllers/drawer_controller.js +974 -355
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +31 -1
  95. package/dist/controllers/dropdown_controller.js +339 -96
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +315 -88
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +79 -58
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +63 -18
  103. package/dist/controllers/file_dropzone_controller.js +322 -211
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.js +77 -42
  106. package/dist/controllers/filter_controller.js.map +1 -1
  107. package/dist/controllers/flash_controller.d.ts +35 -12
  108. package/dist/controllers/flash_controller.js +238 -347
  109. package/dist/controllers/flash_controller.js.map +1 -1
  110. package/dist/controllers/focus_controller.d.ts +15 -6
  111. package/dist/controllers/focus_controller.js +679 -240
  112. package/dist/controllers/focus_controller.js.map +1 -1
  113. package/dist/controllers/form_field_controller.js +156 -132
  114. package/dist/controllers/form_field_controller.js.map +1 -1
  115. package/dist/controllers/form_validation_controller.d.ts +3 -3
  116. package/dist/controllers/form_validation_controller.js +31 -97
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +47 -14
  119. package/dist/controllers/frame_loading_controller.js +275 -217
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +27 -6
  122. package/dist/controllers/highlight_controller.js +107 -75
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +60 -4
  125. package/dist/controllers/hover_card_controller.js +327 -130
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +48 -6
  128. package/dist/controllers/idle_controller.js +271 -101
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +30 -13
  131. package/dist/controllers/input_mask_controller.js +483 -97
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +150 -106
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +19 -2
  137. package/dist/controllers/lazy_frame_controller.js +24 -68
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +44 -9
  140. package/dist/controllers/listbox_controller.js +523 -240
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +9 -7
  143. package/dist/controllers/local_time_controller.js +102 -77
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +80 -96
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +55 -11
  149. package/dist/controllers/menu_controller.js +302 -188
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +51 -9
  152. package/dist/controllers/menubar_controller.js +232 -345
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +33 -20
  155. package/dist/controllers/meter_controller.js +417 -87
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +48 -18
  158. package/dist/controllers/multi_select_controller.js +785 -393
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +27 -6
  161. package/dist/controllers/navigation_menu_controller.js +190 -250
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +396 -192
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +268 -62
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +50 -8
  170. package/dist/controllers/number_input_controller.js +610 -303
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.d.ts +8 -5
  173. package/dist/controllers/optimistic_controller.js +45 -87
  174. package/dist/controllers/optimistic_controller.js.map +1 -1
  175. package/dist/controllers/otp_controller.d.ts +55 -8
  176. package/dist/controllers/otp_controller.js +658 -255
  177. package/dist/controllers/otp_controller.js.map +1 -1
  178. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  179. package/dist/controllers/overflow_indicator_controller.js +122 -113
  180. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  181. package/dist/controllers/overflow_menu_controller.d.ts +58 -20
  182. package/dist/controllers/overflow_menu_controller.js +470 -295
  183. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  184. package/dist/controllers/pagination_controller.d.ts +37 -7
  185. package/dist/controllers/pagination_controller.js +332 -123
  186. package/dist/controllers/pagination_controller.js.map +1 -1
  187. package/dist/controllers/password_reveal_controller.d.ts +46 -8
  188. package/dist/controllers/password_reveal_controller.js +392 -121
  189. package/dist/controllers/password_reveal_controller.js.map +1 -1
  190. package/dist/controllers/password_strength_controller.d.ts +15 -6
  191. package/dist/controllers/password_strength_controller.js +111 -158
  192. package/dist/controllers/password_strength_controller.js.map +1 -1
  193. package/dist/controllers/persist_controller.d.ts +11 -4
  194. package/dist/controllers/persist_controller.js +165 -111
  195. package/dist/controllers/persist_controller.js.map +1 -1
  196. package/dist/controllers/pointer_drag_controller.d.ts +35 -12
  197. package/dist/controllers/pointer_drag_controller.js +434 -188
  198. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  199. package/dist/controllers/popover_controller.d.ts +41 -5
  200. package/dist/controllers/popover_controller.js +269 -113
  201. package/dist/controllers/popover_controller.js.map +1 -1
  202. package/dist/controllers/portal_controller.d.ts +44 -4
  203. package/dist/controllers/portal_controller.js +215 -76
  204. package/dist/controllers/portal_controller.js.map +1 -1
  205. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  206. package/dist/controllers/preview_guard_controller.js +243 -116
  207. package/dist/controllers/preview_guard_controller.js.map +1 -1
  208. package/dist/controllers/progress_controller.d.ts +20 -19
  209. package/dist/controllers/progress_controller.js +391 -78
  210. package/dist/controllers/progress_controller.js.map +1 -1
  211. package/dist/controllers/radio_group_controller.d.ts +21 -9
  212. package/dist/controllers/radio_group_controller.js +278 -211
  213. package/dist/controllers/radio_group_controller.js.map +1 -1
  214. package/dist/controllers/range_slider_controller.d.ts +47 -6
  215. package/dist/controllers/range_slider_controller.js +220 -113
  216. package/dist/controllers/range_slider_controller.js.map +1 -1
  217. package/dist/controllers/rating_controller.d.ts +20 -4
  218. package/dist/controllers/rating_controller.js +458 -211
  219. package/dist/controllers/rating_controller.js.map +1 -1
  220. package/dist/controllers/read_more_controller.d.ts +2 -0
  221. package/dist/controllers/read_more_controller.js +138 -63
  222. package/dist/controllers/read_more_controller.js.map +1 -1
  223. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  224. package/dist/controllers/reading_progress_controller.js +166 -137
  225. package/dist/controllers/reading_progress_controller.js.map +1 -1
  226. package/dist/controllers/relative_time_controller.d.ts +20 -6
  227. package/dist/controllers/relative_time_controller.js +211 -112
  228. package/dist/controllers/relative_time_controller.js.map +1 -1
  229. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  230. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  231. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  232. package/dist/controllers/resizable_controller.d.ts +31 -2
  233. package/dist/controllers/resizable_controller.js +299 -150
  234. package/dist/controllers/resizable_controller.js.map +1 -1
  235. package/dist/controllers/roving_controller.d.ts +3 -3
  236. package/dist/controllers/roving_controller.js +49 -88
  237. package/dist/controllers/roving_controller.js.map +1 -1
  238. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  239. package/dist/controllers/scroll_area_controller.js +490 -246
  240. package/dist/controllers/scroll_area_controller.js.map +1 -1
  241. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  242. package/dist/controllers/scroll_restore_controller.js +14 -55
  243. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  244. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  245. package/dist/controllers/scroll_visibility_controller.js +432 -149
  246. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  247. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  248. package/dist/controllers/scrollspy_controller.js +193 -248
  249. package/dist/controllers/scrollspy_controller.js.map +1 -1
  250. package/dist/controllers/separator_controller.d.ts +20 -5
  251. package/dist/controllers/separator_controller.js +285 -142
  252. package/dist/controllers/separator_controller.js.map +1 -1
  253. package/dist/controllers/sidebar_controller.d.ts +65 -8
  254. package/dist/controllers/sidebar_controller.js +1069 -404
  255. package/dist/controllers/sidebar_controller.js.map +1 -1
  256. package/dist/controllers/skeleton_controller.d.ts +23 -0
  257. package/dist/controllers/skeleton_controller.js +259 -101
  258. package/dist/controllers/skeleton_controller.js.map +1 -1
  259. package/dist/controllers/slider_controller.d.ts +29 -2
  260. package/dist/controllers/slider_controller.js +187 -86
  261. package/dist/controllers/slider_controller.js.map +1 -1
  262. package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
  263. package/dist/controllers/smart_sticky_header_controller.js +173 -66
  264. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  265. package/dist/controllers/sortable_controller.d.ts +4 -4
  266. package/dist/controllers/sortable_controller.js +26 -125
  267. package/dist/controllers/sortable_controller.js.map +1 -1
  268. package/dist/controllers/spinner_controller.d.ts +39 -4
  269. package/dist/controllers/spinner_controller.js +288 -173
  270. package/dist/controllers/spinner_controller.js.map +1 -1
  271. package/dist/controllers/step_indicator_controller.d.ts +9 -4
  272. package/dist/controllers/step_indicator_controller.js +105 -46
  273. package/dist/controllers/step_indicator_controller.js.map +1 -1
  274. package/dist/controllers/stepper_controller.d.ts +14 -6
  275. package/dist/controllers/stepper_controller.js +120 -73
  276. package/dist/controllers/stepper_controller.js.map +1 -1
  277. package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
  278. package/dist/controllers/stick_to_bottom_controller.js +275 -140
  279. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  280. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  281. package/dist/controllers/sticky_observer_controller.js +220 -49
  282. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  283. package/dist/controllers/submit_once_controller.d.ts +24 -8
  284. package/dist/controllers/submit_once_controller.js +376 -227
  285. package/dist/controllers/submit_once_controller.js.map +1 -1
  286. package/dist/controllers/switch_controller.d.ts +18 -4
  287. package/dist/controllers/switch_controller.js +201 -84
  288. package/dist/controllers/switch_controller.js.map +1 -1
  289. package/dist/controllers/tabs_controller.d.ts +9 -6
  290. package/dist/controllers/tabs_controller.js +41 -30
  291. package/dist/controllers/tabs_controller.js.map +1 -1
  292. package/dist/controllers/tags_input_controller.d.ts +16 -3
  293. package/dist/controllers/tags_input_controller.js +293 -173
  294. package/dist/controllers/tags_input_controller.js.map +1 -1
  295. package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
  296. package/dist/controllers/textarea_autosize_controller.js +108 -74
  297. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  298. package/dist/controllers/theme_controller.d.ts +24 -11
  299. package/dist/controllers/theme_controller.js +140 -116
  300. package/dist/controllers/theme_controller.js.map +1 -1
  301. package/dist/controllers/time_picker_controller.d.ts +55 -5
  302. package/dist/controllers/time_picker_controller.js +331 -127
  303. package/dist/controllers/time_picker_controller.js.map +1 -1
  304. package/dist/controllers/toast_controller.d.ts +40 -16
  305. package/dist/controllers/toast_controller.js +244 -324
  306. package/dist/controllers/toast_controller.js.map +1 -1
  307. package/dist/controllers/toggle_group_controller.d.ts +14 -6
  308. package/dist/controllers/toggle_group_controller.js +252 -191
  309. package/dist/controllers/toggle_group_controller.js.map +1 -1
  310. package/dist/controllers/toolbar_controller.d.ts +8 -6
  311. package/dist/controllers/toolbar_controller.js +219 -120
  312. package/dist/controllers/toolbar_controller.js.map +1 -1
  313. package/dist/controllers/tooltip_controller.d.ts +49 -4
  314. package/dist/controllers/tooltip_controller.js +158 -124
  315. package/dist/controllers/tooltip_controller.js.map +1 -1
  316. package/dist/controllers/transition_controller.d.ts +22 -4
  317. package/dist/controllers/transition_controller.js +123 -119
  318. package/dist/controllers/transition_controller.js.map +1 -1
  319. package/dist/controllers/tree_view_controller.d.ts +22 -12
  320. package/dist/controllers/tree_view_controller.js +283 -369
  321. package/dist/controllers/tree_view_controller.js.map +1 -1
  322. package/dist/index.d.ts +10 -5
  323. package/dist/index.js +8573 -10410
  324. package/dist/index.js.map +1 -1
  325. package/dist/inspector/cli.d.ts +38 -14
  326. package/dist/inspector/cli.js +108 -40
  327. package/dist/inspector/cli.js.map +1 -1
  328. package/dist/inspector/cli_bin.js +145 -64
  329. package/dist/inspector/cli_bin.js.map +1 -1
  330. package/dist/inspector/examples.json +20 -20
  331. package/dist/inspector/manifest.json +802 -101
  332. package/dist/positioning/index.d.ts +15 -7
  333. package/dist/positioning/index.js +154 -47
  334. package/dist/positioning/index.js.map +1 -1
  335. package/package.json +11 -10
  336. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  337. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,30 +1,173 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/scroll_visibility_controller.ts
3
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
4
+ if (element.getAttribute(name) === value) return false;
5
+ if (value === null) element.removeAttribute(name);
6
+ else element.setAttribute(name, value);
7
+ return true;
8
+ }
9
+
10
+ function sharedRegistry(key, create) {
11
+ const symbol = Symbol.for(key);
12
+ const scope = globalThis;
13
+ const existing = scope[symbol];
14
+ if (existing !== void 0) return existing;
15
+ const registry2 = create();
16
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
17
+ return registry2;
18
+ }
19
+
20
+ var registry = sharedRegistry(
21
+ "stimeo-ui.attribute-lease.registry.v1",
22
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
23
+ );
24
+ function isAttributeValue(value) {
25
+ return value === null || typeof value === "string";
26
+ }
27
+ function readLeaseRecord(element, record) {
28
+ try {
29
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
30
+ if (!Array.isArray(parsed)) return void 0;
31
+ const [original, written, ...rest] = parsed;
32
+ const linked = rest[0] !== false;
33
+ const beneath = linked ? rest : rest.slice(1);
34
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
35
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ function attributeStack(element, attribute) {
41
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
42
+ registry.stacks.set(element, byAttribute);
43
+ const known = byAttribute.get(attribute);
44
+ if (known) return known;
45
+ const found = [];
46
+ for (const record of registry.names.get(attribute) ?? []) {
47
+ const read = readLeaseRecord(element, record);
48
+ if (read) found.push({ ...read, record });
49
+ }
50
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
51
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
52
+ lease: null,
53
+ record,
54
+ written,
55
+ original,
56
+ linked: linked && index > 0
57
+ }));
58
+ byAttribute.set(attribute, stack);
59
+ return stack;
60
+ }
61
+ function findHold(stack, lease, record) {
62
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
63
+ }
64
+ var AttributeLease = class {
65
+ #attribute;
66
+ #record;
67
+ #base;
68
+ #preserveAuthor;
69
+ #held = /* @__PURE__ */ new Set();
70
+ constructor(attribute, owner, options = {}) {
71
+ this.#attribute = attribute;
72
+ this.#record = `data-${owner}-${attribute}-lease`;
73
+ this.#base = options.base === true;
74
+ this.#preserveAuthor = options.preserveAuthor === true;
75
+ const names = registry.names;
76
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
77
+ }
78
+ write(element, value) {
79
+ const stack = attributeStack(element, this.#attribute);
80
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
81
+ this.#held.add(element);
82
+ const shows = !this.#covered(element, stack, hold);
83
+ hold.written = value;
84
+ this.#mark(element, stack);
85
+ if (shows) this.#reflect(element, value);
86
+ }
87
+ return(element) {
88
+ const held = this.#held.delete(element);
89
+ const known = registry.stacks.get(element)?.has(this.#attribute);
90
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
91
+ const hold = this.#take(stack);
92
+ if (!hold) return false;
93
+ const carries = element.getAttribute(this.#attribute) === hold.written;
94
+ const owns = carries && !this.#covered(element, stack, hold);
95
+ const index = stack.indexOf(hold);
96
+ stack.splice(index, 1);
97
+ const above = stack[index];
98
+ if (above) above.linked &&= hold.linked;
99
+ this.#mark(element, stack);
100
+ if (!owns) return true;
101
+ const below = hold.linked ? stack[index - 1] : void 0;
102
+ this.#reflect(element, below ? below.written : hold.original);
103
+ return true;
104
+ }
105
+ returnAll() {
106
+ for (const element of Array.from(this.#held)) this.return(element);
107
+ }
108
+ #take(stack) {
109
+ const hold = findHold(stack, this, this.#record);
110
+ if (hold) hold.lease = this;
111
+ return hold;
112
+ }
113
+ #acquire(element, stack) {
114
+ const current = element.getAttribute(this.#attribute);
115
+ const top = stack.at(-1);
116
+ const bottom = stack[0];
117
+ const over = top?.written === current ? top : void 0;
118
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
119
+ const hold = {
120
+ lease: this,
121
+ record: this.#record,
122
+ written: current,
123
+ original: from ? from.original : current,
124
+ linked: !this.#base && over !== void 0
125
+ };
126
+ if (this.#base && bottom) {
127
+ bottom.linked = true;
128
+ stack.unshift(hold);
129
+ } else stack.push(hold);
130
+ return hold;
131
+ }
132
+ #covered(element, stack, hold) {
133
+ const current = element.getAttribute(this.#attribute);
134
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
135
+ }
136
+ #mark(element, stack) {
137
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
138
+ stack.forEach((hold, index) => {
139
+ const names = stack.slice(0, index).map((under) => under.record);
140
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
141
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
142
+ records.set(
143
+ hold.record,
144
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
145
+ );
146
+ });
147
+ for (const [name, recorded] of records) {
148
+ setAttributeIfChanged(element, name, recorded);
149
+ }
150
+ }
151
+ #reflect(element, value) {
152
+ setAttributeIfChanged(element, this.#attribute, value);
153
+ }
154
+ };
4
155
 
5
- // src/utils/blur_deferral.ts
6
156
  var BlurDeferral = class {
7
- /** Elements currently holding an update back, mapped to their `blur` listener. */
8
157
  #pending = /* @__PURE__ */ new Map();
9
- /** Called after a pending element blurs and has been detached. */
10
158
  #onRelease;
11
- /** @param onRelease - Invoked once `element` actually blurs; never on `release`. */
12
159
  constructor(onRelease) {
13
160
  this.#onRelease = onRelease;
14
161
  }
15
- /** Number of elements currently holding an update back. */
16
162
  get size() {
17
163
  return this.#pending.size;
18
164
  }
19
- /** Snapshot of the pending elements, safe to iterate while releasing them. */
20
165
  get elements() {
21
166
  return [...this.#pending.keys()];
22
167
  }
23
- /** Whether `element` is currently holding an update back. */
24
168
  has(element) {
25
169
  return this.#pending.has(element);
26
170
  }
27
- /** Holds an update back until `element` blurs. Idempotent (no stacked listeners). */
28
171
  defer(element) {
29
172
  if (this.#pending.has(element)) return;
30
173
  const onBlur = () => {
@@ -34,22 +177,18 @@ var BlurDeferral = class {
34
177
  this.#pending.set(element, onBlur);
35
178
  element.addEventListener("blur", onBlur);
36
179
  }
37
- /** Defers `element` as the only pending entry, cancelling any others. */
38
180
  deferOnly(element) {
39
181
  for (const pending of this.elements) {
40
182
  if (pending !== element) this.#detach(pending);
41
183
  }
42
184
  this.defer(element);
43
185
  }
44
- /** Cancels `element`'s deferral without completing it; no-ops when not pending. */
45
186
  release(element) {
46
187
  this.#detach(element);
47
188
  }
48
- /** Cancels every deferral without completing any of them. */
49
189
  releaseAll() {
50
190
  for (const element of this.elements) this.#detach(element);
51
191
  }
52
- /** Removes the `blur` listener for `element` and forgets it. */
53
192
  #detach(element) {
54
193
  const onBlur = this.#pending.get(element);
55
194
  if (onBlur) element.removeEventListener("blur", onBlur);
@@ -57,7 +196,6 @@ var BlurDeferral = class {
57
196
  }
58
197
  };
59
198
 
60
- // src/utils/declared_value.ts
61
199
  function parseDeclared(raw, parse, fallback) {
62
200
  try {
63
201
  return parse(raw);
@@ -77,14 +215,8 @@ function validSelector(element, raw, fallback) {
77
215
  );
78
216
  }
79
217
 
80
- // src/utils/frame_coalescer.ts
81
218
  var FrameCoalescer = class {
82
219
  #frame = null;
83
- /**
84
- * Runs `run` on the next frame, unless a frame is already pending — the first
85
- * request of a burst wins and the rest are dropped. The pending frame is
86
- * released before `run`, so `run` may request the next one.
87
- */
88
220
  schedule(run) {
89
221
  if (this.#frame !== null) return;
90
222
  this.#frame = requestAnimationFrame(() => {
@@ -92,14 +224,6 @@ var FrameCoalescer = class {
92
224
  run();
93
225
  });
94
226
  }
95
- /**
96
- * Drops the pending frame, and reaches the platform only when there is one.
97
- *
98
- * There is no handle value that stands for "nothing pending":
99
- * `cancelAnimationFrame` takes an `unsigned long`, so a negative placeholder
100
- * arrives as a large positive number that the same allocator can hand out, and
101
- * an idle cancel would then drop a frame belonging to someone else.
102
- */
103
227
  cancel() {
104
228
  if (this.#frame === null) return;
105
229
  cancelAnimationFrame(this.#frame);
@@ -107,12 +231,124 @@ var FrameCoalescer = class {
107
231
  }
108
232
  };
109
233
 
110
- // src/utils/reduced_motion.ts
234
+ var ListenerSet = class {
235
+ #abort = new AbortController();
236
+ add(target, type, handler, options) {
237
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
238
+ }
239
+ dispose() {
240
+ this.#abort.abort();
241
+ this.#abort = new AbortController();
242
+ }
243
+ };
244
+
245
+ var MicrotaskCoalescer = class {
246
+ #run;
247
+ #queued = false;
248
+ #active = false;
249
+ #generation = 0;
250
+ constructor(run) {
251
+ this.#run = run;
252
+ }
253
+ activate() {
254
+ this.#active = true;
255
+ }
256
+ cancel() {
257
+ this.#active = false;
258
+ this.#queued = false;
259
+ this.#generation += 1;
260
+ }
261
+ get pending() {
262
+ return this.#queued;
263
+ }
264
+ schedule() {
265
+ if (!this.#active || this.#queued) return;
266
+ this.#queued = true;
267
+ const generation = this.#generation;
268
+ queueMicrotask(() => {
269
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
270
+ this.#queued = false;
271
+ this.#run();
272
+ });
273
+ }
274
+ };
275
+
276
+ var MorphRenderWatcher = class {
277
+ #listeners = new ListenerSet();
278
+ #pass;
279
+ #element = null;
280
+ #onMorph = () => this.#pass.schedule();
281
+ constructor(run) {
282
+ this.#pass = new MicrotaskCoalescer(run);
283
+ }
284
+ observe(element) {
285
+ if (this.#element === element) return;
286
+ this.disconnect();
287
+ this.#element = element;
288
+ this.#pass.activate();
289
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
290
+ }
291
+ schedule() {
292
+ this.#pass.schedule();
293
+ }
294
+ get pending() {
295
+ return this.#pass.pending;
296
+ }
297
+ disconnect() {
298
+ this.#listeners.dispose();
299
+ this.#pass.cancel();
300
+ this.#element = null;
301
+ }
302
+ };
303
+
304
+ var NUMBER_BOUNDS = {
305
+ finite: { finite: true }};
306
+ function matchesNumberBounds(value, bounds) {
307
+ if (!Number.isFinite(value)) {
308
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
309
+ if (direction === null) return false;
310
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
311
+ }
312
+ if (bounds.min !== void 0 && value < bounds.min) return false;
313
+ if (bounds.max !== void 0 && value > bounds.max) return false;
314
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
315
+ if (bounds.integer && !Number.isInteger(value)) return false;
316
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
317
+ return true;
318
+ }
319
+
320
+ function readNumber(raw, fallback, bounds) {
321
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
322
+ }
323
+
324
+ var NumberValueReader = class {
325
+ #lastRejected = /* @__PURE__ */ new Map();
326
+ read(owner, name, raw, fallback, bounds) {
327
+ const resolved = readNumber(raw, fallback, bounds);
328
+ if (matchesNumberBounds(raw, bounds)) {
329
+ this.#lastRejected.delete(name);
330
+ return resolved;
331
+ }
332
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
333
+ const literal = owner.element.getAttribute(attribute);
334
+ if (literal === null) {
335
+ this.#lastRejected.delete(name);
336
+ return resolved;
337
+ }
338
+ if (this.#lastRejected.get(name) !== literal) {
339
+ this.#lastRejected.set(name, literal);
340
+ console.warn(
341
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
342
+ );
343
+ }
344
+ return resolved;
345
+ }
346
+ };
347
+
111
348
  function prefersReducedMotion() {
112
349
  return typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
113
350
  }
114
351
 
115
- // src/utils/scroll_source.ts
116
352
  function resolveScrollContainer(selector) {
117
353
  const match = selector ? document.querySelector(selector) : null;
118
354
  return match instanceof HTMLElement ? match : null;
@@ -123,70 +359,135 @@ function resolveScrollSource(selector) {
123
359
  function scrollOffset(source) {
124
360
  return source === window ? window.scrollY ?? window.pageYOffset ?? 0 : source.scrollTop;
125
361
  }
362
+ function deepActiveElement(ownerDocument) {
363
+ let active = ownerDocument.activeElement;
364
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
365
+ return active;
366
+ }
126
367
 
127
- // src/utils/before_cache_reset.ts
128
- var BeforeCacheReset = class _BeforeCacheReset {
129
- /** Every subscribed instance, iterated by the one shared document listener. */
130
- static #subscribers = /* @__PURE__ */ new Set();
131
- /** The shared listener; installed while at least one instance is subscribed. */
132
- static #onBeforeCache = () => {
133
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
134
- };
135
- #rewind;
136
- /** @param rewind - the pass that returns this controller's state to its initial form. */
137
- constructor(rewind) {
138
- this.#rewind = rewind;
139
- }
140
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
141
- activate() {
142
- const first = _BeforeCacheReset.#subscribers.size === 0;
143
- _BeforeCacheReset.#subscribers.add(this);
144
- if (first) {
145
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
368
+ function queryOpenDescendants(root, selector) {
369
+ const matches = [];
370
+ const roots = [root];
371
+ for (const scope of roots) {
372
+ matches.push(...scope.querySelectorAll(selector));
373
+ if (scope.nodeType === Node.ELEMENT_NODE) {
374
+ const shadow = scope.shadowRoot;
375
+ if (shadow) roots.push(shadow);
376
+ }
377
+ for (const element of scope.querySelectorAll("*")) {
378
+ if (element.shadowRoot) roots.push(element.shadowRoot);
146
379
  }
147
380
  }
148
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
149
- deactivate() {
150
- _BeforeCacheReset.#subscribers.delete(this);
151
- if (_BeforeCacheReset.#subscribers.size > 0) return;
152
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
153
- }
154
- };
381
+ return matches;
382
+ }
155
383
 
156
- // src/utils/tabindex_loan.ts
157
- var TabindexLoan = class {
384
+ function holdsFocus(element) {
385
+ return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
386
+ }
387
+ var TabindexLoan = class _TabindexLoan {
388
+ static #registry = sharedRegistry(
389
+ "stimeo-ui.tabindex-loan.registry.v1",
390
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
391
+ );
158
392
  #value;
393
+ #record;
394
+ #preserveFocus;
159
395
  #lent = /* @__PURE__ */ new Set();
160
- /** Returns live loans before Turbo can copy them into its page snapshot. */
161
- #beforeCache = new BeforeCacheReset(() => this.returnAll());
162
- /**
163
- * @param value - the `tabindex` to lend. `"-1"` (the default) is
164
- * programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
165
- * which a scroll region with no focusable content of its own needs.
166
- */
167
- constructor(value = "-1") {
396
+ constructor(value, owner, options = {}) {
168
397
  this.#value = value;
398
+ this.#record = `data-${owner}-tabindex-loan`;
399
+ this.#preserveFocus = options.preserveFocus === true;
400
+ _TabindexLoan.#registry.names.add(this.#record);
169
401
  }
170
- /** Lends `element` the value; no-ops when it already carries a `tabindex`. */
171
- lend(element) {
172
- if (element.hasAttribute("tabindex")) return;
173
- element.setAttribute("tabindex", this.#value);
402
+ lend(element, options = {}) {
403
+ const stack = this.#stack(element);
404
+ const current = element.getAttribute("tabindex");
405
+ const held = stack.find((hold) => hold.loan === this) ?? stack.find(
406
+ (hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
407
+ );
408
+ const linked = stack.at(-1)?.value === current;
409
+ if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
410
+ if (!held && current !== null && !linked) return;
174
411
  this.#lent.add(element);
175
- this.#beforeCache.activate();
412
+ if (held) held.loan = this;
413
+ else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
414
+ this.#mark(element, stack);
415
+ if (current === null || !held && current !== this.#value) {
416
+ element.setAttribute("tabindex", this.#value);
417
+ }
418
+ }
419
+ return(element) {
420
+ const stack = this.#stack(element);
421
+ const hold = stack.find((held) => held.loan === this) ?? stack.find(
422
+ (held) => held.loan === null && held.record === this.#record && held.value === this.#value
423
+ );
424
+ if (!hold) return false;
425
+ if (!this.#release(element, stack, hold, this.#preserveFocus)) {
426
+ hold.loan = this;
427
+ this.#lent.add(element);
428
+ return false;
429
+ }
430
+ this.#lent.delete(element);
431
+ return true;
176
432
  }
177
- /** Takes back every loan whose value is still the one that was lent. */
178
433
  returnAll() {
179
- for (const element of this.#lent) {
180
- if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
434
+ for (const element of Array.from(this.#lent)) this.return(element);
435
+ }
436
+ reclaim(element) {
437
+ if (element.getAttribute(this.#record) !== this.#value) return;
438
+ const stack = this.#stack(element);
439
+ const hold = stack.find((held) => held.loan === null && held.record === this.#record);
440
+ if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
441
+ }
442
+ reclaimWithin(root) {
443
+ for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
444
+ this.reclaim(element);
445
+ }
446
+ }
447
+ #stack(element) {
448
+ const stacks = _TabindexLoan.#registry.stacks;
449
+ const stack = stacks.get(element) ?? this.#seed(element);
450
+ stacks.set(element, stack);
451
+ return stack;
452
+ }
453
+ #seed(element) {
454
+ const current = element.getAttribute("tabindex");
455
+ const holds = [];
456
+ for (const record of _TabindexLoan.#registry.names) {
457
+ const value = element.getAttribute(record);
458
+ if (value !== null) holds.push({ loan: null, record, value, linked: true });
459
+ }
460
+ holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
461
+ for (const [index, hold] of holds.entries()) hold.linked = index > 0;
462
+ return holds;
463
+ }
464
+ #release(element, stack, hold, preserveFocus = false) {
465
+ const current = element.getAttribute("tabindex");
466
+ const index = stack.indexOf(hold);
467
+ const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
468
+ if (owns && preserveFocus && holdsFocus(element)) return false;
469
+ stack.splice(index, 1);
470
+ const above = stack[index];
471
+ if (above) above.linked &&= hold.linked;
472
+ this.#mark(element, stack);
473
+ if (!owns) return true;
474
+ const below = hold.linked ? stack[index - 1] : void 0;
475
+ if (!below) element.removeAttribute("tabindex");
476
+ else if (below.value !== current) element.setAttribute("tabindex", below.value);
477
+ return true;
478
+ }
479
+ #mark(element, stack) {
480
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
481
+ for (const hold of stack) records.set(hold.record, hold.value);
482
+ for (const [name, recorded] of records) {
483
+ setAttributeIfChanged(element, name, recorded);
181
484
  }
182
- this.#lent.clear();
183
- this.#beforeCache.deactivate();
184
485
  }
185
486
  };
186
487
 
187
- // src/controllers/scroll_visibility_controller.ts
188
488
  var DEFAULT_OFFSET = 400;
189
- var ScrollVisibilityController = class extends Controller {
489
+ var ScrollVisibilityController = class _ScrollVisibilityController extends Controller {
490
+ #numbers = new NumberValueReader();
190
491
  static targets = ["element"];
191
492
  static values = {
192
493
  offset: { type: Number, default: DEFAULT_OFFSET },
@@ -194,95 +495,70 @@ var ScrollVisibilityController = class extends Controller {
194
495
  focusSelector: { type: String, default: "" },
195
496
  root: { type: String, default: "" }
196
497
  };
498
+ static valueConstraints = {
499
+ offset: NUMBER_BOUNDS.finite
500
+ };
197
501
  static actions = ["toTop"];
198
502
  static events = ["change"];
199
- /** Coalesces scroll bursts into one measurement per frame. */
200
503
  #frames = new FrameCoalescer();
201
- /** Previous scroll position, for `direction` mode delta detection. */
202
504
  #lastScrollY = 0;
203
- /** Current visibility, tracked to dispatch `change` only on real transitions. */
204
505
  #visible = null;
205
- /**
206
- * The observed scroll source: a container element when `root` resolves, else
207
- * the window. Captured on connect so teardown detaches from the same source.
208
- */
209
506
  #scrollSource = window;
210
- /**
211
- * Gates the declaration callbacks to the connected window.
212
- *
213
- * Stimulus delivers a Value callback ahead of `connect()` and again for every
214
- * runtime change; without the gate, merely connecting would evaluate — and
215
- * announce — before `connect()` runs its own first reflection.
216
- */
507
+ #scrollListeners = new ListenerSet();
217
508
  #connected = false;
218
- /** Validated threshold; a non-finite declaration reads as the default. */
219
509
  #offset = DEFAULT_OFFSET;
220
- /** Validated `root` selector; an unparsable declaration reads as absent. */
221
510
  #rootSelector = "";
222
- /** Validated `focusSelector`; an unparsable declaration reads as absent. */
223
511
  #focusSelector = "";
224
- /** Focus targets this instance lent a `tabindex` to. */
225
- #tabindex = new TabindexLoan();
226
- /**
227
- * The hide held back while the control itself owns focus.
228
- *
229
- * Completing the deferral re-runs the ordinary evaluation rather than applying
230
- * the stale decision: by the time focus leaves, the scroll position may have
231
- * moved back past the threshold.
232
- */
512
+ #tabindex = new TabindexLoan("-1", this.identifier);
513
+ #hidden = new AttributeLease("hidden", this.identifier);
233
514
  #pendingHide = new BlurDeferral(() => {
234
515
  if (this.#connected) this.#evaluate();
235
516
  });
236
517
  #onScroll = () => this.#frames.schedule(() => this.#evaluate());
518
+ #morphRender = new MorphRenderWatcher(() => {
519
+ this.#followSource();
520
+ this.#evaluate();
521
+ });
237
522
  connect() {
238
- this.#scrollSource = resolveScrollSource(this.#rootSelector);
239
- this.#lastScrollY = scrollOffset(this.#scrollSource);
240
- this.#scrollSource.addEventListener("scroll", this.#onScroll, { passive: true });
523
+ this.#tabindex.reclaimWithin(document);
524
+ this.#morphRender.observe(this.element);
525
+ this.#listenTo(resolveScrollSource(this.#rootSelector));
241
526
  this.#evaluate(false);
242
527
  this.#connected = true;
243
528
  }
244
529
  disconnect() {
530
+ this.#morphRender.disconnect();
245
531
  this.#connected = false;
246
- this.#scrollSource.removeEventListener("scroll", this.#onScroll);
532
+ this.#scrollListeners.dispose();
247
533
  this.#frames.cancel();
248
534
  this.#pendingHide.releaseAll();
249
535
  this.#tabindex.returnAll();
250
536
  this.#visible = null;
251
537
  }
252
- /** Writes the current visibility onto a control that arrives after connect. */
253
538
  elementTargetConnected(element) {
254
- if (this.#visible !== null) element.hidden = !this.#visible;
539
+ if (this.#visible !== null) this.#show(element, this.#visible);
255
540
  }
256
- /** Drops a held-back hide together with the control it was waiting on. */
257
- elementTargetDisconnected() {
541
+ elementTargetDisconnected(element) {
258
542
  this.#pendingHide.releaseAll();
543
+ if (!this.elementTargets.includes(element)) this.#hidden.return(element);
544
+ if (this.#visible !== null && this.hasElementTarget) {
545
+ this.#show(this.elementTarget, this.#visible);
546
+ }
259
547
  }
260
- /**
261
- * Validates `offset` once, then re-renders.
262
- *
263
- * Re-renders when application code (or a Turbo morph) changes `offset` at
264
- * runtime. A declaration that is not a finite number reads as the default, so
265
- * the comparison path never sees `NaN` — which would answer `false` to every
266
- * comparison and strand the element (in `direction` mode, even the guarantee
267
- * that the very top always reveals).
268
- */
269
548
  offsetValueChanged() {
270
- this.#offset = Number.isFinite(this.offsetValue) ? this.offsetValue : DEFAULT_OFFSET;
271
- if (this.#connected) this.#evaluate();
549
+ this.#offset = this.#safeOffset;
550
+ this.#morphRender.schedule();
272
551
  }
273
- /** Re-renders when application code (or a Turbo morph) changes `mode` at runtime. */
274
552
  modeValueChanged() {
275
- if (this.#connected) this.#evaluate();
553
+ this.#morphRender.schedule();
276
554
  }
277
- /** Validates `root` once so connect never parses a selector that throws. */
278
555
  rootValueChanged() {
279
556
  this.#rootSelector = validSelector(this.element, this.rootValue, "");
557
+ this.#morphRender.schedule();
280
558
  }
281
- /** Validates `focusSelector` once so `toTop` never parses a selector that throws. */
282
559
  focusSelectorValueChanged() {
283
560
  this.#focusSelector = validSelector(this.element, this.focusSelectorValue, "");
284
561
  }
285
- /** Scrolls the source to the top and, optionally, moves focus to a safe target. */
286
562
  toTop() {
287
563
  const behavior = prefersReducedMotion() ? "instant" : "smooth";
288
564
  this.#scrollSource.scrollTo({ top: 0, behavior });
@@ -294,14 +570,17 @@ var ScrollVisibilityController = class extends Controller {
294
570
  }
295
571
  }
296
572
  }
297
- /**
298
- * Decides the next visibility from the current scroll state and applies it.
299
- *
300
- * @param notify - whether a transition announces itself. The reflection
301
- * `connect()` performs is the current state, not a change.
302
- *
303
- * @stimeoRenderRoot
304
- */
573
+ #followSource() {
574
+ const source = resolveScrollSource(this.#rootSelector);
575
+ if (source === this.#scrollSource) return;
576
+ this.#scrollListeners.dispose();
577
+ this.#listenTo(source);
578
+ }
579
+ #listenTo(source) {
580
+ this.#scrollSource = source;
581
+ this.#lastScrollY = scrollOffset(source);
582
+ this.#scrollListeners.add(source, "scroll", this.#onScroll, { passive: true });
583
+ }
305
584
  #evaluate(notify = true) {
306
585
  const y = scrollOffset(this.#scrollSource);
307
586
  let nextVisible;
@@ -309,6 +588,7 @@ var ScrollVisibilityController = class extends Controller {
309
588
  if (y <= this.#offset) {
310
589
  nextVisible = true;
311
590
  } else if (y === this.#lastScrollY) {
591
+ this.#setVisible(this.#visible ?? true, notify);
312
592
  return;
313
593
  } else {
314
594
  nextVisible = y < this.#lastScrollY;
@@ -319,33 +599,36 @@ var ScrollVisibilityController = class extends Controller {
319
599
  this.#lastScrollY = y;
320
600
  this.#setVisible(nextVisible, notify);
321
601
  }
322
- /** Applies visibility to the target, syncing `hidden`, `data-state`, `change`. */
323
602
  #setVisible(next, notify) {
324
- if (next === this.#visible) return;
603
+ const changed = next !== this.#visible;
325
604
  const focused = !next && this.hasElementTarget ? this.#focusedWithin() : null;
326
605
  if (focused) {
327
606
  this.#pendingHide.deferOnly(focused);
328
607
  return;
329
608
  }
330
609
  this.#visible = next;
331
- if (this.hasElementTarget) this.elementTarget.hidden = !next;
610
+ if (this.hasElementTarget) this.#show(this.elementTarget, next);
332
611
  this.element.setAttribute("data-state", next ? "visible" : "hidden");
333
- if (notify) this.dispatch("change", { detail: { visible: next } });
334
- }
335
- /**
336
- * The focus owner inside the target, or `null` when focus is elsewhere.
337
- *
338
- * `blur` does not bubble, so the deferral has to ride the focused element
339
- * itself: waiting on a container that never receives the event would hold the
340
- * hide forever.
341
- */
612
+ if (notify && changed) this.dispatch("change", { detail: { visible: next } });
613
+ }
614
+ #show(control, visible) {
615
+ this.#hidden.write(control, visible ? null : "");
616
+ }
342
617
  #focusedWithin() {
343
618
  const focused = document.activeElement;
344
619
  if (focused instanceof HTMLElement && this.elementTarget.contains(focused)) return focused;
345
620
  return null;
346
621
  }
622
+ get #safeOffset() {
623
+ return this.#numbers.read(
624
+ this,
625
+ "offset",
626
+ this.offsetValue,
627
+ _ScrollVisibilityController.values.offset.default,
628
+ _ScrollVisibilityController.valueConstraints.offset
629
+ );
630
+ }
347
631
  };
348
632
 
349
633
  export { ScrollVisibilityController };
350
- //# sourceMappingURL=scroll_visibility_controller.js.map
351
634
  //# sourceMappingURL=scroll_visibility_controller.js.map