stimeo-ui 0.15.0 → 0.17.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 (332) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +128 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +392 -417
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +34 -5
  7. package/dist/controllers/accordion_controller.js +228 -28
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +20 -9
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +38 -7
  13. package/dist/controllers/announcer_controller.js +135 -191
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +295 -110
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +172 -106
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  23. package/dist/controllers/breadcrumb_controller.js +0 -129
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +22 -4
  26. package/dist/controllers/bulk_select_controller.js +264 -53
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +109 -28
  29. package/dist/controllers/calendar_controller.js +346 -180
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +99 -27
  32. package/dist/controllers/carousel_controller.js +587 -300
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +18 -2
  35. package/dist/controllers/character_counter_controller.js +271 -143
  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 +77 -75
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +44 -7
  41. package/dist/controllers/clipboard_controller.js +187 -108
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +41 -5
  44. package/dist/controllers/collapsible_controller.js +374 -108
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +47 -14
  47. package/dist/controllers/color_picker_controller.js +183 -127
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +33 -6
  50. package/dist/controllers/combobox_controller.js +426 -109
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +45 -14
  53. package/dist/controllers/command_palette_controller.js +994 -350
  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 +212 -125
  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 +939 -245
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +36 -3
  62. package/dist/controllers/context_menu_controller.js +275 -127
  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 +75 -2
  68. package/dist/controllers/countdown_controller.js +235 -97
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -15
  71. package/dist/controllers/currency_input_controller.js +241 -118
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +78 -22
  74. package/dist/controllers/data_grid_controller.js +229 -135
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +64 -11
  77. package/dist/controllers/date_range_picker_controller.js +359 -165
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +22 -3
  80. package/dist/controllers/dialog_controller.js +887 -225
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  83. package/dist/controllers/direct_upload_controller.js +80 -142
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  86. package/dist/controllers/dirty_form_controller.js +26 -47
  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 +175 -20
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +52 -16
  92. package/dist/controllers/drawer_controller.js +963 -336
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +39 -3
  95. package/dist/controllers/dropdown_controller.js +342 -85
  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 +182 -58
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +67 -53
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  103. package/dist/controllers/file_dropzone_controller.js +426 -229
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.d.ts +10 -4
  106. package/dist/controllers/filter_controller.js +78 -36
  107. package/dist/controllers/filter_controller.js.map +1 -1
  108. package/dist/controllers/flash_controller.d.ts +57 -14
  109. package/dist/controllers/flash_controller.js +328 -211
  110. package/dist/controllers/flash_controller.js.map +1 -1
  111. package/dist/controllers/focus_controller.d.ts +12 -3
  112. package/dist/controllers/focus_controller.js +654 -239
  113. package/dist/controllers/focus_controller.js.map +1 -1
  114. package/dist/controllers/form_field_controller.js +153 -136
  115. package/dist/controllers/form_field_controller.js.map +1 -1
  116. package/dist/controllers/form_validation_controller.js +29 -96
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  119. package/dist/controllers/frame_loading_controller.js +263 -198
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +22 -2
  122. package/dist/controllers/highlight_controller.js +119 -70
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +65 -4
  125. package/dist/controllers/hover_card_controller.js +335 -118
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +58 -4
  128. package/dist/controllers/idle_controller.js +316 -65
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +5 -2
  131. package/dist/controllers/input_mask_controller.js +116 -61
  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 +147 -103
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  137. package/dist/controllers/lazy_frame_controller.js +9 -56
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +84 -10
  140. package/dist/controllers/listbox_controller.js +413 -153
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +1 -1
  143. package/dist/controllers/local_time_controller.js +54 -64
  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 +70 -95
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +40 -9
  149. package/dist/controllers/menu_controller.js +254 -167
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +15 -4
  152. package/dist/controllers/menubar_controller.js +206 -331
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +20 -0
  155. package/dist/controllers/meter_controller.js +159 -71
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +50 -7
  158. package/dist/controllers/multi_select_controller.js +797 -388
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +19 -1
  161. package/dist/controllers/navigation_menu_controller.js +156 -231
  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 +373 -174
  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 +241 -45
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +62 -10
  170. package/dist/controllers/number_input_controller.js +387 -227
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.js +28 -75
  173. package/dist/controllers/optimistic_controller.js.map +1 -1
  174. package/dist/controllers/otp_controller.d.ts +38 -7
  175. package/dist/controllers/otp_controller.js +344 -196
  176. package/dist/controllers/otp_controller.js.map +1 -1
  177. package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
  178. package/dist/controllers/overflow_indicator_controller.js +167 -93
  179. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  180. package/dist/controllers/overflow_menu_controller.d.ts +66 -9
  181. package/dist/controllers/overflow_menu_controller.js +408 -277
  182. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  183. package/dist/controllers/pagination_controller.d.ts +52 -15
  184. package/dist/controllers/pagination_controller.js +342 -120
  185. package/dist/controllers/pagination_controller.js.map +1 -1
  186. package/dist/controllers/password_reveal_controller.d.ts +60 -9
  187. package/dist/controllers/password_reveal_controller.js +424 -99
  188. package/dist/controllers/password_reveal_controller.js.map +1 -1
  189. package/dist/controllers/password_strength_controller.d.ts +14 -5
  190. package/dist/controllers/password_strength_controller.js +99 -153
  191. package/dist/controllers/password_strength_controller.js.map +1 -1
  192. package/dist/controllers/persist_controller.d.ts +11 -4
  193. package/dist/controllers/persist_controller.js +252 -96
  194. package/dist/controllers/persist_controller.js.map +1 -1
  195. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  196. package/dist/controllers/pointer_drag_controller.js +263 -164
  197. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  198. package/dist/controllers/popover_controller.d.ts +46 -4
  199. package/dist/controllers/popover_controller.js +267 -94
  200. package/dist/controllers/popover_controller.js.map +1 -1
  201. package/dist/controllers/portal_controller.d.ts +44 -4
  202. package/dist/controllers/portal_controller.js +209 -72
  203. package/dist/controllers/portal_controller.js.map +1 -1
  204. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  205. package/dist/controllers/preview_guard_controller.js +243 -112
  206. package/dist/controllers/preview_guard_controller.js.map +1 -1
  207. package/dist/controllers/progress_controller.d.ts +11 -0
  208. package/dist/controllers/progress_controller.js +132 -59
  209. package/dist/controllers/progress_controller.js.map +1 -1
  210. package/dist/controllers/radio_group_controller.d.ts +19 -8
  211. package/dist/controllers/radio_group_controller.js +138 -133
  212. package/dist/controllers/radio_group_controller.js.map +1 -1
  213. package/dist/controllers/range_slider_controller.d.ts +92 -4
  214. package/dist/controllers/range_slider_controller.js +245 -112
  215. package/dist/controllers/range_slider_controller.js.map +1 -1
  216. package/dist/controllers/rating_controller.d.ts +33 -6
  217. package/dist/controllers/rating_controller.js +429 -194
  218. package/dist/controllers/rating_controller.js.map +1 -1
  219. package/dist/controllers/read_more_controller.d.ts +26 -2
  220. package/dist/controllers/read_more_controller.js +185 -47
  221. package/dist/controllers/read_more_controller.js.map +1 -1
  222. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  223. package/dist/controllers/reading_progress_controller.js +147 -135
  224. package/dist/controllers/reading_progress_controller.js.map +1 -1
  225. package/dist/controllers/relative_time_controller.d.ts +18 -4
  226. package/dist/controllers/relative_time_controller.js +145 -110
  227. package/dist/controllers/relative_time_controller.js.map +1 -1
  228. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  229. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  230. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  231. package/dist/controllers/resizable_controller.d.ts +44 -4
  232. package/dist/controllers/resizable_controller.js +294 -118
  233. package/dist/controllers/resizable_controller.js.map +1 -1
  234. package/dist/controllers/roving_controller.js +39 -84
  235. package/dist/controllers/roving_controller.js.map +1 -1
  236. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  237. package/dist/controllers/scroll_area_controller.js +428 -221
  238. package/dist/controllers/scroll_area_controller.js.map +1 -1
  239. package/dist/controllers/scroll_restore_controller.js +0 -53
  240. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  241. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  242. package/dist/controllers/scroll_visibility_controller.js +395 -146
  243. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  244. package/dist/controllers/scrollspy_controller.d.ts +9 -3
  245. package/dist/controllers/scrollspy_controller.js +181 -236
  246. package/dist/controllers/scrollspy_controller.js.map +1 -1
  247. package/dist/controllers/separator_controller.d.ts +59 -17
  248. package/dist/controllers/separator_controller.js +288 -132
  249. package/dist/controllers/separator_controller.js.map +1 -1
  250. package/dist/controllers/sidebar_controller.d.ts +81 -7
  251. package/dist/controllers/sidebar_controller.js +1071 -375
  252. package/dist/controllers/sidebar_controller.js.map +1 -1
  253. package/dist/controllers/skeleton_controller.d.ts +23 -0
  254. package/dist/controllers/skeleton_controller.js +248 -92
  255. package/dist/controllers/skeleton_controller.js.map +1 -1
  256. package/dist/controllers/slider_controller.d.ts +72 -8
  257. package/dist/controllers/slider_controller.js +211 -96
  258. package/dist/controllers/slider_controller.js.map +1 -1
  259. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  260. package/dist/controllers/smart_sticky_header_controller.js +163 -54
  261. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  262. package/dist/controllers/sortable_controller.js +13 -110
  263. package/dist/controllers/sortable_controller.js.map +1 -1
  264. package/dist/controllers/spinner_controller.d.ts +39 -4
  265. package/dist/controllers/spinner_controller.js +277 -161
  266. package/dist/controllers/spinner_controller.js.map +1 -1
  267. package/dist/controllers/step_indicator_controller.d.ts +24 -17
  268. package/dist/controllers/step_indicator_controller.js +107 -53
  269. package/dist/controllers/step_indicator_controller.js.map +1 -1
  270. package/dist/controllers/stepper_controller.d.ts +43 -9
  271. package/dist/controllers/stepper_controller.js +171 -48
  272. package/dist/controllers/stepper_controller.js.map +1 -1
  273. package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
  274. package/dist/controllers/stick_to_bottom_controller.js +310 -116
  275. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  276. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  277. package/dist/controllers/sticky_observer_controller.js +214 -49
  278. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  279. package/dist/controllers/submit_once_controller.d.ts +22 -6
  280. package/dist/controllers/submit_once_controller.js +294 -192
  281. package/dist/controllers/submit_once_controller.js.map +1 -1
  282. package/dist/controllers/switch_controller.d.ts +40 -7
  283. package/dist/controllers/switch_controller.js +107 -43
  284. package/dist/controllers/switch_controller.js.map +1 -1
  285. package/dist/controllers/tabs_controller.d.ts +13 -1
  286. package/dist/controllers/tabs_controller.js +50 -25
  287. package/dist/controllers/tabs_controller.js.map +1 -1
  288. package/dist/controllers/tags_input_controller.d.ts +27 -3
  289. package/dist/controllers/tags_input_controller.js +290 -149
  290. package/dist/controllers/tags_input_controller.js.map +1 -1
  291. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  292. package/dist/controllers/textarea_autosize_controller.js +71 -58
  293. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  294. package/dist/controllers/theme_controller.d.ts +37 -8
  295. package/dist/controllers/theme_controller.js +148 -80
  296. package/dist/controllers/theme_controller.js.map +1 -1
  297. package/dist/controllers/time_picker_controller.d.ts +18 -1
  298. package/dist/controllers/time_picker_controller.js +162 -81
  299. package/dist/controllers/time_picker_controller.js.map +1 -1
  300. package/dist/controllers/toast_controller.d.ts +85 -23
  301. package/dist/controllers/toast_controller.js +365 -193
  302. package/dist/controllers/toast_controller.js.map +1 -1
  303. package/dist/controllers/toggle_group_controller.d.ts +60 -9
  304. package/dist/controllers/toggle_group_controller.js +194 -99
  305. package/dist/controllers/toggle_group_controller.js.map +1 -1
  306. package/dist/controllers/toolbar_controller.js +92 -90
  307. package/dist/controllers/toolbar_controller.js.map +1 -1
  308. package/dist/controllers/tooltip_controller.d.ts +52 -2
  309. package/dist/controllers/tooltip_controller.js +185 -125
  310. package/dist/controllers/tooltip_controller.js.map +1 -1
  311. package/dist/controllers/transition_controller.d.ts +22 -4
  312. package/dist/controllers/transition_controller.js +123 -115
  313. package/dist/controllers/transition_controller.js.map +1 -1
  314. package/dist/controllers/tree_view_controller.d.ts +59 -18
  315. package/dist/controllers/tree_view_controller.js +248 -290
  316. package/dist/controllers/tree_view_controller.js.map +1 -1
  317. package/dist/index.d.ts +33 -3
  318. package/dist/index.js +8649 -8572
  319. package/dist/index.js.map +1 -1
  320. package/dist/inspector/cli.d.ts +95 -9
  321. package/dist/inspector/cli.js +343 -77
  322. package/dist/inspector/cli.js.map +1 -1
  323. package/dist/inspector/cli_bin.js +390 -96
  324. package/dist/inspector/cli_bin.js.map +1 -1
  325. package/dist/inspector/examples.json +45 -45
  326. package/dist/inspector/manifest.json +1308 -155
  327. package/dist/positioning/index.d.ts +8 -0
  328. package/dist/positioning/index.js +148 -45
  329. package/dist/positioning/index.js.map +1 -1
  330. package/package.json +7 -7
  331. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  332. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,8 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/nested_form_controller.ts
