stimeo-ui 0.14.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 (279) hide show
  1. package/CHANGELOG.md +212 -0
  2. package/README.md +120 -0
  3. package/dist/cable/index.js +123 -29
  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 +96 -62
  12. package/dist/controllers/announcer_controller.js.map +1 -1
  13. package/dist/controllers/auto_submit_controller.js +83 -7
  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 +38 -11
  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 +101 -23
  24. package/dist/controllers/calendar_controller.js +340 -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 +81 -12
  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 +85 -17
  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/count_up_controller.js.map +1 -1
  57. package/dist/controllers/countdown_controller.d.ts +30 -1
  58. package/dist/controllers/countdown_controller.js +129 -26
  59. package/dist/controllers/countdown_controller.js.map +1 -1
  60. package/dist/controllers/currency_input_controller.d.ts +77 -16
  61. package/dist/controllers/currency_input_controller.js +221 -67
  62. package/dist/controllers/currency_input_controller.js.map +1 -1
  63. package/dist/controllers/data_grid_controller.d.ts +63 -18
  64. package/dist/controllers/data_grid_controller.js +195 -29
  65. package/dist/controllers/data_grid_controller.js.map +1 -1
  66. package/dist/controllers/date_range_picker_controller.d.ts +41 -6
  67. package/dist/controllers/date_range_picker_controller.js +151 -30
  68. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  69. package/dist/controllers/dialog_controller.d.ts +10 -3
  70. package/dist/controllers/dialog_controller.js +35 -8
  71. package/dist/controllers/dialog_controller.js.map +1 -1
  72. package/dist/controllers/direct_upload_controller.js +22 -4
  73. package/dist/controllers/direct_upload_controller.js.map +1 -1
  74. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  75. package/dist/controllers/dirty_form_controller.js +46 -13
  76. package/dist/controllers/dirty_form_controller.js.map +1 -1
  77. package/dist/controllers/dismissible_controller.js +1 -0
  78. package/dist/controllers/dismissible_controller.js.map +1 -1
  79. package/dist/controllers/drawer_controller.d.ts +18 -10
  80. package/dist/controllers/drawer_controller.js +54 -19
  81. package/dist/controllers/drawer_controller.js.map +1 -1
  82. package/dist/controllers/dropdown_controller.d.ts +9 -3
  83. package/dist/controllers/dropdown_controller.js +36 -9
  84. package/dist/controllers/dropdown_controller.js.map +1 -1
  85. package/dist/controllers/editable_controller.js +34 -0
  86. package/dist/controllers/editable_controller.js.map +1 -1
  87. package/dist/controllers/file_dropzone_controller.js +144 -51
  88. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  89. package/dist/controllers/filter_controller.d.ts +10 -4
  90. package/dist/controllers/filter_controller.js +20 -6
  91. package/dist/controllers/filter_controller.js.map +1 -1
  92. package/dist/controllers/flash_controller.d.ts +26 -6
  93. package/dist/controllers/flash_controller.js +432 -71
  94. package/dist/controllers/flash_controller.js.map +1 -1
  95. package/dist/controllers/focus_controller.js +1 -0
  96. package/dist/controllers/focus_controller.js.map +1 -1
  97. package/dist/controllers/form_field_controller.js +7 -5
  98. package/dist/controllers/form_field_controller.js.map +1 -1
  99. package/dist/controllers/form_validation_controller.js +19 -13
  100. package/dist/controllers/form_validation_controller.js.map +1 -1
  101. package/dist/controllers/frame_loading_controller.js +45 -8
  102. package/dist/controllers/frame_loading_controller.js.map +1 -1
  103. package/dist/controllers/highlight_controller.js +82 -25
  104. package/dist/controllers/highlight_controller.js.map +1 -1
  105. package/dist/controllers/hover_card_controller.d.ts +10 -2
  106. package/dist/controllers/hover_card_controller.js +40 -14
  107. package/dist/controllers/hover_card_controller.js.map +1 -1
  108. package/dist/controllers/idle_controller.d.ts +16 -3
  109. package/dist/controllers/idle_controller.js +90 -5
  110. package/dist/controllers/idle_controller.js.map +1 -1
  111. package/dist/controllers/input_mask_controller.d.ts +5 -2
  112. package/dist/controllers/input_mask_controller.js +65 -9
  113. package/dist/controllers/input_mask_controller.js.map +1 -1
  114. package/dist/controllers/intersection_controller.js +3 -0
  115. package/dist/controllers/intersection_controller.js.map +1 -1
  116. package/dist/controllers/lazy_frame_controller.js +11 -2
  117. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  118. package/dist/controllers/listbox_controller.d.ts +50 -8
  119. package/dist/controllers/listbox_controller.js +203 -45
  120. package/dist/controllers/listbox_controller.js.map +1 -1
  121. package/dist/controllers/local_time_controller.js +10 -5
  122. package/dist/controllers/local_time_controller.js.map +1 -1
  123. package/dist/controllers/masonry_controller.d.ts +3 -3
  124. package/dist/controllers/masonry_controller.js +31 -15
  125. package/dist/controllers/masonry_controller.js.map +1 -1
  126. package/dist/controllers/menu_controller.d.ts +9 -3
  127. package/dist/controllers/menu_controller.js +45 -16
  128. package/dist/controllers/menu_controller.js.map +1 -1
  129. package/dist/controllers/menubar_controller.d.ts +11 -0
  130. package/dist/controllers/menubar_controller.js +58 -24
  131. package/dist/controllers/menubar_controller.js.map +1 -1
  132. package/dist/controllers/meter_controller.js +9 -5
  133. package/dist/controllers/meter_controller.js.map +1 -1
  134. package/dist/controllers/multi_select_controller.d.ts +18 -3
  135. package/dist/controllers/multi_select_controller.js +278 -104
  136. package/dist/controllers/multi_select_controller.js.map +1 -1
  137. package/dist/controllers/navigation_menu_controller.d.ts +11 -0
  138. package/dist/controllers/navigation_menu_controller.js +48 -15
  139. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  140. package/dist/controllers/nested_form_controller.js +37 -8
  141. package/dist/controllers/nested_form_controller.js.map +1 -1
  142. package/dist/controllers/network_status_controller.js +9 -1
  143. package/dist/controllers/network_status_controller.js.map +1 -1
  144. package/dist/controllers/number_input_controller.d.ts +36 -8
  145. package/dist/controllers/number_input_controller.js +124 -21
  146. package/dist/controllers/number_input_controller.js.map +1 -1
  147. package/dist/controllers/optimistic_controller.js +42 -5
  148. package/dist/controllers/optimistic_controller.js.map +1 -1
  149. package/dist/controllers/otp_controller.d.ts +22 -7
  150. package/dist/controllers/otp_controller.js +198 -55
  151. package/dist/controllers/otp_controller.js.map +1 -1
  152. package/dist/controllers/overflow_indicator_controller.d.ts +8 -12
  153. package/dist/controllers/overflow_indicator_controller.js +115 -21
  154. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  155. package/dist/controllers/overflow_menu_controller.d.ts +26 -6
  156. package/dist/controllers/overflow_menu_controller.js +141 -44
  157. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  158. package/dist/controllers/pagination_controller.d.ts +19 -11
  159. package/dist/controllers/pagination_controller.js +74 -28
  160. package/dist/controllers/pagination_controller.js.map +1 -1
  161. package/dist/controllers/password_reveal_controller.d.ts +15 -1
  162. package/dist/controllers/password_reveal_controller.js +59 -2
  163. package/dist/controllers/password_reveal_controller.js.map +1 -1
  164. package/dist/controllers/persist_controller.js +30 -8
  165. package/dist/controllers/persist_controller.js.map +1 -1
  166. package/dist/controllers/pointer_drag_controller.js +131 -52
  167. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  168. package/dist/controllers/popover_controller.d.ts +9 -3
  169. package/dist/controllers/popover_controller.js +45 -11
  170. package/dist/controllers/popover_controller.js.map +1 -1
  171. package/dist/controllers/portal_controller.d.ts +1 -1
  172. package/dist/controllers/portal_controller.js +6 -2
  173. package/dist/controllers/portal_controller.js.map +1 -1
  174. package/dist/controllers/preview_guard_controller.js +16 -1
  175. package/dist/controllers/preview_guard_controller.js.map +1 -1
  176. package/dist/controllers/progress_controller.js +8 -4
  177. package/dist/controllers/progress_controller.js.map +1 -1
  178. package/dist/controllers/radio_group_controller.d.ts +6 -4
  179. package/dist/controllers/radio_group_controller.js +42 -17
  180. package/dist/controllers/radio_group_controller.js.map +1 -1
  181. package/dist/controllers/range_slider_controller.d.ts +49 -1
  182. package/dist/controllers/range_slider_controller.js +88 -42
  183. package/dist/controllers/range_slider_controller.js.map +1 -1
  184. package/dist/controllers/rating_controller.d.ts +14 -3
  185. package/dist/controllers/rating_controller.js +39 -15
  186. package/dist/controllers/rating_controller.js.map +1 -1
  187. package/dist/controllers/read_more_controller.d.ts +24 -2
  188. package/dist/controllers/read_more_controller.js +100 -7
  189. package/dist/controllers/read_more_controller.js.map +1 -1
  190. package/dist/controllers/reading_progress_controller.js +65 -19
  191. package/dist/controllers/reading_progress_controller.js.map +1 -1
  192. package/dist/controllers/relative_time_controller.js +10 -5
  193. package/dist/controllers/relative_time_controller.js.map +1 -1
  194. package/dist/controllers/resizable_controller.d.ts +16 -2
  195. package/dist/controllers/resizable_controller.js +82 -22
  196. package/dist/controllers/resizable_controller.js.map +1 -1
  197. package/dist/controllers/scroll_area_controller.js +75 -27
  198. package/dist/controllers/scroll_area_controller.js.map +1 -1
  199. package/dist/controllers/scroll_restore_controller.js +37 -16
  200. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  201. package/dist/controllers/scroll_visibility_controller.js +49 -30
  202. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  203. package/dist/controllers/scrollspy_controller.d.ts +3 -2
  204. package/dist/controllers/scrollspy_controller.js +71 -26
  205. package/dist/controllers/scrollspy_controller.js.map +1 -1
  206. package/dist/controllers/separator_controller.d.ts +41 -14
  207. package/dist/controllers/separator_controller.js +66 -37
  208. package/dist/controllers/separator_controller.js.map +1 -1
  209. package/dist/controllers/sidebar_controller.d.ts +20 -3
  210. package/dist/controllers/sidebar_controller.js +77 -18
  211. package/dist/controllers/sidebar_controller.js.map +1 -1
  212. package/dist/controllers/skeleton_controller.js +6 -1
  213. package/dist/controllers/skeleton_controller.js.map +1 -1
  214. package/dist/controllers/slider_controller.d.ts +45 -7
  215. package/dist/controllers/slider_controller.js +82 -47
  216. package/dist/controllers/slider_controller.js.map +1 -1
  217. package/dist/controllers/smart_sticky_header_controller.js +60 -26
  218. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  219. package/dist/controllers/sortable_controller.js +17 -2
  220. package/dist/controllers/sortable_controller.js.map +1 -1
  221. package/dist/controllers/spinner_controller.js +10 -2
  222. package/dist/controllers/spinner_controller.js.map +1 -1
  223. package/dist/controllers/step_indicator_controller.d.ts +19 -17
  224. package/dist/controllers/step_indicator_controller.js +18 -17
  225. package/dist/controllers/step_indicator_controller.js.map +1 -1
  226. package/dist/controllers/stepper_controller.d.ts +34 -9
  227. package/dist/controllers/stepper_controller.js +101 -19
  228. package/dist/controllers/stepper_controller.js.map +1 -1
  229. package/dist/controllers/stick_to_bottom_controller.d.ts +19 -0
  230. package/dist/controllers/stick_to_bottom_controller.js +104 -8
  231. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  232. package/dist/controllers/submit_once_controller.d.ts +3 -2
  233. package/dist/controllers/submit_once_controller.js +45 -9
  234. package/dist/controllers/submit_once_controller.js.map +1 -1
  235. package/dist/controllers/switch_controller.d.ts +29 -7
  236. package/dist/controllers/switch_controller.js +101 -10
  237. package/dist/controllers/switch_controller.js.map +1 -1
  238. package/dist/controllers/tabs_controller.d.ts +12 -0
  239. package/dist/controllers/tabs_controller.js +21 -2
  240. package/dist/controllers/tabs_controller.js.map +1 -1
  241. package/dist/controllers/tags_input_controller.d.ts +15 -3
  242. package/dist/controllers/tags_input_controller.js +209 -59
  243. package/dist/controllers/tags_input_controller.js.map +1 -1
  244. package/dist/controllers/textarea_autosize_controller.js +29 -3
  245. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  246. package/dist/controllers/theme_controller.d.ts +20 -3
  247. package/dist/controllers/theme_controller.js +64 -14
  248. package/dist/controllers/theme_controller.js.map +1 -1
  249. package/dist/controllers/time_picker_controller.js +23 -8
  250. package/dist/controllers/time_picker_controller.js.map +1 -1
  251. package/dist/controllers/toast_controller.d.ts +55 -15
  252. package/dist/controllers/toast_controller.js +451 -105
  253. package/dist/controllers/toast_controller.js.map +1 -1
  254. package/dist/controllers/toggle_group_controller.d.ts +49 -7
  255. package/dist/controllers/toggle_group_controller.js +159 -23
  256. package/dist/controllers/toggle_group_controller.js.map +1 -1
  257. package/dist/controllers/toolbar_controller.js +32 -0
  258. package/dist/controllers/toolbar_controller.js.map +1 -1
  259. package/dist/controllers/tooltip_controller.d.ts +8 -0
  260. package/dist/controllers/tooltip_controller.js +39 -13
  261. package/dist/controllers/tooltip_controller.js.map +1 -1
  262. package/dist/controllers/transition_controller.js +4 -0
  263. package/dist/controllers/transition_controller.js.map +1 -1
  264. package/dist/controllers/tree_view_controller.d.ts +39 -8
  265. package/dist/controllers/tree_view_controller.js +169 -16
  266. package/dist/controllers/tree_view_controller.js.map +1 -1
  267. package/dist/index.d.ts +28 -1
  268. package/dist/index.js +5002 -1911
  269. package/dist/index.js.map +1 -1
  270. package/dist/inspector/cli.d.ts +72 -6
  271. package/dist/inspector/cli.js +262 -51
  272. package/dist/inspector/cli.js.map +1 -1
  273. package/dist/inspector/cli_bin.js +309 -51
  274. package/dist/inspector/cli_bin.js.map +1 -1
  275. package/dist/inspector/examples.json +40 -40
  276. package/dist/inspector/manifest.json +529 -61
  277. package/dist/positioning/index.js +2 -0
  278. package/dist/positioning/index.js.map +1 -1
  279. package/package.json +2 -2
