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,13 +1,31 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/radio_group_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/logical_scroll.ts
6
25
  function isRtl(element) {
7
26
  return window.getComputedStyle(element).direction === "rtl";
8
27
  }
9
28
 
10
- // src/utils/arrow_step.ts
11
29
  function logicalArrowStep(key, element) {
12
30
  if (key === "ArrowDown") return 1;
13
31
  if (key === "ArrowUp") return -1;
@@ -19,8 +37,108 @@ function isReservedArrowChord(event, allow = []) {
19
37
  if (!event.key.startsWith("Arrow")) return false;
20
38
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
21
39
  }
40
+ function hasModifierChord(event) {
41
+ return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
42
+ }
43
+
44
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
45
+ if (element.getAttribute(name) === value) return false;
46
+ beforeWrite?.();
47
+ if (value === null) element.removeAttribute(name);
48
+ else element.setAttribute(name, value);
49
+ return true;
50
+ }
51
+
52
+ var AuthoredMutationWatcher = class {
53
+ #onAuthored;
54
+ #claims = null;
55
+ #observer = null;
56
+ constructor(onAuthored) {
57
+ this.#onAuthored = onAuthored;
58
+ }
59
+ observe(root, options) {
60
+ this.disconnect();
61
+ const observer = new MutationObserver((records) => {
62
+ if (this.#observer !== observer) return;
63
+ const authored = this.#filter(records);
64
+ if (authored.length > 0) this.#onAuthored(authored);
65
+ });
66
+ this.#observer = observer;
67
+ this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
68
+ observer.observe(root, {
69
+ attributes: true,
70
+ attributeFilter: [...options.attributes],
71
+ subtree: options.subtree ?? false,
72
+ childList: options.childList ?? false
73
+ });
74
+ if (options.ancestorAttributes) {
75
+ let ancestor = root.parentElement;
76
+ while (ancestor) {
77
+ observer.observe(ancestor, {
78
+ attributes: true,
79
+ attributeFilter: [...options.ancestorAttributes]
80
+ });
81
+ ancestor = ancestor.parentElement;
82
+ }
83
+ }
84
+ }
85
+ claim(target, name, value) {
86
+ const claims = this.#claims?.attributes;
87
+ let attributes = claims?.get(target);
88
+ if (!attributes) {
89
+ attributes = /* @__PURE__ */ new Map();
90
+ claims?.set(target, attributes);
91
+ }
92
+ const pending = attributes.get(name);
93
+ attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
94
+ }
95
+ claimCurrent(target, name) {
96
+ this.claim(target, name, target.getAttribute(name));
97
+ }
98
+ claimChildList(target, added, removed) {
99
+ const claims = this.#claims?.children;
100
+ const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
101
+ for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
102
+ for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
103
+ claims?.set(target, pending);
104
+ }
105
+ take() {
106
+ return this.#filter(this.#observer?.takeRecords() ?? []);
107
+ }
108
+ disconnect() {
109
+ this.#observer?.disconnect();
110
+ this.#observer = null;
111
+ this.#claims = null;
112
+ }
113
+ #filter(records) {
114
+ const authored = records.filter((record) => !this.#consume(record));
115
+ this.#claims?.attributes.clear();
116
+ this.#claims?.children.clear();
117
+ return authored;
118
+ }
119
+ #consume(record) {
120
+ if (record.type === "attributes") {
121
+ const target = record.target;
122
+ const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
123
+ if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
124
+ return false;
125
+ }
126
+ pending2.count -= 1;
127
+ return true;
128
+ }
129
+ const pending = this.#claims?.children.get(record.target);
130
+ if (!pending) return false;
131
+ const added = [...record.addedNodes];
132
+ const removed = [...record.removedNodes];
133
+ if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
134
+ return false;
135
+ }
136
+ for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
137
+ for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
138
+ return true;
139
+ }
140
+ };
22
141
 
