stimeo-ui 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +128 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +392 -417
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +34 -5
  7. package/dist/controllers/accordion_controller.js +228 -28
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +20 -9
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +38 -7
  13. package/dist/controllers/announcer_controller.js +135 -191
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +295 -110
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +172 -106
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  23. package/dist/controllers/breadcrumb_controller.js +0 -129
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +22 -4
  26. package/dist/controllers/bulk_select_controller.js +264 -53
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +109 -28
  29. package/dist/controllers/calendar_controller.js +346 -180
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +99 -27
  32. package/dist/controllers/carousel_controller.js +587 -300
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +18 -2
  35. package/dist/controllers/character_counter_controller.js +271 -143
  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 +77 -75
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +44 -7
  41. package/dist/controllers/clipboard_controller.js +187 -108
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +41 -5
  44. package/dist/controllers/collapsible_controller.js +374 -108
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +47 -14
  47. package/dist/controllers/color_picker_controller.js +183 -127
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +33 -6
  50. package/dist/controllers/combobox_controller.js +426 -109
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +45 -14
  53. package/dist/controllers/command_palette_controller.js +994 -350
  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 +212 -125
  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 +939 -245
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +36 -3
  62. package/dist/controllers/context_menu_controller.js +275 -127
  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 +75 -2
  68. package/dist/controllers/countdown_controller.js +235 -97
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -15
  71. package/dist/controllers/currency_input_controller.js +241 -118
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +78 -22
  74. package/dist/controllers/data_grid_controller.js +229 -135
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +64 -11
  77. package/dist/controllers/date_range_picker_controller.js +359 -165
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +22 -3
  80. package/dist/controllers/dialog_controller.js +887 -225
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  83. package/dist/controllers/direct_upload_controller.js +80 -142
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  86. package/dist/controllers/dirty_form_controller.js +26 -47
  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 +175 -20
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +52 -16
  92. package/dist/controllers/drawer_controller.js +963 -336
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +39 -3
  95. package/dist/controllers/dropdown_controller.js +342 -85
  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 +182 -58
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +67 -53
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  103. package/dist/controllers/file_dropzone_controller.js +426 -229
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.d.ts +10 -4
  106. package/dist/controllers/filter_controller.js +78 -36
  107. package/dist/controllers/filter_controller.js.map +1 -1
  108. package/dist/controllers/flash_controller.d.ts +57 -14
  109. package/dist/controllers/flash_controller.js +328 -211
  110. package/dist/controllers/flash_controller.js.map +1 -1
  111. package/dist/controllers/focus_controller.d.ts +12 -3
  112. package/dist/controllers/focus_controller.js +654 -239
  113. package/dist/controllers/focus_controller.js.map +1 -1
  114. package/dist/controllers/form_field_controller.js +153 -136
  115. package/dist/controllers/form_field_controller.js.map +1 -1
  116. package/dist/controllers/form_validation_controller.js +29 -96
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  119. package/dist/controllers/frame_loading_controller.js +263 -198
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +22 -2
  122. package/dist/controllers/highlight_controller.js +119 -70
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +65 -4
  125. package/dist/controllers/hover_card_controller.js +335 -118
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +58 -4
  128. package/dist/controllers/idle_controller.js +316 -65
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +5 -2
  131. package/dist/controllers/input_mask_controller.js +116 -61
  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 +147 -103
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  137. package/dist/controllers/lazy_frame_controller.js +9 -56
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +84 -10
  140. package/dist/controllers/listbox_controller.js +413 -153
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +1 -1
  143. package/dist/controllers/local_time_controller.js +54 -64
  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 +70 -95
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +40 -9
  149. package/dist/controllers/menu_controller.js +254 -167
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +15 -4
  152. package/dist/controllers/menubar_controller.js +206 -331
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +20 -0
  155. package/dist/controllers/meter_controller.js +159 -71
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +50 -7
  158. package/dist/controllers/multi_select_controller.js +797 -388
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +19 -1
  161. package/dist/controllers/navigation_menu_controller.js +156 -231
  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 +373 -174
  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 +241 -45
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +62 -10
  170. package/dist/controllers/number_input_controller.js +387 -227
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.js +28 -75
  173. package/dist/controllers/optimistic_controller.js.map +1 -1
  174. package/dist/controllers/otp_controller.d.ts +38 -7
  175. package/dist/controllers/otp_controller.js +344 -196
  176. package/dist/controllers/otp_controller.js.map +1 -1
  177. package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
  178. package/dist/controllers/overflow_indicator_controller.js +167 -93
  179. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  180. package/dist/controllers/overflow_menu_controller.d.ts +66 -9
  181. package/dist/controllers/overflow_menu_controller.js +408 -277
  182. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  183. package/dist/controllers/pagination_controller.d.ts +52 -15
  184. package/dist/controllers/pagination_controller.js +342 -120
  185. package/dist/controllers/pagination_controller.js.map +1 -1
  186. package/dist/controllers/password_reveal_controller.d.ts +60 -9
  187. package/dist/controllers/password_reveal_controller.js +424 -99
  188. package/dist/controllers/password_reveal_controller.js.map +1 -1
  189. package/dist/controllers/password_strength_controller.d.ts +14 -5
  190. package/dist/controllers/password_strength_controller.js +99 -153
  191. package/dist/controllers/password_strength_controller.js.map +1 -1
  192. package/dist/controllers/persist_controller.d.ts +11 -4
  193. package/dist/controllers/persist_controller.js +252 -96
  194. package/dist/controllers/persist_controller.js.map +1 -1
  195. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  196. package/dist/controllers/pointer_drag_controller.js +263 -164
  197. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  198. package/dist/controllers/popover_controller.d.ts +46 -4
  199. package/dist/controllers/popover_controller.js +267 -94
  200. package/dist/controllers/popover_controller.js.map +1 -1
  201. package/dist/controllers/portal_controller.d.ts +44 -4
  202. package/dist/controllers/portal_controller.js +209 -72
  203. package/dist/controllers/portal_controller.js.map +1 -1
  204. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  205. package/dist/controllers/preview_guard_controller.js +243 -112
  206. package/dist/controllers/preview_guard_controller.js.map +1 -1
  207. package/dist/controllers/progress_controller.d.ts +11 -0
  208. package/dist/controllers/progress_controller.js +132 -59
  209. package/dist/controllers/progress_controller.js.map +1 -1
  210. package/dist/controllers/radio_group_controller.d.ts +19 -8
  211. package/dist/controllers/radio_group_controller.js +138 -133
  212. package/dist/controllers/radio_group_controller.js.map +1 -1
  213. package/dist/controllers/range_slider_controller.d.ts +92 -4
  214. package/dist/controllers/range_slider_controller.js +245 -112
  215. package/dist/controllers/range_slider_controller.js.map +1 -1
  216. package/dist/controllers/rating_controller.d.ts +33 -6
  217. package/dist/controllers/rating_controller.js +429 -194
  218. package/dist/controllers/rating_controller.js.map +1 -1
  219. package/dist/controllers/read_more_controller.d.ts +26 -2
  220. package/dist/controllers/read_more_controller.js +185 -47
  221. package/dist/controllers/read_more_controller.js.map +1 -1
  222. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  223. package/dist/controllers/reading_progress_controller.js +147 -135
  224. package/dist/controllers/reading_progress_controller.js.map +1 -1
  225. package/dist/controllers/relative_time_controller.d.ts +18 -4
  226. package/dist/controllers/relative_time_controller.js +145 -110
  227. package/dist/controllers/relative_time_controller.js.map +1 -1
  228. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  229. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  230. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  231. package/dist/controllers/resizable_controller.d.ts +44 -4
  232. package/dist/controllers/resizable_controller.js +294 -118
  233. package/dist/controllers/resizable_controller.js.map +1 -1
  234. package/dist/controllers/roving_controller.js +39 -84
  235. package/dist/controllers/roving_controller.js.map +1 -1
  236. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  237. package/dist/controllers/scroll_area_controller.js +428 -221
  238. package/dist/controllers/scroll_area_controller.js.map +1 -1
  239. package/dist/controllers/scroll_restore_controller.js +0 -53
  240. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  241. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  242. package/dist/controllers/scroll_visibility_controller.js +395 -146
  243. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  244. package/dist/controllers/scrollspy_controller.d.ts +9 -3
  245. package/dist/controllers/scrollspy_controller.js +181 -236
  246. package/dist/controllers/scrollspy_controller.js.map +1 -1
  247. package/dist/controllers/separator_controller.d.ts +59 -17
  248. package/dist/controllers/separator_controller.js +288 -132
  249. package/dist/controllers/separator_controller.js.map +1 -1
  250. package/dist/controllers/sidebar_controller.d.ts +81 -7
  251. package/dist/controllers/sidebar_controller.js +1071 -375
  252. package/dist/controllers/sidebar_controller.js.map +1 -1
  253. package/dist/controllers/skeleton_controller.d.ts +23 -0
  254. package/dist/controllers/skeleton_controller.js +248 -92
  255. package/dist/controllers/skeleton_controller.js.map +1 -1
  256. package/dist/controllers/slider_controller.d.ts +72 -8
  257. package/dist/controllers/slider_controller.js +211 -96
  258. package/dist/controllers/slider_controller.js.map +1 -1
  259. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  260. package/dist/controllers/smart_sticky_header_controller.js +163 -54
  261. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  262. package/dist/controllers/sortable_controller.js +13 -110
  263. package/dist/controllers/sortable_controller.js.map +1 -1
  264. package/dist/controllers/spinner_controller.d.ts +39 -4
  265. package/dist/controllers/spinner_controller.js +277 -161
  266. package/dist/controllers/spinner_controller.js.map +1 -1
  267. package/dist/controllers/step_indicator_controller.d.ts +24 -17
  268. package/dist/controllers/step_indicator_controller.js +107 -53
  269. package/dist/controllers/step_indicator_controller.js.map +1 -1
  270. package/dist/controllers/stepper_controller.d.ts +43 -9
  271. package/dist/controllers/stepper_controller.js +171 -48
  272. package/dist/controllers/stepper_controller.js.map +1 -1
  273. package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
  274. package/dist/controllers/stick_to_bottom_controller.js +310 -116
  275. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  276. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  277. package/dist/controllers/sticky_observer_controller.js +214 -49
  278. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  279. package/dist/controllers/submit_once_controller.d.ts +22 -6
  280. package/dist/controllers/submit_once_controller.js +294 -192
  281. package/dist/controllers/submit_once_controller.js.map +1 -1
  282. package/dist/controllers/switch_controller.d.ts +40 -7
  283. package/dist/controllers/switch_controller.js +107 -43
  284. package/dist/controllers/switch_controller.js.map +1 -1
  285. package/dist/controllers/tabs_controller.d.ts +13 -1
  286. package/dist/controllers/tabs_controller.js +50 -25
  287. package/dist/controllers/tabs_controller.js.map +1 -1
  288. package/dist/controllers/tags_input_controller.d.ts +27 -3
  289. package/dist/controllers/tags_input_controller.js +290 -149
  290. package/dist/controllers/tags_input_controller.js.map +1 -1
  291. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  292. package/dist/controllers/textarea_autosize_controller.js +71 -58
  293. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  294. package/dist/controllers/theme_controller.d.ts +37 -8
  295. package/dist/controllers/theme_controller.js +148 -80
  296. package/dist/controllers/theme_controller.js.map +1 -1
  297. package/dist/controllers/time_picker_controller.d.ts +18 -1
  298. package/dist/controllers/time_picker_controller.js +162 -81
  299. package/dist/controllers/time_picker_controller.js.map +1 -1
  300. package/dist/controllers/toast_controller.d.ts +85 -23
  301. package/dist/controllers/toast_controller.js +365 -193
  302. package/dist/controllers/toast_controller.js.map +1 -1
  303. package/dist/controllers/toggle_group_controller.d.ts +60 -9
  304. package/dist/controllers/toggle_group_controller.js +194 -99
  305. package/dist/controllers/toggle_group_controller.js.map +1 -1
  306. package/dist/controllers/toolbar_controller.js +92 -90
  307. package/dist/controllers/toolbar_controller.js.map +1 -1
  308. package/dist/controllers/tooltip_controller.d.ts +52 -2
  309. package/dist/controllers/tooltip_controller.js +185 -125
  310. package/dist/controllers/tooltip_controller.js.map +1 -1
  311. package/dist/controllers/transition_controller.d.ts +22 -4
  312. package/dist/controllers/transition_controller.js +123 -115
  313. package/dist/controllers/transition_controller.js.map +1 -1
  314. package/dist/controllers/tree_view_controller.d.ts +59 -18
  315. package/dist/controllers/tree_view_controller.js +248 -290
  316. package/dist/controllers/tree_view_controller.js.map +1 -1
  317. package/dist/index.d.ts +33 -3
  318. package/dist/index.js +8649 -8572
  319. package/dist/index.js.map +1 -1
  320. package/dist/inspector/cli.d.ts +95 -9
  321. package/dist/inspector/cli.js +343 -77
  322. package/dist/inspector/cli.js.map +1 -1
  323. package/dist/inspector/cli_bin.js +390 -96
  324. package/dist/inspector/cli_bin.js.map +1 -1
  325. package/dist/inspector/examples.json +45 -45
  326. package/dist/inspector/manifest.json +1308 -155
  327. package/dist/positioning/index.d.ts +8 -0
  328. package/dist/positioning/index.js +148 -45
  329. package/dist/positioning/index.js.map +1 -1
  330. package/package.json +7 -7
  331. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  332. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -10,7 +10,10 @@ import { Controller } from '@hotwired/stimulus';