@@ -90,12 +90,91 @@ var BeforeCacheReset = class _BeforeCacheReset {
90
90
  }
91
91
  };
92
92
 
93
+ // src/utils/detach_gate.ts
94
+ var DetachGate = class _DetachGate {
95
+ /** Set while a probe is queued, waiting for a reconnect to cancel it. */
96
+ #pending = false;
97
+ /**
98
+ * True while a probe is queued — the last disconnect was ambiguous and no
99
+ * reconnect has cancelled it yet. Read it from `connect()` to tell the
100
+ * reconnect half of an in-page move from a first connect: a controller whose
101
+ * initialisation restarts a measurement (a min-duration floor, an elapsed
102
+ * counter) must skip it for the move, where nothing actually restarted.
103
+ */
104
+ get pending() {
105
+ return this.#pending;
106
+ }
107
+ /**
108
+ * True when the disconnect is definitely a real detach — the element left
109
+ * the document, or `data-controller` no longer lists the identifier. False
110
+ * means ambiguous (in-page move or observed-root exit), NOT "alive".
111
+ */
112
+ static isDetached(host) {
113
+ if (!host.element.isConnected) return true;
114
+ const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
115
+ return !tokens.includes(host.identifier);
116
+ }
117
+ /**
118
+ * Call from `disconnect()`: runs `teardown` synchronously on a definite
119
+ * detach (fast path), otherwise defers it one microtask — a reconnect
120
+ * ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.
121
+ * One microtask is the whole probe window: Stimulus reconnects a moved
122
+ * element within the same mutation batch, before the checkpoint drains.
123
+ */
124
+ disconnected(host, teardown) {
125
+ if (_DetachGate.isDetached(host)) {
126
+ this.#pending = false;
127
+ teardown();
128
+ return;
129
+ }
130
+ this.#pending = true;
131
+ queueMicrotask(() => {
132
+ if (!this.#pending) return;
133
+ this.#pending = false;
134
+ teardown();
135
+ });
136
+ }
137
+ /**
138
+ * Disarms a pending probe. Call from `connect()` (the reconnect that proves
139
+ * an in-page move) and from the head of any teardown path not routed through
140
+ * {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can
141
+ * never run the teardown a second time.
142
+ */
143
+ cancel() {
144
+ this.#pending = false;
145
+ }
146
+ };
147
+
93
148
  // src/utils/event_owner.ts
94
149
  function ownerIndex(candidates, node) {
95
150
  if (!(node instanceof Node)) return -1;
96
151
  return candidates.findIndex((candidate) => candidate.contains(node));
97
152
  }
98
153
 
154
+ // src/utils/listener_set.ts
155
+ var ListenerSet = class {
156
+ /** The generation every `add` joins until the next `dispose()`. */
157
+ #abort = new AbortController();
158
+ /**
159
+ * Attaches `handler` to the open generation, exactly as the caller spelled it.
160
+ *
161
+ * The set supplies the signal, so `options` carries everything else the DOM
162
+ * accepts — `capture` included, which has to match at release time and no
163
+ * longer has a second place to drift from.
164
+ */
165
+ add(target, type, handler, options) {
166
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
167
+ }
168
+ /**
169
+ * Releases every listener of the open generation, synchronously, and opens the
170
+ * next one. Idempotent, and safe before anything has been added.
171
+ */
172
+ dispose() {
173
+ this.#abort.abort();
174
+ this.#abort = new AbortController();
175
+ }
176
+ };
177
+
99
178
  // src/utils/microtask_coalescer.ts
100
179
  var MicrotaskCoalescer = class {
101
180
  #run;
@@ -219,11 +298,61 @@ var SafeInterval = class extends TimerRegistry {
219
298
  }
220
299
  };
221
300
 
301
+ // src/utils/state_regions.ts
302
+ var StateRegions = class {
303
+ #whenTrue;
304
+ #whenFalse;
305
+ #taken = /* @__PURE__ */ new WeakSet();
306
+ constructor(options) {
307
+ this.#whenTrue = options.whenTrue;
308
+ this.#whenFalse = options.whenFalse ?? null;
309
+ }
310
+ /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
311
+ reflect(host, isTrue) {
312
+ const shown = this.#inside(host, this.#whenTrue());
313
+ if (!this.#whenFalse) {
314
+ for (const region of shown) this.#write(region, !isTrue);
315
+ return;
316
+ }
317
+ const hiddenSide = this.#inside(host, this.#whenFalse());
318
+ if (shown.length === 0 || hiddenSide.length === 0) {
319
+ for (const region of shown) this.#give(region);
320
+ for (const region of hiddenSide) this.#give(region);
321
+ return;
322
+ }
323
+ for (const region of shown) this.#write(region, !isTrue);
324
+ for (const region of hiddenSide) this.#write(region, isTrue);
325
+ }
326
+ /** Writes `hidden` where it moves, noting which regions it takes out of view. */
327
+ #write(region, hidden) {
328
+ if (hidden) this.#taken.add(region);
329
+ else this.#taken.delete(region);
330
+ if (region.hidden !== hidden) region.hidden = hidden;
331
+ }
332
+ /** Returns a region this instance hid; one it never hid keeps what it carries. */
333
+ #give(region) {
334
+ if (this.#taken.delete(region)) region.hidden = false;
335
+ }
336
+ /** The declared regions that sit within `host`, which may be the host itself. */
337
+ #inside(host, regions) {
338
+ return regions.filter((region) => host.contains(region));
339
+ }
340
+ };
341
+
222
342
  // src/controllers/carousel_controller.ts
