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/nested_form_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,84 +16,160 @@ 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
- });
19
+
20
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
21
+ if (element.getAttribute(name) === value) return false;
22
+ if (value === null) element.removeAttribute(name);
23
+ else element.setAttribute(name, value);
24
+ return true;
25
+ }
26
+
27
+ function sharedRegistry(key, create) {
28
+ const symbol = Symbol.for(key);
29
+ const scope = globalThis;
30
+ const existing = scope[symbol];
31
+ if (existing !== void 0) return existing;
32
+ const registry2 = create();
33
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
34
+ return registry2;
20
35
  }
21
36
 
22
- // src/utils/attribute_lease.ts
37
+ var registry = sharedRegistry(
38
+ "stimeo-ui.attribute-lease.registry.v1",
39
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
40
+ );
41
+ function isAttributeValue(value) {
42
+ return value === null || typeof value === "string";
43
+ }
44
+ function readLeaseRecord(element, record) {
45
+ try {
46
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
47
+ if (!Array.isArray(parsed)) return void 0;
48
+ const [original, written, ...rest] = parsed;
49
+ const linked = rest[0] !== false;
50
+ const beneath = linked ? rest : rest.slice(1);
51
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
52
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
53
+ } catch {
54
+ return void 0;
55
+ }
56
+ }
57
+ function attributeStack(element, attribute) {
58
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
59
+ registry.stacks.set(element, byAttribute);
60
+ const known = byAttribute.get(attribute);
61
+ if (known) return known;
62
+ const found = [];
63
+ for (const record of registry.names.get(attribute) ?? []) {
64
+ const read = readLeaseRecord(element, record);
65
+ if (read) found.push({ ...read, record });
66
+ }
67
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
68
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
69
+ lease: null,
70
+ record,
71
+ written,
72
+ original,
73
+ linked: linked && index > 0
74
+ }));
75
+ byAttribute.set(attribute, stack);
76
+ return stack;
77
+ }
78
+ function findHold(stack, lease, record) {
79
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
80
+ }
23
81
  var AttributeLease = class {
24
82
  #attribute;
25
- #records = /* @__PURE__ */ new Map();
26
- /** @param attribute - The attribute whose temporary values this lease owns. */
27
- constructor(attribute) {
83
+ #record;
84
+ #base;
85
+ #preserveAuthor;
86
+ #held = /* @__PURE__ */ new Set();
87
+ constructor(attribute, owner, options = {}) {
28
88
  this.#attribute = attribute;
89
+ this.#record = `data-${owner}-${attribute}-lease`;
90
+ this.#base = options.base === true;
91
+ this.#preserveAuthor = options.preserveAuthor === true;
92
+ const names = registry.names;
93
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
29
94
  }
30
- /** Writes or removes the leased attribute while preserving its authored value. */
31
95
  write(element, value) {
32
- const existing = this.#records.get(element);
33
- if (existing) {
34
- existing.written = value;
35
- } else {
36
- this.#records.set(element, {
37
- original: element.getAttribute(this.#attribute),
38
- written: value
39
- });
40
- }
41
- this.#reflect(element, value);
96
+ const stack = attributeStack(element, this.#attribute);
97
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
98
+ this.#held.add(element);
99
+ const shows = !this.#covered(element, stack, hold);
100
+ hold.written = value;
101
+ this.#mark(element, stack);
102
+ if (shows) this.#reflect(element, value);
42
103
  }
43
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
44
104
  return(element) {
45
- const record = this.#records.get(element);
46
- if (!record) return;
47
- this.#records.delete(element);
48
- const stillOwned = element.getAttribute(this.#attribute) === record.written;
49
- if (stillOwned) this.#reflect(element, record.original);
50
- }
51
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
52
- #reflect(element, value) {
53
- if (element.getAttribute(this.#attribute) === value) return;
54
- if (value === null) element.removeAttribute(this.#attribute);
55
- else element.setAttribute(this.#attribute, value);
105
+ const held = this.#held.delete(element);
106
+ const known = registry.stacks.get(element)?.has(this.#attribute);
107
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
108
+ const hold = this.#take(stack);
109
+ if (!hold) return false;
110
+ const carries = element.getAttribute(this.#attribute) === hold.written;
111
+ const owns = carries && !this.#covered(element, stack, hold);
112
+ const index = stack.indexOf(hold);
113
+ stack.splice(index, 1);
114
+ const above = stack[index];
115
+ if (above) above.linked &&= hold.linked;
116
+ this.#mark(element, stack);
117
+ if (!owns) return true;
118
+ const below = hold.linked ? stack[index - 1] : void 0;
119
+ this.#reflect(element, below ? below.written : hold.original);
120
+ return true;
56
121
  }
57
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
58
122
  returnAll() {
59
- for (const element of Array.from(this.#records.keys())) this.return(element);
60
- }
61
- };
62
-
63
- // src/utils/before_cache_reset.ts
64
- var BeforeCacheReset = class _BeforeCacheReset {
65
- /** Every subscribed instance, iterated by the one shared document listener. */
66
- static #subscribers = /* @__PURE__ */ new Set();
67
- /** The shared listener; installed while at least one instance is subscribed. */
68
- static #onBeforeCache = () => {
69
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
70
- };
71
- #rewind;
72
- /** @param rewind - the pass that returns this controller's state to its initial form. */
73
- constructor(rewind) {
74
- this.#rewind = rewind;
75
- }
76
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
77
- activate() {
78
- const first = _BeforeCacheReset.#subscribers.size === 0;
79
- _BeforeCacheReset.#subscribers.add(this);
80
- if (first) {
81
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
123
+ for (const element of Array.from(this.#held)) this.return(element);
124
+ }
125
+ #take(stack) {
126
+ const hold = findHold(stack, this, this.#record);
127
+ if (hold) hold.lease = this;
128
+ return hold;
129
+ }
130
+ #acquire(element, stack) {
131
+ const current = element.getAttribute(this.#attribute);
132
+ const top = stack.at(-1);
133
+ const bottom = stack[0];
134
+ const over = top?.written === current ? top : void 0;
135
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
136
+ const hold = {
137
+ lease: this,
138
+ record: this.#record,
139
+ written: current,
140
+ original: from ? from.original : current,
141
+ linked: !this.#base && over !== void 0
142
+ };
143
+ if (this.#base && bottom) {
144
+ bottom.linked = true;
145
+ stack.unshift(hold);
146
+ } else stack.push(hold);
147
+ return hold;
148
+ }
149
+ #covered(element, stack, hold) {
150
+ const current = element.getAttribute(this.#attribute);
151
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
152
+ }
153
+ #mark(element, stack) {
154
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
155
+ stack.forEach((hold, index) => {
156
+ const names = stack.slice(0, index).map((under) => under.record);
157
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
158
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
159
+ records.set(
160
+ hold.record,
161
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
162
+ );
163
+ });
164
+ for (const [name, recorded] of records) {
165
+ setAttributeIfChanged(element, name, recorded);
82
166
  }
83
167
  }
84
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
85
- deactivate() {
86
- _BeforeCacheReset.#subscribers.delete(this);
87
- if (_BeforeCacheReset.#subscribers.size > 0) return;
88
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
168
+ #reflect(element, value) {
169
+ setAttributeIfChanged(element, this.#attribute, value);
89
170
  }
90
171
  };
91
172
 
92
- // src/utils/focus_candidate.ts
93
173
  function inheritsFieldsetDisabled(control) {
94
174
  let fieldset = control.closest("fieldset[disabled]");
95
175
  while (fieldset) {
@@ -99,8 +179,19 @@ function inheritsFieldsetDisabled(control) {
99
179
  }
100
180
  return false;
101
181
  }
182
+ function unslotted(element) {
183
+ return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
184
+ }
185
+ function flatTreeParent(element) {
186
+ if (unslotted(element)) return null;
187
+ const parent = element.assignedSlot ?? element.parentNode;
188
+ if (parent instanceof ShadowRoot) return parent.host;
189
+ return parent instanceof Element ? parent : null;
190
+ }
102
191
  function canTakeFocus(element) {
103
- if (element.closest("[hidden], [inert]")) return false;
192
+ for (let node = element; node; node = flatTreeParent(node)) {
193
+ if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
194
+ }
104
195
  if (element instanceof HTMLInputElement && element.type === "hidden") return false;
105
196
  if (!("disabled" in element)) return true;
106
197
  if (element.disabled) return false;
@@ -163,28 +254,42 @@ function firstTabStop(root) {
163
254
  }
164
255
  return null;
165
256
  }
257
+ function deepActiveElement(ownerDocument) {
258
+ let active = ownerDocument.activeElement;
259
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
260
+ return active;
261
+ }
262
+
263
+ var ListenerSet = class {
264
+ #abort = new AbortController();
265
+ add(target, type, handler, options) {
266
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
267
+ }
268
+ dispose() {
269
+ this.#abort.abort();
270
+ this.#abort = new AbortController();
271
+ }
272
+ };
166
273
 
167
- // src/utils/microtask_coalescer.ts
168
274
  var MicrotaskCoalescer = class {
169
275
  #run;
170
276
  #queued = false;
171
277
  #active = false;
172
278
  #generation = 0;
173
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
174
279
  constructor(run) {
175
280
  this.#run = run;
176
281
  }
177
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
178
282
  activate() {
179
283
  this.#active = true;
180
284
  }
181
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
182
285
  cancel() {
183
286
  this.#active = false;
184
287
  this.#queued = false;
185
288
  this.#generation += 1;
186
289
  }
187
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
290
+ get pending() {
291
+ return this.#queued;
292
+ }
188
293
  schedule() {
189
294
  if (!this.#active || this.#queued) return;
190
295
  this.#queued = true;
@@ -197,56 +302,213 @@ var MicrotaskCoalescer = class {
197
302
  }
198
303
  };
199
304
 
200
- // src/utils/tabindex_loan.ts
201
- var TabindexLoan = class {
305
+ var MorphRenderWatcher = class {
306
+ #listeners = new ListenerSet();
307
+ #pass;
308
+ #element = null;
309
+ #onMorph = () => this.#pass.schedule();
310
+ constructor(run) {
311
+ this.#pass = new MicrotaskCoalescer(run);
312
+ }
313
+ observe(element) {
314
+ if (this.#element === element) return;
315
+ this.disconnect();
316
+ this.#element = element;
317
+ this.#pass.activate();
318
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
319
+ }
320
+ schedule() {
321
+ this.#pass.schedule();
322
+ }
323
+ get pending() {
324
+ return this.#pass.pending;
325
+ }
326
+ disconnect() {
327
+ this.#listeners.dispose();
328
+ this.#pass.cancel();
329
+ this.#element = null;
330
+ }
331
+ };
332
+
333
+ var NUMBER_BOUNDS = {
334
+ nonNegative: { finite: true, min: 0 }};
335
+ function matchesNumberBounds(value, bounds) {
336
+ if (!Number.isFinite(value)) {
337
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
338
+ if (direction === null) return false;
339
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
340
+ }
341
+ if (bounds.min !== void 0 && value < bounds.min) return false;
342
+ if (bounds.max !== void 0 && value > bounds.max) return false;
343
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
344
+ if (bounds.integer && !Number.isInteger(value)) return false;
345
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
346
+ return true;
347
+ }
348
+
349
+ function readNumber(raw, fallback, bounds) {
350
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
351
+ }
352
+
353
+ var NumberValueReader = class {
354
+ #lastRejected = /* @__PURE__ */ new Map();
355
+ read(owner, name, raw, fallback, bounds) {
356
+ const resolved = readNumber(raw, fallback, bounds);
357
+ if (matchesNumberBounds(raw, bounds)) {
358
+ this.#lastRejected.delete(name);
359
+ return resolved;
360
+ }
361
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
362
+ const literal = owner.element.getAttribute(attribute);
363
+ if (literal === null) {
364
+ this.#lastRejected.delete(name);
365
+ return resolved;
366
+ }
367
+ if (this.#lastRejected.get(name) !== literal) {
368
+ this.#lastRejected.set(name, literal);
369
+ console.warn(
370
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
371
+ );
372
+ }
373
+ return resolved;
374
+ }
375
+ };
376
+
377
+ function queryOpenDescendants(root, selector) {
378
+ const matches = [];
379
+ const roots = [root];
380
+ for (const scope of roots) {
381
+ matches.push(...scope.querySelectorAll(selector));
382
+ if (scope.nodeType === Node.ELEMENT_NODE) {
383
+ const shadow = scope.shadowRoot;
384
+ if (shadow) roots.push(shadow);
385
+ }
386
+ for (const element of scope.querySelectorAll("*")) {
387
+ if (element.shadowRoot) roots.push(element.shadowRoot);
388
+ }
389
+ }
390
+ return matches;
391
+ }
392
+
393
+ function holdsFocus(element) {
394
+ return element.matches(":focus") || deepActiveElement(element.ownerDocument) === element;
395
+ }
396
+ var TabindexLoan = class _TabindexLoan {
397
+ static #registry = sharedRegistry(
398
+ "stimeo-ui.tabindex-loan.registry.v1",
399
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
400
+ );
202
401
  #value;
402
+ #record;
403
+ #preserveFocus;
203
404
  #lent = /* @__PURE__ */ new Set();
204
- /** Returns live loans before Turbo can copy them into its page snapshot. */
205
- #beforeCache = new BeforeCacheReset(() => this.returnAll());
206
- /**
207
- * @param value - the `tabindex` to lend. `"-1"` (the default) is
208
- * programmatically focusable but not a Tab stop; `"0"` is a real Tab stop,
209
- * which a scroll region with no focusable content of its own needs.
210
- */
211
- constructor(value = "-1") {
405
+ constructor(value, owner, options = {}) {
212
406
  this.#value = value;
213
- }
214
- /** Lends `element` the value; no-ops when it already carries a `tabindex`. */
215
- lend(element) {
216
- if (element.hasAttribute("tabindex")) return;
217
- element.setAttribute("tabindex", this.#value);
407
+ this.#record = `data-${owner}-tabindex-loan`;
408
+ this.#preserveFocus = options.preserveFocus === true;
409
+ _TabindexLoan.#registry.names.add(this.#record);
410
+ }
411
+ lend(element, options = {}) {
412
+ const stack = this.#stack(element);
413
+ const current = element.getAttribute("tabindex");
414
+ const held = stack.find((hold) => hold.loan === this) ?? stack.find(
415
+ (hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
416
+ );
417
+ const linked = stack.at(-1)?.value === current;
418
+ if (!held && options.onlyIfUnfocusable && (current !== null || element.tabIndex >= 0)) return;
419
+ if (!held && current !== null && !linked) return;
218
420
  this.#lent.add(element);
219
- this.#beforeCache.activate();
421
+ if (held) held.loan = this;
422
+ else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
423
+ this.#mark(element, stack);
424
+ if (current === null || !held && current !== this.#value) {
425
+ element.setAttribute("tabindex", this.#value);
426
+ }
427
+ }
428
+ return(element) {
429
+ const stack = this.#stack(element);
430
+ const hold = stack.find((held) => held.loan === this) ?? stack.find(
431
+ (held) => held.loan === null && held.record === this.#record && held.value === this.#value
432
+ );
433
+ if (!hold) return false;
434
+ if (!this.#release(element, stack, hold, this.#preserveFocus)) {
435
+ hold.loan = this;
436
+ this.#lent.add(element);
437
+ return false;
438
+ }
439
+ this.#lent.delete(element);
440
+ return true;
220
441
  }
221
- /** Takes back every loan whose value is still the one that was lent. */
222
442
  returnAll() {
223
- for (const element of this.#lent) {
224
- if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
443
+ for (const element of Array.from(this.#lent)) this.return(element);
444
+ }
445
+ reclaim(element) {
446
+ if (element.getAttribute(this.#record) !== this.#value) return;
447
+ const stack = this.#stack(element);
448
+ const hold = stack.find((held) => held.loan === null && held.record === this.#record);
449
+ if (hold && !holdsFocus(element)) this.#release(element, stack, hold);
450
+ }
451
+ reclaimWithin(root) {
452
+ for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
453
+ this.reclaim(element);
454
+ }
455
+ }
456
+ #stack(element) {
457
+ const stacks = _TabindexLoan.#registry.stacks;
458
+ const stack = stacks.get(element) ?? this.#seed(element);
459
+ stacks.set(element, stack);
460
+ return stack;
461
+ }
462
+ #seed(element) {
463
+ const current = element.getAttribute("tabindex");
464
+ const holds = [];
465
+ for (const record of _TabindexLoan.#registry.names) {
466
+ const value = element.getAttribute(record);
467
+ if (value !== null) holds.push({ loan: null, record, value, linked: true });
468
+ }
469
+ holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
470
+ for (const [index, hold] of holds.entries()) hold.linked = index > 0;
471
+ return holds;
472
+ }
473
+ #release(element, stack, hold, preserveFocus = false) {
474
+ const current = element.getAttribute("tabindex");
475
+ const index = stack.indexOf(hold);
476
+ const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
477
+ if (owns && preserveFocus && holdsFocus(element)) return false;
478
+ stack.splice(index, 1);
479
+ const above = stack[index];
480
+ if (above) above.linked &&= hold.linked;
481
+ this.#mark(element, stack);
482
+ if (!owns) return true;
483
+ const below = hold.linked ? stack[index - 1] : void 0;
484
+ if (!below) element.removeAttribute("tabindex");
485
+ else if (below.value !== current) element.setAttribute("tabindex", below.value);
486
+ return true;
487
+ }
488
+ #mark(element, stack) {
489
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
490
+ for (const hold of stack) records.set(hold.record, hold.value);
491
+ for (const [name, recorded] of records) {
492
+ setAttributeIfChanged(element, name, recorded);
225
493
  }
226
- this.#lent.clear();
227
- this.#beforeCache.deactivate();
228
494
  }
229
495
  };
230
496
 
231
- // src/utils/target_selector.ts
232
497
  function targetSelector(identifier, name) {
233
498
  return `[data-${identifier}-target~="${name}"]`;
234
499
  }
235
500
 
236
- // src/controllers/nested_form_controller.ts
237
501
  var REMOVE_PART = "remove";
238
502
  var DESTROY_FLAG_PART = "destroyFlag";
239
503
  var DESTROYED_VALUES = /* @__PURE__ */ new Set(["1", "true"]);
240
- var NestedFormController = class extends Controller {
241
- /** Selects any root of this controller; the nearest one owns an element. */
504
+ var NestedFormController = class _NestedFormController extends Controller {
505
+ #numbers = new NumberValueReader();
242
506
  get #rootSelector() {
243
507
  return `[data-controller~="${this.identifier}"]`;
244
508
  }
245
- /** Selects per-row remove buttons, in the namespace this controller is registered under. */
246
509
  get #removeSelector() {
247
510
  return targetSelector(this.identifier, REMOVE_PART);
248
511
  }
249
- /** Selects the hidden `_destroy` input a persisted row carries. */
250
512
  get #destroyFlagSelector() {
251
513
  return targetSelector(this.identifier, DESTROY_FLAG_PART);
252
514
  }
@@ -258,27 +520,20 @@ var NestedFormController = class extends Controller {
258
520
  announce: { type: Boolean, default: true },
259
521
  countMessage: { type: String, default: "" }
260
522
  };
523
+ static valueConstraints = {
524
+ min: NUMBER_BOUNDS.nonNegative,
525
+ max: NUMBER_BOUNDS.nonNegative
526
+ };
261
527
  static actions = ["add"];
262
528
  static events = ["add", "remove", "reconcile"];
263
- /** Monotonic source for unique row indices; never a row-state counter. */
264
529
  #lastIndex = 0;
265
530
  #warnedMissing = false;
266
531
  #warnedTemplate = false;
267
- /** The state last written to the hooks; reconciliation reports only real moves. */
268
532
  #published = null;
269
- /** Watches the list for row changes the controller did not perform itself. */
270
533
  #observer = null;
271
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileNow());
272
- /** Restores the authored add-button `disabled` when a lease ends. */
273
- #addDisabled = new AttributeLease("disabled");
274
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
275
- /** Makes the root a programmatic focus destination when no other candidate survives. */
276
- #tabindex = new TabindexLoan();
277
- /**
278
- * Delegated click handler for the per-row remove buttons (dynamic-safe). Only
279
- * buttons whose nearest nested-form root is this instance are acted on, so a
280
- * nested inner form's buttons never remove an outer row.
281
- */
534
+ #reconcile = new MorphRenderWatcher(() => this.#reconcileNow());
535
+ #addDisabled = new AttributeLease("disabled", this.identifier);
536
+ #tabindex = new TabindexLoan("-1", this.identifier);
282
537
  #onClick = (event) => {
283
538
  const target = event.target;
284
539
  const button = target?.closest(this.#removeSelector);
@@ -286,41 +541,33 @@ var NestedFormController = class extends Controller {
286
541
  const row = this.#rowContaining(button);
287
542
  if (row) this.#removeRow(row);
288
543
  };
544
+ #listeners = new ListenerSet();
289
545
  connect() {
546
+ this.#tabindex.reclaim(this.element);
290
547
  this.#warnedMissing = false;
291
548
  this.#warnedTemplate = false;
292
- this.element.addEventListener("click", this.#onClick);
293
- this.#reconcile.activate();
294
- this.#beforeCache.activate();
549
+ this.#listeners.add(this.element, "click", this.#onClick);
550
+ this.#reconcile.observe(this.element);
295
551
  if (!this.hasListTarget || !this.hasTemplateTarget) this.#warnMissing();
296
552
  this.#refresh();
297
553
  }
298
554
  disconnect() {
299
- this.element.removeEventListener("click", this.#onClick);
555
+ this.#listeners.dispose();
300
556
  this.#observer?.disconnect();
301
557
  this.#observer = null;
302
- this.#reconcile.cancel();
303
- this.#beforeCache.deactivate();
304
- this.#rewindForCache();
558
+ this.#reconcile.disconnect();
559
+ this.#addDisabled.returnAll();
305
560
  this.#tabindex.returnAll();
306
561
  this.#published = null;
307
562
  }
308
- /** Follows an arriving or swapped-in list: rebind to the primary, then reconcile. */
309
563
  listTargetConnected() {
310
564
  this.#rebindObserver();
311
565
  this.#reconcile.schedule();
312
566
  }
313
- /** Follows a departing list the same way — the primary may have changed. */
314
567
  listTargetDisconnected() {
315
568
  this.#rebindObserver();
316
569
  this.#reconcile.schedule();
317
570
  }
318
- /**
319
- * Points the observer at the current primary list. Re-deriving on every list
320
- * arrival and departure makes the binding independent of the order Stimulus
321
- * reports an overlapping swap in — a staggered swap (successor appended before
322
- * the old list leaves) ends observed and reconciled either way.
323
- */
324
571
  #rebindObserver() {
325
572
  this.#observer?.disconnect();
326
573
  this.#observer = null;
@@ -333,7 +580,6 @@ var NestedFormController = class extends Controller {
333
580
  attributeFilter: ["value"]
334
581
  });
335
582
  }
336
- /** Returns the lease with a departing add button; a new one re-arms on refresh. */
337
583
  addTargetDisconnected(target) {
338
584
  this.#addDisabled.return(target);
339
585
  this.#reconcile.schedule();
@@ -341,21 +587,12 @@ var NestedFormController = class extends Controller {
341
587
  addTargetConnected() {
342
588
  this.#reconcile.schedule();
343
589
  }
344
- /** Re-clamps when application code or a Turbo morph changes `min`. */
345
590
  minValueChanged() {
346
591
  this.#reconcile.schedule();
347
592
  }
348
- /** Re-clamps when application code or a Turbo morph changes `max`. */
349
593
  maxValueChanged() {
350
594
  this.#reconcile.schedule();
351
595
  }
352
- /**
353
- * Clones the template row, replaces the index placeholder with a unique value,
354
- * appends it, focuses its first tab stop, and announces the new count. No-ops at
355
- * `max`, when the required targets are missing (named on the console once per
356
- * connection), or when the template does not produce exactly one root element
357
- * (also named once; the insertion is rolled back so nothing accumulates).
358
- */
359
596
  add() {
360
597
  if (!this.hasListTarget || !this.hasTemplateTarget) {
361
598
  this.#warnMissing();
@@ -386,13 +623,6 @@ var NestedFormController = class extends Controller {
386
623
  this.dispatch("add", { detail: { index, element: row } });
387
624
  this.#announce();
388
625
  }
389
- /**
390
- * Removes a row: a persisted row (one carrying its own `destroyFlag`) has the
391
- * flag set to `1` and is hidden so Rails destroys it on submit; an unsaved row
392
- * is dropped from the DOM. Returns focus to a surviving row. No-ops at `min`.
393
- *
394
- * @stimeoRuntimeOnly `min` decides whether this one removal is allowed.
395
- */
396
626
  #removeRow(row) {
397
627
  const rows = this.#effectiveRows;
398
628
  if (this.#destroyed(row)) {
@@ -400,7 +630,7 @@ var NestedFormController = class extends Controller {
400
630
  this.#focusAfterRemove(this.#positionAmong(rows, row));
401
631
  return;
402
632
  }
403
- if (rows.length <= this.minValue) return;
633
+ if (rows.length <= this.#safeMin) return;
404
634
  const position = rows.indexOf(row);
405
635
  const flag = this.#destroyFlagOf(row);
406
636
  const persisted = flag !== null;
@@ -415,21 +645,11 @@ var NestedFormController = class extends Controller {
415
645
  this.dispatch("remove", { detail: { element: row, persisted } });
416
646
  this.#announce();
417
647
  }
418
- /**
419
- * The index of the first effective row following `row` in document order.
420
- * -1 (no following row) feeds the focus slices as a negative index, which
421
- * yields the same fully-reversed nearest-first order as `rows.length` would.
422
- */
423
648
  #positionAmong(rows, row) {
424
649
  return rows.findIndex(
425
650
  (candidate) => (row.compareDocumentPosition(candidate) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0
426
651
  );
427
652
  }
428
- /**
429
- * Moves focus to the first tab stop of the nearest surviving row — following
430
- * rows first, then preceding ones — falling back to the add button and finally
431
- * to the root via a temporary `tabindex`.
432
- */
433
653
  #focusAfterRemove(position) {
434
654
  const rows = this.#effectiveRows;
435
655
  const ordered = [...rows.slice(position), ...rows.slice(0, position).reverse()];
@@ -447,31 +667,20 @@ var NestedFormController = class extends Controller {
447
667
  this.#tabindex.lend(this.element);
448
668
  this.element.focus();
449
669
  }
450
- /**
451
- * Recomputes the count and min/max hooks from the DOM and records them as published.
452
- *
453
- * @stimeoRenderRoot
454
- */
455
670
  #refresh() {
456
671
  if (!this.hasListTarget) return;
457
672
  const count = this.#effectiveRows.length;
458
- const atMin = count <= this.minValue;
459
- const atMax = this.maxValue > 0 && count >= this.maxValue;
673
+ const atMin = count <= this.#safeMin;
674
+ const atMax = this.#safeMax > 0 && count >= this.#safeMax;
460
675
  this.element.setAttribute("data-nested-count", String(count));
461
676
  this.#reflect("data-nested-at-max", atMax);
462
677
  this.#reflect("data-nested-at-min", atMin);
463
678
  if (this.hasAddTarget) {
464
- if (this.maxValue > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
679
+ if (this.#safeMax > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
465
680
  else this.#addDisabled.return(this.addTarget);
466
681
  }
467
682
  this.#published = { count, atMin, atMax };
468
683
  }
469
- /**
470
- * Applies row changes the controller did not perform itself (Turbo Streams,
471
- * morphs, runtime Value changes): refreshes the hooks and reports a moved
472
- * public state as `reconcile`. The controller's own operations refresh
473
- * synchronously first, so their observer echo arrives here as a no-move.
474
- */
475
684
  #reconcileNow() {
476
685
  const previous = this.#published;
477
686
  this.#refresh();
@@ -480,25 +689,14 @@ var NestedFormController = class extends Controller {
480
689
  const moved = previous.count !== current.count || previous.atMin !== current.atMin || previous.atMax !== current.atMax;
481
690
  if (moved) this.dispatch("reconcile", { detail: { ...current } });
482
691
  }
483
- /**
484
- * Bridges the count change to the shared announcer when configured.
485
- *
486
- * @stimeoRuntimeOnly `announce` and `countMessage` word the one announcement of this change.
487
- */
488
692
  #announce() {
489
693
  if (!this.announceValue || this.countMessageValue === "") return;
490
694
  announce(fillTemplate(this.countMessageValue, { count: this.#effectiveRows.length }));
491
695
  }
492
- /** Sets `attribute` to `"true"` when `on`, else removes it. */
493
696
  #reflect(attribute, on) {
494
697
  if (on) this.element.setAttribute(attribute, "true");
495
698
  else this.element.removeAttribute(attribute);
496
699
  }
497
- /** Returns the disabled lease so an authored value never leaks into a snapshot. */
498
- #rewindForCache() {
499
- if (this.hasAddTarget) this.#addDisabled.return(this.addTarget);
500
- }
501
- /** Names the missing required target(s) once per connection. */
502
700
  #warnMissing() {
503
701
  if (this.#warnedMissing) return;
504
702
  this.#warnedMissing = true;
@@ -510,49 +708,38 @@ var NestedFormController = class extends Controller {
510
708
  `Stimeo UI: "${this.identifier}" cannot manage rows because its markup lacks ${missing}.`
511
709
  );
512
710
  }
513
- /** Names a template that does not produce exactly one element, once per connection. */
514
711
  #warnBadTemplate(produced) {
515
712
  if (this.#warnedTemplate) return;
516
713
  this.#warnedTemplate = true;
517
714
  const reason = produced === 0 ? "produces no element" : "must produce exactly one root element";
518
715
  console.warn(`Stimeo UI: "${this.identifier}" added no row because its template ${reason}.`);
519
716
  }
520
- /** A strictly-increasing unique index (collision-free even on rapid adds). */
521
717
  #nextIndex() {
522
718
  const index = Math.max(Date.now(), this.#lastIndex + 1);
523
719
  this.#lastIndex = index;
524
720
  return index;
525
721
  }
526
722
  get #atMax() {
527
- return this.maxValue > 0 && this.#effectiveRows.length >= this.maxValue;
723
+ return this.#safeMax > 0 && this.#effectiveRows.length >= this.#safeMax;
528
724
  }
529
- /**
530
- * Direct child rows of the list whose own destroy flag is not set. Callers
531
- * reach this only behind a list-presence gate (`add`, `#refresh`, and the
532
- * click path through `#rowContaining`).
533
- */
534
725
  get #effectiveRows() {
535
726
  return Array.from(this.listTarget.children).filter(
536
727
  (row) => !this.#destroyed(row)
537
728
  );
538
729
  }
539
- /** Whether `row` is flagged for destruction; the flag value is the truth source. */
540
730
  #destroyed(row) {
541
731
  const flag = this.#destroyFlagOf(row);
542
732
  return flag !== null && DESTROYED_VALUES.has(flag.value);
543
733
  }
544
- /** The row's own destroy flag, skipping flags owned by a nested inner form. */
545
734
  #destroyFlagOf(row) {
546
735
  for (const flag of row.querySelectorAll(this.#destroyFlagSelector)) {
547
736
  if (this.#ownerOf(flag) === this.element) return flag;
548
737
  }
549
738
  return null;
550
739
  }
551
- /** The nearest nested-form root that owns `el`. */
552
740
  #ownerOf(el) {
553
741
  return el.closest(this.#rootSelector);
554
742
  }
555
- /** The nearest ancestor of `el` that is a direct child of the list, else null. */
556
743
  #rowContaining(el) {
557
744
  if (!this.hasListTarget) return null;
558
745
  let node = el;
@@ -561,8 +748,25 @@ var NestedFormController = class extends Controller {
561
748
  }
562
749
  return node;
563
750
  }
751
+ get #safeMin() {
752
+ return this.#numbers.read(
753
+ this,
754
+ "min",
755
+ this.minValue,
756
+ _NestedFormController.values.min.default,
757
+ _NestedFormController.valueConstraints.min
758
+ );
759
+ }
760
+ get #safeMax() {
761
+ return this.#numbers.read(
762
+ this,
763
+ "max",
764
+ this.maxValue,
765
+ _NestedFormController.values.max.default,
766
+ _NestedFormController.valueConstraints.max
767
+ );
768
+ }
564
769
  };
565
770
 
566
771
  export { NestedFormController };
567
- //# sourceMappingURL=nested_form_controller.js.map
568
772
  //# sourceMappingURL=nested_form_controller.js.map