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 +1 @@
1
- {"version":3,"sources":["../../src/utils/layout_observer.ts","../../src/utils/reduced_motion.ts","../../src/utils/state_regions.ts","../../src/controllers/stick_to_bottom_controller.ts"],"names":[],"mappings":"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","file":"stick_to_bottom_controller.js","sourcesContent":["/**\n * Unified layout observation for Stimeo controllers.\n *\n * Widgets whose output is measured — an overflow boundary, a masonry column count,\n * an autosized textarea — have three sources that move the layout under them: their\n * *own* box changing — via {@link ResizeObserver} — the *viewport* changing — via the\n * `window` `resize` event — and a descendant *resource settling*, because an image or\n * a frame reports a height of zero until it has loaded. Wiring those three by hand in\n * every controller risks leaked listeners on `disconnect()`. {@link LayoutObserver}\n * owns all three behind one callback and one\n * {@link LayoutObserver.disconnect | disconnect()} that releases everything.\n *\n * Behavior only: the helper reports *that* layout changed; it never reads or\n * writes styles. Consumers decide what to recompute.\n */\n\n/** Invoked whenever an observed element or the viewport changes size. */\nexport type LayoutCallback = () => void;\n\n/** Constructs a {@link ResizeObserver}; injectable so tests stay deterministic. */\nexport type ResizeObserverFactory = (callback: ResizeObserverCallback) => ResizeObserver;\n\n/** Options for {@link LayoutObserver}. */\nexport interface LayoutObserverOptions {\n /**\n * Factory for the {@link ResizeObserver} used by {@link LayoutObserver.observe}.\n * Defaults to the global constructor; override it in tests, or to no-op in\n * environments where `ResizeObserver` is unavailable.\n */\n resizeObserverFactory?: ResizeObserverFactory;\n}\n\n/**\n * Observes element resizes and/or viewport resizes through a single callback,\n * with guaranteed teardown.\n *\n * @example\n * ```ts\n * #layout = new LayoutObserver(() => this.#reposition());\n *\n * connect() {\n * this.#layout.observe(this.panelTarget);\n * this.#layout.observeViewport();\n * this.#layout.observeDescendantLoads(this.panelTarget);\n * }\n *\n * disconnect() {\n * this.#layout.disconnect();\n * }\n * ```\n */\nexport class LayoutObserver {\n readonly #callback: LayoutCallback;\n readonly #resizeObserverFactory: ResizeObserverFactory | null;\n #resizeObserver: ResizeObserver | null = null;\n #observingViewport = false;\n #loadContainer: Element | null = null;\n\n /** Stable bound handler so add/removeEventListener target the same reference. */\n readonly #handleViewportResize = (): void => {\n this.#callback();\n };\n\n /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */\n readonly #handleDescendantLoad = (): void => {\n this.#callback();\n };\n\n constructor(callback: LayoutCallback, options: LayoutObserverOptions = {}) {\n this.#callback = callback;\n this.#resizeObserverFactory =\n options.resizeObserverFactory ??\n (typeof ResizeObserver === \"undefined\" ? null : (cb) => new ResizeObserver(cb));\n }\n\n /**\n * Starts observing an element's size. Repeated calls observe additional\n * elements through the same shared observer. No-ops when no\n * `ResizeObserver` implementation is available.\n */\n observe(element: Element): void {\n if (!this.#resizeObserverFactory) return;\n if (!this.#resizeObserver) {\n this.#resizeObserver = this.#resizeObserverFactory(() => {\n this.#callback();\n });\n }\n this.#resizeObserver.observe(element);\n }\n\n /** Stops observing a single element while leaving any others in place. */\n unobserve(element: Element): void {\n this.#resizeObserver?.unobserve(element);\n }\n\n /** Starts observing viewport resizes. Idempotent: the listener is added once. */\n observeViewport(): void {\n if (this.#observingViewport) return;\n this.#observingViewport = true;\n window.addEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /** Stops observing viewport resizes without affecting element observation. */\n unobserveViewport(): void {\n if (!this.#observingViewport) return;\n this.#observingViewport = false;\n window.removeEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /**\n * Starts reporting a `load` from anywhere inside `container` — an image or a\n * frame settling changes the box it sits in, and it measures as zero high until\n * then. `load` does not bubble, so the subscription is a capture-phase listener\n * on the container itself and nothing the caller spells.\n *\n * **One container at a time.** A further call moves the observation, so a widget\n * whose content element is swapped at runtime releases the element it let go by\n * naming the new one — there is no second place for the release to drift from.\n */\n observeDescendantLoads(container: Element): void {\n this.unobserveDescendantLoads();\n this.#loadContainer = container;\n container.addEventListener(\"load\", this.#handleDescendantLoad, true);\n }\n\n /** Stops reporting descendant loads without affecting element or viewport observation. */\n unobserveDescendantLoads(): void {\n this.#loadContainer?.removeEventListener(\"load\", this.#handleDescendantLoad, true);\n this.#loadContainer = null;\n }\n\n /**\n * Releases every observation: disconnects the {@link ResizeObserver} and removes\n * the viewport and descendant-load listeners. Safe to call multiple times. Call\n * this from a controller's `disconnect()`.\n */\n disconnect(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.unobserveViewport();\n this.unobserveDescendantLoads();\n }\n}\n","/**\n * Shared `prefers-reduced-motion` lookup for the motion-aware controllers.\n *\n * This one-liner keeps the media query string and the environment guard\n * single-sourced across them. The preference is intentionally re-read on every\n * call — the controllers check it at each animation/scroll start (WCAG 2.2\n * **2.3.3**), so flipping the OS setting takes effect immediately without any\n * listener or cache bookkeeping here.\n */\n\n/**\n * Whether the user currently requests reduced motion.\n *\n * @returns `true` when `(prefers-reduced-motion: reduce)` matches; `false`\n * otherwise, including environments without `window.matchMedia` (treated as\n * \"no preference\").\n */\nexport function prefersReducedMotion(): boolean {\n return (\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n );\n}\n","/** What a consumer declares once about the regions one boolean state reveals. */\nexport interface StateRegionsOptions {\n /** Returns the declared regions shown while the state holds. */\n readonly whenTrue: () => readonly HTMLElement[];\n /**\n * Returns the declared regions shown while the state does not hold. Omitted by a\n * state whose other side has nothing of its own to show.\n */\n readonly whenFalse?: () => readonly HTMLElement[];\n}\n\n/**\n * Owns `hidden` on the regions a boolean state reveals.\n *\n * A region is a **declared** target, so the consumer decides whether one exists at\n * all; where it does, which side is shown is a pure function of the state and the\n * controller writes it unconditionally. An authored `hidden` is therefore not read\n * back — the first reflection after a connection settles it, and a restored DOM\n * cannot leave a region contradicting the state it belongs to.\n *\n * The regions arrive as the caller's own target arrays, which Stimulus has already\n * scoped to this instance: a nested controller registered under the same identifier\n * keeps its own regions. `host` narrows that set further, to the regions inside one\n * trigger or item, so a widget with several of them reflects each independently.\n *\n * **Two sides are a pair.** Where the state has a `whenFalse` side and only one half\n * sits inside `host`, the half that is there is left in view: hiding it would take\n * the only label with it, and the static check names the missing one instead. What\n * the author wrote on a lone half is theirs and stays — only a half this instance\n * took out of view is given back, which is what a pair losing one side at runtime\n * leaves behind.\n *\n * `hidden` moves only when it changes, so a consumer watching its own subtree for\n * attribute mutations sees one record per transition rather than one per reflection.\n *\n * @example\n * ```ts\n * readonly #labels = new StateRegions({\n * whenTrue: () => this.expandedLabelTargets,\n * whenFalse: () => this.collapsedLabelTargets,\n * });\n *\n * #reflect(): void {\n * this.#labels.reflect(this.triggerTarget, this.#expanded);\n * }\n * ```\n */\nexport class StateRegions {\n readonly #whenTrue: () => readonly HTMLElement[];\n readonly #whenFalse: (() => readonly HTMLElement[]) | null;\n readonly #taken = new WeakSet<HTMLElement>();\n\n constructor(options: StateRegionsOptions) {\n this.#whenTrue = options.whenTrue;\n this.#whenFalse = options.whenFalse ?? null;\n }\n\n /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */\n reflect(host: Element, isTrue: boolean): void {\n const shown = this.#inside(host, this.#whenTrue());\n if (!this.#whenFalse) {\n for (const region of shown) this.#write(region, !isTrue);\n return;\n }\n const hiddenSide = this.#inside(host, this.#whenFalse());\n if (shown.length === 0 || hiddenSide.length === 0) {\n for (const region of shown) this.#give(region);\n for (const region of hiddenSide) this.#give(region);\n return;\n }\n for (const region of shown) this.#write(region, !isTrue);\n for (const region of hiddenSide) this.#write(region, isTrue);\n }\n\n /** Writes `hidden` where it moves, noting which regions it takes out of view. */\n #write(region: HTMLElement, hidden: boolean): void {\n if (hidden) this.#taken.add(region);\n else this.#taken.delete(region);\n if (region.hidden !== hidden) region.hidden = hidden;\n }\n\n /** Returns a region this instance hid; one it never hid keeps what it carries. */\n #give(region: HTMLElement): void {\n if (this.#taken.delete(region)) region.hidden = false;\n }\n\n /** The declared regions that sit within `host`, which may be the host itself. */\n #inside(host: Element, regions: readonly HTMLElement[]): HTMLElement[] {\n return regions.filter((region) => host.contains(region));\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { LayoutObserver } from \"../utils/layout_observer\";\nimport { prefersReducedMotion } from \"../utils/reduced_motion\";\nimport { StateRegions } from \"../utils/state_regions\";\n\n/** Distance from the bottom, in px, that still counts as pinned. */\nconst DEFAULT_THRESHOLD = 80;\n\n/** Counts the element nodes in an added-node list (text nodes are ignored). */\nconst countElements = (nodes: NodeList): number => {\n let n = 0;\n for (const node of nodes) if (node.nodeType === Node.ELEMENT_NODE) n += 1;\n return n;\n};\n\n/**\n * Headless **stick-to-bottom**: auto-follows new content to the bottom of a scroll\n * container (a chat log, a live console) — but only while the user is already near the\n * bottom; if they have scrolled up to read, it holds position and flags that new content\n * arrived instead (no APG pattern; honors WCAG 2.3.3 via `prefers-reduced-motion` and\n * never steals focus).\n *\n * Markup contract (identifier: `stimeo--stick-to-bottom`):\n * <div data-controller=\"stimeo--stick-to-bottom\"\n * data-stimeo--stick-to-bottom-threshold-value=\"80\"\n * data-stimeo--stick-to-bottom-pin-on-connect-value=\"true\" style=\"overflow:auto\">\n * <ul data-stimeo--stick-to-bottom-target=\"content\"><!-- Turbo Stream appends --></ul>\n * <button type=\"button\" hidden data-stimeo--stick-to-bottom-target=\"hasNew\"\n * data-action=\"stimeo--stick-to-bottom#scrollToBottom\">New messages</button>\n * </div>\n *\n * The container is \"pinned\" while it has a box to measure and its distance from the\n * bottom is within `threshold`. A `MutationObserver` on `content` (or the element) reacts\n * to appended children: while pinned it scrolls to the bottom; while unpinned it sets\n * `data-has-new` and emits `new`. Scrolling recomputes pinned and reflects `data-pinned`,\n * emitting `pin` on change; the `scrollToBottom` action jumps back down (a \"new messages\"\n * button). A `threshold` changed at runtime re-derives the state at once.\n *\n * `data-has-new` says which state the container is in; the optional `hasNew` targets are\n * the regions that belong to the flagged side — the affordance offering the jump back\n * down. They are shown while the flag is up and hidden while it is down, written wherever\n * the flag moves, so an authored `hidden` settles at the first reflection and a restored\n * DOM cannot leave the region contradicting the flag.\n *\n * A container that already overflows renders at `scrollTop` 0 — further than\n * `threshold` from the bottom, so unpinned — and flags the first append rather\n * than following it. `pinOnConnect` opts into the other starting point:\n * `connect()` jumps to the bottom and reads the state back from where it landed. It runs\n * on every connect, so a container inserted or re-rendered later (a Turbo Stream, a panel\n * built on open) starts at the bottom too — which a once-per-document page event cannot\n * do. It is off by default, so a restored reading position (a Turbo cache restore, a user\n * who had scrolled up) is never yanked to the bottom unless the consumer asks for it.\n *\n * A container connected without a box — inside a closed panel — reports every metric as\n * 0, so it is held unpinned until layout arrives: appends meanwhile are flagged rather\n * than followed into a box that cannot move, and the decision is then made against the\n * layout the user actually sees.\n *\n * `pin` dispatches `{ pinned }`; `new` dispatches `{ count }`.\n *\n * @remarks\n * The `MutationObserver` watches `childList` only (not `subtree`), so it follows\n * direct appends to `content` (or the element). Appends made deeper inside a nested\n * wrapper are not detected — keep messages as direct children, or call the public\n * `scrollToBottom` action after such inserts. A `content` target that is swapped at\n * runtime moves the watch with it.\n *\n * Behavior only — it does not add content (Turbo Stream / the consumer does) and is the\n * minimal follow primitive, not a full chat UI (no virtualization / message input). State\n * is derived from the scroll position each pass (no module-scope state), so `connect()`\n * re-syncs after a Turbo Stream insert; reduced motion forces an instant jump\n * independently of consumer CSS; auto-scroll never moves focus; the observers and the\n * passive scroll listener are released on `disconnect()` (Turbo navigation included).\n */\nexport class StickToBottomController extends Controller<HTMLElement> {\n static override targets = [\"content\", \"hasNew\"];\n static override values = {\n threshold: { type: Number, default: DEFAULT_THRESHOLD },\n behavior: { type: String, default: \"auto\" },\n pinOnConnect: { type: Boolean, default: false },\n };\n static actions = [\"scrollToBottom\"] as const;\n static events = [\"pin\", \"new\"] as const;\n\n declare readonly contentTarget: HTMLElement;\n declare readonly hasContentTarget: boolean;\n declare readonly hasNewTargets: HTMLElement[];\n\n declare thresholdValue: number;\n declare behaviorValue: string;\n declare pinOnConnectValue: boolean;\n\n #observer: MutationObserver | null = null;\n /** The element the append observer currently holds, so a swap can be detected. */\n #watched: HTMLElement | null = null;\n /** Watches for the box a container connected without one is still waiting on. */\n readonly #layout = new LayoutObserver(() => this.#onLaidOut());\n #awaitingLayout = false;\n #connected = false;\n #pinned = false;\n #hasNew = false;\n /** Owns `hidden` on the regions declared for the has-new state. */\n readonly #hasNewRegion = new StateRegions({ whenTrue: () => this.hasNewTargets });\n\n readonly #onScroll = (): void => this.#updatePinned();\n\n override connect(): void {\n this.#connected = true;\n // Instant whatever `behavior` says, overriding a consumer's `scroll-behavior: smooth`:\n // an animated jump emits scroll events on the way down, each recomputing pinned from a\n // position still far from the bottom, so the container would unpin (and re-pin)\n // mid-flight before it ever settles.\n if (this.pinOnConnectValue && this.#measurable()) this.#scrollToBottom(\"instant\");\n // Read the state back from where the container actually is, asked for a jump or not: a\n // request the engine does not honor must leave it unpinned, so the next append is\n // flagged rather than swallowed. Both hooks are re-derived here, so a stale data-pinned\n // or data-has-new a Turbo cache restore brought back is dropped: has-new records an\n // arrival this connection has not seen, and nothing in the DOM can attest to one.\n this.#setHasNew(false);\n this.#pinned = this.#isPinned();\n this.#reflectPinned();\n\n this.element.addEventListener(\"scroll\", this.#onScroll, { passive: true });\n this.#syncWatched();\n // A container with no box yet reports every metric as 0; it stays unpinned until the\n // layout it will actually be read against arrives.\n if (!this.#measurable()) this.#waitForLayout();\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.element.removeEventListener(\"scroll\", this.#onScroll);\n this.#stopWatching();\n this.#stopWaitingForLayout();\n }\n\n /** Moves the append watch onto a `content` target that arrived at runtime. */\n contentTargetConnected(): void {\n this.#syncWatched();\n }\n\n /** Moves the append watch off a `content` target that left, back onto the container. */\n contentTargetDisconnected(): void {\n this.#syncWatched();\n }\n\n /**\n * Settles a has-new region that arrived at runtime on the side the flag is currently\n * on, the way a region present at connect is settled by the connect-time normalization.\n *\n * Stimulus attaches targets before `connect()`, and a container that leaves the document\n * and comes back keeps its controller, so in that window the flag still reads the\n * arrival of the connection that ended. A region attached there is left to the\n * connect-time normalization, which drops the flag and settles the region in one write.\n */\n hasNewTargetConnected(): void {\n if (!this.#connected) return;\n this.#hasNewRegion.reflect(this.element, this.#hasNew);\n }\n\n /**\n * Re-derives pinned when the distance that counts as the bottom is changed at runtime\n * (a morph that swaps the attribute on a retained element).\n */\n thresholdValueChanged(): void {\n if (!this.#connected) return;\n this.#updatePinned();\n }\n\n /**\n * Jumps to the bottom and re-pins (wired to a \"new messages\" button).\n *\n * The has-new flag clears on request — the user has acknowledged the arrival — while\n * pinned is read back from where the scroll landed: a jump that arrives by the time\n * this returns pins immediately, an animated one settles from its own scroll events,\n * and a jump the engine cannot honor leaves the container unpinned, so the next append\n * flags it again instead of being swallowed by a pinned state that does not hold.\n *\n * Which of those happens is not this method's to decide — see {@link behaviorValue}.\n */\n scrollToBottom(): void {\n this.#scrollToBottom();\n this.#setHasNew(false);\n this.#updatePinned();\n }\n\n /** Follows appended children while pinned; otherwise flags new content. */\n #onMutations(mutations: MutationRecord[]): void {\n let added = 0;\n for (const mutation of mutations) added += countElements(mutation.addedNodes);\n if (added === 0) return;\n\n if (this.#pinned) {\n this.#scrollToBottom();\n } else {\n this.#setHasNew(true);\n this.dispatch(\"new\", { detail: { count: added } });\n }\n }\n\n /**\n * Recomputes pinned from the scroll position and reflects it on a transition.\n *\n * @stimeoRenderRoot\n */\n #updatePinned(): void {\n const pinned = this.#isPinned();\n if (pinned === this.#pinned) return;\n this.#pinned = pinned;\n this.#reflectPinned();\n this.dispatch(\"pin\", { detail: { pinned } });\n }\n\n /** Mirrors the current `#pinned` onto the state hooks (clearing has-new once pinned). */\n #reflectPinned(): void {\n if (this.#pinned) {\n this.element.setAttribute(\"data-pinned\", \"true\");\n this.#setHasNew(false); // caught up with the bottom\n } else {\n this.element.removeAttribute(\"data-pinned\");\n }\n }\n\n /**\n * Mirrors whether content has arrived that the container did not follow onto the state\n * hook and onto the regions declared for that state.\n */\n #setHasNew(hasNew: boolean): void {\n this.#hasNew = hasNew;\n if (hasNew) {\n this.element.setAttribute(\"data-has-new\", \"true\");\n } else {\n this.element.removeAttribute(\"data-has-new\");\n }\n this.#hasNewRegion.reflect(this.element, hasNew);\n }\n\n /** Whether the container currently sits within `threshold` of its bottom. */\n #isPinned(): boolean {\n if (!this.#measurable()) return false;\n const el = this.element;\n return el.scrollHeight - el.clientHeight - el.scrollTop <= this.#threshold;\n }\n\n /**\n * The distance from the bottom that counts as pinned: a finite, non-negative number of\n * pixels. Anything else names no distance the container can be at, and settles the\n * comparison the same way at every scroll position, so it falls back to the default.\n * `Number` reads `\"abc\"` as `NaN` and every comparison against it is false; a negative\n * distance sits below the closest the container ever gets; `Infinity` is never\n * exceeded. The first two stop following and flag every append as new, and the last\n * never stops following — it takes the reading position the flag exists to protect.\n * Zero is a real declaration: it pins at the exact bottom only.\n */\n get #threshold(): number {\n const declared = this.thresholdValue;\n return Number.isFinite(declared) && declared >= 0 ? declared : DEFAULT_THRESHOLD;\n }\n\n /**\n * Whether the container has a box to scroll and to measure. One that is not rendered\n * (inside a closed panel) reports every metric as 0, which reads as \"already at the\n * bottom\" — a position describing no layout the user will ever see.\n */\n #measurable(): boolean {\n return this.element.clientHeight > 0;\n }\n\n /**\n * Holds the pinned decision until the container is laid out — otherwise the panel opens\n * at the top while the state claims the bottom, and the appends that arrived meanwhile\n * were followed into a box that could not move rather than flagged.\n */\n #waitForLayout(): void {\n this.#awaitingLayout = true;\n this.#layout.observe(this.element);\n }\n\n /** Runs the held decision once the container has the box it was waiting for. */\n #onLaidOut(): void {\n if (!this.#awaitingLayout || !this.#measurable()) return;\n this.#stopWaitingForLayout();\n if (this.pinOnConnectValue) this.#scrollToBottom(\"instant\");\n this.#updatePinned();\n }\n\n /** Releases the layout watch, whether or not the held decision ever ran. */\n #stopWaitingForLayout(): void {\n this.#awaitingLayout = false;\n this.#layout.disconnect();\n }\n\n /**\n * Scrolls to the bottom, clamped by the engine to the maximum scroll offset — which is\n * 0 for a container tall enough to hold its whole content, so the jump moves nothing\n * there. `behavior` defaults to the configured follow behavior; pass `\"instant\"` for a\n * jump that must not animate.\n */\n #scrollToBottom(behavior: ScrollBehavior = this.#behavior()): void {\n const top = this.element.scrollHeight;\n if (typeof this.element.scrollTo === \"function\") {\n this.element.scrollTo({ top, behavior });\n } else {\n this.element.scrollTop = top;\n }\n }\n\n /**\n * Points the append watch at the current `content` target, or at the container when\n * there is none. Re-resolved whenever that target changes, so a swap does not leave the\n * observer holding a detached node whose appends nobody sees.\n *\n * Stimulus runs the target callbacks outside the connected window too — before\n * `connect()` for a target already in the DOM, and after `disconnect()` while the\n * element is torn down — where this would arm an observer nothing releases. Re-syncing\n * to the target already held is left alone, so an arrival still in flight is not\n * dropped with the observer that was about to deliver it.\n */\n #syncWatched(): void {\n if (!this.#connected) return;\n const next = this.hasContentTarget ? this.contentTarget : this.element;\n if (next === this.#watched) return;\n\n this.#stopWatching();\n this.#watched = next;\n if (typeof MutationObserver === \"undefined\") return;\n this.#observer = new MutationObserver((mutations) => this.#onMutations(mutations));\n this.#observer.observe(next, { childList: true });\n }\n\n /** Releases the append watch and the element it held. */\n #stopWatching(): void {\n this.#observer?.disconnect();\n this.#observer = null;\n this.#watched = null;\n }\n\n /**\n * The behavior a follow-scroll runs with. `\"auto\"` is **not** a request to arrive at\n * once: it hands the decision to the element's computed `scroll-behavior`, so a\n * consumer stylesheet saying `smooth` animates these scrolls too. Only `\"instant\"`\n * overrides that CSS, which is why reduced motion and the `pinOnConnect` jump name it.\n */\n #behavior(): ScrollBehavior {\n if (prefersReducedMotion()) return \"instant\";\n return this.behaviorValue === \"smooth\" ? \"smooth\" : \"auto\";\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/layout_observer.ts","../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/utils/number_bounds.ts","../../src/utils/coerce.ts","../../src/utils/number_value.ts","../../src/utils/reduced_motion.ts","../../src/utils/attribute_write.ts","../../src/utils/shared_registry.ts","../../src/utils/state_regions.ts","../../src/controllers/stick_to_bottom_controller.ts"],"names":[],"mappings":"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","file":"stick_to_bottom_controller.js","sourcesContent":["/**\n * Unified layout observation for Stimeo controllers.\n *\n * Widgets whose output is measured — an overflow boundary, a masonry column count,\n * an autosized textarea — have three sources that move the layout under them: their\n * *own* box changing — via {@link ResizeObserver} — the *viewport* changing — via the\n * `window` `resize` event — and a descendant *resource settling*, because an image or\n * a frame reports a height of zero until it has loaded. Wiring those three by hand in\n * every controller risks leaked listeners on `disconnect()`. {@link LayoutObserver}\n * owns all three behind one callback and one\n * {@link LayoutObserver.disconnect | disconnect()} that releases everything.\n *\n * Behavior only: the helper reports *that* layout changed; it never reads or\n * writes styles. Consumers decide what to recompute.\n */\n\n/** Invoked whenever an observed element or the viewport changes size. */\nexport type LayoutCallback = () => void;\n\n/** Constructs a {@link ResizeObserver}; injectable so tests stay deterministic. */\nexport type ResizeObserverFactory = (callback: ResizeObserverCallback) => ResizeObserver;\n\n/** Options for {@link LayoutObserver}. */\nexport interface LayoutObserverOptions {\n /**\n * Factory for the {@link ResizeObserver} used by {@link LayoutObserver.observe}.\n * Defaults to the global constructor; override it in tests, or to no-op in\n * environments where `ResizeObserver` is unavailable.\n */\n resizeObserverFactory?: ResizeObserverFactory;\n}\n\n/**\n * Observes element resizes and/or viewport resizes through a single callback,\n * with guaranteed teardown.\n *\n * @example\n * ```ts\n * #layout = new LayoutObserver(() => this.#reposition());\n *\n * connect() {\n * this.#layout.observe(this.panelTarget);\n * this.#layout.observeViewport();\n * this.#layout.observeDescendantLoads(this.panelTarget);\n * }\n *\n * disconnect() {\n * this.#layout.disconnect();\n * }\n * ```\n */\nexport class LayoutObserver {\n readonly #callback: LayoutCallback;\n readonly #resizeObserverFactory: ResizeObserverFactory | null;\n #resizeObserver: ResizeObserver | null = null;\n #observingViewport = false;\n #loadContainer: Element | null = null;\n\n /** Stable bound handler so add/removeEventListener target the same reference. */\n readonly #handleViewportResize = (): void => {\n this.#callback();\n };\n\n /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */\n readonly #handleDescendantLoad = (): void => {\n this.#callback();\n };\n\n constructor(callback: LayoutCallback, options: LayoutObserverOptions = {}) {\n this.#callback = callback;\n this.#resizeObserverFactory =\n options.resizeObserverFactory ??\n (typeof ResizeObserver === \"undefined\" ? null : (cb) => new ResizeObserver(cb));\n }\n\n /**\n * Starts observing an element's size. Repeated calls observe additional\n * elements through the same shared observer. No-ops when no\n * `ResizeObserver` implementation is available.\n */\n observe(element: Element): void {\n if (!this.#resizeObserverFactory) return;\n if (!this.#resizeObserver) {\n this.#resizeObserver = this.#resizeObserverFactory(() => {\n this.#callback();\n });\n }\n this.#resizeObserver.observe(element);\n }\n\n /** Stops observing a single element while leaving any others in place. */\n unobserve(element: Element): void {\n this.#resizeObserver?.unobserve(element);\n }\n\n /** Starts observing viewport resizes. Idempotent: the listener is added once. */\n observeViewport(): void {\n if (this.#observingViewport) return;\n this.#observingViewport = true;\n window.addEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /** Stops observing viewport resizes without affecting element observation. */\n unobserveViewport(): void {\n if (!this.#observingViewport) return;\n this.#observingViewport = false;\n window.removeEventListener(\"resize\", this.#handleViewportResize);\n }\n\n /**\n * Starts reporting a `load` from anywhere inside `container` — an image or a\n * frame settling changes the box it sits in, and it measures as zero high until\n * then. `load` does not bubble, so the subscription is a capture-phase listener\n * on the container itself and nothing the caller spells.\n *\n * **One container at a time.** A further call moves the observation, so a widget\n * whose content element is swapped at runtime releases the element it let go by\n * naming the new one — there is no second place for the release to drift from.\n */\n observeDescendantLoads(container: Element): void {\n this.unobserveDescendantLoads();\n this.#loadContainer = container;\n container.addEventListener(\"load\", this.#handleDescendantLoad, true);\n }\n\n /** Stops reporting descendant loads without affecting element or viewport observation. */\n unobserveDescendantLoads(): void {\n this.#loadContainer?.removeEventListener(\"load\", this.#handleDescendantLoad, true);\n this.#loadContainer = null;\n }\n\n /**\n * Releases every observation: disconnects the {@link ResizeObserver} and removes\n * the viewport and descendant-load listeners. Safe to call multiple times. Call\n * this from a controller's `disconnect()`.\n */\n disconnect(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.unobserveViewport();\n this.unobserveDescendantLoads();\n }\n}\n","/** What {@link ListenerSet.add} passes on besides the signal it supplies itself. */\nexport type ListenerOptions = Omit<AddEventListenerOptions, \"signal\">;\n\n/**\n * The event map a target's own `addEventListener` types its handlers with: the\n * window's, a document's, an HTML or SVG element's, any other element's, or a\n * media query list's. Other targets name none.\n */\ntype EventMapOf<T extends EventTarget> = T extends Window\n ? WindowEventMap\n : T extends Document\n ? DocumentEventMap\n : T extends HTMLElement\n ? HTMLElementEventMap\n : T extends SVGElement\n ? SVGElementEventMap\n : T extends Element\n ? ElementEventMap\n : T extends MediaQueryList\n ? MediaQueryListEventMap\n : Record<never, never>;\n\n/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt.\n * {@link ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * A lifetime shorter than the connection — while a Value holds, until a first\n * load, while a subscription is open — gets a set of its own, which the end of\n * that lifetime disposes. A controller therefore holds one set per lifetime: the\n * connection's, and one for each swapped receiver or shorter span.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, listeners of\n * several elements that come and go one at a time, and those one gesture holds\n * keep their own pair of calls or a signal of their own.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included. A removal by hand would have to repeat that\n * flag; aborting the signal releases the listener without it.\n *\n * A `type` the target's own event map names types `handler` with that event,\n * as the target's `addEventListener` does, so a handler written for a\n * `KeyboardEvent` is passed as it is; any other `type` takes an\n * `EventListener`.\n */\n add<T extends EventTarget, K extends keyof EventMapOf<T> & string>(\n target: T,\n type: K,\n handler: (event: EventMapOf<T>[K]) => void,\n options?: ListenerOptions,\n ): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Whether a requested pass has yet to run. */\n get pending(): boolean {\n return this.#queued;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Whether a pass requested by a morph or {@link schedule} has yet to run. */\n get pending(): boolean {\n return this.#pass.pending;\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","import { MAX_TIMER_DELAY_MS } from \"./timer_bounds\";\n\n/** Semantic constraints on an already decoded number, independent of the DOM. */\nexport interface NumberBounds {\n /** Finite numbers are required, including when this field is omitted. */\n readonly finite?: true;\n /** Inclusive lower endpoint. */\n readonly min?: number;\n /** Inclusive upper endpoint. */\n readonly max?: number;\n /** Exclusive lower endpoint. */\n readonly exclusiveMin?: number;\n /** Require a number without a fractional part. */\n readonly integer?: boolean;\n /** Exact decoded numbers accepted by a discrete contract. */\n readonly allowedValues?: readonly number[];\n /** JSON-safe exceptions to finite-number checking for unbounded endpoints. */\n readonly allowInfinity?: \"negative\" | \"positive\" | \"both\";\n}\n\n/** One constraint for each Number Value, including shorthand declarations. */\nexport type NumberValueConstraints<Values> = {\n readonly [Key in keyof Values as Values[Key] extends\n | NumberConstructor\n | { type: NumberConstructor }\n ? Key\n : never]: NumberBounds;\n};\n\n/** Reusable semantic domains; fractional inputs remain fractional unless excluded. */\nexport const NUMBER_BOUNDS = {\n finite: { finite: true },\n nonNegative: { finite: true, min: 0 },\n positive: { finite: true, exclusiveMin: 0 },\n nonNegativeInteger: { finite: true, min: 0, integer: true },\n positiveInteger: { finite: true, exclusiveMin: 0, integer: true },\n lowerBound: { finite: true, allowInfinity: \"negative\" },\n upperBound: { finite: true, allowInfinity: \"positive\" },\n timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS },\n positiveTimer: { finite: true, exclusiveMin: 0, max: MAX_TIMER_DELAY_MS },\n} as const satisfies Record<string, NumberBounds>;\n\n/**\n * Checks all declared bounds without rounding, clamping, or coercing the input.\n * Infinity exceptions relax only finite checking; every other bound still applies.\n */\nexport function matchesNumberBounds(value: number, bounds: NumberBounds): boolean {\n if (!Number.isFinite(value)) {\n const direction = value === Infinity ? \"positive\" : value === -Infinity ? \"negative\" : null;\n if (direction === null) return false;\n if (bounds.allowInfinity !== \"both\" && bounds.allowInfinity !== direction) return false;\n }\n if (bounds.min !== undefined && value < bounds.min) return false;\n if (bounds.max !== undefined && value > bounds.max) return false;\n if (bounds.exclusiveMin !== undefined && value <= bounds.exclusiveMin) return false;\n if (bounds.integer && !Number.isInteger(value)) return false;\n if (bounds.allowedValues !== undefined && !bounds.allowedValues.includes(value)) return false;\n return true;\n}\n\n/** Decodes a Number Value literal; action params use their own JSON decoder. */\nexport function decodeNumberValue(raw: string): number {\n return Number(raw.replace(/_/g, \"\"));\n}\n","/**\n * Numeric coercion for controllers that accept a number through a Value, an action\n * param, or an event detail.\n *\n * Stimulus decodes action params as JSON when possible and leaves other text\n * untouched. CustomEvent detail can also supply a number or numeric string.\n * The reader accepts those two types while rejecting blank and non-finite input.\n */\n\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/** Numeric update input, accepting decoded action params before CustomEvent detail. */\nexport type NumericValueEvent = Event & {\n params?: { amount?: unknown };\n detail?: { value?: unknown };\n};\n\n/** Reads the nullish action-param/detail precedence without falling back from invalid input. */\nexport function eventNumber(event: NumericValueEvent): number | null {\n return toFiniteNumber(event.params?.amount ?? event.detail?.value);\n}\n\n/**\n * Reads a finite number or nonblank numeric string; other input returns `null`.\n * Blank strings cannot silently reset a value to zero.\n */\nexport function toFiniteNumber(raw: unknown): number | null {\n if (typeof raw !== \"number\" && typeof raw !== \"string\") return null;\n if (typeof raw === \"string\" && raw.trim().length === 0) return null;\n const value = Number(raw);\n return Number.isFinite(value) ? value : null;\n}\n\n/**\n * Returns an accepted decoded Number Value or the caller's fallback unchanged.\n * The caller owns the fallback contract; this reader does not parse or write a Value.\n */\nexport function readNumber(raw: number, fallback: number, bounds: NumberBounds): number {\n return matchesNumberBounds(raw, bounds) ? raw : fallback;\n}\n","import { readNumber } from \"./coerce\";\nimport { matchesNumberBounds, type NumberBounds } from \"./number_bounds\";\n\n/** The instance identity carried by a Number Value read. */\nexport interface NumberValueOwner {\n readonly identifier: string;\n readonly element: Element;\n}\n\n/** Reads current declarations at the point of use without rewriting their attributes. */\nexport class NumberValueReader {\n /** The last rejected literal per Value; no history grows for an unchanged instance. */\n readonly #lastRejected = new Map<string, string>();\n\n /** Resolves the declared number using its own fallback and shared class contract. */\n read(\n owner: NumberValueOwner,\n name: string,\n raw: number,\n fallback: number,\n bounds: NumberBounds,\n ): number {\n const resolved = readNumber(raw, fallback, bounds);\n if (matchesNumberBounds(raw, bounds)) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;\n const literal = owner.element.getAttribute(attribute);\n if (literal === null) {\n this.#lastRejected.delete(name);\n return resolved;\n }\n if (this.#lastRejected.get(name) !== literal) {\n this.#lastRejected.set(name, literal);\n console.warn(\n `Stimeo UI: \"${owner.identifier}\" has an invalid number Value \"${name}\" declaration ${JSON.stringify(literal)}; using ${fallback}.`,\n );\n }\n return resolved;\n }\n}\n","/**\n * Shared `prefers-reduced-motion` lookup for the motion-aware controllers.\n *\n * This one-liner keeps the media query string and the environment guard\n * single-sourced across them. The preference is intentionally re-read on every\n * call — the controllers check it at each animation/scroll start (WCAG 2.2\n * **2.3.3**), so flipping the OS setting takes effect immediately without any\n * listener or cache bookkeeping here.\n */\n\n/**\n * Whether the user currently requests reduced motion.\n *\n * @returns `true` when `(prefers-reduced-motion: reduce)` matches; `false`\n * otherwise, including environments without `window.matchMedia` (treated as\n * \"no preference\").\n */\nexport function prefersReducedMotion(): boolean {\n return (\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n );\n}\n","/**\n * Reflects a serialized attribute value only when the current value differs.\n *\n * A same-value `setAttribute` still queues a mutation record for every observer, so an\n * unchanged value writes nothing.\n *\n * @param element - The element whose attribute is reflected.\n * @param name - The attribute name, using the non-namespaced DOM API.\n * @param value - The serialized value, or null to remove the attribute.\n * @param beforeWrite - Called only for a change, before the DOM mutation, so the\n * caller can record ownership or identify its own observer records.\n * @returns Whether an attribute was written or removed.\n */\nexport function setAttributeIfChanged(\n element: Element,\n name: string,\n value: string | null,\n beforeWrite?: () => void,\n): boolean {\n if (element.getAttribute(name) === value) return false;\n beforeWrite?.();\n if (value === null) element.removeAttribute(name);\n else element.setAttribute(name, value);\n return true;\n}\n","/**\n * One registry per page for every copy of the library.\n *\n * Each `stimeo-ui/controllers/*` file, and each opt-in entry, is built on its own and carries\n * its own copy of every utility it uses, so a page that imports controllers one by one, or one\n * of them next to the barrel, evaluates a utility several times. A registry that coordinates\n * instances of different controllers — the stacks of leased values, the loans of a\n * `tabindex`, the Escape layers, the focus traps — must not be split by that: a dialog inside\n * a drawer, or a character counter and a form field on one input, coordinate whichever files\n * they came from. So such a registry lives once on `globalThis`, under a `Symbol.for` key the\n * copies agree on, and the first copy to ask for it creates it.\n *\n * The key names the registry and the version of its shape\n * (`stimeo-ui.<utility>.registry.v<n>`). Copies whose registries have the same shape share\n * one; a release that changes the shape changes the version, so a copy of another release\n * keeps a registry of its own instead of reading entries it does not understand — the two\n * then coordinate within each release only. An entry another copy put in a registry is read\n * through its fields alone, never through `#private` members, which belong to the class of\n * the copy that created it.\n */\n\n/**\n * The registry stored under `key`, created by `create` on first use.\n *\n * @param key - `stimeo-ui.<utility>.registry.v<n>`: the version is that of the shape `create`\n * returns.\n * @param create - Builds the empty registry.\n */\nexport function sharedRegistry<T extends object>(key: string, create: () => T): T {\n const symbol = Symbol.for(key);\n const scope = globalThis as unknown as Record<symbol, T | undefined>;\n const existing = scope[symbol];\n if (existing !== undefined) return existing;\n const registry = create();\n Object.defineProperty(globalThis, symbol, { value: registry });\n return registry;\n}\n","import { setAttributeIfChanged } from \"./attribute_write\";\nimport { sharedRegistry } from \"./shared_registry\";\n\n/** What a consumer declares once about the regions one boolean state reveals. */\nexport interface StateRegionsOptions {\n /** Returns the declared regions shown while the state holds. */\n readonly whenTrue: () => readonly HTMLElement[];\n /**\n * Returns the declared regions shown while the state does not hold. Omitted by a\n * state whose other side has nothing of its own to show.\n */\n readonly whenFalse?: () => readonly HTMLElement[];\n}\n\n/** One instance's hold on one region's `hidden`, in the stack every instance shares. */\ninterface RegionHold {\n /** The instance holding it; `null` for a hold read from a copy's record until it is taken. */\n instance: object | null;\n readonly record: string;\n /** What the author wrote: the `hidden` it hands back with nothing beneath it. */\n readonly authored: string | null;\n /** Whether the visibility it wrote last hides the region. */\n written: boolean;\n /** Whether it hands back what the hold beneath it wrote last instead. */\n linked: boolean;\n}\n\n/** What an instance's record on a region says. */\ninterface RegionRecord {\n readonly authored: string | null;\n readonly written: boolean;\n readonly linked: boolean;\n /** The records of the other owners' holds beneath it, nearest first. */\n readonly beneath: readonly string[];\n}\n\n/** The holds on each region and every instance's record name. */\ninterface StateRegionsRegistry {\n readonly stacks: WeakMap<HTMLElement, RegionHold[]>;\n readonly names: Set<string>;\n}\n\n/**\n * Owns `hidden` on the regions a boolean state reveals.\n *\n * A region is a **declared** target, so the consumer decides whether one exists at\n * all; where it does, which side is shown is a pure function of the state and the\n * controller writes it unconditionally. An authored `hidden` is therefore not read\n * back — the first reflection after a connection settles it, and a restored DOM\n * cannot leave a region contradicting the state it belongs to.\n *\n * The regions arrive as the caller's own target arrays, which Stimulus has already\n * scoped to this instance: a nested controller registered under the same identifier\n * keeps its own regions. `host` narrows that set further, to the regions inside one\n * trigger or item, so a widget with several of them reflects each independently.\n *\n * **Two sides are a pair.** Where the state has a `whenFalse` side and only one half\n * sits inside `host`, the half that is there is left in view: hiding it would take\n * the only label with it, and the static check names the missing one instead. What\n * the author wrote on a lone half is theirs and stays — only a half this instance\n * took out of view is given back, which is what a pair losing one side at runtime\n * leaves behind.\n *\n * `hidden` moves only where it changes, and the record below only where what it says changes:\n * a transition costs a consumer watching its own subtree for attribute mutations two of them on\n * each region it moves — `hidden` and the record — and a reflection that moves nothing costs\n * none. A write that moves no `hidden` — the page moved the region first, or another\n * instance's hold lies above this one — changes only the record.\n *\n * **Every instance shares one stack of holds per region**, whatever its owner and whichever\n * file of the package constructed it (the stacks are one per page): the holds sit in the order\n * their instances first wrote the region, each with the visibility it wrote last. The region's\n * visibility is the highest hold's that carries it, and the page's when none does. A write\n * moves `hidden` unless a hold above the writer carries the region's visibility, so the top\n * hold always shows its writes and the page's visibility gives way to the next write of any\n * hold. A release gives a visibility back only from the hold the region's visibility is: the\n * one the hold beneath it wrote last, or the author's `hidden` once nothing is beneath it; any\n * other release only leaves the stack. Holds that wrote the same visibility are told apart by\n * the stack: the higher one keeps it. A first write over a top hold whose visibility the page\n * has replaced takes the region's `hidden` as the author's. Two instances of one owner on one\n * region stack the same way.\n *\n * **The stack travels with the region.** While the region carries anything but what the author\n * wrote under its top hold, or a hold lies on another hold or is covered by one, the region\n * carries that hold's owner's record `data-<owner>-hidden-region`: JSON of what the author\n * wrote (a string, or `null` for an absent attribute), whether the visibility written last\n * hides the region, `false` where the hold hands back the author's `hidden` rather than the\n * latest visibility of the hold beneath it, and the records of every hold beneath it, nearest\n * first; the last release of an owner removes it. A copy of the region — a page Turbo restores\n * from its cache — therefore carries the stack, read back under the record names of the\n * instances constructed so far and ordered by the records each hold lists beneath it, and an\n * instance that reflects, gives back or releases the copy takes the hold its owner's record\n * describes over in place. Complete records for distinct owners preserve their order across\n * a copy, including release without a new reflection. Records describe owners, not instances:\n * one owner holding the region more than once leaves only its topmost hold's record, which\n * the first of its instances to write or release on the copy takes. Its lower holds and\n * positions are absent; mutually listed owners cannot express their full live order. A hold\n * that showed the author's `hidden` alone carries no record and is taken by its first write\n * on the copy. Missing or unreadable records also leave their holds unknown, so copying the\n * DOM does not promise identical decisions for every live stack. A new stack with no readable\n * record takes the current value as the author's; a morph can remove records while existing\n * holds remain in memory until released.\n *\n * @example\n * ```ts\n * readonly #labels = new StateRegions(\n * {\n * whenTrue: () => this.expandedLabelTargets,\n * whenFalse: () => this.collapsedLabelTargets,\n * },\n * this.identifier,\n * );\n *\n * #reflect(): void {\n * this.#labels.reflect(this.triggerTarget, this.#expanded);\n * }\n * ```\n */\nexport class StateRegions {\n /** The holds on each region, bottom first, and the record names; one per page. */\n static readonly #registry = sharedRegistry(\n \"stimeo-ui.state-regions.registry.v1\",\n (): StateRegionsRegistry => ({ stacks: new WeakMap(), names: new Set() }),\n );\n readonly #whenTrue: () => readonly HTMLElement[];\n readonly #whenFalse: (() => readonly HTMLElement[]) | null;\n readonly #record: string;\n\n /**\n * @param options - The declared regions of each side.\n * @param owner - Names the record on a region; the controller's identifier.\n */\n constructor(options: StateRegionsOptions, owner: string) {\n this.#whenTrue = options.whenTrue;\n this.#whenFalse = options.whenFalse ?? null;\n this.#record = `data-${owner}-hidden-region`;\n StateRegions.#registry.names.add(this.#record);\n }\n\n /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */\n reflect(host: Element, isTrue: boolean): void {\n const shown = this.#inside(host, this.#whenTrue());\n if (!this.#whenFalse) {\n for (const region of shown) this.#write(region, !isTrue);\n return;\n }\n const hiddenSide = this.#inside(host, this.#whenFalse());\n if (shown.length === 0 || hiddenSide.length === 0) {\n for (const region of shown) this.#give(region);\n for (const region of hiddenSide) this.#give(region);\n return;\n }\n for (const region of shown) this.#write(region, !isTrue);\n for (const region of hiddenSide) this.#write(region, isTrue);\n }\n\n /**\n * Restores the regions this instance wrote inside `host` to what the hold beneath it wrote last\n * or the authored `hidden`, unless changed since; found by scan, as the declared lists stop\n * resolving with targets.\n */\n release(host: Element): void {\n for (const region of [host, ...host.querySelectorAll(\"*\")]) {\n if (!(region instanceof HTMLElement)) continue;\n const stack = this.#held(region);\n const hold = stack && this.#take(stack);\n if (!stack || !hold) continue;\n const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);\n const index = stack.indexOf(hold);\n stack.splice(index, 1);\n const above = stack[index];\n if (above) above.linked &&= hold.linked;\n if (owns) {\n const below = hold.linked ? stack[index - 1] : undefined;\n if (below) {\n if (region.hidden !== below.written) region.hidden = below.written;\n } else {\n setAttributeIfChanged(region, \"hidden\", hold.authored);\n }\n }\n this.#mark(region, stack);\n }\n }\n\n /** Writes `hidden` where it moves, unless a hold above this one carries the region's. */\n #write(region: HTMLElement, hidden: boolean): void {\n const stack = this.#stack(region);\n const hold = this.#take(stack) ?? this.#acquire(region, stack);\n const shows = !this.#covered(region, stack, hold);\n hold.written = hidden;\n if (shows && region.hidden !== hidden) region.hidden = hidden;\n this.#mark(region, stack);\n }\n\n /** Returns a region this instance hid; one it never hid keeps what it carries. */\n #give(region: HTMLElement): void {\n const stack = this.#held(region);\n if (stack && this.#take(stack)?.written) this.#write(region, false);\n }\n\n /** The region's stack, while it has one or carries this owner's record. */\n #held(region: HTMLElement): RegionHold[] | undefined {\n return StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record)\n ? this.#stack(region)\n : undefined;\n }\n\n /** The region's stack, read from its records the first time. */\n #stack(region: HTMLElement): RegionHold[] {\n const stacks = StateRegions.#registry.stacks;\n const stack = stacks.get(region) ?? this.#seed(region);\n stacks.set(region, stack);\n return stack;\n }\n\n /** The holds a copy's records describe, each above every hold its record lists beneath. */\n #seed(region: HTMLElement): RegionHold[] {\n const found: (RegionRecord & { readonly record: string })[] = [];\n for (const record of StateRegions.#registry.names) {\n const read = this.#recorded(region, record);\n if (read) found.push({ ...read, record });\n }\n const depth = ({ beneath }: (typeof found)[number]) =>\n beneath.filter((name) => found.some((other) => other.record === name)).length;\n return found\n .sort((one, other) => depth(one) - depth(other))\n .map(({ record, authored, written, linked }, index) => ({\n instance: null,\n record,\n authored,\n written,\n linked: linked && index > 0,\n }));\n }\n\n /** Its hold in `stack`, taking over one read from its record. */\n #take(stack: RegionHold[]): RegionHold | undefined {\n const hold =\n stack.find((held) => held.instance === this) ??\n stack.find((held) => held.instance === null && held.record === this.#record);\n if (hold) hold.instance = this;\n return hold;\n }\n\n /** Whether a hold above `hold` carries the region's visibility, then that hold's. */\n #covered(region: HTMLElement, stack: readonly RegionHold[], hold: RegionHold): boolean {\n return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);\n }\n\n /** A new hold on top, over the top hold while the region shows what it wrote. */\n #acquire(region: HTMLElement, stack: RegionHold[]): RegionHold {\n const top = stack.at(-1);\n const over = top && region.hidden === top.written ? top : undefined;\n const hold: RegionHold = {\n instance: this,\n record: this.#record,\n authored: over ? over.authored : region.getAttribute(\"hidden\"),\n written: false,\n linked: over !== undefined,\n };\n stack.push(hold);\n return hold;\n }\n\n /** Leaves each owner's record of its topmost hold, or none where nothing needs one. */\n #mark(region: HTMLElement, stack: readonly RegionHold[]): void {\n const records = new Map<string, string | null>([[this.#record, null]]);\n stack.forEach((hold, index) => {\n const names = stack.slice(0, index).map((under) => under.record);\n const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);\n const marker = hold.linked || beneath.length === 0 ? [] : [false];\n records.set(\n hold.record,\n region.getAttribute(\"hidden\") === hold.authored &&\n beneath.length === 0 &&\n index === stack.length - 1\n ? null\n : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath]),\n );\n });\n for (const [name, recorded] of records) {\n setAttributeIfChanged(region, name, recorded);\n }\n }\n\n /**\n * The author's `hidden`, the visibility written last, whether it hands back the visibility\n * beneath it (`false` before the names when it does not) and the records beneath that the\n * record `name` on `region` holds.\n */\n #recorded(region: HTMLElement, name: string): RegionRecord | null {\n try {\n const parsed: unknown = JSON.parse(region.getAttribute(name) ?? \"null\");\n if (!Array.isArray(parsed)) return null;\n const [authored, written, ...rest] = parsed as unknown[];\n if (authored !== null && typeof authored !== \"string\") return null;\n const linked = rest[0] !== false;\n const beneath = linked ? rest : rest.slice(1);\n if (!beneath.every((under): under is string => typeof under === \"string\")) return null;\n return typeof written === \"boolean\" ? { authored, written, linked, beneath } : null;\n } catch {\n return null;\n }\n }\n\n /** The declared regions that sit within `host`, which may be the host itself. */\n #inside(host: Element, regions: readonly HTMLElement[]): HTMLElement[] {\n return regions.filter((region) => host.contains(region));\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { LayoutObserver } from \"../utils/layout_observer\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\nimport { NUMBER_BOUNDS, type NumberValueConstraints } from \"../utils/number_bounds\";\nimport { NumberValueReader } from \"../utils/number_value\";\nimport { prefersReducedMotion } from \"../utils/reduced_motion\";\nimport { StateRegions } from \"../utils/state_regions\";\n\n/** Distance from the bottom, in px, that still counts as pinned. */\nconst DEFAULT_THRESHOLD = 80;\n\n/** Counts the element nodes in an added-node list (text nodes are ignored). */\nconst countElements = (nodes: NodeList): number => {\n let n = 0;\n for (const node of nodes) if (node.nodeType === Node.ELEMENT_NODE) n += 1;\n return n;\n};\n\n/**\n * Headless **stick-to-bottom**: auto-follows new content to the bottom of a scroll\n * container (a chat log, a live console) — but only while the user is already near the\n * bottom; if they have scrolled up to read, it holds position and flags that new content\n * arrived instead (no APG pattern; honors WCAG 2.3.3 via `prefers-reduced-motion` and\n * never steals focus).\n *\n * Markup contract (identifier: `stimeo--stick-to-bottom`):\n * <div data-controller=\"stimeo--stick-to-bottom\"\n * data-stimeo--stick-to-bottom-threshold-value=\"80\"\n * data-stimeo--stick-to-bottom-pin-on-connect-value=\"true\" style=\"overflow:auto\">\n * <ul data-stimeo--stick-to-bottom-target=\"content\"><!-- Turbo Stream appends --></ul>\n * <button type=\"button\" hidden data-stimeo--stick-to-bottom-target=\"hasNew\"\n * data-action=\"stimeo--stick-to-bottom#scrollToBottom\">New messages</button>\n * </div>\n *\n * The container is \"pinned\" while it has a box to measure and its distance from the\n * bottom is within `threshold`. A `MutationObserver` on `content` (or the element) reacts\n * to appended children: while pinned it scrolls to the bottom; while unpinned it sets\n * `data-has-new` and emits `new`. Scrolling recomputes pinned and reflects `data-pinned`,\n * emitting `pin` on change; the `scrollToBottom` action jumps back down (a \"new messages\"\n * button). A `threshold` changed at runtime re-derives the state at once.\n *\n * `data-has-new` says which state the container is in; the optional `hasNew` targets are\n * the regions that belong to the flagged side — the affordance offering the jump back\n * down. They are shown while the flag is up and hidden while it is down, written wherever\n * the flag moves, so an authored `hidden` settles at the first reflection and a restored\n * DOM cannot leave the region contradicting the flag.\n *\n * A container that already overflows renders at `scrollTop` 0 — further than\n * `threshold` from the bottom, so unpinned — and flags the first append rather\n * than following it. `pinOnConnect` opts into the other starting point:\n * `connect()` jumps to the bottom and reads the state back from where it landed. It runs\n * on every connect, so a container inserted or re-rendered later (a Turbo Stream, a panel\n * built on open) starts at the bottom too — which a once-per-document page event cannot\n * do. It is off by default, so a restored reading position (a Turbo cache restore, a user\n * who had scrolled up) is never yanked to the bottom unless the consumer asks for it.\n *\n * A container connected without a box — inside a closed panel — reports every metric as\n * 0, so it is held unpinned until layout arrives: appends meanwhile are flagged rather\n * than followed into a box that cannot move, and the decision is then made against the\n * layout the user actually sees.\n *\n * `pin` dispatches `{ pinned }`; `new` dispatches `{ count }`.\n *\n * @remarks\n * The `MutationObserver` watches `childList` only (not `subtree`), so it follows\n * direct appends to `content` (or the element). Appends made deeper inside a nested\n * wrapper are not detected — keep messages as direct children, or call the public\n * `scrollToBottom` action after such inserts. A `content` target that is swapped at\n * runtime moves the watch with it.\n *\n * Behavior only — it does not add content (Turbo Stream / the consumer does) and is the\n * minimal follow primitive, not a full chat UI (no virtualization / message input). State\n * is derived from the scroll position each pass (no module-scope state), so `connect()`\n * re-syncs after a Turbo Stream insert; reduced motion forces an instant jump\n * independently of consumer CSS; auto-scroll never moves focus; the observers and the\n * passive scroll listener are released on `disconnect()` (Turbo navigation included).\n */\nexport class StickToBottomController extends Controller<HTMLElement> {\n /** Numeric read boundaries share one reader for this controller instance. */\n readonly #numbers = new NumberValueReader();\n\n static override targets = [\"content\", \"hasNew\"];\n static override values = {\n threshold: { type: Number, default: DEFAULT_THRESHOLD },\n behavior: { type: String, default: \"auto\" },\n pinOnConnect: { type: Boolean, default: false },\n };\n\n static valueConstraints = {\n threshold: NUMBER_BOUNDS.nonNegative,\n } satisfies NumberValueConstraints<typeof StickToBottomController.values>;\n static actions = [\"scrollToBottom\"] as const;\n static events = [\"pin\", \"new\"] as const;\n\n declare readonly contentTarget: HTMLElement;\n declare readonly hasContentTarget: boolean;\n declare readonly hasNewTargets: HTMLElement[];\n\n declare thresholdValue: number;\n declare behaviorValue: string;\n declare pinOnConnectValue: boolean;\n\n #observer: MutationObserver | null = null;\n /** The element the append observer currently holds, so a swap can be detected. */\n #watched: HTMLElement | null = null;\n /** Watches for the box a container connected without one is still waiting on. */\n readonly #layout = new LayoutObserver(() => this.#onLaidOut());\n #awaitingLayout = false;\n #connected = false;\n #pinned = false;\n #hasNew = false;\n /** Owns `hidden` on the regions declared for the has-new state. */\n readonly #hasNewRegion = new StateRegions(\n { whenTrue: () => this.hasNewTargets },\n this.identifier,\n );\n\n readonly #onScroll = (): void => this.#updatePinned();\n\n readonly #morphRender = new MorphRenderWatcher(() => {\n this.#updatePinned();\n this.#setHasNew(this.#hasNew);\n });\n\n /** The listeners of one connection, released together on `disconnect()`. */\n readonly #listeners = new ListenerSet();\n\n override connect(): void {\n this.#morphRender.observe(this.element);\n this.#connected = true;\n // Instant whatever `behavior` says, overriding a consumer's `scroll-behavior: smooth`:\n // an animated jump emits scroll events on the way down, each recomputing pinned from a\n // position still far from the bottom, so the container would unpin (and re-pin)\n // mid-flight before it ever settles.\n if (this.pinOnConnectValue && this.#measurable()) this.#scrollToBottom(\"instant\");\n // Read the state back from where the container actually is, asked for a jump or not: a\n // request the engine does not honor must leave it unpinned, so the next append is\n // flagged rather than swallowed. Both hooks are re-derived here, so a stale data-pinned\n // or data-has-new a Turbo cache restore brought back is dropped: has-new records an\n // arrival this connection has not seen, and nothing in the DOM can attest to one.\n this.#setHasNew(false);\n this.#pinned = this.#isPinned();\n this.#reflectPinned();\n\n this.#listeners.add(this.element, \"scroll\", this.#onScroll, { passive: true });\n this.#syncWatched();\n // A container with no box yet reports every metric as 0; it stays unpinned until the\n // layout it will actually be read against arrives.\n if (!this.#measurable()) this.#waitForLayout();\n }\n\n override disconnect(): void {\n this.#morphRender.disconnect();\n this.#connected = false;\n this.#listeners.dispose();\n this.#stopWatching();\n this.#stopWaitingForLayout();\n }\n\n /** Moves the append watch onto a `content` target that arrived at runtime. */\n contentTargetConnected(): void {\n this.#syncWatched();\n }\n\n /** Moves the append watch off a `content` target that left, back onto the container. */\n contentTargetDisconnected(): void {\n this.#syncWatched();\n }\n\n /**\n * Settles a has-new region that arrived at runtime on the side the flag is currently\n * on, the way a region present at connect is settled by the connect-time normalization.\n *\n * Stimulus attaches targets before `connect()`, and a container that leaves the document\n * and comes back keeps its controller, so in that window the flag still reads the\n * arrival of the connection that ended. A region attached there is left to the\n * connect-time normalization, which drops the flag and settles the region in one write.\n */\n hasNewTargetConnected(): void {\n if (!this.#connected) return;\n this.#hasNewRegion.reflect(this.element, this.#hasNew);\n }\n\n /**\n * Re-derives pinned when the distance that counts as the bottom is changed at runtime\n * (a morph that swaps the attribute on a retained element).\n */\n thresholdValueChanged(): void {\n if (!this.#connected) return;\n this.#updatePinned();\n }\n\n /**\n * Jumps to the bottom and re-pins (wired to a \"new messages\" button).\n *\n * The has-new flag clears on request — the user has acknowledged the arrival — while\n * pinned is read back from where the scroll landed: a jump that arrives by the time\n * this returns pins immediately, an animated one settles from its own scroll events,\n * and a jump the engine cannot honor leaves the container unpinned, so the next append\n * flags it again instead of being swallowed by a pinned state that does not hold.\n *\n * Whether the jump arrives at once or animates follows the `behavior` value: `\"auto\"`\n * defers to the element's computed `scroll-behavior`, `\"smooth\"` animates, and reduced\n * motion forces an instant jump.\n */\n scrollToBottom(): void {\n this.#scrollToBottom();\n this.#setHasNew(false);\n this.#updatePinned();\n }\n\n /** Follows appended children while pinned; otherwise flags new content. */\n #onMutations(mutations: MutationRecord[]): void {\n let added = 0;\n for (const mutation of mutations) added += countElements(mutation.addedNodes);\n if (added === 0) return;\n\n if (this.#pinned) {\n this.#scrollToBottom();\n } else {\n this.#setHasNew(true);\n this.dispatch(\"new\", { detail: { count: added } });\n }\n }\n\n /**\n * Recomputes pinned from the scroll position and reflects it on a transition.\n *\n * @stimeoRenderRoot\n */\n #updatePinned(): void {\n const pinned = this.#isPinned();\n const changed = pinned !== this.#pinned;\n this.#pinned = pinned;\n this.#reflectPinned();\n if (changed) this.dispatch(\"pin\", { detail: { pinned } });\n }\n\n /** Mirrors the current `#pinned` onto the state hooks (clearing has-new once pinned). */\n #reflectPinned(): void {\n if (this.#pinned) {\n this.element.setAttribute(\"data-pinned\", \"true\");\n this.#setHasNew(false); // caught up with the bottom\n } else {\n this.element.removeAttribute(\"data-pinned\");\n }\n }\n\n /**\n * Mirrors whether content has arrived that the container did not follow onto the state\n * hook and onto the regions declared for that state.\n */\n #setHasNew(hasNew: boolean): void {\n this.#hasNew = hasNew;\n if (hasNew) {\n this.element.setAttribute(\"data-has-new\", \"true\");\n } else {\n this.element.removeAttribute(\"data-has-new\");\n }\n this.#hasNewRegion.reflect(this.element, hasNew);\n }\n\n /** Whether the container currently sits within `threshold` of its bottom. */\n #isPinned(): boolean {\n if (!this.#measurable()) return false;\n const el = this.element;\n return el.scrollHeight - el.clientHeight - el.scrollTop <= this.#threshold;\n }\n\n /**\n * The distance from the bottom that counts as pinned: a finite, non-negative number of\n * pixels. Anything else names no distance the container can be at, and settles the\n * comparison the same way at every scroll position, so it falls back to the default.\n * `Number` reads `\"abc\"` as `NaN` and every comparison against it is false; a negative\n * distance sits below the closest the container ever gets; `Infinity` is never\n * exceeded. The first two stop following and flag every append as new, and the last\n * never stops following — it takes the reading position the flag exists to protect.\n * Zero is a real declaration: it pins at the exact bottom only.\n */\n get #threshold(): number {\n return this.#safeThreshold;\n }\n\n /**\n * Whether the container has a box to scroll and to measure. One that is not rendered\n * (inside a closed panel) reports every metric as 0, which reads as \"already at the\n * bottom\" — a position describing no layout the user will ever see.\n */\n #measurable(): boolean {\n return this.element.clientHeight > 0;\n }\n\n /**\n * Holds the pinned decision until the container is laid out — otherwise the panel would\n * open at the top while the state claims the bottom, and appends arriving meanwhile would\n * be followed into a box that cannot move instead of being flagged.\n */\n #waitForLayout(): void {\n this.#awaitingLayout = true;\n this.#layout.observe(this.element);\n }\n\n /** Runs the held decision once the container has the box it was waiting for. */\n #onLaidOut(): void {\n if (!this.#awaitingLayout || !this.#measurable()) return;\n this.#stopWaitingForLayout();\n if (this.pinOnConnectValue) this.#scrollToBottom(\"instant\");\n this.#updatePinned();\n }\n\n /** Releases the layout watch, whether or not the held decision ever ran. */\n #stopWaitingForLayout(): void {\n this.#awaitingLayout = false;\n this.#layout.disconnect();\n }\n\n /**\n * Scrolls to the bottom, clamped by the engine to the maximum scroll offset — which is\n * 0 for a container tall enough to hold its whole content, so the jump moves nothing\n * there. `behavior` defaults to the configured follow behavior; pass `\"instant\"` for a\n * jump that must not animate.\n */\n #scrollToBottom(behavior: ScrollBehavior = this.#behavior()): void {\n const top = this.element.scrollHeight;\n if (typeof this.element.scrollTo === \"function\") {\n this.element.scrollTo({ top, behavior });\n } else {\n this.element.scrollTop = top;\n }\n }\n\n /**\n * Points the append watch at the current `content` target, or at the container when\n * there is none. Re-resolved whenever that target changes, so a swap does not leave the\n * observer holding a detached node whose appends nobody sees.\n *\n * Stimulus runs the target callbacks outside the connected window too — before\n * `connect()` for a target already in the DOM, and after `disconnect()` while the\n * element is torn down — where this would arm an observer nothing releases. Re-syncing\n * to the target already held is left alone, so an arrival still in flight is not\n * dropped with the observer that was about to deliver it.\n */\n #syncWatched(): void {\n if (!this.#connected) return;\n const next = this.hasContentTarget ? this.contentTarget : this.element;\n if (next === this.#watched) return;\n\n this.#stopWatching();\n this.#watched = next;\n if (typeof MutationObserver === \"undefined\") return;\n this.#observer = new MutationObserver((mutations) => this.#onMutations(mutations));\n this.#observer.observe(next, { childList: true });\n }\n\n /** Releases the append watch and the element it held. */\n #stopWatching(): void {\n this.#observer?.disconnect();\n this.#observer = null;\n this.#watched = null;\n }\n\n /**\n * The behavior a follow-scroll runs with. `\"auto\"` is **not** a request to arrive at\n * once: it hands the decision to the element's computed `scroll-behavior`, so a\n * consumer stylesheet saying `smooth` animates these scrolls too. Only `\"instant\"`\n * overrides that CSS, which is why reduced motion and the `pinOnConnect` jump name it.\n */\n #behavior(): ScrollBehavior {\n if (prefersReducedMotion()) return \"instant\";\n return this.behaviorValue === \"smooth\" ? \"smooth\" : \"auto\";\n }\n /** Current `threshold` declaration resolved against its numeric contract. */\n get #safeThreshold(): number {\n return this.#numbers.read(\n this,\n \"threshold\",\n this.thresholdValue,\n StickToBottomController.values.threshold.default,\n StickToBottomController.valueConstraints.threshold,\n );\n }\n}\n"]}
@@ -45,9 +45,15 @@ declare class StickyObserverController extends Controller<HTMLElement> {
45
45
  default: number;
46
46
  };
