stimeo-ui 0.16.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 (328) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +8 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +363 -436
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +4 -2
  7. package/dist/controllers/accordion_controller.js +166 -55
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  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 +35 -5
  13. package/dist/controllers/announcer_controller.js +135 -198
  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 +271 -127
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +171 -105
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.js +0 -130
  23. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  24. package/dist/controllers/bulk_select_controller.d.ts +18 -0
  25. package/dist/controllers/bulk_select_controller.js +262 -52
  26. package/dist/controllers/bulk_select_controller.js.map +1 -1
  27. package/dist/controllers/calendar_controller.d.ts +24 -5
  28. package/dist/controllers/calendar_controller.js +175 -209
  29. package/dist/controllers/calendar_controller.js.map +1 -1
  30. package/dist/controllers/carousel_controller.d.ts +34 -7
  31. package/dist/controllers/carousel_controller.js +461 -399
  32. package/dist/controllers/carousel_controller.js.map +1 -1
  33. package/dist/controllers/character_counter_controller.d.ts +17 -1
  34. package/dist/controllers/character_counter_controller.js +246 -156
  35. package/dist/controllers/character_counter_controller.js.map +1 -1
  36. package/dist/controllers/checkbox_controller.d.ts +7 -2
  37. package/dist/controllers/checkbox_controller.js +32 -74
  38. package/dist/controllers/checkbox_controller.js.map +1 -1
  39. package/dist/controllers/clipboard_controller.d.ts +16 -5
  40. package/dist/controllers/clipboard_controller.js +168 -147
  41. package/dist/controllers/clipboard_controller.js.map +1 -1
  42. package/dist/controllers/collapsible_controller.d.ts +16 -4
  43. package/dist/controllers/collapsible_controller.js +301 -120
  44. package/dist/controllers/collapsible_controller.js.map +1 -1
  45. package/dist/controllers/color_picker_controller.d.ts +18 -6
  46. package/dist/controllers/color_picker_controller.js +149 -139
  47. package/dist/controllers/color_picker_controller.js.map +1 -1
  48. package/dist/controllers/combobox_controller.d.ts +23 -5
  49. package/dist/controllers/combobox_controller.js +360 -133
  50. package/dist/controllers/combobox_controller.js.map +1 -1
  51. package/dist/controllers/command_palette_controller.d.ts +45 -14
  52. package/dist/controllers/command_palette_controller.js +970 -358
  53. package/dist/controllers/command_palette_controller.js.map +1 -1
  54. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  55. package/dist/controllers/conditional_fields_controller.js +166 -124
  56. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  57. package/dist/controllers/confirm_controller.d.ts +49 -12
  58. package/dist/controllers/confirm_controller.js +939 -248
  59. package/dist/controllers/confirm_controller.js.map +1 -1
  60. package/dist/controllers/context_menu_controller.d.ts +31 -4
  61. package/dist/controllers/context_menu_controller.js +256 -128
  62. package/dist/controllers/context_menu_controller.js.map +1 -1
  63. package/dist/controllers/count_up_controller.d.ts +13 -1
  64. package/dist/controllers/count_up_controller.js +73 -21
  65. package/dist/controllers/count_up_controller.js.map +1 -1
  66. package/dist/controllers/countdown_controller.d.ts +45 -1
  67. package/dist/controllers/countdown_controller.js +183 -148
  68. package/dist/controllers/countdown_controller.js.map +1 -1
  69. package/dist/controllers/currency_input_controller.d.ts +21 -2
  70. package/dist/controllers/currency_input_controller.js +134 -158
  71. package/dist/controllers/currency_input_controller.js.map +1 -1
  72. package/dist/controllers/data_grid_controller.d.ts +15 -4
  73. package/dist/controllers/data_grid_controller.js +114 -186
  74. package/dist/controllers/data_grid_controller.js.map +1 -1
  75. package/dist/controllers/date_range_picker_controller.d.ts +30 -5
  76. package/dist/controllers/date_range_picker_controller.js +288 -206
  77. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  78. package/dist/controllers/dialog_controller.d.ts +13 -1
  79. package/dist/controllers/dialog_controller.js +858 -223
  80. package/dist/controllers/dialog_controller.js.map +1 -1
  81. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  82. package/dist/controllers/direct_upload_controller.js +73 -153
  83. package/dist/controllers/direct_upload_controller.js.map +1 -1
  84. package/dist/controllers/dirty_form_controller.js +14 -48
  85. package/dist/controllers/dirty_form_controller.js.map +1 -1
  86. package/dist/controllers/dismissible_controller.d.ts +19 -0
  87. package/dist/controllers/dismissible_controller.js +175 -21
  88. package/dist/controllers/dismissible_controller.js.map +1 -1
  89. package/dist/controllers/drawer_controller.d.ts +39 -11
  90. package/dist/controllers/drawer_controller.js +933 -341
  91. package/dist/controllers/drawer_controller.js.map +1 -1
  92. package/dist/controllers/dropdown_controller.d.ts +31 -1
  93. package/dist/controllers/dropdown_controller.js +312 -82
  94. package/dist/controllers/dropdown_controller.js.map +1 -1
  95. package/dist/controllers/editable_controller.d.ts +12 -1
  96. package/dist/controllers/editable_controller.js +158 -68
  97. package/dist/controllers/editable_controller.js.map +1 -1
  98. package/dist/controllers/empty_state_controller.js +67 -53
  99. package/dist/controllers/empty_state_controller.js.map +1 -1
  100. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  101. package/dist/controllers/file_dropzone_controller.js +311 -207
  102. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  103. package/dist/controllers/filter_controller.js +67 -39
  104. package/dist/controllers/filter_controller.js.map +1 -1
  105. package/dist/controllers/flash_controller.d.ts +31 -8
  106. package/dist/controllers/flash_controller.js +172 -314
  107. package/dist/controllers/flash_controller.js.map +1 -1
  108. package/dist/controllers/focus_controller.d.ts +12 -3
  109. package/dist/controllers/focus_controller.js +654 -240
  110. package/dist/controllers/focus_controller.js.map +1 -1
  111. package/dist/controllers/form_field_controller.js +147 -132
  112. package/dist/controllers/form_field_controller.js.map +1 -1
  113. package/dist/controllers/form_validation_controller.js +13 -86
  114. package/dist/controllers/form_validation_controller.js.map +1 -1
  115. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  116. package/dist/controllers/frame_loading_controller.js +264 -213
  117. package/dist/controllers/frame_loading_controller.js.map +1 -1
  118. package/dist/controllers/highlight_controller.d.ts +22 -2
  119. package/dist/controllers/highlight_controller.js +107 -75
  120. package/dist/controllers/highlight_controller.js.map +1 -1
  121. package/dist/controllers/hover_card_controller.d.ts +56 -3
  122. package/dist/controllers/hover_card_controller.js +309 -118
  123. package/dist/controllers/hover_card_controller.js.map +1 -1
  124. package/dist/controllers/idle_controller.d.ts +42 -1
  125. package/dist/controllers/idle_controller.js +267 -101
  126. package/dist/controllers/idle_controller.js.map +1 -1
  127. package/dist/controllers/input_mask_controller.js +71 -72
  128. package/dist/controllers/input_mask_controller.js.map +1 -1
  129. package/dist/controllers/intersection_controller.d.ts +32 -13
  130. package/dist/controllers/intersection_controller.js +147 -106
  131. package/dist/controllers/intersection_controller.js.map +1 -1
  132. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  133. package/dist/controllers/lazy_frame_controller.js +9 -65
  134. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  135. package/dist/controllers/listbox_controller.d.ts +34 -2
  136. package/dist/controllers/listbox_controller.js +306 -204
  137. package/dist/controllers/listbox_controller.js.map +1 -1
  138. package/dist/controllers/local_time_controller.d.ts +1 -1
  139. package/dist/controllers/local_time_controller.js +54 -64
  140. package/dist/controllers/local_time_controller.js.map +1 -1
  141. package/dist/controllers/masonry_controller.d.ts +11 -3
  142. package/dist/controllers/masonry_controller.js +70 -93
  143. package/dist/controllers/masonry_controller.js.map +1 -1
  144. package/dist/controllers/menu_controller.d.ts +32 -7
  145. package/dist/controllers/menu_controller.js +214 -156
  146. package/dist/controllers/menu_controller.js.map +1 -1
  147. package/dist/controllers/menubar_controller.d.ts +4 -4
  148. package/dist/controllers/menubar_controller.js +170 -329
  149. package/dist/controllers/menubar_controller.js.map +1 -1
  150. package/dist/controllers/meter_controller.d.ts +20 -0
  151. package/dist/controllers/meter_controller.js +151 -67
  152. package/dist/controllers/meter_controller.js.map +1 -1
  153. package/dist/controllers/multi_select_controller.d.ts +32 -4
  154. package/dist/controllers/multi_select_controller.js +593 -358
  155. package/dist/controllers/multi_select_controller.js.map +1 -1
  156. package/dist/controllers/navigation_menu_controller.d.ts +8 -1
  157. package/dist/controllers/navigation_menu_controller.js +124 -232
  158. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  159. package/dist/controllers/nested_form_controller.d.ts +10 -0
  160. package/dist/controllers/nested_form_controller.js +352 -182
  161. package/dist/controllers/nested_form_controller.js.map +1 -1
  162. package/dist/controllers/network_status_controller.d.ts +23 -0
  163. package/dist/controllers/network_status_controller.js +242 -54
  164. package/dist/controllers/network_status_controller.js.map +1 -1
  165. package/dist/controllers/number_input_controller.d.ts +26 -2
  166. package/dist/controllers/number_input_controller.js +320 -263
  167. package/dist/controllers/number_input_controller.js.map +1 -1
  168. package/dist/controllers/optimistic_controller.js +16 -76
  169. package/dist/controllers/optimistic_controller.js.map +1 -1
  170. package/dist/controllers/otp_controller.d.ts +20 -4
  171. package/dist/controllers/otp_controller.js +198 -193
  172. package/dist/controllers/otp_controller.js.map +1 -1
  173. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  174. package/dist/controllers/overflow_indicator_controller.js +116 -113
  175. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  176. package/dist/controllers/overflow_menu_controller.d.ts +48 -11
  177. package/dist/controllers/overflow_menu_controller.js +343 -283
  178. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  179. package/dist/controllers/pagination_controller.d.ts +35 -6
  180. package/dist/controllers/pagination_controller.js +295 -119
  181. package/dist/controllers/pagination_controller.js.map +1 -1
  182. package/dist/controllers/password_reveal_controller.d.ts +45 -8
  183. package/dist/controllers/password_reveal_controller.js +389 -121
  184. package/dist/controllers/password_reveal_controller.js.map +1 -1
  185. package/dist/controllers/password_strength_controller.d.ts +14 -5
  186. package/dist/controllers/password_strength_controller.js +99 -153
  187. package/dist/controllers/password_strength_controller.js.map +1 -1
  188. package/dist/controllers/persist_controller.d.ts +11 -4
  189. package/dist/controllers/persist_controller.js +237 -103
  190. package/dist/controllers/persist_controller.js.map +1 -1
  191. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  192. package/dist/controllers/pointer_drag_controller.js +185 -165
  193. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  194. package/dist/controllers/popover_controller.d.ts +38 -2
  195. package/dist/controllers/popover_controller.js +236 -97
  196. package/dist/controllers/popover_controller.js.map +1 -1
  197. package/dist/controllers/portal_controller.d.ts +44 -4
  198. package/dist/controllers/portal_controller.js +209 -76
  199. package/dist/controllers/portal_controller.js.map +1 -1
  200. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  201. package/dist/controllers/preview_guard_controller.js +232 -116
  202. package/dist/controllers/preview_guard_controller.js.map +1 -1
  203. package/dist/controllers/progress_controller.d.ts +11 -0
  204. package/dist/controllers/progress_controller.js +125 -56
  205. package/dist/controllers/progress_controller.js.map +1 -1
  206. package/dist/controllers/radio_group_controller.d.ts +14 -5
  207. package/dist/controllers/radio_group_controller.js +117 -137
  208. package/dist/controllers/radio_group_controller.js.map +1 -1
  209. package/dist/controllers/range_slider_controller.d.ts +43 -3
  210. package/dist/controllers/range_slider_controller.js +192 -105
  211. package/dist/controllers/range_slider_controller.js.map +1 -1
  212. package/dist/controllers/rating_controller.d.ts +20 -4
  213. package/dist/controllers/rating_controller.js +418 -207
  214. package/dist/controllers/rating_controller.js.map +1 -1
  215. package/dist/controllers/read_more_controller.d.ts +2 -0
  216. package/dist/controllers/read_more_controller.js +134 -63
  217. package/dist/controllers/read_more_controller.js.map +1 -1
  218. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  219. package/dist/controllers/reading_progress_controller.js +147 -135
  220. package/dist/controllers/reading_progress_controller.js.map +1 -1
  221. package/dist/controllers/relative_time_controller.d.ts +18 -4
  222. package/dist/controllers/relative_time_controller.js +145 -110
  223. package/dist/controllers/relative_time_controller.js.map +1 -1
  224. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  225. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  226. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  227. package/dist/controllers/resizable_controller.d.ts +28 -2
  228. package/dist/controllers/resizable_controller.js +255 -139
  229. package/dist/controllers/resizable_controller.js.map +1 -1
  230. package/dist/controllers/roving_controller.js +39 -84
  231. package/dist/controllers/roving_controller.js.map +1 -1
  232. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  233. package/dist/controllers/scroll_area_controller.js +428 -225
  234. package/dist/controllers/scroll_area_controller.js.map +1 -1
  235. package/dist/controllers/scroll_restore_controller.js +0 -53
  236. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  237. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  238. package/dist/controllers/scroll_visibility_controller.js +395 -145
  239. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  240. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  241. package/dist/controllers/scrollspy_controller.js +181 -242
  242. package/dist/controllers/scrollspy_controller.js.map +1 -1
  243. package/dist/controllers/separator_controller.d.ts +19 -4
  244. package/dist/controllers/separator_controller.js +267 -140
  245. package/dist/controllers/separator_controller.js.map +1 -1
  246. package/dist/controllers/sidebar_controller.d.ts +64 -7
  247. package/dist/controllers/sidebar_controller.js +1023 -386
  248. package/dist/controllers/sidebar_controller.js.map +1 -1
  249. package/dist/controllers/skeleton_controller.d.ts +23 -0
  250. package/dist/controllers/skeleton_controller.js +248 -97
  251. package/dist/controllers/skeleton_controller.js.map +1 -1
  252. package/dist/controllers/slider_controller.d.ts +27 -1
  253. package/dist/controllers/slider_controller.js +159 -79
  254. package/dist/controllers/slider_controller.js.map +1 -1
  255. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  256. package/dist/controllers/smart_sticky_header_controller.js +162 -63
  257. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  258. package/dist/controllers/sortable_controller.js +0 -112
  259. package/dist/controllers/sortable_controller.js.map +1 -1
  260. package/dist/controllers/spinner_controller.d.ts +39 -4
  261. package/dist/controllers/spinner_controller.js +277 -169
  262. package/dist/controllers/spinner_controller.js.map +1 -1
  263. package/dist/controllers/step_indicator_controller.d.ts +5 -0
  264. package/dist/controllers/step_indicator_controller.js +99 -46
  265. package/dist/controllers/step_indicator_controller.js.map +1 -1
  266. package/dist/controllers/stepper_controller.d.ts +12 -3
  267. package/dist/controllers/stepper_controller.js +114 -73
  268. package/dist/controllers/stepper_controller.js.map +1 -1
  269. package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
  270. package/dist/controllers/stick_to_bottom_controller.js +262 -138
  271. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  272. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  273. package/dist/controllers/sticky_observer_controller.js +214 -49
  274. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  275. package/dist/controllers/submit_once_controller.d.ts +23 -8
  276. package/dist/controllers/submit_once_controller.js +294 -205
  277. package/dist/controllers/submit_once_controller.js.map +1 -1
  278. package/dist/controllers/switch_controller.d.ts +12 -1
  279. package/dist/controllers/switch_controller.js +33 -60
  280. package/dist/controllers/switch_controller.js.map +1 -1
  281. package/dist/controllers/tabs_controller.d.ts +2 -2
  282. package/dist/controllers/tabs_controller.js +35 -29
  283. package/dist/controllers/tabs_controller.js.map +1 -1
  284. package/dist/controllers/tags_input_controller.d.ts +12 -0
  285. package/dist/controllers/tags_input_controller.js +136 -145
  286. package/dist/controllers/tags_input_controller.js.map +1 -1
  287. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  288. package/dist/controllers/textarea_autosize_controller.js +71 -58
  289. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  290. package/dist/controllers/theme_controller.d.ts +21 -9
  291. package/dist/controllers/theme_controller.js +130 -112
  292. package/dist/controllers/theme_controller.js.map +1 -1
  293. package/dist/controllers/time_picker_controller.d.ts +18 -1
  294. package/dist/controllers/time_picker_controller.js +146 -80
  295. package/dist/controllers/time_picker_controller.js.map +1 -1
  296. package/dist/controllers/toast_controller.d.ts +36 -11
  297. package/dist/controllers/toast_controller.js +186 -297
  298. package/dist/controllers/toast_controller.js.map +1 -1
  299. package/dist/controllers/toggle_group_controller.d.ts +12 -3
  300. package/dist/controllers/toggle_group_controller.js +95 -136
  301. package/dist/controllers/toggle_group_controller.js.map +1 -1
  302. package/dist/controllers/toolbar_controller.js +68 -98
  303. package/dist/controllers/toolbar_controller.js.map +1 -1
  304. package/dist/controllers/tooltip_controller.d.ts +45 -3
  305. package/dist/controllers/tooltip_controller.js +158 -124
  306. package/dist/controllers/tooltip_controller.js.map +1 -1
  307. package/dist/controllers/transition_controller.d.ts +22 -4
  308. package/dist/controllers/transition_controller.js +123 -119
  309. package/dist/controllers/transition_controller.js.map +1 -1
  310. package/dist/controllers/tree_view_controller.d.ts +21 -11
  311. package/dist/controllers/tree_view_controller.js +150 -345
  312. package/dist/controllers/tree_view_controller.js.map +1 -1
  313. package/dist/index.d.ts +8 -5
  314. package/dist/index.js +6370 -9346
  315. package/dist/index.js.map +1 -1
  316. package/dist/inspector/cli.d.ts +23 -3
  317. package/dist/inspector/cli.js +92 -37
  318. package/dist/inspector/cli.js.map +1 -1
  319. package/dist/inspector/cli_bin.js +92 -56
  320. package/dist/inspector/cli_bin.js.map +1 -1
  321. package/dist/inspector/examples.json +8 -8
  322. package/dist/inspector/manifest.json +787 -100
  323. package/dist/positioning/index.d.ts +8 -0
  324. package/dist/positioning/index.js +148 -47
  325. package/dist/positioning/index.js.map +1 -1
  326. package/package.json +6 -6
  327. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  328. 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
