stimeo-ui 0.16.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 (328) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +8 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +363 -436
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +4 -2
  7. package/dist/controllers/accordion_controller.js +166 -55
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  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 +35 -5
  13. package/dist/controllers/announcer_controller.js +135 -198
  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 +271 -127
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +171 -105
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.js +0 -130
  23. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  24. package/dist/controllers/bulk_select_controller.d.ts +18 -0
  25. package/dist/controllers/bulk_select_controller.js +262 -52
  26. package/dist/controllers/bulk_select_controller.js.map +1 -1
  27. package/dist/controllers/calendar_controller.d.ts +24 -5
  28. package/dist/controllers/calendar_controller.js +175 -209
  29. package/dist/controllers/calendar_controller.js.map +1 -1
  30. package/dist/controllers/carousel_controller.d.ts +34 -7
  31. package/dist/controllers/carousel_controller.js +461 -399
  32. package/dist/controllers/carousel_controller.js.map +1 -1
  33. package/dist/controllers/character_counter_controller.d.ts +17 -1
  34. package/dist/controllers/character_counter_controller.js +246 -156
  35. package/dist/controllers/character_counter_controller.js.map +1 -1
  36. package/dist/controllers/checkbox_controller.d.ts +7 -2
  37. package/dist/controllers/checkbox_controller.js +32 -74
  38. package/dist/controllers/checkbox_controller.js.map +1 -1
  39. package/dist/controllers/clipboard_controller.d.ts +16 -5
  40. package/dist/controllers/clipboard_controller.js +168 -147
  41. package/dist/controllers/clipboard_controller.js.map +1 -1
  42. package/dist/controllers/collapsible_controller.d.ts +16 -4
  43. package/dist/controllers/collapsible_controller.js +301 -120
  44. package/dist/controllers/collapsible_controller.js.map +1 -1
  45. package/dist/controllers/color_picker_controller.d.ts +18 -6
  46. package/dist/controllers/color_picker_controller.js +149 -139
  47. package/dist/controllers/color_picker_controller.js.map +1 -1
  48. package/dist/controllers/combobox_controller.d.ts +23 -5
  49. package/dist/controllers/combobox_controller.js +360 -133
  50. package/dist/controllers/combobox_controller.js.map +1 -1
  51. package/dist/controllers/command_palette_controller.d.ts +45 -14
  52. package/dist/controllers/command_palette_controller.js +970 -358
  53. package/dist/controllers/command_palette_controller.js.map +1 -1
  54. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  55. package/dist/controllers/conditional_fields_controller.js +166 -124
  56. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  57. package/dist/controllers/confirm_controller.d.ts +49 -12
  58. package/dist/controllers/confirm_controller.js +939 -248
  59. package/dist/controllers/confirm_controller.js.map +1 -1
  60. package/dist/controllers/context_menu_controller.d.ts +31 -4
  61. package/dist/controllers/context_menu_controller.js +256 -128
  62. package/dist/controllers/context_menu_controller.js.map +1 -1
  63. package/dist/controllers/count_up_controller.d.ts +13 -1
  64. package/dist/controllers/count_up_controller.js +73 -21
  65. package/dist/controllers/count_up_controller.js.map +1 -1
  66. package/dist/controllers/countdown_controller.d.ts +45 -1
  67. package/dist/controllers/countdown_controller.js +183 -148
  68. package/dist/controllers/countdown_controller.js.map +1 -1
  69. package/dist/controllers/currency_input_controller.d.ts +21 -2
  70. package/dist/controllers/currency_input_controller.js +134 -158
  71. package/dist/controllers/currency_input_controller.js.map +1 -1
  72. package/dist/controllers/data_grid_controller.d.ts +15 -4
  73. package/dist/controllers/data_grid_controller.js +114 -186
  74. package/dist/controllers/data_grid_controller.js.map +1 -1
  75. package/dist/controllers/date_range_picker_controller.d.ts +30 -5
  76. package/dist/controllers/date_range_picker_controller.js +288 -206
  77. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  78. package/dist/controllers/dialog_controller.d.ts +13 -1
  79. package/dist/controllers/dialog_controller.js +858 -223
  80. package/dist/controllers/dialog_controller.js.map +1 -1
  81. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  82. package/dist/controllers/direct_upload_controller.js +73 -153
  83. package/dist/controllers/direct_upload_controller.js.map +1 -1
  84. package/dist/controllers/dirty_form_controller.js +14 -48
  85. package/dist/controllers/dirty_form_controller.js.map +1 -1
  86. package/dist/controllers/dismissible_controller.d.ts +19 -0
  87. package/dist/controllers/dismissible_controller.js +175 -21
  88. package/dist/controllers/dismissible_controller.js.map +1 -1
  89. package/dist/controllers/drawer_controller.d.ts +39 -11
  90. package/dist/controllers/drawer_controller.js +933 -341
  91. package/dist/controllers/drawer_controller.js.map +1 -1
  92. package/dist/controllers/dropdown_controller.d.ts +31 -1
  93. package/dist/controllers/dropdown_controller.js +312 -82
  94. package/dist/controllers/dropdown_controller.js.map +1 -1
  95. package/dist/controllers/editable_controller.d.ts +12 -1
  96. package/dist/controllers/editable_controller.js +158 -68
  97. package/dist/controllers/editable_controller.js.map +1 -1
  98. package/dist/controllers/empty_state_controller.js +67 -53
  99. package/dist/controllers/empty_state_controller.js.map +1 -1
  100. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  101. package/dist/controllers/file_dropzone_controller.js +311 -207
  102. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  103. package/dist/controllers/filter_controller.js +67 -39
  104. package/dist/controllers/filter_controller.js.map +1 -1
  105. package/dist/controllers/flash_controller.d.ts +31 -8
  106. package/dist/controllers/flash_controller.js +172 -314
  107. package/dist/controllers/flash_controller.js.map +1 -1
  108. package/dist/controllers/focus_controller.d.ts +12 -3
  109. package/dist/controllers/focus_controller.js +654 -240
  110. package/dist/controllers/focus_controller.js.map +1 -1
  111. package/dist/controllers/form_field_controller.js +147 -132
  112. package/dist/controllers/form_field_controller.js.map +1 -1
  113. package/dist/controllers/form_validation_controller.js +13 -86
  114. package/dist/controllers/form_validation_controller.js.map +1 -1
  115. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  116. package/dist/controllers/frame_loading_controller.js +264 -213
  117. package/dist/controllers/frame_loading_controller.js.map +1 -1
  118. package/dist/controllers/highlight_controller.d.ts +22 -2
  119. package/dist/controllers/highlight_controller.js +107 -75
  120. package/dist/controllers/highlight_controller.js.map +1 -1
  121. package/dist/controllers/hover_card_controller.d.ts +56 -3
  122. package/dist/controllers/hover_card_controller.js +309 -118
  123. package/dist/controllers/hover_card_controller.js.map +1 -1
  124. package/dist/controllers/idle_controller.d.ts +42 -1
  125. package/dist/controllers/idle_controller.js +267 -101
  126. package/dist/controllers/idle_controller.js.map +1 -1
  127. package/dist/controllers/input_mask_controller.js +71 -72
  128. package/dist/controllers/input_mask_controller.js.map +1 -1
  129. package/dist/controllers/intersection_controller.d.ts +32 -13
  130. package/dist/controllers/intersection_controller.js +147 -106
  131. package/dist/controllers/intersection_controller.js.map +1 -1
  132. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  133. package/dist/controllers/lazy_frame_controller.js +9 -65
  134. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  135. package/dist/controllers/listbox_controller.d.ts +34 -2
  136. package/dist/controllers/listbox_controller.js +306 -204
  137. package/dist/controllers/listbox_controller.js.map +1 -1
  138. package/dist/controllers/local_time_controller.d.ts +1 -1
  139. package/dist/controllers/local_time_controller.js +54 -64
  140. package/dist/controllers/local_time_controller.js.map +1 -1
  141. package/dist/controllers/masonry_controller.d.ts +11 -3
  142. package/dist/controllers/masonry_controller.js +70 -93
  143. package/dist/controllers/masonry_controller.js.map +1 -1
  144. package/dist/controllers/menu_controller.d.ts +32 -7
  145. package/dist/controllers/menu_controller.js +214 -156
  146. package/dist/controllers/menu_controller.js.map +1 -1
  147. package/dist/controllers/menubar_controller.d.ts +4 -4
  148. package/dist/controllers/menubar_controller.js +170 -329
  149. package/dist/controllers/menubar_controller.js.map +1 -1
  150. package/dist/controllers/meter_controller.d.ts +20 -0
  151. package/dist/controllers/meter_controller.js +151 -67
  152. package/dist/controllers/meter_controller.js.map +1 -1
  153. package/dist/controllers/multi_select_controller.d.ts +32 -4
  154. package/dist/controllers/multi_select_controller.js +593 -358
  155. package/dist/controllers/multi_select_controller.js.map +1 -1
  156. package/dist/controllers/navigation_menu_controller.d.ts +8 -1
  157. package/dist/controllers/navigation_menu_controller.js +124 -232
  158. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  159. package/dist/controllers/nested_form_controller.d.ts +10 -0
  160. package/dist/controllers/nested_form_controller.js +352 -182
  161. package/dist/controllers/nested_form_controller.js.map +1 -1
  162. package/dist/controllers/network_status_controller.d.ts +23 -0
  163. package/dist/controllers/network_status_controller.js +242 -54
  164. package/dist/controllers/network_status_controller.js.map +1 -1
  165. package/dist/controllers/number_input_controller.d.ts +26 -2
  166. package/dist/controllers/number_input_controller.js +320 -263
  167. package/dist/controllers/number_input_controller.js.map +1 -1
  168. package/dist/controllers/optimistic_controller.js +16 -76
  169. package/dist/controllers/optimistic_controller.js.map +1 -1
  170. package/dist/controllers/otp_controller.d.ts +20 -4
  171. package/dist/controllers/otp_controller.js +198 -193
  172. package/dist/controllers/otp_controller.js.map +1 -1
  173. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  174. package/dist/controllers/overflow_indicator_controller.js +116 -113
  175. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  176. package/dist/controllers/overflow_menu_controller.d.ts +48 -11
  177. package/dist/controllers/overflow_menu_controller.js +343 -283
  178. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  179. package/dist/controllers/pagination_controller.d.ts +35 -6
  180. package/dist/controllers/pagination_controller.js +295 -119
  181. package/dist/controllers/pagination_controller.js.map +1 -1
  182. package/dist/controllers/password_reveal_controller.d.ts +45 -8
  183. package/dist/controllers/password_reveal_controller.js +389 -121
  184. package/dist/controllers/password_reveal_controller.js.map +1 -1
  185. package/dist/controllers/password_strength_controller.d.ts +14 -5
  186. package/dist/controllers/password_strength_controller.js +99 -153
  187. package/dist/controllers/password_strength_controller.js.map +1 -1
  188. package/dist/controllers/persist_controller.d.ts +11 -4
  189. package/dist/controllers/persist_controller.js +237 -103
  190. package/dist/controllers/persist_controller.js.map +1 -1
  191. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  192. package/dist/controllers/pointer_drag_controller.js +185 -165
  193. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  194. package/dist/controllers/popover_controller.d.ts +38 -2
  195. package/dist/controllers/popover_controller.js +236 -97
  196. package/dist/controllers/popover_controller.js.map +1 -1
  197. package/dist/controllers/portal_controller.d.ts +44 -4
  198. package/dist/controllers/portal_controller.js +209 -76
  199. package/dist/controllers/portal_controller.js.map +1 -1
  200. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  201. package/dist/controllers/preview_guard_controller.js +232 -116
  202. package/dist/controllers/preview_guard_controller.js.map +1 -1
  203. package/dist/controllers/progress_controller.d.ts +11 -0
  204. package/dist/controllers/progress_controller.js +125 -56
  205. package/dist/controllers/progress_controller.js.map +1 -1
  206. package/dist/controllers/radio_group_controller.d.ts +14 -5
  207. package/dist/controllers/radio_group_controller.js +117 -137
  208. package/dist/controllers/radio_group_controller.js.map +1 -1
  209. package/dist/controllers/range_slider_controller.d.ts +43 -3
  210. package/dist/controllers/range_slider_controller.js +192 -105
  211. package/dist/controllers/range_slider_controller.js.map +1 -1
  212. package/dist/controllers/rating_controller.d.ts +20 -4
  213. package/dist/controllers/rating_controller.js +418 -207
  214. package/dist/controllers/rating_controller.js.map +1 -1
  215. package/dist/controllers/read_more_controller.d.ts +2 -0
  216. package/dist/controllers/read_more_controller.js +134 -63
  217. package/dist/controllers/read_more_controller.js.map +1 -1
  218. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  219. package/dist/controllers/reading_progress_controller.js +147 -135
  220. package/dist/controllers/reading_progress_controller.js.map +1 -1
  221. package/dist/controllers/relative_time_controller.d.ts +18 -4
  222. package/dist/controllers/relative_time_controller.js +145 -110
  223. package/dist/controllers/relative_time_controller.js.map +1 -1
  224. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  225. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  226. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  227. package/dist/controllers/resizable_controller.d.ts +28 -2
  228. package/dist/controllers/resizable_controller.js +255 -139
  229. package/dist/controllers/resizable_controller.js.map +1 -1
  230. package/dist/controllers/roving_controller.js +39 -84
  231. package/dist/controllers/roving_controller.js.map +1 -1
  232. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  233. package/dist/controllers/scroll_area_controller.js +428 -225
  234. package/dist/controllers/scroll_area_controller.js.map +1 -1
  235. package/dist/controllers/scroll_restore_controller.js +0 -53
  236. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  237. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  238. package/dist/controllers/scroll_visibility_controller.js +395 -145
  239. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  240. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  241. package/dist/controllers/scrollspy_controller.js +181 -242
  242. package/dist/controllers/scrollspy_controller.js.map +1 -1
  243. package/dist/controllers/separator_controller.d.ts +19 -4
  244. package/dist/controllers/separator_controller.js +267 -140
  245. package/dist/controllers/separator_controller.js.map +1 -1
  246. package/dist/controllers/sidebar_controller.d.ts +64 -7
  247. package/dist/controllers/sidebar_controller.js +1023 -386
  248. package/dist/controllers/sidebar_controller.js.map +1 -1
  249. package/dist/controllers/skeleton_controller.d.ts +23 -0
  250. package/dist/controllers/skeleton_controller.js +248 -97
  251. package/dist/controllers/skeleton_controller.js.map +1 -1
  252. package/dist/controllers/slider_controller.d.ts +27 -1
  253. package/dist/controllers/slider_controller.js +159 -79
  254. package/dist/controllers/slider_controller.js.map +1 -1
  255. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  256. package/dist/controllers/smart_sticky_header_controller.js +162 -63
  257. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  258. package/dist/controllers/sortable_controller.js +0 -112
  259. package/dist/controllers/sortable_controller.js.map +1 -1
  260. package/dist/controllers/spinner_controller.d.ts +39 -4
  261. package/dist/controllers/spinner_controller.js +277 -169
  262. package/dist/controllers/spinner_controller.js.map +1 -1
  263. package/dist/controllers/step_indicator_controller.d.ts +5 -0
  264. package/dist/controllers/step_indicator_controller.js +99 -46
  265. package/dist/controllers/step_indicator_controller.js.map +1 -1
  266. package/dist/controllers/stepper_controller.d.ts +12 -3
  267. package/dist/controllers/stepper_controller.js +114 -73
  268. package/dist/controllers/stepper_controller.js.map +1 -1
  269. package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
  270. package/dist/controllers/stick_to_bottom_controller.js +262 -138
  271. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  272. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  273. package/dist/controllers/sticky_observer_controller.js +214 -49
  274. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  275. package/dist/controllers/submit_once_controller.d.ts +23 -8
  276. package/dist/controllers/submit_once_controller.js +294 -205
  277. package/dist/controllers/submit_once_controller.js.map +1 -1
  278. package/dist/controllers/switch_controller.d.ts +12 -1
  279. package/dist/controllers/switch_controller.js +33 -60
  280. package/dist/controllers/switch_controller.js.map +1 -1
  281. package/dist/controllers/tabs_controller.d.ts +2 -2
  282. package/dist/controllers/tabs_controller.js +35 -29
  283. package/dist/controllers/tabs_controller.js.map +1 -1
  284. package/dist/controllers/tags_input_controller.d.ts +12 -0
  285. package/dist/controllers/tags_input_controller.js +136 -145
  286. package/dist/controllers/tags_input_controller.js.map +1 -1
  287. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  288. package/dist/controllers/textarea_autosize_controller.js +71 -58
  289. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  290. package/dist/controllers/theme_controller.d.ts +21 -9
  291. package/dist/controllers/theme_controller.js +130 -112
  292. package/dist/controllers/theme_controller.js.map +1 -1
  293. package/dist/controllers/time_picker_controller.d.ts +18 -1
  294. package/dist/controllers/time_picker_controller.js +146 -80
  295. package/dist/controllers/time_picker_controller.js.map +1 -1
  296. package/dist/controllers/toast_controller.d.ts +36 -11
  297. package/dist/controllers/toast_controller.js +186 -297
  298. package/dist/controllers/toast_controller.js.map +1 -1
  299. package/dist/controllers/toggle_group_controller.d.ts +12 -3
  300. package/dist/controllers/toggle_group_controller.js +95 -136
  301. package/dist/controllers/toggle_group_controller.js.map +1 -1
  302. package/dist/controllers/toolbar_controller.js +68 -98
  303. package/dist/controllers/toolbar_controller.js.map +1 -1
  304. package/dist/controllers/tooltip_controller.d.ts +45 -3
  305. package/dist/controllers/tooltip_controller.js +158 -124
  306. package/dist/controllers/tooltip_controller.js.map +1 -1
  307. package/dist/controllers/transition_controller.d.ts +22 -4
  308. package/dist/controllers/transition_controller.js +123 -119
  309. package/dist/controllers/transition_controller.js.map +1 -1
  310. package/dist/controllers/tree_view_controller.d.ts +21 -11
  311. package/dist/controllers/tree_view_controller.js +150 -345
  312. package/dist/controllers/tree_view_controller.js.map +1 -1
  313. package/dist/index.d.ts +8 -5
  314. package/dist/index.js +6370 -9346
  315. package/dist/index.js.map +1 -1
  316. package/dist/inspector/cli.d.ts +23 -3
  317. package/dist/inspector/cli.js +92 -37
  318. package/dist/inspector/cli.js.map +1 -1
  319. package/dist/inspector/cli_bin.js +92 -56
  320. package/dist/inspector/cli_bin.js.map +1 -1
  321. package/dist/inspector/examples.json +8 -8
  322. package/dist/inspector/manifest.json +787 -100
  323. package/dist/positioning/index.d.ts +8 -0
  324. package/dist/positioning/index.js +148 -47
  325. package/dist/positioning/index.js.map +1 -1
  326. package/package.json +6 -6
  327. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  328. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,6 +1,69 @@
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
+ schedule() {
31
+ if (!this.#active || this.#queued) return;
32
+ this.#queued = true;
33
+ const generation = this.#generation;
34
+ queueMicrotask(() => {
35
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
36
+ this.#queued = false;
37
+ this.#run();
38
+ });
39
+ }
40
+ };
41
+
42
+ var MorphRenderWatcher = class {
43
+ #listeners = new ListenerSet();
44
+ #pass;
45
+ #element = null;
46
+ #onMorph = () => this.#pass.schedule();
47
+ constructor(run) {
48
+ this.#pass = new MicrotaskCoalescer(run);
49
+ }
50
+ observe(element) {
51
+ if (this.#element === element) return;
52
+ this.disconnect();
53
+ this.#element = element;
54
+ this.#pass.activate();
55
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
56
+ }
57
+ schedule() {
58
+ this.#pass.schedule();
59
+ }
60
+ disconnect() {
61
+ this.#listeners.dispose();
62
+ this.#pass.cancel();
63
+ this.#element = null;
64
+ }
65
+ };
66
+
4
67
  var FilterController = class extends Controller {
5
68
  static targets = ["item", "control", "group", "empty"];
6
69
  static values = { match: { type: String, default: "all" } };
@@ -9,55 +72,28 @@ var FilterController = class extends Controller {
9
72
  #onChange = () => {
10
73
  this.apply();
11
74
  };
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
75
  #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
76
  #connected = false;
77
+ #morphRender = new MorphRenderWatcher(() => this.#evaluate());
26
78
  connect() {
79
+ this.#morphRender.observe(this.element);
27
80
  this.#reported = null;
28
81
  this.#evaluate();
29
82
  this.element.addEventListener("change", this.#onChange);
30
83
  this.#connected = true;
31
84
  }
32
85
  disconnect() {
86
+ this.#morphRender.disconnect();
33
87
  this.#connected = false;
34
88
  this.element.removeEventListener("change", this.#onChange);
35
89
  }
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
- */
44
90
  matchValueChanged() {
45
91
  if (!this.#connected) return;
46
92
  this.#evaluate();
47
93
  }
48
- /** Re-derives every item's visibility from the active tokens and syncs groups/empty. */
49
94
  apply() {
50
95
  this.#evaluate();
51
96
  }
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
97
  #evaluate() {
62
98
  const active = this.#activeTokens();
63
99
  let visibleCount = 0;
@@ -80,7 +116,6 @@ var FilterController = class extends Controller {
80
116
  this.#reported = { active, visibleCount, total };
81
117
  this.dispatch("change", { detail: { active: [...active], visibleCount, total } });
82
118
  }
83
- /** Turns every control off (uncheck / aria-pressed="false") and re-applies. */
84
119
  clear() {
85
120
  for (const control of this.controlTargets) {
86
121
  if (control instanceof HTMLInputElement) {
@@ -91,7 +126,6 @@ var FilterController = class extends Controller {
91
126
  }
92
127
  this.apply();
93
128
  }
94
- /** The tokens of every control currently "on" (checked or aria-pressed="true"). */
95
129
  #activeTokens() {
96
130
  const tokens = [];
97
131
  for (const control of this.controlTargets) {
@@ -99,33 +133,27 @@ var FilterController = class extends Controller {
99
133
  }
100
134
  return tokens.filter(Boolean);
101
135
  }
102
- /** Whether a control is in its "on" state. */
103
136
  #isOn(control) {
104
137
  if (control instanceof HTMLInputElement) return control.checked;
105
138
  return control.getAttribute("aria-pressed") === "true";
106
139
  }
107
- /** A control's token: explicit `data-stimeo--filter-token`, else `data-value` / value. */
108
140
  #tokenOf(control) {
109
141
  const explicit = control.getAttribute(`data-${this.identifier}-token`) ?? control.dataset.value;
110
142
  if (explicit) return explicit;
111
143
  return control instanceof HTMLInputElement ? control.value : "";
112
144
  }
113
- /** Whether an item satisfies the active token set per `match` (empty active → shown). */
114
145
  #matches(item, active) {
115
146
  if (active.length === 0) return true;
116
147
  const tokens = this.#tokensOf(item);
117
148
  return this.matchValue === "any" ? active.some((token) => tokens.includes(token)) : active.every((token) => tokens.includes(token));
118
149
  }
119
- /** An item's declared tokens (`data-stimeo--filter-tokens`, space-separated). */
120
150
  #tokensOf(item) {
121
151
  return (item.getAttribute(`data-${this.identifier}-tokens`) ?? "").split(/\s+/).filter(Boolean);
122
152
  }
123
- /** Whether a group still contains at least one non-hidden item. */
124
153
  #hasVisibleItem(group) {
125
154
  return this.itemTargets.some((item) => !item.hidden && group.contains(item));
126
155
  }
127
156
  };
128
157
 
129
158
  export { FilterController };
130
- //# sourceMappingURL=filter_controller.js.map
131
159
  //# 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":";;AAkDO,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;;ACYO,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,EAE5D,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,OAAA,CAAQ,gBAAA,CAAiB,QAAA,EAAU,IAAA,CAAK,SAAS,CAAA;AACtD,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,OAAA,CAAQ,mBAAA,CAAoB,QAAA,EAAU,IAAA,CAAK,SAAS,CAAA;AAAA,EAC3D;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":["/**\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 { 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 override connect(): void {\n this.#morphRender.observe(this.element);\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.#morphRender.disconnect();\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"]}
@@ -7,6 +7,9 @@ import { Controller } from '@hotwired/stimulus';
7
7
  * 2.2 **4.1.3 Status Messages**). The general-purpose sibling is 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,11 +30,13 @@ 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
42
  * Reading is **delegated to the shared Announcer** — but only for the *initial*,
@@ -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 };