stimeo-ui 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +27 -11
  3. package/dist/cable/index.d.ts +88 -31
  4. package/dist/cable/index.js +384 -449
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.d.ts +4 -2
  8. package/dist/controllers/accordion_controller.js +174 -55
  9. package/dist/controllers/accordion_controller.js.map +1 -1
  10. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  11. package/dist/controllers/alert_dialog_controller.js +903 -235
  12. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  13. package/dist/controllers/announcer_controller.d.ts +37 -5
  14. package/dist/controllers/announcer_controller.js +238 -205
  15. package/dist/controllers/announcer_controller.js.map +1 -1
  16. package/dist/controllers/aspect_ratio_controller.js +0 -10
  17. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  18. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  19. package/dist/controllers/auto_submit_controller.js +414 -145
  20. package/dist/controllers/auto_submit_controller.js.map +1 -1
  21. package/dist/controllers/avatar_controller.js +185 -107
  22. package/dist/controllers/avatar_controller.js.map +1 -1
  23. package/dist/controllers/breadcrumb_controller.js +0 -130
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +21 -3
  26. package/dist/controllers/bulk_select_controller.js +282 -58
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +38 -16
  29. package/dist/controllers/calendar_controller.js +228 -260
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +43 -12
  32. package/dist/controllers/carousel_controller.js +536 -436
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +44 -7
  35. package/dist/controllers/character_counter_controller.js +493 -207
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +38 -74
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +16 -5
  41. package/dist/controllers/clipboard_controller.js +261 -149
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +17 -5
  44. package/dist/controllers/collapsible_controller.js +304 -120
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +18 -6
  47. package/dist/controllers/color_picker_controller.js +178 -146
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +24 -6
  50. package/dist/controllers/combobox_controller.js +522 -156
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +51 -20
  53. package/dist/controllers/command_palette_controller.js +1156 -395
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +300 -171
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +980 -262
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +80 -18
  62. package/dist/controllers/context_menu_controller.js +346 -153
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +47 -3
  68. package/dist/controllers/countdown_controller.js +281 -158
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -10
  71. package/dist/controllers/currency_input_controller.js +575 -225
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +25 -12
  74. package/dist/controllers/data_grid_controller.js +248 -214
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +42 -14
  77. package/dist/controllers/date_range_picker_controller.js +338 -235
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +17 -6
  80. package/dist/controllers/dialog_controller.js +899 -237
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +25 -8
  83. package/dist/controllers/direct_upload_controller.js +97 -167
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  86. package/dist/controllers/dirty_form_controller.js +72 -69
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +196 -24
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +39 -11
  92. package/dist/controllers/drawer_controller.js +974 -355
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +31 -1
  95. package/dist/controllers/dropdown_controller.js +339 -96
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +315 -88
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +79 -58
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +63 -18
  103. package/dist/controllers/file_dropzone_controller.js +322 -211
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.js +77 -42
  106. package/dist/controllers/filter_controller.js.map +1 -1
  107. package/dist/controllers/flash_controller.d.ts +35 -12
  108. package/dist/controllers/flash_controller.js +238 -347
  109. package/dist/controllers/flash_controller.js.map +1 -1
  110. package/dist/controllers/focus_controller.d.ts +15 -6
  111. package/dist/controllers/focus_controller.js +679 -240
  112. package/dist/controllers/focus_controller.js.map +1 -1
  113. package/dist/controllers/form_field_controller.js +156 -132
  114. package/dist/controllers/form_field_controller.js.map +1 -1
  115. package/dist/controllers/form_validation_controller.d.ts +3 -3
  116. package/dist/controllers/form_validation_controller.js +31 -97
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +47 -14
  119. package/dist/controllers/frame_loading_controller.js +275 -217
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +27 -6
  122. package/dist/controllers/highlight_controller.js +107 -75
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +60 -4
  125. package/dist/controllers/hover_card_controller.js +327 -130
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +48 -6
  128. package/dist/controllers/idle_controller.js +271 -101
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +30 -13
  131. package/dist/controllers/input_mask_controller.js +483 -97
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +150 -106
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +19 -2
  137. package/dist/controllers/lazy_frame_controller.js +24 -68
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +44 -9
  140. package/dist/controllers/listbox_controller.js +523 -240
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +9 -7
  143. package/dist/controllers/local_time_controller.js +102 -77
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +80 -96
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +55 -11
  149. package/dist/controllers/menu_controller.js +302 -188
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +51 -9
  152. package/dist/controllers/menubar_controller.js +232 -345
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +33 -20
  155. package/dist/controllers/meter_controller.js +417 -87
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +48 -18
  158. package/dist/controllers/multi_select_controller.js +785 -393
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +27 -6
  161. package/dist/controllers/navigation_menu_controller.js +190 -250
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +396 -192
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +268 -62
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +50 -8
  170. package/dist/controllers/number_input_controller.js +610 -303
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.d.ts +8 -5
  173. package/dist/controllers/optimistic_controller.js +45 -87
  174. package/dist/controllers/optimistic_controller.js.map +1 -1
  175. package/dist/controllers/otp_controller.d.ts +55 -8
  176. package/dist/controllers/otp_controller.js +658 -255
  177. package/dist/controllers/otp_controller.js.map +1 -1
  178. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  179. package/dist/controllers/overflow_indicator_controller.js +122 -113
  180. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  181. package/dist/controllers/overflow_menu_controller.d.ts +58 -20
  182. package/dist/controllers/overflow_menu_controller.js +470 -295
  183. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  184. package/dist/controllers/pagination_controller.d.ts +37 -7
  185. package/dist/controllers/pagination_controller.js +332 -123
  186. package/dist/controllers/pagination_controller.js.map +1 -1
  187. package/dist/controllers/password_reveal_controller.d.ts +46 -8
  188. package/dist/controllers/password_reveal_controller.js +392 -121
  189. package/dist/controllers/password_reveal_controller.js.map +1 -1
  190. package/dist/controllers/password_strength_controller.d.ts +15 -6
  191. package/dist/controllers/password_strength_controller.js +111 -158
  192. package/dist/controllers/password_strength_controller.js.map +1 -1
  193. package/dist/controllers/persist_controller.d.ts +11 -4
  194. package/dist/controllers/persist_controller.js +165 -111
  195. package/dist/controllers/persist_controller.js.map +1 -1
  196. package/dist/controllers/pointer_drag_controller.d.ts +35 -12
  197. package/dist/controllers/pointer_drag_controller.js +434 -188
  198. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  199. package/dist/controllers/popover_controller.d.ts +41 -5
  200. package/dist/controllers/popover_controller.js +269 -113
  201. package/dist/controllers/popover_controller.js.map +1 -1
  202. package/dist/controllers/portal_controller.d.ts +44 -4
  203. package/dist/controllers/portal_controller.js +215 -76
  204. package/dist/controllers/portal_controller.js.map +1 -1
  205. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  206. package/dist/controllers/preview_guard_controller.js +243 -116
  207. package/dist/controllers/preview_guard_controller.js.map +1 -1
  208. package/dist/controllers/progress_controller.d.ts +20 -19
  209. package/dist/controllers/progress_controller.js +391 -78
  210. package/dist/controllers/progress_controller.js.map +1 -1
  211. package/dist/controllers/radio_group_controller.d.ts +21 -9
  212. package/dist/controllers/radio_group_controller.js +278 -211
  213. package/dist/controllers/radio_group_controller.js.map +1 -1
  214. package/dist/controllers/range_slider_controller.d.ts +47 -6
  215. package/dist/controllers/range_slider_controller.js +220 -113
  216. package/dist/controllers/range_slider_controller.js.map +1 -1
  217. package/dist/controllers/rating_controller.d.ts +20 -4
  218. package/dist/controllers/rating_controller.js +458 -211
  219. package/dist/controllers/rating_controller.js.map +1 -1
  220. package/dist/controllers/read_more_controller.d.ts +2 -0
  221. package/dist/controllers/read_more_controller.js +138 -63
  222. package/dist/controllers/read_more_controller.js.map +1 -1
  223. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  224. package/dist/controllers/reading_progress_controller.js +166 -137
  225. package/dist/controllers/reading_progress_controller.js.map +1 -1
  226. package/dist/controllers/relative_time_controller.d.ts +20 -6
  227. package/dist/controllers/relative_time_controller.js +211 -112
  228. package/dist/controllers/relative_time_controller.js.map +1 -1
  229. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  230. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  231. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  232. package/dist/controllers/resizable_controller.d.ts +31 -2
  233. package/dist/controllers/resizable_controller.js +299 -150
  234. package/dist/controllers/resizable_controller.js.map +1 -1
  235. package/dist/controllers/roving_controller.d.ts +3 -3
  236. package/dist/controllers/roving_controller.js +49 -88
  237. package/dist/controllers/roving_controller.js.map +1 -1
  238. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  239. package/dist/controllers/scroll_area_controller.js +490 -246
  240. package/dist/controllers/scroll_area_controller.js.map +1 -1
  241. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  242. package/dist/controllers/scroll_restore_controller.js +14 -55
  243. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  244. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  245. package/dist/controllers/scroll_visibility_controller.js +432 -149
  246. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  247. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  248. package/dist/controllers/scrollspy_controller.js +193 -248
  249. package/dist/controllers/scrollspy_controller.js.map +1 -1
  250. package/dist/controllers/separator_controller.d.ts +20 -5
  251. package/dist/controllers/separator_controller.js +285 -142
  252. package/dist/controllers/separator_controller.js.map +1 -1
  253. package/dist/controllers/sidebar_controller.d.ts +65 -8
  254. package/dist/controllers/sidebar_controller.js +1069 -404
  255. package/dist/controllers/sidebar_controller.js.map +1 -1
  256. package/dist/controllers/skeleton_controller.d.ts +23 -0
  257. package/dist/controllers/skeleton_controller.js +259 -101
  258. package/dist/controllers/skeleton_controller.js.map +1 -1
  259. package/dist/controllers/slider_controller.d.ts +29 -2
  260. package/dist/controllers/slider_controller.js +187 -86
  261. package/dist/controllers/slider_controller.js.map +1 -1
  262. package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
  263. package/dist/controllers/smart_sticky_header_controller.js +173 -66
  264. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  265. package/dist/controllers/sortable_controller.d.ts +4 -4
  266. package/dist/controllers/sortable_controller.js +26 -125
  267. package/dist/controllers/sortable_controller.js.map +1 -1
  268. package/dist/controllers/spinner_controller.d.ts +39 -4
  269. package/dist/controllers/spinner_controller.js +288 -173
  270. package/dist/controllers/spinner_controller.js.map +1 -1
  271. package/dist/controllers/step_indicator_controller.d.ts +9 -4
  272. package/dist/controllers/step_indicator_controller.js +105 -46
  273. package/dist/controllers/step_indicator_controller.js.map +1 -1
  274. package/dist/controllers/stepper_controller.d.ts +14 -6
  275. package/dist/controllers/stepper_controller.js +120 -73
  276. package/dist/controllers/stepper_controller.js.map +1 -1
  277. package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
  278. package/dist/controllers/stick_to_bottom_controller.js +275 -140
  279. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  280. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  281. package/dist/controllers/sticky_observer_controller.js +220 -49
  282. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  283. package/dist/controllers/submit_once_controller.d.ts +24 -8
  284. package/dist/controllers/submit_once_controller.js +376 -227
  285. package/dist/controllers/submit_once_controller.js.map +1 -1
  286. package/dist/controllers/switch_controller.d.ts +18 -4
  287. package/dist/controllers/switch_controller.js +201 -84
  288. package/dist/controllers/switch_controller.js.map +1 -1
  289. package/dist/controllers/tabs_controller.d.ts +9 -6
  290. package/dist/controllers/tabs_controller.js +41 -30
  291. package/dist/controllers/tabs_controller.js.map +1 -1
  292. package/dist/controllers/tags_input_controller.d.ts +16 -3
  293. package/dist/controllers/tags_input_controller.js +293 -173
  294. package/dist/controllers/tags_input_controller.js.map +1 -1
  295. package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
  296. package/dist/controllers/textarea_autosize_controller.js +108 -74
  297. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  298. package/dist/controllers/theme_controller.d.ts +24 -11
  299. package/dist/controllers/theme_controller.js +140 -116
  300. package/dist/controllers/theme_controller.js.map +1 -1
  301. package/dist/controllers/time_picker_controller.d.ts +55 -5
  302. package/dist/controllers/time_picker_controller.js +331 -127
  303. package/dist/controllers/time_picker_controller.js.map +1 -1
  304. package/dist/controllers/toast_controller.d.ts +40 -16
  305. package/dist/controllers/toast_controller.js +244 -324
  306. package/dist/controllers/toast_controller.js.map +1 -1
  307. package/dist/controllers/toggle_group_controller.d.ts +14 -6
  308. package/dist/controllers/toggle_group_controller.js +252 -191
  309. package/dist/controllers/toggle_group_controller.js.map +1 -1
  310. package/dist/controllers/toolbar_controller.d.ts +8 -6
  311. package/dist/controllers/toolbar_controller.js +219 -120
  312. package/dist/controllers/toolbar_controller.js.map +1 -1
  313. package/dist/controllers/tooltip_controller.d.ts +49 -4
  314. package/dist/controllers/tooltip_controller.js +158 -124
  315. package/dist/controllers/tooltip_controller.js.map +1 -1
  316. package/dist/controllers/transition_controller.d.ts +22 -4
  317. package/dist/controllers/transition_controller.js +123 -119
  318. package/dist/controllers/transition_controller.js.map +1 -1
  319. package/dist/controllers/tree_view_controller.d.ts +22 -12
  320. package/dist/controllers/tree_view_controller.js +283 -369
  321. package/dist/controllers/tree_view_controller.js.map +1 -1
  322. package/dist/index.d.ts +10 -5
  323. package/dist/index.js +8573 -10410
  324. package/dist/index.js.map +1 -1
  325. package/dist/inspector/cli.d.ts +38 -14
  326. package/dist/inspector/cli.js +108 -40
  327. package/dist/inspector/cli.js.map +1 -1
  328. package/dist/inspector/cli_bin.js +145 -64
  329. package/dist/inspector/cli_bin.js.map +1 -1
  330. package/dist/inspector/examples.json +20 -20
  331. package/dist/inspector/manifest.json +802 -101
  332. package/dist/positioning/index.d.ts +15 -7
  333. package/dist/positioning/index.js +154 -47
  334. package/dist/positioning/index.js.map +1 -1
  335. package/package.json +11 -10
  336. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  337. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,6 +1,75 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/filter_controller.ts