- };
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);
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
164
  };
91
165
 
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,56 +287,195 @@ 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/utils/target_selector.ts
232
464
  function targetSelector(identifier, name) {
233
465
  return `[data-${identifier}-target~="${name}"]`;
234
466
  }
235
467
 
236
- // src/controllers/nested_form_controller.ts
237
468
  var REMOVE_PART = "remove";
238
469
  var DESTROY_FLAG_PART = "destroyFlag";
239
470
  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. */
471
+ var NestedFormController = class _NestedFormController extends Controller {
472
+ #numbers = new NumberValueReader();
242
473
  get #rootSelector() {
243
474
  return `[data-controller~="${this.identifier}"]`;
244
475
  }
245
- /** Selects per-row remove buttons, in the namespace this controller is registered under. */
246
476
  get #removeSelector() {
247
477
  return targetSelector(this.identifier, REMOVE_PART);
248
478
  }
249
- /** Selects the hidden `_destroy` input a persisted row carries. */
250
479
  get #destroyFlagSelector() {
251
480
  return targetSelector(this.identifier, DESTROY_FLAG_PART);
252
481
  }
@@ -258,27 +487,20 @@ var NestedFormController = class extends Controller {
258
487
  announce: { type: Boolean, default: true },
259
488
  countMessage: { type: String, default: "" }
260
489
  };