10
10
  * data-stimeo--accordion-target="trigger"
11
11
  * data-action="click->stimeo--accordion#toggle
12
12
  * keydown->stimeo--accordion#onKeydown"
13
- * aria-expanded="false" aria-controls="panel-1">Section 1</button>
13
+ * aria-expanded="false" aria-controls="panel-1">
14
+ * <span data-stimeo--accordion-target="expandedLabel" hidden>Hide section 1</span>
15
+ * <span data-stimeo--accordion-target="collapsedLabel">Show section 1</span>
16
+ * </button>
14
17
  * </h3>
15
18
  * <div id="panel-1" data-stimeo--accordion-target="panel"
16
19
  * role="region" aria-labelledby="trigger-1" hidden>…</div>
@@ -32,21 +35,47 @@ import { Controller } from '@hotwired/stimulus';
32
35
  * - `ArrowDown`/`ArrowUp` move focus between headers; `Home`/`End` jump to the
33
36
  * first/last header. Hidden headers (including those inside a hidden subtree,
34
37
  * e.g. a section a filter collapsed) are skipped so focus stays perceivable.
38
+ * - A header may carry an `expandedLabel` / `collapsedLabel` pair: the half that
39
+ * matches its `aria-expanded` is shown and the other hidden, settled as soon as
40
+ * the header and both halves are connected — whichever of them arrives last.
41
+ * Both halves belong inside the same header; a header holding only one of them
42
+ * keeps it exactly as authored. The header's accessible name stays the
43
+ * consumer's to write.
35
44
  * - {@link expandAll} / {@link collapseAll} open or close every panel at once,