3
+ var ListenerSet = class {
4
+ #abort = new AbortController();
5
+ add(target, type, handler, options) {
6
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
7
+ }
8
+ dispose() {
9
+ this.#abort.abort();
10
+ this.#abort = new AbortController();
11
+ }
12
+ };
13
+
14
+ var MicrotaskCoalescer = class {
15
+ #run;
16
+ #queued = false;
17
+ #active = false;
18
+ #generation = 0;
19
+ constructor(run) {
20
+ this.#run = run;
21
+ }
22
+ activate() {
23
+ this.#active = true;
24
+ }
25
+ cancel() {
26
+ this.#active = false;
27
+ this.#queued = false;
28
+ this.#generation += 1;
29
+ }
30
+ get pending() {
31
+ return this.#queued;
32
+ }
33
+ schedule() {
34
+ if (!this.#active || this.#queued) return;
35
+ this.#queued = true;
36
+ const generation = this.#generation;
37
+ queueMicrotask(() => {
38
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
39
+ this.#queued = false;
40
+ this.#run();
41
+ });
42
+ }
43
+ };
44
+
45
+ var MorphRenderWatcher = class {
46
+ #listeners = new ListenerSet();
47
+ #pass;
48
+ #element = null;
49
+ #onMorph = () => this.#pass.schedule();
50
+ constructor(run) {
51
+ this.#pass = new MicrotaskCoalescer(run);
52
+ }
53
+ observe(element) {
54
+ if (this.#element === element) return;
55
+ this.disconnect();
56
+ this.#element = element;
57
+ this.#pass.activate();
58
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
59
+ }
60
+ schedule() {
61
+ this.#pass.schedule();
62
+ }
63
+ get pending() {
64
+ return this.#pass.pending;
65
+ }
66
+ disconnect() {
67
+ this.#listeners.dispose();
68
+ this.#pass.cancel();
69
+ this.#element = null;
70
+ }
71
+ };
72
+
4
73
  var FilterController = class extends Controller {
5
74
  static targets = ["item", "control", "group", "empty"];
6
75
  static values = { match: { type: String, default: "all" } };
@@ -9,55 +78,29 @@ var FilterController = class extends Controller {
9
78
  #onChange = () => {
10
79
  this.apply();
11
80
  };
12
- /**
13
- * The outcome the last `change` reported, which the next evaluation is compared
14
- * with; `null` until the first evaluation after `connect()`, which reports
15
- * whatever it finds.
16
- */
17
81
  #reported = null;
18
- /**
19
- * Gates the declaration callback to the connected window.
20
- *
21
- * Stimulus delivers a Value callback ahead of `connect()` and again for every
22
- * runtime change; without the gate, merely connecting would emit an evaluation
23
- * — and its `change` — before `connect()` runs its own.
24
- */
25
82
  #connected = false;
83
+ #morphRender = new MorphRenderWatcher(() => this.#evaluate());
84
+ #listeners = new ListenerSet();
26
85
  connect() {
86
+ this.#morphRender.observe(this.element);
27
87
  this.#reported = null;
28
88
  this.#evaluate();
29
- this.element.addEventListener("change", this.#onChange);
89
+ this.#listeners.add(this.element, "change", this.#onChange);
30
90
  this.#connected = true;
31
91
  }
32
92
  disconnect() {
93
+ this.#morphRender.disconnect();
33
94
  this.#connected = false;
34
- this.element.removeEventListener("change", this.#onChange);
35
- }
36
- /**
37
- * Re-evaluates when the match declaration changes at runtime.
38
- *
39
- * The declaration decides which items are shown, so an element kept across a
40
- * morph — where `connect()` does not run again — still has to follow it instead
41
- * of waiting for the next control interaction. The evaluation is the ordinary
42
- * one, `change` included: a declaration swap is an evaluation like any other.
43
- */
95
+ this.#listeners.dispose();
96
+ }
44
97
  matchValueChanged() {
45
98
  if (!this.#connected) return;
46
99
  this.#evaluate();
47
100
  }
48
- /** Re-derives every item's visibility from the active tokens and syncs groups/empty. */
49
101
  apply() {
50
102
  this.#evaluate();
51
103
  }
52
- /**
53
- * The evaluation itself: every item's visibility from the active tokens, then
54
- * the groups and the empty element, then `change` when the outcome moved from
55
- * the one last reported. Several triggers can land on the same outcome — a
56
- * `clear` evaluates, and a toggle group wired to `apply` reports the presses it
57
- * turned off — so an evaluation that changes nothing reports nothing.
58
- *
59
- * @stimeoRenderRoot
60
- */
61
104
  #evaluate() {
62
105
  const active = this.#activeTokens();
63
106
  let visibleCount = 0;
@@ -80,7 +123,6 @@ var FilterController = class extends Controller {
80
123
  this.#reported = { active, visibleCount, total };
81
124
  this.dispatch("change", { detail: { active: [...active], visibleCount, total } });
82
125
  }
83
- /** Turns every control off (uncheck / aria-pressed="false") and re-applies. */
84
126
  clear() {
85
127
  for (const control of this.controlTargets) {
86
128
  if (control instanceof HTMLInputElement) {
@@ -91,7 +133,6 @@ var FilterController = class extends Controller {
91
133
  }
92
134
  this.apply();
93
135
  }
94
- /** The tokens of every control currently "on" (checked or aria-pressed="true"). */
95
136
  #activeTokens() {
96
137
  const tokens = [];
97
138
  for (const control of this.controlTargets) {
@@ -99,33 +140,27 @@ var FilterController = class extends Controller {
99
140
  }
100
141
  return tokens.filter(Boolean);
101
142
  }
102
- /** Whether a control is in its "on" state. */
103
143
  #isOn(control) {
104
144
  if (control instanceof HTMLInputElement) return control.checked;
105
145
  return control.getAttribute("aria-pressed") === "true";
106
146
  }
107
- /** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */
108
147
  #tokenOf(control) {
109
148
  const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;
110
149
  if (explicit) return explicit;
111
150
  return control instanceof HTMLInputElement ? control.value : "";
112
151
  }
113
- /** Whether an item satisfies the active token set per `match` (empty active → shown). */
114
152
  #matches(item, active) {
115
153
  if (active.length === 0) return true;
116
154
  const tokens = this.#tokensOf(item);
117
155
  return this.matchValue === "any" ? active.some((token) => tokens.includes(token)) : active.every((token) => tokens.includes(token));
118
156
  }
119
- /** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */
120
157
  #tokensOf(item) {
121
158
  return (item.getAttribute(`data-${this.identifier}-tokens`) ?? "").split(/\s+/).filter(Boolean);
122
159
  }
123
- /** Whether a group still contains at least one non-hidden item. */
124
160
  #hasVisibleItem(group) {
125
161
  return this.itemTargets.some((item) => !item.hidden && group.contains(item));
126
162
  }
127
163
  };
128
164
 
129
165
  export { FilterController };
130
- //# sourceMappingURL=filter_controller.js.map
131
166
  //# sourceMappingURL=filter_controller.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/controllers/filter_controller.ts"],"names":[],"mappings":"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","file":"filter_controller.js","sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\n\n/** A control whose \"on\" state contributes its token to the active filter set. */\ntype FilterControl = HTMLElement;\n\n/** What an evaluation settles on and `change` reports. */\ninterface FilterOutcome {\n readonly active: readonly string[];\n readonly visibleCount: number;\n readonly total: number;\n}\n\n/**\n * Headless faceted-filter behavior: shows or hides a collection of items based on the\n * set of currently-active facet tokens, decoupled from how those tokens are toggled\n * (native checkboxes/radios, or button toggles such as `stimeo--toggle-group`).\n *\n * Markup contract (identifier: `stimeo--filter`):\n * <div data-controller=\"stimeo--filter\" data-stimeo--filter-match-value=\"all\">\n * <!-- controls: each contributes its token while \"on\" (aria-pressed / checked) -->\n * <button type=\"button\" aria-pressed=\"false\" data-value=\"apg:dialog\"\n * data-stimeo--filter-target=\"control\">Dialog</button>\n * <!-- items: filtered in/out by their space-separated tokens -->\n * <li data-stimeo--filter-target=\"item\"\n * data-stimeo--filter-tokens=\"apg:dialog overlay\">…</li>\n * <!-- optional: a container hidden once it holds no visible item -->\n * <section data-stimeo--filter-target=\"group\">…</section>\n * <!-- optional: an element revealed when nothing matches -->\n * <p data-stimeo--filter-target=\"empty\" hidden>No matches</p>\n * </div>\n *\n * `match` decides how multiple active tokens combine: `all` (default — an item must\n * carry every active token) or `any` (at least one). With no active token every item\n * is shown. Items are toggled via `hidden`; groups with zero visible items and the\n * `empty` element are kept in sync, and `stimeo--filter:change` reports the outcome\n * when it moves.\n *\n * `change` dispatches `{ active, visibleCount, total }` — the active tokens, how\n * many items they leave visible, and how many items there are — whenever an\n * evaluation lands on an outcome other than the one it last reported. The first\n * evaluation after `connect()` always reports.\n *\n * @remarks\n * Behavior only — the consumer owns all styling and the controls' own accessible\n * state (e.g. `aria-pressed`); the one exception is `clear`, which writes those\n * controls one way, to off. Visibility is re-derived from the live DOM on\n * `connect()`, on every native `change` that bubbles to the root, on the `apply`\n * and `clear` actions, and when `match` changes at runtime — the declaration\n * decides what is shown, so an element kept across a morph still follows it.\n * Button toggles that emit no native `change` wire their events to the `apply`\n * action: `stimeo--toggle-group:change->stimeo--filter#apply` for a press, and\n * `stimeo--toggle-group:reconcile->stimeo--filter#apply` for a pressed set the\n * page moves.\n */\nexport class FilterController extends Controller<HTMLElement> {\n static override targets = [\"item\", \"control\", \"group\", \"empty\"];\n static override values = { match: { type: String, default: \"all\" } };\n static actions = [\"apply\", \"clear\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly itemTargets: HTMLElement[];\n declare readonly controlTargets: FilterControl[];\n declare readonly groupTargets: HTMLElement[];\n declare readonly emptyTargets: HTMLElement[];\n\n declare matchValue: string;\n\n readonly #onChange = (): void => {\n this.apply();\n };\n\n /**\n * The outcome the last `change` reported, which the next evaluation is compared\n * with; `null` until the first evaluation after `connect()`, which reports\n * whatever it finds.\n */\n #reported: FilterOutcome | null = null;\n\n /**\n * Gates the declaration callback to the connected window.\n *\n * Stimulus delivers a Value callback ahead of `connect()` and again for every\n * runtime change; without the gate, merely connecting would emit an evaluation\n * — and its `change` — before `connect()` runs its own.\n */\n #connected = false;\n\n override connect(): void {\n this.#reported = null;\n this.#evaluate();\n this.element.addEventListener(\"change\", this.#onChange);\n this.#connected = true;\n }\n\n override disconnect(): void {\n this.#connected = false;\n this.element.removeEventListener(\"change\", this.#onChange);\n }\n\n /**\n * Re-evaluates when the match declaration changes at runtime.\n *\n * The declaration decides which items are shown, so an element kept across a\n * morph — where `connect()` does not run again — still has to follow it instead\n * of waiting for the next control interaction. The evaluation is the ordinary\n * one, `change` included: a declaration swap is an evaluation like any other.\n */\n matchValueChanged(): void {\n if (!this.#connected) return;\n this.#evaluate();\n }\n\n /** Re-derives every item's visibility from the active tokens and syncs groups/empty. */\n apply(): void {\n this.#evaluate();\n }\n\n /**\n * The evaluation itself: every item's visibility from the active tokens, then\n * the groups and the empty element, then `change` when the outcome moved from\n * the one last reported. Several triggers can land on the same outcome — a\n * `clear` evaluates, and a toggle group wired to `apply` reports the presses it\n * turned off — so an evaluation that changes nothing reports nothing.\n *\n * @stimeoRenderRoot\n */\n #evaluate(): void {\n const active = this.#activeTokens();\n let visibleCount = 0;\n for (const item of this.itemTargets) {\n const visible = this.#matches(item, active);\n item.hidden = !visible;\n if (visible) visibleCount += 1;\n }\n\n for (const group of this.groupTargets) {\n group.hidden = !this.#hasVisibleItem(group);\n }\n for (const empty of this.emptyTargets) {\n empty.hidden = visibleCount > 0;\n }\n\n const total = this.itemTargets.length;\n const reported = this.#reported;\n if (\n reported !== null &&\n reported.visibleCount === visibleCount &&\n reported.total === total &&\n reported.active.length === active.length &&\n reported.active.every((token, index) => token === active[index])\n ) {\n return;\n }\n this.#reported = { active, visibleCount, total };\n this.dispatch(\"change\", { detail: { active: [...active], visibleCount, total } });\n }\n\n /** Turns every control off (uncheck / aria-pressed=\"false\") and re-applies. */\n clear(): void {\n for (const control of this.controlTargets) {\n if (control instanceof HTMLInputElement) {\n control.checked = false;\n } else if (control.hasAttribute(\"aria-pressed\")) {\n control.setAttribute(\"aria-pressed\", \"false\");\n }\n }\n this.apply();\n }\n\n /** The tokens of every control currently \"on\" (checked or aria-pressed=\"true\"). */\n #activeTokens(): string[] {\n const tokens: string[] = [];\n for (const control of this.controlTargets) {\n if (this.#isOn(control)) tokens.push(this.#tokenOf(control));\n }\n return tokens.filter(Boolean);\n }\n\n /** Whether a control is in its \"on\" state. */\n #isOn(control: FilterControl): boolean {\n if (control instanceof HTMLInputElement) return control.checked;\n return control.getAttribute(\"aria-pressed\") === \"true\";\n }\n\n /** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */\n #tokenOf(control: FilterControl): string {\n const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;\n if (explicit) return explicit;\n return control instanceof HTMLInputElement ? control.value : \"\";\n }\n\n /** Whether an item satisfies the active token set per `match` (empty active → shown). */\n #matches(item: HTMLElement, active: string[]): boolean {\n if (active.length === 0) return true;\n const tokens = this.#tokensOf(item);\n return this.matchValue === \"any\"\n ? active.some((token) => tokens.includes(token))\n : active.every((token) => tokens.includes(token));\n }\n\n /** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */\n #tokensOf(item: HTMLElement): string[] {\n return (item.getAttribute(`data-${this.identifier}-tokens`) ?? \"\").split(/\\s+/).filter(Boolean);\n }\n\n /** Whether a group still contains at least one non-hidden item. */\n #hasVisibleItem(group: HTMLElement): boolean {\n return this.itemTargets.some((item) => !item.hidden && group.contains(item));\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/utils/listener_set.ts","../../src/utils/microtask_coalescer.ts","../../src/utils/morph_render_watcher.ts","../../src/controllers/filter_controller.ts"],"names":[],"mappings":";;AA8EO,IAAM,cAAN,MAAkB;AAAA,EAEvB,MAAA,GAAS,IAAI,eAAA,EAAgB;AAAA,EAqB7B,GAAA,CAAI,MAAA,EAAqB,IAAA,EAAc,OAAA,EAAwB,OAAA,EAAiC;AAC9F,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;;AChEO,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,IAAI,OAAA,GAAmB;AACrB,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;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;;AC7EO,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,IAAI,OAAA,GAAmB;AACrB,IAAA,OAAO,KAAK,KAAA,CAAM,OAAA;AAAA,EACpB;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;;ACQO,IAAM,gBAAA,GAAN,cAA+B,UAAA,CAAwB;AAAA,EAC5D,OAAgB,OAAA,GAAU,CAAC,MAAA,EAAQ,SAAA,EAAW,SAAS,OAAO,CAAA;AAAA,EAC9D,OAAgB,SAAS,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAM,EAAE;AAAA,EACnE,OAAO,OAAA,GAAU,CAAC,OAAA,EAAS,OAAO,CAAA;AAAA,EAClC,OAAO,MAAA,GAAS,CAAC,QAAQ,CAAA;AAAA,EAShB,YAAY,MAAY;AAC/B,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb,CAAA;AAAA,EAOA,SAAA,GAAkC,IAAA;AAAA,EASlC,UAAA,GAAa,KAAA;AAAA,EAEJ,eAAe,IAAI,kBAAA,CAAmB,MAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EAG5D,UAAA,GAAa,IAAI,WAAA,EAAY;AAAA,EAE7B,OAAA,GAAgB;AACvB,IAAA,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,IAAA,CAAK,OAAO,CAAA;AACtC,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AACjB,IAAA,IAAA,CAAK,SAAA,EAAU;AACf,IAAA,IAAA,CAAK,WAAW,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,QAAA,EAAU,KAAK,SAAS,CAAA;AAC1D,IAAA,IAAA,CAAK,UAAA,GAAa,IAAA;AAAA,EACpB;AAAA,EAES,UAAA,GAAmB;AAC1B,IAAA,IAAA,CAAK,aAAa,UAAA,EAAW;AAC7B,IAAA,IAAA,CAAK,UAAA,GAAa,KAAA;AAClB,IAAA,IAAA,CAAK,WAAW,OAAA,EAAQ;AAAA,EAC1B;AAAA,EAUA,iBAAA,GAA0B;AACxB,IAAA,IAAI,CAAC,KAAK,UAAA,EAAY;AACtB,IAAA,IAAA,CAAK,SAAA,EAAU;AAAA,EACjB;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,SAAA,EAAU;AAAA,EACjB;AAAA,EAWA,SAAA,GAAkB;AAChB,IAAA,MAAM,MAAA,GAAS,KAAK,aAAA,EAAc;AAClC,IAAA,IAAI,YAAA,GAAe,CAAA;AACnB,IAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,WAAA,EAAa;AACnC,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,QAAA,CAAS,IAAA,EAAM,MAAM,CAAA;AAC1C,MAAA,IAAA,CAAK,SAAS,CAAC,OAAA;AACf,MAAA,IAAI,SAAS,YAAA,IAAgB,CAAA;AAAA,IAC/B;AAEA,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,YAAA,EAAc;AACrC,MAAA,KAAA,CAAM,MAAA,GAAS,CAAC,IAAA,CAAK,eAAA,CAAgB,KAAK,CAAA;AAAA,IAC5C;AACA,IAAA,KAAA,MAAW,KAAA,IAAS,KAAK,YAAA,EAAc;AACrC,MAAA,KAAA,CAAM,SAAS,YAAA,GAAe,CAAA;AAAA,IAChC;AAEA,IAAA,MAAM,KAAA,GAAQ,KAAK,WAAA,CAAY,MAAA;AAC/B,IAAA,MAAM,WAAW,IAAA,CAAK,SAAA;AACtB,IAAA,IACE,QAAA,KAAa,QACb,QAAA,CAAS,YAAA,KAAiB,gBAC1B,QAAA,CAAS,KAAA,KAAU,KAAA,IACnB,QAAA,CAAS,MAAA,CAAO,MAAA,KAAW,OAAO,MAAA,IAClC,QAAA,CAAS,MAAA,CAAO,KAAA,CAAM,CAAC,KAAA,EAAO,UAAU,KAAA,KAAU,MAAA,CAAO,KAAK,CAAC,CAAA,EAC/D;AACA,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,SAAA,GAAY,EAAE,MAAA,EAAQ,YAAA,EAAc,KAAA,EAAM;AAC/C,IAAA,IAAA,CAAK,QAAA,CAAS,QAAA,EAAU,EAAE,MAAA,EAAQ,EAAE,MAAA,EAAQ,CAAC,GAAG,MAAM,CAAA,EAAG,YAAA,EAAc,KAAA,IAAS,CAAA;AAAA,EAClF;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,cAAA,EAAgB;AACzC,MAAA,IAAI,mBAAmB,gBAAA,EAAkB;AACvC,QAAA,OAAA,CAAQ,OAAA,GAAU,KAAA;AAAA,MACpB,CAAA,MAAA,IAAW,OAAA,CAAQ,YAAA,CAAa,cAAc,CAAA,EAAG;AAC/C,QAAA,OAAA,CAAQ,YAAA,CAAa,gBAAgB,OAAO,CAAA;AAAA,MAC9C;AAAA,IACF;AACA,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb;AAAA,EAGA,aAAA,GAA0B;AACxB,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,cAAA,EAAgB;AACzC,MAAA,IAAI,IAAA,CAAK,MAAM,OAAO,CAAA,SAAU,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,OAAO,CAAC,CAAA;AAAA,IAC7D;AACA,IAAA,OAAO,MAAA,CAAO,OAAO,OAAO,CAAA;AAAA,EAC9B;AAAA,EAGA,MAAM,OAAA,EAAiC;AACrC,IAAA,IAAI,OAAA,YAAmB,gBAAA,EAAkB,OAAO,OAAA,CAAQ,OAAA;AACxD,IAAA,OAAO,OAAA,CAAQ,YAAA,CAAa,cAAc,CAAA,KAAM,MAAA;AAAA,EAClD;AAAA,EAGA,SAAS,OAAA,EAAgC;AACvC,IAAA,MAAM,QAAA,GAAW,QAAQ,YAAA,CAAa,CAAA,KAAA,EAAQ,KAAK,UAAU,CAAA,MAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,OAAA,CAAQ,KAAA;AAC1F,IAAA,IAAI,UAAU,OAAO,QAAA;AACrB,IAAA,OAAO,OAAA,YAAmB,gBAAA,GAAmB,OAAA,CAAQ,KAAA,GAAQ,EAAA;AAAA,EAC/D;AAAA,EAGA,QAAA,CAAS,MAAmB,MAAA,EAA2B;AACrD,IAAA,IAAI,MAAA,CAAO,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAChC,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA;AAClC,IAAA,OAAO,KAAK,UAAA,KAAe,KAAA,GACvB,OAAO,IAAA,CAAK,CAAC,UAAU,MAAA,CAAO,QAAA,CAAS,KAAK,CAAC,CAAA,GAC7C,OAAO,KAAA,CAAM,CAAC,UAAU,MAAA,CAAO,QAAA,CAAS,KAAK,CAAC,CAAA;AAAA,EACpD;AAAA,EAGA,UAAU,IAAA,EAA6B;AACrC,IAAA,OAAA,CAAQ,IAAA,CAAK,YAAA,CAAa,CAAA,KAAA,EAAQ,IAAA,CAAK,UAAU,CAAA,OAAA,CAAS,CAAA,IAAK,EAAA,EAAI,KAAA,CAAM,KAAK,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA;AAAA,EAChG;AAAA,EAGA,gBAAgB,KAAA,EAA6B;AAC3C,IAAA,OAAO,IAAA,CAAK,WAAA,CAAY,IAAA,CAAK,CAAC,IAAA,KAAS,CAAC,IAAA,CAAK,MAAA,IAAU,KAAA,CAAM,QAAA,CAAS,IAAI,CAAC,CAAA;AAAA,EAC7E;AACF","file":"filter_controller.js","sourcesContent":["/** What {@link ListenerSet.add} passes on besides the signal it supplies itself. */\nexport type ListenerOptions = Omit<AddEventListenerOptions, \"signal\">;\n\n/**\n * The event map a target's own `addEventListener` types its handlers with: the\n * window's, a document's, an HTML or SVG element's, any other element's, or a\n * media query list's. Other targets name none.\n */\ntype EventMapOf<T extends EventTarget> = T extends Window\n ? WindowEventMap\n : T extends Document\n ? DocumentEventMap\n : T extends HTMLElement\n ? HTMLElementEventMap\n : T extends SVGElement\n ? SVGElementEventMap\n : T extends Element\n ? ElementEventMap\n : T extends MediaQueryList\n ? MediaQueryListEventMap\n : Record<never, never>;\n\n/**\n * Owns every listener a controller attaches for one connected lifetime and\n * releases them together, so an event name is spelled once and the release\n * cannot drift from the registration.\n *\n * Detaching by hand repeats three things at the release site — the type, the\n * handler reference and the capture flag — and any one of them drifting leaves\n * a listener alive on an element the controller no longer drives. Aborting the\n * signal a registration was created with releases it whatever it was made of,\n * so the release site names nothing.\n *\n * One set per lifetime, held as a field and never rebuilt.\n * {@link ListenerSet.dispose} is the only thing that closes a generation, and it opens\n * the next one in the same step, so the lifecycle hooks can call `add` and\n * `dispose` in any order any number of times, and no `add` ever meets an\n * already aborted signal — a DOM that registers a listener on such a signal\n * would keep it forever. Rebuilding the set instead would leak:\n * `addEventListener` matches an existing registration on target, type, callback\n * and capture alone, so re-adding the same handler while the first registration\n * is live is discarded, and that live registration keeps the signal it was\n * attached with — a set built by a second `connect()` can never release what it\n * appears to own.\n *\n * Scope is the registration only. Timers, observers, leases and the order they\n * are unwound in stay with the controller, which places `dispose()` in its\n * cleanup sequence. A receiver that changes at runtime (a\n * swapped target) is rebound by calling `dispose()` and then `add()` again from\n * the rebind, not by a second set.\n *\n * A lifetime shorter than the connection — while a Value holds, until a first\n * load, while a subscription is open — gets a set of its own, which the end of\n * that lifetime disposes. A controller therefore holds one set per lifetime: the\n * connection's, and one for each swapped receiver or shorter span.\n *\n * The set must own every registration of a tuple it holds: a listener the same\n * target already carries for the same type, callback and capture flag keeps the\n * right to remove it, and this set's `add` for that tuple is discarded. A\n * subscription shared between instances behind a participant count, listeners of\n * several elements that come and go one at a time, and those one gesture holds\n * keep their own pair of calls or a signal of their own.\n *\n * @example\n * ```ts\n * readonly #listeners = new ListenerSet();\n *\n * connect(): void {\n * this.#listeners.add(this.element, \"submit\", this.#onNativeSubmit, { capture: true });\n * this.#listeners.add(this.element, \"turbo:submit-start\", this.#onSubmitStart);\n * this.#listeners.add(this.element, \"turbo:submit-end\", this.#onSubmitEnd);\n * }\n *\n * disconnect(): void {\n * this.#listeners.dispose();\n * }\n * ```\n */\nexport class ListenerSet {\n /** The generation every `add` joins until the next `dispose()`. */\n #abort = new AbortController();\n\n /**\n * Attaches `handler` to the open generation, exactly as the caller spelled it.\n *\n * The set supplies the signal, so `options` carries everything else the DOM\n * accepts — `capture` included. A removal by hand would have to repeat that\n * flag; aborting the signal releases the listener without it.\n *\n * A `type` the target's own event map names types `handler` with that event,\n * as the target's `addEventListener` does, so a handler written for a\n * `KeyboardEvent` is passed as it is; any other `type` takes an\n * `EventListener`.\n */\n add<T extends EventTarget, K extends keyof EventMapOf<T> & string>(\n target: T,\n type: K,\n handler: (event: EventMapOf<T>[K]) => void,\n options?: ListenerOptions,\n ): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void;\n add(target: EventTarget, type: string, handler: EventListener, options?: ListenerOptions): void {\n target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });\n }\n\n /**\n * Releases every listener of the open generation, synchronously, and opens the\n * next one. Idempotent, and safe before anything has been added.\n */\n dispose(): void {\n this.#abort.abort();\n this.#abort = new AbortController();\n }\n}\n","/**\n * Collapses many Stimulus lifecycle callbacks from one DOM mutation into a\n * single pass.\n *\n * Stimulus fires `<name>TargetConnected` / `Disconnected` once per element and\n * `<name>ValueChanged` once per changed attribute. Replacing a list of N options\n * or morphing several render Values therefore delivers N callbacks — but the\n * useful unit of work is \"reconcile against the resulting declarative input\",\n * once, after the batch has settled. Every controller with reconcilable targets\n * or render Values needs the same shape: a `queued` flag plus `queueMicrotask`.\n *\n * **A microtask is the right horizon, and the reason is specific.** Stimulus\n * drives these callbacks from a `MutationObserver`, whose own callback already\n * runs as a microtask with the whole batch in hand; scheduling one more lands\n * after the last sibling callback of that batch and still before paint or any\n * event handler. A timer would be later than it needs to be, and reconciling\n * synchronously would run once per element against a half-applied DOM.\n *\n * **The two guards are not the same guard.** Scheduling is refused before the\n * controller connects, and running is refused after it disconnects:\n *\n * - **Before `connect()`** — Stimulus delivers initial target and Value callbacks\n * ahead of `connect()`. Reconciling there would compute output against a\n * controller whose own state has not been initialised, and `connect()` is\n * about to do a full pass anyway.\n * - **After `disconnect()`** — Stimulus fires a callback for **every** target\n * during teardown, and a microtask queued just before it would otherwise run\n * against a detached tree. {@link MicrotaskCoalescer.cancel} exists for the\n * teardown path to drop the pending pass outright.\n *\n * Both guards are part of one contract here rather than something each consumer\n * has to remember separately.\n *\n * Scope is the scheduling only. *What* to reconcile — keep the surviving active\n * option, fall back to the next / previous / first visible one, rebuild derived\n * chips or hidden fields — stays in the controller, because no two consumers\n * answer it the same way.\n *\n * @example\n * ```ts\n * readonly #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());\n *\n * connect() { this.#reconcile.activate(); }\n * disconnect() { this.#reconcile.cancel(); }\n *\n * optionTargetConnected() { this.#reconcile.schedule(); }\n * optionTargetDisconnected() { this.#reconcile.schedule(); }\n * ```\n */\nexport class MicrotaskCoalescer {\n readonly #run: () => void;\n #queued = false;\n #active = false;\n #generation = 0;\n\n /** @param run - the single reconciliation pass, invoked at most once per batch. */\n constructor(run: () => void) {\n this.#run = run;\n }\n\n /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */\n activate(): void {\n this.#active = true;\n }\n\n /** Closes the window and drops any pending pass; call from `disconnect()`. */\n cancel(): void {\n this.#active = false;\n this.#queued = false;\n this.#generation += 1;\n }\n\n /** Whether a requested pass has yet to run. */\n get pending(): boolean {\n return this.#queued;\n }\n\n /** Requests one pass after the batch settles. Idempotent; inert outside the window. */\n schedule(): void {\n if (!this.#active || this.#queued) return;\n this.#queued = true;\n const generation = this.#generation;\n queueMicrotask(() => {\n // A cancelled callback must not consume a pass queued after reconnect.\n if (generation !== this.#generation || !this.#queued || !this.#active) return;\n this.#queued = false;\n this.#run();\n });\n }\n}\n","import { ListenerSet } from \"./listener_set\";\nimport { MicrotaskCoalescer } from \"./microtask_coalescer\";\n\n/**\n * Coalesces retained-element morphs and explicit lifecycle requests into one pass.\n *\n * Observe the controller's element after its initial state is settled. Morphs on\n * that element and bubbling morphs on its descendants share the same microtask\n * as {@link schedule}. The callback repairs derived output; the controller owns\n * any logical transition reporting. {@link disconnect} releases the listener and\n * cancels pending work, including work from an earlier connected lifetime.\n */\nexport class MorphRenderWatcher {\n readonly #listeners = new ListenerSet();\n readonly #pass: MicrotaskCoalescer;\n #element: Element | null = null;\n readonly #onMorph = (): void => this.#pass.schedule();\n\n /** @param run - the single pass that repairs the settled controller's output. */\n constructor(run: () => void) {\n this.#pass = new MicrotaskCoalescer(run);\n }\n\n /** Opens observation; observing the same element again preserves queued work. */\n observe(element: Element): void {\n if (this.#element === element) return;\n this.disconnect();\n this.#element = element;\n this.#pass.activate();\n this.#listeners.add(element, \"turbo:morph-element\", this.#onMorph);\n }\n\n /** Requests one pass; requests outside an observed lifetime are ignored. */\n schedule(): void {\n this.#pass.schedule();\n }\n\n /** Whether a pass requested by a morph or {@link schedule} has yet to run. */\n get pending(): boolean {\n return this.#pass.pending;\n }\n\n /** Releases observation and pending work synchronously; safe to repeat. */\n disconnect(): void {\n this.#listeners.dispose();\n this.#pass.cancel();\n this.#element = null;\n }\n}\n","import { Controller } from \"@hotwired/stimulus\";\nimport { ListenerSet } from \"../utils/listener_set\";\nimport { MorphRenderWatcher } from \"../utils/morph_render_watcher\";\n\n/** A control whose \"on\" state contributes its token to the active filter set. */\ntype FilterControl = HTMLElement;\n\n/** What an evaluation settles on and `change` reports. */\ninterface FilterOutcome {\n readonly active: readonly string[];\n readonly visibleCount: number;\n readonly total: number;\n}\n\n/**\n * Headless faceted-filter behavior: shows or hides a collection of items based on the\n * set of currently-active facet tokens, decoupled from how those tokens are toggled\n * (native checkboxes/radios, or button toggles such as `stimeo--toggle-group`).\n *\n * Markup contract (identifier: `stimeo--filter`):\n * <div data-controller=\"stimeo--filter\" data-stimeo--filter-match-value=\"all\">\n * <!-- controls: each contributes its token while \"on\" (aria-pressed / checked) -->\n * <button type=\"button\" aria-pressed=\"false\" data-value=\"apg:dialog\"\n * data-stimeo--filter-target=\"control\">Dialog</button>\n * <!-- items: filtered in/out by their space-separated tokens -->\n * <li data-stimeo--filter-target=\"item\"\n * data-stimeo--filter-tokens=\"apg:dialog overlay\">…</li>\n * <!-- optional: a container hidden once it holds no visible item -->\n * <section data-stimeo--filter-target=\"group\">…</section>\n * <!-- optional: an element revealed when nothing matches -->\n * <p data-stimeo--filter-target=\"empty\" hidden>No matches</p>\n * </div>\n *\n * `match` decides how multiple active tokens combine: `all` (default — an item must\n * carry every active token) or `any` (at least one). With no active token every item\n * is shown. Items are toggled via `hidden`; groups with zero visible items and the\n * `empty` element are kept in sync, and `stimeo--filter:change` reports the outcome\n * when it moves.\n *\n * `change` dispatches `{ active, visibleCount, total }` — the active tokens, how\n * many items they leave visible, and how many items there are — whenever an\n * evaluation lands on an outcome other than the one it last reported. The first\n * evaluation after `connect()` always reports.\n *\n * @remarks\n * Behavior only — the consumer owns all styling and the controls' own accessible\n * state (e.g. `aria-pressed`); the one exception is `clear`, which writes those\n * controls one way, to off. Visibility is re-derived from the live DOM on\n * `connect()`, on every native `change` that bubbles to the root, on the `apply`\n * and `clear` actions, and when `match` changes at runtime — the declaration\n * decides what is shown, so an element kept across a morph still follows it.\n * Button toggles that emit no native `change` wire their events to the `apply`\n * action: `stimeo--toggle-group:change->stimeo--filter#apply` for a press, and\n * `stimeo--toggle-group:reconcile->stimeo--filter#apply` for a pressed set the\n * page moves.\n */\nexport class FilterController extends Controller<HTMLElement> {\n static override targets = [\"item\", \"control\", \"group\", \"empty\"];\n static override values = { match: { type: String, default: \"all\" } };\n static actions = [\"apply\", \"clear\"] as const;\n static events = [\"change\"] as const;\n\n declare readonly itemTargets: HTMLElement[];\n declare readonly controlTargets: FilterControl[];\n declare readonly groupTargets: HTMLElement[];\n declare readonly emptyTargets: HTMLElement[];\n\n declare matchValue: string;\n\n readonly #onChange = (): void => {\n this.apply();\n };\n\n /**\n * The outcome the last `change` reported, which the next evaluation is compared\n * with; `null` until the first evaluation after `connect()`, which reports\n * whatever it finds.\n */\n #reported: FilterOutcome | null = null;\n\n /**\n * Gates the declaration callback to the connected window.\n *\n * Stimulus delivers a Value callback ahead of `connect()` and again for every\n * runtime change; without the gate, merely connecting would emit an evaluation\n * — and its `change` — before `connect()` runs its own.\n */\n #connected = false;\n\n readonly #morphRender = new MorphRenderWatcher(() => this.#evaluate());\n\n /** The listeners of one connection, released together on `disconnect()`. */\n readonly #listeners = new ListenerSet();\n\n override connect(): void {\n this.#morphRender.observe(this.element);\n this.#reported = null;\n this.#evaluate();\n this.#listeners.add(this.element, \"change\", this.#onChange);\n this.#connected = true;\n }\n\n override disconnect(): void {\n this.#morphRender.disconnect();\n this.#connected = false;\n this.#listeners.dispose();\n }\n\n /**\n * Re-evaluates when the match declaration changes at runtime.\n *\n * The declaration decides which items are shown, so an element kept across a\n * morph — where `connect()` does not run again — still has to follow it instead\n * of waiting for the next control interaction. The evaluation is the ordinary\n * one, `change` included: a declaration swap is an evaluation like any other.\n */\n matchValueChanged(): void {\n if (!this.#connected) return;\n this.#evaluate();\n }\n\n /** Re-derives every item's visibility from the active tokens and syncs groups/empty. */\n apply(): void {\n this.#evaluate();\n }\n\n /**\n * The evaluation itself: every item's visibility from the active tokens, then\n * the groups and the empty element, then `change` when the outcome moved from\n * the one last reported. Several triggers can land on the same outcome — a\n * `clear` evaluates, and a toggle group wired to `apply` reports the presses it\n * turned off — so an evaluation that changes nothing reports nothing.\n *\n * @stimeoRenderRoot\n */\n #evaluate(): void {\n const active = this.#activeTokens();\n let visibleCount = 0;\n for (const item of this.itemTargets) {\n const visible = this.#matches(item, active);\n item.hidden = !visible;\n if (visible) visibleCount += 1;\n }\n\n for (const group of this.groupTargets) {\n group.hidden = !this.#hasVisibleItem(group);\n }\n for (const empty of this.emptyTargets) {\n empty.hidden = visibleCount > 0;\n }\n\n const total = this.itemTargets.length;\n const reported = this.#reported;\n if (\n reported !== null &&\n reported.visibleCount === visibleCount &&\n reported.total === total &&\n reported.active.length === active.length &&\n reported.active.every((token, index) => token === active[index])\n ) {\n return;\n }\n this.#reported = { active, visibleCount, total };\n this.dispatch(\"change\", { detail: { active: [...active], visibleCount, total } });\n }\n\n /** Turns every control off (uncheck / aria-pressed=\"false\") and re-applies. */\n clear(): void {\n for (const control of this.controlTargets) {\n if (control instanceof HTMLInputElement) {\n control.checked = false;\n } else if (control.hasAttribute(\"aria-pressed\")) {\n control.setAttribute(\"aria-pressed\", \"false\");\n }\n }\n this.apply();\n }\n\n /** The tokens of every control currently \"on\" (checked or aria-pressed=\"true\"). */\n #activeTokens(): string[] {\n const tokens: string[] = [];\n for (const control of this.controlTargets) {\n if (this.#isOn(control)) tokens.push(this.#tokenOf(control));\n }\n return tokens.filter(Boolean);\n }\n\n /** Whether a control is in its \"on\" state. */\n #isOn(control: FilterControl): boolean {\n if (control instanceof HTMLInputElement) return control.checked;\n return control.getAttribute(\"aria-pressed\") === \"true\";\n }\n\n /** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */\n #tokenOf(control: FilterControl): string {\n const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;\n if (explicit) return explicit;\n return control instanceof HTMLInputElement ? control.value : \"\";\n }\n\n /** Whether an item satisfies the active token set per `match` (empty active → shown). */\n #matches(item: HTMLElement, active: string[]): boolean {\n if (active.length === 0) return true;\n const tokens = this.#tokensOf(item);\n return this.matchValue === \"any\"\n ? active.some((token) => tokens.includes(token))\n : active.every((token) => tokens.includes(token));\n }\n\n /** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */\n #tokensOf(item: HTMLElement): string[] {\n return (item.getAttribute(`data-${this.identifier}-tokens`) ?? \"\").split(/\\s+/).filter(Boolean);\n }\n\n /** Whether a group still contains at least one non-hidden item. */\n #hasVisibleItem(group: HTMLElement): boolean {\n return this.itemTargets.some((item) => !item.hidden && group.contains(item));\n }\n}\n"]}
@@ -4,9 +4,12 @@ import { Controller } from '@hotwired/stimulus';
4
4
  * Headless **Rails flash bridge**: turns server-rendered (and Turbo Stream-inserted)
5
5
  * `flash` elements into live-region announcements with auto-dismiss and a stacking
6
6
  * cap (no dedicated APG pattern; follows the WAI-ARIA status/alert guidance and WCAG
7
- * 2.2 **4.1.3 Status Messages**). The general-purpose sibling is Toast; this one is
7
+ * 2.2 **4.1.3 Status Messages**). The general-purpose sibling is `stimeo--toast`; this one is
8
8
  * specialized to the Rails `flash` convention.