490
+ static valueConstraints = {
491
+ min: NUMBER_BOUNDS.nonNegative,
492
+ max: NUMBER_BOUNDS.nonNegative
493
+ };
261
494
  static actions = ["add"];
262
495
  static events = ["add", "remove", "reconcile"];
263
- /** Monotonic source for unique row indices; never a row-state counter. */
264
496
  #lastIndex = 0;
265
497
  #warnedMissing = false;
266
498
  #warnedTemplate = false;
267
- /** The state last written to the hooks; reconciliation reports only real moves. */
268
499
  #published = null;
269
- /** Watches the list for row changes the controller did not perform itself. */
270
500
  #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
- */
501
+ #reconcile = new MorphRenderWatcher(() => this.#reconcileNow());
502
+ #addDisabled = new AttributeLease("disabled", this.identifier);
503
+ #tabindex = new TabindexLoan("-1", this.identifier);
282
504
  #onClick = (event) => {
283
505
  const target = event.target;
284
506
  const button = target?.closest(this.#removeSelector);
@@ -287,11 +509,11 @@ var NestedFormController = class extends Controller {
287
509
  if (row) this.#removeRow(row);
288
510
  };
289
511
  connect() {
512
+ this.#tabindex.reclaim(this.element);
290
513
  this.#warnedMissing = false;
291
514
  this.#warnedTemplate = false;
292
515
  this.element.addEventListener("click", this.#onClick);
293
- this.#reconcile.activate();
294
- this.#beforeCache.activate();
516
+ this.#reconcile.observe(this.element);
295
517
  if (!this.hasListTarget || !this.hasTemplateTarget) this.#warnMissing();
296
518
  this.#refresh();
297
519
  }
@@ -299,28 +521,19 @@ var NestedFormController = class extends Controller {
299
521
  this.element.removeEventListener("click", this.#onClick);
300
522
  this.#observer?.disconnect();
301
523
  this.#observer = null;
302
- this.#reconcile.cancel();
303
- this.#beforeCache.deactivate();
304
- this.#rewindForCache();
524
+ this.#reconcile.disconnect();
525
+ this.#addDisabled.returnAll();
305
526
  this.#tabindex.returnAll();
306
527
  this.#published = null;
307
528
  }
308
- /** Follows an arriving or swapped-in list: rebind to the primary, then reconcile. */
309
529
  listTargetConnected() {
310
530
  this.#rebindObserver();
311
531
  this.#reconcile.schedule();
312
532
  }
313
- /** Follows a departing list the same way — the primary may have changed. */
314
533
  listTargetDisconnected() {
315
534
  this.#rebindObserver();
316
535
  this.#reconcile.schedule();
317
536
  }
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
537
  #rebindObserver() {
325
538
  this.#observer?.disconnect();
326
539
  this.#observer = null;
@@ -333,7 +546,6 @@ var NestedFormController = class extends Controller {
333
546
  attributeFilter: ["value"]
334
547
  });
335
548
  }
336
- /** Returns the lease with a departing add button; a new one re-arms on refresh. */
337
549
  addTargetDisconnected(target) {
338
550
  this.#addDisabled.return(target);
339
551
  this.#reconcile.schedule();
@@ -341,21 +553,12 @@ var NestedFormController = class extends Controller {
341
553
  addTargetConnected() {
342
554
  this.#reconcile.schedule();
343
555
  }
344
- /** Re-clamps when application code or a Turbo morph changes `min`. */
345
556
  minValueChanged() {
346
557
  this.#reconcile.schedule();
347
558
  }
348
- /** Re-clamps when application code or a Turbo morph changes `max`. */
349
559
  maxValueChanged() {
350
560
  this.#reconcile.schedule();
351
561
  }
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
562
  add() {
360
563
  if (!this.hasListTarget || !this.hasTemplateTarget) {
361
564
  this.#warnMissing();
@@ -386,13 +589,6 @@ var NestedFormController = class extends Controller {
386
589
  this.dispatch("add", { detail: { index, element: row } });
387
590
  this.#announce();
388
591
  }
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
592
  #removeRow(row) {
397
593
  const rows = this.#effectiveRows;
398
594
  if (this.#destroyed(row)) {
@@ -400,7 +596,7 @@ var NestedFormController = class extends Controller {
400
596
  this.#focusAfterRemove(this.#positionAmong(rows, row));
401
597
  return;
402
598
  }
403
- if (rows.length <= this.minValue) return;
599
+ if (rows.length <= this.#safeMin) return;
404
600
  const position = rows.indexOf(row);
405
601
  const flag = this.#destroyFlagOf(row);
406
602
  const persisted = flag !== null;
@@ -415,21 +611,11 @@ var NestedFormController = class extends Controller {
415
611
  this.dispatch("remove", { detail: { element: row, persisted } });
416
612
  this.#announce();
417
613
  }
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
614
  #positionAmong(rows, row) {
424
615
  return rows.findIndex(
425
616
  (candidate) => (row.compareDocumentPosition(candidate) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0
426
617
  );
427
618
  }
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
619
  #focusAfterRemove(position) {
434
620
  const rows = this.#effectiveRows;
435
621
  const ordered = [...rows.slice(position), ...rows.slice(0, position).reverse()];
@@ -447,31 +633,20 @@ var NestedFormController = class extends Controller {
447
633
  this.#tabindex.lend(this.element);
448
634
  this.element.focus();
449
635
  }
450
- /**
451
- * Recomputes the count and min/max hooks from the DOM and records them as published.
452
- *
453
- * @stimeoRenderRoot
454
- */
455
636
  #refresh() {
456
637
  if (!this.hasListTarget) return;
457
638
  const count = this.#effectiveRows.length;
458
- const atMin = count <= this.minValue;
459
- const atMax = this.maxValue > 0 && count >= this.maxValue;
639
+ const atMin = count <= this.#safeMin;
640
+ const atMax = this.#safeMax > 0 && count >= this.#safeMax;
460
641
  this.element.setAttribute("data-nested-count", String(count));
461
642
  this.#reflect("data-nested-at-max", atMax);
462
643
  this.#reflect("data-nested-at-min", atMin);
463
644
  if (this.hasAddTarget) {
464
- if (this.maxValue > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
645
+ if (this.#safeMax > 0) this.#addDisabled.write(this.addTarget, atMax ? "" : null);
465
646
  else this.#addDisabled.return(this.addTarget);
466
647
  }
467
648
  this.#published = { count, atMin, atMax };
468
649
  }
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
650
  #reconcileNow() {
476
651
  const previous = this.#published;
477
652
  this.#refresh();
@@ -480,25 +655,14 @@ var NestedFormController = class extends Controller {
480
655
  const moved = previous.count !== current.count || previous.atMin !== current.atMin || previous.atMax !== current.atMax;
481
656
  if (moved) this.dispatch("reconcile", { detail: { ...current } });
482
657
  }
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
658
  #announce() {
489
659
  if (!this.announceValue || this.countMessageValue === "") return;
490
660
  announce(fillTemplate(this.countMessageValue, { count: this.#effectiveRows.length }));
491
661
  }
492
- /** Sets `attribute` to `"true"` when `on`, else removes it. */
493
662
  #reflect(attribute, on) {
494
663
  if (on) this.element.setAttribute(attribute, "true");
495
664
  else this.element.removeAttribute(attribute);
496
665
  }
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
666
  #warnMissing() {
503
667
  if (this.#warnedMissing) return;
504
668
  this.#warnedMissing = true;
@@ -510,49 +674,38 @@ var NestedFormController = class extends Controller {
510
674
  `Stimeo UI: "${this.identifier}" cannot manage rows because its markup lacks ${missing}.`
511
675
  );
512
676
  }
513
- /** Names a template that does not produce exactly one element, once per connection. */
514
677
  #warnBadTemplate(produced) {
515
678
  if (this.#warnedTemplate) return;
516
679
  this.#warnedTemplate = true;
517
680
  const reason = produced === 0 ? "produces no element" : "must produce exactly one root element";
518
681
  console.warn(`Stimeo UI: "${this.identifier}" added no row because its template ${reason}.`);
519
682
  }
520
- /** A strictly-increasing unique index (collision-free even on rapid adds). */
521
683
  #nextIndex() {
522
684
  const index = Math.max(Date.now(), this.#lastIndex + 1);
523
685
  this.#lastIndex = index;
524
686
  return index;
525
687
  }
526
688
  get #atMax() {
527
- return this.maxValue > 0 && this.#effectiveRows.length >= this.maxValue;
689
+ return this.#safeMax > 0 && this.#effectiveRows.length >= this.#safeMax;
528
690
  }
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
691
  get #effectiveRows() {
535
692
  return Array.from(this.listTarget.children).filter(
536
693
  (row) => !this.#destroyed(row)
537
694
  );
538
695
  }
539
- /** Whether `row` is flagged for destruction; the flag value is the truth source. */
540
696
  #destroyed(row) {
541
697
  const flag = this.#destroyFlagOf(row);
542
698
  return flag !== null && DESTROYED_VALUES.has(flag.value);
543
699
  }
544
- /** The row's own destroy flag, skipping flags owned by a nested inner form. */
545
700
  #destroyFlagOf(row) {
546
701
  for (const flag of row.querySelectorAll(this.#destroyFlagSelector)) {
547
702
  if (this.#ownerOf(flag) === this.element) return flag;
548
703
  }
549
704
  return null;
550
705
  }
551
- /** The nearest nested-form root that owns `el`. */
552
706
  #ownerOf(el) {
553
707
  return el.closest(this.#rootSelector);
554
708
  }
555
- /** The nearest ancestor of `el` that is a direct child of the list, else null. */
556
709
  #rowContaining(el) {
557
710
  if (!this.hasListTarget) return null;
558
711
  let node = el;
@@ -561,8 +714,25 @@ var NestedFormController = class extends Controller {
561
714
  }
562
715
  return node;
563
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
+ }
564
735
  };
565
736
 
566
737
  export { NestedFormController };
567
- //# sourceMappingURL=nested_form_controller.js.map
568
738
  //# sourceMappingURL=nested_form_controller.js.map