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/data_grid_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 logicalArrowKey(key, element) {
12
30
  if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
13
31
  if (!isRtl(element)) return key;
@@ -18,7 +36,104 @@ function isReservedArrowChord(event, allow = []) {
18
36
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
19
37
  }
20
38
 
21
- // src/utils/field_mirror.ts
39
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
40
+ if (element.getAttribute(name) === value) return false;
41
+ beforeWrite?.();
42
+ if (value === null) element.removeAttribute(name);
43
+ else element.setAttribute(name, value);
44
+ return true;
45
+ }
46
+
47
+ var AuthoredMutationWatcher = class {
48
+ #onAuthored;
49
+ #claims = null;
50
+ #observer = null;
51
+ constructor(onAuthored) {
52
+ this.#onAuthored = onAuthored;
53
+ }
54
+ observe(root, options) {
55
+ this.disconnect();
56
+ const observer = new MutationObserver((records) => {
57
+ if (this.#observer !== observer) return;
58
+ const authored = this.#filter(records);
59
+ if (authored.length > 0) this.#onAuthored(authored);
60
+ });
61
+ this.#observer = observer;
62
+ this.#claims = { attributes: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
63
+ observer.observe(root, {
64
+ attributes: true,
65
+ attributeFilter: [...options.attributes],
66
+ subtree: options.subtree ?? false,
67
+ childList: options.childList ?? false
68
+ });
69
+ if (options.ancestorAttributes) {
70
+ let ancestor = root.parentElement;
71
+ while (ancestor) {
72
+ observer.observe(ancestor, {
73
+ attributes: true,
74
+ attributeFilter: [...options.ancestorAttributes]
75
+ });
76
+ ancestor = ancestor.parentElement;
77
+ }
78
+ }
79
+ }
80
+ claim(target, name, value) {
81
+ const claims = this.#claims?.attributes;
82
+ let attributes = claims?.get(target);
83
+ if (!attributes) {
84
+ attributes = /* @__PURE__ */ new Map();
85
+ claims?.set(target, attributes);
86
+ }
87
+ const pending = attributes.get(name);
88
+ attributes.set(name, { value, count: (pending?.count ?? 0) + 1 });
89
+ }
90
+ claimCurrent(target, name) {
91
+ this.claim(target, name, target.getAttribute(name));
92
+ }
93
+ claimChildList(target, added, removed) {
94
+ const claims = this.#claims?.children;
95
+ const pending = claims?.get(target) ?? { added: /* @__PURE__ */ new Map(), removed: /* @__PURE__ */ new Map() };
96
+ for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) + 1);
97
+ for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) + 1);
98
+ claims?.set(target, pending);
99
+ }
100
+ take() {
101
+ return this.#filter(this.#observer?.takeRecords() ?? []);
102
+ }
103
+ disconnect() {
104
+ this.#observer?.disconnect();
105
+ this.#observer = null;
106
+ this.#claims = null;
107
+ }
108
+ #filter(records) {
109
+ const authored = records.filter((record) => !this.#consume(record));
110
+ this.#claims?.attributes.clear();
111
+ this.#claims?.children.clear();
112
+ return authored;
113
+ }
114
+ #consume(record) {
115
+ if (record.type === "attributes") {
116
+ const target = record.target;
117
+ const pending2 = this.#claims?.attributes.get(target)?.get(record.attributeName);
118
+ if (!pending2 || pending2.count === 0 || target.getAttribute(record.attributeName) !== pending2.value) {
119
+ return false;
120
+ }
121
+ pending2.count -= 1;
122
+ return true;
123
+ }
124
+ const pending = this.#claims?.children.get(record.target);
125
+ if (!pending) return false;
126
+ const added = [...record.addedNodes];
127
+ const removed = [...record.removedNodes];
128
+ if (added.some((node) => !pending.added.get(node)) || removed.some((node) => !pending.removed.get(node))) {
129
+ return false;
130
+ }
131
+ for (const node of added) pending.added.set(node, (pending.added.get(node) ?? 0) - 1);
132
+ for (const node of removed) pending.removed.set(node, (pending.removed.get(node) ?? 0) - 1);
133
+ return true;
134
+ }
135
+ };
136
+
22
137
  function writeFields(container, values, { name, form = "" }) {
23
138
  const current = [...container.children];
24
139
  const submits = current.length === values.length && current.every(
@@ -41,7 +156,6 @@ function commitField(target) {
41
156
  target.dispatchEvent(new Event("change", { bubbles: true }));
42
157
  }
43
158
 
44
- // src/utils/interactive_host.ts
45
159
  var INTERACTIVE_HOST_SELECTOR = "button, input, select, textarea, label, a[href], area[href], summary, details, audio[controls], video[controls], iframe, object, embed";
46
160
  function isInteractiveHost(element) {
47
161
  if (element.matches(INTERACTIVE_HOST_SELECTOR)) return true;
@@ -58,27 +172,36 @@ function isInteractiveHost(element) {
58
172
  return false;
59
173
  }
60
174
 
61
- // src/utils/microtask_coalescer.ts
175
+ var ListenerSet = class {
176
+ #abort = new AbortController();
177
+ add(target, type, handler, options) {
178
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
179
+ }
180
+ dispose() {
181
+ this.#abort.abort();
182
+ this.#abort = new AbortController();
183
+ }
184
+ };
185
+
62
186
  var MicrotaskCoalescer = class {
63
187
  #run;
64
188
  #queued = false;
65
189
  #active = false;
66
190
  #generation = 0;
67
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
68
191
  constructor(run) {
69
192
  this.#run = run;
70
193
  }
71
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
72
194
  activate() {
73
195
  this.#active = true;
74
196
  }
75
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
76
197
  cancel() {
77
198
  this.#active = false;
78
199
  this.#queued = false;
79
200
  this.#generation += 1;
80
201
  }
81
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
202
+ get pending() {
203
+ return this.#queued;
204
+ }
82
205
  schedule() {
83
206
  if (!this.#active || this.#queued) return;
84
207
  this.#queued = true;
@@ -91,12 +214,51 @@ var MicrotaskCoalescer = class {
91
214
  }
92
215
  };
93
216
 
94
- // src/controllers/data_grid_controller.ts
217
+ var MorphRenderWatcher = class {
218
+ #listeners = new ListenerSet();
219
+ #pass;
220
+ #element = null;
221
+ #onMorph = () => this.#pass.schedule();
222
+ constructor(run) {
223
+ this.#pass = new MicrotaskCoalescer(run);
224
+ }
225
+ observe(element) {
226
+ if (this.#element === element) return;
227
+ this.disconnect();
228
+ this.#element = element;
229
+ this.#pass.activate();
230
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
231
+ }
232
+ schedule() {
233
+ this.#pass.schedule();
234
+ }
235
+ get pending() {
236
+ return this.#pass.pending;
237
+ }
238
+ disconnect() {
239
+ this.#listeners.dispose();
240
+ this.#pass.cancel();
241
+ this.#element = null;
242
+ }
243
+ };
244
+
245
+ var MoveCounter = class {
246
+ #sequence = 0;
247
+ record() {
248
+ this.#sequence += 1;
249
+ return this.#sequence;
250
+ }
251
+ isLatest(token) {
252
+ return token === this.#sequence;
253
+ }
254
+ };
255
+
256
+ function targetSelector(identifier, name) {
257
+ return `[data-${identifier}-target~="${name}"]`;
258
+ }
259
+
95
260
  var SORT_CYCLE = ["none", "ascending", "descending"];
96
261
  var OBSERVED_ATTRIBUTES = ["aria-selected", "data-value"];
97
- function setAttributeIfChanged(element, name, value) {
98
- if (element.getAttribute(name) !== value) element.setAttribute(name, value);
99
- }
100
262
  function sameSelection(a, b) {
101
263
  if (a.size !== b.size) return false;
102
264
  for (const [row, value] of a) {
@@ -110,6 +272,9 @@ function nextSortDirection(current) {
110
272
  return SORT_CYCLE[(from + 1) % SORT_CYCLE.length] ?? "ascending";
111
273
  }
112
274
  var DataGridController = class extends Controller {
275
+ #moves = new MoveCounter();
276
+ #connectionGeneration = 0;
277
+ #stopHolder = null;
113
278
  static targets = ["columnHeader", "row", "cell", "fields"];
114
279
  static values = {
115
280
  selection: { type: String, default: "none" },
@@ -118,164 +283,93 @@ var DataGridController = class extends Controller {
118
283
  };
119
284
  static actions = ["onKeydown", "sort", "toggleSelect"];
120
285
  static events = ["selectionchange", "sort", "reconcile"];
121
- /**
122
- * Collapses the target callbacks, Value changes and observed row writes of one
123
- * DOM mutation into a single pass, and refuses to run before `connect()` or
124
- * after `disconnect()`.
125
- *
126
- * Stimulus reports every target one at a time, so an ungated pass would re-walk
127
- * the whole grid once per authored cell on mount and once per streamed cell
128
- * afterwards — quadratic in the cell count both times.
129
- */
130
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileRows());
131
- /** Watches the row attributes a page can rewrite in place; set while connected. */
132
- #observer = null;
133
- /** The selection last settled: on connect, by the user, or by a reported pass. */
286
+ #reconcile = new MorphRenderWatcher(() => this.#reconcileRows());
287
+ #observer = new AuthoredMutationWatcher((records) => {
288
+ if (this.#concernsRows(records)) this.#reconcile.schedule();
289
+ });
134
290
  #settled = /* @__PURE__ */ new Map();
135
- /**
136
- * Establishes a single tab stop across all navigable cells/headers, brings the
137
- * rows to their baseline, and settles the selection without reporting it.
138
- *
139
- * The whole baseline runs here rather than in the Value callbacks: Stimulus
140
- * delivers those before `connect()`, where the pass is refused, and a
141
- * re-attached element reuses its cached context, whose value observer does not
142
- * fire again.
143
- */
144
291
  connect() {
145
292
  this.#restoreBaseline();
146
293
  this.#settled = this.#selection();
147
- this.#reconcile.activate();
294
+ this.#reconcile.observe(this.element);
148
295
  this.#observeRows();
149
296
  }
150
- /** Releases the row observer and drops a queued pass, so neither outlives the element. */
151
297
  disconnect() {
152
- this.#reconcile.cancel();
153
- this.#observer?.disconnect();
154
- this.#observer = null;
298
+ this.#connectionGeneration += 1;
299
+ this.#reconcile.disconnect();
300
+ this.#observer.disconnect();
155
301
  }
156
- /**
157
- * Rebuilds the DOM-owned baselines from the live grid: exactly one navigable
158
- * cell is in the Tab sequence, `aria-multiselectable` follows `selection`, every
159
- * selectable row carries an explicit `aria-selected`, and the fields mirror the
160
- * selected rows.
161
- *
162
- * The tab stop keeps whichever cell already holds it, so a rebuild triggered by
163
- * an unrelated row arriving does not throw the user's position away; only when
164
- * no cell holds it — the grid is fresh, or the holder was removed — does the
165
- * first navigable cell take over. Without that fallback a grid whose active row
166
- * is removed keeps every cell at `-1` and drops out of the Tab sequence
167
- * entirely.
168
- */
169
302
  #restoreBaseline() {
170
303
  const cells = this.#navigableCells();
171
- const active = cells.find((cell) => cell.tabIndex === 0) ?? cells[0];
304
+ const holder = this.#stopHolder;
305
+ const kept = holder !== null && holder.tabIndex === 0 && cells.includes(holder);
306
+ const active = (kept ? holder : cells.find((cell) => cell.tabIndex === 0)) ?? cells[0];
172
307
  if (active) this.#setActiveCell(active, { focus: false }, cells);
173
308
  this.#syncSelectable();
174
309
  const pageWrote = this.#takePageRecords();
175
310
  this.#normalizeSelection();
176
- this.#dropOwnRecords(pageWrote);
311
+ this.#flushMutations(pageWrote);
177
312
  this.#mirrorFields(false);
178
313
  }
179
- /**
180
- * The pass the page's changes run: the baselines are rebuilt, then a selection
181
- * that moved is reported. The report comes last, so rows a subscriber rewrites
182
- * are the next pass's to settle.
183
- */
184
314
  #reconcileRows() {
185
315
  this.#restoreBaseline();
186
316
  this.#reportMove();
187
317
  }
188
- /**
189
- * Reports the selection as `reconcile` when it differs from the one last
190
- * settled. The settled selection is replaced before dispatching, so a move a
191
- * subscriber makes is measured against what it was told.
192
- */
193
318
  #reportMove() {
194
319
  const selection = this.#selection();
195
320
  if (sameSelection(selection, this.#settled)) return;
196
321
  this.#settled = selection;
197
322
  this.dispatch("reconcile", { detail: { rows: [...selection.keys()] } });
198
323
  }
199
- /**
200
- * Re-renders `aria-multiselectable`, the rows and the fields when application
201
- * code (or a Turbo morph) changes `selection` at runtime; a selection the new
202
- * mode collapses is reported as `reconcile`.
203
- */
204
324
  selectionValueChanged() {
205
325
  this.#reconcile.schedule();
206
326
  }
207
- /** Rebuilds the submitted fields when the public name changes at runtime. */
208
327
  nameValueChanged() {
209
328
  this.#reconcile.schedule();
210
329
  }
211
- /** Repoints the submitted fields when the owning form changes at runtime. */
212
330
  formValueChanged() {
213
331
  this.#reconcile.schedule();
214
332
  }
215
- /** Seeds a fields container inserted after connect from the current selection. */
216
333
  fieldsTargetConnected() {
217
334
  this.#reconcile.schedule();
218
335
  }
219
- /** Re-establishes the baselines for a row added after connect. */
336
+ fieldsTargetDisconnected() {
337
+ this.#reconcile.schedule();
338
+ }
220
339
  rowTargetConnected() {
221
340
  this.#reconcile.schedule();
222
341
  }
223
- /** Settles the selection again when a row leaves the grid or drops its target token. */
224
342
  rowTargetDisconnected() {
225
343
  this.#reconcile.schedule();
226
344
  }
227
- /** Re-establishes the tab stop when a cell joins the grid after connect. */
228
345
  cellTargetConnected() {
229
346
  this.#reconcile.schedule();
230
347
  }
231
- /** Re-establishes the tab stop when a cell leaves the grid. */
232
348
  cellTargetDisconnected() {
233
349
  this.#reconcile.schedule();
234
350
  }
235
- /** Re-establishes the tab stop when a header joins the grid after connect. */
236
351
  columnHeaderTargetConnected() {
237
352
  this.#reconcile.schedule();
238
353
  }
239
- /** Re-establishes the tab stop when a header leaves the grid. */
240
354
  columnHeaderTargetDisconnected() {
241
355
  this.#reconcile.schedule();
242
356
  }
243
- /**
244
- * Brings the authored rows to the shape the APG requires, without changing
245
- * which rows the author chose.
246
- *
247
- * Every selectable row gets an explicit value — an absent `aria-selected` means
248
- * "not selectable" in ARIA, so a forgotten attribute hides a selectable row —
249
- * and a single-select grid keeps at most one `true`, first in DOM order.
250
- * A grid that declares `selection="none"` has no selectable rows, so the
251
- * attribute is removed rather than written: in ARIA its absence is what "not
252
- * selectable" looks like.
253
- *
254
- * @stimeoRenderRoot
255
- */
256
357
  #normalizeSelection() {
257
358
  const rows = this.rowTargets;
258
359
  if (this.selectionValue === "none") {
259
- for (const row of rows) row.removeAttribute("aria-selected");
360
+ for (const row of rows) this.#writeSelection(row, null);
260
361
  return;
261
362
  }
262
363
  const single = this.selectionValue === "single";
263
364
  const first = rows.find((row) => row.getAttribute("aria-selected") === "true");
264
365
  for (const row of rows) {
265
366
  if (single) {
266
- setAttributeIfChanged(row, "aria-selected", row === first ? "true" : "false");
367
+ this.#writeSelection(row, row === first ? "true" : "false");
267
368
  } else if (row.getAttribute("aria-selected") !== "true") {
268
- setAttributeIfChanged(row, "aria-selected", "false");
369
+ this.#writeSelection(row, "false");
269
370
  }
270
371
  }
271
372
  }
272
- /**
273
- * Mirrors `selection="multiple"` onto `aria-multiselectable` (APG Grid) so SRs
274
- * announce that more than one row can be selected; cleared for single/none so a
275
- * grid never carries a misleading attribute.
276
- *
277
- * @stimeoRenderRoot
278
- */
279
373
  #syncSelectable() {
280
374
  if (this.selectionValue === "multiple") {
281
375
  setAttributeIfChanged(this.element, "aria-multiselectable", "true");
@@ -283,34 +377,39 @@ var DataGridController = class extends Controller {
283
377
  this.element.removeAttribute("aria-multiselectable");
284
378
  }
285
379
  }
286
- /** Cycles the activated column header's sort and emits `sort`. */
287
- sort(event) {
288
- const header = event.currentTarget;
289
- if (!this.columnHeaderTargets.includes(header)) return;
290
- if (event.defaultPrevented) return;
291
- const control = this.#claimingControl(event, header);
380
+ sort(source) {
381
+ const { event, host, origin, reason } = actionSource(source);
382
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
383
+ return;
384
+ const header = host?.closest(targetSelector(this.identifier, "columnHeader"));
385
+ if (!header || !this.columnHeaderTargets.includes(header)) return;
386
+ if (event?.defaultPrevented) return;
387
+ const control = event ? this.#claimingControl(event, header) : null;
292
388
  if (control && !(control instanceof HTMLButtonElement)) return;
293
389
  const direction = nextSortDirection(header.getAttribute("aria-sort") ?? "none");
294
390
  for (const other of this.columnHeaderTargets) {
295
391
  other.setAttribute("aria-sort", other === header ? direction : "none");
296
392
  }
297
393
  this.#setActiveCell(header, { focus: false });
298
- this.dispatch("sort", { detail: { column: header, direction } });
394
+ this.dispatch("sort", { detail: { column: header, direction, reason } });
299
395
  }
300
- /** Toggles selection of the row owning the event target. Bound optionally. */
301
- toggleSelect(event) {
396
+ toggleSelect(source) {
397
+ const { event, host, origin, reason } = actionSource(source);
398
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
399
+ return;
302
400
  if (this.selectionValue === "none") return;
303
- if (event.defaultPrevented) return;
304
- const host = event.currentTarget;
305
- if (this.#claimedByDescendant(event, host)) return;
306
- const row = host.closest("[role='row']");
307
- if (row && this.rowTargets.includes(row)) this.#toggleRow(row);
401
+ if (event?.defaultPrevented) return;
402
+ if (event && host instanceof HTMLElement && this.#claimedByDescendant(event, host)) return;
403
+ const row = host?.closest(targetSelector(this.identifier, "row"));
404
+ if (row && this.rowTargets.includes(row)) this.#toggleRow(row, reason);
308
405
  }
309
- /** Grid navigation + sort/select activation. Bound to cells and headers. */
310
406
  onKeydown(event) {
311
407
  if (event.defaultPrevented) return;
312
408
  if (isReservedArrowChord(event)) return;
313
409
  if (event.isComposing) return;
410
+ if ((event.key === "Home" || event.key === "End") && (event.altKey || event.metaKey || event.shiftKey)) {
411
+ return;
412
+ }
314
413
  const cell = event.currentTarget;
315
414
  if (this.#claimedByDescendant(event, cell)) return;
316
415
  const matrix = this.#matrix();
@@ -351,38 +450,15 @@ var DataGridController = class extends Controller {
351
450
  this.#setActiveCell(target, { focus: true }, matrix.flat());
352
451
  }
353
452
  }
354
- /**
355
- * Whether the event was addressed to a control inside `host` rather than to the
356
- * grid.
357
- *
358
- * Cells and headers hold consumer markup, and APG's grid pattern expects that
359
- * markup to include working controls — a row action button, an inline editor.
360
- * Those own their own keystrokes and clicks, so the grid stands down entirely
361
- * rather than acting in parallel. An editable host (its `contenteditable` state
362
- * is inherited, so the walk is explicit) counts the same way.
363
- */
364
453
  #claimedByDescendant(event, host) {
365
454
  return this.#claimingControl(event, host) !== null;
366
455
  }
367
- /**
368
- * The nested control this event belongs to, or `null` when the host owns it.
369
- *
370
- * Naming the control, rather than answering yes or no, is what lets the click
371
- * path treat a sortable header's `<button>` as the activation it is while every
372
- * other control still takes the event away.
373
- */
374
456
  #claimingControl(event, host) {
375
457
  const source = event.target;
376
458
  const control = source.closest(INTERACTIVE_HOST_SELECTOR);
377
459
  if (control && host.contains(control)) return control;
378
460
  return isInteractiveHost(source) ? source : null;
379
461
  }
380
- /**
381
- * Performs a header's sort or a cell row's selection toggle on activation.
382
- *
383
- * @stimeoRuntimeOnly `selection` decides whether this one activation toggles the row; the sort it
384
- * cycles does not depend on it.
385
- */
386
462
  #activate(cell) {
387
463
  if (this.columnHeaderTargets.includes(cell)) {
388
464
  this.#cycleSort(cell);
@@ -392,90 +468,59 @@ var DataGridController = class extends Controller {
392
468
  const row = cell.closest("[role='row']");
393
469
  if (row && this.rowTargets.includes(row)) this.#toggleRow(row);
394
470
  }
395
- /** Cycles a header's sort on keyboard activation and emits `sort`. */
396
471
  #cycleSort(header) {
397
472
  const direction = nextSortDirection(header.getAttribute("aria-sort") ?? "none");
398
473
  for (const other of this.columnHeaderTargets) {
399
474
  other.setAttribute("aria-sort", other === header ? direction : "none");
400
475
  }
401
- this.dispatch("sort", { detail: { column: header, direction } });
402
- }
403
- /**
404
- * Toggles a row's `aria-selected`, honoring single vs. multiple selection.
405
- *
406
- * @stimeoRuntimeOnly `selection` decides how this one toggle treats the other rows.
407
- */
408
- #toggleRow(row) {
476
+ this.dispatch("sort", { detail: { column: header, direction, reason: "user" } });
477
+ }
478
+ #toggleRow(row, reason = "user") {
479
+ const generation = this.#connectionGeneration;
409
480
  const selected = row.getAttribute("aria-selected") === "true";
410
481
  const pageWrote = this.#takePageRecords();
411
482
  if (this.selectionValue === "single") {
412
483
  for (const other of this.rowTargets) {
413
- if (other !== row) setAttributeIfChanged(other, "aria-selected", "false");
484
+ if (other !== row) this.#writeSelection(other, "false");
414
485
  }
415
486
  }
416
- row.setAttribute("aria-selected", selected ? "false" : "true");
417
- this.#dropOwnRecords(pageWrote);
418
- this.#settled = this.#selection();
487
+ this.#writeSelection(row, selected ? "false" : "true");
488
+ this.#flushMutations(pageWrote);
489
+ const selection = this.#selection();
490
+ const changed = !sameSelection(selection, this.#settled);
491
+ this.#settled = selection;
492
+ if (!changed) {
493
+ this.#mirrorFields(false);
494
+ return;
495
+ }
496
+ const token = this.#moves.record();
419
497
  this.#mirrorFields(true);
420
- this.dispatch("selectionchange", { detail: { rows: [...this.#settled.keys()] } });
498
+ if (generation !== this.#connectionGeneration || !this.#moves.isLatest(token)) return;
499
+ this.dispatch("selectionchange", { detail: { rows: [...selection.keys()], reason } });
421
500
  }
422
- /** The selected rows in DOM order, each with the `data-value` it submits, if any. */
423
501
  #selection() {
424
502
  return new Map(
425
503
  this.rowTargets.filter((row) => row.getAttribute("aria-selected") === "true").map((row) => [row, row.dataset.value])
426
504
  );
427
505
  }
428
- /**
429
- * Watches the rows' `aria-selected` and `data-value`. Every write the grid makes
430
- * to them sits between a take and a drop of the observer's queue, so each record
431
- * the callback receives is the page's, and only a record on one of the grid's own
432
- * rows schedules a pass.
433
- */
434
506
  #observeRows() {
435
- const observer = new MutationObserver((records) => {
436
- if (this.#concernsRows(records)) this.#reconcile.schedule();
437
- });
438
- observer.observe(this.element, {
507
+ this.#observer.observe(this.element, {
439
508
  subtree: true,
440
- attributes: true,
441
- attributeFilter: OBSERVED_ATTRIBUTES
509
+ attributes: OBSERVED_ATTRIBUTES
442
510
  });
443
- this.#observer = observer;
444
511
  }
445
- /**
446
- * Empties the observer's queue before the grid writes to the rows, and says
447
- * whether the page had queued anything about them. Those records still owe a
448
- * pass: a toggle in `multiple` mode rewrites only its own row, so it does not fold
449
- * in what the page wrote to the others.
450
- */
451
512
  #takePageRecords() {
452
- const records = this.#observer?.takeRecords() ?? [];
513
+ const records = this.#observer.take();
453
514
  return records.length > 0 && this.#concernsRows(records);
454
515
  }
455
- /**
456
- * Whether any record is about one of this grid's own rows. A widget nested in a
457
- * cell writes the same attributes on its own elements, and those are no reason
458
- * to reconcile the grid.
459
- */
460
516
  #concernsRows(records) {
461
517
  const rows = new Set(this.rowTargets);
462
518
  return records.some((record) => rows.has(record.target));
463
519
  }
464
- /**
465
- * Drops the records the grid's own writes just queued, so the observer never
466
- * takes them for the page's, then schedules the pass the page's records owe.
467
- */
468
- #dropOwnRecords(pageWrote) {
469
- this.#observer?.takeRecords();
470
- if (pageWrote) this.#reconcile.schedule();
471
- }
472
- /**
473
- * Mirrors the selected rows' `data-value` into the optional fields container.
474
- * A selected row without one submits nothing, so a grid whose rows carry no
475
- * value submits an empty set rather than a row of blanks.
476
- *
477
- * @stimeoRenderRoot
478
- */
520
+ #flushMutations(pageWrote) {
521
+ const authored = this.#concernsRows(this.#observer.take());
522
+ if (pageWrote || authored) this.#reconcile.schedule();
523
+ }
479
524
  #mirrorFields(notify) {
480
525
  if (!this.hasFieldsTarget) return;
481
526
  const values = this.rowTargets.filter((row) => row.getAttribute("aria-selected") === "true").map((row) => row.dataset.value).filter((value) => value !== void 0);
@@ -484,30 +529,17 @@ var DataGridController = class extends Controller {
484
529
  commitField(this.fieldsTarget);
485
530
  }
486
531
  }
487
- /**
488
- * Makes `cell` the single tabbable cell (roving) and optionally focuses it.
489
- *
490
- * `cells` lets a caller that already walked the grid hand its collection over,
491
- * so one keystroke rebuilds the matrix once instead of twice. The write is
492
- * skipped where the attribute already holds the wanted value — comparing the
493
- * attribute rather than the IDL property, because a cell with no `tabindex` at
494
- * all reports `-1` and would then never receive the attribute it needs to be
495
- * focusable.
496
- */
497
532
  #setActiveCell(cell, { focus }, cells) {
533
+ this.#stopHolder = cell;
498
534
  for (const candidate of cells ?? this.#navigableCells()) {
499
535
  const wanted = candidate === cell ? "0" : "-1";
500
- if (candidate.getAttribute("tabindex") !== wanted) {
501
- candidate.setAttribute("tabindex", wanted);
502
- }
536
+ setAttributeIfChanged(candidate, "tabindex", wanted);
503
537
  }
504
538
  if (focus) cell.focus();
505
539
  }
506
- /** All navigable elements (headers + cells) in DOM order. */
507
540
  #navigableCells() {
508
541
  return this.#matrix().flat();
509
542
  }
510
- /** The grid as rows of navigable cells, derived from each `role="row"`. */
511
543
  #matrix() {
512
544
  const navigable = /* @__PURE__ */ new Set([...this.columnHeaderTargets, ...this.cellTargets]);
513
545
  const rows = Array.from(this.element.querySelectorAll("[role='row']"));
@@ -517,7 +549,6 @@ var DataGridController = class extends Controller {
517
549
  )
518
550
  ).filter((cells) => cells.length > 0);
519
551
  }
520
- /** Finds `[rowIndex, colIndex]` of `cell` within `matrix`, or null. */
521
552
  #locate(matrix, cell) {
522
553
  for (let row = 0; row < matrix.length; row++) {
523
554
  const col = (matrix[row] ?? []).indexOf(cell);
@@ -525,14 +556,17 @@ var DataGridController = class extends Controller {
525
556
  }
526
557
  return null;
527
558
  }
528
- /** The cell at `[row, col]`, clamping `col` to that row's last cell. */
529
559
  #cellInRow(matrix, row, col) {
530
560
  const cells = matrix[row];
531
561
  if (!cells || cells.length === 0) return void 0;
532
562
  return cells[Math.min(col, cells.length - 1)];
533
563
  }
564
+ #writeSelection(target, value) {
565
+ setAttributeIfChanged(target, "aria-selected", value, () => {
566
+ this.#observer.claim(target, "aria-selected", value);
567
+ });
568
+ }
534
569
  };
535
570
 
536
571
  export { DataGridController };
537
- //# sourceMappingURL=data_grid_controller.js.map
538
572
  //# sourceMappingURL=data_grid_controller.js.map