23
- // src/utils/event_owner.ts
24
142
  function ownerIndex(candidates, node) {
25
143
  if (!(node instanceof Node)) return -1;
26
144
  return candidates.findIndex((candidate) => candidate.contains(node));
@@ -29,7 +147,6 @@ function ownerOf(candidates, node) {
29
147
  return candidates[ownerIndex(candidates, node)] ?? null;
30
148
  }
31
149
 
32
- // src/utils/field_mirror.ts
33
150
  function writeField(field, value) {
34
151
  if (field.value === value) return false;
35
152
  field.value = value;
@@ -39,7 +156,6 @@ function commitField(target) {
39
156
  target.dispatchEvent(new Event("change", { bubbles: true }));
40
157
  }
41
158
 
42
- // src/utils/focus_candidate.ts
43
159
  function inheritsFieldsetDisabled(control) {
44
160
  let fieldset = control.closest("fieldset[disabled]");
45
161
  while (fieldset) {
@@ -50,7 +166,6 @@ function inheritsFieldsetDisabled(control) {
50
166
  return false;
51
167
  }
52
168
 
53
- // src/utils/interactive_host.ts
54
169
  var INTERACTIVE_HOST_SELECTOR = "button, input, select, textarea, label, a[href], area[href], summary, details, audio[controls], video[controls], iframe, object, embed";
55
170
  function isInteractiveHost(element) {
56
171
  if (element.matches(INTERACTIVE_HOST_SELECTOR)) return true;
@@ -67,27 +182,36 @@ function isInteractiveHost(element) {
67
182
  return false;
68
183
  }
69
184
 
70
- // src/utils/microtask_coalescer.ts
185
+ var ListenerSet = class {
186
+ #abort = new AbortController();
187
+ add(target, type, handler, options) {
188
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
189
+ }
190
+ dispose() {
191
+ this.#abort.abort();
192
+ this.#abort = new AbortController();
193
+ }
194
+ };
195
+
71
196
  var MicrotaskCoalescer = class {
72
197
  #run;
73
198
  #queued = false;
74
199
  #active = false;
75
200
  #generation = 0;
76
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
77
201
  constructor(run) {
78
202
  this.#run = run;
79
203
  }
80
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
81
204
  activate() {
82
205
  this.#active = true;
83
206
  }
84
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
85
207
  cancel() {
86
208
  this.#active = false;
87
209
  this.#queued = false;
88
210
  this.#generation += 1;
89
211
  }
90
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
212
+ get pending() {
213
+ return this.#queued;
214
+ }
91
215
  schedule() {
92
216
  if (!this.#active || this.#queued) return;
93
217
  this.#queued = true;
@@ -100,31 +224,53 @@ var MicrotaskCoalescer = class {
100
224
  }
101
225
  };
102
226
 
103
- // src/utils/roving_tabindex.ts
227
+ var MorphRenderWatcher = class {
228
+ #listeners = new ListenerSet();
229
+ #pass;
230
+ #element = null;
231
+ #onMorph = () => this.#pass.schedule();
232
+ constructor(run) {
233
+ this.#pass = new MicrotaskCoalescer(run);
234
+ }
235
+ observe(element) {
236
+ if (this.#element === element) return;
237
+ this.disconnect();
238
+ this.#element = element;
239
+ this.#pass.activate();
240
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
241
+ }
242
+ schedule() {
243
+ this.#pass.schedule();
244
+ }
245
+ get pending() {
246
+ return this.#pass.pending;
247
+ }
248
+ disconnect() {
249
+ this.#listeners.dispose();
250
+ this.#pass.cancel();
251
+ this.#element = null;
252
+ }
253
+ };
254
+
255
+ var MoveCounter = class {
256
+ #sequence = 0;
257
+ record() {
258
+ this.#sequence += 1;
259
+ return this.#sequence;
260
+ }
261
+ isLatest(token) {
262
+ return token === this.#sequence;
263
+ }
264
+ };
265
+
104
266
  var RovingTabindex = class {
105
- /** Returns the current ordered item elements; called on every operation. */
106
267
  #getItems;
107
- /**
108
- * @param getItems - Returns the current ordered item elements. Called on every
109
- * operation so the live target list is always used.
110
- */
111
268
  constructor(getItems) {
112
269
  this.#getItems = getItems;
113
270
  }
114
- /** Index of the currently tabbable item (`tabindex="0"`), or `-1` if none. */
115
271
  get activeIndex() {
116
272
  return this.#getItems().findIndex((item) => item.tabIndex === 0);
117
273
  }
118
- /**
119
- * Makes exactly the item at `index` tabbable (`tabindex="0"`) and removes every
120
- * other item from the Tab sequence (`tabindex="-1"`). An out-of-range `index`
121
- * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express
122
- * "nothing is currently tabbable".
123
- *
124
- * @param index - Position of the item to make tabbable.
125
- * @param options - Pass `{ focus: true }` to also move DOM focus to that item,
126
- * and `items` to reuse an event-scoped collection snapshot.
127
- */
128
274
  setActive(index, options = {}) {
129
275
  const { focus = false } = options;
130
276
  const items = options.items ?? this.#getItems();
@@ -140,7 +286,10 @@ function rovingMove(current, length, delta, wrap) {
140
286
  return (next + length) % length;
141
287
  }
142
288
 
143
- // src/controllers/radio_group_controller.ts
289
+ function targetSelector(identifier, name) {
290
+ return `[data-${identifier}-target~="${name}"]`;
291
+ }
292
+
144
293
  var NO_SELECTION = { radio: null, value: "" };
145
294
  var OBSERVED_ATTRIBUTES = [
146
295
  "aria-checked",
@@ -155,32 +304,32 @@ var OBSERVED_ATTRIBUTES = [
155
304
  "type",
156
305
  "value"
157
306
  ];
158
- var hasModifier = (event) => event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
159
307
  var RadioGroupController = class extends Controller {
308
+ #moves = new MoveCounter();
309
+ #connectionGeneration = 0;
310
+ #moveToken = 0;
160
311
  static targets = ["radio", "field"];
161
312
  static actions = ["onKeydown", "select"];
162
313
  static events = ["change", "reconcile"];
163
314
  #roving = new RovingTabindex(() => this.#managedTargets);
164
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileDom());
315
+ #reconcile = new MorphRenderWatcher(() => this.#reconcileDom());
165
316
  #handledEvents = /* @__PURE__ */ new WeakSet();
166
317
  #managedRadios = /* @__PURE__ */ new Set();
167
318
  #originalTabindex = /* @__PURE__ */ new Map();
168
- #ownedChecked = /* @__PURE__ */ new Set();
169
- #internalCheckedValues = /* @__PURE__ */ new Map();
170
- #internalTabindexValues = /* @__PURE__ */ new Map();
171
- #observer = null;
172
- /** The selection last published: read on connect, then committed or reported. */
319
+ #ownedChecked = /* @__PURE__ */ new Map();
320
+ #writtenTabindex = /* @__PURE__ */ new WeakMap();
321
+ #observer = new AuthoredMutationWatcher(
322
+ (records) => this.#handleMutationRecords(records)
323
+ );
173
324
  #committed = NO_SELECTION;
174
325
  #connected = false;
175
326
  #lastOrder = [];
176
327
  #focusedRadio = null;
177
328
  #pendingFocusIndex = null;
178
329
  #preferChecked = false;
179
- /**
180
- * Normalizes authored selection, establishes the APG Tab entry point, and
181
- * reflects derived form state without reporting a user edit.
182
- */
330
+ #listeners = new ListenerSet();
183
331
  connect() {
332
+ this.#releaseDefaultsRewrittenWhileAway();
184
333
  const currentRadios = new Set(this.radioTargets);
185
334
  for (const radio of this.#managedRadios) {
186
335
  if (!currentRadios.has(radio)) this.#releaseRadio(radio);
@@ -188,49 +337,37 @@ var RadioGroupController = class extends Controller {
188
337
  for (const radio of this.radioTargets) this.#reconcileHost(radio, false);
189
338
  this.#normalizeSelection();
190
339
  this.#ensureTabStop(true);
191
- this.#reflectField(this.#selectedRadio, { silent: true });
340
+ this.#reflectField(this.#selectedRadio);
192
341
  this.#committed = this.#settled;
193
342
  this.#lastOrder = this.#managedTargets;
194
- this.element.addEventListener("click", this.#onClickCapture, true);
195
- this.element.addEventListener("keydown", this.#onKeydownCapture, true);
196
- this.element.addEventListener("click", this.#onClick);
197
- this.element.addEventListener("keydown", this.#onKeydown);
198
- this.element.addEventListener("focusin", this.#onFocusin);
199
- this.element.addEventListener("focusout", this.#onFocusout);
343
+ this.#listeners.add(this.element, "click", this.#onClickCapture, { capture: true });
344
+ this.#listeners.add(this.element, "keydown", this.#onKeydownCapture, { capture: true });
345
+ this.#listeners.add(this.element, "click", this.#onClick);
346
+ this.#listeners.add(this.element, "keydown", this.#onKeydown);
347
+ this.#listeners.add(this.element, "focusin", this.#onFocusin);
348
+ this.#listeners.add(this.element, "focusout", this.#onFocusout);
200
349
  this.#connected = true;
201
350
  this.#preferChecked = false;
202
- this.#internalCheckedValues.clear();
203
- this.#internalTabindexValues.clear();
204
- this.#reconcile.activate();
351
+ this.#reconcile.observe(this.element);
205
352
  this.#observeMutations();
206
353
  }
207
- /** Releases every listener, observer, and queued pass while retaining live DOM state. */
208
354
  disconnect() {
355
+ this.#connectionGeneration += 1;
209
356
  this.#connected = false;
210
- this.#reconcile.cancel();
211
- this.element.removeEventListener("click", this.#onClickCapture, true);
212
- this.element.removeEventListener("keydown", this.#onKeydownCapture, true);
213
- this.element.removeEventListener("click", this.#onClick);
214
- this.element.removeEventListener("keydown", this.#onKeydown);
215
- this.element.removeEventListener("focusin", this.#onFocusin);
216
- this.element.removeEventListener("focusout", this.#onFocusout);
217
- this.#observer?.disconnect();
218
- this.#observer = null;
219
- this.#internalCheckedValues.clear();
220
- this.#internalTabindexValues.clear();
357
+ this.#reconcile.disconnect();
358
+ this.#listeners.dispose();
359
+ this.#observer.disconnect();
221
360
  this.#focusedRadio = null;
222
361
  this.#pendingFocusIndex = null;
223
362
  this.#committed = NO_SELECTION;
224
363
  this.#preferChecked = false;
225
364
  }
226
- /** Removes a newly connected radio from the Tab sequence before batch reconciliation. */
227
365
  radioTargetConnected(radio) {
228
366
  if (this.#connected === false) return;
229
367
  this.#reconcileHost(radio, true);
230
368
  this.#preferChecked = true;
231
369
  this.#reconcile.schedule();
232
370
  }
233
- /** Releases target-only attributes and repairs selection, form state, focus, and roving. */
234
371
  radioTargetDisconnected(radio) {
235
372
  if (!this.#connected) return;
236
373
  if (this.#focusedRadio === radio) {
@@ -241,36 +378,33 @@ var RadioGroupController = class extends Controller {
241
378
  this.#preferChecked = true;
242
379
  this.#reconcile.schedule();
243
380
  }
244
- /** Reflects the current selection into a field added or replaced at runtime. */
245
381
  fieldTargetConnected() {
246
382
  this.#reconcile.schedule();
247
383
  }
248
- /** Reconciles after a field target is removed from a retained group. */
249
384
  fieldTargetDisconnected() {
250
385
  this.#reconcile.schedule();
251
386
  }
252
- /** Selects the action's radio. Per-radio action wiring is optional. */
253
- select(event) {
254
- if (event.defaultPrevented || !this.#ownsEventTarget(event.target)) return;
255
- const radio = event.currentTarget;
387
+ select(source) {
388
+ const { event, host, origin, reason } = actionSource(source);
389
+ const radio = host?.closest(targetSelector(this.identifier, "radio"));
256
390
  if (!radio || !this.radioTargets.includes(radio)) return;
257
- this.#handledEvents.add(event);
258
- this.#selectRadio(radio, { focus: false });
391
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
392
+ return;
393
+ if (event?.defaultPrevented || event && !this.#ownsEventTarget(origin)) return;
394
+ if (event) this.#handledEvents.add(event);
395
+ this.#selectRadio(radio, { focus: false }, reason);
259
396
  }
260
- /** Applies the APG key map to the action's radio. Per-radio action wiring is optional. */
261
397
  onKeydown(event) {
262
398
  const radio = event.currentTarget;
263
399
  if (!radio || !this.radioTargets.includes(radio)) return;
264
400
  this.#handleKeydown(event, radio);
265
401
  }
266
- /** Blocks disabled pointer activation before per-radio and consumer handlers run. */
267
402
  #onClickCapture = (event) => {
268
403
  const radio = this.#radioForEventTarget(event.target);
269
404
  if (!radio || !this.#isSupportedHost(radio) || !this.#isActivationDisabled(radio)) return;
270
405
  event.preventDefault();
271
406
  event.stopImmediatePropagation();
272
407
  };
273
- /** Blocks disabled activation and suppresses a button's non-APG Enter activation. */
274
408
  #onKeydownCapture = (event) => {
275
409
  if (event.key !== " " && event.key !== "Enter") return;
276
410
  const radio = this.#radioForEventTarget(event.target);
@@ -280,25 +414,21 @@ var RadioGroupController = class extends Controller {
280
414
  event.stopImmediatePropagation();
281
415
  }
282
416
  };
283
- /** Delegated click path for static and runtime-added radios without actions. */
284
417
  #onClick = (event) => {
285
418
  if (this.#handledEvents.delete(event) || event.defaultPrevented) return;
286
419
  const radio = this.#radioForEventTarget(event.target);
287
420
  if (radio) this.#selectRadio(radio, { focus: false });
288
421
  };
289
- /** Delegated keydown path for static and runtime-added radios without actions. */
290
422
  #onKeydown = (event) => {
291
423
  const radio = this.#radioForEventTarget(event.target);
292
424
  if (radio) this.#handleKeydown(event, radio);
293
425
  };
294
- /** Keeps programmatic and pointer focus as the group's current roving position. */
295
426
  #onFocusin = (event) => {
296
427
  const radio = this.#radioForEventTarget(event.target);
297
428
  if (!radio || !this.#isNavigable(radio)) return;
298
429
  this.#focusedRadio = radio;
299
430
  this.#setActive(radio);
300
431
  };
301
- /** Distinguishes ordinary focus departure from focus lost because its radio was removed. */
302
432
  #onFocusout = (event) => {
303
433
  const radio = this.#radioForEventTarget(event.target);
304
434
  if (!radio) return;
@@ -308,11 +438,10 @@ var RadioGroupController = class extends Controller {
308
438
  if (!(active instanceof Node) || !radio.contains(active)) this.#focusedRadio = null;
309
439
  });
310
440
  };
311
- /** Applies navigation and Space activation to one supported radio. */
312
441
  #handleKeydown(event, radio) {
313
442
  if (event.defaultPrevented || !this.#isSupportedHost(radio)) return;
314
443
  if (event.isComposing || isReservedArrowChord(event)) return;
315
- if ((event.key === "Home" || event.key === "End") && hasModifier(event)) return;
444
+ if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
316
445
  if (event.key === " ") {
317
446
  if (this.#isActivationDisabled(radio)) {
318
447
  event.preventDefault();
@@ -346,65 +475,54 @@ var RadioGroupController = class extends Controller {
346
475
  if (this.#isActivationDisabled(destination)) this.#setActive(destination, true);
347
476
  else this.#selectRadio(destination, { focus: true });
348
477
  }
349
- /**
350
- * Applies one user selection and emits only when selected identity changes. A
351
- * new selection is taken as published before either report goes out, so a
352
- * listener that selects again is measured from it. Activating the radio that is
353
- * already checked publishes nothing: if the page checked it, the next pass
354
- * reports that move.
355
- */
356
- #selectRadio(radio, { focus }) {
478
+ #selectRadio(radio, { focus }, reason = "user") {
357
479
  if (!this.#isSupportedHost(radio) || this.#isActivationDisabled(radio)) return;
358
- const previous = this.#selectedRadio;
359
- const changed = previous !== radio;
360
- if (changed) {
361
- for (const item of this.#managedTargets) this.#setChecked(item, item === radio);
362
- this.#committed = { radio, value: this.#radioValue(radio) };
363
- }
364
- this.#setActive(radio, focus);
365
- this.#reflectField(radio, { silent: !changed });
366
- if (changed) {
367
- this.dispatch("change", { detail: { value: this.#radioValue(radio), radio } });
480
+ const generation = this.#connectionGeneration;
481
+ const value = this.#radioValue(radio);
482
+ const changed = this.#committed.radio !== radio || this.#committed.value !== value;
483
+ for (const item of this.#managedTargets) this.#setChecked(item, item === radio);
484
+ this.#committed = { radio, value };
485
+ if (changed) this.#moveToken = this.#moves.record();
486
+ const token = this.#moveToken;
487
+ this.#setActive(radio);
488
+ const fieldChanged = this.#writeField(value);
489
+ this.#handleMutationRecords(this.#observer.take());
490
+ if (focus) radio.focus();
491
+ if (generation !== this.#connectionGeneration || !this.#connected || !this.#moves.isLatest(token))
492
+ return;
493
+ if (changed && fieldChanged) commitField(this.fieldTarget);
494
+ if (changed && generation === this.#connectionGeneration && this.#connected && this.#moves.isLatest(token)) {
495
+ this.dispatch("change", { detail: { value, radio, reason } });
368
496
  }
369
497
  }
370
- /** Reconciles target ownership, selection, roving, focus continuity, and form state. */
371
498
  #reconcileDom() {
372
- const observer = this.#observer;
373
- observer?.disconnect();
499
+ this.#handleMutationRecords(this.#observer.take());
374
500
  for (const radio of this.radioTargets) this.#reconcileHost(radio, true);
375
501
  this.#normalizeSelection();
376
502
  const pendingFocusIndex = this.#pendingFocusIndex;
377
503
  this.#pendingFocusIndex = null;
504
+ let destination = null;
378
505
  if (pendingFocusIndex !== null) {
379
- const destination = this.#nearestNavigable(pendingFocusIndex);
380
- this.#setActive(destination, destination !== null);
506
+ destination = this.#nearestNavigable(pendingFocusIndex);
507
+ this.#setActive(destination);
381
508
  } else {
382
509
  this.#ensureTabStop(this.#preferChecked);
383
510
  }
384
- this.#reflectField(this.#selectedRadio, { silent: true });
511
+ this.#reflectField(this.#selectedRadio);
385
512
  this.#lastOrder = this.#managedTargets;
386
513
  this.#preferChecked = false;
387
- this.#internalCheckedValues.clear();
388
- this.#internalTabindexValues.clear();
389
- if (this.#connected && observer) this.#observeWith(observer);
390
- this.#reportReconciledSelection();
391
- }
392
- /**
393
- * Announces a selection that moved since the one this group last published —
394
- * another radio, or the same radio submitting another value. Dispatched after
395
- * observation resumes so a consumer's own DOM edits are seen by the next pass,
396
- * and only while connected: moving focus inside the pass runs listeners that
397
- * can disconnect the group.
398
- */
399
- #reportReconciledSelection() {
400
- if (!this.#connected) return;
514
+ this.#handleMutationRecords(this.#observer.take());
401
515
  const settled = this.#settled;
402
516
  const committed = this.#committed;
403
- if (settled.radio === committed.radio && settled.value === committed.value) return;
517
+ const changed = settled.radio !== committed.radio || settled.value !== committed.value;
404
518
  this.#committed = settled;
405
- this.dispatch("reconcile", { detail: { value: settled.value, radio: settled.radio } });
519
+ const token = this.#moveToken;
520
+ destination?.focus();
521
+ if (!this.#connected || !this.#moves.isLatest(token)) return;
522
+ if (changed) {
523
+ this.dispatch("reconcile", { detail: { value: settled.value, radio: settled.radio } });
524
+ }
406
525
  }
407
- /** Begins or ends ownership according to a radio's current host semantics. */
408
526
  #reconcileHost(radio, dropFromTabSequence) {
409
527
  if (!this.#isSupportedHost(radio)) {
410
528
  this.#releaseRadio(radio);
@@ -417,44 +535,36 @@ var RadioGroupController = class extends Controller {
417
535
  }
418
536
  this.#normalizeChecked(radio);
419
537
  }
420
- /** Restores only defaults owned while an element belonged to this group. */
421
538
  #releaseRadio(radio) {
422
539
  this.#managedRadios.delete(radio);
423
540
  const original = this.#originalTabindex.get(radio);
424
541
  if (original === null) {
425
- this.#markInternal(this.#internalTabindexValues, radio, null);
426
- radio.removeAttribute("tabindex");
542
+ this.#writeAttribute(radio, "tabindex", null);
427
543
  } else if (original !== void 0) {
428
- this.#markInternal(this.#internalTabindexValues, radio, original);
429
- radio.setAttribute("tabindex", original);
544
+ this.#writeAttribute(radio, "tabindex", original);
430
545
  }
431
546
  this.#originalTabindex.delete(radio);
432
547
  if (this.#ownedChecked.delete(radio)) {
433
- this.#markInternal(this.#internalCheckedValues, radio, null);
434
- radio.removeAttribute("aria-checked");
548
+ this.#writeAttribute(radio, "aria-checked", null);
435
549
  }
436
550
  }
437
- /** Supplies a missing checked state and normalizes invalid ARIA tokens. */
438
551
  #normalizeChecked(radio) {
439
552
  const value = radio.getAttribute("aria-checked");
440
553
  if (value === null) {
441
- this.#ownedChecked.add(radio);
554
+ this.#ownedChecked.set(radio, "false");
442
555
  this.#setChecked(radio, false);
443
556
  } else if (value !== "true" && value !== "false") {
444
557
  this.#setChecked(radio, false);
445
558
  }
446
559
  }
447
- /** Makes the first DOM-ordered checked radio the sole checked radio. */
448
560
  #normalizeSelection() {
449
561
  let found = false;
450
562
  for (const radio of this.#managedTargets) {
451
- this.#normalizeChecked(radio);
452
563
  if (!this.#isChecked(radio)) continue;
453
564
  if (!found) found = true;
454
565
  else this.#setChecked(radio, false);
455
566
  }
456
567
  }
457
- /** Keeps one navigable Tab stop, preferring checked state when selection changed externally. */
458
568
  #ensureTabStop(preferChecked) {
459
569
  const navigable = this.#navigableRadios;
460
570
  let active;
@@ -469,12 +579,6 @@ var RadioGroupController = class extends Controller {
469
579
  active ??= navigable[0];
470
580
  this.#setActive(active ?? null);
471
581
  }
472
- /**
473
- * The surviving navigable radio closest to a position in the order captured
474
- * before the removal: the first one at or after it, else the last one before it.
475
- * The saved position and this search read the same population, so a `hidden` or
476
- * natively disabled sibling never shifts the destination past its neighbor.
477
- */
478
582
  #nearestNavigable(index) {
479
583
  const navigable = new Set(this.#navigableRadios);
480
584
  for (let i = index; i < this.#lastOrder.length; i++) {
@@ -487,7 +591,6 @@ var RadioGroupController = class extends Controller {
487
591
  }
488
592
  return this.#navigableRadios[0] ?? null;
489
593
  }
490
- /** Moves through full DOM order until a navigable radio is found. */
491
594
  #nextNavigable(fromIndex, delta) {
492
595
  const items = this.#managedTargets;
493
596
  let index = fromIndex;
@@ -498,36 +601,32 @@ var RadioGroupController = class extends Controller {
498
601
  }
499
602
  return void 0;
500
603
  }
501
- /** Assigns the single roving Tab stop and optionally moves DOM focus. */
502
604
  #setActive(radio, focus = false) {
503
605
  const items = this.#managedTargets;
504
606
  const index = items.indexOf(radio);
505
- this.#roving.setActive(index, { focus });
607
+ this.#roving.setActive(index);
506
608
  for (const item of items) {
507
- this.#markInternal(this.#internalTabindexValues, item, item.getAttribute("tabindex"));
609
+ this.#observer.claimCurrent(item, "tabindex");
610
+ this.#writtenTabindex.set(item, item.getAttribute("tabindex"));
508
611
  }
612
+ if (focus) radio?.focus();
509
613
  }
510
- /** Finds this group's radio containing an event target, excluding nested groups. */
511
614
  #radioForEventTarget(target) {
512
615
  if (!this.#ownsEventTarget(target)) return null;
513
616
  return ownerOf(this.radioTargets, target);
514
617
  }
515
- /** Whether the closest Radio Group scope around a target is this instance. */
516
618
  #ownsEventTarget(target) {
517
619
  return target instanceof Element && target.closest(`[data-controller~="${this.identifier}"]`) === this.element;
518
620
  }
519
- /** Hosts whose activation model can be owned without conflicting native behavior. */
520
621
  #isSupportedHost(radio) {
521
622
  if (radio instanceof HTMLButtonElement) return radio.type === "button";
522
623
  return !isInteractiveHost(radio);
523
624
  }
524
- /** Radios eligible for roving focus; ARIA-disabled deliberately remains eligible. */
525
625
  #isNavigable(radio) {
526
626
  if (!this.#isSupportedHost(radio) || this.#isHidden(radio)) return false;
527
627
  if (!(radio instanceof HTMLButtonElement)) return true;
528
628
  return !radio.disabled && !inheritsFieldsetDisabled(radio);
529
629
  }
530
- /** Whether hidden applies on the path from a radio up to the group root. */
531
630
  #isHidden(radio) {
532
631
  let current = radio;
533
632
  while (current && current !== this.element) {
@@ -536,7 +635,6 @@ var RadioGroupController = class extends Controller {
536
635
  }
537
636
  return false;
538
637
  }
539
- /** Whether ARIA, visibility, or native HTML semantics suppress selection. */
540
638
  #isActivationDisabled(radio) {
541
639
  if (!this.#isNavigable(radio)) return true;
542
640
  let current = radio;
@@ -546,119 +644,88 @@ var RadioGroupController = class extends Controller {
546
644
  }
547
645
  return false;
548
646
  }
549
- /** Distinguishes external retained-element changes from this controller's own writes. */
550
647
  #handleMutationRecords(records) {
551
648
  let external = false;
552
649
  for (const record of records) {
553
650
  const target = record.target;
554
651
  const attribute = record.attributeName;
555
652
  if (attribute === "aria-checked") {
556
- const value = target.getAttribute(attribute);
557
- if (this.#matchesInternal(this.#internalCheckedValues, target, value)) continue;
558
653
  this.#ownedChecked.delete(target);
559
654
  this.#preferChecked = true;
560
655
  external = true;
561
656
  } else if (attribute === "tabindex") {
562
657
  const value = target.getAttribute(attribute);
563
- if (this.#matchesInternal(this.#internalTabindexValues, target, value)) continue;
564
658
  if (this.#managedRadios.has(target)) this.#originalTabindex.set(target, value);
565
659
  external = true;
566
660
  } else {
567
661
  external = true;
568
662
  }
569
663
  }
570
- this.#internalCheckedValues.clear();
571
- this.#internalTabindexValues.clear();
572
664
  if (external) this.#reconcile.schedule();
573
665
  }
574
- /** Records an attribute write this pass made, keeping one claim per record it will produce. */
575
- #markInternal(values, target, value) {
576
- const pending = values.get(target);
577
- if (pending) {
578
- pending.value = value;
579
- pending.count += 1;
580
- } else {
581
- values.set(target, { value, count: 1 });
582
- }
583
- }
584
- /** Consumes one claim on a record whose value this pass wrote. */
585
- #matchesInternal(values, target, value) {
586
- const pending = values.get(target);
587
- if (!pending || pending.value !== value) return false;
588
- pending.count -= 1;
589
- if (pending.count === 0) values.delete(target);
590
- return true;
591
- }
592
- /** Watches target membership, retained state/hosts, form state, and fieldset ancestry. */
593
666
  #observeMutations() {
594
- const observer = new MutationObserver((records) => this.#handleMutationRecords(records));
595
- this.#observer = observer;
596
- this.#observeWith(observer);
597
- }
598
- /** Registers root and ancestor observation on one observer instance. */
599
- #observeWith(observer) {
600
- observer.observe(this.element, {
667
+ this.#observer.observe(this.element, {
601
668
  subtree: true,
602
669
  childList: true,
603
- attributes: true,
604
- attributeFilter: OBSERVED_ATTRIBUTES
670
+ attributes: OBSERVED_ATTRIBUTES,
671
+ ancestorAttributes: ["contenteditable", "disabled"]
605
672
  });
606
- let ancestor = this.element.parentElement;
607
- while (ancestor) {
608
- observer.observe(ancestor, {
609
- attributes: true,
610
- attributeFilter: ["contenteditable", "disabled"]
611
- });
612
- ancestor = ancestor.parentElement;
673
+ }
674
+ #releaseDefaultsRewrittenWhileAway() {
675
+ for (const [radio, written] of this.#ownedChecked) {
676
+ const current = radio.getAttribute("aria-checked");
677
+ if (current !== null && current !== written) this.#ownedChecked.delete(radio);
678
+ }
679
+ for (const radio of this.#managedRadios) {
680
+ const written = this.#writtenTabindex.get(radio);
681
+ const current = radio.getAttribute("tabindex");
682
+ if (current !== written) this.#originalTabindex.set(radio, current);
613
683
  }
614
684
  }
615
- /** Supported targets currently managed by the roving primitive. */
616
685
  get #managedTargets() {
617
686
  return this.radioTargets.filter((radio) => this.#managedRadios.has(radio));
618
687
  }
619
- /** Managed radios eligible for the roving Tab stop. */
620
688
  get #navigableRadios() {
621
689
  return this.#managedTargets.filter((radio) => this.#isNavigable(radio));
622
690
  }
623
- /** The currently checked managed radio, if any. */
624
691
  get #selectedRadio() {
625
692
  return this.#managedTargets.find((radio) => this.#isChecked(radio));
626
693
  }
627
- /** The current selection as the group publishes it. */
628
694
  get #settled() {
629
695
  const radio = this.#selectedRadio;
630
696
  return radio ? { radio, value: this.#radioValue(radio) } : NO_SELECTION;
631
697
  }
632
- /** Whether a radio is currently checked. */
633
698
  #isChecked(radio) {
634
699
  return radio.getAttribute("aria-checked") === "true";
635
700
  }
636
- /** Reflects checked state while distinguishing controller writes from authored morphs. */
637
701
  #setChecked(radio, checked) {
638
702
  const value = checked ? "true" : "false";
639
- if (radio.getAttribute("aria-checked") === value) return;
640
- this.#markInternal(this.#internalCheckedValues, radio, value);
641
- radio.setAttribute("aria-checked", value);
703
+ if (this.#ownedChecked.has(radio)) this.#ownedChecked.set(radio, value);
704
+ this.#writeAttribute(radio, "aria-checked", value);
642
705
  }
643
- /** Writes a roving value before the radio is visible to the shared primitive. */
644
706
  #writeTabindex(radio, value) {
645
707
  const serialized = String(value);
646
- this.#markInternal(this.#internalTabindexValues, radio, serialized);
647
- radio.tabIndex = value;
708
+ this.#writtenTabindex.set(radio, serialized);
709
+ this.#writeAttribute(radio, "tabindex", serialized);
648
710
  }
649
- /** Mirrors the selected value or the empty state to the optional hidden field. */
650
- #reflectField(radio, { silent = false } = {}) {
651
- if (!this.hasFieldTarget) return;
711
+ #writeAttribute(target, name, value) {
712
+ setAttributeIfChanged(target, name, value, () => this.#observer.claim(target, name, value));
713
+ }
714
+ #reflectField(radio) {
652
715
  const value = radio ? this.#radioValue(radio) : "";
653
- if (!writeField(this.fieldTarget, value)) return;
654
- if (!silent) commitField(this.fieldTarget);
716
+ this.#writeField(value);
655
717
  }
656
- /** A radio's submitted value (`data-value`, defaulting to empty). */
657
718
  #radioValue(radio) {
658
719
  return radio.getAttribute("data-value") ?? "";
659
720
  }
721
+ #writeField(value) {
722
+ if (!this.hasFieldTarget) return false;
723
+ const changed = writeField(this.fieldTarget, value);
724
+ if (changed && this.fieldTarget.type === "hidden")
725
+ this.#observer.claimCurrent(this.fieldTarget, "value");
726
+ return changed;
727
+ }
660
728
  };
661
729
 
662
730
  export { RadioGroupController };
663
- //# sourceMappingURL=radio_group_controller.js.map
664
731
  //# sourceMappingURL=radio_group_controller.js.map