36
45
  * for an optional "expand all / collapse all" control pair anywhere in scope.
46
+ * - Each move of a panel is reported: `stimeo--accordion:open` and
47
+ * `stimeo--accordion:close` dispatch
48
+ * `{ reason: StateReason, index: number, trigger: HTMLElement, panel: HTMLElement }`
49
+ * — `index` is the header's position in `triggerTargets` — after
50
+ * `aria-expanded` and `hidden` are written. Both are informational, so
51
+ * neither is cancelable. {@link expandAll} / {@link collapseAll} report one
52
+ * event per pair that actually moved, so a second `expandAll` says nothing.
53
+ * The controller establishes no baseline of its own — a header and the panel it
54
+ * controls keep the state their markup carries — so a connection reports nothing.
37
55
  */
38
56
  declare class AccordionController extends Controller<HTMLElement> {
39
57
  #private;
40
58
  static targets: string[];
41
59
  static actions: readonly ["collapseAll", "expandAll", "onKeydown", "toggle"];
60
+ static events: readonly ["close", "open"];
42
61
  readonly triggerTargets: HTMLButtonElement[];
43
62
  readonly panelTargets: HTMLElement[];
44
- /** Toggles the panel controlled by the activated header. */
45
- toggle(event: Event): void;
63
+ readonly expandedLabelTargets: HTMLElement[];
64
+ readonly collapsedLabelTargets: HTMLElement[];
65
+ /** Settles the label pair of a header as it joins the group. */
66
+ triggerTargetConnected(trigger: HTMLButtonElement): void;
67
+ /** Gives a header that no longer resolves as a trigger the label pair its author wrote. */
68
+ triggerTargetDisconnected(trigger: HTMLButtonElement): void;
69
+ /** Settles the pair of the header an arriving expanded half belongs to. */
70
+ expandedLabelTargetConnected(label: HTMLElement): void;
71
+ /** Settles the pair of the header an arriving collapsed half belongs to. */
72
+ collapsedLabelTargetConnected(label: HTMLElement): void;
73
+ /** Toggles the panel of an owned header, supplied directly or through an action event. */
74
+ toggle(source: Event | HTMLElement): void;
46
75
  /** Opens every panel. Bound via `data-action` on an "expand all" control. */
47
- expandAll(): void;
76
+ expandAll(event?: Event): void;
48
77
  /** Closes every panel. Bound via `data-action` on a "collapse all" control. */
49
- collapseAll(): void;
78
+ collapseAll(event?: Event): void;
50
79
  /**
51
80
  * Moves focus between headers per the APG keyboard model, skipping any header
52
81
  * that is hidden or nested in a hidden subtree. A consumer may hide whole
@@ -1,51 +1,253 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/accordion_controller.ts
3
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
4
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
5
+ function stateReasonFor(event) {
6
+ if (!event) return "api";
7
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
8
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
9
+ return "user";
10
+ }
11
+
12
+ function actionSource(source) {
13
+ if (source instanceof HTMLElement) {
14
+ return { event: null, host: source, origin: source, reason: "api" };
15
+ }
16
+ const event = source ?? null;
17
+ return {
18
+ event,
19
+ host: event?.currentTarget instanceof Element ? event.currentTarget : null,
20
+ origin: event?.target instanceof Element ? event.target : null,
21
+ reason: stateReasonFor(event)
22
+ };
23
+ }
4
24
 
5
- // src/utils/arrow_step.ts
6
25
  function isReservedArrowChord(event, allow = []) {
7
26
  if (!event.key.startsWith("Arrow")) return false;
8
27
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
9
28
  }
10
29
 
11
- // src/controllers/accordion_controller.ts
30
+ function sharedRegistry(key, create) {
31
+ const symbol = Symbol.for(key);
32
+ const scope = globalThis;
33
+ const existing = scope[symbol];
34
+ if (existing !== void 0) return existing;
35
+ const registry = create();
36
+ Object.defineProperty(globalThis, symbol, { value: registry });
37
+ return registry;
38
+ }
39
+
40
+ var StateRegions = class _StateRegions {
41
+ static #registry = sharedRegistry(
42
+ "stimeo-ui.state-regions.registry.v1",
43
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
44
+ );
45
+ #whenTrue;
46
+ #whenFalse;
47
+ #record;
48
+ constructor(options, owner) {
49
+ this.#whenTrue = options.whenTrue;
50
+ this.#whenFalse = options.whenFalse ?? null;
51
+ this.#record = `data-${owner}-hidden-region`;
52
+ _StateRegions.#registry.names.add(this.#record);
53
+ }
54
+ reflect(host, isTrue) {
55
+ const shown = this.#inside(host, this.#whenTrue());
56
+ if (!this.#whenFalse) {
57
+ for (const region of shown) this.#write(region, !isTrue);
58
+ return;
59
+ }
60
+ const hiddenSide = this.#inside(host, this.#whenFalse());
61
+ if (shown.length === 0 || hiddenSide.length === 0) {
62
+ for (const region of shown) this.#give(region);
63
+ for (const region of hiddenSide) this.#give(region);
64
+ return;
65
+ }
66
+ for (const region of shown) this.#write(region, !isTrue);
67
+ for (const region of hiddenSide) this.#write(region, isTrue);
68
+ }
69
+ release(host) {
70
+ for (const region of [host, ...host.querySelectorAll("*")]) {
71
+ if (!(region instanceof HTMLElement)) continue;
72
+ const stack = this.#held(region);
73
+ const hold = stack && this.#take(stack);
74
+ if (!stack || !hold) continue;
75
+ const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);
76
+ const index = stack.indexOf(hold);
77
+ stack.splice(index, 1);
78
+ const above = stack[index];
79
+ if (above) above.linked &&= hold.linked;
80
+ if (owns) {
81
+ const below = hold.linked ? stack[index - 1] : void 0;
82
+ if (below) {
83
+ if (region.hidden !== below.written) region.hidden = below.written;
84
+ } else if (region.getAttribute("hidden") !== hold.authored) {
85
+ if (hold.authored === null) region.removeAttribute("hidden");
86
+ else region.setAttribute("hidden", hold.authored);
87
+ }
88
+ }
89
+ this.#mark(region, stack);
90
+ }
91
+ }
92
+ #write(region, hidden) {
93
+ const stack = this.#stack(region);
94
+ const hold = this.#take(stack) ?? this.#acquire(region, stack);
95
+ const shows = !this.#covered(region, stack, hold);
96
+ hold.written = hidden;
97
+ if (shows && region.hidden !== hidden) region.hidden = hidden;
98
+ this.#mark(region, stack);
99
+ }
100
+ #give(region) {
101
+ const stack = this.#held(region);
102
+ if (stack && this.#take(stack)?.written) this.#write(region, false);
103
+ }
104
+ #held(region) {
105
+ return _StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record) ? this.#stack(region) : void 0;
106
+ }
107
+ #stack(region) {
108
+ const stacks = _StateRegions.#registry.stacks;
109
+ const stack = stacks.get(region) ?? this.#seed(region);
110
+ stacks.set(region, stack);
111
+ return stack;
112
+ }
113
+ #seed(region) {
114
+ const found = [];
115
+ for (const record of _StateRegions.#registry.names) {
116
+ const read = this.#recorded(region, record);
117
+ if (read) found.push({ ...read, record });
118
+ }
119
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
120
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, authored, written, linked }, index) => ({
121
+ instance: null,
122
+ record,
123
+ authored,
124
+ written,
125
+ linked: linked && index > 0
126
+ }));
127
+ }
128
+ #take(stack) {
129
+ const hold = stack.find((held) => held.instance === this) ?? stack.find((held) => held.instance === null && held.record === this.#record);
130
+ if (hold) hold.instance = this;
131
+ return hold;
132
+ }
133
+ #covered(region, stack, hold) {
134
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);
135
+ }
136
+ #acquire(region, stack) {
137
+ const top = stack.at(-1);
138
+ const over = top && region.hidden === top.written ? top : void 0;
139
+ const hold = {
140
+ instance: this,
141
+ record: this.#record,
142
+ authored: over ? over.authored : region.getAttribute("hidden"),
143
+ written: false,
144
+ linked: over !== void 0
145
+ };
146
+ stack.push(hold);
147
+ return hold;
148
+ }
149
+ #mark(region, stack) {
150
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
151
+ stack.forEach((hold, index) => {
152
+ const names = stack.slice(0, index).map((under) => under.record);
153
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
154
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
155
+ records.set(
156
+ hold.record,
157
+ region.getAttribute("hidden") === hold.authored && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath])
158
+ );
159
+ });
160
+ for (const [name, recorded] of records) {
161
+ if (region.getAttribute(name) === recorded) continue;
162
+ if (recorded === null) region.removeAttribute(name);
163
+ else region.setAttribute(name, recorded);
164
+ }
165
+ }
166
+ #recorded(region, name) {
167
+ try {
168
+ const parsed = JSON.parse(region.getAttribute(name) ?? "null");
169
+ if (!Array.isArray(parsed)) return null;
170
+ const [authored, written, ...rest] = parsed;
171
+ if (authored !== null && typeof authored !== "string") return null;
172
+ const linked = rest[0] !== false;
173
+ const beneath = linked ? rest : rest.slice(1);
174
+ if (!beneath.every((under) => typeof under === "string")) return null;
175
+ return typeof written === "boolean" ? { authored, written, linked, beneath } : null;
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+ #inside(host, regions) {
181
+ return regions.filter((region) => host.contains(region));
182
+ }
183
+ };
184
+
185
+ function targetSelector(identifier, name) {
186
+ return `[data-${identifier}-target~="${name}"]`;
187
+ }
188
+
12
189
  var AccordionController = class extends Controller {
13
- static targets = ["trigger", "panel"];
190
+ static targets = ["trigger", "panel", "expandedLabel", "collapsedLabel"];
14
191
  static actions = ["collapseAll", "expandAll", "onKeydown", "toggle"];
15
- /** Toggles the panel controlled by the activated header. */
16
- toggle(event) {
17
- const trigger = event.currentTarget;
192
+ static events = ["close", "open"];
193
+ #labels = new StateRegions(
194
+ {
195
+ whenTrue: () => this.expandedLabelTargets,
196
+ whenFalse: () => this.collapsedLabelTargets
197
+ },
198
+ this.identifier
199
+ );
200
+ triggerTargetConnected(trigger) {
201
+ this.#reflectLabels(trigger);
202
+ }
203
+ triggerTargetDisconnected(trigger) {
204
+ if (!this.triggerTargets.includes(trigger)) this.#labels.release(trigger);
205
+ }
206
+ expandedLabelTargetConnected(label) {
207
+ this.#settleLabelHost(label);
208
+ }
209
+ collapsedLabelTargetConnected(label) {
210
+ this.#settleLabelHost(label);
211
+ }
212
+ #settleLabelHost(label) {
213
+ const trigger = this.triggerTargets.find((candidate) => candidate.contains(label));
214
+ if (trigger) this.#reflectLabels(trigger);
215
+ }
216
+ toggle(source) {
217
+ const { host, origin, reason } = actionSource(source);
218
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
219
+ return;
220
+ const trigger = host?.closest(targetSelector(this.identifier, "trigger"));
221
+ if (!trigger || !this.triggerTargets.includes(trigger)) return;
18
222
  const panel = this.#panelFor(trigger);
19
223
  if (!panel) return;
20
- this.#setExpanded(trigger, panel, trigger.getAttribute("aria-expanded") !== "true");
224
+ this.#setExpanded(trigger, panel, trigger.getAttribute("aria-expanded") !== "true", reason);
21
225
  }