223
343
  var DEFAULT_INTERVAL = 5e3;
224
344
  var OBSERVED_ATTRIBUTES = ["aria-selected", "data-state", "hidden"];
225
345
  var CarouselController = class extends Controller {
226
- static targets = ["slide", "viewport", "prev", "next", "picker", "playToggle"];
346
+ static targets = [
347
+ "slide",
348
+ "viewport",
349
+ "prev",
350
+ "next",
351
+ "picker",
352
+ "playToggle",
353
+ "onLabel",
354
+ "offLabel"
355
+ ];
227
356
  static values = {
228
357
  autoplay: { type: Boolean, default: false },
229
358
  interval: { type: Number, default: DEFAULT_INTERVAL },
@@ -248,7 +377,16 @@ var CarouselController = class extends Controller {
248
377
  #ariaLive = new AttributeLease("aria-live");
249
378
  /** Pairs with {@link CarouselController.#ariaLive}: only the changed slide is read. */
250
379
  #ariaAtomic = new AttributeLease("aria-atomic");
380
+ /** The label pair a play toggle may carry, one half per rotation state. */
381
+ #labels = new StateRegions({
382
+ whenTrue: () => this.onLabelTargets,
383
+ whenFalse: () => this.offLabelTargets
384
+ });
251
385
  #beforeCache = new BeforeCacheReset(() => this.#returnLeases());
386
+ /** Tells an in-page move, which keeps the rotation, from a real detach. */
387
+ #gate = new DetachGate();
388
+ /** The next pointer movement, listened for while a pointer suspension waits on it. */
389
+ #pointer = new ListenerSet();
252
390
  /**
253
391
  * Events an authored action binding already took. The delegated listener runs
254
392
  * later — it sits on the controller element, above every control — so it can
@@ -268,12 +406,17 @@ var CarouselController = class extends Controller {
268
406
  #activeSlide = null;
269
407
  /** Slide count at the last resolved state, so a changed total is reportable. */
270
408
  #total = 0;
271
- /** Pointer rests on the carousel: a suspension that lifts on `mouseleave`. */
409
+ /**
410
+ * Pointer rests on the carousel: a suspension that lifts on `mouseleave`, or on the
411
+ * pointer movement that confirms a suspension a move or a connect left unconfirmed.
412
+ */
272
413
  #pointerPaused = false;
273
414
  /** Focus is inside the carousel: a suspension that lifts when it leaves. */
274
415
  #focusPaused = false;
275
416
  /** The tab is in the background: a suspension that lifts when it returns. */
276
417
  #hiddenPaused = false;
418
+ /** Reduced motion holds back the intent declared at connect, until a press or a new one. */
419
+ #motionHold = false;
277
420
  /** Id of the live autoplay interval, or null when stopped. */
278
421
  #timerId = null;
279
422
  /** Delay the live interval was armed with, so an `interval` change re-arms it. */
@@ -289,17 +432,24 @@ var CarouselController = class extends Controller {
289
432
  * Renders the initial slide, wires the delegated listeners, and starts autoplay
290
433
  * when requested.
291
434
  *
292
- * Every suspension is re-derived from the environment rather than carried, so an
293
- * in-page move — which Stimulus delivers to the *same* controller instance as
294
- * `disconnect()` then `connect()` — cannot strand the carousel in a suspension
295
- * whose lifting event will never arrive. The attribute observer starts after the
296
- * first render so the controller's own opening writes are not fed back to it.
435
+ * Focus, the tab's visibility and the reduced-motion hold are read afresh. The
436
+ * pointer is not something the document can answer yet: no `mouseleave` reaches a
437
+ * node that moved, and `:hover` can answer from before the move until the pointer
438
+ * moves again. So an in-page move — which Stimulus delivers to the *same* controller
439
+ * instance as `disconnect()` then `connect()` — keeps the pointer suspension it had,
440
+ * `:hover` reading true adds one, and either waits for the next pointer movement to
441
+ * be confirmed or lifted. A rotation the move carried is still running, so the edges
442
+ * are judged against it. The attribute observer starts after the first render so the
443
+ * controller's own opening writes are not fed back to it.
297
444
  */
298
445
  connect() {
299
- this.#pointerPaused = this.element.matches(":hover");
446
+ const moved = this.#gate.pending;
447
+ this.#gate.cancel();
448
+ this.#pointerPaused = moved && this.#pointerPaused || this.element.matches(":hover");
449
+ if (this.#pointerPaused) this.#awaitPointer();
300
450
  this.#focusPaused = this.element.contains(document.activeElement);
301
451
  this.#hiddenPaused = document.visibilityState === "hidden";
302
- if (this.autoplayValue && prefersReducedMotion()) this.autoplayValue = false;
452
+ this.#motionHold = prefersReducedMotion();
303
453
  this.#index = this.#resolveIndex();
304
454
  this.#render({ focus: false });
305
455
  this.#total = this.slideTargets.length;
@@ -321,9 +471,15 @@ var CarouselController = class extends Controller {
321
471
  attributeFilter: OBSERVED_ATTRIBUTES
322
472
  });
323
473
  }
324
- /** Releases every listener and observer, returns the leased ARIA, drops the suspensions. */
474
+ /**
475
+ * Releases every listener and observer, returns the leased ARIA, drops the focus
476
+ * and tab suspensions. The rotation goes through the detach gate: a reconnect in
477
+ * the same batch — an in-page move — keeps it, and the pointer suspension with it;
478
+ * anything else ends it.
479
+ */
325
480
  disconnect() {
326
481
  this.#connected = false;
482
+ this.#pointer.dispose();
327
483
  this.element.removeEventListener("click", this.#onClick);
328
484
  this.element.removeEventListener("keydown", this.#onKeydown);
329
485
  this.element.removeEventListener("focusin", this.#onFocusin);
@@ -334,14 +490,12 @@ var CarouselController = class extends Controller {
334
490
  this.#observer?.disconnect();
335
491
  this.#observer = null;
336
492
  this.#reconcileTargets.cancel();
337
- this.#intervals.clearAll();
338
- this.#timerId = null;
339
- this.#pointerPaused = false;
340
493
  this.#focusPaused = false;
341
494
  this.#hiddenPaused = false;
342
495
  this.#activeSlide = null;
343
496
  this.#returnLeases();
344
497
  this.#beforeCache.deactivate();
498
+ this.#gate.disconnected(this, () => this.#endRun());
345
499
  }
346
500
  /**
347
501
  * Re-establishes the single selected picker when one is added after connect.
@@ -368,19 +522,46 @@ var CarouselController = class extends Controller {
368
522
  slideTargetDisconnected() {
369
523
  this.#reconcileTargets.schedule();
370
524
  }
371
- /** Follows a rotation intent the page changed at runtime. */
372
- autoplayValueChanged() {
525
+ /**
526
+ * Publishes the rotation state on a toggle that joins a connected carousel.
527
+ *
528
+ * A toggle the markup already carries connects ahead of `connect()`, where the
529
+ * suspensions and the reduced-motion hold are not settled yet; the opening pass
530
+ * covers it.
531
+ */
532
+ playToggleTargetConnected() {
373
533
  if (this.#connected) this.#syncTimer();
374
534
  }
535
+ /**
536
+ * Publishes the rotation state on a label half that joins a connected toggle.
537
+ *
538
+ * A half arriving changes the toggle's subtree, not its attributes, so the
539
+ * attribute observer never sees it; where the arrival completes a pair that was
540
+ * standing alone, this is what makes that pair whole and reflectable.
541
+ */
542
+ onLabelTargetConnected() {
543
+ if (this.#connected) this.#syncTimer();
544
+ }
545
+ /** Publishes the rotation state on a label half that joins a connected toggle. */
546
+ offLabelTargetConnected() {
547
+ if (this.#connected) this.#syncTimer();
548
+ }
549
+ /** Follows a rotation intent the page changed at runtime, lifting the reduced-motion hold. */
550
+ autoplayValueChanged() {
551
+ if (!this.#connected) return;
552
+ this.#motionHold = false;
553
+ this.#syncTimer();
554
+ }
375
555
  /** Re-arms the live interval at the new delay without reporting a state change. */
376
556
  intervalValueChanged() {
377
557
  if (this.#connected) this.#syncTimer();
378
558
  }
379
- /** Re-publishes the step controls and re-evaluates the non-looping end. */
559
+ /**
560
+ * Re-publishes the step controls and re-evaluates the non-looping end through the
561
+ * reconcile pass, which reports a move only when the position changed.
562
+ */
380
563
  loopValueChanged() {
381
- if (!this.#connected) return;
382
- this.#render({ focus: false });
383
- this.#syncTimer();
564
+ this.#reconcileTargets.schedule();
384
565
  }
385
566
  /** Advances to the next slide. Delegated; `data-action` wiring is optional. */
386
567
  next(event) {
@@ -402,11 +583,11 @@ var CarouselController = class extends Controller {
402
583
  if (index !== -1) this.#select(index, { focus: false });
403
584
  }
404
585
  /**
405
- * Flips the rotation intent on the user's explicit request.
586
+ * Flips the rotation intent the toggle shows, on the user's explicit request.
406
587
  *
407
- * The intent is written back to the `autoplay` Value, which is where every other
408
- * path reads it from. A carousel with nothing to advance to has no intent to
409
- * flip: the toggle is marked `aria-disabled` and does nothing.
588
+ * The result is written to the `autoplay` Value, which is where every other path
589
+ * reads it from. A carousel with nothing to advance to has no intent to flip: the
590
+ * toggle is marked `aria-disabled` and does nothing.
410
591
  */
411
592
  togglePlay(event) {
412
593
  if (event?.defaultPrevented) return;
@@ -422,7 +603,7 @@ var CarouselController = class extends Controller {
422
603
  pause(event) {
423
604
  this.#markHandled(event);
424
605
  if (isFocusEvent(event)) this.#focusPaused = true;
425
- else this.#pointerPaused = true;
606
+ else this.#setPointerPaused(true);
426
607
  this.#syncTimer();
427
608
  }
428
609
  /**
@@ -437,7 +618,7 @@ var CarouselController = class extends Controller {
437
618
  if (this.#focusStaysInside(event)) return;
438
619
  this.#focusPaused = false;
439
620
  } else {
440
- this.#pointerPaused = false;
621
+ this.#setPointerPaused(false);
441
622
  }
442
623
  this.#syncTimer();
443
624
  }
@@ -479,14 +660,36 @@ var CarouselController = class extends Controller {
479
660
  });
480
661
  /** Pointer entry suspends the rotation. */
481
662
  #onMouseenter = (event) => this.#delegate(event, () => {
482
- this.#pointerPaused = true;
663
+ this.#setPointerPaused(true);
483
664
  this.#syncTimer();
484
665
  });
485
666
  /** Pointer exit lifts the suspension. */
486
667
  #onMouseleave = (event) => this.#delegate(event, () => {
487
- this.#pointerPaused = false;
668
+ this.#setPointerPaused(false);
488
669
  this.#syncTimer();
489
670
  });
671
+ /**
672
+ * Reads `:hover` once the pointer has moved, when the answer is current, and lifts
673
+ * the pointer suspension if the pointer is not over the carousel. Listens once.
674
+ */
675
+ #onPointerMove = () => {
676
+ this.#pointer.dispose();
677
+ if (this.element.matches(":hover")) return;
678
+ this.#pointerPaused = false;
679
+ this.#syncTimer();
680
+ };
681
+ /** Leaves the pointer suspension to be confirmed or lifted on the next pointer movement. */
682
+ #awaitPointer() {
683
+ this.#pointer.add(document, "pointermove", this.#onPointerMove, {
684
+ capture: true,
685
+ passive: true
686
+ });
687
+ }
688
+ /** Records the pointer suspension; a newer answer ends any wait on the next movement. */
689
+ #setPointerPaused(paused) {
690
+ this.#pointer.dispose();
691
+ this.#pointerPaused = paused;
692
+ }
490
693
  /**
491
694
  * Schedules one reconciliation when a state attribute is rewritten in place —
492
695
  * the only shape of change no target callback reports.
@@ -523,10 +726,12 @@ var CarouselController = class extends Controller {
523
726
  const next = event.relatedTarget;
524
727
  return next instanceof Node && this.element.contains(next);
525
728
  }
526
- /** Flips the rotation intent, unless there is nothing to rotate to. */
729
+ /** Flips the rotation intent unless there is nothing to rotate to; lifts the motion hold. */
527
730
  #togglePlay() {
528
731
  if (!this.#canAutoplay()) return;
529
- this.autoplayValue = !this.autoplayValue;
732
+ const next = !this.#intent;
733
+ this.#motionHold = false;
734
+ this.autoplayValue = next;
530
735
  this.#syncTimer();
531
736
  }
532
737
  /** Applies the APG picker key map: arrows, Home, and End all move focus only. */
@@ -585,8 +790,7 @@ var CarouselController = class extends Controller {
585
790
  /**
586
791
  * Changes the active slide, updates state hooks, and emits `change` — but only
587
792
  * when the index actually changes, so a `next`/`prev` clamped at the end (or an
588
- * autoplay tick at a non-looping boundary) re-renders without a spurious event
589
- * (matching the "emit on real change" policy of flash/masonry/bulk-select).
793
+ * autoplay tick at a non-looping boundary) re-renders without a spurious event.
590
794
  */
591
795
  #select(index, { focus }) {
592
796
  const target = this.#clampToSlides(index);
@@ -673,6 +877,10 @@ var CarouselController = class extends Controller {
673
877
  this.dispatch("reconcile", { detail: { index: this.#index, total: this.#total } });
674
878
  }
675
879
  }
880
+ /** The rotation intent in effect: `autoplay`, unless reduced motion holds it back. */
881
+ get #intent() {
882
+ return this.autoplayValue && !this.#motionHold;
883
+ }
676
884
  /** Whether autoplay has anywhere left to advance to. */
677
885
  #canAutoplay() {
678
886
  const total = this.slideTargets.length;
@@ -688,15 +896,18 @@ var CarouselController = class extends Controller {
688
896
  * Drives the autoplay interval toward the desired state and publishes it.
689
897
  *
690
898
  * Rotation runs when the intent is on, nothing suspends it, and a slide is left
691
- * to advance to. A carousel that has run out normalizes the intent to `false`, so
692
- * `aria-pressed` never claims a rotation that cannot happen. Crossing the
693
- * run/stop boundary emits `play`/`pause`; re-arming at a new `interval` is the
694
- * same state and stays silent.
899
+ * to advance to. The toggle shows the intent only while a slide is left, so
900
+ * `aria-pressed` never claims a rotation that cannot happen. This body reads
901
+ * `autoplay` and never writes it, so the declared intent returns once a slide
902
+ * lies ahead again. Crossing the run/stop boundary emits `play`/`pause`;
903
+ * re-arming at a new `interval` is the same state and stays silent.
904
+ *
905
+ * @stimeoRenderRoot
695
906
  */
696
907
  #syncTimer() {
697
908
  const canAutoplay = this.#canAutoplay();
698
- if (this.autoplayValue && !canAutoplay) this.autoplayValue = false;
699
- const shouldRun = this.autoplayValue && canAutoplay && !this.#pointerPaused && !this.#focusPaused && !this.#hiddenPaused;
909
+ const pressed = this.#intent && canAutoplay;
910
+ const shouldRun = pressed && !this.#pointerPaused && !this.#focusPaused && !this.#hiddenPaused;
700
911
  const wasRunning = this.#timerId !== null;
701
912
  const interval = this.#interval;
702
913
  if (this.#timerId !== null && (!shouldRun || interval !== this.#timerInterval)) {
@@ -713,14 +924,28 @@ var CarouselController = class extends Controller {
713
924
  else this.dispatch("pause");
714
925
  }
715
926
  for (const toggle of this.playToggleTargets) {
716
- toggle.setAttribute("aria-pressed", this.autoplayValue ? "true" : "false");
927
+ toggle.setAttribute("aria-pressed", pressed ? "true" : "false");
717
928
  this.#ariaDisabled.write(toggle, canAutoplay ? null : "true");
929
+ this.#labels.reflect(toggle, pressed);
718
930
  }
719
931
  if (this.hasViewportTarget) {
720
932
  this.#ariaLive.write(this.viewportTarget, shouldRun ? "off" : "polite");
721
933
  this.#ariaAtomic.write(this.viewportTarget, "false");
722
934
  }
723
935
  }
936
+ /**
937
+ * Ends the rotation once the controller has really left. A carousel still in
938
+ * the document hears the end — `pause`, and `data-state="paused"` — so no `play`
939
+ * it reported is left without one; a detached tree keeps the markup it had.
940
+ */
941
+ #endRun() {
942
+ const wasRunning = this.#timerId !== null;
943
+ this.#intervals.clearAll();
944
+ this.#timerId = null;
945
+ if (!wasRunning || !this.element.isConnected) return;
946
+ setAttributeIfChanged(this.element, "data-state", "paused");
947
+ this.dispatch("pause");
948
+ }
724
949
  /** Hands every leased attribute back to the value the consumer authored. */
725
950
  #returnLeases() {
726
951
  this.#ariaDisabled.returnAll();