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,26 +1,17 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/checkbox_controller.ts
4
-
5
- // src/utils/form_reset_watcher.ts
6
3
  var FormResetWatcher = class {
7
4
  #owns;
8
5
  #onReset;
9
6
  #frames = /* @__PURE__ */ new Set();
10
7
  #generation = 0;
11
- /**
12
- * @param owns - whether `form` owns a control this controller derives from.
13
- * @param onReset - the reconciliation pass, run once per honoured reset.
14
- */
15
8
  constructor(owns, onReset) {
16
9
  this.#owns = owns;
17
10
  this.#onReset = onReset;
18
11
  }
19
- /** Subscribes to `reset` at the document; call from `connect()`. Idempotent. */
20
12
  observe() {
21
13
  document.addEventListener("reset", this.#handleReset, true);
22
14
  }
23
- /** Unsubscribes and drops every pending pass; call from `disconnect()`. */
24
15
  disconnect() {
25
16
  document.removeEventListener("reset", this.#handleReset, true);
26
17
  this.#generation += 1;
@@ -44,57 +35,41 @@ var FormResetWatcher = class {
44
35
  this.#frames.add(frame);
45
36
  });
46
37
  };
47
- /** Runs the pass unless a listener cancelled the reset before it landed. */
48
38
  #settle(event) {
49
39
  if (!event.defaultPrevented) this.#onReset();
50
40
  }
51
41
  };
52
42
 
53
- // src/utils/listener_set.ts
54
43
  var ListenerSet = class {
55
- /** The generation every `add` joins until the next `dispose()`. */
56
44
  #abort = new AbortController();
57
- /**
58
- * Attaches `handler` to the open generation, exactly as the caller spelled it.
59
- *
60
- * The set supplies the signal, so `options` carries everything else the DOM
61
- * accepts — `capture` included, which has to match at release time and no
62
- * longer has a second place to drift from.
63
- */
64
45
  add(target, type, handler, options) {
65
46
  target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
66
47
  }
67
- /**
68
- * Releases every listener of the open generation, synchronously, and opens the
69
- * next one. Idempotent, and safe before anything has been added.
70
- */
71
48
  dispose() {
72
49
  this.#abort.abort();
73
50
  this.#abort = new AbortController();
74
51
  }
75
52
  };
76
53
 
77
- // src/utils/microtask_coalescer.ts
78
54
  var MicrotaskCoalescer = class {
79
55
  #run;
80
56
  #queued = false;
81
57
  #active = false;
82
58
  #generation = 0;
83
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
84
59
  constructor(run) {
85
60
  this.#run = run;
86
61
  }
87
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
88
62
  activate() {
89
63
  this.#active = true;
90
64
  }
91
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
92
65
  cancel() {
93
66
  this.#active = false;
94
67
  this.#queued = false;
95
68
  this.#generation += 1;
96
69
  }
97
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
70
+ get pending() {
71
+ return this.#queued;
72
+ }
98
73
  schedule() {
99
74
  if (!this.#active || this.#queued) return;
100
75
  this.#queued = true;
@@ -107,82 +82,95 @@ var MicrotaskCoalescer = class {
107
82
  }
108
83
  };
109
84
 
110
- // src/controllers/checkbox_controller.ts
85
+ var MorphRenderWatcher = class {
86
+ #listeners = new ListenerSet();
87
+ #pass;
88
+ #element = null;
89
+ #onMorph = () => this.#pass.schedule();
90
+ constructor(run) {
91
+ this.#pass = new MicrotaskCoalescer(run);
92
+ }
93
+ observe(element) {
94
+ if (this.#element === element) return;
95
+ this.disconnect();
96
+ this.#element = element;
97
+ this.#pass.activate();
98
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
99
+ }
100
+ schedule() {
101
+ this.#pass.schedule();
102
+ }
103
+ get pending() {
104
+ return this.#pass.pending;
105
+ }
106
+ disconnect() {
107
+ this.#listeners.dispose();
108
+ this.#pass.cancel();
109
+ this.#element = null;
110
+ }
111
+ };
112
+
111
113
  var CheckboxController = class extends Controller {
112
114
  static targets = ["parent", "child"];
113
115
  static actions = ["onChildChange", "onParentChange"];
114
116
  static events = ["change", "reconcile"];
115
- /** Collapses every lifecycle signal from one DOM update into one derived pass. */
116
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileFromChildren());
117
- #listeners = new ListenerSet();
117
+ #reconcile = new MorphRenderWatcher(() => this.#reconcileFromChildren());
118
118
  #formReset = new FormResetWatcher(
119
119
  (form) => this.#hasCheckboxOwnedBy(form),
120
120
  () => this.#reconcile.schedule()
121
121
  );
122
- /** Aggregate this root last settled on, so a derived repair is reported once. */
123
122
  #committedState = null;
124
- /** Watches authored checked-attribute changes on retained target elements. */
125
123
  #checkedObserver = new MutationObserver((records) => {
126
124
  if (records.some((record) => this.#isManagedCheckbox(record.target))) {
127
125
  this.#reconcile.schedule();
128
126
  }
129
127
  });
130
- /** Reflects the initial aggregate and starts retained-DOM reconciliation. */
131
128
  connect() {
132
- this.#reconcile.activate();
133
129
  this.#syncFromChildren();
130
+ this.#reconcile.observe(this.element);
134
131
  this.#checkedObserver.observe(this.element, {
135
132
  attributes: true,
136
133
  attributeFilter: ["checked"],
137
134
  subtree: true
138
135
  });