22
- /** Opens every panel. Bound via `data-action` on an "expand all" control. */
23
- expandAll() {
24
- this.#setAll(true);
226
+ expandAll(event) {
227
+ this.#setAll(true, stateReasonFor(event));
25
228
  }
26
- /** Closes every panel. Bound via `data-action` on a "collapse all" control. */
27
- collapseAll() {
28
- this.#setAll(false);
229
+ collapseAll(event) {
230
+ this.#setAll(false, stateReasonFor(event));
29
231
  }
30
- /** Drives every header/panel pair to the same expanded state. */
31
- #setAll(open) {
232
+ #setAll(open, reason) {
32
233
  for (const trigger of this.triggerTargets) {
33
234
  const panel = this.#panelFor(trigger);
34
- if (panel) this.#setExpanded(trigger, panel, open);
235
+ if (panel) this.#setExpanded(trigger, panel, open, reason);
35
236
  }
36
237
  }
37
- /** Reflects one header/panel pair's state through `aria-expanded` + `hidden`. */
38
- #setExpanded(trigger, panel, open) {
238
+ #setExpanded(trigger, panel, open, reason) {
239
+ const was = trigger.getAttribute("aria-expanded") === "true";
39
240
  trigger.setAttribute("aria-expanded", open ? "true" : "false");
40
241
  panel.hidden = !open;
242
+ this.#labels.reflect(trigger, open);
243
+ if (was === open) return;
244
+ const detail = { reason, index: this.triggerTargets.indexOf(trigger), trigger, panel };
245
+ if (open) this.dispatch("open", { detail, cancelable: false });
246
+ else this.dispatch("close", { detail, cancelable: false });
247
+ }
248
+ #reflectLabels(trigger) {
249
+ this.#labels.reflect(trigger, trigger.getAttribute("aria-expanded") === "true");
41
250
  }