9
9
  *
10
+ * Capacity evicts by arrival order, independent of DOM order. The same controller
11
+ * instance preserves that order across reconnects; a new instance uses DOM order.
12
+ *
10
13
  * Markup contract (identifier: `stimeo--flash`):
11
14
  * <div data-controller="stimeo--flash" data-stimeo--flash-target="region">
12
15
  * <!-- server-rendered or Turbo Stream-inserted -->
@@ -27,20 +30,22 @@ import { Controller } from '@hotwired/stimulus';
27
30
  * those holds is released.
28
31
  * A close button wired to the `dismiss` action removes one manually.
29
32
  *
30
- * `dismiss` dispatches `{ element, reason }`.
33
+ * `dismiss` dispatches `{ element, reason: StateReason }`.
31
34
  *
32
- * `show` dispatches `{ type, message }`.
33
- * `reconcile` dispatches `{ removed: number }` — the messages the Turbo cache
34
- * rewind took out, which no `dismiss` will report because nobody dismissed them.
35
+ * `show` dispatches `{ type, message }`, once per message: a reconnection of the same
36
+ * instance — an in-page move, a `data-turbo-permanent` region carried to the next page —
37
+ * takes its messages on again without showing or announcing them a second time.
38
+ * `reconcile` dispatches `{ removed: number }` — the messages a page restored from the
39
+ * Turbo cache still carried, which no `dismiss` will report because nobody dismissed them.
35
40
  *