4
-
5
- // src/utils/announce.ts
6
3
  function announce(message, options = {}) {
7
4
  const text = message.trim();
8
5
  if (text.length === 0) return;
@@ -19,77 +16,153 @@ function fillTemplate(template, values) {
19
16
  });
20
17
  }
21
18
 
22
- // src/utils/attribute_lease.ts
23
- var AttributeLease = class {
19
+ function sharedRegistry(key, create) {
20
+ const symbol = Symbol.for(key);
21
+ const scope = globalThis;
22
+ const existing = scope[symbol];
23
+ if (existing !== void 0) return existing;
24
+ const registry = create();
25
+ Object.defineProperty(globalThis, symbol, { value: registry });
26
+ return registry;
27
+ }
28
+
29
+ function isAttributeValue(value) {
30
+ return value === null || typeof value === "string";
31
+ }
32
+ function readLeaseRecord(element, record) {
33
+ try {
34
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
35
+ if (!Array.isArray(parsed)) return void 0;
36
+ const [original, written, ...rest] = parsed;
37
+ const linked = rest[0] !== false;
38
+ const beneath = linked ? rest : rest.slice(1);
39
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
40
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
41
+ } catch {
42
+ return void 0;
43
+ }
44
+ }
45
+ var AttributeLease = class _AttributeLease {
46
+ static #registry = sharedRegistry(
47
+ "stimeo-ui.attribute-lease.registry.v1",
48
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
49
+ );
24
50
  #attribute;
25
- #records = /* @__PURE__ */ new Map();
26
- /** @param attribute - The attribute whose temporary values this lease owns. */
27
- constructor(attribute) {
51
+ #record;
52
+ #base;
53
+ #held = /* @__PURE__ */ new Set();
54
+ constructor(attribute, owner, options = {}) {
28
55
  this.#attribute = attribute;
56
+ this.#record = `data-${owner}-${attribute}-lease`;
57
+ this.#base = options.base === true;
58
+ const names = _AttributeLease.#registry.names;
59
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
29
60
  }
30
- /** Writes or removes the leased attribute while preserving its authored value. */
31
61
  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);
62
+ const stack = this.#stack(element);
63
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
64
+ this.#held.add(element);
65
+ const shows = !this.#covered(element, stack, hold);
66
+ hold.written = value;
67
+ this.#mark(element, stack);
68
+ if (shows) this.#reflect(element, value);
42
69
  }