42
- /**
43
- * Moves focus between headers per the APG keyboard model, skipping any header
44
- * that is hidden or nested in a hidden subtree. A consumer may hide whole
45
- * sections (e.g. an `stimeo--filter` that collapses empty groups), and an
46
- * unperceivable header must never become an arrow-key target — otherwise
47
- * `.focus()` lands on nothing and navigation appears to stall.
48
- */
49
251
  onKeydown(event) {
50
252
  if (event.defaultPrevented) return;
51
253
  if (isReservedArrowChord(event)) return;
@@ -74,7 +276,6 @@ var AccordionController = class extends Controller {
74
276
  event.preventDefault();
75
277
  next?.focus();
76
278
  }
77
- /** Resolves the panel a header controls via its `aria-controls` reference. */
78
279
  #panelFor(trigger) {
79
280
  const id = trigger.getAttribute("aria-controls");
80
281
  return id ? this.panelTargets.find((panel) => panel.id === id) ?? null : null;
@@ -82,5 +283,4 @@ var AccordionController = class extends Controller {
82
283
  };
83
284
 
84
285
  export { AccordionController };
85
- //# sourceMappingURL=accordion_controller.js.map
86
286
  //# sourceMappingURL=accordion_controller.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/arrow_step.ts","../../src/controllers/accordion_controller.ts"],"names":[],"mappings":"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","file":"accordion_controller.js","sourcesContent":["import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n\n/**\n * True when a press arrived carrying any modifier, for the keys the browser and\n * the OS own outright: return without calling `preventDefault()` and without\n * moving any state.\n *\n * `Control+Home` and `Control+End` jump the document to its ends, and a widget\n * that swallows them makes the shortcut work or not depending on where focus\n * happens to sit. {@link isReservedArrowChord} answers the same question for the\n * arrows, but returns `false` for every other key so that this one can decide.\n *\n * There is no `allow` list here on purpose. APG assigns no modifier chord to\n * `Home`/`End`, so a widget that wanted one would be claiming a combination the\n * pattern never gave it.\n *\n * @example\n * ```ts\n * case \"Home\":\n * case \"End\":\n * if (hasModifierChord(event)) return;\n * ```\n */\nexport function hasModifierChord(event: KeyboardEvent): boolean {\n return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\n\n/**\n * Headless, accessible accordion behavior.\n *\n * Markup contract (identifier: `stimeo--accordion`):\n * <div data-controller=\"stimeo--accordion\">\n * <h3>\n * <button id=\"trigger-1\"\n * data-stimeo--accordion-target=\"trigger\"\n * data-action=\"click->stimeo--accordion#toggle\n * keydown->stimeo--accordion#onKeydown\"\n * aria-expanded=\"false\" aria-controls=\"panel-1\">Section 1</button>\n * </h3>\n * <div id=\"panel-1\" data-stimeo--accordion-target=\"panel\"\n * role=\"region\" aria-labelledby=\"trigger-1\" hidden>…</div>\n * <!-- repeat header/panel pairs -->\n * </div>\n *\n * Implements the WAI-ARIA APG **Accordion** pattern. Each header button is\n * associated with its panel through `aria-controls`; the panel's `id` is the\n * single source of truth for the pairing, so headers and panels need not be\n * adjacent siblings.\n *\n * @remarks\n * Multiple panels may be open at once (this is the APG-allowed default). State\n * is reflected through `aria-expanded` on the header and the `hidden` attribute\n * on the panel — never through visual styling, which the consumer owns.\n *\n * Behavior provided:\n * - Click a header to toggle its panel (`aria-expanded` + `hidden` reflect state).\n * - `ArrowDown`/`ArrowUp` move focus between headers; `Home`/`End` jump to the\n * first/last header. Hidden headers (including those inside a hidden subtree,\n * e.g. a section a filter collapsed) are skipped so focus stays perceivable.\n * - {@link expandAll} / {@link collapseAll} open or close every panel at once,\n * for an optional \"expand all / collapse all\" control pair anywhere in scope.\n */\nexport class AccordionController extends Controller<HTMLElement> {\n static override targets = [\"trigger\", \"panel\"];\n static actions = [\"collapseAll\", \"expandAll\", \"onKeydown\", \"toggle\"] as const;\n\n declare readonly triggerTargets: HTMLButtonElement[];\n declare readonly panelTargets: HTMLElement[];\n\n /** Toggles the panel controlled by the activated header. */\n toggle(event: Event): void {\n const trigger = event.currentTarget as HTMLButtonElement;\n const panel = this.#panelFor(trigger);\n if (!panel) return;\n\n this.#setExpanded(trigger, panel, trigger.getAttribute(\"aria-expanded\") !== \"true\");\n }\n\n /** Opens every panel. Bound via `data-action` on an \"expand all\" control. */\n expandAll(): void {\n this.#setAll(true);\n }\n\n /** Closes every panel. Bound via `data-action` on a \"collapse all\" control. */\n collapseAll(): void {\n this.#setAll(false);\n }\n\n /** Drives every header/panel pair to the same expanded state. */\n #setAll(open: boolean): void {\n for (const trigger of this.triggerTargets) {\n const panel = this.#panelFor(trigger);\n if (panel) this.#setExpanded(trigger, panel, open);\n }\n }\n\n /** Reflects one header/panel pair's state through `aria-expanded` + `hidden`. */\n #setExpanded(trigger: HTMLButtonElement, panel: HTMLElement, open: boolean): void {\n trigger.setAttribute(\"aria-expanded\", open ? \"true\" : \"false\");\n panel.hidden = !open;\n }\n\n /**\n * Moves focus between headers per the APG keyboard model, skipping any header\n * that is hidden or nested in a hidden subtree. A consumer may hide whole\n * sections (e.g. an `stimeo--filter` that collapses empty groups), and an\n * unperceivable header must never become an arrow-key target — otherwise\n * `.focus()` lands on nothing and navigation appears to stall.\n */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key (a grabbed drag handle, a\n // nested menu) must not ALSO act on it — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n const current = event.currentTarget as HTMLButtonElement;\n if (this.triggerTargets.indexOf(current) === -1) return;\n\n // `closest(\"[hidden]\")` catches both a directly-hidden header and one inside a\n // hidden ancestor (the filter-group case); navigate over the visible set only.\n const navigable = this.triggerTargets.filter((trigger) => trigger.closest(\"[hidden]\") === null);\n const here = navigable.indexOf(current);\n if (here === -1) return;\n\n let next: HTMLButtonElement | undefined;\n switch (event.key) {\n case \"ArrowDown\":\n next = navigable[(here + 1) % navigable.length];\n break;\n case \"ArrowUp\":\n next = navigable[(here - 1 + navigable.length) % navigable.length];\n break;\n case \"Home\":\n next = navigable[0];\n break;\n case \"End\":\n next = navigable[navigable.length - 1];\n break;\n default:\n return;\n }\n\n event.preventDefault();\n next?.focus();\n }\n\n /** Resolves the panel a header controls via its `aria-controls` reference. */\n #panelFor(trigger: HTMLButtonElement): HTMLElement | null {\n const id = trigger.getAttribute(\"aria-controls\");\n return id ? (this.panelTargets.find((panel) => panel.id === id) ?? null) : null;\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/state_reason.ts","../../src/utils/action_source.ts","../../src/utils/arrow_step.ts","../../src/utils/shared_registry.ts","../../src/utils/state_regions.ts","../../src/utils/target_selector.ts","../../src/controllers/accordion_controller.ts"],"names":[],"mappings":"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","file":"accordion_controller.js","sourcesContent":["/**\n * Why a component's public state moved, carried as `detail.reason`\n * on the state event that reports the move.\n *\n * The attribute a component publishes (`aria-expanded`, `hidden`, `data-state`)\n * only says *what* the state is now. A subscriber that mirrors the state\n * elsewhere, or reports it, needs *why*: closing on `Escape` and closing because\n * the consumer called the action are the same attribute write and different\n * events to the page around it.\n *\n * `\"api\"` is the one every subscriber has to look at. A close the consumer asked\n * for arrives back at their own listener, so a listener that closes something\n * else on `close` loops unless it ignores its own calls.\n *\n * | Reason | The state moved because |\n * | --- | --- |\n * | `\"user\"` | a control of this component was operated |\n * | `\"select\"` | an item inside it was activated |\n * | `\"escape\"` | `Escape` was pressed and this layer owned it |\n * | `\"outside\"` | a pointer landed outside it, or on its backdrop |\n * | `\"focus\"` | focus entered or left it |\n * | `\"pointer\"` | the pointer entered or left it |\n * | `\"scroll\"` | a tracked scroll container scrolled |\n * | `\"api\"` | a public action was called with no DOM event |\n * | `\"timeout\"` | an automatic timer advanced or dismissed the state |\n * | `\"limit\"` | a capacity limit dismissed an item |\n * | `\"media\"` | an operating-system media preference changed |\n */\nexport type StateReason =\n | \"user\"\n | \"select\"\n | \"escape\"\n | \"outside\"\n | \"focus\"\n | \"pointer\"\n | \"scroll\"\n | \"api\"\n | \"timeout\"\n | \"limit\"\n | \"media\";\n\n/** Focus modality: the events a focus move delivers to an action. */\nconst FOCUS_EVENTS = new Set([\"blur\", \"focus\", \"focusin\", \"focusout\"]);\n\n/** Pointer modality: the crossing events an action is bound to for hover. */\nconst POINTER_EVENTS = new Set([\"mouseenter\", \"mouseleave\", \"pointerenter\", \"pointerleave\"]);\n\n/**\n * Reads the interaction behind a **public action** from the DOM event it was\n * handed.\n *\n * Stimulus always passes the event to an action it invoked from `data-action`,\n * and a consumer calling the method themselves passes nothing — which is what\n * separates `\"api\"` from the rest.\n *\n * Only an action entry may resolve a reason this way. Inside the component the\n * event type no longer identifies the interaction: one `click` is the trigger\n * being pressed, the page outside being pressed, and an item being activated,\n * and those paths pass their own reason instead.\n *\n * @param event - The event the action received, if any.\n * @returns `\"api\"` with no event, else the modality the event type names, else\n * `\"user\"`.\n *\n * @example\n * ```ts\n * toggle(event?: Event): void {\n * this.#apply(!this.#isOpen, stateReasonFor(event));\n * }\n * ```\n */\nexport function stateReasonFor(event?: Event | null): \"user\" | \"focus\" | \"pointer\" | \"api\" {\n if (!event) return \"api\";\n if (FOCUS_EVENTS.has(event.type)) return \"focus\";\n if (POINTER_EVENTS.has(event.type)) return \"pointer\";\n return \"user\";\n}\n","import { stateReasonFor } from \"./state_reason\";\n\n/** The DOM binding and origin of a public action, or its explicit API element. */\nexport interface ActionSource {\n readonly event: Event | null;\n readonly host: Element | null;\n readonly origin: Element | null;\n readonly reason: ReturnType<typeof stateReasonFor>;\n}\n\n/**\n * Normalizes an action's DOM event or explicit element without choosing an owned target.\n *\n * Event hosts and origins stay separate so consumers can yield to nested controls.\n * An explicit element supplies both, with no DOM event and an `\"api\"` reason.\n * Consumers resolve current targets and enforce their own scope and interaction rules.\n *\n * @param source - The DOM event, explicit element, or absent optional argument.\n * @returns The source elements, original event and public interaction reason.\n */\nexport function actionSource(source?: Event | HTMLElement): ActionSource {\n if (source instanceof HTMLElement) {\n return { event: null, host: source, origin: source, reason: \"api\" };\n }\n const event = source ?? null;\n return {\n event,\n host: event?.currentTarget instanceof Element ? event.currentTarget : null,\n origin: event?.target instanceof Element ? event.target : null,\n reason: stateReasonFor(event),\n };\n}\n","import { isRtl } from \"./logical_scroll\";\n\n/**\n * Turns an arrow key into a **logical** step: `+1` for \"next\", `-1` for\n * \"previous\", `0` when the key names neither.\n *\n * APG defines the horizontal pair as *next / previous* and says a vertical\n * arrangement swaps in Down/Up for the same meaning — so the pair is one axis's\n * spelling of an order, and the order reverses with the writing direction. Only\n * the horizontal pair reverses. Down/Up name an axis the writing direction does\n * not mirror, and returning them unchanged is the point: many controllers fold\n * both pairs into one branch, where swapping the branches under RTL would flip\n * the vertical axis too — a bug that reads as \"the arrows work\" until someone\n * presses Down.\n *\n * **Direction is read from the element the caller passes, which should be the\n * container that lays the items out** — not the focused child. A child may carry\n * its own `dir` (an LTR input inside an RTL form is ordinary authoring), and\n * probing per handler makes two handlers disagree at the boundary between them.\n *\n * This decides direction only. Whether the axis is even active (an\n * `orientation=\"horizontal\"` widget ignoring Down/Up), how far the step lands,\n * and what wrapping does all stay with the caller.\n *\n * **It encodes the list-order convention: `ArrowDown` is *next*.** Widgets that\n * pair the arrows by *value* instead — `ArrowUp` meaning \"more\", as a rating or a\n * slider does — must not use this, or their vertical axis inverts. Reverse the\n * horizontal pair on its own there.\n *\n * @example\n * ```ts\n * const step = logicalArrowStep(event.key, this.element);\n * if (step === 0) return;\n * this.#roving.setActive(rovingMove(current, length, step, \"wrap\"), { focus: true });\n * ```\n */\nexport function logicalArrowStep(key: string, element: Element): 1 | -1 | 0 {\n if (key === \"ArrowDown\") return 1;\n if (key === \"ArrowUp\") return -1;\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return 0;\n const forward = isRtl(element) ? \"ArrowLeft\" : \"ArrowRight\";\n return key === forward ? 1 : -1;\n}\n\n/**\n * Rewrites `key` so an existing LTR-shaped branch keeps working under RTL:\n * `ArrowRight` and `ArrowLeft` trade places, everything else passes through.\n *\n * The alternative — negating a delta — silently breaks handlers whose two\n * horizontal branches are **not mirror images**. A grid that clamps one edge but\n * not the other, or a segmented field guarding `index > 0` on one side and\n * `index < length - 1` on the other, ends up applying the wrong guard to the\n * wrong direction. Swapping the key leaves each branch, guards and all, exactly\n * where its author put it.\n *\n * Same rule as {@link logicalArrowStep} about which element to read: pass the\n * container that lays the items out, not the focused child.\n *\n * @example\n * ```ts\n * switch (logicalArrowKey(event.key, this.element)) {\n * case \"ArrowLeft\": // \"previous\" — whatever direction that is on screen\n * ```\n */\nexport function logicalArrowKey(key: string, element: Element): string {\n if (key !== \"ArrowRight\" && key !== \"ArrowLeft\") return key;\n if (!isRtl(element)) return key;\n return key === \"ArrowRight\" ? \"ArrowLeft\" : \"ArrowRight\";\n}\n\n/** Modifiers a widget may claim on an arrow key, named for the `allow` list. */\nexport type ArrowModifier = \"alt\" | \"ctrl\" | \"meta\" | \"shift\";\n\n/**\n * True when an arrow key arrived carrying a modifier the widget must leave to\n * the browser: return without calling `preventDefault()` and without moving any\n * state.\n *\n * A bare arrow belongs to the widget; a chorded one usually does not.\n * `Alt`/`Meta` plus a horizontal arrow is history back/forward on every desktop\n * browser, and a widget that swallows it makes the shortcut work or not\n * depending on where focus happens to sit — a coin-flip the user cannot see.\n *\n * `allow` is for the combinations APG assigns to a pattern **and the widget\n * actually implements** — today only Combobox's optional `Alt+Down`/`Alt+Up`.\n * Listing one the widget does not implement defeats the point: the chord then\n * runs the plain-arrow branch, which is exactly what this guard exists to stop.\n * Non-arrow keys return `false`, so chorded letters and\n * `Control+Home`/`Control+End` are untouched.\n *\n * @example\n * ```ts\n * if (isReservedArrowChord(event)) return;\n * ```\n */\nexport function isReservedArrowChord(\n event: KeyboardEvent,\n allow: readonly ArrowModifier[] = [],\n): boolean {\n if (!event.key.startsWith(\"Arrow\")) return false;\n return (\n (event.altKey && !allow.includes(\"alt\")) ||\n (event.ctrlKey && !allow.includes(\"ctrl\")) ||\n (event.metaKey && !allow.includes(\"meta\")) ||\n (event.shiftKey && !allow.includes(\"shift\"))\n );\n}\n\n/**\n * True when a press arrived carrying any modifier, for the keys the browser and\n * the OS own outright: return without calling `preventDefault()` and without\n * moving any state.\n *\n * `Control+Home` and `Control+End` jump the document to its ends, and a widget\n * that swallows them makes the shortcut work or not depending on where focus\n * happens to sit. {@link isReservedArrowChord} answers the same question for the\n * arrows, but returns `false` for every other key so that this one can decide.\n *\n * There is no `allow` list here on purpose. APG assigns no modifier chord to\n * `Home`/`End`, so a widget that wanted one would be claiming a combination the\n * pattern never gave it.\n *\n * @example\n * ```ts\n * case \"Home\":\n * case \"End\":\n * if (hasModifierChord(event)) return;\n * ```\n */\nexport function hasModifierChord(event: KeyboardEvent): boolean {\n return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;\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 { 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 if (region.getAttribute(\"hidden\") !== hold.authored) {\n if (hold.authored === null) region.removeAttribute(\"hidden\");\n else region.setAttribute(\"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 if (region.getAttribute(name) === recorded) continue;\n if (recorded === null) region.removeAttribute(name);\n else region.setAttribute(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","/**\n * The attribute selector for one declared target, in a controller's own namespace.\n *\n * Stimulus reads `data-<identifier>-target` as a **space-separated token list**, so a\n * single element can carry several target names at once. Matching the attribute value\n * whole would reach only the elements that declare exactly one name, which is why the\n * name is paired with `~=`.\n *\n * `identifier` is the name the controller is registered under, so the selector reaches\n * the markup that registration renders rather than one fixed spelling.\n */\nexport function targetSelector(identifier: string, name: string): string {\n return `[data-${identifier}-target~=\"${name}\"]`;\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { actionSource } from \"../utils/action_source\";\nimport { isReservedArrowChord } from \"../utils/arrow_step\";\nimport { type StateReason, stateReasonFor } from \"../utils/state_reason\";\nimport { StateRegions } from \"../utils/state_regions\";\nimport { targetSelector } from \"../utils/target_selector\";\n\n/**\n * Headless, accessible accordion behavior.\n *\n * Markup contract (identifier: `stimeo--accordion`):\n * <div data-controller=\"stimeo--accordion\">\n * <h3>\n * <button id=\"trigger-1\"\n * data-stimeo--accordion-target=\"trigger\"\n * data-action=\"click->stimeo--accordion#toggle\n * keydown->stimeo--accordion#onKeydown\"\n * aria-expanded=\"false\" aria-controls=\"panel-1\">\n * <span data-stimeo--accordion-target=\"expandedLabel\" hidden>Hide section 1</span>\n * <span data-stimeo--accordion-target=\"collapsedLabel\">Show section 1</span>\n * </button>\n * </h3>\n * <div id=\"panel-1\" data-stimeo--accordion-target=\"panel\"\n * role=\"region\" aria-labelledby=\"trigger-1\" hidden>…</div>\n * <!-- repeat header/panel pairs -->\n * </div>\n *\n * Implements the WAI-ARIA APG **Accordion** pattern. Each header button is\n * associated with its panel through `aria-controls`; the panel's `id` is the\n * single source of truth for the pairing, so headers and panels need not be\n * adjacent siblings.\n *\n * @remarks\n * Multiple panels may be open at once (this is the APG-allowed default). State\n * is reflected through `aria-expanded` on the header and the `hidden` attribute\n * on the panel — never through visual styling, which the consumer owns.\n *\n * Behavior provided:\n * - Click a header to toggle its panel (`aria-expanded` + `hidden` reflect state).\n * - `ArrowDown`/`ArrowUp` move focus between headers; `Home`/`End` jump to the\n * first/last header. Hidden headers (including those inside a hidden subtree,\n * e.g. a section a filter collapsed) are skipped so focus stays perceivable.\n * - A header may carry an `expandedLabel` / `collapsedLabel` pair: the half that\n * matches its `aria-expanded` is shown and the other hidden, settled as soon as\n * the header and both halves are connected — whichever of them arrives last.\n * Both halves belong inside the same header; a header holding only one of them\n * keeps it exactly as authored. The header's accessible name stays the\n * consumer's to write.\n * - {@link expandAll} / {@link collapseAll} open or close every panel at once,\n * for an optional \"expand all / collapse all\" control pair anywhere in scope.\n * - Each move of a panel is reported: `stimeo--accordion:open` and\n * `stimeo--accordion:close` dispatch\n * `{ reason: StateReason, index: number, trigger: HTMLElement, panel: HTMLElement }`\n * — `index` is the header's position in `triggerTargets` — after\n * `aria-expanded` and `hidden` are written. Both are informational, so\n * neither is cancelable. {@link expandAll} / {@link collapseAll} report one\n * event per pair that actually moved, so a second `expandAll` says nothing.\n * The controller establishes no baseline of its own — a header and the panel it\n * controls keep the state their markup carries — so a connection reports nothing.\n */\nexport class AccordionController extends Controller<HTMLElement> {\n static override targets = [\"trigger\", \"panel\", \"expandedLabel\", \"collapsedLabel\"];\n static actions = [\"collapseAll\", \"expandAll\", \"onKeydown\", \"toggle\"] as const;\n static events = [\"close\", \"open\"] as const;\n\n declare readonly triggerTargets: HTMLButtonElement[];\n declare readonly panelTargets: HTMLElement[];\n declare readonly expandedLabelTargets: HTMLElement[];\n declare readonly collapsedLabelTargets: HTMLElement[];\n\n /** Owns `hidden` on the label pair a header declares, narrowed to that header. */\n readonly #labels = new StateRegions(\n {\n whenTrue: () => this.expandedLabelTargets,\n whenFalse: () => this.collapsedLabelTargets,\n },\n this.identifier,\n );\n\n /** Settles the label pair of a header as it joins the group. */\n triggerTargetConnected(trigger: HTMLButtonElement): void {\n this.#reflectLabels(trigger);\n }\n\n /** Gives a header that no longer resolves as a trigger the label pair its author wrote. */\n triggerTargetDisconnected(trigger: HTMLButtonElement): void {\n if (!this.triggerTargets.includes(trigger)) this.#labels.release(trigger);\n }\n\n /** Settles the pair of the header an arriving expanded half belongs to. */\n expandedLabelTargetConnected(label: HTMLElement): void {\n this.#settleLabelHost(label);\n }\n\n /** Settles the pair of the header an arriving collapsed half belongs to. */\n collapsedLabelTargetConnected(label: HTMLElement): void {\n this.#settleLabelHost(label);\n }\n\n /** Reflects the pair of the header a label half sits in, where a header holds it. */\n #settleLabelHost(label: HTMLElement): void {\n const trigger = this.triggerTargets.find((candidate) => candidate.contains(label));\n if (trigger) this.#reflectLabels(trigger);\n }\n\n /** Toggles the panel of an owned header, supplied directly or through an action event. */\n toggle(source: Event | HTMLElement): void {\n const { host, origin, reason } = actionSource(source);\n if (origin && origin.closest(`[data-controller~=\"${this.identifier}\"]`) !== this.element)\n return;\n const trigger = host?.closest<HTMLButtonElement>(targetSelector(this.identifier, \"trigger\"));\n if (!trigger || !this.triggerTargets.includes(trigger)) return;\n const panel = this.#panelFor(trigger);\n if (!panel) return;\n\n this.#setExpanded(trigger, panel, trigger.getAttribute(\"aria-expanded\") !== \"true\", reason);\n }\n\n /** Opens every panel. Bound via `data-action` on an \"expand all\" control. */\n expandAll(event?: Event): void {\n this.#setAll(true, stateReasonFor(event));\n }\n\n /** Closes every panel. Bound via `data-action` on a \"collapse all\" control. */\n collapseAll(event?: Event): void {\n this.#setAll(false, stateReasonFor(event));\n }\n\n /** Drives every header/panel pair to the same expanded state. */\n #setAll(open: boolean, reason: StateReason): void {\n for (const trigger of this.triggerTargets) {\n const panel = this.#panelFor(trigger);\n if (panel) this.#setExpanded(trigger, panel, open, reason);\n }\n }\n\n /** Reflects one header/panel pair's state through `aria-expanded` + `hidden`. */\n #setExpanded(\n trigger: HTMLButtonElement,\n panel: HTMLElement,\n open: boolean,\n reason: StateReason,\n ): void {\n const was = trigger.getAttribute(\"aria-expanded\") === \"true\";\n trigger.setAttribute(\"aria-expanded\", open ? \"true\" : \"false\");\n panel.hidden = !open;\n this.#labels.reflect(trigger, open);\n if (was === open) return;\n const detail = { reason, index: this.triggerTargets.indexOf(trigger), trigger, panel };\n if (open) this.dispatch(\"open\", { detail, cancelable: false });\n else this.dispatch(\"close\", { detail, cancelable: false });\n }\n\n /** Shows the half of a header's label pair that belongs to its `aria-expanded`. */\n #reflectLabels(trigger: HTMLButtonElement): void {\n this.#labels.reflect(trigger, trigger.getAttribute(\"aria-expanded\") === \"true\");\n }\n\n /**\n * Moves focus between headers per the APG keyboard model, skipping any header\n * that is hidden or nested in a hidden subtree. A consumer may hide whole\n * sections (e.g. an `stimeo--filter` that collapses empty groups), and an\n * unperceivable header must never become an arrow-key target — otherwise\n * `.focus()` lands on nothing and navigation appears to stall.\n */\n onKeydown(event: KeyboardEvent): void {\n // A descendant widget that already claimed the key (a grabbed drag handle, a\n // nested menu) must not ALSO act on it — composition depends on this yield.\n if (event.defaultPrevented) return;\n if (isReservedArrowChord(event)) return;\n const current = event.currentTarget as HTMLButtonElement;\n if (this.triggerTargets.indexOf(current) === -1) return;\n\n // `closest(\"[hidden]\")` catches both a directly-hidden header and one inside a\n // hidden ancestor (the filter-group case); navigate over the visible set only.\n const navigable = this.triggerTargets.filter((trigger) => trigger.closest(\"[hidden]\") === null);\n const here = navigable.indexOf(current);\n if (here === -1) return;\n\n let next: HTMLButtonElement | undefined;\n switch (event.key) {\n case \"ArrowDown\":\n next = navigable[(here + 1) % navigable.length];\n break;\n case \"ArrowUp\":\n next = navigable[(here - 1 + navigable.length) % navigable.length];\n break;\n case \"Home\":\n next = navigable[0];\n break;\n case \"End\":\n next = navigable[navigable.length - 1];\n break;\n default:\n return;\n }\n\n event.preventDefault();\n next?.focus();\n }\n\n /** Resolves the panel a header controls via its `aria-controls` reference. */\n #panelFor(trigger: HTMLButtonElement): HTMLElement | null {\n const id = trigger.getAttribute(\"aria-controls\");\n return id ? (this.panelTargets.find((panel) => panel.id === id) ?? null) : null;\n }\n}\n"]}
@@ -19,9 +19,9 @@ import { Controller } from '@hotwired/stimulus';
19
19
  * Implements the WAI-ARIA APG **Alert Dialog** pattern. It is the same modal as
20
20
  * `stimeo--dialog` with two deliberate differences that suit an *interrupting
21
21
  * confirmation*: it never closes on a backdrop click (an accidental dismissal
22
- * would be dangerous), and it exposes explicit {@link confirm}/{@link cancel}
23
- * actions that emit events so the consumer only writes the message and "what to
24
- * do on confirm".
22
+ * would be dangerous), and it exposes explicit
23
+ * {@link AlertDialogController.confirm | confirm}/{@link cancel} actions that emit
24
+ * events so the consumer only writes the message and "what to do on confirm".
25
25
  *