47
47
  };
48
+ static valueConstraints: {
49
+ offset: {
50
+ readonly finite: true;
51
+ };
52
+ };
48
53
  static events: readonly ["change"];
49
54
  readonly sentinelTarget: HTMLElement;
50
55
  readonly elementTarget: HTMLElement;
56
+ readonly elementTargets: HTMLElement[];
51
57
  readonly hasSentinelTarget: boolean;
52
58
  readonly hasElementTarget: boolean;
53
59
  rootSelectorValue: string;
@@ -60,6 +66,11 @@ declare class StickyObserverController extends Controller<HTMLElement> {
60
66
  sentinelTargetDisconnected(): void;
61
67
  /** Reflects the last snapshot onto an element inserted after that snapshot. */
62
68
  elementTargetConnected(element: HTMLElement): void;
69
+ /**
70
+ * Gives a sticky element that no longer resolves its own `data-stuck` back, even after
71
+ * `disconnect()`, and while connected reflects the last snapshot onto the one that stays.
72
+ */
73
+ elementTargetDisconnected(element: HTMLElement): void;
63
74
  /** Rebuilds the observer when Turbo morphs the configured root. */
64
75
  rootSelectorValueChanged(): void;
65
76
  /** Rebuilds the observer when Turbo morphs the configured top offset. */