stimeo-ui 0.15.0 → 0.16.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 (267) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +120 -0
  3. package/dist/cable/index.js +63 -15
  4. package/dist/cable/index.js.map +1 -1
  5. package/dist/controllers/accordion_controller.d.ts +30 -3
  6. package/dist/controllers/accordion_controller.js +98 -9
  7. package/dist/controllers/accordion_controller.js.map +1 -1
  8. package/dist/controllers/alert_dialog_controller.d.ts +8 -7
  9. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  10. package/dist/controllers/announcer_controller.d.ts +3 -2
  11. package/dist/controllers/announcer_controller.js +8 -1
  12. package/dist/controllers/announcer_controller.js.map +1 -1
  13. package/dist/controllers/auto_submit_controller.js +43 -2
  14. package/dist/controllers/auto_submit_controller.js.map +1 -1
  15. package/dist/controllers/avatar_controller.js +1 -1
  16. package/dist/controllers/avatar_controller.js.map +1 -1
  17. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  18. package/dist/controllers/breadcrumb_controller.js +9 -8
  19. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  20. package/dist/controllers/bulk_select_controller.d.ts +4 -4
  21. package/dist/controllers/bulk_select_controller.js +7 -6
  22. package/dist/controllers/bulk_select_controller.js.map +1 -1
  23. package/dist/controllers/calendar_controller.d.ts +85 -23
  24. package/dist/controllers/calendar_controller.js +323 -123
  25. package/dist/controllers/calendar_controller.js.map +1 -1
  26. package/dist/controllers/carousel_controller.d.ts +67 -22
  27. package/dist/controllers/carousel_controller.js +263 -38
  28. package/dist/controllers/carousel_controller.js.map +1 -1
  29. package/dist/controllers/character_counter_controller.d.ts +1 -1
  30. package/dist/controllers/character_counter_controller.js +40 -2
  31. package/dist/controllers/character_counter_controller.js.map +1 -1
  32. package/dist/controllers/checkbox_controller.js +54 -10
  33. package/dist/controllers/checkbox_controller.js.map +1 -1
  34. package/dist/controllers/clipboard_controller.d.ts +28 -2
  35. package/dist/controllers/clipboard_controller.js +63 -5
  36. package/dist/controllers/clipboard_controller.js.map +1 -1
  37. package/dist/controllers/collapsible_controller.d.ts +25 -1
  38. package/dist/controllers/collapsible_controller.js +99 -14
  39. package/dist/controllers/collapsible_controller.js.map +1 -1
  40. package/dist/controllers/color_picker_controller.d.ts +29 -8
  41. package/dist/controllers/color_picker_controller.js +80 -34
  42. package/dist/controllers/color_picker_controller.js.map +1 -1
  43. package/dist/controllers/combobox_controller.d.ts +10 -1
  44. package/dist/controllers/combobox_controller.js +106 -16
  45. package/dist/controllers/combobox_controller.js.map +1 -1
  46. package/dist/controllers/command_palette_controller.d.ts +3 -3
  47. package/dist/controllers/command_palette_controller.js +35 -3
  48. package/dist/controllers/command_palette_controller.js.map +1 -1
  49. package/dist/controllers/conditional_fields_controller.js +56 -11
  50. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  51. package/dist/controllers/confirm_controller.js +3 -0
  52. package/dist/controllers/confirm_controller.js.map +1 -1
  53. package/dist/controllers/context_menu_controller.d.ts +6 -0
  54. package/dist/controllers/context_menu_controller.js +32 -12
  55. package/dist/controllers/context_menu_controller.js.map +1 -1
  56. package/dist/controllers/countdown_controller.d.ts +30 -1
  57. package/dist/controllers/countdown_controller.js +129 -26
  58. package/dist/controllers/countdown_controller.js.map +1 -1
  59. package/dist/controllers/currency_input_controller.d.ts +74 -15
  60. package/dist/controllers/currency_input_controller.js +205 -58
  61. package/dist/controllers/currency_input_controller.js.map +1 -1
  62. package/dist/controllers/data_grid_controller.d.ts +63 -18
  63. package/dist/controllers/data_grid_controller.js +195 -29
  64. package/dist/controllers/data_grid_controller.js.map +1 -1
  65. package/dist/controllers/date_range_picker_controller.d.ts +34 -6
  66. package/dist/controllers/date_range_picker_controller.js +139 -27
  67. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  68. package/dist/controllers/dialog_controller.d.ts +10 -3
  69. package/dist/controllers/dialog_controller.js +35 -8
  70. package/dist/controllers/dialog_controller.js.map +1 -1
  71. package/dist/controllers/direct_upload_controller.js +22 -4
  72. package/dist/controllers/direct_upload_controller.js.map +1 -1
  73. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  74. package/dist/controllers/dirty_form_controller.js +14 -1
  75. package/dist/controllers/dirty_form_controller.js.map +1 -1
  76. package/dist/controllers/dismissible_controller.js +1 -0
  77. package/dist/controllers/dismissible_controller.js.map +1 -1
  78. package/dist/controllers/drawer_controller.d.ts +18 -10
  79. package/dist/controllers/drawer_controller.js +54 -19
  80. package/dist/controllers/drawer_controller.js.map +1 -1
  81. package/dist/controllers/dropdown_controller.d.ts +9 -3
  82. package/dist/controllers/dropdown_controller.js +36 -9
  83. package/dist/controllers/dropdown_controller.js.map +1 -1
  84. package/dist/controllers/editable_controller.js +34 -0
  85. package/dist/controllers/editable_controller.js.map +1 -1
  86. package/dist/controllers/file_dropzone_controller.js +144 -51
  87. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  88. package/dist/controllers/filter_controller.d.ts +10 -4
  89. package/dist/controllers/filter_controller.js +20 -6
  90. package/dist/controllers/filter_controller.js.map +1 -1
  91. package/dist/controllers/flash_controller.d.ts +26 -6
  92. package/dist/controllers/flash_controller.js +305 -46
  93. package/dist/controllers/flash_controller.js.map +1 -1
  94. package/dist/controllers/focus_controller.js +1 -0
  95. package/dist/controllers/focus_controller.js.map +1 -1
  96. package/dist/controllers/form_field_controller.js +7 -5
  97. package/dist/controllers/form_field_controller.js.map +1 -1
  98. package/dist/controllers/form_validation_controller.js +19 -13
  99. package/dist/controllers/form_validation_controller.js.map +1 -1
  100. package/dist/controllers/frame_loading_controller.js +16 -2
  101. package/dist/controllers/frame_loading_controller.js.map +1 -1
  102. package/dist/controllers/highlight_controller.js +19 -2
  103. package/dist/controllers/highlight_controller.js.map +1 -1
  104. package/dist/controllers/hover_card_controller.d.ts +10 -2
  105. package/dist/controllers/hover_card_controller.js +40 -14
  106. package/dist/controllers/hover_card_controller.js.map +1 -1
  107. package/dist/controllers/idle_controller.d.ts +16 -3
  108. package/dist/controllers/idle_controller.js +90 -5
  109. package/dist/controllers/idle_controller.js.map +1 -1
  110. package/dist/controllers/input_mask_controller.d.ts +5 -2
  111. package/dist/controllers/input_mask_controller.js +65 -9
  112. package/dist/controllers/input_mask_controller.js.map +1 -1
  113. package/dist/controllers/intersection_controller.js +3 -0
  114. package/dist/controllers/intersection_controller.js.map +1 -1
  115. package/dist/controllers/lazy_frame_controller.js +11 -2
  116. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  117. package/dist/controllers/listbox_controller.d.ts +50 -8
  118. package/dist/controllers/listbox_controller.js +203 -45
  119. package/dist/controllers/listbox_controller.js.map +1 -1
  120. package/dist/controllers/masonry_controller.js +0 -2
  121. package/dist/controllers/masonry_controller.js.map +1 -1
  122. package/dist/controllers/menu_controller.d.ts +9 -3
  123. package/dist/controllers/menu_controller.js +45 -16
  124. package/dist/controllers/menu_controller.js.map +1 -1
  125. package/dist/controllers/menubar_controller.d.ts +11 -0
  126. package/dist/controllers/menubar_controller.js +58 -24
  127. package/dist/controllers/menubar_controller.js.map +1 -1
  128. package/dist/controllers/meter_controller.js +9 -5
  129. package/dist/controllers/meter_controller.js.map +1 -1
  130. package/dist/controllers/multi_select_controller.d.ts +18 -3
  131. package/dist/controllers/multi_select_controller.js +278 -104
  132. package/dist/controllers/multi_select_controller.js.map +1 -1
  133. package/dist/controllers/navigation_menu_controller.d.ts +11 -0
  134. package/dist/controllers/navigation_menu_controller.js +48 -15
  135. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  136. package/dist/controllers/nested_form_controller.js +37 -8
  137. package/dist/controllers/nested_form_controller.js.map +1 -1
  138. package/dist/controllers/network_status_controller.js +9 -1
  139. package/dist/controllers/network_status_controller.js.map +1 -1
  140. package/dist/controllers/number_input_controller.d.ts +36 -8
  141. package/dist/controllers/number_input_controller.js +124 -21
  142. package/dist/controllers/number_input_controller.js.map +1 -1
  143. package/dist/controllers/optimistic_controller.js +14 -1
  144. package/dist/controllers/optimistic_controller.js.map +1 -1
  145. package/dist/controllers/otp_controller.d.ts +22 -7
  146. package/dist/controllers/otp_controller.js +198 -55
  147. package/dist/controllers/otp_controller.js.map +1 -1
  148. package/dist/controllers/overflow_indicator_controller.d.ts +8 -12
  149. package/dist/controllers/overflow_indicator_controller.js +84 -13
  150. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  151. package/dist/controllers/overflow_menu_controller.d.ts +26 -6
  152. package/dist/controllers/overflow_menu_controller.js +112 -41
  153. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  154. package/dist/controllers/pagination_controller.d.ts +19 -11
  155. package/dist/controllers/pagination_controller.js +74 -28
  156. package/dist/controllers/pagination_controller.js.map +1 -1
  157. package/dist/controllers/password_reveal_controller.d.ts +15 -1
  158. package/dist/controllers/password_reveal_controller.js +59 -2
  159. package/dist/controllers/password_reveal_controller.js.map +1 -1
  160. package/dist/controllers/persist_controller.js +30 -8
  161. package/dist/controllers/persist_controller.js.map +1 -1
  162. package/dist/controllers/pointer_drag_controller.js +131 -52
  163. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  164. package/dist/controllers/popover_controller.d.ts +9 -3
  165. package/dist/controllers/popover_controller.js +45 -11
  166. package/dist/controllers/popover_controller.js.map +1 -1
  167. package/dist/controllers/portal_controller.d.ts +1 -1
  168. package/dist/controllers/portal_controller.js +6 -2
  169. package/dist/controllers/portal_controller.js.map +1 -1
  170. package/dist/controllers/preview_guard_controller.js +16 -1
  171. package/dist/controllers/preview_guard_controller.js.map +1 -1
  172. package/dist/controllers/progress_controller.js +8 -4
  173. package/dist/controllers/progress_controller.js.map +1 -1
  174. package/dist/controllers/radio_group_controller.d.ts +6 -4
  175. package/dist/controllers/radio_group_controller.js +42 -17
  176. package/dist/controllers/radio_group_controller.js.map +1 -1
  177. package/dist/controllers/range_slider_controller.d.ts +49 -1
  178. package/dist/controllers/range_slider_controller.js +88 -42
  179. package/dist/controllers/range_slider_controller.js.map +1 -1
  180. package/dist/controllers/rating_controller.d.ts +14 -3
  181. package/dist/controllers/rating_controller.js +39 -15
  182. package/dist/controllers/rating_controller.js.map +1 -1
  183. package/dist/controllers/read_more_controller.d.ts +24 -2
  184. package/dist/controllers/read_more_controller.js +69 -2
  185. package/dist/controllers/read_more_controller.js.map +1 -1
  186. package/dist/controllers/resizable_controller.d.ts +16 -2
  187. package/dist/controllers/resizable_controller.js +82 -22
  188. package/dist/controllers/resizable_controller.js.map +1 -1
  189. package/dist/controllers/scroll_area_controller.js +5 -1
  190. package/dist/controllers/scroll_area_controller.js.map +1 -1
  191. package/dist/controllers/scroll_visibility_controller.js +0 -1
  192. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  193. package/dist/controllers/scrollspy_controller.d.ts +3 -2
  194. package/dist/controllers/scrollspy_controller.js +6 -0
  195. package/dist/controllers/scrollspy_controller.js.map +1 -1
  196. package/dist/controllers/separator_controller.d.ts +41 -14
  197. package/dist/controllers/separator_controller.js +66 -37
  198. package/dist/controllers/separator_controller.js.map +1 -1
  199. package/dist/controllers/sidebar_controller.d.ts +20 -3
  200. package/dist/controllers/sidebar_controller.js +77 -18
  201. package/dist/controllers/sidebar_controller.js.map +1 -1
  202. package/dist/controllers/skeleton_controller.js +6 -1
  203. package/dist/controllers/skeleton_controller.js.map +1 -1
  204. package/dist/controllers/slider_controller.d.ts +45 -7
  205. package/dist/controllers/slider_controller.js +82 -47
  206. package/dist/controllers/slider_controller.js.map +1 -1
  207. package/dist/controllers/smart_sticky_header_controller.js +11 -1
  208. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  209. package/dist/controllers/sortable_controller.js +17 -2
  210. package/dist/controllers/sortable_controller.js.map +1 -1
  211. package/dist/controllers/spinner_controller.js +10 -2
  212. package/dist/controllers/spinner_controller.js.map +1 -1
  213. package/dist/controllers/step_indicator_controller.d.ts +19 -17
  214. package/dist/controllers/step_indicator_controller.js +18 -17
  215. package/dist/controllers/step_indicator_controller.js.map +1 -1
  216. package/dist/controllers/stepper_controller.d.ts +34 -9
  217. package/dist/controllers/stepper_controller.js +101 -19
  218. package/dist/controllers/stepper_controller.js.map +1 -1
  219. package/dist/controllers/stick_to_bottom_controller.d.ts +19 -0
  220. package/dist/controllers/stick_to_bottom_controller.js +75 -5
  221. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  222. package/dist/controllers/submit_once_controller.d.ts +3 -2
  223. package/dist/controllers/submit_once_controller.js +16 -3
  224. package/dist/controllers/submit_once_controller.js.map +1 -1
  225. package/dist/controllers/switch_controller.d.ts +29 -7
  226. package/dist/controllers/switch_controller.js +101 -10
  227. package/dist/controllers/switch_controller.js.map +1 -1
  228. package/dist/controllers/tabs_controller.d.ts +12 -0
  229. package/dist/controllers/tabs_controller.js +21 -2
  230. package/dist/controllers/tabs_controller.js.map +1 -1
  231. package/dist/controllers/tags_input_controller.d.ts +15 -3
  232. package/dist/controllers/tags_input_controller.js +209 -59
  233. package/dist/controllers/tags_input_controller.js.map +1 -1
  234. package/dist/controllers/theme_controller.d.ts +20 -3
  235. package/dist/controllers/theme_controller.js +64 -14
  236. package/dist/controllers/theme_controller.js.map +1 -1
  237. package/dist/controllers/time_picker_controller.js +23 -8
  238. package/dist/controllers/time_picker_controller.js.map +1 -1
  239. package/dist/controllers/toast_controller.d.ts +52 -15
  240. package/dist/controllers/toast_controller.js +337 -54
  241. package/dist/controllers/toast_controller.js.map +1 -1
  242. package/dist/controllers/toggle_group_controller.d.ts +49 -7
  243. package/dist/controllers/toggle_group_controller.js +159 -23
  244. package/dist/controllers/toggle_group_controller.js.map +1 -1
  245. package/dist/controllers/toolbar_controller.js +32 -0
  246. package/dist/controllers/toolbar_controller.js.map +1 -1
  247. package/dist/controllers/tooltip_controller.d.ts +8 -0
  248. package/dist/controllers/tooltip_controller.js +39 -13
  249. package/dist/controllers/tooltip_controller.js.map +1 -1
  250. package/dist/controllers/transition_controller.js +4 -0
  251. package/dist/controllers/transition_controller.js.map +1 -1
  252. package/dist/controllers/tree_view_controller.d.ts +39 -8
  253. package/dist/controllers/tree_view_controller.js +169 -16
  254. package/dist/controllers/tree_view_controller.js.map +1 -1
  255. package/dist/index.d.ts +28 -1
  256. package/dist/index.js +4588 -1535
  257. package/dist/index.js.map +1 -1
  258. package/dist/inspector/cli.d.ts +72 -6
  259. package/dist/inspector/cli.js +262 -51
  260. package/dist/inspector/cli.js.map +1 -1
  261. package/dist/inspector/cli_bin.js +309 -51
  262. package/dist/inspector/cli_bin.js.map +1 -1
  263. package/dist/inspector/examples.json +40 -40
  264. package/dist/inspector/manifest.json +525 -59
  265. package/dist/positioning/index.js +2 -0
  266. package/dist/positioning/index.js.map +1 -1
  267. package/package.json +2 -2