26
26
  * `cancel` dispatches `{ reason: "user" | "escape" }`; `confirm` carries no detail.
27
27
  *
@@ -32,13 +32,16 @@ import { Controller } from '@hotwired/stimulus';
32
32
  * and `Escape` (which cancels) — there is intentionally no backdrop close.
33
33
  *
34
34
  * Behavior provided:
35
- * - {@link open} shows the dialog and moves focus to the `initialFocus` target
36
- * (the least destructive action, by convention), else the first focusable
37
- * element.
35
+ * - {@link AlertDialogController.open | open} shows the dialog and moves focus to
36
+ * the `initialFocus` target (the least destructive action, by convention), else
37
+ * the first focusable element.
38
38
  * - `Tab`/`Shift+Tab` cycle focus within the dialog (focus trap).
39
- * - {@link confirm} closes and dispatches `stimeo--alert-dialog:confirm`.
39
+ * - {@link AlertDialogController.confirm | confirm} closes and dispatches
40
+ * `stimeo--alert-dialog:confirm`.
40
41
  * - {@link cancel} and `Escape` close and dispatch `stimeo--alert-dialog:cancel`
41
42
  * with a `reason` of `"user"` / `"escape"`. Focus returns to the opener.
43
+ * - A Turbo morph that puts the server's `hidden` back on an open dialog, or takes it
44
+ * off a closed one, is answered by writing the open state back, silently.
42
45
  */
