stimeo-ui 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +128 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +392 -417
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +34 -5
  7. package/dist/controllers/accordion_controller.js +228 -28
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +20 -9
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +38 -7
  13. package/dist/controllers/announcer_controller.js +135 -191
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +295 -110
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +172 -106
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  23. package/dist/controllers/breadcrumb_controller.js +0 -129
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +22 -4
  26. package/dist/controllers/bulk_select_controller.js +264 -53
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +109 -28
  29. package/dist/controllers/calendar_controller.js +346 -180
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +99 -27
  32. package/dist/controllers/carousel_controller.js +587 -300
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +18 -2
  35. package/dist/controllers/character_counter_controller.js +271 -143
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +77 -75
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +44 -7
  41. package/dist/controllers/clipboard_controller.js +187 -108
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +41 -5
  44. package/dist/controllers/collapsible_controller.js +374 -108
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +47 -14
  47. package/dist/controllers/color_picker_controller.js +183 -127
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +33 -6
  50. package/dist/controllers/combobox_controller.js +426 -109
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +45 -14
  53. package/dist/controllers/command_palette_controller.js +994 -350
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +212 -125
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +939 -245
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +36 -3
  62. package/dist/controllers/context_menu_controller.js +275 -127
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +75 -2
  68. package/dist/controllers/countdown_controller.js +235 -97
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -15
  71. package/dist/controllers/currency_input_controller.js +241 -118
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +78 -22
  74. package/dist/controllers/data_grid_controller.js +229 -135
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +64 -11
  77. package/dist/controllers/date_range_picker_controller.js +359 -165
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +22 -3
  80. package/dist/controllers/dialog_controller.js +887 -225
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  83. package/dist/controllers/direct_upload_controller.js +80 -142
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  86. package/dist/controllers/dirty_form_controller.js +26 -47
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +175 -20
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +52 -16
  92. package/dist/controllers/drawer_controller.js +963 -336
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +39 -3
  95. package/dist/controllers/dropdown_controller.js +342 -85
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +182 -58
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +67 -53
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  103. package/dist/controllers/file_dropzone_controller.js +426 -229
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.d.ts +10 -4
  106. package/dist/controllers/filter_controller.js +78 -36
  107. package/dist/controllers/filter_controller.js.map +1 -1
  108. package/dist/controllers/flash_controller.d.ts +57 -14
  109. package/dist/controllers/flash_controller.js +328 -211
  110. package/dist/controllers/flash_controller.js.map +1 -1
  111. package/dist/controllers/focus_controller.d.ts +12 -3
  112. package/dist/controllers/focus_controller.js +654 -239
  113. package/dist/controllers/focus_controller.js.map +1 -1
  114. package/dist/controllers/form_field_controller.js +153 -136
  115. package/dist/controllers/form_field_controller.js.map +1 -1
  116. package/dist/controllers/form_validation_controller.js +29 -96
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  119. package/dist/controllers/frame_loading_controller.js +263 -198
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +22 -2
  122. package/dist/controllers/highlight_controller.js +119 -70
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +65 -4
  125. package/dist/controllers/hover_card_controller.js +335 -118
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +58 -4
  128. package/dist/controllers/idle_controller.js +316 -65
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +5 -2
  131. package/dist/controllers/input_mask_controller.js +116 -61
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +147 -103
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  137. package/dist/controllers/lazy_frame_controller.js +9 -56
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +84 -10
  140. package/dist/controllers/listbox_controller.js +413 -153
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +1 -1
  143. package/dist/controllers/local_time_controller.js +54 -64
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +70 -95
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +40 -9
  149. package/dist/controllers/menu_controller.js +254 -167
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +15 -4
  152. package/dist/controllers/menubar_controller.js +206 -331
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +20 -0
  155. package/dist/controllers/meter_controller.js +159 -71
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +50 -7
  158. package/dist/controllers/multi_select_controller.js +797 -388
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +19 -1
  161. package/dist/controllers/navigation_menu_controller.js +156 -231
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +373 -174
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +241 -45
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +62 -10
  170. package/dist/controllers/number_input_controller.js +387 -227
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.js +28 -75
  173. package/dist/controllers/optimistic_controller.js.map +1 -1
  174. package/dist/controllers/otp_controller.d.ts +38 -7
  175. package/dist/controllers/otp_controller.js +344 -196
  176. package/dist/controllers/otp_controller.js.map +1 -1
  177. package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
  178. package/dist/controllers/overflow_indicator_controller.js +167 -93
  179. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  180. package/dist/controllers/overflow_menu_controller.d.ts +66 -9
  181. package/dist/controllers/overflow_menu_controller.js +408 -277
  182. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  183. package/dist/controllers/pagination_controller.d.ts +52 -15
  184. package/dist/controllers/pagination_controller.js +342 -120
  185. package/dist/controllers/pagination_controller.js.map +1 -1
  186. package/dist/controllers/password_reveal_controller.d.ts +60 -9
  187. package/dist/controllers/password_reveal_controller.js +424 -99
  188. package/dist/controllers/password_reveal_controller.js.map +1 -1
  189. package/dist/controllers/password_strength_controller.d.ts +14 -5
  190. package/dist/controllers/password_strength_controller.js +99 -153
  191. package/dist/controllers/password_strength_controller.js.map +1 -1
  192. package/dist/controllers/persist_controller.d.ts +11 -4
  193. package/dist/controllers/persist_controller.js +252 -96
  194. package/dist/controllers/persist_controller.js.map +1 -1
  195. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  196. package/dist/controllers/pointer_drag_controller.js +263 -164
  197. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  198. package/dist/controllers/popover_controller.d.ts +46 -4
  199. package/dist/controllers/popover_controller.js +267 -94
  200. package/dist/controllers/popover_controller.js.map +1 -1
  201. package/dist/controllers/portal_controller.d.ts +44 -4
  202. package/dist/controllers/portal_controller.js +209 -72
  203. package/dist/controllers/portal_controller.js.map +1 -1
  204. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  205. package/dist/controllers/preview_guard_controller.js +243 -112
  206. package/dist/controllers/preview_guard_controller.js.map +1 -1
  207. package/dist/controllers/progress_controller.d.ts +11 -0
  208. package/dist/controllers/progress_controller.js +132 -59
  209. package/dist/controllers/progress_controller.js.map +1 -1
  210. package/dist/controllers/radio_group_controller.d.ts +19 -8
  211. package/dist/controllers/radio_group_controller.js +138 -133
  212. package/dist/controllers/radio_group_controller.js.map +1 -1
  213. package/dist/controllers/range_slider_controller.d.ts +92 -4
  214. package/dist/controllers/range_slider_controller.js +245 -112
  215. package/dist/controllers/range_slider_controller.js.map +1 -1
  216. package/dist/controllers/rating_controller.d.ts +33 -6
  217. package/dist/controllers/rating_controller.js +429 -194
  218. package/dist/controllers/rating_controller.js.map +1 -1
  219. package/dist/controllers/read_more_controller.d.ts +26 -2
  220. package/dist/controllers/read_more_controller.js +185 -47
  221. package/dist/controllers/read_more_controller.js.map +1 -1
  222. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  223. package/dist/controllers/reading_progress_controller.js +147 -135
  224. package/dist/controllers/reading_progress_controller.js.map +1 -1
  225. package/dist/controllers/relative_time_controller.d.ts +18 -4
  226. package/dist/controllers/relative_time_controller.js +145 -110
  227. package/dist/controllers/relative_time_controller.js.map +1 -1
  228. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  229. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  230. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  231. package/dist/controllers/resizable_controller.d.ts +44 -4
  232. package/dist/controllers/resizable_controller.js +294 -118
  233. package/dist/controllers/resizable_controller.js.map +1 -1
  234. package/dist/controllers/roving_controller.js +39 -84
  235. package/dist/controllers/roving_controller.js.map +1 -1
  236. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  237. package/dist/controllers/scroll_area_controller.js +428 -221
  238. package/dist/controllers/scroll_area_controller.js.map +1 -1
  239. package/dist/controllers/scroll_restore_controller.js +0 -53
  240. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  241. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  242. package/dist/controllers/scroll_visibility_controller.js +395 -146
  243. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  244. package/dist/controllers/scrollspy_controller.d.ts +9 -3
  245. package/dist/controllers/scrollspy_controller.js +181 -236
  246. package/dist/controllers/scrollspy_controller.js.map +1 -1
  247. package/dist/controllers/separator_controller.d.ts +59 -17
  248. package/dist/controllers/separator_controller.js +288 -132
  249. package/dist/controllers/separator_controller.js.map +1 -1
  250. package/dist/controllers/sidebar_controller.d.ts +81 -7
  251. package/dist/controllers/sidebar_controller.js +1071 -375
  252. package/dist/controllers/sidebar_controller.js.map +1 -1
  253. package/dist/controllers/skeleton_controller.d.ts +23 -0
  254. package/dist/controllers/skeleton_controller.js +248 -92
  255. package/dist/controllers/skeleton_controller.js.map +1 -1
  256. package/dist/controllers/slider_controller.d.ts +72 -8
  257. package/dist/controllers/slider_controller.js +211 -96
  258. package/dist/controllers/slider_controller.js.map +1 -1
  259. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  260. package/dist/controllers/smart_sticky_header_controller.js +163 -54
  261. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  262. package/dist/controllers/sortable_controller.js +13 -110
  263. package/dist/controllers/sortable_controller.js.map +1 -1
  264. package/dist/controllers/spinner_controller.d.ts +39 -4
  265. package/dist/controllers/spinner_controller.js +277 -161
  266. package/dist/controllers/spinner_controller.js.map +1 -1
  267. package/dist/controllers/step_indicator_controller.d.ts +24 -17
  268. package/dist/controllers/step_indicator_controller.js +107 -53
  269. package/dist/controllers/step_indicator_controller.js.map +1 -1
  270. package/dist/controllers/stepper_controller.d.ts +43 -9
  271. package/dist/controllers/stepper_controller.js +171 -48
  272. package/dist/controllers/stepper_controller.js.map +1 -1
  273. package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
  274. package/dist/controllers/stick_to_bottom_controller.js +310 -116
  275. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  276. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  277. package/dist/controllers/sticky_observer_controller.js +214 -49
  278. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  279. package/dist/controllers/submit_once_controller.d.ts +22 -6
  280. package/dist/controllers/submit_once_controller.js +294 -192
  281. package/dist/controllers/submit_once_controller.js.map +1 -1
  282. package/dist/controllers/switch_controller.d.ts +40 -7
  283. package/dist/controllers/switch_controller.js +107 -43
  284. package/dist/controllers/switch_controller.js.map +1 -1
  285. package/dist/controllers/tabs_controller.d.ts +13 -1
  286. package/dist/controllers/tabs_controller.js +50 -25
  287. package/dist/controllers/tabs_controller.js.map +1 -1
  288. package/dist/controllers/tags_input_controller.d.ts +27 -3
  289. package/dist/controllers/tags_input_controller.js +290 -149
  290. package/dist/controllers/tags_input_controller.js.map +1 -1
  291. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  292. package/dist/controllers/textarea_autosize_controller.js +71 -58
  293. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  294. package/dist/controllers/theme_controller.d.ts +37 -8
  295. package/dist/controllers/theme_controller.js +148 -80
  296. package/dist/controllers/theme_controller.js.map +1 -1
  297. package/dist/controllers/time_picker_controller.d.ts +18 -1
  298. package/dist/controllers/time_picker_controller.js +162 -81
  299. package/dist/controllers/time_picker_controller.js.map +1 -1
  300. package/dist/controllers/toast_controller.d.ts +85 -23
  301. package/dist/controllers/toast_controller.js +365 -193
  302. package/dist/controllers/toast_controller.js.map +1 -1
  303. package/dist/controllers/toggle_group_controller.d.ts +60 -9
  304. package/dist/controllers/toggle_group_controller.js +194 -99
  305. package/dist/controllers/toggle_group_controller.js.map +1 -1
  306. package/dist/controllers/toolbar_controller.js +92 -90
  307. package/dist/controllers/toolbar_controller.js.map +1 -1
  308. package/dist/controllers/tooltip_controller.d.ts +52 -2
  309. package/dist/controllers/tooltip_controller.js +185 -125
  310. package/dist/controllers/tooltip_controller.js.map +1 -1
  311. package/dist/controllers/transition_controller.d.ts +22 -4
  312. package/dist/controllers/transition_controller.js +123 -115
  313. package/dist/controllers/transition_controller.js.map +1 -1
  314. package/dist/controllers/tree_view_controller.d.ts +59 -18
  315. package/dist/controllers/tree_view_controller.js +248 -290
  316. package/dist/controllers/tree_view_controller.js.map +1 -1
  317. package/dist/index.d.ts +33 -3
  318. package/dist/index.js +8649 -8572
  319. package/dist/index.js.map +1 -1
  320. package/dist/inspector/cli.d.ts +95 -9
  321. package/dist/inspector/cli.js +343 -77
  322. package/dist/inspector/cli.js.map +1 -1
  323. package/dist/inspector/cli_bin.js +390 -96
  324. package/dist/inspector/cli_bin.js.map +1 -1
  325. package/dist/inspector/examples.json +45 -45
  326. package/dist/inspector/manifest.json +1308 -155
  327. package/dist/positioning/index.d.ts +8 -0
  328. package/dist/positioning/index.js +148 -45
  329. package/dist/positioning/index.js.map +1 -1
  330. package/package.json +7 -7
  331. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  332. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/controllers/checkbox_controller.ts"],"names":[],"mappings":";;;;;AAsDO,IAAM,cAAN,MAAkB;AAAA;AAAA,EAEvB,MAAA,GAAS,IAAI,eAAA,EAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS7B,GAAA,CACE,MAAA,EACA,IAAA,EACA,OAAA,EACA,OAAA,EACM;AACN,IAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,OAAA,EAAS,EAAE,GAAG,SAAS,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,MAAA,EAAQ,CAAA;AAAA,EACnF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,OAAO,KAAA,EAAM;AAClB,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,eAAA,EAAgB;AAAA,EACpC;AACF,CAAA;;;AC7BO,IAAM,qBAAN,MAAyB;AAAA,EACrB,IAAA;AAAA,EACT,OAAA,GAAU,KAAA;AAAA,EACV,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,CAAA;AAAA;AAAA,EAGd,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AAAA;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AAAA,EACtB;AAAA;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,IAAe,CAAC,KAAK,OAAA,IAAW,CAAC,KAAK,OAAA,EAAS;AACvE,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;AC9BO,IAAM,kBAAA,GAAN,cAAiC,UAAA,CAAwB;AAAA,EAC9D,OAAgB,OAAA,GAAU,CAAC,QAAA,EAAU,OAAO,CAAA;AAAA,EAC5C,OAAO,OAAA,GAAU,CAAC,eAAA,EAAiB,gBAAgB,CAAA;AAAA,EACnD,OAAO,MAAA,GAAS,CAAC,QAAA,EAAU,WAAW,CAAA;AAAA;AAAA,EAQ7B,aAAa,IAAI,kBAAA,CAAmB,MAAM,IAAA,CAAK,wBAAwB,CAAA;AAAA,EACvE,UAAA,GAAa,IAAI,WAAA,EAAY;AAAA;AAAA,EAEtC,eAAA,GAAwC,IAAA;AAAA;AAAA,EAE/B,gBAAA,GAAmB,IAAI,gBAAA,CAAiB,CAAC,OAAA,KAAY;AAC5D,IAAA,IAAI,OAAA,CAAQ,KAAK,CAAC,MAAA,KAAW,KAAK,kBAAA,CAAmB,MAAA,CAAO,MAAM,CAAC,CAAA,EAAG;AACpE,MAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,IAC3B;AAAA,EACF,CAAC,CAAA;AAAA;AAAA,EAGQ,OAAA,GAAgB;AACvB,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AACzB,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,gBAAA,CAAiB,OAAA,CAAQ,IAAA,CAAK,OAAA,EAAS;AAAA,MAC1C,UAAA,EAAY,IAAA;AAAA,MACZ,eAAA,EAAiB,CAAC,SAAS,CAAA;AAAA,MAC3B,OAAA,EAAS;AAAA,KACV,CAAA;AACD,IAAA,IAAA,CAAK,WAAW,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,qBAAA,EAAuB,KAAK,QAAQ,CAAA;AACtE,IAAA,QAAA,CAAS,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,QAAA,EAAU,IAAI,CAAA;AAAA,EACxD;AAAA;AAAA,EAGS,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,WAAW,MAAA,EAAO;AACvB,IAAA,IAAA,CAAK,iBAAiB,UAAA,EAAW;AACjC,IAAA,IAAA,CAAK,WAAW,OAAA,EAAQ;AACxB,IAAA,QAAA,CAAS,mBAAA,CAAoB,OAAA,EAAS,IAAA,CAAK,QAAA,EAAU,IAAI,CAAA;AAAA,EAC3D;AAAA;AAAA,EAGA,qBAAA,GAA8B;AAC5B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA;AAAA,EAGA,wBAAA,GAAiC;AAC/B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA;AAAA,EAGA,oBAAA,GAA6B;AAC3B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA;AAAA,EAGA,uBAAA,GAAgC;AAC9B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA;AAAA,EAGA,cAAA,GAAuB;AACrB,IAAA,IAAI,CAAC,KAAK,eAAA,EAAiB;AAC3B,IAAA,MAAM,OAAA,GAAU,KAAK,YAAA,CAAa,OAAA;AAClC,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,YAAA,EAAc;AACrC,MAAA,KAAA,CAAM,OAAA,GAAU,OAAA;AAAA,IAClB;AACA,IAAA,IAAA,CAAK,QAAA,CAAS,OAAA,GAAU,KAAA,GAAQ,MAAA,EAAQ,IAAI,CAAA;AAC5C,IAAA,MAAM,MAAA,GAAS,KAAK,cAAA,EAAe;AACnC,IAAA,IAAI,QAAQ,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,QAAQ,CAAA;AAAA,EAChD;AAAA;AAAA,EAGA,aAAA,GAAsB;AACpB,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,MAAM,MAAA,GAAS,KAAK,cAAA,EAAe;AACnC,IAAA,IAAI,QAAQ,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,QAAQ,CAAA;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAA,GAA+B;AAC7B,IAAA,MAAM,WAAW,IAAA,CAAK,eAAA;AACtB,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAI,IAAA,CAAK,oBAAoB,QAAA,EAAU;AACvC,IAAA,MAAM,MAAA,GAAS,KAAK,cAAA,EAAe;AACnC,IAAA,IAAI,QAAQ,IAAA,CAAK,QAAA,CAAS,WAAA,EAAa,EAAE,QAAQ,CAAA;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAA,GAA0B;AAIxB,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,UAAA,IAAc,IAAA,CAAK,YAAA,CAAa,SAAS,CAAC,CAAA;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,QAAA,CAAS,OAAsB,WAAA,EAA4B;AACzD,IAAA,IAAI,WAAA,IAAe,KAAK,eAAA,EAAiB;AACvC,MAAA,IAAA,CAAK,YAAA,CAAa,UAAU,KAAA,KAAU,KAAA;AACtC,MAAA,IAAA,CAAK,YAAA,CAAa,gBAAgB,KAAA,KAAU,SAAA;AAAA,IAC9C;AACA,IAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,YAAA,EAAc,KAAK,CAAA;AAC7C,IAAA,IAAA,CAAK,eAAA,GAAkB,KAAA;AAAA,EACzB;AAAA;AAAA,EAGA,cAAA,GAA4F;AAC1F,IAAA,MAAM,QAAQ,IAAA,CAAK,eAAA;AACnB,IAAA,IAAI,KAAA,KAAU,MAAM,OAAO,IAAA;AAC3B,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,KAAU,OAAO,aAAA,EAAe,KAAA,KAAU,WAAW,KAAA,EAAM;AAAA,EAC/E;AAAA;AAAA,EAGS,WAAW,MAAY;AAC9B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B,CAAA;AAAA;AAAA,EAGS,QAAA,GAAW,CAAC,KAAA,KAAuB;AAC1C,IAAA,MAAM,OAAO,KAAA,CAAM,MAAA;AACnB,IAAA,IAAI,EAAE,IAAA,YAAgB,eAAA,CAAA,IAAoB,CAAC,IAAA,CAAK,mBAAA,CAAoB,IAAI,CAAA,EAAG;AAC3E,IAAA,cAAA,CAAe,MAAM;AACnB,MAAA,IAAI,CAAC,KAAA,CAAM,gBAAA,EAAkB,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,IACxD,CAAC,CAAA;AAAA,EACH,CAAA;AAAA;AAAA,EAGA,oBAAoB,IAAA,EAAgC;AAClD,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,aAAA,EAAe,GAAG,IAAA,CAAK,YAAY,CAAA,CAAE,IAAA,CAAK,CAAC,QAAA,KAAa,QAAA,CAAS,SAAS,IAAI,CAAA;AAAA,EAChG;AAAA;AAAA,EAGA,mBAAmB,IAAA,EAAqB;AACtC,IAAA,OACE,IAAA,CAAK,aAAA,CAAc,IAAA,CAAK,CAAC,aAAa,QAAA,KAAa,IAAI,CAAA,IACvD,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,CAAC,QAAA,KAAa,aAAa,IAAI,CAAA;AAAA,EAE1D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAA,GAA4B;AAC1B,IAAA,MAAM,WAAW,IAAA,CAAK,YAAA;AACtB,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,MAAA,IAAI,IAAA,CAAK,eAAA,IAAmB,IAAA,CAAK,YAAA,CAAa,eAAe,OAAO,SAAA;AACpE,MAAA,OAAO,IAAA,CAAK,eAAA,IAAmB,IAAA,CAAK,YAAA,CAAa,UAAU,KAAA,GAAQ,MAAA;AAAA,IACrE;AACA,IAAA,MAAM,UAAU,QAAA,CAAS,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,OAAO,CAAA,CAAE,MAAA;AAC1D,IAAA,IAAI,OAAA,KAAY,GAAG,OAAO,MAAA;AAC1B,IAAA,IAAI,OAAA,KAAY,QAAA,CAAS,MAAA,EAAQ,OAAO,KAAA;AACxC,IAAA,OAAO,SAAA;AAAA,EACT;AACF","file":"checkbox_controller.js","sourcesContent":["/**\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 { 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 /** 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 document.addEventListener(\"reset\", this.#onReset, true);\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 document.removeEventListener(\"reset\", this.#onReset, true);\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 /** Reconciles after a non-cancelled reset restores any managed checkbox. */\n readonly #onReset = (event: Event): void => {\n const form = event.target;\n if (!(form instanceof HTMLFormElement) || !this.#hasCheckboxOwnedBy(form)) return;\n queueMicrotask(() => {\n if (!event.defaultPrevented) this.#reconcile.schedule();\n });\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":";;AA6CO,IAAM,mBAAN,MAAuB;AAAA,EACnB,KAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA,uBAAc,GAAA,EAAY;AAAA,EACnC,WAAA,GAAc,CAAA;AAAA,EAMd,WAAA,CAAY,MAA0C,OAAA,EAAqB;AACzE,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AACb,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAAA,EAClB;AAAA,EAGA,OAAA,GAAgB;AACd,IAAA,QAAA,CAAS,gBAAA,CAAiB,OAAA,EAAS,IAAA,CAAK,YAAA,EAAc,IAAI,CAAA;AAAA,EAC5D;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,QAAA,CAAS,mBAAA,CAAoB,OAAA,EAAS,IAAA,CAAK,YAAA,EAAc,IAAI,CAAA;AAC7D,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AACpB,IAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,OAAA,EAAS,oBAAA,CAAqB,KAAK,CAAA;AAC5D,IAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AAAA,EACrB;AAAA,EAES,YAAA,GAAe,CAAC,KAAA,KAAuB;AAC9C,IAAA,MAAM,OAAO,KAAA,CAAM,MAAA;AACnB,IAAA,IAAI,EAAE,IAAA,YAAgB,eAAA,CAAA,IAAoB,CAAC,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,EAAG;AAC7D,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,EAAa;AACrC,MAAA,IAAI,KAAA,CAAM,UAAA,KAAe,KAAA,CAAM,IAAA,EAAM;AACnC,QAAA,IAAA,CAAK,QAAQ,KAAK,CAAA;AAClB,QAAA;AAAA,MACF;AACA,MAAA,MAAM,KAAA,GAAQ,sBAAsB,MAAM;AACxC,QAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,KAAK,CAAA;AACzB,QAAA,IAAA,CAAK,QAAQ,KAAK,CAAA;AAAA,MACpB,CAAC,CAAA;AACD,MAAA,IAAA,CAAK,OAAA,CAAQ,IAAI,KAAK,CAAA;AAAA,IACxB,CAAC,CAAA;AAAA,EACH,CAAA;AAAA,EAGA,QAAQ,KAAA,EAAoB;AAC1B,IAAA,IAAI,CAAC,KAAA,CAAM,gBAAA,EAAkB,IAAA,CAAK,QAAA,EAAS;AAAA,EAC7C;AACF,CAAA;;AC9CO,IAAM,cAAN,MAAkB;AAAA,EAEvB,MAAA,GAAS,IAAI,eAAA,EAAgB;AAAA,EAS7B,GAAA,CACE,MAAA,EACA,IAAA,EACA,OAAA,EACA,OAAA,EACM;AACN,IAAA,MAAA,CAAO,gBAAA,CAAiB,IAAA,EAAM,OAAA,EAAS,EAAE,GAAG,SAAS,MAAA,EAAQ,IAAA,CAAK,MAAA,CAAO,MAAA,EAAQ,CAAA;AAAA,EACnF;AAAA,EAMA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,OAAO,KAAA,EAAM;AAClB,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,eAAA,EAAgB;AAAA,EACpC;AACF,CAAA;;AC7BO,IAAM,qBAAN,MAAyB;AAAA,EACrB,IAAA;AAAA,EACT,OAAA,GAAU,KAAA;AAAA,EACV,OAAA,GAAU,KAAA;AAAA,EACV,WAAA,GAAc,CAAA;AAAA,EAGd,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,IAAA,GAAO,GAAA;AAAA,EACd;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AAAA,EACjB;AAAA,EAGA,MAAA,GAAe;AACb,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,IAAA,IAAA,CAAK,WAAA,IAAe,CAAA;AAAA,EACtB;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAI,CAAC,IAAA,CAAK,OAAA,IAAW,IAAA,CAAK,OAAA,EAAS;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA;AACf,IAAA,MAAM,aAAa,IAAA,CAAK,WAAA;AACxB,IAAA,cAAA,CAAe,MAAM;AAEnB,MAAA,IAAI,UAAA,KAAe,KAAK,WAAA,IAAe,CAAC,KAAK,OAAA,IAAW,CAAC,KAAK,OAAA,EAAS;AACvE,MAAA,IAAA,CAAK,OAAA,GAAU,KAAA;AACf,MAAA,IAAA,CAAK,IAAA,EAAK;AAAA,IACZ,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;ACxEO,IAAM,qBAAN,MAAyB;AAAA,EACrB,UAAA,GAAa,IAAI,WAAA,EAAY;AAAA,EAC7B,KAAA;AAAA,EACT,QAAA,GAA2B,IAAA;AAAA,EAClB,QAAA,GAAW,MAAY,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AAAA,EAGpD,YAAY,GAAA,EAAiB;AAC3B,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAI,kBAAA,CAAmB,GAAG,CAAA;AAAA,EACzC;AAAA,EAGA,QAAQ,OAAA,EAAwB;AAC9B,IAAA,IAAI,IAAA,CAAK,aAAa,OAAA,EAAS;AAC/B,IAAA,IAAA,CAAK,UAAA,EAAW;AAChB,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAChB,IAAA,IAAA,CAAK,MAAM,QAAA,EAAS;AACpB,IAAA,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,OAAA,EAAS,qBAAA,EAAuB,KAAK,QAAQ,CAAA;AAAA,EACnE;AAAA,EAGA,QAAA,GAAiB;AACf,IAAA,IAAA,CAAK,MAAM,QAAA,EAAS;AAAA,EACtB;AAAA,EAGA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,WAAW,OAAA,EAAQ;AACxB,IAAA,IAAA,CAAK,MAAM,MAAA,EAAO;AAClB,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAAA,EAClB;AACF,CAAA;;ACoBO,IAAM,kBAAA,GAAN,cAAiC,UAAA,CAAwB;AAAA,EAC9D,OAAgB,OAAA,GAAU,CAAC,QAAA,EAAU,OAAO,CAAA;AAAA,EAC5C,OAAO,OAAA,GAAU,CAAC,eAAA,EAAiB,gBAAgB,CAAA;AAAA,EACnD,OAAO,MAAA,GAAS,CAAC,QAAA,EAAU,WAAW,CAAA;AAAA,EAQ7B,aAAa,IAAI,kBAAA,CAAmB,MAAM,IAAA,CAAK,wBAAwB,CAAA;AAAA,EACvE,aAAa,IAAI,gBAAA;AAAA,IACxB,CAAC,IAAA,KAAS,IAAA,CAAK,mBAAA,CAAoB,IAAI,CAAA;AAAA,IACvC,MAAM,IAAA,CAAK,UAAA,CAAW,QAAA;AAAS,GACjC;AAAA,EAEA,eAAA,GAAwC,IAAA;AAAA,EAE/B,gBAAA,GAAmB,IAAI,gBAAA,CAAiB,CAAC,OAAA,KAAY;AAC5D,IAAA,IAAI,OAAA,CAAQ,KAAK,CAAC,MAAA,KAAW,KAAK,kBAAA,CAAmB,MAAA,CAAO,MAAM,CAAC,CAAA,EAAG;AACpE,MAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,IAC3B;AAAA,EACF,CAAC,CAAA;AAAA,EAGQ,OAAA,GAAgB;AACvB,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAA,CAAK,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA;AACpC,IAAA,IAAA,CAAK,gBAAA,CAAiB,OAAA,CAAQ,IAAA,CAAK,OAAA,EAAS;AAAA,MAC1C,UAAA,EAAY,IAAA;AAAA,MACZ,eAAA,EAAiB,CAAC,SAAS,CAAA;AAAA,MAC3B,OAAA,EAAS;AAAA,KACV,CAAA;AACD,IAAA,IAAA,CAAK,WAAW,OAAA,EAAQ;AAAA,EAC1B;AAAA,EAGS,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,WAAW,UAAA,EAAW;AAC3B,IAAA,IAAA,CAAK,iBAAiB,UAAA,EAAW;AACjC,IAAA,IAAA,CAAK,WAAW,UAAA,EAAW;AAAA,EAC7B;AAAA,EAGA,qBAAA,GAA8B;AAC5B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA,EAGA,wBAAA,GAAiC;AAC/B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA,EAGA,oBAAA,GAA6B;AAC3B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA,EAGA,uBAAA,GAAgC;AAC9B,IAAA,IAAA,CAAK,WAAW,QAAA,EAAS;AAAA,EAC3B;AAAA,EAGA,cAAA,GAAuB;AACrB,IAAA,IAAI,CAAC,KAAK,eAAA,EAAiB;AAC3B,IAAA,MAAM,WAAW,IAAA,CAAK,eAAA;AACtB,IAAA,MAAM,OAAA,GAAU,KAAK,YAAA,CAAa,OAAA;AAClC,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,YAAA,EAAc;AACrC,MAAA,KAAA,CAAM,OAAA,GAAU,OAAA;AAAA,IAClB;AACA,IAAA,IAAA,CAAK,QAAA,CAAS,OAAA,GAAU,KAAA,GAAQ,MAAA,EAAQ,IAAI,CAAA;AAC5C,IAAA,IAAI,IAAA,CAAK,oBAAoB,QAAA,EAAU;AACvC,IAAA,MAAM,MAAA,GAAS,KAAK,cAAA,EAAe;AACnC,IAAA,IAAI,QAAQ,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,QAAQ,CAAA;AAAA,EAChD;AAAA,EAGA,aAAA,GAAsB;AACpB,IAAA,MAAM,WAAW,IAAA,CAAK,eAAA;AACtB,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAI,IAAA,CAAK,oBAAoB,QAAA,EAAU;AACvC,IAAA,MAAM,MAAA,GAAS,KAAK,cAAA,EAAe;AACnC,IAAA,IAAI,QAAQ,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,QAAQ,CAAA;AAAA,EAChD;AAAA,EAMA,sBAAA,GAA+B;AAC7B,IAAA,MAAM,WAAW,IAAA,CAAK,eAAA;AACtB,IAAA,IAAA,CAAK,iBAAA,EAAkB;AACvB,IAAA,IAAI,IAAA,CAAK,oBAAoB,QAAA,EAAU;AACvC,IAAA,MAAM,MAAA,GAAS,KAAK,cAAA,EAAe;AACnC,IAAA,IAAI,QAAQ,IAAA,CAAK,QAAA,CAAS,WAAA,EAAa,EAAE,QAAQ,CAAA;AAAA,EACnD;AAAA,EAOA,iBAAA,GAA0B;AAIxB,IAAA,IAAA,CAAK,SAAS,IAAA,CAAK,UAAA,IAAc,IAAA,CAAK,YAAA,CAAa,SAAS,CAAC,CAAA;AAAA,EAC/D;AAAA,EAQA,QAAA,CAAS,OAAsB,WAAA,EAA4B;AACzD,IAAA,IAAI,WAAA,IAAe,KAAK,eAAA,EAAiB;AACvC,MAAA,IAAA,CAAK,YAAA,CAAa,UAAU,KAAA,KAAU,KAAA;AACtC,MAAA,IAAA,CAAK,YAAA,CAAa,gBAAgB,KAAA,KAAU,SAAA;AAAA,IAC9C;AACA,IAAA,IAAA,CAAK,OAAA,CAAQ,YAAA,CAAa,YAAA,EAAc,KAAK,CAAA;AAC7C,IAAA,IAAA,CAAK,eAAA,GAAkB,KAAA;AAAA,EACzB;AAAA,EAGA,cAAA,GAA4F;AAC1F,IAAA,MAAM,QAAQ,IAAA,CAAK,eAAA;AACnB,IAAA,IAAI,KAAA,KAAU,MAAM,OAAO,IAAA;AAC3B,IAAA,OAAO,EAAE,OAAA,EAAS,KAAA,KAAU,OAAO,aAAA,EAAe,KAAA,KAAU,WAAW,KAAA,EAAM;AAAA,EAC/E;AAAA,EAGA,oBAAoB,IAAA,EAAgC;AAClD,IAAA,OAAO,CAAC,GAAG,IAAA,CAAK,aAAA,EAAe,GAAG,IAAA,CAAK,YAAY,CAAA,CAAE,IAAA,CAAK,CAAC,QAAA,KAAa,QAAA,CAAS,SAAS,IAAI,CAAA;AAAA,EAChG;AAAA,EAGA,mBAAmB,IAAA,EAAqB;AACtC,IAAA,OACE,IAAA,CAAK,aAAA,CAAc,IAAA,CAAK,CAAC,aAAa,QAAA,KAAa,IAAI,CAAA,IACvD,IAAA,CAAK,YAAA,CAAa,IAAA,CAAK,CAAC,QAAA,KAAa,aAAa,IAAI,CAAA;AAAA,EAE1D;AAAA,EAOA,UAAA,GAA4B;AAC1B,IAAA,MAAM,WAAW,IAAA,CAAK,YAAA;AACtB,IAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACzB,MAAA,IAAI,IAAA,CAAK,eAAA,IAAmB,IAAA,CAAK,YAAA,CAAa,eAAe,OAAO,SAAA;AACpE,MAAA,OAAO,IAAA,CAAK,eAAA,IAAmB,IAAA,CAAK,YAAA,CAAa,UAAU,KAAA,GAAQ,MAAA;AAAA,IACrE;AACA,IAAA,MAAM,UAAU,QAAA,CAAS,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,OAAO,CAAA,CAAE,MAAA;AAC1D,IAAA,IAAI,OAAA,KAAY,GAAG,OAAO,MAAA;AAC1B,IAAA,IAAI,OAAA,KAAY,QAAA,CAAS,MAAA,EAAQ,OAAO,KAAA;AACxC,IAAA,OAAO,SAAA;AAAA,EACT;AACF","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","/**\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, 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 * 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 * @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 * @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 { 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 /** 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,20 @@ 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.