43
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
44
70
  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);
71
+ const held = this.#held.delete(element);
72
+ const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
73
+ const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
74
+ const hold = this.#take(stack);
75
+ if (!hold) return false;
76
+ const carries = element.getAttribute(this.#attribute) === hold.written;
77
+ const owns = carries && !this.#covered(element, stack, hold);
78
+ const index = stack.indexOf(hold);
79
+ stack.splice(index, 1);
80
+ const above = stack[index];
81
+ if (above) above.linked &&= hold.linked;
82
+ this.#mark(element, stack);
83
+ if (!owns) return true;
84
+ const below = hold.linked ? stack[index - 1] : void 0;
85
+ this.#reflect(element, below ? below.written : hold.original);
86
+ return true;
87
+ }
88
+ returnAll() {
89
+ for (const element of Array.from(this.#held)) this.return(element);
90
+ }
91
+ #stack(element) {
92
+ const stacks = _AttributeLease.#registry.stacks;
93
+ const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
94
+ stacks.set(element, byAttribute);
95
+ const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
96
+ byAttribute.set(this.#attribute, stack);
97
+ return stack;
98
+ }
99
+ #seed(element) {
100
+ const found = [];
101
+ for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
102
+ const read = readLeaseRecord(element, record);
103
+ if (read) found.push({ ...read, record });
104
+ }
105
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
106
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
107
+ lease: null,
108
+ record,
109
+ written,
110
+ original,
111
+ linked: linked && index > 0
112
+ }));
113
+ }
114
+ #take(stack) {
115
+ const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
116
+ if (hold) hold.lease = this;
117
+ return hold;
118
+ }
119
+ #acquire(element, stack) {
120
+ const current = element.getAttribute(this.#attribute);
121
+ const top = stack.at(-1);
122
+ const bottom = stack[0];
123
+ const over = top?.written === current ? top : void 0;
124
+ const from = this.#base ? bottom : over;
125
+ const hold = {
126
+ lease: this,
127
+ record: this.#record,
128
+ written: current,
129
+ original: from ? from.original : current,
130
+ linked: !this.#base && over !== void 0
131
+ };
132
+ if (this.#base && bottom) {
133
+ bottom.linked = true;
134
+ stack.unshift(hold);
135
+ } else stack.push(hold);
136
+ return hold;
137
+ }
138
+ #covered(element, stack, hold) {
139
+ const current = element.getAttribute(this.#attribute);
140
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
141
+ }
142
+ #mark(element, stack) {
143
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
144
+ stack.forEach((hold, index) => {
145
+ const names = stack.slice(0, index).map((under) => under.record);
146
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
147
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
148
+ records.set(
149
+ hold.record,
150
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
151
+ );
152
+ });
153
+ for (const [name, recorded] of records) {
154
+ if (element.getAttribute(name) === recorded) continue;
155
+ if (recorded === null) element.removeAttribute(name);
156
+ else element.setAttribute(name, recorded);
157
+ }
50
158
  }
