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
@@ -42,6 +42,15 @@ var BeforeCacheReset = class _BeforeCacheReset {
42
42
  }
43
43
  };
44
44
 
45
+ // src/utils/event_owner.ts
46
+ function ownerIndex(candidates, node) {
47
+ if (!(node instanceof Node)) return -1;
48
+ return candidates.findIndex((candidate) => candidate.contains(node));
49
+ }
50
+ function ownerOf(candidates, node) {
51
+ return candidates[ownerIndex(candidates, node)] ?? null;
52
+ }
53
+
45
54
  // src/utils/safe_timeout.ts
46
55
  var TimerRegistry = class {
47
56
  /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
@@ -138,6 +147,63 @@ var KeyedTimers = class {
138
147
  }
139
148
  };
140
149
 
150
+ // src/utils/listener_set.ts
151
+ var ListenerSet = class {
152
+ /** The generation every `add` joins until the next `dispose()`. */
153
+ #abort = new AbortController();
154
+ /**
155
+ * Attaches `handler` to the open generation, exactly as the caller spelled it.
156
+ *
157
+ * The set supplies the signal, so `options` carries everything else the DOM
158
+ * accepts — `capture` included, which has to match at release time and no
159
+ * longer has a second place to drift from.
160
+ */
161
+ add(target, type, handler, options) {
162
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
163
+ }
164
+ /**
165
+ * Releases every listener of the open generation, synchronously, and opens the
166
+ * next one. Idempotent, and safe before anything has been added.
167
+ */
168
+ dispose() {
169
+ this.#abort.abort();
170
+ this.#abort = new AbortController();
171
+ }
172
+ };
173
+
174
+ // src/utils/microtask_coalescer.ts
175
+ var MicrotaskCoalescer = class {
176
+ #run;
177
+ #queued = false;
178
+ #active = false;
179
+ #generation = 0;
180
+ /** @param run - the single reconciliation pass, invoked at most once per batch. */
181
+ constructor(run) {
182
+ this.#run = run;
183
+ }
184
+ /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
185
+ activate() {
186
+ this.#active = true;
187
+ }
188
+ /** Closes the window and drops any pending pass; call from `disconnect()`. */
189
+ cancel() {
190
+ this.#active = false;
191
+ this.#queued = false;
192
+ this.#generation += 1;
193
+ }
194
+ /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
195
+ schedule() {
196
+ if (!this.#active || this.#queued) return;
197
+ this.#queued = true;
198
+ const generation = this.#generation;
199
+ queueMicrotask(() => {
200
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
201
+ this.#queued = false;
202
+ this.#run();
203
+ });
204
+ }
205
+ };
206
+
141
207
  // src/utils/pausable_timers.ts
142
208
  var PausableTimers = class {
143
209
  #timers = new KeyedTimers();
@@ -151,16 +217,20 @@ var PausableTimers = class {
151
217
  const reasons = this.#entries.get(key)?.reasons ?? /* @__PURE__ */ new Set();
152
218
  const entry = { callback, startedAt: 0, remaining: delay, reasons };
153
219
  this.#entries.set(key, entry);
154
- if (reasons.size === 0) this.#arm(key, entry);
220
+ if (reasons.size === 0) this.#arm(key, entry, callback);
155
221
  }
156
222
  /**
157
223
  * Holds `key` for `reason` and banks the time left, with a floor of one
158
- * millisecond. Reports whether this call is the one that stopped a running
159
- * timer, so the caller can write its held-state hook exactly once.
224
+ * millisecond. A key with no timer is held all the same. Reports whether this
225
+ * call is the one that stopped a running timer, so the caller can write its
226
+ * held-state hook exactly once.
160
227
  */
161
228
  pause(key, reason) {
162
- const entry = this.#entries.get(key);
163
- if (entry === void 0) return false;
229
+ let entry = this.#entries.get(key);
230
+ if (entry === void 0) {
231
+ entry = { callback: null, startedAt: 0, remaining: 0, reasons: /* @__PURE__ */ new Set() };
232
+ this.#entries.set(key, entry);
233
+ }
164
234
  entry.reasons.add(reason);
165
235
  if (!this.#timers.has(key)) return false;
166
236
  this.#timers.clear(key);
@@ -169,20 +239,40 @@ var PausableTimers = class {
169
239
  }
170
240
  /**
171
241
  * Releases `reason` on `key`, arming the banked time again once no reason is
172
- * left. Reports whether this call is the one that started the timer again.
242
+ * left; a key released with no timer is forgotten. Reports whether this call
243
+ * is the one that started the timer again.
173
244
  */
174
245
  resume(key, reason) {
175
246
  const entry = this.#entries.get(key);
176
247
  if (entry === void 0) return false;
177
248
  entry.reasons.delete(reason);
178
249
  if (entry.reasons.size > 0 || this.#timers.has(key)) return false;
179
- this.#arm(key, entry);
250
+ const { callback } = entry;
251
+ if (callback === null) {
252
+ this.#entries.delete(key);
253
+ return false;
254
+ }
255
+ this.#arm(key, entry, callback);
180
256
  return true;
181
257
  }
258
+ /** Whether any reason holds `key`, with or without a timer. */
259
+ isHeld(key) {
260
+ return (this.#entries.get(key)?.reasons.size ?? 0) > 0;
261
+ }
182
262
  /** Whether `key` is armed or held — that is, whether this registry drives it at all. */
183
263
  tracks(key) {
184
264
  return this.#entries.has(key);
185
265
  }
266
+ /**
267
+ * Cancels `key`'s timer and keeps its hold, so a release arms nothing; a key
268
+ * nothing holds is forgotten.
269
+ */
270
+ disarm(key) {
271
+ this.#timers.clear(key);
272
+ const entry = this.#entries.get(key);
273
+ if (entry?.reasons.size) entry.callback = null;
274
+ else this.#entries.delete(key);
275
+ }
186
276
  /** Cancels `key`'s timer and drops its hold. */
187
277
  clear(key) {
188
278
  this.#timers.clear(key);
@@ -196,20 +286,25 @@ var PausableTimers = class {
196
286
  this.#timers.clearAll();
197
287
  this.#entries.clear();
198
288
  }
199
- /** Starts `entry`'s banked time running for `key`, and drops it as it fires. */
200
- #arm(key, entry) {
289
+ /** Starts `entry`'s banked time running for `key`, and drops it as `callback` fires. */
290
+ #arm(key, entry, callback) {
201
291
  entry.startedAt = Date.now();
202
292
  this.#timers.set(
203
293
  key,
204
294
  () => {
205
295
  this.#entries.delete(key);
206
- entry.callback();
296
+ callback();
207
297
  },
208
298
  entry.remaining
209
299
  );
210
300
  }
211
301
  };
212
302
 
303
+ // src/utils/target_selector.ts
304
+ function targetSelector(identifier, name) {
305
+ return `[data-${identifier}-target~="${name}"]`;
306
+ }
307
+
213
308
  // src/utils/transition_completion.ts
214
309
  function timeMs(value) {
215
310
  const trimmed = value.trim();
@@ -246,8 +341,12 @@ function maxTransitionTotalMs(style) {
246
341
 
247
342
  // src/controllers/flash_controller.ts
248
343
  var ASSERTIVE_TYPES = /* @__PURE__ */ new Set(["alert", "error"]);
249
- var MESSAGE_SELECTOR = '[data-stimeo--flash-target="message"]';
344
+ var MESSAGE_PART = "message";
250
345
  var FlashController = class extends Controller {
346
+ /** Selects the message parts, in the namespace this controller is registered under. */
347
+ get #messageSelector() {
348
+ return targetSelector(this.identifier, MESSAGE_PART);
349
+ }
251
350
  static targets = ["region", "message"];
252
351
  static values = {
253
352
  duration: { type: Number, default: 5e3 },
@@ -258,11 +357,33 @@ var FlashController = class extends Controller {
258
357
  static events = ["show", "dismiss", "reconcile"];
259
358
  /** Removal timers for the leaving transition; the auto-dismiss ones live below. */
260
359
  #timers = new SafeTimeout();
261
- /** Per-message auto-dismiss, held open while the message is hovered or focused. */
360
+ /**
361
+ * Per-message auto-dismiss held open while the message is hovered or focused, and
362
+ * the one record of which messages hover or focus holds, which the cap reads.
363
+ */
262
364
  #dismiss = new PausableTimers();
365
+ /**
366
+ * Auto-dismiss of the messages taken on while `pauseOnHover` is off. It runs on
367
+ * whatever holds the message; the hold itself still keeps the cap away.
368
+ */
369
+ #fixedDismiss = new KeyedTimers();
370
+ /** Applies the cap again once the event that released a hold has run its course. */
371
+ #reapply = new MicrotaskCoalescer(() => this.#reapplyCap());
372
+ /** Messages the pointer or focus moved into since the cap was last applied again. */
373
+ #spared = [];
374
+ /** The next pointer movement, listened for while a hover hold waits on it. */
375
+ #pointer = new ListenerSet();
376
+ /** Messages whose hover hold waits for the next pointer movement to be confirmed. */
377
+ #awaitingPointer = /* @__PURE__ */ new Set();
263
378
  #observer = null;
264
379
  /** Whether the controller is between `connect()` and `disconnect()`. */
265
380
  #connected = false;
381
+ /**
382
+ * The `max` the stack was last held to, or `null` before the first time. `connect()`
383
+ * applies the cap only when `max` is not that one, so a reconnect of the same instance
384
+ * leaves the stack as it was unless `max` changed while it was away.
385
+ */
386
+ #appliedMax = null;
266
387
  /** Messages already processed, in insertion order, to enforce `max` and avoid double work. */
267
388
  #order = [];
268
389
  /**
@@ -274,12 +395,28 @@ var FlashController = class extends Controller {
274
395
  #leaving = /* @__PURE__ */ new Set();
275
396
  #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
276
397
  #onEnter = (event) => this.#pause(event.currentTarget, event.type === "focusin" ? "focus" : "hover");
277
- #onLeave = (event) => this.#resume(event.currentTarget, event.type === "focusout" ? "focus" : "hover");
398
+ /**
399
+ * Releases hover or focus on a message. Focus moving between two of the message's
400
+ * own controls is no release, since `focusout` names a control still inside it; and
401
+ * the message the pointer or focus is moving into is spared when the release
402
+ * applies the cap again, because its own hold only arrives after this event.
403
+ */
404
+ #onLeave = (event) => {
405
+ const message = event.currentTarget;
406
+ const next = ownerOf(this.#order, event.relatedTarget);
407
+ if (next === message) return;
408
+ this.#resume(message, event.type === "focusout" ? "focus" : "hover", next);
409
+ };
278
410
  connect() {
279
411
  this.#connected = true;
412
+ this.#reapply.activate();
280
413
  for (const message of this.messageTargets) {
281
414
  if (this.#owns(message)) this.#process(message, true);
282
415
  }
416
+ if (!Object.is(this.#appliedMax, this.maxValue)) {
417
+ this.#appliedMax = this.maxValue;
418
+ this.#enforceMax();
419
+ }
283
420
  this.#syncObservation();
284
421
  this.#beforeCache.activate();
285
422
  }
@@ -289,6 +426,11 @@ var FlashController = class extends Controller {
289
426
  this.#stopObserving();
290
427
  this.#timers.clearAll();
291
428
  this.#dismiss.clearAll();
429
+ this.#fixedDismiss.clearAll();
430
+ this.#reapply.cancel();
431
+ this.#spared = [];
432
+ this.#pointer.dispose();
433
+ this.#awaitingPointer.clear();
292
434
  for (const message of this.#order) this.#unbindPause(message);
293
435
  this.#order.length = 0;
294
436
  this.#leaving.clear();
@@ -319,6 +461,19 @@ var FlashController = class extends Controller {
319
461
  regionTargetDisconnected() {
320
462
  this.#resync();
321
463
  }
464
+ /**
465
+ * Holds the stack to a changed `max`: lowering it below the count dismisses the
466
+ * oldest messages hover and focus do not hold, with reason `limit`, as an arrival
467
+ * past the cap does, while raising it or setting `0` dismisses nothing. Stimulus can
468
+ * also call this ahead of `connect()` — for an attribute that changed while the
469
+ * controller was away, and on every connect for an undeclared `max`, with its default;
470
+ * `connect()` decides then.
471
+ */
472
+ maxValueChanged() {
473
+ if (!this.#connected) return;
474
+ this.#appliedMax = this.maxValue;
475
+ this.#enforceMax();
476
+ }
322
477
  /**
323
478
  * Whether this controller owns `message`. Ownership is the current `region`'s
324
479
  * subtree: a message target anywhere else in the controller's scope is the
@@ -337,12 +492,16 @@ var FlashController = class extends Controller {
337
492
  * scheduled removal are cancelled. A move *within* the region keeps all of them —
338
493
  * which is why the element must still be a message to be treated as one: ownership
339
494
  * alone reads an in-place attribute rewrite as a move, and a node outside the target
340
- * set belongs to the consumer, so nothing here may dismiss it.
495
+ * set belongs to the consumer, so nothing here may dismiss it. A move may still have
496
+ * ended a hold, so those are read again.
341
497
  */
342
498
  messageTargetDisconnected(message) {
343
499
  if (!this.#connected) return;
344
- const moved = this.#owns(message) && message.matches(MESSAGE_SELECTOR);
345
- if (moved) return;
500
+ const moved = this.#owns(message) && message.matches(this.#messageSelector);
501
+ if (moved) {
502
+ this.#afterMove(message);
503
+ return;
504
+ }
346
505
  this.#forget(message);
347
506
  this.#leaving.delete(message);
348
507
  }
@@ -376,13 +535,11 @@ var FlashController = class extends Controller {
376
535
  this.#observer = null;
377
536
  }
378
537
  /**
379
- * Pause-on-hover/focus listeners, bound and unbound as a pair so the two sides
380
- * stay in sync. Binding is gated by `pauseOnHover`; unbinding is unconditional
381
- * and idempotent (a no-op when nothing was bound), which keeps teardown correct
382
- * even if `pauseOnHover` were ever toggled during a message's life.
538
+ * Hover and focus listeners, bound and unbound as a pair so the two sides stay in
539
+ * sync. Every message gets them, whatever `pauseOnHover` says, because the cap reads
540
+ * the holds they record; unbinding is idempotent (a no-op when nothing was bound).
383
541
  */
384
542
  #bindPause(message) {
385
- if (!this.pauseOnHoverValue) return;
386
543
  message.addEventListener("mouseenter", this.#onEnter);
387
544
  message.addEventListener("mouseleave", this.#onLeave);
388
545
  message.addEventListener("focusin", this.#onEnter);
@@ -397,7 +554,7 @@ var FlashController = class extends Controller {
397
554
  /** Dismisses the flash whose close control fired the event. */
398
555
  dismiss(event) {
399
556
  const target = event.currentTarget || event.target;
400
- const message = target?.closest(MESSAGE_SELECTOR);
557
+ const message = target?.closest(this.#messageSelector);
401
558
  if (message) this.#beginDismiss(message, "user");
402
559
  }
403
560
  /** Processes messages added after connect (Turbo Stream); their own role announces them. */
@@ -405,20 +562,25 @@ var FlashController = class extends Controller {
405
562
  for (const mutation of mutations) {
406
563
  for (const node of mutation.addedNodes) {
407
564
  if (!(node instanceof HTMLElement)) continue;
408
- if (node.matches(MESSAGE_SELECTOR)) this.#process(node, false);
409
- for (const message of node.querySelectorAll(MESSAGE_SELECTOR)) {
565
+ if (node.matches(this.#messageSelector)) this.#process(node, false);
566
+ for (const message of node.querySelectorAll(this.#messageSelector)) {
410
567
  this.#process(message, false);
411
568
  }
412
569
  }
413
570
  }
414
571
  }
415
572
  /**
416
- * Applies role/state, wires pause listeners, schedules auto-dismiss, and either
417
- * bridges to the Announcer (`bridge`, for initial flashes) or leaves the message's
418
- * own role to do the announcing (dynamic inserts). Idempotent per message.
573
+ * Applies role/state, wires pause listeners, takes the holds the message already has,
574
+ * and schedules auto-dismiss. A message `connect()` takes on (`atConnect`) is bridged
575
+ * to the Announcer and meets no cap here, `connect()` holding the stack to it; a later
576
+ * insert announces through its own role and meets the cap as it arrives. Idempotent
577
+ * per message, and a node that has left the region by the time its insertion is
578
+ * reported is not taken on.
419
579
  */
420
- #process(message, bridge) {
421
- if (this.#order.includes(message) || this.#leaving.has(message)) return;
580
+ #process(message, atConnect) {
581
+ if (this.#order.includes(message) || this.#leaving.has(message) || !this.#owns(message)) {
582
+ return;
583
+ }
422
584
  const type = message.getAttribute("data-flash-type") ?? "";
423
585
  const assertive = ASSERTIVE_TYPES.has(type);
424
586
  if (!message.hasAttribute("role")) {
@@ -427,44 +589,141 @@ var FlashController = class extends Controller {
427
589
  message.setAttribute("data-flash-state", "visible");
428
590
  this.#order.push(message);
429
591
  this.#bindPause(message);
592
+ this.#readHolds(message);
430
593
  const text = message.textContent?.trim() ?? "";
431
594
  this.dispatch("show", { target: message, detail: { type, message: text } });
432
- if (bridge) announce(text, { assertive });
595
+ if (atConnect) announce(text, { assertive });
433
596
  this.#startTimer(message);
434
- this.#enforceMax();
597
+ if (!atConnect) this.#enforceMax([message]);
435
598
  }
436
- /** Removes the oldest visible flashes once the count exceeds `max` (0 = unlimited). */
437
- #enforceMax() {
438
- if (this.maxValue <= 0) return;
439
- while (this.#order.length > this.maxValue) {
440
- const oldest = this.#order[0];
441
- if (!oldest) break;
442
- this.#beginDismiss(oldest, "limit");
599
+ /**
600
+ * Dismisses the oldest messages with reason `limit` while more than `max` are shown
601
+ * (0 or less, or not a finite number, means no cap), passing over every message hover
602
+ * or focus holds and those in `spared` — the message just taken on, or the ones the
603
+ * pointer or focus moved into. Only messages still in the region count or go: one a
604
+ * script has just taken out stays on the stack until its removal is reported. With
605
+ * nothing else left to go the stack stays over the cap. The loop walks a copy and
606
+ * re-reads the count on every step, because a `dismiss` listener may already have
607
+ * changed both.
608
+ *
609
+ * @stimeoRenderRoot
610
+ */
611
+ #enforceMax(spared = []) {
612
+ if (!Number.isFinite(this.maxValue) || this.maxValue <= 0) return;
613
+ for (const message of [...this.#order]) {
614
+ if (this.#shownCount() <= this.maxValue) return;
615
+ if (this.#owns(message) && !spared.includes(message) && !this.#dismiss.isHeld(message)) {
616
+ this.#beginDismiss(message, "limit");
617
+ }
443
618
  }
444
619
  }
445
- /** Arms a message's auto-dismiss; a non-positive `duration` means it never expires. */
620
+ /** How many messages the stack shows: the ones taken on that are still in the region. */
621
+ #shownCount() {
622
+ return this.#order.filter((message) => this.#owns(message)).length;
623
+ }
624
+ /**
625
+ * Arms a message's auto-dismiss; a non-positive `duration` means it never expires.
626
+ * The timer waits out hover and focus when `pauseOnHover` is on, and runs on
627
+ * regardless when it is off.
628
+ *
629
+ * @stimeoRuntimeOnly `duration` is the delay of the one dismissal timer this call arms,
630
+ * and `pauseOnHover` whether hover and focus hold that timer.
631
+ */
446
632
  #startTimer(message) {
447
633
  if (this.durationValue <= 0) return;
448
- this.#dismiss.set(message, () => this.#beginDismiss(message, "timeout"), this.durationValue);
634
+ const dismiss = () => this.#beginDismiss(message, "timeout");
635
+ if (this.pauseOnHoverValue) this.#dismiss.set(message, dismiss, this.durationValue);
636
+ else this.#fixedDismiss.set(message, dismiss, this.durationValue);
449
637
  }
450
638
  /**
451
- * Holds a message's auto-dismiss open while it is hovered or focused
452
- * (WCAG 2.2 2.2.1). Hover and focus are independent reasons, and the registry
453
- * banks the time left on the first of them and waits for the last.
639
+ * Records hover or focus on a message. Hover and focus are independent reasons:
640
+ * the registry banks the time left on the first of them and waits for the last
641
+ * (WCAG 2.2 2.2.1), and the cap passes over the message while either is held.
454
642
  */
455
643
  #pause(message, reason) {
456
644
  this.#dismiss.pause(message, reason);
457
645
  }
458
- /** Releases one reason, resuming the banked time once no reason is left. */
459
- #resume(message, reason) {
646
+ /**
647
+ * Releases one reason, resuming the banked time once no reason is left. Releasing
648
+ * the last one applies the cap again, sparing `spare`.
649
+ */
650
+ #resume(message, reason, spare) {
651
+ const held = this.#dismiss.isHeld(message);
460
652
  this.#dismiss.resume(message, reason);
653
+ if (held && !this.#dismiss.isHeld(message)) this.#capLater(spare);
654
+ }
655
+ /**
656
+ * Applies the cap again once the current event is over, sparing `spare`. A hold can
657
+ * end in a `focusout` the engine fires while it is itself taking the node out for a
658
+ * script's move or removal, and dismissing the message inside that event would pull
659
+ * the node from under the caller's own call.
660
+ */
661
+ #capLater(spare) {
662
+ if (spare) this.#spared.push(spare);
663
+ this.#reapply.schedule();
664
+ }
665
+ /** Applies the cap again, sparing every message gathered since the last pass. */
666
+ #reapplyCap() {
667
+ const spared = this.#spared;
668
+ this.#spared = [];
669
+ this.#enforceMax(spared);
670
+ }
671
+ /**
672
+ * Reads the holds of a message that moved within the region. Focus is read at once:
673
+ * an ordinary move ends it with a `focusout` and `moveBefore` keeps it. Where the
674
+ * pointer is, nothing says until it moves again: no `mouseleave` reaches a moved node,
675
+ * and `:hover` can answer from before the move. A message still held waits for that
676
+ * movement.
677
+ */
678
+ #afterMove(message) {
679
+ if (!message.contains(document.activeElement)) this.#resume(message, "focus", null);
680
+ if (this.#dismiss.isHeld(message)) this.#awaitPointer(message);
461
681
  }
462
- /** Releases every per-message resource: timer, stacking slot, pause listeners. */
682
+ /**
683
+ * Takes the holds a message already has as it is taken on: focus inside it, and the
684
+ * pointer over it by `:hover`. That answer can date from before the message came here,
685
+ * so a hover hold taken on it waits for the next pointer movement to be confirmed.
686
+ */
687
+ #readHolds(message) {
688
+ if (message.contains(document.activeElement)) this.#pause(message, "focus");
689
+ if (!message.matches(":hover")) return;
690
+ this.#pause(message, "hover");
691
+ this.#awaitPointer(message);
692
+ }
693
+ /** Leaves `message`'s hover hold to be confirmed or let go on the next pointer movement. */
694
+ #awaitPointer(message) {
695
+ this.#awaitingPointer.add(message);
696
+ this.#pointer.add(document, "pointermove", this.#onPointerMove, {
697
+ capture: true,
698
+ passive: true
699
+ });
700
+ }
701
+ /**
702
+ * Reads `:hover` once the pointer has moved, when the answer is current, and lets go of
703
+ * the hover hold of every waiting message the pointer is not over. Listens once.
704
+ */
705
+ #onPointerMove = () => {
706
+ this.#pointer.dispose();
707
+ const waiting = [...this.#awaitingPointer];
708
+ this.#awaitingPointer.clear();
709
+ for (const message of waiting) {
710
+ if (!message.matches(":hover")) this.#resume(message, "hover", null);
711
+ }
712
+ };
713
+ /**
714
+ * Releases every per-message resource: timers, hold, stacking slot, pause listeners,
715
+ * and the wait on the pointer. A message something still held counts as released: the
716
+ * cap is applied again.
717
+ */
463
718
  #forget(message) {
719
+ if (this.#dismiss.isHeld(message)) this.#capLater(null);
464
720
  this.#dismiss.clear(message);
721
+ this.#fixedDismiss.clear(message);
465
722
  const index = this.#order.indexOf(message);
466
723
  if (index !== -1) this.#order.splice(index, 1);
467
724
  this.#unbindPause(message);
725
+ this.#awaitingPointer.delete(message);
726
+ if (this.#awaitingPointer.size === 0) this.#pointer.dispose();
468
727
  }
469
728
  /** Marks a message leaving, then removes it after its CSS transition and emits dismiss. */
470
729
  #beginDismiss(message, reason) {