53
+ *
54
+ * A `copiedLabel` / `errorLabel` changed on the live element while its result is
55
+ * shown rewrites the slot — only while the slot still reads the label the copy wrote
56
+ * — without announcing, dispatching or moving the return to idle. Stimulus reports no
57
+ * change when a label attribute whose value was empty is removed, so the slot keeps
58
+ * the empty label until the next copy. `feedbackDuration` and the announcement texts
59
+ * belong to one copy, so a change to them applies from the next.
50
60
  */
51
61
  declare class ClipboardController extends Controller<HTMLElement> {
52
62
  #private;
@@ -77,11 +87,19 @@ declare class ClipboardController extends Controller<HTMLElement> {
77
87
  default: string;
78
88
  };
79
89
  };
90
+ static valueConstraints: {
91
+ feedbackDuration: {
92
+ readonly finite: true;
93
+ readonly min: 0;
94
+ readonly max: 2147483647;
95
+ };
96
+ };
80
97
  static actions: readonly ["copy"];
81
98
  static events: readonly ["copy"];
82
99
  readonly sourceTarget: HTMLElement;
83
100
  readonly buttonTarget: HTMLElement;
84
101
  readonly feedbackTarget: HTMLElement;
102
+ readonly feedbackTargets: HTMLElement[];
85
103
  readonly hasSourceTarget: boolean;
