stimeo-ui 0.16.0 → 0.18.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 (337) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +27 -11
  3. package/dist/cable/index.d.ts +88 -31
  4. package/dist/cable/index.js +384 -449
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.d.ts +4 -2
  8. package/dist/controllers/accordion_controller.js +174 -55
  9. package/dist/controllers/accordion_controller.js.map +1 -1
  10. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  11. package/dist/controllers/alert_dialog_controller.js +903 -235
  12. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  13. package/dist/controllers/announcer_controller.d.ts +37 -5
  14. package/dist/controllers/announcer_controller.js +238 -205
  15. package/dist/controllers/announcer_controller.js.map +1 -1
  16. package/dist/controllers/aspect_ratio_controller.js +0 -10
  17. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  18. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  19. package/dist/controllers/auto_submit_controller.js +414 -145
  20. package/dist/controllers/auto_submit_controller.js.map +1 -1
  21. package/dist/controllers/avatar_controller.js +185 -107
  22. package/dist/controllers/avatar_controller.js.map +1 -1
  23. package/dist/controllers/breadcrumb_controller.js +0 -130
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +21 -3
  26. package/dist/controllers/bulk_select_controller.js +282 -58
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +38 -16
  29. package/dist/controllers/calendar_controller.js +228 -260
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +43 -12
  32. package/dist/controllers/carousel_controller.js +536 -436
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +44 -7
  35. package/dist/controllers/character_counter_controller.js +493 -207
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +38 -74
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +16 -5
  41. package/dist/controllers/clipboard_controller.js +261 -149
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +17 -5
  44. package/dist/controllers/collapsible_controller.js +304 -120
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +18 -6
  47. package/dist/controllers/color_picker_controller.js +178 -146
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +24 -6
  50. package/dist/controllers/combobox_controller.js +522 -156
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +51 -20
  53. package/dist/controllers/command_palette_controller.js +1156 -395
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +300 -171
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +980 -262
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +80 -18
  62. package/dist/controllers/context_menu_controller.js +346 -153
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +47 -3
  68. package/dist/controllers/countdown_controller.js +281 -158
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -10
  71. package/dist/controllers/currency_input_controller.js +575 -225
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +25 -12
  74. package/dist/controllers/data_grid_controller.js +248 -214
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +42 -14
  77. package/dist/controllers/date_range_picker_controller.js +338 -235
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +17 -6
  80. package/dist/controllers/dialog_controller.js +899 -237
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +25 -8
  83. package/dist/controllers/direct_upload_controller.js +97 -167
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  86. package/dist/controllers/dirty_form_controller.js +72 -69
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +196 -24
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +39 -11
  92. package/dist/controllers/drawer_controller.js +974 -355
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +31 -1
  95. package/dist/controllers/dropdown_controller.js +339 -96
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +315 -88
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +79 -58
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +63 -18
  103. package/dist/controllers/file_dropzone_controller.js +322 -211
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.js +77 -42
  106. package/dist/controllers/filter_controller.js.map +1 -1
  107. package/dist/controllers/flash_controller.d.ts +35 -12
  108. package/dist/controllers/flash_controller.js +238 -347
  109. package/dist/controllers/flash_controller.js.map +1 -1
  110. package/dist/controllers/focus_controller.d.ts +15 -6
  111. package/dist/controllers/focus_controller.js +679 -240
  112. package/dist/controllers/focus_controller.js.map +1 -1
  113. package/dist/controllers/form_field_controller.js +156 -132
  114. package/dist/controllers/form_field_controller.js.map +1 -1
  115. package/dist/controllers/form_validation_controller.d.ts +3 -3
  116. package/dist/controllers/form_validation_controller.js +31 -97
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +47 -14
  119. package/dist/controllers/frame_loading_controller.js +275 -217
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +27 -6
  122. package/dist/controllers/highlight_controller.js +107 -75
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +60 -4
  125. package/dist/controllers/hover_card_controller.js +327 -130
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +48 -6
  128. package/dist/controllers/idle_controller.js +271 -101
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +30 -13
  131. package/dist/controllers/input_mask_controller.js +483 -97
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +150 -106
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +19 -2
  137. package/dist/controllers/lazy_frame_controller.js +24 -68
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +44 -9
  140. package/dist/controllers/listbox_controller.js +523 -240
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +9 -7
  143. package/dist/controllers/local_time_controller.js +102 -77
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +80 -96
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +55 -11
  149. package/dist/controllers/menu_controller.js +302 -188
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +51 -9
  152. package/dist/controllers/menubar_controller.js +232 -345
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +33 -20
  155. package/dist/controllers/meter_controller.js +417 -87
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +48 -18
  158. package/dist/controllers/multi_select_controller.js +785 -393
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +27 -6
  161. package/dist/controllers/navigation_menu_controller.js +190 -250
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +396 -192
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +268 -62
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +50 -8
  170. package/dist/controllers/number_input_controller.js +610 -303
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.d.ts +8 -5
  173. package/dist/controllers/optimistic_controller.js +45 -87
  174. package/dist/controllers/optimistic_controller.js.map +1 -1
  175. package/dist/controllers/otp_controller.d.ts +55 -8
  176. package/dist/controllers/otp_controller.js +658 -255
  177. package/dist/controllers/otp_controller.js.map +1 -1
  178. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  179. package/dist/controllers/overflow_indicator_controller.js +122 -113
  180. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  181. package/dist/controllers/overflow_menu_controller.d.ts +58 -20
  182. package/dist/controllers/overflow_menu_controller.js +470 -295
  183. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  184. package/dist/controllers/pagination_controller.d.ts +37 -7
  185. package/dist/controllers/pagination_controller.js +332 -123
  186. package/dist/controllers/pagination_controller.js.map +1 -1
  187. package/dist/controllers/password_reveal_controller.d.ts +46 -8
  188. package/dist/controllers/password_reveal_controller.js +392 -121
  189. package/dist/controllers/password_reveal_controller.js.map +1 -1
  190. package/dist/controllers/password_strength_controller.d.ts +15 -6
  191. package/dist/controllers/password_strength_controller.js +111 -158
  192. package/dist/controllers/password_strength_controller.js.map +1 -1
  193. package/dist/controllers/persist_controller.d.ts +11 -4
  194. package/dist/controllers/persist_controller.js +165 -111
  195. package/dist/controllers/persist_controller.js.map +1 -1
  196. package/dist/controllers/pointer_drag_controller.d.ts +35 -12
  197. package/dist/controllers/pointer_drag_controller.js +434 -188
  198. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  199. package/dist/controllers/popover_controller.d.ts +41 -5
  200. package/dist/controllers/popover_controller.js +269 -113
  201. package/dist/controllers/popover_controller.js.map +1 -1
  202. package/dist/controllers/portal_controller.d.ts +44 -4
  203. package/dist/controllers/portal_controller.js +215 -76
  204. package/dist/controllers/portal_controller.js.map +1 -1
  205. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  206. package/dist/controllers/preview_guard_controller.js +243 -116
  207. package/dist/controllers/preview_guard_controller.js.map +1 -1
  208. package/dist/controllers/progress_controller.d.ts +20 -19
  209. package/dist/controllers/progress_controller.js +391 -78
  210. package/dist/controllers/progress_controller.js.map +1 -1
  211. package/dist/controllers/radio_group_controller.d.ts +21 -9
  212. package/dist/controllers/radio_group_controller.js +278 -211
  213. package/dist/controllers/radio_group_controller.js.map +1 -1
  214. package/dist/controllers/range_slider_controller.d.ts +47 -6
  215. package/dist/controllers/range_slider_controller.js +220 -113
  216. package/dist/controllers/range_slider_controller.js.map +1 -1
  217. package/dist/controllers/rating_controller.d.ts +20 -4
  218. package/dist/controllers/rating_controller.js +458 -211
  219. package/dist/controllers/rating_controller.js.map +1 -1
  220. package/dist/controllers/read_more_controller.d.ts +2 -0
  221. package/dist/controllers/read_more_controller.js +138 -63
  222. package/dist/controllers/read_more_controller.js.map +1 -1
  223. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  224. package/dist/controllers/reading_progress_controller.js +166 -137
  225. package/dist/controllers/reading_progress_controller.js.map +1 -1
  226. package/dist/controllers/relative_time_controller.d.ts +20 -6
  227. package/dist/controllers/relative_time_controller.js +211 -112
  228. package/dist/controllers/relative_time_controller.js.map +1 -1
  229. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  230. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  231. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  232. package/dist/controllers/resizable_controller.d.ts +31 -2
  233. package/dist/controllers/resizable_controller.js +299 -150
  234. package/dist/controllers/resizable_controller.js.map +1 -1
  235. package/dist/controllers/roving_controller.d.ts +3 -3
  236. package/dist/controllers/roving_controller.js +49 -88
  237. package/dist/controllers/roving_controller.js.map +1 -1
  238. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  239. package/dist/controllers/scroll_area_controller.js +490 -246
  240. package/dist/controllers/scroll_area_controller.js.map +1 -1
  241. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  242. package/dist/controllers/scroll_restore_controller.js +14 -55
  243. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  244. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  245. package/dist/controllers/scroll_visibility_controller.js +432 -149
  246. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  247. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  248. package/dist/controllers/scrollspy_controller.js +193 -248
  249. package/dist/controllers/scrollspy_controller.js.map +1 -1
  250. package/dist/controllers/separator_controller.d.ts +20 -5
  251. package/dist/controllers/separator_controller.js +285 -142
  252. package/dist/controllers/separator_controller.js.map +1 -1
  253. package/dist/controllers/sidebar_controller.d.ts +65 -8
  254. package/dist/controllers/sidebar_controller.js +1069 -404
  255. package/dist/controllers/sidebar_controller.js.map +1 -1
  256. package/dist/controllers/skeleton_controller.d.ts +23 -0
  257. package/dist/controllers/skeleton_controller.js +259 -101
  258. package/dist/controllers/skeleton_controller.js.map +1 -1
  259. package/dist/controllers/slider_controller.d.ts +29 -2
  260. package/dist/controllers/slider_controller.js +187 -86
  261. package/dist/controllers/slider_controller.js.map +1 -1
  262. package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
  263. package/dist/controllers/smart_sticky_header_controller.js +173 -66
  264. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  265. package/dist/controllers/sortable_controller.d.ts +4 -4
  266. package/dist/controllers/sortable_controller.js +26 -125
  267. package/dist/controllers/sortable_controller.js.map +1 -1
  268. package/dist/controllers/spinner_controller.d.ts +39 -4
  269. package/dist/controllers/spinner_controller.js +288 -173
  270. package/dist/controllers/spinner_controller.js.map +1 -1
  271. package/dist/controllers/step_indicator_controller.d.ts +9 -4
  272. package/dist/controllers/step_indicator_controller.js +105 -46
  273. package/dist/controllers/step_indicator_controller.js.map +1 -1
  274. package/dist/controllers/stepper_controller.d.ts +14 -6
  275. package/dist/controllers/stepper_controller.js +120 -73
  276. package/dist/controllers/stepper_controller.js.map +1 -1
  277. package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
  278. package/dist/controllers/stick_to_bottom_controller.js +275 -140
  279. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  280. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  281. package/dist/controllers/sticky_observer_controller.js +220 -49
  282. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  283. package/dist/controllers/submit_once_controller.d.ts +24 -8
  284. package/dist/controllers/submit_once_controller.js +376 -227
  285. package/dist/controllers/submit_once_controller.js.map +1 -1
  286. package/dist/controllers/switch_controller.d.ts +18 -4
  287. package/dist/controllers/switch_controller.js +201 -84
  288. package/dist/controllers/switch_controller.js.map +1 -1
  289. package/dist/controllers/tabs_controller.d.ts +9 -6
  290. package/dist/controllers/tabs_controller.js +41 -30
  291. package/dist/controllers/tabs_controller.js.map +1 -1
  292. package/dist/controllers/tags_input_controller.d.ts +16 -3
  293. package/dist/controllers/tags_input_controller.js +293 -173
  294. package/dist/controllers/tags_input_controller.js.map +1 -1
  295. package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
  296. package/dist/controllers/textarea_autosize_controller.js +108 -74
  297. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  298. package/dist/controllers/theme_controller.d.ts +24 -11
  299. package/dist/controllers/theme_controller.js +140 -116
  300. package/dist/controllers/theme_controller.js.map +1 -1
  301. package/dist/controllers/time_picker_controller.d.ts +55 -5
  302. package/dist/controllers/time_picker_controller.js +331 -127
  303. package/dist/controllers/time_picker_controller.js.map +1 -1
  304. package/dist/controllers/toast_controller.d.ts +40 -16
  305. package/dist/controllers/toast_controller.js +244 -324
  306. package/dist/controllers/toast_controller.js.map +1 -1
  307. package/dist/controllers/toggle_group_controller.d.ts +14 -6
  308. package/dist/controllers/toggle_group_controller.js +252 -191
  309. package/dist/controllers/toggle_group_controller.js.map +1 -1
  310. package/dist/controllers/toolbar_controller.d.ts +8 -6
  311. package/dist/controllers/toolbar_controller.js +219 -120
  312. package/dist/controllers/toolbar_controller.js.map +1 -1
  313. package/dist/controllers/tooltip_controller.d.ts +49 -4
  314. package/dist/controllers/tooltip_controller.js +158 -124
  315. package/dist/controllers/tooltip_controller.js.map +1 -1
  316. package/dist/controllers/transition_controller.d.ts +22 -4
  317. package/dist/controllers/transition_controller.js +123 -119
  318. package/dist/controllers/transition_controller.js.map +1 -1
  319. package/dist/controllers/tree_view_controller.d.ts +22 -12
  320. package/dist/controllers/tree_view_controller.js +283 -369
  321. package/dist/controllers/tree_view_controller.js.map +1 -1
  322. package/dist/index.d.ts +10 -5
  323. package/dist/index.js +8573 -10410
  324. package/dist/index.js.map +1 -1
  325. package/dist/inspector/cli.d.ts +38 -14
  326. package/dist/inspector/cli.js +108 -40
  327. package/dist/inspector/cli.js.map +1 -1
  328. package/dist/inspector/cli_bin.js +145 -64
  329. package/dist/inspector/cli_bin.js.map +1 -1
  330. package/dist/inspector/examples.json +20 -20
  331. package/dist/inspector/manifest.json +802 -101
  332. package/dist/positioning/index.d.ts +15 -7
  333. package/dist/positioning/index.js +154 -47
  334. package/dist/positioning/index.js.map +1 -1
  335. package/package.json +11 -10
  336. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  337. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,8 +1,12 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/tags_input_controller.ts
