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
@@ -35,6 +35,25 @@ function isReservedArrowChord(event, allow = []) {
35
35
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
36
36
  }
37
37
 
38
+ // src/utils/element_part.ts
39
+ function matchingPart(root, selector) {
40
+ return root.matches(selector) ? root : root.querySelector(selector);
41
+ }
42
+ function writeLabel(slot, text) {
43
+ if (slot.childElementCount === 0) {
44
+ slot.textContent = text;
45
+ return;
46
+ }
47
+ let first = null;
48
+ for (const node of Array.from(slot.childNodes)) {
49
+ if (node.nodeType !== Node.TEXT_NODE) continue;
50
+ if (first) node.remove();
51
+ else first = node;
52
+ }
53
+ if (first) first.nodeValue = text;
54
+ else slot.prepend(text);
55
+ }
56
+
38
57
  // src/utils/roving_tabindex.ts
39
58
  var RovingTabindex = class {
40
59
  /** Returns the current ordered item elements; called on every operation. */
@@ -82,7 +101,7 @@ var ChipRow = class {
82
101
  constructor(options) {
83
102
  this.#directionElement = options.directionElement;
84
103
  this.#getItems = options.getItems;
85
- this.#getButton = options.getButton ?? ((item) => item.querySelector("button"));
104
+ this.#getButton = options.getButton ?? ((item) => matchingPart(item, "button"));
86
105
  this.#onRemove = options.onRemove;
87
106
  this.#focusAfterEnd = options.focusAfterEnd;
88
107
  }
@@ -207,11 +226,19 @@ var ChipRow = class {
207
226
  };
208
227
 
209
228
  // src/utils/composition_tracker.ts
229
+ var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
230
+ "insertCompositionText",
231
+ "insertFromComposition",
232
+ "deleteCompositionText",
233
+ "deleteByComposition"
234
+ ]);
210
235
  var CompositionTracker = class {
211
236
  #observedTargets = /* @__PURE__ */ new Set();
212
237
  #activeTargets = /* @__PURE__ */ new Set();
213
238
  #onStart;
214
239
  #onEnd;
240
+ /** The field whose confirming `input` is still owed, while the window is open. */
241
+ #confirmedTarget = null;
215
242
  constructor(options = {}) {
216
243
  this.#onStart = options.onStart;
217
244
  this.#onEnd = options.onEnd;
@@ -221,6 +248,7 @@ var CompositionTracker = class {
221
248
  if (this.#observedTargets.has(target)) return;
222
249
  target.addEventListener("compositionstart", this.#handleStart);
223
250
  target.addEventListener("compositionend", this.#handleEnd);
251
+ target.addEventListener("keydown", this.#handleKeydown);
224
252
  this.#observedTargets.add(target);
225
253
  }
226
254
  /** Stops tracking one target and clears any active composition it owned. */
@@ -228,31 +256,77 @@ var CompositionTracker = class {
228
256
  if (!this.#observedTargets.delete(target)) return;
229
257
  target.removeEventListener("compositionstart", this.#handleStart);
230
258
  target.removeEventListener("compositionend", this.#handleEnd);
259
+ target.removeEventListener("keydown", this.#handleKeydown);
231
260
  this.#activeTargets.delete(target);
261
+ if (this.#confirmedTarget === target) this.#confirmedTarget = null;
232
262
  }
233
263
  /** Releases every listener and clears state so reconnect starts cleanly. */
234
264
  disconnect() {
235
265
  for (const target of this.#observedTargets) {
236
266
  target.removeEventListener("compositionstart", this.#handleStart);
237
267
  target.removeEventListener("compositionend", this.#handleEnd);
268
+ target.removeEventListener("keydown", this.#handleKeydown);
238
269
  }
239
270
  this.#observedTargets.clear();
240
271
  this.#activeTargets.clear();
272
+ this.#confirmedTarget = null;
241
273
  }
242
274
  /** True when lifecycle tracking or the current event reports composition. */
243
275
  isComposing(event) {
244
276
  return this.#activeTargets.size > 0 || event?.isComposing === true;
245
277
  }
278
+ /**
279
+ * Whether `event` is the `input` echoing the composition just confirmed.
280
+ *
281
+ * Asking closes the window either way, so one confirmation is folded at most
282
+ * once and a consumer asks once per `input`.
283
+ */
284
+ consumesConfirmedInput(event) {
285
+ const confirmed = this.#confirmedTarget;
286
+ this.#confirmedTarget = null;
287
+ if (confirmed === null || confirmed !== event.target) return false;
288
+ const inputType = event.inputType;
289
+ return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
290
+ }
246
291
  #handleStart = (event) => {
292
+ this.#confirmedTarget = null;
247
293
  if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
248
294
  this.#onStart?.(event);
249
295
  };
250
296
  #handleEnd = (event) => {
251
297
  if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
298
+ this.#confirmedTarget = event.target;
252
299
  this.#onEnd?.(event);
253
300
  };
301
+ /** A key on an observed field opens an edit of its own, so no echo is owed. */
302
+ #handleKeydown = () => {
303
+ this.#confirmedTarget = null;
304
+ };
254
305
  };
255
306
 
307
+ // src/utils/field_mirror.ts
308
+ function writeFields(container, values, { name, form = "" }) {
309
+ const current = [...container.children];
310
+ const submits = current.length === values.length && current.every(
311
+ (child, index) => child instanceof HTMLInputElement && child.type === "hidden" && child.value === values[index] && child.name === name && (child.getAttribute("form") ?? "") === form
312
+ );
313
+ if (submits) return false;
314
+ container.replaceChildren(
315
+ ...values.map((value) => {
316
+ const input = document.createElement("input");
317
+ input.type = "hidden";
318
+ input.name = name;
319
+ input.value = value;
320
+ if (form !== "") input.setAttribute("form", form);
321
+ return input;
322
+ })
323
+ );
324
+ return true;
325
+ }
326
+ function commitField(target) {
327
+ target.dispatchEvent(new Event("change", { bubbles: true }));
328
+ }
329
+
256
330
  // src/utils/microtask_coalescer.ts
257
331
  var MicrotaskCoalescer = class {
258
332
  #run;
@@ -286,6 +360,85 @@ var MicrotaskCoalescer = class {
286
360
  }
287
361
  };
288
362
 
363
+ // src/utils/target_selector.ts
364
+ function targetSelector(identifier, name) {
365
+ return `[data-${identifier}-target~="${name}"]`;
366
+ }
367
+
368
+ // src/utils/template_row.ts
369
+ function cloneTemplateRoot(template) {
370
+ const root = template.content.firstElementChild;
371
+ return root instanceof HTMLElement ? root.cloneNode(true) : null;
372
+ }
373
+ var TemplateRow = class {
374
+ #options;
375
+ #warned = false;
376
+ constructor(options) {
377
+ this.#options = options;
378
+ }
379
+ /** The attribute selector for one declared part, in this controller's namespace. */
380
+ selector(name) {
381
+ return targetSelector(this.#options.identifier, name);
382
+ }
383
+ /** Re-arms the once-per-connection diagnostic. */
384
+ connect() {
385
+ this.#warned = false;
386
+ }
387
+ /**
388
+ * Names one missing part on the console, at most once per connection, and
389
+ * returns `null` so a caller can hand it straight back.
390
+ */
391
+ report(missing) {
392
+ return this.#say(`lacks ${missing}`);
393
+ }
394
+ /** Writes one diagnostic per connection, saying what the template got wrong. */
395
+ #say(problem) {
396
+ if (this.#warned) return null;
397
+ this.#warned = true;
398
+ const { identifier, outcome, noun } = this.#options;
399
+ console.warn(`Stimeo UI: "${identifier}" ${outcome} because its ${noun} ${problem}.`);
400
+ return null;
401
+ }
402
+ /**
403
+ * Clones the row and resolves its parts, or names what the template got wrong
404
+ * and returns `null`. `values` fills the button's authored `aria-label`.
405
+ */
406
+ instantiate(template, values) {
407
+ const { root: rootName, required, optional, button: buttonName } = this.#options;
408
+ const root = cloneTemplateRoot(template);
409
+ if (!root?.matches(this.selector(rootName))) {
410
+ return this.report(`${this.#article(rootName)} "${rootName}" root`);
411
+ }
412
+ const count = template.content.children.length;
413
+ if (count !== 1) {
414
+ return this.#say(`holds ${count} elements, and the row has to be its only one`);
415
+ }
416
+ const slots = {};
417
+ for (const name2 of required) {
418
+ const part = this.#resolve(root, name2);
419
+ if (!part) return this.report(`${this.#article(name2)} "${name2}" target`);
420
+ slots[name2] = part;
421
+ }
422
+ for (const name2 of optional ?? []) slots[name2] = this.#resolve(root, name2);
423
+ const named = this.#resolve(root, buttonName);
424
+ if (!(named instanceof HTMLButtonElement)) {
425
+ return this.report(`${this.#article(buttonName)} "${buttonName}" target <button>`);
426
+ }
427
+ const button = named;
428
+ const name = button.getAttribute("aria-label")?.trim() ?? "";
429
+ if (name === "") return this.report(`a non-empty aria-label on its "${buttonName}" target`);
430
+ button.setAttribute("aria-label", fillTemplate(name, values));
431
+ return { root, slots, button };
432
+ }
433
+ /** The row may be its own part; otherwise the first descendant carrying the target. */
434
+ #resolve(root, name) {
435
+ return matchingPart(root, this.selector(name));
436
+ }
437
+ #article(name) {
438
+ return /^[aeiou]/i.test(name) ? "an" : "a";
439
+ }
440
+ };
441
+
289
442
  // src/controllers/tags_input_controller.ts
290
443
  var TagsInputController = class extends Controller {
291
444
  static targets = ["input", "tags", "tag", "tagTemplate", "label", "remove", "fields"];
@@ -294,6 +447,7 @@ var TagsInputController = class extends Controller {
294
447
  max: { type: Number, default: 0 },
295
448
  allowDuplicates: { type: Boolean, default: false },
296
449
  name: { type: String, default: "tags[]" },
450
+ form: { type: String, default: "" },
297
451
  announceText: { type: String, default: "" },
298
452
  announceRemovedText: { type: String, default: "" }
299
453
  };
@@ -301,14 +455,21 @@ var TagsInputController = class extends Controller {
301
455
  static events = ["change", "reconcile", "reject"];
302
456
  /** Last reconciled tag order, separating user edits from DOM/Turbo repair. */
303
457
  #tagValues = [];
304
- /** Whether this connection already reported its unusable chip template. */
305
- #warnedTemplate = false;
458
+ /** Builds one chip from the authored template and owns its diagnostic. */
459
+ #rows = new TemplateRow({
460
+ identifier: this.identifier,
461
+ root: "tag",
462
+ required: ["label"],
463
+ button: "remove",
464
+ outcome: "added no tag",
465
+ noun: "chip template"
466
+ });
306
467
  /** Collapses one target/Value mutation batch into one final-DOM repair pass. */
307
468
  #reconcile = new MicrotaskCoalescer(() => this.#reconcileTags());
308
469
  #chipRow = new ChipRow({
309
470
  directionElement: this.element,
310
471
  getItems: () => this.tagTargets,
311
- getButton: (tag) => tag.querySelector('button[data-stimeo--tags-input-target~="remove"]'),
472
+ getButton: (tag) => matchingPart(tag, `button${this.#rows.selector("remove")}`),
312
473
  onRemove: (index) => this.#removeAt(index),
313
474
  focusAfterEnd: () => this.#focusInput()
314
475
  });
@@ -316,7 +477,7 @@ var TagsInputController = class extends Controller {
316
477
  #composition = new CompositionTracker();
317
478
  /** Wires tag-list keyboard navigation and removal, and seeds the single Tab stop. */
318
479
  connect() {
319
- this.#warnedTemplate = false;
480
+ this.#rows.connect();
320
481
  if (this.hasInputTarget) this.#composition.observe(this.inputTarget);
321
482
  if (this.hasTagsTarget) this.#chipRow.connect(this.tagsTarget);
322
483
  const tags = this.#values;
@@ -363,6 +524,10 @@ var TagsInputController = class extends Controller {
363
524
  nameValueChanged() {
364
525
  this.#reconcile.schedule();
365
526
  }
527
+ /** Repoints submitted fields when the owning form changes at runtime. */
528
+ formValueChanged() {
529
+ this.#reconcile.schedule();
530
+ }
366
531
  /** Recomputes the full hook when the cap changes at runtime. */
367
532
  maxValueChanged() {
368
533
  this.#reconcile.schedule();
@@ -393,7 +558,11 @@ var TagsInputController = class extends Controller {
393
558
  }
394
559
  }
395
560
  }
396
- /** Validates and adds the current input value as a tag, then clears the input. */
561
+ /**
562
+ * Validates and adds the current input value as a tag, then clears the input.
563
+ *
564
+ * @stimeoRuntimeOnly `max` and `allowDuplicates` decide whether this one entry is taken.
565
+ */
397
566
  #commitInput() {
398
567
  const value = this.inputTarget.value.trim();
399
568
  if (value === "") {
@@ -411,53 +580,31 @@ var TagsInputController = class extends Controller {
411
580
  if (!this.#appendTag(value)) return;
412
581
  this.inputTarget.value = "";
413
582
  const tags = this.#values;
414
- this.#syncState(tags);
583
+ this.#syncState(tags, true);
415
584
  this.#tagValues = tags;
416
585
  this.#announceTransition(true, value, tags.length);
417
586
  this.dispatch("change", { detail: { tags } });
418
587
  }
419
- /** Builds one chip from the template and appends it to the tag list. */
420
- #appendTag(value) {
421
- if (!this.hasTagsTarget) return false;
422
- if (!this.hasTagTemplateTarget) return this.#warnTemplate('a "tagTemplate" target');
423
- const fragment = this.tagTemplateTarget.content.cloneNode(true);
424
- const tag = fragment.querySelector('[data-stimeo--tags-input-target~="tag"]');
425
- const label = fragment.querySelector('[data-stimeo--tags-input-target~="label"]');
426
- const button = fragment.querySelector(
427
- 'button[data-stimeo--tags-input-target~="remove"]'
428
- );
429
- const removeName = button?.getAttribute("aria-label")?.trim() ?? "";
430
- if (!tag) return this.#warnTemplate('a "tag" target');
431
- if (!label) return this.#warnTemplate('a "label" target');
432
- if (!button) return this.#warnTemplate('a "remove" target <button>');
433
- if (removeName === "") {
434
- return this.#warnTemplate('a non-empty aria-label on its "remove" target');
435
- }
436
- tag.dataset.value = value;
437
- label.textContent = value;
438
- button.setAttribute("aria-label", fillTemplate(removeName, { label: value, value }));
439
- button.tabIndex = -1;
440
- this.tagsTarget.appendChild(fragment);
441
- return true;
442
- }
443
588
  /**
444
- * Reports an unusable chip template to the author, once per connection.
589
+ * Builds one chip from the template and appends it to the tag list.
445
590
  *
446
- * The commit itself stays a no-op — nothing about the input, the tag set, the
447
- * hidden fields, the announcement, or the events changes. Without this line
448
- * the only symptom is a field that accepts no tags at all, and the two causes
449
- * the Inspector cannot see statically (a name that renders empty from a
450
- * missing translation, a server-rendered template) would have no diagnostic
451
- * anywhere.
591
+ * A refused row leaves the commit a no-op — nothing about the input, the tag
592
+ * set, the hidden fields, the announcement, or the events changes — and the
593
+ * row reports why on the console once per connection.
452
594
  */
453
- #warnTemplate(missing) {
454
- if (!this.#warnedTemplate) {
455
- this.#warnedTemplate = true;
456
- console.warn(
457
- `Stimeo UI: "${this.identifier}" added no tag because its chip template lacks ${missing}.`
458
- );
595
+ #appendTag(value) {
596
+ if (!this.hasTagsTarget) return false;
597
+ if (!this.hasTagTemplateTarget) {
598
+ this.#rows.report('a "tagTemplate" target');
599
+ return false;
459
600
  }
460
- return false;
601
+ const row = this.#rows.instantiate(this.tagTemplateTarget, { label: value, value });
602
+ if (!row) return false;
603
+ row.root.dataset.value = value;
604
+ writeLabel(row.slots.label, value);
605
+ row.button.tabIndex = -1;
606
+ this.tagsTarget.appendChild(row.root);
607
+ return true;
461
608
  }
462
609
  /** Removes the tag at `index`, then applies the interaction-origin focus policy. */
463
610
  #removeAt(index, focus = "neighbor") {
@@ -466,7 +613,7 @@ var TagsInputController = class extends Controller {
466
613
  const value = tag.dataset.value ?? "";
467
614
  tag.remove();
468
615
  const tags = this.#values;
469
- this.#syncState(tags);
616
+ this.#syncState(tags, true);
470
617
  this.#tagValues = tags;
471
618
  this.#announceTransition(false, value, tags.length);
472
619
  this.dispatch("change", { detail: { tags } });
@@ -480,21 +627,20 @@ var TagsInputController = class extends Controller {
480
627
  #focusInput() {
481
628
  if (this.hasInputTarget) this.inputTarget.focus();
482
629
  }
483
- /** Rebuilds the hidden form fields, the `full` flag, and the roving Tab stop. */
484
- #syncState(values) {
630
+ /**
631
+ * Rebuilds the hidden form fields, the `full` flag, and the roving Tab stop.
632
+ *
633
+ * @stimeoRenderRoot
634
+ */
635
+ #syncState(values, notify = false) {
485
636
  if (this.hasFieldsTarget) {
486
- this.fieldsTarget.replaceChildren(
487
- ...values.map((value) => {
488
- const input = document.createElement("input");
489
- input.type = "hidden";
490
- input.name = this.nameValue;
491
- input.value = value;
492
- return input;
493
- })
494
- );
637
+ const options = { name: this.nameValue, form: this.formValue };
638
+ if (writeFields(this.fieldsTarget, values, options) && notify) {
639
+ commitField(this.fieldsTarget);
640
+ }
495
641
  }
496
642
  const full = this.maxValue > 0 && values.length >= this.maxValue;
497
- this.element.toggleAttribute("data-stimeo--tags-input-full", full);
643
+ this.element.toggleAttribute(`data-${this.identifier}-full`, full);
498
644
  this.#chipRow.ensureTabStop();
499
645
  }
500
646
  /** Repairs derived state after DOM/Turbo changes and reports a changed tag order. */
@@ -507,7 +653,11 @@ var TagsInputController = class extends Controller {
507
653
  this.#tagValues = tags;
508
654
  if (changed) this.dispatch("reconcile", { detail: { tags } });
509
655
  }
510
- /** Sends one localized tag transition through the page's shared announcer. */
656
+ /**
657
+ * Sends one localized tag transition through the page's shared announcer.
658
+ *
659
+ * @stimeoRuntimeOnly The texts word the one announcement of this change.
660
+ */
511
661
  #announceTransition(added, value, count) {
512
662
  const template = added ? this.announceTextValue : this.announceRemovedTextValue;
513
663
  announce(fillTemplate(template, { label: value, value, count }));