86
104
  readonly hasButtonTarget: boolean;
87
105
  readonly hasFeedbackTarget: boolean;
@@ -93,10 +111,29 @@ declare class ClipboardController extends Controller<HTMLElement> {
93
111
  announceErrorTextValue: string;
94
112
  connect(): void;
95
113
  disconnect(): void;
114
+ /**
115
+ * Follows a `copiedLabel` swapped in place while a successful copy is shown.
116
+ *
117
+ * Render only: nothing is announced or dispatched, and the return to idle keeps the
118
+ * deadline the copy set. The slot changes only while `data-state` is `copied` and it
119
+ * still reads the previous label — the one the copy wrote there — so text someone
120
+ * else put in the slot stays. Stimulus also runs this ahead of `connect()`, which
121
+ * settles a result restored with the markup itself, so nothing is written then.
122
+ */
123
+ copiedLabelValueChanged(label: string, previous: string | undefined): void;
124
+ /**
125
+ * Follows an `errorLabel` swapped in place while a failed copy is shown, on the same
126
+ * terms as {@link ClipboardController.copiedLabelValueChanged}: only while
127
+ * `data-state` is `error` and the slot still reads the previous label.
128
+ */
129
+ errorLabelValueChanged(label: string, previous: string | undefined): void;
96
130
  /**
97
131
  * Copies the resolved text and reports the outcome. Bound via `data-action`
98
- * (click). Dispatches `stimeo--clipboard:copy` with `{ success, text }` once per
99
- * completed attempt — including on failure — so consumers can react either way.
132
+ * (click). Dispatches `stimeo--clipboard:copy` with `{ success, text, message }`
133
+ * once per completed attempt — including on failure — so consumers can react
134
+ * either way. `message` is the authored label for that outcome, so a part that
135
+ * shows wording — `stimeo--clipboard:copy->stimeo--toast#show` — needs nothing
136
+ * else; wiring both that and the announcer reads the same result twice.
100
137
  * An attempt whose connection ended while it was in flight reports nothing.
101
138
  */
102
139
  copy(): Promise<void>;
@@ -1,8 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/clipboard_controller.ts
4
-
5
- // src/utils/announce.ts
6
3
  function announce(message, options = {}) {
7
4
  const text = message.trim();
8
5
  if (text.length === 0) return;
@@ -13,79 +10,140 @@ function announce(message, options = {}) {
13
10
  );
14
11
  }
15
12
 
16
- // src/utils/before_cache_reset.ts
17
- var BeforeCacheReset = class _BeforeCacheReset {
18
- /** Every subscribed instance, iterated by the one shared document listener. */
19
- static #subscribers = /* @__PURE__ */ new Set();
20
- /** The shared listener; installed while at least one instance is subscribed. */
21
- static #onBeforeCache = () => {
22
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
23
- };
24
- #rewind;
25
- /** @param rewind - the pass that returns this controller's state to its initial form. */
26
- constructor(rewind) {
27
- this.#rewind = rewind;
13
+ function setDefaultAttribute(element, name, value) {
14
+ if (element.hasAttribute(name)) return false;
15
+ element.setAttribute(name, value);
16
+ return true;
17
+ }
18
+
19
+ var ListenerSet = class {
20
+ #abort = new AbortController();
21
+ add(target, type, handler, options) {
22
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
23
+ }
24
+ dispose() {
25
+ this.#abort.abort();
26
+ this.#abort = new AbortController();
27
+ }
28
+ };
29
+
30
+ var MicrotaskCoalescer = class {
31
+ #run;
32
+ #queued = false;
33
+ #active = false;
34
+ #generation = 0;
35
+ constructor(run) {
36
+ this.#run = run;
28
37
  }
29
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
30
38
  activate() {
31
- const first = _BeforeCacheReset.#subscribers.size === 0;
32
- _BeforeCacheReset.#subscribers.add(this);
33
- if (first) {
34
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
35
- }
39
+ this.#active = true;
40
+ }
41
+ cancel() {
42
+ this.#active = false;
43
+ this.#queued = false;
44
+ this.#generation += 1;
36
45
  }
37
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
38
- deactivate() {
39
- _BeforeCacheReset.#subscribers.delete(this);
40
- if (_BeforeCacheReset.#subscribers.size > 0) return;
41
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
46
+ schedule() {
47
+ if (!this.#active || this.#queued) return;
48
+ this.#queued = true;
49
+ const generation = this.#generation;
50
+ queueMicrotask(() => {
51
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
52
+ this.#queued = false;
53
+ this.#run();
54
+ });
42
55
  }
43
56
  };
44
57
 
45
- // src/utils/default_attribute.ts
46
- function setDefaultAttribute(element, name, value) {
47
- if (element.hasAttribute(name)) return false;
48
- element.setAttribute(name, value);
58
+ var MorphRenderWatcher = class {
59
+ #listeners = new ListenerSet();
60
+ #pass;
61
+ #element = null;
62
+ #onMorph = () => this.#pass.schedule();
63
+ constructor(run) {
64
+ this.#pass = new MicrotaskCoalescer(run);
65
+ }
66
+ observe(element) {
67
+ if (this.#element === element) return;
68
+ this.disconnect();
69
+ this.#element = element;
70
+ this.#pass.activate();
71
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
72
+ }
73
+ schedule() {
74
+ this.#pass.schedule();
75
+ }
76
+ disconnect() {
77
+ this.#listeners.dispose();
78
+ this.#pass.cancel();
79
+ this.#element = null;
80
+ }
81
+ };
82
+
83
+ var MAX_TIMER_DELAY_MS = 2147483647;
84
+
85
+ var NUMBER_BOUNDS = {
86
+ timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
87
+ function matchesNumberBounds(value, bounds) {
88
+ if (!Number.isFinite(value)) {
89
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
90
+ if (direction === null) return false;
91
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
92
+ }
93
+ if (bounds.min !== void 0 && value < bounds.min) return false;
94
+ if (bounds.max !== void 0 && value > bounds.max) return false;
95
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
96
+ if (bounds.integer && !Number.isInteger(value)) return false;
97
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
49
98
  return true;
50
99
  }
51
100
 
52
- // src/utils/safe_timeout.ts
101
+ function readNumber(raw, fallback, bounds) {
102
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
103
+ }
104
+
105
+ var NumberValueReader = class {
106
+ #lastRejected = /* @__PURE__ */ new Map();
107
+ read(owner, name, raw, fallback, bounds) {
108
+ const resolved = readNumber(raw, fallback, bounds);
109
+ if (matchesNumberBounds(raw, bounds)) {
110
+ this.#lastRejected.delete(name);
111
+ return resolved;
112
+ }
113
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
114
+ const literal = owner.element.getAttribute(attribute);
115
+ if (literal === null) {
116
+ this.#lastRejected.delete(name);
117
+ return resolved;
118
+ }
119
+ if (this.#lastRejected.get(name) !== literal) {
120
+ this.#lastRejected.set(name, literal);
121
+ console.warn(
122
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
123
+ );
124
+ }
125
+ return resolved;
126
+ }
127
+ };
128
+
53
129
  var TimerRegistry = class {
54
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
55
130
  ids = /* @__PURE__ */ new Set();
56
- /**
57
- * Cancels a single tracked timer.
58
- *
59
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
60
- * registry), so callers can clear defensively without guarding.
61
- */
62
131
  clear(id) {
63
132
  if (this.ids.delete(id)) {
64
133
  this.cancel(id);
65
134
  }
66
135
  }
67
- /**
68
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
69
- * to guarantee no timer outlives the element.
70
- */
71
136
  clearAll() {
72
137
  for (const id of this.ids) {
73
138
  this.cancel(id);
74
139
  }
75
140
  this.ids.clear();
76
141
  }
77
- /** Number of timers currently tracked (pending). */
78
142
  get size() {
79
143
  return this.ids.size;
80
144
  }
81
145
  };
82
146
  var SafeTimeout = class extends TimerRegistry {
83
- /**
84
- * Schedules `callback` after `delay` ms and returns the timer id.
85
- *
86
- * The id is removed from the registry automatically when the timeout fires,
87
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
88
- */
89
147
  set(callback, delay) {
90
148
  const id = this.schedule(() => {
91
149
  this.ids.delete(id);
@@ -102,9 +160,9 @@ var SafeTimeout = class extends TimerRegistry {
102
160
  }
103
161
  };
104
162
 
105
- // src/controllers/clipboard_controller.ts
106
163
  var TRANSIENT_STATES = /* @__PURE__ */ new Set(["copied", "error"]);
107
- var ClipboardController = class extends Controller {
164
+ var ClipboardController = class _ClipboardController extends Controller {
165
+ #numbers = new NumberValueReader();
108
166
  static targets = ["source", "button", "feedback"];
109
167
  static values = {
110
168
  text: { type: String, default: "" },
@@ -114,38 +172,39 @@ var ClipboardController = class extends Controller {
114
172
  announceCopiedText: { type: String, default: "" },
115
173
  announceErrorText: { type: String, default: "" }
116
174
  };
175
+ static valueConstraints = {
176
+ feedbackDuration: NUMBER_BOUNDS.timer
177
+ };
117
178
  static actions = ["copy"];
118
179
  static events = ["copy"];
119
- /**
120
- * The pending return to idle — the only timer this controller schedules, so
121
- * `clearAll()` is exactly "drop the auto-clear" and needs no id of its own.
122
- */
123
180
  #timers = new SafeTimeout();
124
- /** Returns the completion state to idle for the snapshot Turbo takes. */
125
- #beforeCache = new BeforeCacheReset(() => this.#rewind());
126
- /**
127
- * Whether this connection is still live. `copy()` suspends on the Clipboard API,
128
- * and a teardown that lands while it is suspended must win: the continuation
129
- * would otherwise write to an element nobody owns and arm a timer past the
130
- * `clearAll()` that was supposed to be the last word.
131
- */
132
181
  #connected = false;
182
+ #result = null;
183
+ #feedback = null;
184
+ #written = /* @__PURE__ */ new Map();
185
+ #morphRender = new MorphRenderWatcher(() => this.#renderResult());
133
186
  connect() {
134
187
  this.#connected = true;
135
188
  this.#adopt();
136
- this.#beforeCache.activate();
189
+ this.#result = null;
190
+ this.#feedback = this.hasFeedbackTarget ? {
191
+ element: this.feedbackTarget,
192
+ original: this.feedbackTarget.textContent ?? "",
193
+ text: this.feedbackTarget.textContent ?? ""
194
+ } : null;
195
+ this.#morphRender.observe(this.element);
137
196
  }
138
197
  disconnect() {
139
198
  this.#connected = false;
140
- this.#beforeCache.deactivate();
199
+ this.#morphRender.disconnect();
141
200
  this.#timers.clearAll();
142
201
  }
143
- /**
144
- * Copies the resolved text and reports the outcome. Bound via `data-action`
145
- * (click). Dispatches `stimeo--clipboard:copy` with `{ success, text }` once per
146
- * completed attempt — including on failure — so consumers can react either way.
147
- * An attempt whose connection ended while it was in flight reports nothing.
148
- */
202
+ copiedLabelValueChanged(label, previous) {
203
+ this.#followLabel("copied", label, previous);
204
+ }
205
+ errorLabelValueChanged(label, previous) {
206
+ this.#followLabel("error", label, previous);
207
+ }
149
208
  async copy() {
150
209
  const text = this.#resolveText();
151
210
  let success = false;
@@ -157,12 +216,9 @@ var ClipboardController = class extends Controller {
157
216
  }
158
217
  if (!this.#connected) return;
159
218
  this.#reportResult(success);
160
- this.dispatch("copy", { detail: { success, text } });
219
+ const message = success ? this.copiedLabelValue : this.errorLabelValue;
220
+ this.dispatch("copy", { detail: { success, text, message } });
161
221
  }
162
- /**
163
- * The text to copy: the explicit `text` value when set, otherwise the source
164
- * target's current value (inputs/textareas) or text content.
165
- */
166
222
  #resolveText() {
167
223
  if (this.textValue.length > 0) return this.textValue;
168
224
  if (!this.hasSourceTarget) return "";
@@ -172,60 +228,83 @@ var ClipboardController = class extends Controller {
172
228
  }
173
229
  return source.textContent ?? "";
174
230
  }
175
- /**
176
- * Reads the current state back from the DOM.
177
- *
178
- * A `copied` or `error` found at connect time is this controller's own output
179
- * from a connection that is gone, and so is the timer that would have cleared it
180
- * — nothing else would ever return the element to `idle`. Any other authored
181
- * value belongs to the consumer and only a missing attribute takes the default.
182
- */
183
231
  #adopt() {
184
232
  if (this.#inTransientState()) {
233
+ const restored = this.#written.size === 0;
185
234
  this.#reset();
235
+ if (restored && this.hasFeedbackTarget) this.feedbackTarget.textContent = "";
186
236
  return;
187
237
  }
188
238
  setDefaultAttribute(this.element, "data-state", "idle");
189
239
  }
190
- /** Whether `data-state` currently holds one of the values this controller writes. */
191
240
  #inTransientState() {
192
241
  const state = this.element.getAttribute("data-state");
193
242
  return state !== null && TRANSIENT_STATES.has(state);
194
243
  }
195
- /**
196
- * Returns the completion state to idle for the snapshot Turbo is about to take,
197
- * so a page reached with the Back button does not report a copy that happened
198
- * before the navigation. Only a state this controller wrote is rewound — an
199
- * authored one is the consumer's and has to survive into the snapshot, exactly as
200
- * `connect()` leaves it alone. State only — no `copy` is dispatched, which would
201
- * claim a fresh copy ran.
202
- */
203
- #rewind() {
204
- if (!this.#inTransientState()) return;
205
- this.#timers.clearAll();
206
- this.#reset();
207
- }
208
- /** Reflects the result, announces it, and schedules the return to idle. */
209
244
  #reportResult(success) {
210
- this.element.setAttribute("data-state", success ? "copied" : "error");
211
- if (this.hasFeedbackTarget) {
212
- this.feedbackTarget.textContent = success ? this.copiedLabelValue : this.errorLabelValue;
245
+ this.#result = success;
246
+ this.#renderResult(true);
247
+ this.#announceResult(success);
248
+ this.#scheduleReset();
249
+ }
250
+ #renderResult(force = false) {
251
+ if (this.#result === null) {
252
+ setDefaultAttribute(this.element, "data-state", "idle");
253
+ return;
213
254
  }
255
+ this.element.setAttribute("data-state", this.#result ? "copied" : "error");
256
+ if (!this.hasFeedbackTarget) return;
257
+ const element = this.feedbackTarget;
258
+ const held = this.#feedback;
259
+ if (!force && (!held || element !== held.element || element.firstElementChild !== null || element.textContent !== held.text && element.textContent !== held.original))
260
+ return;
261
+ const text = this.#result ? this.copiedLabelValue : this.errorLabelValue;
262
+ this.#feedback = {
263
+ element,
264
+ original: held?.element === element ? held.original : element.textContent ?? "",
265
+ text
266
+ };
267
+ element.textContent = text;
268
+ this.#written.set(element, text);
269
+ }
270
+ #announceResult(success) {
214
271
  announce(success ? this.announceCopiedTextValue : this.announceErrorTextValue);
272
+ }
273
+ #scheduleReset() {
215
274
  this.#timers.clearAll();
216
- if (this.feedbackDurationValue > 0) {
217
- this.#timers.set(() => this.#reset(), this.feedbackDurationValue);
275
+ if (this.#safeFeedbackDuration > 0) {
276
+ this.#timers.set(() => this.#reset(), this.#safeFeedbackDuration);
218
277
  }
219
278
  }
220
- /** Returns to the idle state and empties the completion slot. */
279
+ #followLabel(state, label, previous) {
280
+ if (!this.#connected) return;
281
+ if (this.element.getAttribute("data-state") !== state || !this.hasFeedbackTarget) return;
282
+ if (this.feedbackTarget.textContent !== previous) return;
283
+ this.feedbackTarget.textContent = label;
284
+ this.#written.set(this.feedbackTarget, label);
285
+ if (this.#feedback?.element === this.feedbackTarget) this.#feedback.text = label;
286
+ }
221
287
  #reset() {
288
+ this.#result = null;
222
289
  this.element.setAttribute("data-state", "idle");
223
- if (this.hasFeedbackTarget) {
224
- this.feedbackTarget.textContent = "";
290
+ for (const [element, text] of this.#written) {
291
+ if (!this.feedbackTargets.includes(element)) continue;
292
+ if (element.firstElementChild === null && element.textContent === text) {
293
+ element.textContent = "";
294
+ }
225
295
  }
296
+ this.#written.clear();
297
+ }
298
+ get #safeFeedbackDuration() {
299
+ return this.#numbers.read(
300
+ this,
301
+ "feedbackDuration",
302
+ this.feedbackDurationValue,
303
+ _ClipboardController.values.feedbackDuration.default,
304
+ _ClipboardController.valueConstraints.feedbackDuration
305
+ );
226
306
  }
227
307
  };
228
308
 
229
309
  export { ClipboardController };
230
- //# sourceMappingURL=clipboard_controller.js.map
231
310
  //# sourceMappingURL=clipboard_controller.js.map