@@ -2,6 +2,72 @@ import { Controller } from '@hotwired/stimulus';
2
2
 
3
3
  // src/controllers/toast_controller.ts
4
4
 
5
+ // src/utils/event_owner.ts
6
+ function ownerIndex(candidates, node) {
7
+ if (!(node instanceof Node)) return -1;
8
+ return candidates.findIndex((candidate) => candidate.contains(node));
9
+ }
10
+ function ownerOf(candidates, node) {
11
+ return candidates[ownerIndex(candidates, node)] ?? null;
12
+ }
13
+
14
+ // src/utils/listener_set.ts
15
+ var ListenerSet = class {
16
+ /** The generation every `add` joins until the next `dispose()`. */
17
+ #abort = new AbortController();
18
+ /**
19
+ * Attaches `handler` to the open generation, exactly as the caller spelled it.
20
+ *
21
+ * The set supplies the signal, so `options` carries everything else the DOM
22
+ * accepts — `capture` included, which has to match at release time and no
23
+ * longer has a second place to drift from.
24
+ */
25
+ add(target, type, handler, options) {
26
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
27
+ }
28
+ /**
29
+ * Releases every listener of the open generation, synchronously, and opens the
30
+ * next one. Idempotent, and safe before anything has been added.
31
+ */
32
+ dispose() {
33
+ this.#abort.abort();
34
+ this.#abort = new AbortController();
35
+ }
36
+ };
37
+
38
+ // src/utils/microtask_coalescer.ts
39
+ var MicrotaskCoalescer = class {
40
+ #run;
41
+ #queued = false;
42
+ #active = false;
43
+ #generation = 0;
44
+ /** @param run - the single reconciliation pass, invoked at most once per batch. */
45
+ constructor(run) {
46
+ this.#run = run;
47
+ }
48
+ /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
49
+ activate() {
50
+ this.#active = true;
51
+ }
52
+ /** Closes the window and drops any pending pass; call from `disconnect()`. */
53
+ cancel() {
54
+ this.#active = false;
55
+ this.#queued = false;
56
+ this.#generation += 1;
57
+ }
58
+ /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
59
+ schedule() {
60
+ if (!this.#active || this.#queued) return;
61
+ this.#queued = true;
62
+ const generation = this.#generation;
63
+ queueMicrotask(() => {
64
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
65
+ this.#queued = false;
66
+ this.#run();
67
+ });
68
+ }
69
+ };
70
+
5
71
  // src/utils/safe_timeout.ts
6
72
  var TimerRegistry = class {
7
73
  /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
@@ -111,16 +177,20 @@ var PausableTimers = class {
111
177
  const reasons = this.#entries.get(key)?.reasons ?? /* @__PURE__ */ new Set();
112
178
  const entry = { callback, startedAt: 0, remaining: delay, reasons };
113
179
  this.#entries.set(key, entry);
114
- if (reasons.size === 0) this.#arm(key, entry);
180
+ if (reasons.size === 0) this.#arm(key, entry, callback);
115
181
  }
116
182
  /**
117
183
  * Holds `key` for `reason` and banks the time left, with a floor of one
118
- * millisecond. Reports whether this call is the one that stopped a running
119
- * timer, so the caller can write its held-state hook exactly once.
184
+ * millisecond. A key with no timer is held all the same. Reports whether this
185
+ * call is the one that stopped a running timer, so the caller can write its
186
+ * held-state hook exactly once.
120
187
  */
121
188
  pause(key, reason) {
122
- const entry = this.#entries.get(key);
123
- if (entry === void 0) return false;
189
+ let entry = this.#entries.get(key);
190
+ if (entry === void 0) {
191
+ entry = { callback: null, startedAt: 0, remaining: 0, reasons: /* @__PURE__ */ new Set() };
192
+ this.#entries.set(key, entry);
193
+ }
124
194
  entry.reasons.add(reason);
125
195
  if (!this.#timers.has(key)) return false;
126
196
  this.#timers.clear(key);
@@ -129,20 +199,40 @@ var PausableTimers = class {
129
199
  }
130
200
  /**
131
201
  * Releases `reason` on `key`, arming the banked time again once no reason is
132
- * left. Reports whether this call is the one that started the timer again.
202
+ * left; a key released with no timer is forgotten. Reports whether this call
203
+ * is the one that started the timer again.
133
204
  */
134
205
  resume(key, reason) {
135
206
  const entry = this.#entries.get(key);
136
207
  if (entry === void 0) return false;
137
208
  entry.reasons.delete(reason);
138
209
  if (entry.reasons.size > 0 || this.#timers.has(key)) return false;
139
- this.#arm(key, entry);
210
+ const { callback } = entry;
211
+ if (callback === null) {
212
+ this.#entries.delete(key);
213
+ return false;
214
+ }
215
+ this.#arm(key, entry, callback);
140
216
  return true;
141
217
  }
218
+ /** Whether any reason holds `key`, with or without a timer. */
219
+ isHeld(key) {
220
+ return (this.#entries.get(key)?.reasons.size ?? 0) > 0;
221
+ }
142
222
  /** Whether `key` is armed or held — that is, whether this registry drives it at all. */
143
223
  tracks(key) {
144
224
  return this.#entries.has(key);
145
225
  }
226
+ /**
227
+ * Cancels `key`'s timer and keeps its hold, so a release arms nothing; a key
228
+ * nothing holds is forgotten.
229
+ */
230
+ disarm(key) {
231
+ this.#timers.clear(key);
232
+ const entry = this.#entries.get(key);
233
+ if (entry?.reasons.size) entry.callback = null;
234
+ else this.#entries.delete(key);
235
+ }
146
236
  /** Cancels `key`'s timer and drops its hold. */
147
237
  clear(key) {
148
238
  this.#timers.clear(key);
@@ -156,20 +246,31 @@ var PausableTimers = class {
156
246
  this.#timers.clearAll();
157
247
  this.#entries.clear();
158
248
  }
159
- /** Starts `entry`'s banked time running for `key`, and drops it as it fires. */
160
- #arm(key, entry) {
249
+ /** Starts `entry`'s banked time running for `key`, and drops it as `callback` fires. */
250
+ #arm(key, entry, callback) {
161
251
  entry.startedAt = Date.now();
162
252
  this.#timers.set(
163
253
  key,
164
254
  () => {
165
255
  this.#entries.delete(key);
166
- entry.callback();
256
+ callback();
167
257
  },
168
258
  entry.remaining
169
259
  );
170
260
  }
171
261
  };
172
262
 
263
+ // src/utils/target_selector.ts
264
+ function targetSelector(identifier, name) {
265
+ return `[data-${identifier}-target~="${name}"]`;
266
+ }
267
+
268
+ // src/utils/template_row.ts
269
+ function cloneTemplateRoot(template) {
270
+ const root = template.content.firstElementChild;
271
+ return root instanceof HTMLElement ? root.cloneNode(true) : null;
272
+ }
273
+
173
274
  // src/utils/transition_completion.ts
174
275
  function timeMs(value) {
175
276
  const trimmed = value.trim();
@@ -218,6 +319,16 @@ var ToastController = class extends Controller {
218
319
  #timers = new SafeTimeout();
219
320
  /** Per-toast auto-dismiss, held open while the toast is hovered or focused. */
220
321
  #dismiss = new PausableTimers();
322
+ /** Toasts taken on and not let go; a move within the list keeps a toast here. */
323
+ #taken = /* @__PURE__ */ new Set();
324
+ /** Applies the `max` limit again once the event that released a hold has run its course. */
325
+ #reapply = new MicrotaskCoalescer(() => this.#reapplyLimit());
326
+ /** Toasts the pointer or focus moved into since the limit was last applied again. */
327
+ #spared = [];
328
+ /** The next pointer movement, listened for while a hover hold waits on it. */
329
+ #pointer = new ListenerSet();
330
+ /** Toasts whose hover hold waits for the next pointer movement to be confirmed. */
331
+ #awaitingPointer = /* @__PURE__ */ new Set();
221
332
  /**
222
333
  * Pending one-shot `requestAnimationFrame` handles (the entering→visible flip).
223
334
  * Tracked so {@link disconnect} can cancel any that have not fired, preventing a
@@ -226,19 +337,43 @@ var ToastController = class extends Controller {
226
337
  #rafHandles = /* @__PURE__ */ new Map();
227
338
  /** The stable list that owns delegated listeners for dynamically added items. */
228
339
  #delegatedList = null;
340
+ /** Whether the controller is between `connect()` and `disconnect()`. */
341
+ #connected = false;
342
+ /**
343
+ * The `max` the list was last held to, or `null` before the first time. `connect()`
344
+ * applies the limit only when `max` is not that one, so a reconnect of the same instance
345
+ * leaves the list as it was unless `max` changed while it was away.
346
+ */
347
+ #appliedMax = null;
348
+ /**
349
+ * Stimulus reports every toast the list already holds before this runs, and each is
350
+ * taken on then with its holds, meeting no limit. The limit is applied here once, over
351
+ * all of them, unless the list was already held to the current `max`.
352
+ */
229
353
  connect() {
354
+ this.#connected = true;
355
+ this.#reapply.activate();
230
356
  this.#connectDelegatedEvents();
231
- this.enforceMaxLimit();
232
357
  for (const item of this.itemTargets) {
233
358
  if (!this.#dismiss.tracks(item) && item.dataset.state !== "leaving") {
234
359
  this.#startTimer(item);
235
360
  }
236
361
  }
362
+ if (!Object.is(this.#appliedMax, this.maxValue)) {
363
+ this.#appliedMax = this.maxValue;
364
+ this.#enforceMax();
365
+ }
237
366
  }
238
367
  disconnect() {
368
+ this.#connected = false;
239
369
  this.#disconnectDelegatedEvents();
240
370
  this.#timers.clearAll();
241
371
  this.#dismiss.clearAll();
372
+ this.#taken.clear();
373
+ this.#reapply.cancel();
374
+ this.#spared = [];
375
+ this.#pointer.dispose();
376
+ this.#awaitingPointer.clear();
242
377
  for (const handle of this.#rafHandles.values()) {
243
378
  window.cancelAnimationFrame(handle);
244
379
  }
@@ -256,24 +391,37 @@ var ToastController = class extends Controller {
256
391
  for (const item of this.itemTargets) {
257
392
  if (item.dataset.state === "leaving") continue;
258
393
  if (this.durationValue <= 0) {
259
- this.#dismiss.clear(item);
394
+ this.#dismiss.disarm(item);
260
395
  item.removeAttribute("data-paused");
261
396
  } else {
262
397
  this.#startTimer(item);
263
398
  }
264
399
  }
265
400
  }
401
+ /**
402
+ * Holds the list to a changed `max`. Stimulus can also call this ahead of `connect()` —
403
+ * for an attribute that changed while the controller was away, and on every connect
404
+ * for an undeclared `max`, with its default; `connect()` decides then.
405
+ */
266
406
  maxValueChanged() {
407
+ if (!this.#connected) return;
408
+ this.#appliedMax = this.maxValue;
267
409
  this.enforceMaxLimit();
268
410
  }
269
411
  /**
270
412
  * Stimulus lifecycle callback triggered automatically when a new item target
271
- * enters the DOM, from a client-side injection or a Turbo Stream append alike. An
272
- * item already leaving, or parented outside `list`, is skipped.
413
+ * enters the DOM, from a client-side injection or a Turbo Stream append alike, and for
414
+ * every toast the list already holds ahead of `connect()`. An item already leaving,
415
+ * parented outside `list`, or already taken on (a move within the list) is skipped; any
416
+ * other is taken on with the holds it already has. On a connected controller the `max`
417
+ * limit is then applied, never to the arrival itself; the toasts reported ahead of
418
+ * `connect()` are left to it.
273
419
  */
274
420
  itemTargetConnected(element) {
275
- this.enforceMaxLimit();
421
+ if (this.#taken.has(element)) return;
276
422
  if (element.dataset.state === "leaving" || element.parentNode !== this.listTarget) return;
423
+ this.#taken.add(element);
424
+ this.#readHolds(element);
277
425
  this.#startTimer(element);
278
426
  element.setAttribute("data-state", "entering");
279
427
  this.#cancelAnimation(element);
@@ -283,10 +431,23 @@ var ToastController = class extends Controller {
283
431
  element.setAttribute("data-state", "visible");
284
432
  });
285
433
  this.#rafHandles.set(element, handle);
434
+ if (this.#connected) this.#enforceMax([element]);
286
435
  }
287
- /** Clears any active timer when a toast is removed from the DOM. */
436
+ /**
437
+ * Lets go of a toast that left the list or stopped being an item: its timer, holds,
438
+ * `data-paused` and pending frame. After a move within the list the node is already
439
+ * back, still an item, when Stimulus reports it, so it keeps all of those, and only the
440
+ * holds the move may have ended are read again.
441
+ */
288
442
  itemTargetDisconnected(element) {
289
- this.#dismiss.clear(element);
443
+ const moved = this.#isShown(element) && element.matches(targetSelector(this.identifier, "item"));
444
+ if (moved) {
445
+ this.#afterMove(element);
446
+ return;
447
+ }
448
+ this.#taken.delete(element);
449
+ element.removeAttribute("data-paused");
450
+ this.#letGo(element);
290
451
  this.#cancelAnimation(element);
291
452
  }
292
453
  /**
@@ -295,24 +456,31 @@ var ToastController = class extends Controller {
295
456
  * (remote / Turbo trigger); the action param wins when both are present.
296
457
  *
297
458
  * <button data-action="click->stimeo--toast#show"
298
- * data-stimeo--toast-body-param="Saved"
459
+ * data-stimeo--toast-message-param="Saved"
299
460
  * data-stimeo--toast-type-param="status">Show</button>
300
461
  *
301
- * element.dispatchEvent(new CustomEvent("show", { detail: { body, type } }))
462
+ * element.dispatchEvent(new CustomEvent("show", { detail: { message, type } }))
302
463
  *
303
- * Clones the template slot, interpolates the body text, and appends to the list.
464
+ * Clones the template slot, interpolates the message, and appends to the list.
465
+ * `message` is the wording key the rest of the library dispatches under, so a
466
+ * part that already carries its own wording — `stimeo--clipboard:copy`,
467
+ * `stimeo--auto-submit:done` — wires straight into this action.
304
468
  */
305
469
  show(event) {
306
470
  if (!this.hasTemplateTarget || !this.hasListTarget) return;
307
- const body = this.#readField(event, "body");
308
- if (!body) return;
309
- const clone = this.templateTarget.content.cloneNode(true);
310
- const item = clone.querySelector("[data-stimeo--toast-target='item']");
311
- if (!item) return;
312
- const bodySlot = item.querySelector("[data-toast-slot='body']");
313
- if (!bodySlot) return;
314
- bodySlot.textContent = body;
315
- bodySlot.setAttribute("role", this.#readField(event, "type") === "alert" ? "alert" : "status");
471
+ const message = this.#readField(event, "message");
472
+ if (!message) return;
473
+ const template = this.templateTarget;
474
+ const item = cloneTemplateRoot(template);
475
+ if (template.content.children.length !== 1) return;
476
+ if (!item?.matches(targetSelector(this.identifier, "item"))) return;
477
+ const messageSlot = item.querySelector("[data-toast-slot='message']");
478
+ if (!messageSlot) return;
479
+ messageSlot.textContent = message;
480
+ messageSlot.setAttribute(
481
+ "role",
482
+ this.#readField(event, "type") === "alert" ? "alert" : "status"
483
+ );
316
484
  this.listTarget.appendChild(item);
317
485
  this.dispatch("show", { detail: { item } });
318
486
  }
@@ -354,7 +522,9 @@ var ToastController = class extends Controller {
354
522
  * Hover and focus are tracked as independent reasons: the timer is only
355
523
  * snapshotted on the first active reason, and only resumed once *every*
356
524
  * reason has been released (see {@link resume}). This keeps a toast paused
357
- * while it is still hovered *or* focused, per WCAG 2.2.1.
525
+ * while it is still hovered *or* focused, per WCAG 2.2.1. The hold is recorded
526
+ * whether or not a timer runs, so the `max` limit passes over the toast either
527
+ * way; `data-paused` marks a timer that stopped, and only that.
358
528
  */
359
529
  pause(event) {
360
530
  const item = this.#itemFromEvent(event);
@@ -362,38 +532,129 @@ var ToastController = class extends Controller {
362
532
  item.setAttribute("data-paused", "true");
363
533
  }
364
534
  }
365
- /** Resumes the auto-dismiss timer once both hover and focus have been released. */
535
+ /**
536
+ * Resumes the auto-dismiss timer once both hover and focus have been released,
537
+ * and then applies the `max` limit again, sparing the toast the pointer or focus is
538
+ * moving into: its own hold only arrives after this event.
539
+ */
366
540
  resume(event) {
367
541
  const item = this.#itemFromEvent(event);
368
- if (item && this.#dismiss.resume(item, this.#pauseReason(event))) {
369
- item.removeAttribute("data-paused");
542
+ if (item) {
543
+ this.#release(
544
+ item,
545
+ this.#pauseReason(event),
546
+ ownerOf(this.itemTargets, event.relatedTarget)
547
+ );
370
548
  }
371
549
  }
550
+ /** Releases one reason on `item`; releasing the last one applies the limit again. */
551
+ #release(item, reason, spare) {
552
+ const held = this.#dismiss.isHeld(item);
553
+ if (this.#dismiss.resume(item, reason)) item.removeAttribute("data-paused");
554
+ if (held && !this.#dismiss.isHeld(item)) this.#limitLater(spare);
555
+ }
556
+ /**
557
+ * Applies the limit again once the current event is over, sparing `spare`. A hold can
558
+ * end in a `focusout` the engine fires while it is itself taking the node out for a
559
+ * script's move or removal, and taking the toast away inside that event would pull
560
+ * the node from under the caller's own call.
561
+ */
562
+ #limitLater(spare) {
563
+ if (spare) this.#spared.push(spare);
564
+ this.#reapply.schedule();
565
+ }
566
+ /** Applies the limit again, sparing every toast gathered since the last pass. */
567
+ #reapplyLimit() {
568
+ const spared = this.#spared;
569
+ this.#spared = [];
570
+ this.#enforceMax(spared);
571
+ }
572
+ /**
573
+ * Reads the holds of a toast that moved within the list. Focus is read at once: an
574
+ * ordinary move ends it with a `focusout` and `moveBefore` keeps it. Where the pointer
575
+ * is, nothing says until it moves again: no `mouseout` reaches a moved node, and
576
+ * `:hover` can answer from before the move. A toast still held waits for that movement.
577
+ */
578
+ #afterMove(item) {
579
+ if (!item.contains(document.activeElement)) this.#release(item, "focus", null);
580
+ if (this.#dismiss.isHeld(item)) this.#awaitPointer(item);
581
+ }
582
+ /**
583
+ * Takes the holds a toast already has as it is taken on: focus inside it, and the pointer
584
+ * over it by `:hover`. That answer can date from before the toast came here, so a hover
585
+ * hold taken on it waits for the next pointer movement to be confirmed.
586
+ */
587
+ #readHolds(item) {
588
+ if (item.contains(document.activeElement)) this.#dismiss.pause(item, "focus");
589
+ if (!item.matches(":hover")) return;
590
+ this.#dismiss.pause(item, "hover");
591
+ this.#awaitPointer(item);
592
+ }
593
+ /** Leaves `item`'s hover hold to be confirmed or let go on the next pointer movement. */
594
+ #awaitPointer(item) {
595
+ this.#awaitingPointer.add(item);
596
+ this.#pointer.add(document, "pointermove", this.#onPointerMove, {
597
+ capture: true,
598
+ passive: true
599
+ });
600
+ }
601
+ /**
602
+ * Reads `:hover` once the pointer has moved, when the answer is current, and lets go of
603
+ * the hover hold of every waiting toast the pointer is not over. Listens once.
604
+ */
605
+ #onPointerMove = () => {
606
+ this.#pointer.dispose();
607
+ const waiting = [...this.#awaitingPointer];
608
+ this.#awaitingPointer.clear();
609
+ for (const item of waiting) {
610
+ if (!item.matches(":hover")) this.#release(item, "hover", null);
611
+ }
612
+ };
613
+ /**
614
+ * Drops a toast's timer and holds as it leaves, and stops waiting on the pointer for it.
615
+ * A toast something still held counts as released: the limit is applied again.
616
+ */
617
+ #letGo(item) {
618
+ if (this.#dismiss.isHeld(item)) this.#limitLater(null);
619
+ this.#dismiss.clear(item);
620
+ this.#awaitingPointer.delete(item);
621
+ if (this.#awaitingPointer.size === 0) this.#pointer.dispose();
622
+ }
372
623
  /** Resolves the toast item element a pause/resume event targets. */
373
624
  #itemFromEvent(event) {
374
625
  const target = event.target instanceof Element ? event.target : event.currentTarget;
375
626
  if (!(target instanceof Element) || !this.hasListTarget) return null;
376
- const item = target.closest("[data-stimeo--toast-target='item']");
627
+ const item = target.closest(targetSelector(this.identifier, "item"));
377
628
  return item && this.listTarget.contains(item) ? item : null;
378
629
  }
379
630
  /** Classifies a pause/resume event as a hover or focus reason. */
380
631
  #pauseReason(event) {
381
632
  return event.type === "focusin" || event.type === "focusout" ? "focus" : "hover";
382
633
  }
383
- /** Arms a toast's auto-dismiss; a non-positive `duration` means it never expires. */
634
+ /**
635
+ * Arms a toast's auto-dismiss; a non-positive `duration` means it never expires.
636
+ * On a toast hover or focus already holds, the timer waits for the release.
637
+ * `data-paused` is written from that state, so a mark the toast carried in with it,
638
+ * from a restored page, does not outlive it.
639
+ *
640
+ * @stimeoRuntimeOnly `duration` is the delay of the one dismissal timer this call arms.
641
+ */
384
642
  #startTimer(element) {
385
- if (this.durationValue <= 0 || element.dataset.state === "leaving" || element.parentNode !== this.listTarget) {
386
- return;
643
+ if (element.dataset.state === "leaving" || element.parentNode !== this.listTarget) return;
644
+ const armed = this.durationValue > 0;
645
+ if (armed) {
646
+ this.#dismiss.set(
647
+ element,
648
+ () => this.#removeWithTransition(element, "timeout"),
649
+ this.durationValue
650
+ );
387
651
  }
388
- this.#dismiss.set(
389
- element,
390
- () => this.#removeWithTransition(element, "timeout"),
391
- this.durationValue
392
- );
652
+ if (armed && this.#dismiss.isHeld(element)) element.setAttribute("data-paused", "true");
653
+ else element.removeAttribute("data-paused");
393
654
  }
394
655
  #removeWithTransition(element, reason) {
395
656
  if (element.dataset.state === "leaving" || element.parentNode !== this.listTarget) return;
396
- this.#dismiss.clear(element);
657
+ this.#letGo(element);
397
658
  this.#cancelAnimation(element);
398
659
  element.setAttribute("data-state", "leaving");
399
660
  const finalize = () => {
@@ -409,24 +670,46 @@ var ToastController = class extends Controller {
409
670
  }
410
671
  }
411
672
  /**
412
- * Removes the oldest toasts when the list exceeds `maxValue`.
673
+ * Removes the oldest toasts while the list shows more than `maxValue`, passing
674
+ * over every toast hover or focus holds.
413
675
  *
414
676
  * Public (not `#private`) as a deterministic seam: enforcement normally runs
415
- * from `itemTargetConnected`, a Stimulus callback delivered through a
416
- * MutationObserver, which a DOM-only environment does not reliably fire — so
417
- * it can also be invoked directly. It is not a user-wired action.
677
+ * as each toast is taken on in `itemTargetConnected`, a Stimulus callback delivered
678
+ * through a MutationObserver, which a DOM-only environment does not reliably fire —
679
+ * so it can also be invoked directly. It counts only the toasts taken on. It is not a
680
+ * user-wired action.
418
681
  */
419
682
  enforceMaxLimit() {
420
- const currentItems = this.itemTargets;
421
- const max = Math.max(0, this.maxValue);
422
- if (currentItems.length > max) {
423
- const excessCount = currentItems.length - max;
424
- for (let i = 0; i < excessCount; i++) {
425
- const oldest = currentItems[i];
426
- if (oldest) this.#removeWithTransition(oldest, "timeout");
683
+ this.#enforceMax();
684
+ }
685
+ /**
686
+ * Removes the oldest toasts with reason `limit` while the list shows more than `max`
687
+ * (`0` or less, or not a finite number, means no limit), passing over every toast hover
688
+ * or focus holds and those in `spared` — the toast just taken on, or the ones the
689
+ * pointer or focus moved into. Only toasts taken on count or go, so one Stimulus has
690
+ * yet to report is neither; a toast already leaving does not count. With nothing else
691
+ * left to go the list stays over the limit. The count is read again on every step,
692
+ * because a `dismiss` listener may already have changed it.
693
+ *
694
+ * @stimeoRenderRoot
695
+ */
696
+ #enforceMax(spared = []) {
697
+ if (!Number.isFinite(this.maxValue) || this.maxValue <= 0) return;
698
+ for (const item of this.itemTargets) {
699
+ if (this.#shownCount() <= this.maxValue) return;
700
+ if (this.#isShown(item) && !spared.includes(item) && !this.#dismiss.isHeld(item)) {
701
+ this.#removeWithTransition(item, "limit");
427
702
  }
428
703
  }
429
704
  }
705
+ /** How many toasts the list shows: the ones taken on that are still shown. */
706
+ #shownCount() {
707
+ return this.itemTargets.filter((item) => this.#isShown(item)).length;
708
+ }
709
+ /** Whether the list shows `item`: a toast taken on, a child of `list`, not leaving. */
710
+ #isShown(item) {
711
+ return this.#taken.has(item) && this.hasListTarget && item.parentNode === this.listTarget && item.dataset.state !== "leaving";
712
+ }
430
713
  /** Wires stable-container delegation so newly appended items work immediately. */
431
714
  #connectDelegatedEvents(list = this.hasListTarget ? this.listTarget : null) {
432
715
  this.#disconnectDelegatedEvents();