43
46
  declare class AlertDialogController extends Controller<HTMLElement> {
44
47
  #private;
@@ -47,14 +50,22 @@ declare class AlertDialogController extends Controller<HTMLElement> {
47
50
  static events: readonly ["cancel", "confirm"];
48
51
  readonly triggerTarget: HTMLElement;
49
52
  readonly dialogTarget: HTMLElement;
50
- readonly initialFocusTarget: HTMLElement;
53
+ readonly dialogTargets: HTMLElement[];
54
+ readonly initialFocusTargets: HTMLElement[];
51
55
  readonly hasTriggerTarget: boolean;
52
56
  readonly hasDialogTarget: boolean;
53
- readonly hasInitialFocusTarget: boolean;
54
57
  /** Starts closed (idempotently reflects the closed state on the markup). */
55
58
  connect(): void;
56
59
  /** Reverts the modal side effects if torn down while open (Turbo navigation). */
57
60
  disconnect(): void;
61
+ /** Applies the open state to a dialog that arrives after connect in front of the others. */
62
+ dialogTargetConnected(): void;
63
+ /**
64
+ * Gives a dialog that no longer resolves as the target its own `hidden` back — after
65
+ * `disconnect()` too, since dropping the identifier leaves the element on the page — and,
66
+ * while connected, applies the open state to the dialog left.
67
+ */
68
+ dialogTargetDisconnected(dialog: HTMLElement): void;
58
69
  /** Opens the dialog, traps focus, and locks background scroll. */
59
70
  open(): void;
60
71
  /** Confirms: closes and dispatches `confirm`. Bound via `data-action`. */