3
+ function fillTemplate(template, values) {
4
+ return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
5
+ const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
6
+ return replacement === void 0 ? match : String(replacement);
7
+ });
8
+ }
4
9
 
5
- // src/utils/announce.ts
6
10
  function announce(message, options = {}) {
7
11
  const text = message.trim();
8
12
  if (text.length === 0) return;
@@ -12,19 +16,11 @@ function announce(message, options = {}) {
12
16
  })
13
17
  );
14
18
  }
15
- function fillTemplate(template, values) {
16
- return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
17
- const replacement = values[name];
18
- return replacement === void 0 ? match : String(replacement);
19
- });
20
- }
21
19
 
22
- // src/utils/logical_scroll.ts
23
20
  function isRtl(element) {
24
21
  return window.getComputedStyle(element).direction === "rtl";
25
22
  }
26
23
 
27
- // src/utils/arrow_step.ts
28
24
  function logicalArrowKey(key, element) {
29
25
  if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
30
26
  if (!isRtl(element)) return key;
@@ -35,7 +31,6 @@ function isReservedArrowChord(event, allow = []) {
35
31
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
36
32
  }
37
33
 
38
- // src/utils/element_part.ts
39
34
  function matchingPart(root, selector) {
40
35
  return root.matches(selector) ? root : root.querySelector(selector);
41
36
  }
@@ -54,31 +49,14 @@ function writeLabel(slot, text) {
54
49
  else slot.prepend(text);
55
50
  }
56
51
 
57
- // src/utils/roving_tabindex.ts
58
52
  var RovingTabindex = class {
59
- /** Returns the current ordered item elements; called on every operation. */
60
53
  #getItems;
61
- /**
62
- * @param getItems - Returns the current ordered item elements. Called on every
63
- * operation so the live target list is always used.
64
- */
65
54
  constructor(getItems) {
66
55
  this.#getItems = getItems;
67
56
  }
68
- /** Index of the currently tabbable item (`tabindex="0"`), or `-1` if none. */
69
57
  get activeIndex() {
70
58
  return this.#getItems().findIndex((item) => item.tabIndex === 0);
71
59
  }
72
- /**
73
- * Makes exactly the item at `index` tabbable (`tabindex="0"`) and removes every
74
- * other item from the Tab sequence (`tabindex="-1"`). An out-of-range `index`
75
- * (e.g. `-1`) leaves all items at `-1`, which a controller can use to express
76
- * "nothing is currently tabbable".
77
- *
78
- * @param index - Position of the item to make tabbable.
79
- * @param options - Pass `{ focus: true }` to also move DOM focus to that item,
80
- * and `items` to reuse an event-scoped collection snapshot.
81
- */
82
60
  setActive(index, options = {}) {
83
61
  const { focus = false } = options;
84
62
  const items = options.items ?? this.#getItems();
@@ -89,9 +67,8 @@ var RovingTabindex = class {
89
67
  }
90
68
  };
91
69
 
92
- // src/utils/chip_row.ts
93
70
  var ChipRow = class {
94
- #directionElement;
71
+ #getDirectionElement;
95
72
  #getItems;
96
73
  #getButton;
97
74
  #onRemove;
@@ -99,16 +76,12 @@ var ChipRow = class {
99
76
  #roving = new RovingTabindex(() => this.buttons);
100
77
  #container = null;
101
78
  constructor(options) {
102
- this.#directionElement = options.directionElement;
79
+ this.#getDirectionElement = options.getDirectionElement;
103
80
  this.#getItems = options.getItems;
104
81
  this.#getButton = options.getButton ?? ((item) => matchingPart(item, "button"));
105
82
  this.#onRemove = options.onRemove;
106
83
  this.#focusAfterEnd = options.focusAfterEnd;
107
84
  }
108
- /**
109
- * Binds delegation to `container`; any row currently bound is released first.
110
- * Idempotent for the same node, so target callbacks may call it freely.
111
- */
112
85
  connect(container) {
113
86
  if (this.#container === container) return;
114
87
  this.disconnect();
@@ -116,10 +89,6 @@ var ChipRow = class {
116
89
  container.addEventListener("click", this.#onClick);
117
90
  container.addEventListener("keydown", this.#onKeydown);
118
91
  }
119
- /**
120
- * Releases the current row. When `container` is supplied, a stale disconnect
121
- * callback cannot tear listeners off a newer replacement target.
122
- */
123
92
  disconnect(container) {
124
93
  const current = this.#container;
125
94
  if (!current || container !== void 0 && current !== container) return;
@@ -127,19 +96,15 @@ var ChipRow = class {
127
96
  current.removeEventListener("keydown", this.#onKeydown);
128
97
  this.#container = null;
129
98
  }
130
- /** Current remove buttons, at most one consumer-resolved button per chip item. */
131
99
  get buttons() {
132
100
  return this.#entries.map(({ button }) => button);
133
101
  }
134
- /** Number of currently navigable remove buttons. */
135
102
  get length() {
136
103
  return this.buttons.length;
137
104
  }
138
- /** Declared-item index of the last navigable chip, or `-1` for an empty row. */
139
105
  get lastIndex() {
140
106
  return this.#entries.at(-1)?.itemIndex ?? -1;
141
107
  }
142
- /** Keeps exactly one button tabbable, preferring the first authored Tab stop. */
143
108
  ensureTabStop() {
144
109
  const buttons = this.#entries.map(({ button }) => button);
145
110
  const active = buttons.findIndex((button) => button.tabIndex === 0);
@@ -148,7 +113,6 @@ var ChipRow = class {
148
113
  this.#roving.setActive(active === -1 ? 0 : active, { items: buttons });
149
114
  }
150
115
  }
151
- /** Focuses the last chip, returning false for an empty row. */
152
116
  focusLast() {
153
117
  const buttons = this.#entries.map(({ button }) => button);
154
118
  const last = buttons.length - 1;
@@ -156,10 +120,6 @@ var ChipRow = class {
156
120
  this.#roving.setActive(last, { focus: true, items: buttons });
157
121
  return true;
158
122
  }
159
- /**
160
- * Focuses the chip that followed a removed index, or the new last chip.
161
- * Returns false when removal emptied the row so the consumer can rescue focus.
162
- */
163
123
  focusAfterRemoval(index) {
164
124
  const entries = this.#entries;
165
125
  if (entries.length === 0) return false;
@@ -170,14 +130,12 @@ var ChipRow = class {
170
130
  });
171
131
  return true;
172
132
  }
173
- /** Current remove-button entries, retaining each button's declared item index. */
174
133
  get #entries() {
175
134
  return this.#getItems().flatMap((item, itemIndex) => {
176
135
  const button = this.#getButton(item);
177
136
  return button ? [{ button, itemIndex }] : [];
178
137
  });
179
138
  }
180
- /** Resolves a delegated event to the remove button owned by a declared chip. */
181
139
  #entry(event) {
182
140
  const target = event.target;
183
141
  if (!(target instanceof Element)) return null;
@@ -188,20 +146,18 @@ var ChipRow = class {
188
146
  const entry = entries[buttonIndex];
189
147
  return { ...entry, buttonIndex, buttons: entries.map(({ button: button2 }) => button2) };
190
148
  }
191
- /** Delegates removal clicks without waiting for Stimulus to wire a new chip. */
192
149
  #onClick = (event) => {
193
150
  const entry = this.#entry(event);
194
151
  if (!entry) return;
195
152
  this.#onRemove(entry.itemIndex);
196
153
  };
197
- /** Applies the shared logical-arrow and removal policy within the chip row. */
198
154
  #onKeydown = (event) => {
199
155
  if (event.defaultPrevented || isReservedArrowChord(event)) return;
200
156
  const entry = this.#entry(event);
201
157
  if (!entry) return;
202
158
  const buttons = entry.buttons;
203
159
  const index = entry.buttonIndex;
204
- switch (logicalArrowKey(event.key, this.#directionElement)) {
160
+ switch (logicalArrowKey(event.key, this.#getDirectionElement())) {
205
161
  case "ArrowLeft":
206
162
  if (index > 0) {
207
163
  event.preventDefault();
@@ -225,86 +181,191 @@ var ChipRow = class {
225
181
  };
226
182
  };
227
183
 
228
- // src/utils/composition_tracker.ts
229
184
  var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
230
185
  "insertCompositionText",
231
186
  "insertFromComposition",
232
187
  "deleteCompositionText",
233
188
  "deleteByComposition"
234
189
  ]);
190
+ function isCompositionInputType(inputType) {
191
+ return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
192
+ }
193
+ var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
194
+ var textOf = (element) => element.value;
195
+ var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
235
196
  var CompositionTracker = class {
236
197
  #observedTargets = /* @__PURE__ */ new Set();
237
- #activeTargets = /* @__PURE__ */ new Set();
198
+ #composition = null;
238
199
  #onStart;
239
200
  #onEnd;
240
- /** The field whose confirming `input` is still owed, while the window is open. */
201
+ #onAbandon;
241
202
  #confirmedTarget = null;
203
+ #keyComposing = false;
204
+ #connected = false;
205
+ #abandoned = null;
242
206
  constructor(options = {}) {
243
207
  this.#onStart = options.onStart;
244
208
  this.#onEnd = options.onEnd;
209
+ this.#onAbandon = options.onAbandon;
245
210
  }
246
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
247
211
  observe(target) {
212
+ this.#connected = true;
248
213
  if (this.#observedTargets.has(target)) return;
249
214
  target.addEventListener("compositionstart", this.#handleStart);
250
215
  target.addEventListener("compositionend", this.#handleEnd);
251
- target.addEventListener("keydown", this.#handleKeydown);
216
+ target.addEventListener("keydown", this.#handleKeydown, true);
217
+ target.addEventListener("beforeinput", this.#handleBeforeInput, true);
218
+ target.addEventListener("input", this.#handleInput, true);
219
+ target.addEventListener("blur", this.#handleBlur, true);
220
+ target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
252
221
  this.#observedTargets.add(target);
253
222
  }
254
- /** Stops tracking one target and clears any active composition it owned. */
255
223
  unobserve(target) {
256
224
  if (!this.#observedTargets.delete(target)) return;
257
- target.removeEventListener("compositionstart", this.#handleStart);
258
- target.removeEventListener("compositionend", this.#handleEnd);
259
- target.removeEventListener("keydown", this.#handleKeydown);
260
- this.#activeTargets.delete(target);
225
+ this.#release(target);
226
+ if (this.#composition?.target === target) this.#letGo();
261
227
  if (this.#confirmedTarget === target) this.#confirmedTarget = null;
262
228
  }
263
- /** Releases every listener and clears state so reconnect starts cleanly. */
264
229
  disconnect() {
265
- for (const target of this.#observedTargets) {
266
- target.removeEventListener("compositionstart", this.#handleStart);
267
- target.removeEventListener("compositionend", this.#handleEnd);
268
- target.removeEventListener("keydown", this.#handleKeydown);
269
- }
230
+ this.#connected = false;
231
+ for (const target of this.#observedTargets) this.#release(target);
270
232
  this.#observedTargets.clear();
271
- this.#activeTargets.clear();
233
+ this.#letGo();
272
234
  this.#confirmedTarget = null;
235
+ this.#keyComposing = false;
236
+ this.#abandoned = null;
273
237
  }
274
- /** True when lifecycle tracking or the current event reports composition. */
275
238
  isComposing(event) {
276
- return this.#activeTargets.size > 0 || event?.isComposing === true;
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
- */
239
+ this.#settle();
240
+ return event?.isComposing === true || this.#composition !== null;
241
+ }
242
+ composingElement() {
243
+ this.#settle();
244
+ return this.#composition?.element ?? null;
245
+ }
246
+ owns(event) {
247
+ const element = this.composingElement();
248
+ return event.isComposing === true || element !== null && element === event.target;
249
+ }
284
250
  consumesConfirmedInput(event) {
285
251
  const confirmed = this.#confirmedTarget;
286
252
  this.#confirmedTarget = null;
287
253
  if (confirmed === null || confirmed !== event.target) return false;
288
254
  const inputType = event.inputType;
289
- return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
255
+ return !inputType || isCompositionInputType(inputType);
290
256
  }
257
+ #release(target) {
258
+ target.removeEventListener("compositionstart", this.#handleStart);
259
+ target.removeEventListener("compositionend", this.#handleEnd);
260
+ target.removeEventListener("keydown", this.#handleKeydown, true);
261
+ target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
262
+ target.removeEventListener("input", this.#handleInput, true);
263
+ target.removeEventListener("blur", this.#handleBlur, true);
264
+ target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
265
+ }
266
+ #letGo() {
267
+ const composition = this.#composition;
268
+ if (composition === null) return;
269
+ if (!holdsFocus(composition.element)) {
270
+ this.#composition = null;
271
+ return;
272
+ }
273
+ queueMicrotask(() => {
274
+ if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
275
+ return;
276
+ }
277
+ this.#composition = null;
278
+ if (!this.#connected) return;
279
+ this.#abandoned = { element: composition.element, inPlace: true };
280
+ this.#runAbandon();
281
+ });
282
+ }
283
+ #settle() {
284
+ const composition = this.#composition;
285
+ if (composition === null) return;
286
+ const { target, element, text } = composition;
287
+ if (target instanceof Node && element instanceof Node && !target.contains(element)) {
288
+ this.#composition = null;
289
+ } else if (textOf(element) !== text) {
290
+ this.#abandon(false);
291
+ }
292
+ }
293
+ #abandon(inPlace) {
294
+ this.#owe(inPlace);
295
+ this.#composition = null;
296
+ queueMicrotask(this.#runAbandon);
297
+ }
298
+ #owe(inPlace) {
299
+ const composition = this.#composition;
300
+ if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
301
+ }
302
+ #runAbandon = () => {
303
+ const abandoned = this.#abandoned;
304
+ if (abandoned === null) return;
305
+ this.#abandoned = null;
306
+ this.#onAbandon?.(abandoned.element, abandoned.inPlace);
307
+ };
291
308
  #handleStart = (event) => {
292
309
  this.#confirmedTarget = null;
293
- if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
310
+ this.#settle();
311
+ this.#owe(true);
312
+ const target = event.currentTarget;
313
+ const element = event.target;
314
+ this.#composition = { target, element, text: textOf(element) };
315
+ this.#runAbandon();
294
316
  this.#onStart?.(event);
295
317
  };
296
318
  #handleEnd = (event) => {
297
- if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
319
+ this.#runAbandon();
320
+ if (this.#composition?.target === event.currentTarget) this.#composition = null;
298
321
  this.#confirmedTarget = event.target;
299
322
  this.#onEnd?.(event);
300
323
  };
301
- /** A key on an observed field opens an edit of its own, so no echo is owed. */
302
- #handleKeydown = () => {
324
+ #handleKeydown = (event) => {
325
+ this.#settle();
326
+ this.#runAbandon();
303
327
  this.#confirmedTarget = null;
328
+ this.#keyComposing = event.isComposing === true;
329
+ };
330
+ #handleBeforeInput = (event) => {
331
+ const keyComposing = this.#keyComposing;
332
+ this.#keyComposing = false;
333
+ this.#settle();
334
+ const { isComposing, inputType } = event;
335
+ if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
336
+ this.#abandon(true);
337
+ }
338
+ this.#runAbandon();
339
+ };
340
+ #handleInput = (event) => {
341
+ const composition = this.#composition;
342
+ const { isTrusted, isComposing, inputType } = event;
343
+ if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
344
+ composition.text = textOf(composition.element);
345
+ } else {
346
+ this.#settle();
347
+ }
348
+ this.#runAbandon();
349
+ };
350
+ #handleBlur = (event) => {
351
+ this.#settle();
352
+ this.#runAbandon();
353
+ const composition = this.#composition;
354
+ if (composition === null || composition.target === composition.element) return;
355
+ const { element } = composition;
356
+ if (!(element instanceof Node) || event.target !== element) return;
357
+ const root = element.getRootNode();
358
+ if (root.activeElement !== element) this.#composition = null;
359
+ };
360
+ #handleBeforeMorphAttribute = (event) => {
361
+ this.#settle();
362
+ const composition = this.#composition;
363
+ if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
364
+ const detail = event.detail;
365
+ if (detail?.attributeName === "value") event.preventDefault();
304
366
  };
305
367
  };
306
368
 
307
- // src/utils/field_mirror.ts
308
369
  function writeFields(container, values, { name, form = "" }) {
309
370
  const current = [...container.children];
310
371
  const submits = current.length === values.length && current.every(
@@ -327,27 +388,36 @@ function commitField(target) {
327
388
  target.dispatchEvent(new Event("change", { bubbles: true }));
328
389
  }
329
390
 
330
- // src/utils/microtask_coalescer.ts
391
+ var ListenerSet = class {
392
+ #abort = new AbortController();
393
+ add(target, type, handler, options) {
394
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
395
+ }
396
+ dispose() {
397
+ this.#abort.abort();
398
+ this.#abort = new AbortController();
399
+ }
400
+ };
401
+
331
402
  var MicrotaskCoalescer = class {
332
403
  #run;
333
404
  #queued = false;
334
405
  #active = false;
335
406
  #generation = 0;
336
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
337
407
  constructor(run) {
338
408
  this.#run = run;
339
409
  }
340
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
341
410
  activate() {
342
411
  this.#active = true;
343
412
  }
344
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
345
413
  cancel() {
346
414
  this.#active = false;
347
415
  this.#queued = false;
348
416
  this.#generation += 1;
349
417
  }
350
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
418
+ get pending() {
419
+ return this.#queued;
420
+ }
351
421
  schedule() {
352
422
  if (!this.#active || this.#queued) return;
353
423
  this.#queued = true;
@@ -360,12 +430,93 @@ var MicrotaskCoalescer = class {
360
430
  }
361
431
  };
362
432
 
363
- // src/utils/target_selector.ts
433
+ var MorphRenderWatcher = class {
434
+ #listeners = new ListenerSet();
435
+ #pass;
436
+ #element = null;
437
+ #onMorph = () => this.#pass.schedule();
438
+ constructor(run) {
439
+ this.#pass = new MicrotaskCoalescer(run);
440
+ }
441
+ observe(element) {
442
+ if (this.#element === element) return;
443
+ this.disconnect();
444
+ this.#element = element;
445
+ this.#pass.activate();
446
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
447
+ }
448
+ schedule() {
449
+ this.#pass.schedule();
450
+ }
451
+ get pending() {
452
+ return this.#pass.pending;
453
+ }
454
+ disconnect() {
455
+ this.#listeners.dispose();
456
+ this.#pass.cancel();
457
+ this.#element = null;
458
+ }
459
+ };
460
+
461
+ var MoveCounter = class {
462
+ #sequence = 0;
463
+ record() {
464
+ this.#sequence += 1;
465
+ return this.#sequence;
466
+ }
467
+ isLatest(token) {
468
+ return token === this.#sequence;
469
+ }
470
+ };
471
+
472
+ var NUMBER_BOUNDS = {
473
+ nonNegative: { finite: true, min: 0 }};
474
+ function matchesNumberBounds(value, bounds) {
475
+ if (!Number.isFinite(value)) {
476
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
477
+ if (direction === null) return false;
478
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
479
+ }
480
+ if (bounds.min !== void 0 && value < bounds.min) return false;
481
+ if (bounds.max !== void 0 && value > bounds.max) return false;
482
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
483
+ if (bounds.integer && !Number.isInteger(value)) return false;
484
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
485
+ return true;
486
+ }
487
+
488
+ function readNumber(raw, fallback, bounds) {
489
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
490
+ }
491
+
492
+ var NumberValueReader = class {
493
+ #lastRejected = /* @__PURE__ */ new Map();
494
+ read(owner, name, raw, fallback, bounds) {
495
+ const resolved = readNumber(raw, fallback, bounds);
496
+ if (matchesNumberBounds(raw, bounds)) {
497
+ this.#lastRejected.delete(name);
498
+ return resolved;
499
+ }
500
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
501
+ const literal = owner.element.getAttribute(attribute);
502
+ if (literal === null) {
503
+ this.#lastRejected.delete(name);
504
+ return resolved;
505
+ }
506
+ if (this.#lastRejected.get(name) !== literal) {
507
+ this.#lastRejected.set(name, literal);
508
+ console.warn(
509
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
510
+ );
511
+ }
512
+ return resolved;
513
+ }
514
+ };
515
+
364
516
  function targetSelector(identifier, name) {
365
517
  return `[data-${identifier}-target~="${name}"]`;
366
518
  }
367
519
 
368
- // src/utils/template_row.ts
369
520
  function cloneTemplateRoot(template) {
370
521
  const root = template.content.firstElementChild;
371
522
  return root instanceof HTMLElement ? root.cloneNode(true) : null;
@@ -376,22 +527,15 @@ var TemplateRow = class {
376
527
  constructor(options) {
377
528
  this.#options = options;
378
529
  }
379
- /** The attribute selector for one declared part, in this controller's namespace. */
380
530
  selector(name) {
381
531
  return targetSelector(this.#options.identifier, name);
382
532
  }
383
- /** Re-arms the once-per-connection diagnostic. */
384
533
  connect() {
385
534
  this.#warned = false;
386
535
  }
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
536
  report(missing) {
392
537
  return this.#say(`lacks ${missing}`);
393
538
  }
394
- /** Writes one diagnostic per connection, saying what the template got wrong. */
395
539
  #say(problem) {
396
540
  if (this.#warned) return null;
397
541
  this.#warned = true;
@@ -399,10 +543,6 @@ var TemplateRow = class {
399
543
  console.warn(`Stimeo UI: "${identifier}" ${outcome} because its ${noun} ${problem}.`);
400
544
  return null;
401
545
  }
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
546
  instantiate(template, values) {
407
547
  const { root: rootName, required, optional, button: buttonName } = this.#options;
408
548
  const root = cloneTemplateRoot(template);
@@ -430,7 +570,6 @@ var TemplateRow = class {
430
570
  button.setAttribute("aria-label", fillTemplate(name, values));
431
571
  return { root, slots, button };
432
572
  }
433
- /** The row may be its own part; otherwise the first descendant carrying the target. */
434
573
  #resolve(root, name) {
435
574
  return matchingPart(root, this.selector(name));
436
575
  }
@@ -439,8 +578,9 @@ var TemplateRow = class {
439
578
  }
440
579
  };
441
580
 
442
- // src/controllers/tags_input_controller.ts
443
- var TagsInputController = class extends Controller {
581
+ var TagsInputController = class _TagsInputController extends Controller {
582
+ #numbers = new NumberValueReader();
583
+ #moves = new MoveCounter();
444
584
  static targets = ["input", "tags", "tag", "tagTemplate", "label", "remove", "fields"];
445
585
  static values = {
446
586
  delimiter: { type: String, default: "," },
@@ -451,11 +591,12 @@ var TagsInputController = class extends Controller {
451
591
  announceText: { type: String, default: "" },
452
592
  announceRemovedText: { type: String, default: "" }
453
593
  };
594
+ static valueConstraints = {
595
+ max: NUMBER_BOUNDS.nonNegative
596
+ };
454
597
  static actions = ["onKeydown"];
455
598
  static events = ["change", "reconcile", "reject"];
456
- /** Last reconciled tag order, separating user edits from DOM/Turbo repair. */
457
599
  #tagValues = [];
458
- /** Builds one chip from the authored template and owns its diagnostic. */
459
600
  #rows = new TemplateRow({
460
601
  identifier: this.identifier,
461
602
  root: "tag",
@@ -464,18 +605,15 @@ var TagsInputController = class extends Controller {
464
605
  outcome: "added no tag",
465
606
  noun: "chip template"
466
607
  });
467
- /** Collapses one target/Value mutation batch into one final-DOM repair pass. */
468
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileTags());
608
+ #reconcile = new MorphRenderWatcher(() => this.#reconcileTags());
469
609
  #chipRow = new ChipRow({
470
- directionElement: this.element,
610
+ getDirectionElement: () => this.#chipDirectionElement,
471
611
  getItems: () => this.tagTargets,
472
612
  getButton: (tag) => matchingPart(tag, `button${this.#rows.selector("remove")}`),
473
613
  onRemove: (index) => this.#removeAt(index),
474
614
  focusAfterEnd: () => this.#focusInput()
475
615
  });
476
- /** Owns IME lifecycle state for commit-key guarding. */
477
616
  #composition = new CompositionTracker();
478
- /** Wires tag-list keyboard navigation and removal, and seeds the single Tab stop. */
479
617
  connect() {
480
618
  this.#rows.connect();
481
619
  if (this.hasInputTarget) this.#composition.observe(this.inputTarget);
@@ -483,56 +621,47 @@ var TagsInputController = class extends Controller {
483
621
  const tags = this.#values;
484
622
  this.#syncState(tags);
485
623
  this.#tagValues = tags;
486
- this.#reconcile.activate();
624
+ this.#reconcile.observe(this.element);
487
625
  }
488
- /** Releases the delegated listeners so no handler outlives the element. */
489
626
  disconnect() {
490
- this.#reconcile.cancel();
627
+ this.#reconcile.disconnect();
491
628
  this.#composition.disconnect();
492
629
  this.#chipRow.disconnect();
493
630
  }
494
- /** Tracks an input added initially or after connect. */
495
631
  inputTargetConnected(input) {
496
632
  this.#composition.observe(input);
497
633
  }
498
- /** Removes composition listeners when the active input is replaced or removed. */
499
634
  inputTargetDisconnected(input) {
500
635
  this.#composition.unobserve(input);
501
636
  }
502
- /** Rebinds delegation and derived state when the tags container is replaced. */
503
637
  tagsTargetConnected(_tags) {
504
638
  this.#reconcile.schedule();
505
639
  }
506
- /** Releases only the row that disconnected, leaving a newer target intact. */
507
640
  tagsTargetDisconnected(tags) {
508
641
  this.#chipRow.disconnect(tags);
509
642
  this.#reconcile.schedule();
510
643
  }
511
- /** Reconciles a tag inserted by Turbo, a server render, or another controller. */
512
644
  tagTargetConnected(_tag) {
513
645
  this.#reconcile.schedule();
514
646
  }
515
- /** Reconciles a tag removed by Turbo, a server render, or another controller. */
516
647
  tagTargetDisconnected(_tag) {
517
648
  this.#reconcile.schedule();
518
649
  }
519
- /** Seeds a fields target inserted after connect. */
520
650
  fieldsTargetConnected() {
521
651
  this.#reconcile.schedule();
522
652
  }
523
- /** Rebuilds submitted fields when the public name changes at runtime. */
653
+ fieldsTargetDisconnected() {
654
+ this.#reconcile.schedule();
655
+ }
524
656
  nameValueChanged() {
525
657
  this.#reconcile.schedule();
526
658
  }
527
- /** Repoints submitted fields when the owning form changes at runtime. */
528
659
  formValueChanged() {
529
660
  this.#reconcile.schedule();
530
661
  }
531
- /** Recomputes the full hook when the cap changes at runtime. */
532
662
  maxValueChanged() {
533
663
  this.#reconcile.schedule();
534
664
  }
535
- /** Commits on `Enter`/delimiter and deletes the last tag on empty `Backspace`. */
536
665
  onKeydown(event) {
537
666
  if (event.defaultPrevented) return;
538
667
  if (isReservedArrowChord(event)) return;
@@ -551,25 +680,20 @@ var TagsInputController = class extends Controller {
551
680
  }
552
681
  return;
553
682
  }
554
- if (logicalArrowKey(event.key, this.element) === "ArrowLeft" && this.inputTarget.value === "") {
683
+ if (logicalArrowKey(event.key, this.#chipDirectionElement) === "ArrowLeft" && this.inputTarget.value === "") {
555
684
  if (this.#chipRow.length > 0) {
556
685
  event.preventDefault();
557
686
  this.#chipRow.focusLast();
558
687
  }
559
688
  }
560
689
  }
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
- */
566
690
  #commitInput() {
567
691
  const value = this.inputTarget.value.trim();
568
692
  if (value === "") {
569
693
  this.#reject(value, "empty");
570
694
  return;
571
695
  }
572
- if (this.maxValue > 0 && this.tagTargets.length >= this.maxValue) {
696
+ if (this.#safeMax > 0 && this.tagTargets.length >= this.#safeMax) {
573
697
  this.#reject(value, "max");
574
698
  return;
575
699
  }
@@ -580,18 +704,8 @@ var TagsInputController = class extends Controller {
580
704
  if (!this.#appendTag(value)) return;
581
705
  this.inputTarget.value = "";
582
706
  const tags = this.#values;
583
- this.#syncState(tags, true);
584
- this.#tagValues = tags;
585
- this.#announceTransition(true, value, tags.length);
586
- this.dispatch("change", { detail: { tags } });
707
+ this.#publishTags(tags, true, value);
587
708
  }
588
- /**
589
- * Builds one chip from the template and appends it to the tag list.
590
- *
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.
594
- */
595
709
  #appendTag(value) {
596
710
  if (!this.hasTagsTarget) return false;
597
711
  if (!this.hasTagTemplateTarget) {
@@ -606,76 +720,82 @@ var TagsInputController = class extends Controller {
606
720
  this.tagsTarget.appendChild(row.root);
607
721
  return true;
608
722
  }
609
- /** Removes the tag at `index`, then applies the interaction-origin focus policy. */
610
723
  #removeAt(index, focus = "neighbor") {
611
724
  const tag = this.tagTargets[index];
612
725
  if (!tag) return;
613
726
  const value = tag.dataset.value ?? "";
614
727
  tag.remove();
615
728
  const tags = this.#values;
616
- this.#syncState(tags, true);
617
- this.#tagValues = tags;
618
- this.#announceTransition(false, value, tags.length);
619
- this.dispatch("change", { detail: { tags } });
729
+ if (!this.#publishTags(tags, false, value)) return;
620
730
  if (focus === "input") {
621
731
  this.#focusInput();
622
732
  return;
623
733
  }
624
734
  if (!this.#chipRow.focusAfterRemoval(index)) this.#focusInput();
625
735
  }
626
- /** Moves focus to the current input when one exists. */
736
+ get #chipDirectionElement() {
737
+ return this.hasTagsTarget ? this.tagsTarget : this.element;
738
+ }
627
739
  #focusInput() {
628
740
  if (this.hasInputTarget) this.inputTarget.focus();
629
741
  }
630
- /**
631
- * Rebuilds the hidden form fields, the `full` flag, and the roving Tab stop.
632
- *
633
- * @stimeoRenderRoot
634
- */
635
- #syncState(values, notify = false) {
742
+ #syncState(values) {
743
+ let fieldChanged = false;
636
744
  if (this.hasFieldsTarget) {
637
745
  const options = { name: this.nameValue, form: this.formValue };
638
- if (writeFields(this.fieldsTarget, values, options) && notify) {
639
- commitField(this.fieldsTarget);
640
- }
746
+ fieldChanged = writeFields(this.fieldsTarget, values, options);
641
747
  }
642
- const full = this.maxValue > 0 && values.length >= this.maxValue;
748
+ const full = this.#safeMax > 0 && values.length >= this.#safeMax;
643
749
  this.element.toggleAttribute(`data-${this.identifier}-full`, full);
644
750
  this.#chipRow.ensureTabStop();
751
+ return fieldChanged;
752
+ }
753
+ #publishTags(tags, added, value) {
754
+ const changed = !this.#sameTags(this.#tagValues, tags);
755
+ const fieldChanged = this.#syncState(tags);
756
+ this.#tagValues = tags;
757
+ if (!changed) return true;
758
+ const token = this.#moves.record();
759
+ if (fieldChanged) commitField(this.fieldsTarget);
760
+ if (!this.#moves.isLatest(token)) return false;
761
+ this.#announceTransition(added, value, tags.length);
762
+ if (!this.#moves.isLatest(token)) return false;
763
+ this.dispatch("change", { detail: { tags } });
764
+ return this.#moves.isLatest(token);
645
765
  }
646
- /** Repairs derived state after DOM/Turbo changes and reports a changed tag order. */
647
766
  #reconcileTags() {
648
767
  if (this.hasTagsTarget) this.#chipRow.connect(this.tagsTarget);
649
- else this.#chipRow.disconnect();
650
768
  const tags = this.#values;
651
769
  this.#syncState(tags);
652
770
  const changed = !this.#sameTags(this.#tagValues, tags);
653
771
  this.#tagValues = tags;
654
- if (changed) this.dispatch("reconcile", { detail: { tags } });
772
+ if (changed) {
773
+ this.dispatch("reconcile", { detail: { tags } });
774
+ }
655
775
  }
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
- */
661
776
  #announceTransition(added, value, count) {
662
777
  const template = added ? this.announceTextValue : this.announceRemovedTextValue;
663
778
  announce(fillTemplate(template, { label: value, value, count }));
664
779
  }
665
- /** Reports rejection with `{ value, reason: "duplicate" | "empty" | "max" }`. */
666
780
  #reject(value, reason) {
667
781
  this.dispatch("reject", { detail: { value, reason } });
668
782
  }
669
- /** Current tag values in order. */
670
783
  get #values() {
671
784
  return this.tagTargets.map((tag) => tag.dataset.value ?? "");
672
785
  }
673
- /** Whether two arrays carry the same values in the same submitted order. */
674
786
  #sameTags(left, right) {
675
787
  return left.length === right.length && left.every((value, index) => value === right[index]);
676
788
  }
789
+ get #safeMax() {
790
+ return this.#numbers.read(
791
+ this,
792
+ "max",
793
+ this.maxValue,
794
+ _TagsInputController.values.max.default,
795
+ _TagsInputController.valueConstraints.max
796
+ );
797
+ }
677
798
  };
678
799
 
679
800
  export { TagsInputController };
680
- //# sourceMappingURL=tags_input_controller.js.map
681
801
  //# sourceMappingURL=tags_input_controller.js.map