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
@@ -13,6 +13,16 @@ function isReservedArrowChord(event, allow = []) {
13
13
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
14
14
  }
15
15
 
16
+ // src/utils/field_mirror.ts
17
+ function writeField(field, value) {
18
+ if (field.value === value) return false;
19
+ field.value = value;
20
+ return true;
21
+ }
22
+ function commitField(target) {
23
+ target.dispatchEvent(new Event("change", { bubbles: true }));
24
+ }
25
+
16
26
  // src/utils/focus_candidate.ts
17
27
  function inheritsFieldsetDisabled(control) {
18
28
  let fieldset = control.closest("fieldset[disabled]");
@@ -48,6 +58,39 @@ function isInteractiveHost(element) {
48
58
  return false;
49
59
  }
50
60
 
61
+ // src/utils/microtask_coalescer.ts
62
+ var MicrotaskCoalescer = class {
63
+ #run;
64
+ #queued = false;
65
+ #active = false;
66
+ #generation = 0;
67
+ /** @param run - the single reconciliation pass, invoked at most once per batch. */
68
+ constructor(run) {
69
+ this.#run = run;
70
+ }
71
+ /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
72
+ activate() {
73
+ this.#active = true;
74
+ }
75
+ /** Closes the window and drops any pending pass; call from `disconnect()`. */
76
+ cancel() {
77
+ this.#active = false;
78
+ this.#queued = false;
79
+ this.#generation += 1;
80
+ }
81
+ /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
82
+ schedule() {
83
+ if (!this.#active || this.#queued) return;
84
+ this.#queued = true;
85
+ const generation = this.#generation;
86
+ queueMicrotask(() => {
87
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
88
+ this.#queued = false;
89
+ this.#run();
90
+ });
91
+ }
92
+ };
93
+
51
94
  // src/utils/roving_tabindex.ts
52
95
  var RovingTabindex = class {
53
96
  /** Returns the current ordered item elements; called on every operation. */
@@ -195,14 +238,28 @@ function findTypeaheadMatch(items, from, query, label = (item) => typeaheadLabel
195
238
  }
196
239
 
197
240
  // src/controllers/tree_view_controller.ts
241
+ var OBSERVED_ATTRIBUTES = ["aria-selected", "data-value"];
242
+ function setAttributeIfChanged(element, name, value) {
243
+ if (element.getAttribute(name) !== value) element.setAttribute(name, value);
244
+ }
198
245
  var NESTED_INTERACTIVE = INTERACTIVE_HOST_SELECTOR;
199
246
  var TreeViewController = class extends Controller {
200
- static targets = ["item", "group"];
247
+ static targets = ["item", "group", "field"];
201
248
  static actions = ["onClick", "onKeydown", "toggle"];
202
- static events = ["select", "toggle"];
249
+ static events = ["select", "toggle", "reconcile"];
203
250
  #roving = new RovingTabindex(() => this.itemTargets);
204
251
  #typeahead = new Typeahead();
205
252
  #timers = new SafeTimeout();
253
+ /**
254
+ * Collapses the item and field callbacks and observed item writes of one DOM
255
+ * mutation into a single selection pass; refused before `connect()` and after
256
+ * `disconnect()`.
257
+ */
258
+ #reconcile = new MicrotaskCoalescer(() => this.#reconcileSelection());
259
+ /** Watches the item attributes a page can rewrite in place; set while connected. */
260
+ #observer = null;
261
+ /** The selection last settled: on connect, by the user, or by a reported pass. */
262
+ #settled = { item: null, value: "" };
206
263
  #connected = false;
207
264
  /**
208
265
  * Item targets in DOM order as of the last connect / target change. A removed
@@ -247,9 +304,10 @@ var TreeViewController = class extends Controller {
247
304
  });
248
305
  };
249
306
  /**
250
- * Reconciles authored expansion state and establishes the single tab stop.
251
- * Idempotent, so a Turbo cache restore / morph re-runs it safely (re-adding
252
- * the same listener reference is a no-op per the DOM spec).
307
+ * Reconciles authored expansion state, establishes the single tab stop, and
308
+ * settles the selection without reporting it. Idempotent, so a Turbo cache
309
+ * restore / morph re-runs it safely (re-adding the same listener reference is a
310
+ * no-op per the DOM spec).
253
311
  */
254
312
  connect() {
255
313
  this.element.addEventListener("focusin", this.#onFocusIn);
@@ -258,14 +316,28 @@ var TreeViewController = class extends Controller {
258
316
  this.#reconcileExpansion();
259
317
  this.#normalizeSelection();
260
318
  this.#normalizeTabStop();
319
+ this.#mirrorField(false);
261
320
  this.#order = [...this.itemTargets];
321
+ this.#settled = this.#selection();
262
322
  this.#connected = true;
323
+ this.#reconcile.activate();
324
+ this.#observeItems();
325
+ }
326
+ /** Fills a form field inserted or replaced at runtime with the current selection. */
327
+ fieldTargetConnected() {
328
+ this.#reconcile.schedule();
263
329
  }
264
- /** Detaches the focus trackers, drops the typeahead buffer and its timer. */
330
+ /**
331
+ * Detaches the focus trackers and the item observer, drops a queued selection
332
+ * pass, the typeahead buffer and its timer.
333
+ */
265
334
  disconnect() {
266
335
  this.element.removeEventListener("focusin", this.#onFocusIn);
267
336
  this.element.removeEventListener("focusout", this.#onFocusOut);
268
337
  this.#connected = false;
338
+ this.#reconcile.cancel();
339
+ this.#observer?.disconnect();
340
+ this.#observer = null;
269
341
  this.#typeahead.reset();
270
342
  this.#timers.clearAll();
271
343
  this.#order = [];
@@ -284,14 +356,15 @@ var TreeViewController = class extends Controller {
284
356
  /**
285
357
  * Re-normalizes the tab stop when a `treeitem` is added at runtime, so an
286
358
  * appended item carrying `tabindex="0"` cannot turn the tree into two Tab
287
- * stops. Skipped before `connect()`: Stimulus registers the initial targets
288
- * first, and `connect()` owns the initial tab-stop policy.
359
+ * stops, and settles the selection once the batch is in. Skipped before
360
+ * `connect()`: Stimulus registers the initial targets first, and `connect()`
361
+ * owns the initial tab-stop policy.
289
362
  */
290
363
  itemTargetConnected() {
291
364
  if (!this.#connected) return;
292
- this.#normalizeSelection();
293
365
  this.#normalizeTabStop();
294
366
  this.#trackOrder();
367
+ this.#reconcile.schedule();
295
368
  }
296
369
  /**
297
370
  * Brings authored `aria-selected` to the shape the APG requires, without
@@ -299,18 +372,91 @@ var TreeViewController = class extends Controller {
299
372
  *
300
373
  * Every item gets an explicit value — an absent `aria-selected` means "not
301
374
  * selectable" in ARIA, so a forgotten attribute hides a selectable row — and a
302
- * single-select tree keeps at most one `true`, first in DOM order. `connect()`
303
- * reconciles authored expansion the same way. The scan is the `item` target
304
- * set: a `role="treeitem"` without the target is outside the contract and is
305
- * neither counted nor written.
375
+ * single-select tree keeps at most one `true`, first in DOM order: an item the
376
+ * page inserts ahead of the selection, already selected, takes it over.
377
+ * `connect()` reconciles authored expansion the same way. The scan is the
378
+ * `item` target set: a `role="treeitem"` without the target is outside the
379
+ * contract and is neither counted nor written.
306
380
  */
307
381
  #normalizeSelection() {
308
382
  const items = this.itemTargets;
309
383
  const selected = items.find((item) => item.getAttribute("aria-selected") === "true");
310
384
  for (const item of items) {
311
- item.setAttribute("aria-selected", item === selected ? "true" : "false");
385
+ setAttributeIfChanged(item, "aria-selected", item === selected ? "true" : "false");
312
386
  }
313
387
  }
388
+ /**
389
+ * Mirrors the selected item's `data-value` into the optional form field.
390
+ * Nothing selected, and an item that carries no value, both submit `""`.
391
+ */
392
+ #mirrorField(notify) {
393
+ if (!this.hasFieldTarget) return;
394
+ const moved = writeField(this.fieldTarget, this.#selection().value);
395
+ if (moved && notify) commitField(this.fieldTarget);
396
+ }
397
+ /** The selected item target, if any, and the value its field submits. */
398
+ #selection() {
399
+ const item = this.itemTargets.find((candidate) => candidate.getAttribute("aria-selected") === "true") ?? null;
400
+ return { item, value: item?.dataset.value ?? "" };
401
+ }
402
+ /**
403
+ * The pass the page's changes run: the selection is normalized and mirrored,
404
+ * then reported if it moved. The report comes last, so items a subscriber
405
+ * rewrites are the next pass's to settle.
406
+ */
407
+ #reconcileSelection() {
408
+ this.#normalizeSelection();
409
+ this.#dropOwnRecords();
410
+ this.#mirrorField(false);
411
+ this.#reportMove();
412
+ }
413
+ /**
414
+ * Reports the selection as `reconcile` when its item or value differs from the
415
+ * selection last settled. The settled selection is replaced before dispatching,
416
+ * so a move a subscriber makes is measured against what it was told.
417
+ */
418
+ #reportMove() {
419
+ const selection = this.#selection();
420
+ if (selection.item === this.#settled.item && selection.value === this.#settled.value) return;
421
+ this.#settled = selection;
422
+ this.dispatch("reconcile", { detail: { item: selection.item } });
423
+ }
424
+ /**
425
+ * Watches the items' `aria-selected` and `data-value`. The tree's own writes to
426
+ * them are dropped from the queue as they happen, so each record the callback
427
+ * receives is the page's, and only a record on one of the tree's own items
428
+ * schedules a pass.
429
+ */
430
+ #observeItems() {
431
+ const observer = new MutationObserver((records) => {
432
+ if (this.#concernsItems(records)) this.#reconcile.schedule();
433
+ });
434
+ observer.observe(this.element, {
435
+ subtree: true,
436
+ attributes: true,
437
+ attributeFilter: OBSERVED_ATTRIBUTES
438
+ });
439
+ this.#observer = observer;
440
+ }
441
+ /**
442
+ * Whether any record is about one of this tree's own items. A widget nested in a
443
+ * row writes the same attributes on its own elements, and those are no reason to
444
+ * reconcile the tree.
445
+ */
446
+ #concernsItems(records) {
447
+ const items = new Set(this.itemTargets);
448
+ return records.some((record) => items.has(record.target));
449
+ }
450
+ /**
451
+ * Drops the records the tree's own writes just queued, so the observer never
452
+ * takes them for the page's. A page write queued just before goes with them and
453
+ * owes nothing: every such write leaves every item's `aria-selected` at the value
454
+ * the selection gives it, and the selection is then read back from the DOM, so it
455
+ * already holds what the page wrote.
456
+ */
457
+ #dropOwnRecords() {
458
+ this.#observer?.takeRecords();
459
+ }
314
460
  /**
315
461
  * Restores the roving invariants when a `treeitem` leaves at runtime — a Turbo
316
462
  * Stream `remove`, a morph, or plain DOM surgery. The mirror of
@@ -326,7 +472,10 @@ var TreeViewController = class extends Controller {
326
472
  * state.
327
473
  *
328
474
  * Focus is only *restored*, never *stolen*: DOM focus moves solely when it was
329
- * inside the removed subtree and the document has nowhere left to put it.
475
+ * inside the removed subtree and the document has nowhere left to put it. The
476
+ * roving recovery runs here, while the pre-removal order is still on record;
477
+ * the selection is settled once the batch is in, and a selected item that left
478
+ * is reported as `reconcile` with no item.
330
479
  */
331
480
  itemTargetDisconnected(item) {
332
481
  if (!this.#connected || !this.element.isConnected) return;
@@ -336,6 +485,7 @@ var TreeViewController = class extends Controller {
336
485
  const next = this.#neighborOf(item);
337
486
  if (next) this.#roving.setActive(this.itemTargets.indexOf(next), { focus: stranded });
338
487
  }
488
+ this.#reconcile.schedule();
339
489
  this.#order = this.#order.filter((tracked) => tracked !== item);
340
490
  }
341
491
  /**
@@ -565,8 +715,11 @@ var TreeViewController = class extends Controller {
565
715
  #select(item) {
566
716
  if (this.#isDisabled(item)) return;
567
717
  for (const candidate of this.itemTargets) {
568
- candidate.setAttribute("aria-selected", candidate === item ? "true" : "false");
718
+ setAttributeIfChanged(candidate, "aria-selected", candidate === item ? "true" : "false");
569
719
  }
720
+ this.#dropOwnRecords();
721
+ this.#settled = this.#selection();
722
+ this.#mirrorField(true);
570
723
  this.dispatch("select", { detail: { item } });
571
724
  }
572
725
  /** Updates expansion, reconciles a collapsed subtree, then synchronously dispatches `toggle`. */