139
- this.#listeners.add(this.element, "turbo:morph-element", this.#onMorph);
140
136
  this.#formReset.observe();
141
137
  }
142
- /** Releases the observer, global reset listener, and every pending reconciliation. */
143
138
  disconnect() {
144
- this.#reconcile.cancel();
139
+ this.#reconcile.disconnect();
145
140
  this.#checkedObserver.disconnect();
146
- this.#listeners.dispose();
147
141
  this.#formReset.disconnect();
148
142
  }
149
- /** Reconciles the aggregate for a parent added or replaced at runtime. */
150
143
  parentTargetConnected() {
151
144
  this.#reconcile.schedule();
152
145
  }
153
- /** Reconciles the aggregate after a parent target leaves the group. */
154
146
  parentTargetDisconnected() {
155
147
  this.#reconcile.schedule();
156
148
  }
157
- /** Reconciles the aggregate for a child added at runtime. */
158
149
  childTargetConnected() {
159
150
  this.#reconcile.schedule();
160
151
  }
161
- /** Reconciles the aggregate after a child leaves the group. */
162
152
  childTargetDisconnected() {
163
153
  this.#reconcile.schedule();
164
154
  }
165
- /** Cascades the parent's state to every child. Bound via `data-action` (change). */
166
155
  onParentChange() {
167
156
  if (!this.hasParentTarget) return;
157
+ const previous = this.#committedState;
168
158
  const checked = this.parentTarget.checked;
169
159
  for (const child of this.childTargets) {
170
160
  child.checked = checked;
171
161
  }
172
162
  this.#reflect(checked ? "all" : "none", true);
163
+ if (this.#committedState === previous) return;
173
164
  const detail = this.#settledDetail();
174
165
  if (detail) this.dispatch("change", { detail });
175
166
  }
176
- /** Recomputes the parent from its children. Bound via `data-action` (change). */
177
167
  onChildChange() {
168
+ const previous = this.#committedState;
178
169
  this.#syncFromChildren();
170
+ if (this.#committedState === previous) return;
179
171
  const detail = this.#settledDetail();
180
172
  if (detail) this.dispatch("change", { detail });
181
173
  }
182
- /**
183
- * Announces an aggregate this pass derived rather than the user. `change` stays
184
- * reserved for the two public actions, so automation never reads a repair as an edit.
185
- */
186
174
  #reconcileFromChildren() {
187
175
  const previous = this.#committedState;
188
176
  this.#syncFromChildren();
@@ -190,20 +178,9 @@ var CheckboxController = class extends Controller {
190
178
  const detail = this.#settledDetail();
191
179
  if (detail) this.dispatch("reconcile", { detail });
192
180
  }
193
- /**
194
- * Derives the parent's `checked`/`indeterminate` and the root `data-state` from
195
- * the children. Writing state and reporting it are separate so the caller — not
196
- * a flag threaded through the write — decides which event describes the cause.
197
- */
198
181
  #syncFromChildren() {
199
182
  this.#reflect(this.#aggregate(), this.childTargets.length > 0);
200
183
  }
201
- /**
202
- * Reflects one aggregate state.
203
- *
204
- * @param writeParent - whether the state is authoritative over the `parent`
205
- * target's own `checked` / `indeterminate`.
206
- */
207
184
  #reflect(state, writeParent) {
208
185
  if (writeParent && this.hasParentTarget) {
209
186
  this.parentTarget.checked = state === "all";
@@ -212,29 +189,17 @@ var CheckboxController = class extends Controller {
212
189
  this.element.setAttribute("data-state", state);
213
190
  this.#committedState = state;
214
191
  }
215
- /** The settled aggregate as event detail, or `null` before anything has settled. */
216
192
  #settledDetail() {
217
193
  const state = this.#committedState;
218
194
  if (state === null) return null;
219
195
  return { checked: state === "all", indeterminate: state === "partial", state };
220
196
  }
221
- /** Reconciles retained targets after Turbo has finished morphing their live state. */
222
- #onMorph = () => {
223
- this.#reconcile.schedule();
224
- };
225
- /** Whether a form owns at least one current parent or child target. */
226
197
  #hasCheckboxOwnedBy(form) {
227
198
  return [...this.parentTargets, ...this.childTargets].some((checkbox) => checkbox.form === form);
228
199
  }
229
- /** Whether an observed attribute mutation belongs to this controller's target set. */
230
200
  #isManagedCheckbox(node) {
231
201
  return this.parentTargets.some((checkbox) => checkbox === node) || this.childTargets.some((checkbox) => checkbox === node);
232
202
  }
