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,37 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/overflow_menu_controller.ts
4
-
5
- // src/utils/before_cache_reset.ts
6
- var BeforeCacheReset = class _BeforeCacheReset {
7
- /** Every subscribed instance, iterated by the one shared document listener. */
8
- static #subscribers = /* @__PURE__ */ new Set();
9
- /** The shared listener; installed while at least one instance is subscribed. */
10
- static #onBeforeCache = () => {
11
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
12
- };
13
- #rewind;
14
- /** @param rewind - the pass that returns this controller's state to its initial form. */
15
- constructor(rewind) {
16
- this.#rewind = rewind;
17
- }
18
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
19
- activate() {
20
- const first = _BeforeCacheReset.#subscribers.size === 0;
21
- _BeforeCacheReset.#subscribers.add(this);
22
- if (first) {
23
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
24
- }
25
- }
26
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
27
- deactivate() {
28
- _BeforeCacheReset.#subscribers.delete(this);
29
- if (_BeforeCacheReset.#subscribers.size > 0) return;
30
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
31
- }
32
- };
33
-
34
- // src/utils/event_owner.ts
35
3
  function ownerIndex(candidates, node) {
36
4
  if (!(node instanceof Node)) return -1;
37
5
  return candidates.findIndex((candidate) => candidate.contains(node));
@@ -40,7 +8,6 @@ function ownerOf(candidates, node) {
40
8
  return candidates[ownerIndex(candidates, node)] ?? null;
41
9
  }
42
10
 
43
- // src/utils/focus_candidate.ts
44
11
  function inheritsFieldsetDisabled(control) {
45
12
  let fieldset = control.closest("fieldset[disabled]");
46
13
  while (fieldset) {
@@ -50,26 +17,39 @@ function inheritsFieldsetDisabled(control) {
50
17
  }
51
18
  return false;
52
19
  }
20
+ function unslotted(element) {
21
+ return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
22
+ }
23
+ function flatTreeParent(element) {
24
+ if (unslotted(element)) return null;
25
+ const parent = element.assignedSlot ?? element.parentNode;
26
+ if (parent instanceof ShadowRoot) return parent.host;
27
+ return parent instanceof Element ? parent : null;
28
+ }
53
29
  function canTakeFocus(element) {
54
- if (element.closest("[hidden], [inert]")) return false;
30
+ for (let node = element; node; node = flatTreeParent(node)) {
31
+ if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
32
+ }
55
33
  if (element instanceof HTMLInputElement && element.type === "hidden") return false;
56
34
  if (!("disabled" in element)) return true;
57
35
  if (element.disabled) return false;
58
36
  return !inheritsFieldsetDisabled(element);
59
37
  }
38
+ function deepActiveElement(ownerDocument) {
39
+ let active = ownerDocument.activeElement;
40
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
41
+ return active;
42
+ }
60
43
 
61
- // src/utils/layout_observer.ts
62
44
  var LayoutObserver = class {
63
45
  #callback;
64
46
  #resizeObserverFactory;
65
47
  #resizeObserver = null;
66
48
  #observingViewport = false;
67
49
  #loadContainer = null;
68
- /** Stable bound handler so add/removeEventListener target the same reference. */
69
50
  #handleViewportResize = () => {
70
51
  this.#callback();
71
52
  };
72
- /** Stable bound handler for the capture-phase `load`; see {@link observeDescendantLoads}. */
73
53
  #handleDescendantLoad = () => {
74
54
  this.#callback();
75
55
  };
@@ -77,11 +57,6 @@ var LayoutObserver = class {
77
57
  this.#callback = callback;
78
58
  this.#resizeObserverFactory = options.resizeObserverFactory ?? (typeof ResizeObserver === "undefined" ? null : (cb) => new ResizeObserver(cb));
79
59
  }
80
- /**
81
- * Starts observing an element's size. Repeated calls observe additional
82
- * elements through the same shared observer. No-ops when no
83
- * `ResizeObserver` implementation is available.
84
- */
85
60
  observe(element) {
86
61
  if (!this.#resizeObserverFactory) return;
87
62
  if (!this.#resizeObserver) {
@@ -91,47 +66,28 @@ var LayoutObserver = class {
91
66
  }
92
67
  this.#resizeObserver.observe(element);
93
68
  }
94
- /** Stops observing a single element while leaving any others in place. */
95
69
  unobserve(element) {
96
70
  this.#resizeObserver?.unobserve(element);
97
71
  }
98
- /** Starts observing viewport resizes. Idempotent: the listener is added once. */
99
72
  observeViewport() {
100
73
  if (this.#observingViewport) return;
101
74
  this.#observingViewport = true;
102
75
  window.addEventListener("resize", this.#handleViewportResize);
103
76
  }
104
- /** Stops observing viewport resizes without affecting element observation. */
105
77
  unobserveViewport() {
106
78
  if (!this.#observingViewport) return;
107
79
  this.#observingViewport = false;
108
80
  window.removeEventListener("resize", this.#handleViewportResize);
109
81
  }
110
- /**
111
- * Starts reporting a `load` from anywhere inside `container` — an image or a
112
- * frame settling changes the box it sits in, and it measures as zero high until
113
- * then. `load` does not bubble, so the subscription is a capture-phase listener
114
- * on the container itself and nothing the caller spells.
115
- *
116
- * **One container at a time.** A further call moves the observation, so a widget
117
- * whose content element is swapped at runtime releases the element it let go by
118
- * naming the new one — there is no second place for the release to drift from.
119
- */
120
82
  observeDescendantLoads(container) {
121
83
  this.unobserveDescendantLoads();
122
84
  this.#loadContainer = container;
123
85
  container.addEventListener("load", this.#handleDescendantLoad, true);
124
86
  }
125
- /** Stops reporting descendant loads without affecting element or viewport observation. */
126
87
  unobserveDescendantLoads() {
127
88
  this.#loadContainer?.removeEventListener("load", this.#handleDescendantLoad, true);
128
89
  this.#loadContainer = null;
129
90
  }
130
- /**
131
- * Releases every observation: disconnects the {@link ResizeObserver} and removes
132
- * the viewport and descendant-load listeners. Safe to call multiple times. Call
133
- * this from a controller's `disconnect()`.
134
- */
135
91
  disconnect() {
136
92
  this.#resizeObserver?.disconnect();
137
93
  this.#resizeObserver = null;
@@ -140,43 +96,228 @@ var LayoutObserver = class {
140
96
  }
141
97
  };
142
98
 
143
- // src/utils/safe_timeout.ts
99
+ var ListenerSet = class {
100
+ #abort = new AbortController();
101
+ add(target, type, handler, options) {
102
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
103
+ }
104
+ dispose() {
105
+ this.#abort.abort();
106
+ this.#abort = new AbortController();
107
+ }
108
+ };
109
+
110
+ var MicrotaskCoalescer = class {
111
+ #run;
112
+ #queued = false;
113
+ #active = false;
114
+ #generation = 0;
115
+ constructor(run) {
116
+ this.#run = run;
117
+ }
118
+ activate() {
119
+ this.#active = true;
120
+ }
121
+ cancel() {
122
+ this.#active = false;
123
+ this.#queued = false;
124
+ this.#generation += 1;
125
+ }
126
+ get pending() {
127
+ return this.#queued;
128
+ }
129
+ schedule() {
130
+ if (!this.#active || this.#queued) return;
131
+ this.#queued = true;
132
+ const generation = this.#generation;
133
+ queueMicrotask(() => {
134
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
135
+ this.#queued = false;
136
+ this.#run();
137
+ });
138
+ }
139
+ };
140
+
141
+ var MorphRenderWatcher = class {
142
+ #listeners = new ListenerSet();
143
+ #pass;
144
+ #element = null;
145
+ #onMorph = () => this.#pass.schedule();
146
+ constructor(run) {
147
+ this.#pass = new MicrotaskCoalescer(run);
148
+ }
149
+ observe(element) {
150
+ if (this.#element === element) return;
151
+ this.disconnect();
152
+ this.#element = element;
153
+ this.#pass.activate();
154
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
155
+ }
156
+ schedule() {
157
+ this.#pass.schedule();
158
+ }
159
+ get pending() {
160
+ return this.#pass.pending;
161
+ }
162
+ disconnect() {
163
+ this.#listeners.dispose();
164
+ this.#pass.cancel();
165
+ this.#element = null;
166
+ }
167
+ };
168
+
169
+ var MAX_TIMER_DELAY_MS = 2147483647;
170
+
171
+ var NUMBER_BOUNDS = {
172
+ timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
173
+ function matchesNumberBounds(value, bounds) {
174
+ if (!Number.isFinite(value)) {
175
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
176
+ if (direction === null) return false;
177
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
178
+ }
179
+ if (bounds.min !== void 0 && value < bounds.min) return false;
180
+ if (bounds.max !== void 0 && value > bounds.max) return false;
181
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
182
+ if (bounds.integer && !Number.isInteger(value)) return false;
183
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
184
+ return true;
185
+ }
186
+
187
+ function readNumber(raw, fallback, bounds) {
188
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
189
+ }
190
+
191
+ var NumberValueReader = class {
192
+ #lastRejected = /* @__PURE__ */ new Map();
193
+ read(owner, name, raw, fallback, bounds) {
194
+ const resolved = readNumber(raw, fallback, bounds);
195
+ if (matchesNumberBounds(raw, bounds)) {
196
+ this.#lastRejected.delete(name);
197
+ return resolved;
198
+ }
199
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
200
+ const literal = owner.element.getAttribute(attribute);
201
+ if (literal === null) {
202
+ this.#lastRejected.delete(name);
203
+ return resolved;
204
+ }
205
+ if (this.#lastRejected.get(name) !== literal) {
206
+ this.#lastRejected.set(name, literal);
207
+ console.warn(
208
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
209
+ );
210
+ }
211
+ return resolved;
212
+ }
213
+ };
214
+
215
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
216
+ if (element.getAttribute(name) === value) return false;
217
+ if (value === null) element.removeAttribute(name);
218
+ else element.setAttribute(name, value);
219
+ return true;
220
+ }
221
+
222
+ var OwnedText = class {
223
+ #attribute;
224
+ #channel;
225
+ #guard;
226
+ #writes = /* @__PURE__ */ new WeakMap();
227
+ constructor(attribute, channel = "text", guard = () => true) {
228
+ this.#attribute = attribute;
229
+ this.#channel = channel;
230
+ this.#guard = guard;
231
+ }
232
+ hasRecord(element) {
233
+ return element.hasAttribute(this.#attribute);
234
+ }
235
+ read(element, evidence = {}) {
236
+ if (evidence.source === "live") return this.#writes.get(element) ?? null;
237
+ const raw = element.getAttribute(this.#attribute);
238
+ if (raw === null) return null;
239
+ try {
240
+ const record = JSON.parse(raw);
241
+ if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
242
+ return null;
243
+ return { original: record[1], written: record[2] };
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ owns(element, evidence = {}) {
249
+ return this.#ownedRecord(element, evidence) !== null;
250
+ }
251
+ #ownedRecord(element, evidence) {
252
+ const record = this.read(element, evidence);
253
+ if (record === null || !this.#guard(element)) return null;
254
+ if (this.#channel === "text" && element.firstElementChild !== null) return null;
255
+ const current = this.#current(element);
256
+ return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
257
+ }
258
+ write(element, written, original, force = false) {
259
+ const current = this.#current(element);
260
+ if (current === null) return;
261
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
262
+ if (force || current !== written) this.#put(element, written);
263
+ this.remember(element, written, before);
264
+ }
265
+ remember(element, written, original) {
266
+ const current = this.#current(element);
267
+ if (current === null) return;
268
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
269
+ this.#writes.set(element, { original: before, written });
270
+ setAttributeIfChanged(
271
+ element,
272
+ this.#attribute,
273
+ JSON.stringify([this.#channel, before, written])
274
+ );
275
+ }
276
+ clear(element, evidence = {}) {
277
+ if (!this.owns(element, evidence)) return false;
278
+ if (this.#current(element) !== "") this.#put(element, "");
279
+ this.forget(element);
280
+ return true;
281
+ }
282
+ restore(element, evidence = {}) {
283
+ const record = this.#ownedRecord(element, evidence);
284
+ if (record === null) return false;
285
+ if (this.#current(element) !== record.original) this.#put(element, record.original);
286
+ this.forget(element);
287
+ return true;
288
+ }
289
+ forget(element) {
290
+ setAttributeIfChanged(element, this.#attribute, null);
291
+ this.#writes.delete(element);
292
+ }
293
+ #current(element) {
294
+ if (this.#channel === "text") return element.textContent ?? "";
295
+ return element instanceof HTMLInputElement ? element.value : null;
296
+ }
297
+ #put(element, text) {
298
+ if (this.#channel === "text") element.textContent = text;
299
+ else if (element instanceof HTMLInputElement) element.value = text;
300
+ }
301
+ };
302
+
144
303
  var TimerRegistry = class {
145
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
146
304
  ids = /* @__PURE__ */ new Set();
147
- /**
148
- * Cancels a single tracked timer.
149
- *
150
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
151
- * registry), so callers can clear defensively without guarding.
152
- */
153
305
  clear(id) {
154
306
  if (this.ids.delete(id)) {
155
307
  this.cancel(id);
156
308
  }
157
309
  }
158
- /**
159
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
160
- * to guarantee no timer outlives the element.
161
- */
162
310
  clearAll() {
163
311
  for (const id of this.ids) {
164
312
  this.cancel(id);
165
313
  }
166
314
  this.ids.clear();
167
315
  }
168
- /** Number of timers currently tracked (pending). */
169
316
  get size() {
170
317
  return this.ids.size;
171
318
  }
172
319
  };
173
320
  var SafeTimeout = class extends TimerRegistry {
174
- /**
175
- * Schedules `callback` after `delay` ms and returns the timer id.
176
- *
177
- * The id is removed from the registry automatically when the timeout fires,
178
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
179
- */
180
321
  set(callback, delay) {
181
322
  const id = this.schedule(() => {
182
323
  this.ids.delete(id);
@@ -193,38 +334,136 @@ var SafeTimeout = class extends TimerRegistry {
193
334
  }
194
335
  };
195
336
 
196
- // src/utils/tabindex_loan.ts
197
- var TabindexLoan = class {
337
+ function queryOpenDescendants(root, selector) {
338
+ const matches = [];
339
+ const roots = [root];
340
+ for (const scope of roots) {
341
+ matches.push(...scope.querySelectorAll(selector));
342
+ if (scope.nodeType === Node.ELEMENT_NODE) {
343
+ const shadow = scope.shadowRoot;
344
+ if (shadow) roots.push(shadow);
345
+ }
346
+ for (const element of scope.querySelectorAll("*")) {
347
+ if (element.shadowRoot) roots.push(element.shadowRoot);
348
+ }
349
+ }
350
+ return matches;
351
+ }
352
+
353
+ function sharedRegistry(key, create) {
354
+ const symbol = Symbol.for(key);
355
+ const scope = globalThis;
356
+ const existing = scope[symbol];
357
+ if (existing !== void 0) return existing;
358
+ const registry = create();
359
+ Object.defineProperty(globalThis, symbol, { value: registry });
360
+ return registry;
361
+ }
362
+
363
+ function holdsFocus(element) {
364
+ return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
365
+ }
366
+ var TabindexLoan = class _TabindexLoan {
367
+ static #registry = sharedRegistry(
368
+ "stimeo-ui.tabindex-loan.registry.v1",
369
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
370
+ );
198
371
  #value;
372
+ #record;
373
+ #preserveFocus;
199
374
  #lent = /* @__PURE__ */ new Set();
200
- /** Returns live loans before Turbo can copy them into its page snapshot. */
201
- #beforeCache = new BeforeCacheReset(() => this.returnAll());
202
- /**
203
- * @param value - the `tabindex` to lend. `"-1"` (the default) is
204
- * programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
205
- * which a scroll region with no focusable content of its own needs.
206
- */
207
- constructor(value = "-1") {
375
+ constructor(value, owner, options = {}) {
208
376
  this.#value = value;
209
- }
210
- /** Lends `element` the value; no-ops when it already carries a `tabindex`. */
211
- lend(element) {
212
- if (element.hasAttribute("tabindex")) return;
213
- element.setAttribute("tabindex", this.#value);
377
+ this.#record = `data-${owner}-tabindex-loan`;
378
+ this.#preserveFocus = options.preserveFocus === true;
379
+ _TabindexLoan.#registry.names.add(this.#record);
380
+ }
381
+ lend(element, options = {}) {
382
+ const stack = this.#stack(element);
383
+ const current = element.getAttribute("tabindex");
384
+ const held = stack.find((hold) => hold.loan === this) ?? stack.find(
385
+ (hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
386
+ );
387
+ const linked = stack.at(-1)?.value === current;
388
+ if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
389
+ if (!held && current !== null && !linked) return;
214
390
  this.#lent.add(element);
215
- this.#beforeCache.activate();
391
+ if (held) held.loan = this;
392
+ else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
393
+ this.#mark(element, stack);
394
+ if (current === null || !held && current !== this.#value) {
395
+ element.setAttribute("tabindex", this.#value);
396
+ }
397
+ }
398
+ return(element) {
399
+ const stack = this.#stack(element);
400
+ const hold = stack.find((held) => held.loan === this) ?? stack.find(
401
+ (held) => held.loan === null && held.record === this.#record && held.value === this.#value
402
+ );
403
+ if (!hold) return false;
404
+ if (!this.#release(element, stack, hold, this.#preserveFocus)) {
405
+ hold.loan = this;
406
+ this.#lent.add(element);
407
+ return false;
408
+ }
409
+ this.#lent.delete(element);
410
+ return true;
216
411
  }
217
- /** Takes back every loan whose value is still the one that was lent. */
218
412
  returnAll() {
219
- for (const element of this.#lent) {
220
- if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
413
+ for (const element of Array.from(this.#lent)) this.return(element);
414
+ }
415
+ reclaim(element) {
416
+ if (element.getAttribute(this.#record) !== this.#value) return;
417
+ const stack = this.#stack(element);
418
+ const hold = stack.find((held) => held.loan === null && held.record === this.#record);
419
+ if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
420
+ }
421
+ reclaimWithin(root) {
422
+ for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
423
+ this.reclaim(element);
424
+ }
425
+ }
426
+ #stack(element) {
427
+ const stacks = _TabindexLoan.#registry.stacks;
428
+ const stack = stacks.get(element) ?? this.#seed(element);
429
+ stacks.set(element, stack);
430
+ return stack;
431
+ }
432
+ #seed(element) {
433
+ const current = element.getAttribute("tabindex");
434
+ const holds = [];
435
+ for (const record of _TabindexLoan.#registry.names) {
436
+ const value = element.getAttribute(record);
437
+ if (value !== null) holds.push({ loan: null, record, value, linked: true });
438
+ }
439
+ holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
440
+ for (const [index, hold] of holds.entries()) hold.linked = index > 0;
441
+ return holds;
442
+ }
443
+ #release(element, stack, hold, preserveFocus = false) {
444
+ const current = element.getAttribute("tabindex");
445
+ const index = stack.indexOf(hold);
446
+ const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
447
+ if (owns && preserveFocus && holdsFocus(element)) return false;
448
+ stack.splice(index, 1);
449
+ const above = stack[index];
450
+ if (above) above.linked &&= hold.linked;
451
+ this.#mark(element, stack);
452
+ if (!owns) return true;
453
+ const below = hold.linked ? stack[index - 1] : void 0;
454
+ if (!below) element.removeAttribute("tabindex");
455
+ else if (below.value !== current) element.setAttribute("tabindex", below.value);
456
+ return true;
457
+ }
458
+ #mark(element, stack) {
459
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
460
+ for (const hold of stack) records.set(hold.record, hold.value);
461
+ for (const [name, recorded] of records) {
462
+ setAttributeIfChanged(element, name, recorded);
221
463
  }
222
- this.#lent.clear();
223
- this.#beforeCache.deactivate();
224
464
  }
225
465
  };
226
466
 
227
- // src/controllers/overflow_menu_controller.ts
228
467
  var BANKED = "banked";
229
468
  var INDEX = "index";
230
469
  var BOUNDARY = "boundary";
@@ -234,8 +473,13 @@ var SAVED_MENU_TARGET = "saved-menu";
234
473
  var MENU_TARGET = "data-stimeo--menu-target";
235
474
  var BOOKKEEPING = [BANKED, INDEX, SAVED_ROLE, SAVED_TABINDEX, SAVED_MENU_TARGET];
236
475
  var OWNED_LABEL = "owns-label";
237
- var OverflowMenuController = class extends Controller {
238
- /** One bookkeeping attribute, in the namespace this controller is registered under. */
476
+ var OverflowMenuController = class _OverflowMenuController extends Controller {
477
+ #labelText = new OwnedText(
478
+ this.#attr(OWNED_LABEL),
479
+ "text",
480
+ (trigger) => this.#carriesOnlyText(trigger)
481
+ );
482
+ #numbers = new NumberValueReader();
239
483
  #attr(name) {
240
484
  return `data-${this.identifier}-${name}`;
241
485
  }
@@ -244,66 +488,84 @@ var OverflowMenuController = class extends Controller {
244
488
  moreLabel: { type: String, default: "More" },
245
489
  debounce: { type: Number, default: 100 }
246
490
  };
491
+ static valueConstraints = {
492
+ debounce: NUMBER_BOUNDS.timer
493
+ };
247
494
  static actions = ["update"];
248
- static events = ["change"];
495
+ static events = ["change", "reconcile"];
249
496
  #layout = new LayoutObserver(() => this.#scheduleUpdate());
250
497
  #timers = new SafeTimeout();
251
- /** The managed items in canonical (authored) order, rebuilt from the DOM each pass. */
252
498
  #items = [];
253
499
  #index = /* @__PURE__ */ new WeakMap();
254
- /** Cached bar-context width per item; see the class remarks for the staleness caveat. */
255
500
  #widths = /* @__PURE__ */ new WeakMap();
256
- /** Last reported overflow count, so `change` fires only on transitions. */
257
501
  #lastHidden = null;
258
- /** The `tabindex` this instance lends the root for the focus fallback. */
259
- #tabindex = new TabindexLoan();
260
- /** Hands Turbo a snapshot of the bar with every item back in place. */
261
- #beforeCache = new BeforeCacheReset(() => this.#restoreAll());
262
- /**
263
- * Whether `connect()` has run for this connection. Stimulus delivers Value
264
- * callbacks ahead of it, and the first pass writes the label anyway.
265
- */
502
+ #pass = 0;
503
+ #restoring = false;
504
+ #tabindex = new TabindexLoan("-1", this.identifier);
505
+ #bar = null;
506
+ #wrapper = null;
266
507
  #connected = false;
508
+ #morphRender = new MorphRenderWatcher(() => this.#reconcile());
267
509
  connect() {
510
+ this.#tabindex.reclaim(this.element);
511
+ this.#morphRender.observe(this.element);
268
512
  this.#connected = true;
269
513
  if (!this.hasItemsTarget || !this.hasMoreTarget) return;
270
- this.#beforeCache.activate();
271
514
  this.#layout.observe(this.element);
272
515
  this.#layout.observeViewport();
273
516
  this.update();
274
517
  }
275
518
  disconnect() {
519
+ this.#morphRender.disconnect();
276
520
  this.#connected = false;
277
521
  this.#layout.disconnect();
278
522
  this.#timers.clearAll();
279
- this.#beforeCache.deactivate();
280
523
  this.#restoreAll();
281
524
  this.#lastHidden = null;
282
525
  }
283
- /**
284
- * Follows a More label swapped in place by a morph, on a trigger that still holds
285
- * the label this controller wrote and nothing else. Render only: nothing is
286
- * measured and `change` is not dispatched here; the next pass measures the bar.
287
- */
526
+ itemsTargetConnected() {
527
+ this.#followTargets();
528
+ }
529
+ itemsTargetDisconnected(bar) {
530
+ if (bar === this.#bar && !this.itemsTargets.includes(bar)) this.#release();
531
+ this.#followTargets();
532
+ }
533
+ moreTargetConnected() {
534
+ this.#followTargets();
535
+ }
536
+ moreTargetDisconnected(more) {
537
+ if (more === this.#wrapper && !this.moreTargets.includes(more)) this.#release();
538
+ this.#followTargets();
539
+ }
288
540
  moreLabelValueChanged() {
289
541
  if (!this.#connected || !this.hasItemsTarget || !this.hasMoreTarget) return;
290
542
  this.#syncLabel();
291
543
  }
292
- /** Re-measures and rebalances items between the bar and the More menu. */
293
544
  update() {
294
- if (!this.hasItemsTarget || !this.hasMoreTarget) return;
545
+ const detail = this.#render();
546
+ if (detail) this.dispatch("change", { detail });
547
+ }
548
+ #reconcile() {
549
+ const detail = this.#render();
550
+ if (detail) this.dispatch("reconcile", { detail });
551
+ }
552
+ #render() {
553
+ if (this.#restoring || !this.hasItemsTarget || !this.hasMoreTarget) return null;
554
+ const pass = ++this.#pass;
555
+ this.#bar = this.itemsTarget;
556
+ this.#wrapper = this.moreTarget;
295
557
  this.#syncLabel();
296
- this.#syncItems();
558
+ this.#syncItems(this.itemsTarget, this.moreTarget);
297
559
  this.moreTarget.hidden = false;
298
560
  if (this.#items.some((item) => item.parentElement !== this.itemsTarget && !this.#widths.has(item))) {
299
- this.#removeBoundary();
561
+ this.#removeBoundary(this.itemsTarget);
300
562
  for (const item of this.#items) this.#unbank(item);
301
563
  this.#reorder(this.itemsTarget, this.#items);
302
564
  }
303
565
  for (const item of this.#items) {
304
566
  if (item.parentElement === this.itemsTarget) this.#widths.set(item, item.offsetWidth);
305
567
  }
306
- const moreWidth = (this.#trigger() ?? this.moreTarget).offsetWidth;
568
+ const moreWidth = (this.#trigger(this.moreTarget) ?? this.moreTarget).offsetWidth;
307
569
  const itemGap = this.#gap(this.itemsTarget);
308
570
  const barGap = this.#gap(this.element);
309
571
  const widthOf = (el) => this.#widths.get(el) ?? el.offsetWidth;
@@ -325,36 +587,39 @@ var OverflowMenuController = class extends Controller {
325
587
  }
326
588
  const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
327
589
  const focused = active === null ? null : this.#ownerOf(active);
328
- const retreating = focused !== null && hidden.has(focused) && focused.parentElement === this.itemsTarget && // i.e. *starting* to retreat
329
- !this.#menuExpanded();
330
- const returning = focused !== null && !hidden.has(focused) && focused.parentElement !== this.itemsTarget && // i.e. *leaving* the menu
331
- this.#menuExpanded();
332
- if (hidden.size === 0 || hidden.size !== this.#items.length) this.#removeBoundary();
590
+ const retreating = focused !== null && hidden.has(focused) && focused.parentElement === this.itemsTarget &&
591
+ !this.#menuExpanded(this.moreTarget);
592
+ const returning = focused !== null && !hidden.has(focused) && focused.parentElement !== this.itemsTarget &&
593
+ this.#menuExpanded(this.moreTarget);
594
+ if (hidden.size === 0 || hidden.size !== this.#items.length) {
595
+ this.#removeBoundary(this.itemsTarget);
596
+ }
333
597
  for (const item of this.#items) {
334
598
  if (hidden.has(item)) this.#bank(item);
335
599
  else this.#unbank(item);
336
600
  }
337
601
  this.#reorder(
338
- this.#menuList(),
602
+ this.#menuList(this.moreTarget),
339
603
  this.#items.filter((item) => hidden.has(item))
340
604
  );
341
605
  this.#reorder(
342
606
  this.itemsTarget,
343
607
  this.#items.filter((item) => !hidden.has(item))
344
608
  );
345
- if (retreating) (this.#trigger() ?? this.moreTarget).focus();
609
+ if (retreating) (this.#trigger(this.moreTarget) ?? this.moreTarget).focus();
346
610
  else if (active !== null && this.#lostFocus(active)) active.focus();
611
+ if (pass !== this.#pass) return null;
347
612
  const count = hidden.size;
348
613
  for (const item of this.#items) {
349
614
  if (count > 0) item.setAttribute(this.#attr(INDEX), String(this.#indexOf(item)));
350
615
  else item.removeAttribute(this.#attr(INDEX));
351
616
  }
352
617
  if (count > 0 && count === this.#items.length) this.#ensureBoundary();
353
- else this.#removeBoundary();
354
618
  const inMore = document.activeElement instanceof HTMLElement && this.moreTarget.contains(document.activeElement);
355
619
  if (count === 0 || returning) {
356
- this.#closeMenu();
357
- if (inMore) this.#rescueFocus();
620
+ this.#closeMenu(this.moreTarget);
621
+ if (inMore && pass === this.#pass) this.#rescueFocus();
622
+ if (pass !== this.#pass) return null;
358
623
  }
359
624
  this.moreTarget.hidden = count === 0;
360
625
  if (count > 0) this.element.setAttribute("data-overflowing", "true");
@@ -362,15 +627,14 @@ var OverflowMenuController = class extends Controller {
362
627
  this.element.setAttribute("data-overflow-count", String(count));
363
628
  if (this.#lastHidden !== count) {
364
629
  this.#lastHidden = count;
365
- this.dispatch("change", { detail: { overflowCount: count, total: this.#items.length } });
630
+ return { overflowCount: count, total: this.#items.length };
366
631
  }
632
+ return null;
367
633
  }
368
- /** The flex `column-gap` on `el` in px (0 when none / unsupported). */
369
634
  #gap(el) {
370
635
  const style = this.#styleOf(el);
371
636
  return style === null ? 0 : this.#px(style.columnGap || style.gap);
372
637
  }
373
- /** Horizontal padding on `el` in px — `clientWidth` counts it, the content box cannot. */
374
638
  #paddingX(el) {
375
639
  const style = this.#styleOf(el);
376
640
  return style === null ? 0 : this.#px(style.paddingLeft) + this.#px(style.paddingRight);
@@ -382,16 +646,11 @@ var OverflowMenuController = class extends Controller {
382
646
  const parsed = Number.parseFloat(value ?? "");
383
647
  return Number.isNaN(parsed) ? 0 : parsed;
384
648
  }
385
- /**
386
- * Rebuilds the managed set and the canonical order from the DOM. The bar's children
387
- * give the order of everything inline (so a leading insert is not silently appended)
388
- * and {@link #merge} weaves the banked items back among them.
389
- */
390
- #syncItems() {
649
+ #syncItems(row, more) {
391
650
  const previous = this.#items;
392
651
  const bar = [];
393
652
  let boundaryAt;
394
- for (const el of this.itemsTarget.children) {
653
+ for (const el of row.children) {
395
654
  if (el instanceof HTMLTemplateElement && el.hasAttribute(this.#attr(BOUNDARY))) {
396
655
  boundaryAt ??= bar.length;
397
656
  continue;
@@ -399,7 +658,7 @@ var OverflowMenuController = class extends Controller {
399
658
  if (el instanceof HTMLElement) bar.push(el);
400
659
  }
401
660
  const banked = [];
402
- for (const el of this.#menuList().children) {
661
+ for (const el of this.#menuList(more).children) {
403
662
  if (el instanceof HTMLElement && el.hasAttribute(this.#attr(BANKED))) banked.push(el);
404
663
  }
405
664
  banked.sort((a, b) => this.#bankedIndex(a) - this.#bankedIndex(b));
@@ -416,29 +675,6 @@ var OverflowMenuController = class extends Controller {
416
675
  this.#index.set(el, i);
417
676
  });
418
677
  }
419
- /**
420
- * Interleaves the banked items back among the bar's children.
421
- *
422
- * A saved index is a position in the *canonical* order, so it can only be compared
423
- * against another canonical position — never used as an offset into the current bar,
424
- * which is a shorter and differently-indexed list. (Doing so survives an untouched
425
- * round trip and silently scrambles the order as soon as the consumer inserts into a
426
- * bar that is already short, and `#bank()` then burns the scrambled index back into
427
- * the attribute, so widening never heals it.) Each bar child the controller already
428
- * knows carries its canonical index from the previous pass; a banked item goes in
429
- * front of the first known child whose index is higher.
430
- *
431
- * Children never seen before — inserted by the consumer since the last pass — have no
432
- * canonical position yet, so they keep their DOM neighbours. The exception is a
433
- * *trailing* run of them: appending to the bar means appending to the toolbar, so
434
- * everything banked is emitted before it.
435
- *
436
- * With no known child at all (a fresh instance connecting to markup that already
437
- * holds banked items), a fully-banked snapshot's inert boundary preserves whether an
438
- * unindexed run was prepended or appended before connect. Markup that holds banked items but no boundary —
439
- * server-rendered or hand-authored — uses the saved index as the
440
- * offset, the inverse of the move that banked it.
441
- */
442
678
  #merge(bar, banked, boundaryAt) {
443
679
  const canonical = bar.map((el) => this.#index.get(el) ?? this.#savedIndex(el));
444
680
  let lastKnown = -1;
@@ -474,56 +710,50 @@ var OverflowMenuController = class extends Controller {
474
710
  drainWhile(() => true);
475
711
  return items;
476
712
  }
477
- /** The canonical index a banked item saved, or ∞ (append) when it carries none. */
478
713
  #bankedIndex(item) {
479
714
  return this.#savedIndex(item) ?? Number.POSITIVE_INFINITY;
480
715
  }
481
- /** A finite canonical index stored on either side of the overflow split. */
482
716
  #savedIndex(item) {
483
717
  const raw = item.getAttribute(this.#attr(INDEX));
484
718
  if (raw === null) return void 0;
485
719
  const value = Number(raw);
486
720
  return Number.isFinite(value) ? value : void 0;
487
721
  }
488
- /** The inert split point written only when every managed item lives in More. */
489
- #boundary() {
490
- for (const el of this.itemsTarget.children) {
722
+ #boundary(bar) {
723
+ for (const el of bar.children) {
491
724
  if (el instanceof HTMLTemplateElement && el.hasAttribute(this.#attr(BOUNDARY))) return el;
492
725
  }
493
726
  return null;
494
727
  }
495
728
  #ensureBoundary() {
496
- if (this.#boundary() !== null) return;
729
+ if (this.#boundary(this.itemsTarget) !== null) return;
497
730
  const boundary = document.createElement("template");
498
731
  boundary.setAttribute(this.#attr(BOUNDARY), "");
499
732
  this.itemsTarget.appendChild(boundary);
500
733
  }
501
- #removeBoundary() {
502
- this.#boundary()?.remove();
734
+ #removeBoundary(bar) {
735
+ this.#boundary(bar)?.remove();
503
736
  }
504
- /** Debounced re-measure for resize-driven churn. */
505
737
  #scheduleUpdate() {
506
738
  this.#timers.clearAll();
507
- this.#timers.set(() => this.update(), this.debounceValue);
508
- }
509
- /** Returns the menu list the items are banked into (falls back to the More wrapper). */
510
- #menuList() {
511
- return this.moreTarget.querySelector('[data-stimeo--menu-target="menu"]') ?? this.moreTarget;
512
- }
513
- #trigger() {
514
- return this.moreTarget.querySelector('[data-stimeo--menu-target="trigger"]');
515
- }
516
- /**
517
- * Keeps the More trigger's text on `moreLabel` wherever that text is this
518
- * controller's to write: a bare trigger, or one still holding the label this
519
- * controller wrote and nothing else. An empty `moreLabel` hands such a trigger
520
- * back bare. A pass that finds the label already in place writes nothing, and a
521
- * trigger that is neither is not written at all.
522
- *
523
- * @stimeoRenderRoot
524
- */
739
+ this.#timers.set(() => this.update(), this.#safeDebounce);
740
+ }
741
+ #followTargets() {
742
+ if (this.#restoring || this.#lastHidden === null) return;
743
+ const bar = this.hasItemsTarget ? this.itemsTarget : null;
744
+ const more = this.hasMoreTarget ? this.moreTarget : null;
745
+ if (bar === this.#bar && more === this.#wrapper) return;
746
+ this.#release();
747
+ this.#morphRender.schedule();
748
+ }
749
+ #menuList(more) {
750
+ return more.querySelector('[data-stimeo--menu-target="menu"]') ?? more;
751
+ }
752
+ #trigger(more) {
753
+ return more.querySelector('[data-stimeo--menu-target="trigger"]');
754
+ }
525
755
  #syncLabel() {
526
- const trigger = this.#trigger();
756
+ const trigger = this.#trigger(this.moreTarget);
527
757
  if (trigger === null) return;
528
758
  const owned = this.#ownsLabel(trigger);
529
759
  if (!owned && !this.#isBareTrigger(trigger)) return;
@@ -532,93 +762,43 @@ var OverflowMenuController = class extends Controller {
532
762
  this.#releaseLabel(trigger);
533
763
  return;
534
764
  }
535
- if (owned && trigger.textContent === label) return;
536
- trigger.textContent = label;
537
- trigger.setAttribute(this.#attr(OWNED_LABEL), label);
765
+ this.#labelText.write(trigger, label);
538
766
  }
539
- /**
540
- * Whether the trigger holds the label this controller wrote there and nothing
541
- * else: the text still matches the marker, and the trigger carries nothing but
542
- * text. Ownership is read afresh on every call, so a trigger the consumer adds to
543
- * is theirs for as long as the addition is there.
544
- */
545
767
  #ownsLabel(trigger) {
546
- return trigger.getAttribute(this.#attr(OWNED_LABEL)) === trigger.textContent && this.#carriesOnlyText(trigger);
768
+ return this.#labelText.owns(trigger);
547
769
  }
548
- /**
549
- * Takes the label this controller wrote back out of the trigger, leaving it bare.
550
- * A trigger that is not this controller's is left exactly as it is, marker
551
- * included: taking the marker off would itself write to it.
552
- */
553
770
  #releaseLabel(trigger) {
554
- if (!this.#ownsLabel(trigger)) return;
555
- trigger.textContent = "";
556
- trigger.removeAttribute(this.#attr(OWNED_LABEL));
557
- }
558
- /**
559
- * Whether the More trigger is bare, so the label is safe to write: no text, no
560
- * element child, no `aria-label` and no `aria-labelledby`. A trigger with neither
561
- * text, nor children, nor a name has nothing to lose and gets the fallback; one
562
- * that is empty *and* unnamed is the only case the label rescues.
563
- */
771
+ this.#labelText.clear(trigger);
772
+ }
564
773
  #isBareTrigger(trigger) {
565
774
  return (trigger.textContent ?? "").trim() === "" && this.#carriesOnlyText(trigger);
566
775
  }
567
- /**
568
- * Whether the trigger carries nothing but text: no element child, no
569
- * `aria-label` and no `aria-labelledby`. Assigning `textContent` replaces
570
- * *every* child node, so a trigger holding an icon would silently lose it — and
571
- * the loss is permanent, since only the label this controller wrote is ever
572
- * taken back. A trigger that carries an accessible name is left alone too:
573
- * visible text written under a different `aria-label` would make the label and
574
- * the name disagree. Only a trigger that carries nothing but text is ever
575
- * written or emptied.
576
- */
577
776
  #carriesOnlyText(trigger) {
578
777
  return trigger.firstElementChild === null && !trigger.hasAttribute("aria-label") && !trigger.hasAttribute("aria-labelledby");
579
778
  }
580
- /**
581
- * Whether the More menu is expanded. Menu owns that state and reflects it on the
582
- * trigger's `aria-expanded`, so read the contract rather than guess from `hidden`
583
- * (which is absent until Menu connects).
584
- */
585
- #menuExpanded() {
586
- return this.#trigger()?.getAttribute("aria-expanded") === "true";
587
- }
588
- /**
589
- * Collapses the More menu before its wrapper is hidden. Menu owns the state (and the
590
- * Escape-stack membership that goes with it), so ask it; the DOM fallback covers markup
591
- * that reached an expanded state with no Menu mounted.
592
- */
593
- #closeMenu() {
594
- if (!this.#menuExpanded()) return;
779
+ #menuExpanded(more) {
780
+ return this.#trigger(more)?.getAttribute("aria-expanded") === "true";
781
+ }
782
+ #closeMenu(more) {
783
+ if (!this.#menuExpanded(more)) return;
595
784
  const menu = this.application.getControllerForElementAndIdentifier(
596
- this.moreTarget,
785
+ more,
597
786
  "stimeo--menu"
598
787
  );
599
788
  if (menu) {
600
789
  menu.close();
601
790
  return;
602
791
  }
603
- this.#trigger()?.setAttribute("aria-expanded", "false");
604
- const list = this.#menuList();
605
- if (list !== this.moreTarget) list.hidden = true;
792
+ this.#trigger(more)?.setAttribute("aria-expanded", "false");
793
+ const list = this.#menuList(more);
794
+ if (list !== more) list.hidden = true;
606
795
  }
607
- /** The managed item that is, or contains, `el` — focus rescue works on either. */
608
796
  #ownerOf(el) {
609
797
  return ownerOf(this.#items, el);
610
798
  }
611
- /** Whether the item takes up room in the bar; an authored `hidden` one does not. */
612
799
  #rendered(item) {
613
800
  return !item.hasAttribute("hidden");
614
801
  }
615
- /**
616
- * Moves focus out of the More wrapper just before it is hidden, to the last
617
- * item in canonical order that can hold it. When every item refuses, the root
618
- * takes focus instead of letting the browser drop it to `<body>`, borrowing a
619
- * `tabindex="-1"` just-in-time — not a Tab stop, and handed back on restore
620
- * when this controller was the one that added it.
621
- */
622
802
  #rescueFocus() {
623
803
  for (let i = this.#items.length - 1; i >= 0; i--) {
624
804
  const item = this.#items[i];
@@ -630,38 +810,38 @@ var OverflowMenuController = class extends Controller {
630
810
  this.#tabindex.lend(this.element);
631
811
  this.element.focus();
632
812
  }
633
- /**
634
- * Whether `el` held focus, is still in a visible part of this controller, and lost it —
635
- * what a re-insert does in a real browser. An item that retreated into a collapsed menu
636
- * is excluded: it sits in a `hidden` subtree and must not be focused back.
637
- */
638
813
  #lostFocus(el) {
639
814
  return el.isConnected && document.activeElement !== el && this.element.contains(el) && el.closest("[hidden]") === null;
640
815
  }
641
- /** Returns every item to the bar and removes all traces of this controller. */
642
816
  #restoreAll() {
643
817
  if (!this.hasItemsTarget || !this.hasMoreTarget) return;
644
- this.#syncItems();
645
- this.#removeBoundary();
646
- this.#reorder(this.itemsTarget, this.#items);
818
+ this.#giveBack(this.itemsTarget, this.moreTarget);
819
+ }
820
+ #release() {
821
+ if (this.#bar && this.#wrapper) this.#giveBack(this.#bar, this.#wrapper);
822
+ }
823
+ #giveBack(bar, more) {
824
+ this.#bar = null;
825
+ this.#wrapper = null;
826
+ const restoring = this.#restoring;
827
+ this.#restoring = true;
828
+ this.#pass++;
829
+ this.#syncItems(bar, more);
830
+ this.#removeBoundary(bar);
831
+ this.#reorder(bar, this.#items);
647
832
  for (const item of this.#items) {
648
833
  this.#unbank(item);
649
834
  item.removeAttribute(this.#attr(INDEX));
650
835
  }
651
- this.#closeMenu();
652
- const trigger = this.#trigger();
836
+ this.#closeMenu(more);
837
+ const trigger = this.#trigger(more);
653
838
  if (trigger !== null) this.#releaseLabel(trigger);
654
- this.moreTarget.hidden = true;
839
+ more.hidden = true;
655
840
  this.element.removeAttribute("data-overflowing");
656
841
  this.element.removeAttribute("data-overflow-count");
657
842
  this.#tabindex.returnAll();
843
+ this.#restoring = restoring;
658
844
  }
659
- /**
660
- * Makes `parent`'s managed children match `sequence`, moving only what is out of place.
661
- * Walking backwards lets each item be compared against the element that should follow
662
- * it, so an unchanged run costs zero DOM mutations — the point of the exercise, since
663
- * every re-insert blurs the focused node.
664
- */
665
845
  #reorder(parent, sequence) {
666
846
  let next = null;
667
847
  for (let i = sequence.length - 1; i >= 0; i--) {
@@ -673,7 +853,6 @@ var OverflowMenuController = class extends Controller {
673
853
  next = item;
674
854
  }
675
855
  }
676
- /** Relocates `item`, preferring `moveBefore` (keeps focus and element state). */
677
856
  #insert(parent, item, before) {
678
857
  const move = parent.moveBefore;
679
858
  if (typeof move === "function") {
@@ -685,10 +864,6 @@ var OverflowMenuController = class extends Controller {
685
864
  }
686
865
  parent.insertBefore(item, before);
687
866
  }
688
- /**
689
- * Gives an item menuitem semantics for Menu to drive and records its canonical index,
690
- * saving every authored value it overwrites ("" means the attribute was absent).
691
- */
692
867
  #bank(item) {
693
868
  if (!item.hasAttribute(this.#attr(BANKED))) {
694
869
  item.setAttribute(this.#attr(BANKED), "true");
@@ -701,7 +876,6 @@ var OverflowMenuController = class extends Controller {
701
876
  }
702
877
  item.setAttribute(this.#attr(INDEX), String(this.#indexOf(item)));
703
878
  }
704
- /** Undoes the banking: restores every authored value and drops the bookkeeping. */
705
879
  #unbank(item) {
706
880
  if (!item.hasAttribute(this.#attr(BANKED))) return;
707
881
  this.#restoreAttr(item, "role", item.getAttribute(this.#attr(SAVED_ROLE)));
@@ -709,7 +883,6 @@ var OverflowMenuController = class extends Controller {
709
883
  this.#restoreAttr(item, MENU_TARGET, item.getAttribute(this.#attr(SAVED_MENU_TARGET)));
710
884
  for (const name of BOOKKEEPING) item.removeAttribute(this.#attr(name));
711
885
  }
712
- /** Re-applies a saved attribute value, or removes the attribute when it was absent. */
713
886
  #restoreAttr(item, name, original) {
714
887
  if (original === null || original === "") item.removeAttribute(name);
715
888
  else item.setAttribute(name, original);
@@ -717,20 +890,22 @@ var OverflowMenuController = class extends Controller {
717
890
  #indexOf(item) {
718
891
  return this.#index.get(item) ?? 0;
719
892
  }
720
- /**
721
- * Retention rank: lower keeps longer. An absent, empty, or non-numeric `data-priority`
722
- * ranks lowest (drops first) — empty is the common ERB accident
723
- * (`data-priority="<%= maybe_nil %>"`), and `Number("")` is 0, which would otherwise
724
- * read as the *highest* retention.
725
- */
726
893
  #rank(item) {
727
894
  const raw = item.getAttribute("data-priority");
728
895
  if (raw === null || raw.trim() === "") return Number.POSITIVE_INFINITY;
729
896
  const value = Number(raw);
730
897
  return Number.isNaN(value) ? Number.POSITIVE_INFINITY : value;
731
898
  }
899
+ get #safeDebounce() {
900
+ return this.#numbers.read(
901
+ this,
902
+ "debounce",
903
+ this.debounceValue,
904
+ _OverflowMenuController.values.debounce.default,
905
+ _OverflowMenuController.valueConstraints.debounce
906
+ );
907
+ }
732
908
  };
733
909
 
734
910
  export { OverflowMenuController };
735
- //# sourceMappingURL=overflow_menu_controller.js.map
736
911
  //# sourceMappingURL=overflow_menu_controller.js.map