51
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
52
159
  #reflect(element, value) {
53
160
  if (element.getAttribute(this.#attribute) === value) return;
54
161
  if (value === null) element.removeAttribute(this.#attribute);
55
162
  else element.setAttribute(this.#attribute, value);
56
163
  }
57
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
58
- returnAll() {
59
- for (const element of Array.from(this.#records.keys())) this.return(element);
60
- }
61
164
  };
62
165
 
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);
82
- }
83
- }
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);
89
- }
90
- };
91
-
92
- // src/utils/focus_candidate.ts
93
166
  function inheritsFieldsetDisabled(control) {
94
167
  let fieldset = control.closest("fieldset[disabled]");
95
168
  while (fieldset) {
@@ -99,8 +172,19 @@ function inheritsFieldsetDisabled(control) {
99
172
  }
100
173
  return false;
101
174
  }
175
+ function unslotted(element) {
176
+ return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
177
+ }
178
+ function flatTreeParent(element) {
179
+ if (unslotted(element)) return null;
180
+ const parent = element.assignedSlot ?? element.parentNode;
181
+ if (parent instanceof ShadowRoot) return parent.host;
182
+ return parent instanceof Element ? parent : null;
183
+ }
102
184
  function canTakeFocus(element) {
103
- if (element.closest("[hidden], [inert]")) return false;
185
+ for (let node = element; node; node = flatTreeParent(node)) {
186
+ if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
187
+ }
104
188
  if (element instanceof HTMLInputElement && element.type === "hidden") return false;
105
189
  if (!("disabled" in element)) return true;
106
190
  if (element.disabled) return false;
@@ -164,27 +248,33 @@ function firstTabStop(root) {
164
248
  return null;
165
249
  }
166
250
 
167
- // src/utils/microtask_coalescer.ts
251
+ var ListenerSet = class {
252
+ #abort = new AbortController();
253
+ add(target, type, handler, options) {
254
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
255
+ }
256
+ dispose() {
257
+ this.#abort.abort();
258
+ this.#abort = new AbortController();
259
+ }
260
+ };
261
+
168
262
  var MicrotaskCoalescer = class {
169
263
  #run;
170
264
  #queued = false;
171
265
  #active = false;
172
266
  #generation = 0;
173
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
174
267
  constructor(run) {
175
268
  this.#run = run;
176
269
  }
177
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
178
270
  activate() {
179
271
  this.#active = true;
180
272
  }
181
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
182
273
  cancel() {
183
274
  this.#active = false;
184
275
  this.#queued = false;
185
276
  this.#generation += 1;
186
277
  }
187
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
188
278
  schedule() {
189
279
  if (!this.#active || this.#queued) return;
190
280
  this.#queued = true;
@@ -197,43 +287,198 @@ var MicrotaskCoalescer = class {
197
287
  }
198
288
  };
199
289
 
200
- // src/utils/tabindex_loan.ts
201
- var TabindexLoan = class {
290
+ var MorphRenderWatcher = class {
291
+ #listeners = new ListenerSet();
292
+ #pass;
293
+ #element = null;
294
+ #onMorph = () => this.#pass.schedule();
295
+ constructor(run) {
296
+ this.#pass = new MicrotaskCoalescer(run);
297
+ }
298
+ observe(element) {
299
+ if (this.#element === element) return;
300
+ this.disconnect();
301
+ this.#element = element;
302
+ this.#pass.activate();
303
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
304
+ }
305
+ schedule() {
306
+ this.#pass.schedule();
307
+ }
308
+ disconnect() {
309
+ this.#listeners.dispose();
310
+ this.#pass.cancel();
311
+ this.#element = null;
312
+ }
313
+ };
314
+
315
+ var NUMBER_BOUNDS = {
316
+ nonNegative: { finite: true, min: 0 }};
317
+ function matchesNumberBounds(value, bounds) {
318
+ if (!Number.isFinite(value)) {
319
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
320
+ if (direction === null) return false;
321
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
322
+ }
323
+ if (bounds.min !== void 0 && value < bounds.min) return false;
324
+ if (bounds.max !== void 0 && value > bounds.max) return false;
325
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
326
+ if (bounds.integer && !Number.isInteger(value)) return false;
327
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
328
+ return true;
329
+ }
330
+
331
+ function readNumber(raw, fallback, bounds) {
332
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
333
+ }
334
+
335
+ var NumberValueReader = class {
336
+ #lastRejected = /* @__PURE__ */ new Map();
337
+ read(owner, name, raw, fallback, bounds) {
338
+ const resolved = readNumber(raw, fallback, bounds);
339
+ if (matchesNumberBounds(raw, bounds)) {
340
+ this.#lastRejected.delete(name);
341
+ return resolved;
342
+ }
343
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
344
+ const literal = owner.element.getAttribute(attribute);
345
+ if (literal === null) {
346
+ this.#lastRejected.delete(name);
347
+ return resolved;
348
+ }
349
+ if (this.#lastRejected.get(name) !== literal) {
350
+ this.#lastRejected.set(name, literal);
351
+ console.warn(
352
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
353
+ );
354
+ }
355
+ return resolved;
356
+ }
357
+ };
358
+
359
+ function queryOpenDescendants(root, selector) {
360
+ const matches = [];
361
+ const roots = [root];
362
+ for (const scope of roots) {
363
+ matches.push(...scope.querySelectorAll(selector));
364
+ if (scope.nodeType === Node.ELEMENT_NODE) {
365
+ const shadow = scope.shadowRoot;
366
+ if (shadow) roots.push(shadow);
367
+ }
368
+ for (const element of scope.querySelectorAll("*")) {
369
+ if (element.shadowRoot) roots.push(element.shadowRoot);
370
+ }
371
+ }
372
+ return matches;
373
+ }
374
+
375
+ var TabindexLoan = class _TabindexLoan {
376
+ static #registry = sharedRegistry(
377
+ "stimeo-ui.tabindex-loan.registry.v1",
378
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
379
+ );
202
380
  #value;
381
+ #record;
203
382
  #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") {
383
+ constructor(value, owner) {
212
384
  this.#value = value;
385
+ this.#record = `data-${owner}-tabindex-loan`;
386
+ _TabindexLoan.#registry.names.add(this.#record);
213
387
  }
214
- /** Lends `element` the value; no-ops when it already carries a `tabindex`. */
215
388
  lend(element) {
216
- if (element.hasAttribute("tabindex")) return;
217
- element.setAttribute("tabindex", this.#value);
389
+ const stack = this.#stack(element);
390
+ const current = element.getAttribute("tabindex");
391
+ const held = stack.find((hold) => hold.loan === this) ?? stack.find(
392
+ (hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
393
+ );
394
+ const linked = stack.at(-1)?.value === current;
395
+ if (!held && current !== null && !linked) return;
218
396
  this.#lent.add(element);
219
- this.#beforeCache.activate();
397
+ if (held) held.loan = this;
398
+ else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
399
+ this.#mark(element, stack);
400
+ if (current === null || !held && current !== this.#value) {
401
+ element.setAttribute("tabindex", this.#value);
402
+ }
220
403
  }
221
- /** Takes back every loan whose value is still the one that was lent. */
222
404
  returnAll() {
223
405
  for (const element of this.#lent) {
224
- if (element.getAttribute("tabindex") === this.#value) element.removeAttribute("tabindex");
406
+ const stack = this.#stack(element);
407
+ const hold = stack.find((held) => held.loan === this);
408
+ if (hold) this.#release(element, stack, hold);
225
409
  }
226
410
  this.#lent.clear();
227
- this.#beforeCache.deactivate();
411
+ }
412
+ reclaim(element) {
413
+ if (element.getAttribute(this.#record) !== this.#value) return;
414
+ const stack = this.#stack(element);
415
+ const hold = stack.find((held) => held.loan === null && held.record === this.#record);
416
+ if (hold && !element.matches(":focus")) this.#release(element, stack, hold);
417
+ }
418
+ reclaimWithin(root) {
419
+ for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
420
+ this.reclaim(element);
421
+ }
422
+ }
423
+ #stack(element) {
424
+ const stacks = _TabindexLoan.#registry.stacks;
425
+ const stack = stacks.get(element) ?? this.#seed(element);
426
+ stacks.set(element, stack);
427
+ return stack;
428
+ }
429
+ #seed(element) {
430
+ const current = element.getAttribute("tabindex");
431
+ const holds = [];
432
+ for (const record of _TabindexLoan.#registry.names) {
433
+ const value = element.getAttribute(record);
434
+ if (value !== null) holds.push({ loan: null, record, value, linked: true });
435
+ }
436
+ holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
437
+ for (const [index, hold] of holds.entries()) hold.linked = index > 0;
438
+ return holds;
439
+ }
440
+ #release(element, stack, hold) {
441
+ const current = element.getAttribute("tabindex");
442
+ const index = stack.indexOf(hold);
443
+ const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
444
+ stack.splice(index, 1);
445
+ const above = stack[index];
446
+ if (above) above.linked &&= hold.linked;
447
+ this.#mark(element, stack);
448
+ if (!owns) return;
449
+ const below = hold.linked ? stack[index - 1] : void 0;
450
+ if (!below) element.removeAttribute("tabindex");
451
+ else if (below.value !== current) element.setAttribute("tabindex", below.value);
452
+ }
453
+ #mark(element, stack) {
454
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
455
+ for (const hold of stack) records.set(hold.record, hold.value);
456
+ for (const [name, recorded] of records) {
457
+ if (element.getAttribute(name) === recorded) continue;
458
+ if (recorded === null) element.removeAttribute(name);
459
+ else element.setAttribute(name, recorded);
460
+ }
228
461
  }
229
462
  };
230
463
 
231
- // src/controllers/nested_form_controller.ts
232
- var ROOT_SELECTOR = '[data-controller~="stimeo--nested-form"]';
233
- var REMOVE_SELECTOR = '[data-stimeo--nested-form-target="remove"]';
234
- var DESTROY_FLAG_SELECTOR = '[data-stimeo--nested-form-target="destroyFlag"]';
464
+ function targetSelector(identifier, name) {
465
+ return `[data-${identifier}-target~="${name}"]`;
466
+ }
467
+
468
+ var REMOVE_PART = "remove";
469
+ var DESTROY_FLAG_PART = "destroyFlag";
235
470
  var DESTROYED_VALUES = /* @__PURE__ */ new Set(["1", "true"]);
236
- var NestedFormController = class extends Controller {
471
+ var NestedFormController = class _NestedFormController extends Controller {
472
+ #numbers = new NumberValueReader();
473
+ get #rootSelector() {
474
+ return `[data-controller~="${this.identifier}"]`;
475
+ }
476
+ get #removeSelector() {
477
+ return targetSelector(this.identifier, REMOVE_PART);
478
+ }
479
+ get #destroyFlagSelector() {
480
+ return targetSelector(this.identifier, DESTROY_FLAG_PART);
481
+ }
237
482
  static targets = ["list", "template", "add", "remove", "destroyFlag"];
238
483
  static values = {
239
484
  min: { type: Number, default: 0 },
@@ -242,40 +487,33 @@ var NestedFormController = class extends Controller {
242
487
  announce: { type: Boolean, default: true },
243
488
  countMessage: { type: String, default: "" }
244
489
  };
490
+ static valueConstraints = {
491
+ min: NUMBER_BOUNDS.nonNegative,
492
+ max: NUMBER_BOUNDS.nonNegative
493
+ };
245
494
  static actions = ["add"];
246
495
  static events = ["add", "remove", "reconcile"];
247
- /** Monotonic source for unique row indices; never a row-state counter. */
248
496
  #lastIndex = 0;
249
497
  #warnedMissing = false;
250
498
  #warnedTemplate = false;
251
- /** The state last written to the hooks; reconciliation reports only real moves. */
252
499
  #published = null;
253
- /** Watches the list for row changes the controller did not perform itself. */
254
500
  #observer = null;
255
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileNow());
256
- /** Restores the authored add-button `disabled` when a lease ends. */
257
- #addDisabled = new AttributeLease("disabled");
258
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
259
- /** Makes the root a programmatic focus destination when no other candidate survives. */
260
- #tabindex = new TabindexLoan();
261
- /**
262
- * Delegated click handler for the per-row remove buttons (dynamic-safe). Only
263
- * buttons whose nearest nested-form root is this instance are acted on, so a
264
- * nested inner form's buttons never remove an outer row.
265
- */
501
+ #reconcile = new MorphRenderWatcher(() => this.#reconcileNow());
502
+ #addDisabled = new AttributeLease("disabled", this.identifier);
503
+ #tabindex = new TabindexLoan("-1", this.identifier);
266
504
  #onClick = (event) => {
267
505
  const target = event.target;
268
- const button = target?.closest(REMOVE_SELECTOR);
506
+ const button = target?.closest(this.#removeSelector);
269
507
  if (!button || this.#ownerOf(button) !== this.element) return;
270
508
  const row = this.#rowContaining(button);
271
509
  if (row) this.#removeRow(row);
272
510
  };
273
511
  connect() {
512
+ this.#tabindex.reclaim(this.element);
274
513
  this.#warnedMissing = false;
275
514
  this.#warnedTemplate = false;
276
515
  this.element.addEventListener("click", this.#onClick);
277
- this.#reconcile.activate();
278
- this.#beforeCache.activate();
516
+ this.#reconcile.observe(this.element);
279
517
  if (!this.hasListTarget || !this.hasTemplateTarget) this.#warnMissing();
280
518
  this.#refresh();
281
519
  }
@@ -283,28 +521,19 @@ var NestedFormController = class extends Controller {
283
521
  this.element.removeEventListener("click", this.#onClick);
284
522
  this.#observer?.disconnect();
285
523
  this.#observer = null;
286
- this.#reconcile.cancel();
287
- this.#beforeCache.deactivate();
288
- this.#rewindForCache();
524
+ this.#reconcile.disconnect();
525
+ this.#addDisabled.returnAll();
289
526
  this.#tabindex.returnAll();
290
527
  this.#published = null;
291
528
  }
292
- /** Follows an arriving or swapped-in list: rebind to the primary, then reconcile. */
293
529
  listTargetConnected() {
294
530
  this.#rebindObserver();
295
531
  this.#reconcile.schedule();
296
532
  }
297
- /** Follows a departing list the same way — the primary may have changed. */
298
533
  listTargetDisconnected() {
299
534
  this.#rebindObserver();
300
535
  this.#reconcile.schedule();
301
536
  }
302
- /**
303
- * Points the observer at the current primary list. Re-deriving on every list
304
- * arrival and departure makes the binding independent of the order Stimulus
305
- * reports an overlapping swap in — a staggered swap (successor appended before
306
- * the old list leaves) ends observed and reconciled either way.
307
- */
308
537
  #rebindObserver() {
309
538
  this.#observer?.disconnect();
310
539
  this.#observer = null;
@@ -317,7 +546,6 @@ var NestedFormController = class extends Controller {
317
546
  attributeFilter: ["value"]
318
547
  });
319
548
  }
320
- /** Returns the lease with a departing add button; a new one re-arms on refresh. */
321
549
  addTargetDisconnected(target) {
322
550
  this.#addDisabled.return(target);
323
551
  this.#reconcile.schedule();
@@ -325,21 +553,12 @@ var NestedFormController = class extends Controller {
325
553
  addTargetConnected() {
326
554
  this.#reconcile.schedule();
327
555
  }
328
- /** Re-clamps when application code or a Turbo morph changes `min`. */
329
556
  minValueChanged() {
330
557
  this.#reconcile.schedule();
331
558
  }
332
- /** Re-clamps when application code or a Turbo morph changes `max`. */
333
559
  maxValueChanged() {
334
560
  this.#reconcile.schedule();
335
561
  }
336
- /**
337
- * Clones the template row, replaces the index placeholder with a unique value,
338
- * appends it, focuses its first tab stop, and announces the new count. No-ops at
339
- * `max`, when the required targets are missing (named on the console once per
340
- * connection), or when the template does not produce exactly one root element
341
- * (also named once; the insertion is rolled back so nothing accumulates).
342
- */
343
562
  add() {
344
563
  if (!this.hasListTarget || !this.hasTemplateTarget) {
345
564
  this.#warnMissing();
@@ -362,16 +581,14 @@ var NestedFormController = class extends Controller {
362
581
  return;
363
582
  }
364
583
  const row = added[0];
584
+ for (const node of Array.from(list.childNodes).slice(beforeNodes)) {
585
+ if (node !== row) node.remove();
586
+ }
365
587
  this.#refresh();
366
588
  firstTabStop(row)?.focus();
367
589
  this.dispatch("add", { detail: { index, element: row } });
368
590
  this.#announce();
369
591
  }
370
- /**
371
- * Removes a row: a persisted row (one carrying its own `destroyFlag`) has the
372
- * flag set to `1` and is hidden so Rails destroys it on submit; an unsaved row
373
- * is dropped from the DOM. Returns focus to a surviving row. No-ops at `min`.
374
- */
375
592
  #removeRow(row) {
376
593
  const rows = this.#effectiveRows;
377
594
  if (this.#destroyed(row)) {
@@ -379,7 +596,7 @@ var NestedFormController = class extends Controller {
379
596
  this.#focusAfterRemove(this.#positionAmong(rows, row));
380
597
  return;
381
598
  }
382
- if (rows.length <= this.minValue) return;
599
+ if (rows.length <= this.#safeMin) return;
383
600
  const position = rows.indexOf(row);
384
601
  const flag = this.#destroyFlagOf(row);
385
602
  const persisted = flag !== null;
@@ -394,21 +611,11 @@ var NestedFormController = class extends Controller {
394
611
  this.dispatch("remove", { detail: { element: row, persisted } });
395
612
  this.#announce();
396
613
  }
397
- /**
398
- * The index of the first effective row following `row` in document order.
399
- * -1 (no following row) feeds the focus slices as a negative index, which
400
- * yields the same fully-reversed nearest-first order as `rows.length` would.
401
- */
402
614
  #positionAmong(rows, row) {
403
615
  return rows.findIndex(
404
616
  (candidate) => (row.compareDocumentPosition(candidate) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0
405
617
  );
406
618
  }
407
- /**
408
- * Moves focus to the first tab stop of the nearest surviving row — following
409
- * rows first, then preceding ones — falling back to the add button and finally
410
- * to the root via a temporary `tabindex`.
411
- */
412
619
  #focusAfterRemove(position) {
413
620
  const rows = this.#effectiveRows;
414
621
  const ordered = [...rows.slice(position), ...rows.slice(0, position).reverse()];
@@ -426,27 +633,20 @@ var NestedFormController = class extends Controller {
426
633
  this.#tabindex.lend(this.element);
427
634
  this.element.focus();
428
635
  }
429
- /** Recomputes the count and min/max hooks from the DOM and records them as published. */
430
636
  #refresh() {
431
637
  if (!this.hasListTarget) return;
432
638
  const count = this.#effectiveRows.length;
433
- const atMin = count <= this.minValue;
434
- const atMax = this.maxValue > 0 && count >= this.maxValue;
639
+ const atMin = count <= this.#safeMin;
640
+ const atMax = this.#safeMax > 0 && count >= this.#safeMax;
435
641
  this.element.setAttribute("data-nested-count", String(count));
436
642
  this.#reflect("data-nested-at-max", atMax);
437
643
  this.#reflect("data-nested-at-min", atMin);
438
644
  if (this.hasAddTarget) {
439
- if (this.maxValue > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
645
+ if (this.#safeMax > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
440
646
  else this.#addDisabled.return(this.addTarget);
441
647
  }
442
648
  this.#published = { count, atMin, atMax };
443
649
  }
444
- /**
445
- * Applies row changes the controller did not perform itself (Turbo Streams,
446
- * morphs, runtime Value changes): refreshes the hooks and reports a moved
447
- * public state as `reconcile`. The controller's own operations refresh
448
- * synchronously first, so their observer echo arrives here as a no-move.
449
- */
450
650
  #reconcileNow() {
451
651
  const previous = this.#published;
452
652
  this.#refresh();
@@ -455,21 +655,14 @@ var NestedFormController = class extends Controller {
455
655
  const moved = previous.count !== current.count || previous.atMin !== current.atMin || previous.atMax !== current.atMax;
456
656
  if (moved) this.dispatch("reconcile", { detail: { ...current } });
457
657
  }
458
- /** Bridges the count change to the shared announcer when configured. */
459
658
  #announce() {
460
659
  if (!this.announceValue || this.countMessageValue === "") return;
461
660
  announce(fillTemplate(this.countMessageValue, { count: this.#effectiveRows.length }));
462
661
  }
463
- /** Sets `attribute` to `"true"` when `on`, else removes it. */
464
662
  #reflect(attribute, on) {
465
663
  if (on) this.element.setAttribute(attribute, "true");
466
664
  else this.element.removeAttribute(attribute);
467
665
  }
468
- /** Returns the disabled lease so an authored value never leaks into a snapshot. */
469
- #rewindForCache() {
470
- if (this.hasAddTarget) this.#addDisabled.return(this.addTarget);
471
- }
472
- /** Names the missing required target(s) once per connection. */
473
666
  #warnMissing() {
474
667
  if (this.#warnedMissing) return;
475
668
  this.#warnedMissing = true;
@@ -481,49 +674,38 @@ var NestedFormController = class extends Controller {
481
674
  `Stimeo UI: "${this.identifier}" cannot manage rows because its markup lacks ${missing}.`
482
675
  );
483
676
  }
484
- /** Names a template that does not produce exactly one element, once per connection. */
485
677
  #warnBadTemplate(produced) {
486
678
  if (this.#warnedTemplate) return;
487
679
  this.#warnedTemplate = true;
488
680
  const reason = produced === 0 ? "produces no element" : "must produce exactly one root element";
489
681
  console.warn(`Stimeo UI: "${this.identifier}" added no row because its template ${reason}.`);
490
682
  }
491
- /** A strictly-increasing unique index (collision-free even on rapid adds). */
492
683
  #nextIndex() {
493
684
  const index = Math.max(Date.now(), this.#lastIndex + 1);
494
685
  this.#lastIndex = index;
495
686
  return index;
496
687
  }
497
688
  get #atMax() {
498
- return this.maxValue > 0 && this.#effectiveRows.length >= this.maxValue;
689
+ return this.#safeMax > 0 && this.#effectiveRows.length >= this.#safeMax;
499
690
  }
500
- /**
501
- * Direct child rows of the list whose own destroy flag is not set. Callers
502
- * reach this only behind a list-presence gate (`add`, `#refresh`, and the
503
- * click path through `#rowContaining`).
504
- */
505
691
  get #effectiveRows() {
506
692
  return Array.from(this.listTarget.children).filter(
507
693
  (row) => !this.#destroyed(row)
508
694
  );
509
695
  }
510
- /** Whether `row` is flagged for destruction; the flag value is the truth source. */
511
696
  #destroyed(row) {
512
697
  const flag = this.#destroyFlagOf(row);
513
698
  return flag !== null && DESTROYED_VALUES.has(flag.value);
514
699
  }
515
- /** The row's own destroy flag, skipping flags owned by a nested inner form. */
516
700
  #destroyFlagOf(row) {
517
- for (const flag of row.querySelectorAll(DESTROY_FLAG_SELECTOR)) {
701
+ for (const flag of row.querySelectorAll(this.#destroyFlagSelector)) {
518
702
  if (this.#ownerOf(flag) === this.element) return flag;
519
703
  }
520
704
  return null;
521
705
  }
522
- /** The nearest nested-form root that owns `el`. */
523
706
  #ownerOf(el) {
524
- return el.closest(ROOT_SELECTOR);
707
+ return el.closest(this.#rootSelector);
525
708
  }
526
- /** The nearest ancestor of `el` that is a direct child of the list, else null. */
527
709
  #rowContaining(el) {
528
710
  if (!this.hasListTarget) return null;
529
711
  let node = el;
@@ -532,8 +714,25 @@ var NestedFormController = class extends Controller {
532
714
  }
533
715
  return node;
534
716
  }
717
+ get #safeMin() {
718
+ return this.#numbers.read(
719
+ this,
720
+ "min",
721
+ this.minValue,
722
+ _NestedFormController.values.min.default,
723
+ _NestedFormController.valueConstraints.min
724
+ );
725
+ }
726
+ get #safeMax() {
727
+ return this.#numbers.read(
728
+ this,
729
+ "max",
730
+ this.maxValue,
731
+ _NestedFormController.values.max.default,
732
+ _NestedFormController.valueConstraints.max
733
+ );
734
+ }
535
735
  };
536
736
 
537
737
  export { NestedFormController };
538
- //# sourceMappingURL=nested_form_controller.js.map
539
738
  //# sourceMappingURL=nested_form_controller.js.map