233
- /**
234
- * Computes the aggregate state. With children it counts them; with none it
235
- * reads the parent so a lone tri-state checkbox still reports a state without
236
- * its externally-set `indeterminate` being clobbered.
237
- */
238
203
  #aggregate() {
239
204
  const children = this.childTargets;
240
205
  if (children.length === 0) {
@@ -249,5 +214,4 @@ var CheckboxController = class extends Controller {
249
214
  };
250
215
 
251
216
  export { CheckboxController };
252
- //# sourceMappingURL=checkbox_controller.js.map
253
217
  //# sourceMappingURL=checkbox_controller.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/form_reset_watcher.ts","../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/checkbox_controller.ts"],"names":[],"mappings":"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","file":"checkbox_controller.js","sourcesContent":["/**\n * Runs a controller's reconciliation pass after a native form reset has\n * restored the form's own controls.\n *\n * **A microtask alone is not \"after the reset\", and the reason is where the\n * dispatch came from.** `form.reset()` fires the event and restores the\n * controls inside one script frame, so a microtask queued from a listener lands\n * after both. A reset the user triggers — activating a `reset` button — is\n * dispatched by the browser with an empty script stack, so the microtask\n * checkpoint runs the moment the listener returns: still inside the dispatch,\n * before the controls are restored and before a later listener has had its\n * chance to cancel. Reconciling there reads the pre-reset values and treats a\n * cancelled reset as a real one.\n *\n * The event itself tells the two origins apart. `eventPhase` returns to\n * `Event.NONE` at the end of dispatch, so a microtask that sees `NONE` is\n * already past the default action and settles on the spot — that is the\n * scripted path, and its timing is exactly a microtask. A microtask\n * that still sees a dispatch phase is the browser-driven path and settles on\n * the next animation frame instead: the default action completes before that\n * frame, and `defaultPrevented` has reached its final value. A frame is only\n * ever reached from a real user interaction, which requires a rendered\n * document, so the frame cannot starve.\n *\n * Pending frames are held per handle rather than collapsed into one slot: a set\n * needs no branch to stay accurate, and every handle in it is cancellable at\n * teardown.\n *\n * Scope is the timing and the ownership test only. *What* to reconcile — an\n * aggregate, a visibility pass, a re-read of every field — stays in the\n * controller, because no two consumers answer it the same way. Ownership stays\n * there too: a control associated through `form=` is reached by the consumer's\n * own `control.form` read.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #formReset = new FormResetWatcher(\n * (form) => this.fieldTargets.some((field) => field.form === form),\n * () => this.#reconcile.schedule(),\n * );\n *\n * connect() { this.#formReset.observe(); }\n * disconnect() { this.#formReset.disconnect(); }\n * ```\n */\nexport class FormResetWatcher {\n readonly #owns: (form: HTMLFormElement) => boolean;\n readonly #onReset: () => void;\n readonly #frames = new Set<number>();\n #generation = 0;\n\n /**\n * @param owns - whether `form` owns a control this controller derives from.\n * @param onReset - the reconciliation pass, run once per honoured reset.\n */\n constructor(owns: (form: HTMLFormElement) => boolean, onReset: () => void) {\n this.#owns = owns;\n this.#onReset = onReset;\n }\n\n /** Subscribes to `reset` at the document; call from `connect()`. Idempotent. */\n observe(): void {\n document.addEventListener(\"reset\", this.#handleReset, true);\n }\n\n /** Unsubscribes and drops every pending pass; call from `disconnect()`. */\n disconnect(): void {\n document.removeEventListener(\"reset\", this.#handleReset, true);\n this.#generation += 1;\n for (const frame of this.#frames) cancelAnimationFrame(frame);\n this.#frames.clear();\n }\n\n readonly #handleReset = (event: Event): void => {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !this.#owns(form)) return;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A pass queued before teardown must not reach a disconnected controller.\n if (generation !== this.#generation) return;\n if (event.eventPhase === Event.NONE) {\n this.#settle(event);\n return;\n }\n const frame = requestAnimationFrame(() => {\n this.#frames.delete(frame);\n this.#settle(event);\n });\n this.#frames.add(frame);\n });\n };\n\n /** Runs the pass unless a listener cancelled the reset before it landed. */\n #settle(event: Event): void {\n if (!event.defaultPrevented) this.#onReset();\n }\n}\n","/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt. {@link\n * ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, so `dispose()` belongs exactly where\n * the `removeEventListener` block stood. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, or one that\n * lives for less than the connection, keeps its own pair of calls.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included, which has to match at release time and no\n * longer has a second place to drift from.\n */\n add(\n target: EventTarget,\n type: string,\n handler: EventListener,\n options?: Omit<AddEventListenerOptions, \"signal\">,\n ): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * This file's own doc block is dropped from `dist`, but every member comment is\n * inlined into each consumer entry (`tsup` builds with `splitting: false`), so\n * rationale belongs here and only the contract belongs on the members.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { FormResetWatcher } from \"../utils/form_reset_watcher\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport { MicrotaskCoalescer } from \"../utils/microtask_coalescer\";\n\n/** Aggregate selection state of a parent/child checkbox group. */\ntype CheckboxState = \"all\" | \"partial\" | \"none\";\n\n/**\n * Headless, accessible checkbox behavior (including the tri-state / parent-child\n * \"select all\" pattern).\n *\n * Markup contract (identifier: `stimeo--checkbox`):\n * <fieldset data-controller=\"stimeo--checkbox\" role=\"group\" aria-labelledby=\"all-label\">\n * <label id=\"all-label\">\n * <input type=\"checkbox\" data-stimeo--checkbox-target=\"parent\"\n * data-action=\"change->stimeo--checkbox#onParentChange\" /> Select all\n * </label>\n * <label><input type=\"checkbox\" data-stimeo--checkbox-target=\"child\"\n * data-action=\"change->stimeo--checkbox#onChildChange\" /> Item A</label>\n * <!-- more children -->\n * </fieldset>\n *\n * Implements the WAI-ARIA APG **Checkbox** pattern. Native `<input type=\"checkbox\">`\n * is used throughout (so Space toggle, focus order, and `mixed` exposure are the\n * browser's); the controller adds what HTML cannot express: the parent's\n * `indeterminate` property (announced as `mixed`) derived from its children, and\n * the cascade from a parent toggle to its children.\n *\n * `change` and `reconcile` dispatch\n * `{ checked: boolean, indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }`.\n *\n * @remarks\n * Behavior only — the check mark and any \"mixed\" affordance are the consumer's\n * CSS (keyed off `:checked` / `:indeterminate` / `data-state`). The `parent`\n * target is optional: a child-only group still exposes its aggregate on the root,\n * while a lone tri-state checkbox can use just `parent` and drive\n * `indeterminate` externally — with no `child` target the parent's own `checked`\n * and `indeterminate` stay the consumer's, because only children are\n * authoritative over them. At most one `parent` may be present.\n *\n * Behavior provided:\n * - Parent toggle checks/unchecks every child and clears its own `indeterminate`.\n * All child targets, including disabled ones, participate; omit the target to\n * exclude an input. The cascade does not synthesize native child `change`\n * events — one aggregate custom event describes the action.\n * - A child change recomputes the parent: all → checked, none → unchecked,\n * some → `indeterminate`.\n * - The aggregate (`all` / `partial` / `none`) is mirrored to `data-state` on the\n * root. Dynamic targets, checked-attribute changes, Turbo morphs, and native\n * form resets are reconciled from the children. Callers that assign the live\n * `checked` property directly must dispatch `change`, because property writes\n * are not observable by a `MutationObserver`.\n * - `stimeo--checkbox:change` is dispatched for the two public change actions;\n * `stimeo--checkbox:reconcile` is dispatched instead when a reconciliation —\n * not the user — moves the aggregate. Both carry `{ checked: boolean,\n * indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }` and describe\n * the aggregate even without a parent. Neither fires on connect.\n */\nexport class CheckboxController extends Controller<HTMLElement> {\n static override targets = [\"parent\", \"child\"];\n static actions = [\"onChildChange\", \"onParentChange\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly parentTarget: HTMLInputElement;\n declare readonly parentTargets: HTMLInputElement[];\n declare readonly hasParentTarget: boolean;\n declare readonly childTargets: HTMLInputElement[];\n\n /** Collapses every lifecycle signal from one DOM update into one derived pass. */\n readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileFromChildren());\n readonly #listeners = new ListenerSet();\n readonly #formReset = new FormResetWatcher(\n (form) => this.#hasCheckboxOwnedBy(form),\n () => this.#reconcile.schedule(),\n );\n /** Aggregate this root last settled on, so a derived repair is reported once. */\n #committedState: CheckboxState | null = null;\n /** Watches authored checked-attribute changes on retained target elements. */\n readonly #checkedObserver = new MutationObserver((records) => {\n if (records.some((record) => this.#isManagedCheckbox(record.target))) {\n this.#reconcile.schedule();\n }\n });\n\n /** Reflects the initial aggregate and starts retained-DOM reconciliation. */\n override connect(): void {\n this.#reconcile.activate();\n this.#syncFromChildren();\n this.#checkedObserver.observe(this.element, {\n attributes: true,\n attributeFilter: [\"checked\"],\n subtree: true,\n });\n this.#listeners.add(this.element, \"turbo:morph-element\", this.#onMorph);\n this.#formReset.observe();\n }\n\n /** Releases the observer, global reset listener, and every pending reconciliation. */\n override disconnect(): void {\n this.#reconcile.cancel();\n this.#checkedObserver.disconnect();\n this.#listeners.dispose();\n this.#formReset.disconnect();\n }\n\n /** Reconciles the aggregate for a parent added or replaced at runtime. */\n parentTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a parent target leaves the group. */\n parentTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate for a child added at runtime. */\n childTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a child leaves the group. */\n childTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Cascades the parent's state to every child. Bound via `data-action` (change). */\n onParentChange(): void {\n if (!this.hasParentTarget) return;\n const checked = this.parentTarget.checked;\n for (const child of this.childTargets) {\n child.checked = checked;\n }\n this.#reflect(checked ? \"all\" : \"none\", true);\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /** Recomputes the parent from its children. Bound via `data-action` (change). */\n onChildChange(): void {\n this.#syncFromChildren();\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /**\n * Announces an aggregate this pass derived rather than the user. `change` stays\n * reserved for the two public actions, so automation never reads a repair as an edit.\n */\n #reconcileFromChildren(): void {\n const previous = this.#committedState;\n this.#syncFromChildren();\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"reconcile\", { detail });\n }\n\n /**\n * Derives the parent's `checked`/`indeterminate` and the root `data-state` from\n * the children. Writing state and reporting it are separate so the caller — not\n * a flag threaded through the write — decides which event describes the cause.\n */\n #syncFromChildren(): void {\n // A childless root reads its aggregate off the parent itself, so writing the\n // derived value back would clear a `checked` the consumer drives alongside\n // `indeterminate`. Only the children are authoritative over the parent.\n this.#reflect(this.#aggregate(), this.childTargets.length > 0);\n }\n\n /**\n * Reflects one aggregate state.\n *\n * @param writeParent - whether the state is authoritative over the `parent`\n * target's own `checked` / `indeterminate`.\n */\n #reflect(state: CheckboxState, writeParent: boolean): void {\n if (writeParent && this.hasParentTarget) {\n this.parentTarget.checked = state === \"all\";\n this.parentTarget.indeterminate = state === \"partial\";\n }\n this.element.setAttribute(\"data-state\", state);\n this.#committedState = state;\n }\n\n /** The settled aggregate as event detail, or `null` before anything has settled. */\n #settledDetail(): { checked: boolean; indeterminate: boolean; state: CheckboxState } | null {\n const state = this.#committedState;\n if (state === null) return null;\n return { checked: state === \"all\", indeterminate: state === \"partial\", state };\n }\n\n /** Reconciles retained targets after Turbo has finished morphing their live state. */\n readonly #onMorph = (): void => {\n this.#reconcile.schedule();\n };\n\n /** Whether a form owns at least one current parent or child target. */\n #hasCheckboxOwnedBy(form: HTMLFormElement): boolean {\n return [...this.parentTargets, ...this.childTargets].some((checkbox) => checkbox.form === form);\n }\n\n /** Whether an observed attribute mutation belongs to this controller's target set. */\n #isManagedCheckbox(node: Node): boolean {\n return (\n this.parentTargets.some((checkbox) => checkbox === node) ||\n this.childTargets.some((checkbox) => checkbox === node)\n );\n }\n\n /**\n * Computes the aggregate state. With children it counts them; with none it\n * reads the parent so a lone tri-state checkbox still reports a state without\n * its externally-set `indeterminate` being clobbered.\n */\n #aggregate(): CheckboxState {\n const children = this.childTargets;\n if (children.length === 0) {\n if (this.hasParentTarget && this.parentTarget.indeterminate) return \"partial\";\n return this.hasParentTarget && this.parentTarget.checked ? \"all\" : \"none\";\n }\n const checked = children.filter((child) => child.checked).length;\n if (checked === 0) return \"none\";\n if (checked === children.length) return \"all\";\n return \"partial\";\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/form_reset_watcher.ts","../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/controllers/checkbox_controller.ts"],"names":[],"mappings":"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","file":"checkbox_controller.js","sourcesContent":["/**\n * Runs a controller's reconciliation pass after a native form reset has\n * restored the form's own controls.\n *\n * **A microtask alone is not \"after the reset\", and the reason is where the\n * dispatch came from.** `form.reset()` fires the event and restores the\n * controls inside one script frame, so a microtask queued from a listener lands\n * after both. A reset the user triggers — activating a `reset` button — is\n * dispatched by the browser with an empty script stack, so the microtask\n * checkpoint runs the moment the listener returns: still inside the dispatch,\n * before the controls are restored and before a later listener has had its\n * chance to cancel. Reconciling there reads the pre-reset values and treats a\n * cancelled reset as a real one.\n *\n * The event itself tells the two origins apart. `eventPhase` returns to\n * `Event.NONE` at the end of dispatch, so a microtask that sees `NONE` is\n * already past the default action and settles on the spot — that is the\n * scripted path, and its timing is exactly a microtask. A microtask\n * that still sees a dispatch phase is the browser-driven path and settles on\n * the next animation frame instead: the default action completes before that\n * frame, and `defaultPrevented` has reached its final value. A frame is only\n * ever reached from a real user interaction, which requires a rendered\n * document, so the frame cannot starve.\n *\n * Pending frames are held per handle rather than collapsed into one slot: a set\n * needs no branch to stay accurate, and every handle in it is cancellable at\n * teardown.\n *\n * Scope is the timing and the ownership test only. *What* to reconcile — an\n * aggregate, a visibility pass, a re-read of every field — stays in the\n * controller, because no two consumers answer it the same way. Ownership stays\n * there too: a control associated through `form=` is reached by the consumer's\n * own `control.form` read.\n *\n * @example\n * ```ts\n * readonly #formReset = new FormResetWatcher(\n * (form) => this.fieldTargets.some((field) => field.form === form),\n * () => this.#reconcile.schedule(),\n * );\n *\n * connect() { this.#formReset.observe(); }\n * disconnect() { this.#formReset.disconnect(); }\n * ```\n */\nexport class FormResetWatcher {\n readonly #owns: (form: HTMLFormElement) => boolean;\n readonly #onReset: () => void;\n readonly #frames = new Set<number>();\n #generation = 0;\n\n /**\n * @param owns - whether `form` owns a control this controller derives from.\n * @param onReset - the reconciliation pass, run once per honoured reset.\n */\n constructor(owns: (form: HTMLFormElement) => boolean, onReset: () => void) {\n this.#owns = owns;\n this.#onReset = onReset;\n }\n\n /** Subscribes to `reset` at the document; call from `connect()`. Idempotent. */\n observe(): void {\n document.addEventListener(\"reset\", this.#handleReset, true);\n }\n\n /** Unsubscribes and drops every pending pass; call from `disconnect()`. */\n disconnect(): void {\n document.removeEventListener(\"reset\", this.#handleReset, true);\n this.#generation += 1;\n for (const frame of this.#frames) cancelAnimationFrame(frame);\n this.#frames.clear();\n }\n\n readonly #handleReset = (event: Event): void => {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !this.#owns(form)) return;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A pass queued before teardown must not reach a disconnected controller.\n if (generation !== this.#generation) return;\n if (event.eventPhase === Event.NONE) {\n this.#settle(event);\n return;\n }\n const frame = requestAnimationFrame(() => {\n this.#frames.delete(frame);\n this.#settle(event);\n });\n this.#frames.add(frame);\n });\n };\n\n /** Runs the pass unless a listener cancelled the reset before it landed. */\n #settle(event: Event): void {\n if (!event.defaultPrevented) this.#onReset();\n }\n}\n","/** What {@link ListenerSet.add} passes on besides the signal it supplies itself. */\nexport type ListenerOptions = Omit<AddEventListenerOptions, \"signal\">;\n\n/**\n * The event map a target's own `addEventListener` types its handlers with: the\n * window's, a document's, an HTML or SVG element's, any other element's, or a\n * media query list's. Other targets name none.\n */\ntype EventMapOf<T extends EventTarget> = T extends Window\n ? WindowEventMap\n : T extends Document\n ? DocumentEventMap\n : T extends HTMLElement\n ? HTMLElementEventMap\n : T extends SVGElement\n ? SVGElementEventMap\n : T extends Element\n ? ElementEventMap\n : T extends MediaQueryList\n ? MediaQueryListEventMap\n : Record<never, never>;\n\n/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt.\n * {@link ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * A lifetime shorter than the connection — while a Value holds, until a first\n * load, while a subscription is open — gets a set of its own, which the end of\n * that lifetime disposes. A controller therefore holds one set per lifetime: the\n * connection's, and one for each swapped receiver or shorter span.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, listeners of\n * several elements that come and go one at a time, and those one gesture holds\n * keep their own pair of calls or a signal of their own.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included. A removal by hand would have to repeat that\n * flag; aborting the signal releases the listener without it.\n *\n * A `type` the target's own event map names types `handler` with that event,\n * as the target's `addEventListener` does, so a handler written for a\n * `KeyboardEvent` is passed as it is; any other `type` takes an\n * `EventListener`.\n */\n add<T extends EventTarget, K extends keyof EventMapOf<T> & string>(\n target: T,\n type: K,\n handler: (event: EventMapOf<T>[K]) => void,\n options?: ListenerOptions,\n ): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Whether a requested pass has yet to run. */\n get pending(): boolean {\n return this.#queued;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Whether a pass requested by a morph or {@link schedule} has yet to run. */\n get pending(): boolean {\n return this.#pass.pending;\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { FormResetWatcher } from \"../utils/form_reset_watcher\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\n\n/** Aggregate selection state of a parent/child checkbox group. */\ntype CheckboxState = \"all\" | \"partial\" | \"none\";\n\n/**\n * Headless, accessible checkbox behavior (including the tri-state / parent-child\n * \"select all\" pattern).\n *\n * Markup contract (identifier: `stimeo--checkbox`):\n * <fieldset data-controller=\"stimeo--checkbox\" role=\"group\" aria-labelledby=\"all-label\">\n * <label id=\"all-label\">\n * <input type=\"checkbox\" data-stimeo--checkbox-target=\"parent\"\n * data-action=\"change->stimeo--checkbox#onParentChange\" /> Select all\n * </label>\n * <label><input type=\"checkbox\" data-stimeo--checkbox-target=\"child\"\n * data-action=\"change->stimeo--checkbox#onChildChange\" /> Item A</label>\n * <!-- more children -->\n * </fieldset>\n *\n * Implements the WAI-ARIA APG **Checkbox** pattern. Native `<input type=\"checkbox\">`\n * is used throughout (so Space toggle, focus order, and `mixed` exposure are the\n * browser's); the controller adds what HTML cannot express: the parent's\n * `indeterminate` property (announced as `mixed`) derived from its children, and\n * the cascade from a parent toggle to its children.\n *\n * `change` and `reconcile` dispatch\n * `{ checked: boolean, indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }`.\n *\n * @remarks\n * Behavior only — the check mark and any \"mixed\" affordance are the consumer's\n * CSS (keyed off `:checked` / `:indeterminate` / `data-state`). The `parent`\n * target is optional: a child-only group still exposes its aggregate on the root,\n * while a lone tri-state checkbox can use just `parent` and drive\n * `indeterminate` externally — with no `child` target the parent's own `checked`\n * and `indeterminate` stay the consumer's, because only children are\n * authoritative over them. At most one `parent` may be present.\n *\n * Behavior provided:\n * - Parent toggle checks/unchecks every child and clears its own `indeterminate`.\n * All child targets, including disabled ones, participate; omit the target to\n * exclude an input. The cascade does not synthesize native child `change`\n * events — one aggregate custom event reports a changed aggregate.\n * - A child change recomputes the parent: all → checked, none → unchecked,\n * some → `indeterminate`.\n * - The aggregate (`all` / `partial` / `none`) is mirrored to `data-state` on the\n * root. Dynamic targets, checked-attribute changes, Turbo morphs, and native\n * form resets are reconciled from the children. Callers that assign the live\n * `checked` property directly must dispatch `change`, because property writes\n * are not observable by a `MutationObserver`.\n * - `stimeo--checkbox:change` is dispatched by the two public change actions only\n * when the aggregate differs from the last published one. Pending page writes\n * are included in that confirmation; returning to the published aggregate is\n * silent. Browser-dispatched listeners can reconcile a page write before the\n * action runs, while script dispatch and consecutive Stimulus actions in one\n * listener include it in the action's `change`.\n * `stimeo--checkbox:reconcile` is dispatched instead when a reconciliation —\n * not the user — moves the aggregate. Both carry `{ checked: boolean,\n * indeterminate: boolean, state: \"all\" | \"partial\" | \"none\" }` and describe\n * the aggregate even without a parent. Neither fires on connect.\n */\nexport class CheckboxController extends Controller<HTMLElement> {\n static override targets = [\"parent\", \"child\"];\n static actions = [\"onChildChange\", \"onParentChange\"] as const;\n static events = [\"change\", \"reconcile\"] as const;\n\n declare readonly parentTarget: HTMLInputElement;\n declare readonly parentTargets: HTMLInputElement[];\n declare readonly hasParentTarget: boolean;\n declare readonly childTargets: HTMLInputElement[];\n\n /** Collapses every lifecycle signal from one DOM update into one derived pass. */\n readonly #reconcile = new MorphRenderWatcher(() => this.#reconcileFromChildren());\n readonly #formReset = new FormResetWatcher(\n (form) => this.#hasCheckboxOwnedBy(form),\n () => this.#reconcile.schedule(),\n );\n /** Aggregate this root last settled on, so a derived repair is reported once. */\n #committedState: CheckboxState | null = null;\n /** Watches authored checked-attribute changes on retained target elements. */\n readonly #checkedObserver = new MutationObserver((records) => {\n if (records.some((record) => this.#isManagedCheckbox(record.target))) {\n this.#reconcile.schedule();\n }\n });\n\n /** Reflects the initial aggregate and starts retained-DOM reconciliation. */\n override connect(): void {\n this.#syncFromChildren();\n this.#reconcile.observe(this.element);\n this.#checkedObserver.observe(this.element, {\n attributes: true,\n attributeFilter: [\"checked\"],\n subtree: true,\n });\n this.#formReset.observe();\n }\n\n /** Releases the observer, global reset listener, and every pending reconciliation. */\n override disconnect(): void {\n this.#reconcile.disconnect();\n this.#checkedObserver.disconnect();\n this.#formReset.disconnect();\n }\n\n /** Reconciles the aggregate for a parent added or replaced at runtime. */\n parentTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a parent target leaves the group. */\n parentTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate for a child added at runtime. */\n childTargetConnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Reconciles the aggregate after a child leaves the group. */\n childTargetDisconnected(): void {\n this.#reconcile.schedule();\n }\n\n /** Cascades the parent's state to every child. Bound via `data-action` (change). */\n onParentChange(): void {\n if (!this.hasParentTarget) return;\n const previous = this.#committedState;\n const checked = this.parentTarget.checked;\n for (const child of this.childTargets) {\n child.checked = checked;\n }\n this.#reflect(checked ? \"all\" : \"none\", true);\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /** Recomputes the parent from its children. Bound via `data-action` (change). */\n onChildChange(): void {\n const previous = this.#committedState;\n this.#syncFromChildren();\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"change\", { detail });\n }\n\n /**\n * Announces an aggregate this pass derived rather than the user. `change` stays\n * reserved for the two public actions, so automation never reads a repair as an edit.\n */\n #reconcileFromChildren(): void {\n const previous = this.#committedState;\n this.#syncFromChildren();\n if (this.#committedState === previous) return;\n const detail = this.#settledDetail();\n if (detail) this.dispatch(\"reconcile\", { detail });\n }\n\n /**\n * Derives the parent's `checked`/`indeterminate` and the root `data-state` from\n * the children. Writing state and reporting it are separate so the caller — not\n * a flag threaded through the write — decides which event describes the cause.\n */\n #syncFromChildren(): void {\n // A childless root reads its aggregate off the parent itself, so writing the\n // derived value back would clear a `checked` the consumer drives alongside\n // `indeterminate`. Only the children are authoritative over the parent.\n this.#reflect(this.#aggregate(), this.childTargets.length > 0);\n }\n\n /**\n * Reflects one aggregate state.\n *\n * @param writeParent - whether the state is authoritative over the `parent`\n * target's own `checked` / `indeterminate`.\n */\n #reflect(state: CheckboxState, writeParent: boolean): void {\n if (writeParent && this.hasParentTarget) {\n this.parentTarget.checked = state === \"all\";\n this.parentTarget.indeterminate = state === \"partial\";\n }\n this.element.setAttribute(\"data-state\", state);\n this.#committedState = state;\n }\n\n /** The settled aggregate as event detail, or `null` before anything has settled. */\n #settledDetail(): { checked: boolean; indeterminate: boolean; state: CheckboxState } | null {\n const state = this.#committedState;\n if (state === null) return null;\n return { checked: state === \"all\", indeterminate: state === \"partial\", state };\n }\n\n /** Whether a form owns at least one current parent or child target. */\n #hasCheckboxOwnedBy(form: HTMLFormElement): boolean {\n return [...this.parentTargets, ...this.childTargets].some((checkbox) => checkbox.form === form);\n }\n\n /** Whether an observed attribute mutation belongs to this controller's target set. */\n #isManagedCheckbox(node: Node): boolean {\n return (\n this.parentTargets.some((checkbox) => checkbox === node) ||\n this.childTargets.some((checkbox) => checkbox === node)\n );\n }\n\n /**\n * Computes the aggregate state. With children it counts them; with none it\n * reads the parent so a lone tri-state checkbox still reports a state without\n * its externally-set `indeterminate` being clobbered.\n */\n #aggregate(): CheckboxState {\n const children = this.childTargets;\n if (children.length === 0) {\n if (this.hasParentTarget && this.parentTarget.indeterminate) return \"partial\";\n return this.hasParentTarget && this.parentTarget.checked ? \"all\" : \"none\";\n }\n const checked = children.filter((child) => child.checked).length;\n if (checked === 0) return \"none\";\n if (checked === children.length) return \"all\";\n return \"partial\";\n }\n}\n"]}
@@ -28,7 +28,7 @@ import { Controller } from '@hotwired/stimulus';
28
28
  *
29
29
  * Values: `text` (copy this instead of reading `source`), `feedbackDuration` (ms the
30
30
  * completion state is held; `0` or less arms no timer, so it stands until the next
31
- * copy — a reconnect and the before-cache rewind still clear it), `copiedLabel` /
31
+ * copy — a reconnect still clears it), `copiedLabel` /
32
32
  * `errorLabel` (what the visible slot shows), `announceCopiedText` /