36
41
  * @remarks
37
- * Reading is **delegated to the shared Announcer** — but only for the *initial*,
42
+ * Reading is **delegated to `stimeo--announcer`** — but only for the *initial*,
38
43
  * page-loaded messages: an in-place live region present at load is not announced by
39
44
  * assistive tech, so the controller bridges those via an `stimeo--announcer:announce`
40
45
  * event. Messages inserted *later* (Turbo Stream) are announced by their own freshly
41
- * inserted `role`, exactly like Toast, so they are not bridged again (no double
46
+ * inserted `role`, exactly like `stimeo--toast`, so they are not bridged again (no double
42
47
  * announcement). Behavior only — no styling; `data-flash-state="leaving"` lets CSS
43
- * animate removal. Focus is never moved (WCAG 2.2 4.1.3): neither a pause nor the `max`
48
+ * animate removal. Focus is never moved: neither a pause nor the `max`
44
49
  * cap removes a message the pointer is over or focus is inside, so neither takes away
45
50
  * the control under the pointer or holding focus; with `pauseOnHover` off the
46
51
  * auto-dismiss still runs out under them. The observation follows a
@@ -48,8 +53,16 @@ import { Controller } from '@hotwired/stimulus';
48
53
  * subtree, and a message that leaves it gives up its stacking slot, its pending
49
54
  * auto-dismiss, and any removal already scheduled. The observer, timers,
50
55
  * and per-message listeners are torn down on `disconnect()` (Turbo navigation
51
- * included), and the managed flashes leave the page before Turbo caches it so a
52
- * restored snapshot does not replay a notification the visitor already received.
56
+ * included).
57
+ *
58
+ * **A restored page does not replay a notification.** Every message taken on carries
59
+ * `data-<identifier>-shown`. A page Turbo restores from its cache is a copy taken while
60
+ * its messages were on screen, so a connection that finds marked messages it never took
61
+ * on itself removes them, silently, before it takes anything else on, and reports how
62
+ * many as `reconcile`. Server-rendered messages carry no mark and are announced as
63
+ * usual. Nothing is removed on `turbo:before-cache`, which Turbo also dispatches on
64
+ * pages that stay — a promoted frame navigation, a state-less `popstate`, a refresh of
65
+ * a cached URL, a permanent region — where the message is still being read.
53
66
  */
54
67
  declare class FlashController extends Controller<HTMLElement> {
55
68
  #private;
@@ -68,6 +81,16 @@ declare class FlashController extends Controller<HTMLElement> {
68
81
  default: number;
69
82
  };
70
83
  };
84
+ static valueConstraints: {
85
+ duration: {
86
+ readonly finite: true;
87
+ readonly min: 0;
88
+ readonly max: 2147483647;
89
+ };
90
+ max: {
91
+ readonly finite: true;
92
+ };
93
+ };
71
94
  static actions: readonly ["dismiss"];
72
95
  static events: readonly ["show", "dismiss", "reconcile"];
73
96
  readonly regionTarget: HTMLElement;
@@ -102,8 +125,8 @@ declare class FlashController extends Controller<HTMLElement> {
102
125
  * ended a hold, so those are read again.
103
126
  */
104
127
  messageTargetDisconnected(message: HTMLElement): void;
105
- /** Dismisses the flash whose close control fired the event. */
106
- dismiss(event: Event): void;
128
+ /** Dismisses an owned message or descendant, or the message a DOM action operated. */
129
+ dismiss(input: Event | HTMLElement): void;
107
130
  }
108
131
 
109
132
  export { FlashController };