33
33
  * `announceErrorText` (what assistive tech hears; empty announces nothing).
34
34
  *
@@ -43,10 +43,13 @@ import { Controller } from '@hotwired/stimulus';
43
43
  * well would say everything twice.
44
44
  *
45
45
  * `copied` and `error` are transient: the timer that clears them belongs to one
46
- * connection, so a fresh `connect()` that finds either — a restored snapshot, an
47
- * in-page move — returns to `idle`, and the state is rewound before Turbo caches
48
- * the page (`BeforeCacheReset`). The rewind is silent: it discards nothing a
49
- * reconnect does not derive again.
46
+ * connection, so a fresh `connect()` that finds either — a page Turbo restores from its
47
+ * cache, an in-page move — returns to `idle`, silently. `turbo:before-cache` changes
48
+ * nothing: Turbo also dispatches it on pages that stay (a promoted frame navigation, a
49
+ * `popstate` without Turbo state, a refresh of a cached URL), where the result is still
50
+ * on screen and its return to idle still due. Returning to `idle` empties only a slot that
51
+ * still shows the wording a copy wrote there; a slot the page swapped in, rewrote, or put
52
+ * an element into keeps what it holds.
50
53
  *
51
54
  * A `copiedLabel` / `errorLabel` changed on the live element while its result is
52
55
  * shown rewrites the slot — only while the slot still reads the label the copy wrote
@@ -84,11 +87,19 @@ declare class ClipboardController extends Controller<HTMLElement> {
84
87
  default: string;
85
88
  };
86
89
  };
90
+ static valueConstraints: {
91
+ feedbackDuration: {
92
+ readonly finite: true;
93
+ readonly min: 0;
94
+ readonly max: 2147483647;
95
+ };
96
+ };
87
97
  static actions: readonly ["copy"];
88
98
  static events: readonly ["copy"];
89
99
  readonly sourceTarget: HTMLElement;
90
100
  readonly buttonTarget: HTMLElement;
91
101
  readonly feedbackTarget: HTMLElement;
102
+ readonly feedbackTargets: HTMLElement[];
92
103
  readonly hasSourceTarget: boolean;
93
104
  readonly hasButtonTarget: boolean;
94
105
  readonly hasFeedbackTarget: boolean;