stimeo-ui 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +27 -11
  3. package/dist/cable/index.d.ts +88 -31
  4. package/dist/cable/index.js +384 -449
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.d.ts +4 -2
  8. package/dist/controllers/accordion_controller.js +174 -55
  9. package/dist/controllers/accordion_controller.js.map +1 -1
  10. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  11. package/dist/controllers/alert_dialog_controller.js +903 -235
  12. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  13. package/dist/controllers/announcer_controller.d.ts +37 -5
  14. package/dist/controllers/announcer_controller.js +238 -205
  15. package/dist/controllers/announcer_controller.js.map +1 -1
  16. package/dist/controllers/aspect_ratio_controller.js +0 -10
  17. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  18. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  19. package/dist/controllers/auto_submit_controller.js +414 -145
  20. package/dist/controllers/auto_submit_controller.js.map +1 -1
  21. package/dist/controllers/avatar_controller.js +185 -107
  22. package/dist/controllers/avatar_controller.js.map +1 -1
  23. package/dist/controllers/breadcrumb_controller.js +0 -130
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +21 -3
  26. package/dist/controllers/bulk_select_controller.js +282 -58
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +38 -16
  29. package/dist/controllers/calendar_controller.js +228 -260
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +43 -12
  32. package/dist/controllers/carousel_controller.js +536 -436
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +44 -7
  35. package/dist/controllers/character_counter_controller.js +493 -207
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +38 -74
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +16 -5
  41. package/dist/controllers/clipboard_controller.js +261 -149
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +17 -5
  44. package/dist/controllers/collapsible_controller.js +304 -120
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +18 -6
  47. package/dist/controllers/color_picker_controller.js +178 -146
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +24 -6
  50. package/dist/controllers/combobox_controller.js +522 -156
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +51 -20
  53. package/dist/controllers/command_palette_controller.js +1156 -395
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +300 -171
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +980 -262
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +80 -18
  62. package/dist/controllers/context_menu_controller.js +346 -153
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +47 -3
  68. package/dist/controllers/countdown_controller.js +281 -158
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -10
  71. package/dist/controllers/currency_input_controller.js +575 -225
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +25 -12
  74. package/dist/controllers/data_grid_controller.js +248 -214
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +42 -14
  77. package/dist/controllers/date_range_picker_controller.js +338 -235
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +17 -6
  80. package/dist/controllers/dialog_controller.js +899 -237
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +25 -8
  83. package/dist/controllers/direct_upload_controller.js +97 -167
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  86. package/dist/controllers/dirty_form_controller.js +72 -69
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +196 -24
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +39 -11
  92. package/dist/controllers/drawer_controller.js +974 -355
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +31 -1
  95. package/dist/controllers/dropdown_controller.js +339 -96
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +315 -88
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +79 -58
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +63 -18
  103. package/dist/controllers/file_dropzone_controller.js +322 -211
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.js +77 -42
  106. package/dist/controllers/filter_controller.js.map +1 -1
  107. package/dist/controllers/flash_controller.d.ts +35 -12
  108. package/dist/controllers/flash_controller.js +238 -347
  109. package/dist/controllers/flash_controller.js.map +1 -1
  110. package/dist/controllers/focus_controller.d.ts +15 -6
  111. package/dist/controllers/focus_controller.js +679 -240
  112. package/dist/controllers/focus_controller.js.map +1 -1
  113. package/dist/controllers/form_field_controller.js +156 -132
  114. package/dist/controllers/form_field_controller.js.map +1 -1
  115. package/dist/controllers/form_validation_controller.d.ts +3 -3
  116. package/dist/controllers/form_validation_controller.js +31 -97
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +47 -14
  119. package/dist/controllers/frame_loading_controller.js +275 -217
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +27 -6
  122. package/dist/controllers/highlight_controller.js +107 -75
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +60 -4
  125. package/dist/controllers/hover_card_controller.js +327 -130
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +48 -6
  128. package/dist/controllers/idle_controller.js +271 -101
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +30 -13
  131. package/dist/controllers/input_mask_controller.js +483 -97
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +150 -106
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +19 -2
  137. package/dist/controllers/lazy_frame_controller.js +24 -68
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +44 -9
  140. package/dist/controllers/listbox_controller.js +523 -240
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +9 -7
  143. package/dist/controllers/local_time_controller.js +102 -77
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +80 -96
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +55 -11
  149. package/dist/controllers/menu_controller.js +302 -188
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +51 -9
  152. package/dist/controllers/menubar_controller.js +232 -345
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +33 -20
  155. package/dist/controllers/meter_controller.js +417 -87
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +48 -18
  158. package/dist/controllers/multi_select_controller.js +785 -393
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +27 -6
  161. package/dist/controllers/navigation_menu_controller.js +190 -250
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +396 -192
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +268 -62
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +50 -8
  170. package/dist/controllers/number_input_controller.js +610 -303
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.d.ts +8 -5
  173. package/dist/controllers/optimistic_controller.js +45 -87
  174. package/dist/controllers/optimistic_controller.js.map +1 -1
  175. package/dist/controllers/otp_controller.d.ts +55 -8
  176. package/dist/controllers/otp_controller.js +658 -255
  177. package/dist/controllers/otp_controller.js.map +1 -1
  178. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  179. package/dist/controllers/overflow_indicator_controller.js +122 -113
  180. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  181. package/dist/controllers/overflow_menu_controller.d.ts +58 -20
  182. package/dist/controllers/overflow_menu_controller.js +470 -295
  183. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  184. package/dist/controllers/pagination_controller.d.ts +37 -7
  185. package/dist/controllers/pagination_controller.js +332 -123
  186. package/dist/controllers/pagination_controller.js.map +1 -1
  187. package/dist/controllers/password_reveal_controller.d.ts +46 -8
  188. package/dist/controllers/password_reveal_controller.js +392 -121
  189. package/dist/controllers/password_reveal_controller.js.map +1 -1
  190. package/dist/controllers/password_strength_controller.d.ts +15 -6
  191. package/dist/controllers/password_strength_controller.js +111 -158
  192. package/dist/controllers/password_strength_controller.js.map +1 -1
  193. package/dist/controllers/persist_controller.d.ts +11 -4
  194. package/dist/controllers/persist_controller.js +165 -111
  195. package/dist/controllers/persist_controller.js.map +1 -1
  196. package/dist/controllers/pointer_drag_controller.d.ts +35 -12
  197. package/dist/controllers/pointer_drag_controller.js +434 -188
  198. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  199. package/dist/controllers/popover_controller.d.ts +41 -5
  200. package/dist/controllers/popover_controller.js +269 -113
  201. package/dist/controllers/popover_controller.js.map +1 -1
  202. package/dist/controllers/portal_controller.d.ts +44 -4
  203. package/dist/controllers/portal_controller.js +215 -76
  204. package/dist/controllers/portal_controller.js.map +1 -1
  205. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  206. package/dist/controllers/preview_guard_controller.js +243 -116
  207. package/dist/controllers/preview_guard_controller.js.map +1 -1
  208. package/dist/controllers/progress_controller.d.ts +20 -19
  209. package/dist/controllers/progress_controller.js +391 -78
  210. package/dist/controllers/progress_controller.js.map +1 -1
  211. package/dist/controllers/radio_group_controller.d.ts +21 -9
  212. package/dist/controllers/radio_group_controller.js +278 -211
  213. package/dist/controllers/radio_group_controller.js.map +1 -1
  214. package/dist/controllers/range_slider_controller.d.ts +47 -6
  215. package/dist/controllers/range_slider_controller.js +220 -113
  216. package/dist/controllers/range_slider_controller.js.map +1 -1
  217. package/dist/controllers/rating_controller.d.ts +20 -4
  218. package/dist/controllers/rating_controller.js +458 -211
  219. package/dist/controllers/rating_controller.js.map +1 -1
  220. package/dist/controllers/read_more_controller.d.ts +2 -0
  221. package/dist/controllers/read_more_controller.js +138 -63
  222. package/dist/controllers/read_more_controller.js.map +1 -1
  223. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  224. package/dist/controllers/reading_progress_controller.js +166 -137
  225. package/dist/controllers/reading_progress_controller.js.map +1 -1
  226. package/dist/controllers/relative_time_controller.d.ts +20 -6
  227. package/dist/controllers/relative_time_controller.js +211 -112
  228. package/dist/controllers/relative_time_controller.js.map +1 -1
  229. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  230. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  231. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  232. package/dist/controllers/resizable_controller.d.ts +31 -2
  233. package/dist/controllers/resizable_controller.js +299 -150
  234. package/dist/controllers/resizable_controller.js.map +1 -1
  235. package/dist/controllers/roving_controller.d.ts +3 -3
  236. package/dist/controllers/roving_controller.js +49 -88
  237. package/dist/controllers/roving_controller.js.map +1 -1
  238. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  239. package/dist/controllers/scroll_area_controller.js +490 -246
  240. package/dist/controllers/scroll_area_controller.js.map +1 -1
  241. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  242. package/dist/controllers/scroll_restore_controller.js +14 -55
  243. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  244. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  245. package/dist/controllers/scroll_visibility_controller.js +432 -149
  246. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  247. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  248. package/dist/controllers/scrollspy_controller.js +193 -248
  249. package/dist/controllers/scrollspy_controller.js.map +1 -1
  250. package/dist/controllers/separator_controller.d.ts +20 -5
  251. package/dist/controllers/separator_controller.js +285 -142
  252. package/dist/controllers/separator_controller.js.map +1 -1
  253. package/dist/controllers/sidebar_controller.d.ts +65 -8
  254. package/dist/controllers/sidebar_controller.js +1069 -404
  255. package/dist/controllers/sidebar_controller.js.map +1 -1
  256. package/dist/controllers/skeleton_controller.d.ts +23 -0
  257. package/dist/controllers/skeleton_controller.js +259 -101
  258. package/dist/controllers/skeleton_controller.js.map +1 -1
  259. package/dist/controllers/slider_controller.d.ts +29 -2
  260. package/dist/controllers/slider_controller.js +187 -86
  261. package/dist/controllers/slider_controller.js.map +1 -1
  262. package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
  263. package/dist/controllers/smart_sticky_header_controller.js +173 -66
  264. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  265. package/dist/controllers/sortable_controller.d.ts +4 -4
  266. package/dist/controllers/sortable_controller.js +26 -125
  267. package/dist/controllers/sortable_controller.js.map +1 -1
  268. package/dist/controllers/spinner_controller.d.ts +39 -4
  269. package/dist/controllers/spinner_controller.js +288 -173
  270. package/dist/controllers/spinner_controller.js.map +1 -1
  271. package/dist/controllers/step_indicator_controller.d.ts +9 -4
  272. package/dist/controllers/step_indicator_controller.js +105 -46
  273. package/dist/controllers/step_indicator_controller.js.map +1 -1
  274. package/dist/controllers/stepper_controller.d.ts +14 -6
  275. package/dist/controllers/stepper_controller.js +120 -73
  276. package/dist/controllers/stepper_controller.js.map +1 -1
  277. package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
  278. package/dist/controllers/stick_to_bottom_controller.js +275 -140
  279. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  280. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  281. package/dist/controllers/sticky_observer_controller.js +220 -49
  282. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  283. package/dist/controllers/submit_once_controller.d.ts +24 -8
  284. package/dist/controllers/submit_once_controller.js +376 -227
  285. package/dist/controllers/submit_once_controller.js.map +1 -1
  286. package/dist/controllers/switch_controller.d.ts +18 -4
  287. package/dist/controllers/switch_controller.js +201 -84
  288. package/dist/controllers/switch_controller.js.map +1 -1
  289. package/dist/controllers/tabs_controller.d.ts +9 -6
  290. package/dist/controllers/tabs_controller.js +41 -30
  291. package/dist/controllers/tabs_controller.js.map +1 -1
  292. package/dist/controllers/tags_input_controller.d.ts +16 -3
  293. package/dist/controllers/tags_input_controller.js +293 -173
  294. package/dist/controllers/tags_input_controller.js.map +1 -1
  295. package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
  296. package/dist/controllers/textarea_autosize_controller.js +108 -74
  297. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  298. package/dist/controllers/theme_controller.d.ts +24 -11
  299. package/dist/controllers/theme_controller.js +140 -116
  300. package/dist/controllers/theme_controller.js.map +1 -1
  301. package/dist/controllers/time_picker_controller.d.ts +55 -5
  302. package/dist/controllers/time_picker_controller.js +331 -127
  303. package/dist/controllers/time_picker_controller.js.map +1 -1
  304. package/dist/controllers/toast_controller.d.ts +40 -16
  305. package/dist/controllers/toast_controller.js +244 -324
  306. package/dist/controllers/toast_controller.js.map +1 -1
  307. package/dist/controllers/toggle_group_controller.d.ts +14 -6
  308. package/dist/controllers/toggle_group_controller.js +252 -191
  309. package/dist/controllers/toggle_group_controller.js.map +1 -1
  310. package/dist/controllers/toolbar_controller.d.ts +8 -6
  311. package/dist/controllers/toolbar_controller.js +219 -120
  312. package/dist/controllers/toolbar_controller.js.map +1 -1
  313. package/dist/controllers/tooltip_controller.d.ts +49 -4
  314. package/dist/controllers/tooltip_controller.js +158 -124
  315. package/dist/controllers/tooltip_controller.js.map +1 -1
  316. package/dist/controllers/transition_controller.d.ts +22 -4
  317. package/dist/controllers/transition_controller.js +123 -119
  318. package/dist/controllers/transition_controller.js.map +1 -1
  319. package/dist/controllers/tree_view_controller.d.ts +22 -12
  320. package/dist/controllers/tree_view_controller.js +283 -369
  321. package/dist/controllers/tree_view_controller.js.map +1 -1
  322. package/dist/index.d.ts +10 -5
  323. package/dist/index.js +8573 -10410
  324. package/dist/index.js.map +1 -1
  325. package/dist/inspector/cli.d.ts +38 -14
  326. package/dist/inspector/cli.js +108 -40
  327. package/dist/inspector/cli.js.map +1 -1
  328. package/dist/inspector/cli_bin.js +145 -64
  329. package/dist/inspector/cli_bin.js.map +1 -1
  330. package/dist/inspector/examples.json +20 -20
  331. package/dist/inspector/manifest.json +802 -101
  332. package/dist/positioning/index.d.ts +15 -7
  333. package/dist/positioning/index.js +154 -47
  334. package/dist/positioning/index.js.map +1 -1
  335. package/package.json +11 -10
  336. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  337. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,8 +1,5 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/submit_once_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;
@@ -13,192 +10,352 @@ function announce(message, options = {}) {
13
10
  );
14
11
  }
15
12
 
16
- // src/utils/attribute_lease.ts
13
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
14
+ if (element.getAttribute(name) === value) return false;
15
+ if (value === null) element.removeAttribute(name);
16
+ else element.setAttribute(name, value);
17
+ return true;
18
+ }
19
+
20
+ function sharedRegistry(key, create) {
21
+ const symbol = Symbol.for(key);
22
+ const scope = globalThis;
23
+ const existing = scope[symbol];
24
+ if (existing !== void 0) return existing;
25
+ const registry2 = create();
26
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
27
+ return registry2;
28
+ }
29
+
30
+ var registry = sharedRegistry(
31
+ "stimeo-ui.attribute-lease.registry.v1",
32
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
33
+ );
34
+ function isAttributeValue(value) {
35
+ return value === null || typeof value === "string";
36
+ }
37
+ function readLeaseRecord(element, record) {
38
+ try {
39
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
40
+ if (!Array.isArray(parsed)) return void 0;
41
+ const [original, written, ...rest] = parsed;
42
+ const linked = rest[0] !== false;
43
+ const beneath = linked ? rest : rest.slice(1);
44
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
45
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
46
+ } catch {
47
+ return void 0;
48
+ }
49
+ }
50
+ function attributeStack(element, attribute) {
51
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
52
+ registry.stacks.set(element, byAttribute);
53
+ const known = byAttribute.get(attribute);
54
+ if (known) return known;
55
+ const found = [];
56
+ for (const record of registry.names.get(attribute) ?? []) {
57
+ const read = readLeaseRecord(element, record);
58
+ if (read) found.push({ ...read, record });
59
+ }
60
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
61
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
62
+ lease: null,
63
+ record,
64
+ written,
65
+ original,
66
+ linked: linked && index > 0
67
+ }));
68
+ byAttribute.set(attribute, stack);
69
+ return stack;
70
+ }
71
+ function findHold(stack, lease, record) {
72
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
73
+ }
17
74
  var AttributeLease = class {
18
75
  #attribute;
19
- #records = /* @__PURE__ */ new Map();
20
- /** @param attribute - The attribute whose temporary values this lease owns. */
21
- constructor(attribute) {
76
+ #record;
77
+ #base;
78
+ #preserveAuthor;
79
+ #held = /* @__PURE__ */ new Set();
80
+ constructor(attribute, owner, options = {}) {
22
81
  this.#attribute = attribute;
82
+ this.#record = `data-${owner}-${attribute}-lease`;
83
+ this.#base = options.base === true;
84
+ this.#preserveAuthor = options.preserveAuthor === true;
85
+ const names = registry.names;
86
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
23
87
  }
24
- /** Writes or removes the leased attribute while preserving its authored value. */
25
88
  write(element, value) {
26
- const existing = this.#records.get(element);
27
- if (existing) {
28
- existing.written = value;
29
- } else {
30
- this.#records.set(element, {
31
- original: element.getAttribute(this.#attribute),
32
- written: value
33
- });
34
- }
35
- this.#reflect(element, value);
89
+ const stack = attributeStack(element, this.#attribute);
90
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
91
+ this.#held.add(element);
92
+ const shows = !this.#covered(element, stack, hold);
93
+ hold.written = value;
94
+ this.#mark(element, stack);
95
+ if (shows) this.#reflect(element, value);
36
96
  }
37
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
38
97
  return(element) {
39
- const record = this.#records.get(element);
40
- if (!record) return;
41
- this.#records.delete(element);
42
- const stillOwned = element.getAttribute(this.#attribute) === record.written;
43
- if (stillOwned) this.#reflect(element, record.original);
44
- }
45
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
46
- #reflect(element, value) {
47
- if (element.getAttribute(this.#attribute) === value) return;
48
- if (value === null) element.removeAttribute(this.#attribute);
49
- else element.setAttribute(this.#attribute, value);
98
+ const held = this.#held.delete(element);
99
+ const known = registry.stacks.get(element)?.has(this.#attribute);
100
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
101
+ const hold = this.#take(stack);
102
+ if (!hold) return false;
103
+ const carries = element.getAttribute(this.#attribute) === hold.written;
104
+ const owns = carries && !this.#covered(element, stack, hold);
105
+ const index = stack.indexOf(hold);
106
+ stack.splice(index, 1);
107
+ const above = stack[index];
108
+ if (above) above.linked &&= hold.linked;
109
+ this.#mark(element, stack);
110
+ if (!owns) return true;
111
+ const below = hold.linked ? stack[index - 1] : void 0;
112
+ this.#reflect(element, below ? below.written : hold.original);
113
+ return true;
50
114
  }
51
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
52
115
  returnAll() {
53
- for (const element of Array.from(this.#records.keys())) this.return(element);
54
- }
55
- };
56
-
57
- // src/utils/before_cache_reset.ts
58
- var BeforeCacheReset = class _BeforeCacheReset {
59
- /** Every subscribed instance, iterated by the one shared document listener. */
60
- static #subscribers = /* @__PURE__ */ new Set();
61
- /** The shared listener; installed while at least one instance is subscribed. */
62
- static #onBeforeCache = () => {
63
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
64
- };
65
- #rewind;
66
- /** @param rewind - the pass that returns this controller's state to its initial form. */
67
- constructor(rewind) {
68
- this.#rewind = rewind;
69
- }
70
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
71
- activate() {
72
- const first = _BeforeCacheReset.#subscribers.size === 0;
73
- _BeforeCacheReset.#subscribers.add(this);
74
- if (first) {
75
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
116
+ for (const element of Array.from(this.#held)) this.return(element);
117
+ }
118
+ #take(stack) {
119
+ const hold = findHold(stack, this, this.#record);
120
+ if (hold) hold.lease = this;
121
+ return hold;
122
+ }
123
+ #acquire(element, stack) {
124
+ const current = element.getAttribute(this.#attribute);
125
+ const top = stack.at(-1);
126
+ const bottom = stack[0];
127
+ const over = top?.written === current ? top : void 0;
128
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
129
+ const hold = {
130
+ lease: this,
131
+ record: this.#record,
132
+ written: current,
133
+ original: from ? from.original : current,
134
+ linked: !this.#base && over !== void 0
135
+ };
136
+ if (this.#base && bottom) {
137
+ bottom.linked = true;
138
+ stack.unshift(hold);
139
+ } else stack.push(hold);
140
+ return hold;
141
+ }
142
+ #covered(element, stack, hold) {
143
+ const current = element.getAttribute(this.#attribute);
144
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
145
+ }
146
+ #mark(element, stack) {
147
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
148
+ stack.forEach((hold, index) => {
149
+ const names = stack.slice(0, index).map((under) => under.record);
150
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
151
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
152
+ records.set(
153
+ hold.record,
154
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
155
+ );
156
+ });
157
+ for (const [name, recorded] of records) {
158
+ setAttributeIfChanged(element, name, recorded);
76
159
  }
77
160
  }
78
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
79
- deactivate() {
80
- _BeforeCacheReset.#subscribers.delete(this);
81
- if (_BeforeCacheReset.#subscribers.size > 0) return;
82
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
161
+ #reflect(element, value) {
162
+ setAttributeIfChanged(element, this.#attribute, value);
83
163
  }
84
164
  };
85
165
 
86
- // src/utils/detach_gate.ts
87
166
  var DetachGate = class _DetachGate {
88
- /** Set while a probe is queued, waiting for a reconnect to cancel it. */
89
167
  #pending = false;
90
- /**
91
- * True while a probe is queued — the last disconnect was ambiguous and no
92
- * reconnect has cancelled it yet. Read it from `connect()` to tell the
93
- * reconnect half of an in-page move from a first connect: a controller whose
94
- * initialisation restarts a measurement (a min-duration floor, an elapsed
95
- * counter) must skip it for the move, where nothing actually restarted.
96
- */
97
168
  get pending() {
98
169
  return this.#pending;
99
170
  }
100
- /**
101
- * True when the disconnect is definitely a real detach — the element left
102
- * the document, or `data-controller` no longer lists the identifier. False
103
- * means ambiguous (in-page move or observed-root exit), NOT "alive".
104
- */
105
171
  static isDetached(host) {
106
- if (!host.element.isConnected) return true;
107
- const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
108
- return !tokens.includes(host.identifier);
109
- }
110
- /**
111
- * Call from `disconnect()`: runs `teardown` synchronously on a definite
112
- * detach (fast path), otherwise defers it one microtask — a reconnect
113
- * ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.
114
- * One microtask is the whole probe window: Stimulus reconnects a moved
115
- * element within the same mutation batch, before the checkpoint drains.
116
- */
172
+ return !host.element.isConnected || !_DetachGate.#listed(host);
173
+ }
117
174
  disconnected(host, teardown) {
118
- if (_DetachGate.isDetached(host)) {
175
+ const carried = _DetachGate.#carried(host);
176
+ if (!carried && _DetachGate.isDetached(host)) {
119
177
  this.#pending = false;
120
178
  teardown();
121
179
  return;
122
180
  }
123
181
  this.#pending = true;
124
- queueMicrotask(() => {
182
+ const probe = () => {
125
183
  if (!this.#pending) return;
126
184
  this.#pending = false;
127
185
  teardown();
128
- });
186
+ };
187
+ if (carried) setTimeout(probe, 0);
188
+ else queueMicrotask(probe);
189
+ }
190
+ static #carried(host) {
191
+ const element = host.element;
192
+ return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
193
+ }
194
+ static #listed(host) {
195
+ const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
196
+ return tokens.includes(host.identifier);
129
197
  }
130
- /**
131
- * Disarms a pending probe. Call from `connect()` (the reconnect that proves
132
- * an in-page move) and from the head of any teardown path not routed through
133
- * {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can
134
- * never run the teardown a second time.
135
- */
136
198
  cancel() {
137
199
  this.#pending = false;
138
200
  }
139
201
  };
140
202
 
141
- // src/utils/listener_set.ts
142
203
  var ListenerSet = class {
143
- /** The generation every `add` joins until the next `dispose()`. */
144
204
  #abort = new AbortController();
145
- /**
146
- * Attaches `handler` to the open generation, exactly as the caller spelled it.
147
- *
148
- * The set supplies the signal, so `options` carries everything else the DOM
149
- * accepts — `capture` included, which has to match at release time and no
150
- * longer has a second place to drift from.
151
- */
152
205
  add(target, type, handler, options) {
153
206
  target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
154
207
  }
155
- /**
156
- * Releases every listener of the open generation, synchronously, and opens the
157
- * next one. Idempotent, and safe before anything has been added.
158
- */
159
208
  dispose() {
160
209
  this.#abort.abort();
161
210
  this.#abort = new AbortController();
162
211
  }
163
212
  };
164
213
 
165
- // src/utils/safe_timeout.ts
214
+ var MAX_TIMER_DELAY_MS = 2147483647;
215
+
216
+ var NUMBER_BOUNDS = {
217
+ timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
218
+ function matchesNumberBounds(value, bounds) {
219
+ if (!Number.isFinite(value)) {
220
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
221
+ if (direction === null) return false;
222
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
223
+ }
224
+ if (bounds.min !== void 0 && value < bounds.min) return false;
225
+ if (bounds.max !== void 0 && value > bounds.max) return false;
226
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
227
+ if (bounds.integer && !Number.isInteger(value)) return false;
228
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
229
+ return true;
230
+ }
231
+
232
+ function readNumber(raw, fallback, bounds) {
233
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
234
+ }
235
+
236
+ var NumberValueReader = class {
237
+ #lastRejected = /* @__PURE__ */ new Map();
238
+ read(owner, name, raw, fallback, bounds) {
239
+ const resolved = readNumber(raw, fallback, bounds);
240
+ if (matchesNumberBounds(raw, bounds)) {
241
+ this.#lastRejected.delete(name);
242
+ return resolved;
243
+ }
244
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
245
+ const literal = owner.element.getAttribute(attribute);
246
+ if (literal === null) {
247
+ this.#lastRejected.delete(name);
248
+ return resolved;
249
+ }
250
+ if (this.#lastRejected.get(name) !== literal) {
251
+ this.#lastRejected.set(name, literal);
252
+ console.warn(
253
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
254
+ );
255
+ }
256
+ return resolved;
257
+ }
258
+ };
259
+
260
+ var OwnedText = class {
261
+ #attribute;
262
+ #channel;
263
+ #guard;
264
+ #writes = /* @__PURE__ */ new WeakMap();
265
+ constructor(attribute, channel = "text", guard = () => true) {
266
+ this.#attribute = attribute;
267
+ this.#channel = channel;
268
+ this.#guard = guard;
269
+ }
270
+ hasRecord(element) {
271
+ return element.hasAttribute(this.#attribute);
272
+ }
273
+ read(element, evidence = {}) {
274
+ if (evidence.source === "live") return this.#writes.get(element) ?? null;
275
+ const raw = element.getAttribute(this.#attribute);
276
+ if (raw === null) return null;
277
+ try {
278
+ const record = JSON.parse(raw);
279
+ if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
280
+ return null;
281
+ return { original: record[1], written: record[2] };
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+ owns(element, evidence = {}) {
287
+ return this.#ownedRecord(element, evidence) !== null;
288
+ }
289
+ #ownedRecord(element, evidence) {
290
+ const record = this.read(element, evidence);
291
+ if (record === null || !this.#guard(element)) return null;
292
+ if (this.#channel === "text" && element.firstElementChild !== null) return null;
293
+ const current = this.#current(element);
294
+ return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
295
+ }
296
+ write(element, written, original, force = false) {
297
+ const current = this.#current(element);
298
+ if (current === null) return;
299
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
300
+ if (force || current !== written) this.#put(element, written);
301
+ this.remember(element, written, before);
302
+ }
303
+ remember(element, written, original) {
304
+ const current = this.#current(element);
305
+ if (current === null) return;
306
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
307
+ this.#writes.set(element, { original: before, written });
308
+ setAttributeIfChanged(
309
+ element,
310
+ this.#attribute,
311
+ JSON.stringify([this.#channel, before, written])
312
+ );
313
+ }
314
+ clear(element, evidence = {}) {
315
+ if (!this.owns(element, evidence)) return false;
316
+ if (this.#current(element) !== "") this.#put(element, "");
317
+ this.forget(element);
318
+ return true;
319
+ }
320
+ restore(element, evidence = {}) {
321
+ const record = this.#ownedRecord(element, evidence);
322
+ if (record === null) return false;
323
+ if (this.#current(element) !== record.original) this.#put(element, record.original);
324
+ this.forget(element);
325
+ return true;
326
+ }
327
+ forget(element) {
328
+ setAttributeIfChanged(element, this.#attribute, null);
329
+ this.#writes.delete(element);
330
+ }
331
+ #current(element) {
332
+ if (this.#channel === "text") return element.textContent ?? "";
333
+ return element instanceof HTMLInputElement ? element.value : null;
334
+ }
335
+ #put(element, text) {
336
+ if (this.#channel === "text") element.textContent = text;
337
+ else if (element instanceof HTMLInputElement) element.value = text;
338
+ }
339
+ };
340
+
166
341
  var TimerRegistry = class {
167
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
168
342
  ids = /* @__PURE__ */ new Set();
169
- /**
170
- * Cancels a single tracked timer.
171
- *
172
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
173
- * registry), so callers can clear defensively without guarding.
174
- */
175
343
  clear(id) {
176
344
  if (this.ids.delete(id)) {
177
345
  this.cancel(id);
178
346
  }
179
347
  }
180
- /**
181
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
182
- * to guarantee no timer outlives the element.
183
- */
184
348
  clearAll() {
185
349
  for (const id of this.ids) {
186
350
  this.cancel(id);
187
351
  }
188
352
  this.ids.clear();
189
353
  }
190
- /** Number of timers currently tracked (pending). */
191
354
  get size() {
192
355
  return this.ids.size;
193
356
  }
194
357
  };
195
358
  var SafeTimeout = class extends TimerRegistry {
196
- /**
197
- * Schedules `callback` after `delay` ms and returns the timer id.
198
- *
199
- * The id is removed from the registry automatically when the timeout fires,
200
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
201
- */
202
359
  set(callback, delay) {
203
360
  const id = this.schedule(() => {
204
361
  this.ids.delete(id);
@@ -215,9 +372,13 @@ var SafeTimeout = class extends TimerRegistry {
215
372
  }
216
373
  };
217
374
 
218
- // src/controllers/submit_once_controller.ts
219
375
  var BUTTON_BUSY_LABEL = "data-submit-once-busy-label";
220
- var SubmitOnceController = class extends Controller {
376
+ var LABEL_RECORD = "label";
377
+ var SUBMITTER_MARK = "submitter";
378
+ var SubmitOnceController = class _SubmitOnceController extends Controller {
379
+ #textLabel = new OwnedText(this.#labelRecord);
380
+ #valueLabel = new OwnedText(this.#labelRecord, "value");
381
+ #numbers = new NumberValueReader();
221
382
  static targets = ["submit", "idle", "busy"];
222
383
  static values = {
223
384
  announceText: { type: String, default: "" },
@@ -226,26 +387,27 @@ var SubmitOnceController = class extends Controller {
226
387
  timeout: { type: Number, default: 0 },
227
388
  restoreFocus: { type: Boolean, default: false }
228
389
  };
390
+ static valueConstraints = {
391
+ timeout: NUMBER_BOUNDS.timer
392
+ };
229
393
  static actions = ["cancel", "finish", "start"];
230
394
  static events = ["start", "end", "reconcile"];
231
395
  #timers = new SafeTimeout();
232
396
  #gate = new DetachGate();
233
397
  #listeners = new ListenerSet();
234
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
235
398
  #sessions = /* @__PURE__ */ new Map();
236
- #disabled = new AttributeLease("disabled");
237
- #controlBusy = new AttributeLease("aria-busy");
238
- #formBusy = new AttributeLease("aria-busy");
239
- #submitting = new AttributeLease("data-submitting");
240
- #ariaLabel = new AttributeLease("aria-label");
241
- #hidden = new AttributeLease("hidden");
399
+ #disabled = new AttributeLease("disabled", this.identifier);
400
+ #controlBusy = new AttributeLease("aria-busy", this.identifier);
401
+ #formBusy = new AttributeLease("aria-busy", this.identifier);
402
+ #submitting = new AttributeLease("data-submitting", this.identifier);
403
+ #ariaLabel = new AttributeLease("aria-label", this.identifier);
404
+ #hidden = new AttributeLease("hidden", this.identifier);
242
405
  #onSubmitStart = (event) => {
243
406
  this.start(event);
244
407
  };
245
408
  #onSubmitEnd = (event) => {
246
409
  this.#complete(this.#eventForm(event), "turbo", this.#eventSuccess(event));
247
410
  };
248
- /** Blocks a later native submit while one for the same form is still active. */
249
411
  #onNativeSubmit = (event) => {
250
412
  const session = this.#sessionFor(event.target);
251
413
  if (!session) return;
@@ -254,24 +416,67 @@ var SubmitOnceController = class extends Controller {
254
416
  };
255
417
  connect() {
256
418
  this.#gate.cancel();
257
- this.#beforeCache.activate();
258
419
  this.#listeners.add(this.element, "submit", this.#onNativeSubmit, { capture: true });
259
420
  this.#listeners.add(this.element, "turbo:submit-start", this.#onSubmitStart);
260
421
  this.#listeners.add(this.element, "turbo:submit-end", this.#onSubmitEnd);
422
+ if (this.#sessions.size === 0) this.#releaseInherited();
261
423
  }
262
424
  disconnect() {
263
425
  this.#listeners.dispose();
264
- this.#beforeCache.deactivate();
265
426
  this.#gate.disconnected(this, () => this.#teardown());
266
427
  }
267
- /**
268
- * Begins the submission represented by `event`.
269
- *
270
- * Native `submit` events are intended for non-Turbo async forms. Turbo calls
271
- * this path through its auto-subscribed `turbo:submit-start` event. An event
272
- * whose default an earlier listener already cancelled carries no submission,
273
- * so it starts nothing.
274
- */
428
+ get #labelRecord() {
429
+ return `data-${this.identifier}-${LABEL_RECORD}`;
430
+ }
431
+ get #submitterMark() {
432
+ return `data-${this.identifier}-${SUBMITTER_MARK}`;
433
+ }
434
+ #releaseInherited() {
435
+ const forms = this.#ownedForms();
436
+ const controls = new Set(
437
+ this.element.querySelectorAll(`[${this.#labelRecord}]`)
438
+ );
439
+ for (const form of forms) {
440
+ for (const control of this.#submitControls(form)) {
441
+ if (control.hasAttribute(this.#labelRecord)) controls.add(control);
442
+ }
443
+ }
444
+ for (const control of controls) {
445
+ if (control.form && !this.#ownsForm(control.form)) continue;
446
+ this.#restoreLabel(control);
447
+ }
448
+ const copied = forms.filter((form) => this.#returnCopied(form));
449
+ if (copied.length > 0) this.dispatch("reconcile", { detail: { forms: copied } });
450
+ }
451
+ #returnCopied(form) {
452
+ let copied = this.#submitting.return(form);
453
+ this.#formBusy.return(form);
454
+ const parts = [...this.idleTargets, ...this.busyTargets];
455
+ for (const control of this.#submitControls(form)) {
456
+ this.#disabled.return(control);
457
+ this.#controlBusy.return(control);
458
+ if (control instanceof HTMLButtonElement) this.#ariaLabel.return(control);
459
+ for (const part of parts) if (control.contains(part)) this.#hidden.return(part);
460
+ if (!control.hasAttribute(this.#submitterMark)) continue;
461
+ control.removeAttribute(this.#submitterMark);
462
+ control.disabled = false;
463
+ if (control.getAttribute("aria-disabled") === "true")
464
+ control.removeAttribute("aria-disabled");
465
+ copied = true;
466
+ }
467
+ if (copied && form.getAttribute("aria-busy") === "true") form.removeAttribute("aria-busy");
468
+ return copied;
469
+ }
470
+ #restoreLabel(control) {
471
+ const owner = control instanceof HTMLInputElement ? this.#valueLabel : this.#textLabel;
472
+ owner.restore(control);
473
+ owner.forget(control);
474
+ }
475
+ #ownedForms() {
476
+ const forms = Array.from(this.element.querySelectorAll("form"));
477
+ if (this.element instanceof HTMLFormElement) forms.unshift(this.element);
478
+ return forms.filter((form) => this.#ownsForm(form));
479
+ }
275
480
  start(event) {
276
481
  if (event.defaultPrevented) return;
277
482
  const form = this.#eventForm(event);
@@ -289,54 +494,33 @@ var SubmitOnceController = class extends Controller {
289
494
  busyPart: null
290
495
  };
291
496
  this.#sessions.set(form, session);
497
+ submitter?.setAttribute(this.#submitterMark, "");
292
498
  this.#formBusy.write(form, "true");
293
499
  this.#submitting.write(form, "true");
294
500
  this.#syncControls(session, controls);
295
501
  this.#enterLabel(session);
296
502
  this.dispatch("start", { detail: { form, submitter } });
297
- if (this.timeoutValue > 0) {
503
+ if (this.#safeTimeout > 0) {
298
504
  session.timeoutId = this.#timers.set(
299
505
  () => this.#complete(form, "timeout", false),
300
- this.timeoutValue
506
+ this.#safeTimeout
301
507
  );
302
508
  }
303
509
  announce(this.announceTextValue);
304
510
  }
305
- /**
306
- * Completes a non-Turbo async submission for the form associated with
307
- * `event`. When called directly with no event, it completes the controller's
308
- * sole active form (or the controller element itself when it is a form).
309
- */
310
511
  finish(event) {
311
512
  const form = event ? this.#eventForm(event) : this.#defaultForm();
312
513
  this.#complete(form, "manual", event ? this.#eventSuccess(event) : void 0);
313
514
  }
314
- /**
315
- * Abandons the submission for the form associated with `event` because it
316
- * never reached the network — a veto from validation, or a request the
317
- * consumer gave up before sending. With no event it targets the controller's
318
- * sole active form, exactly like {@link finish}.
319
- *
320
- * The distinction from `finish` is observable: a request that ran and failed
321
- * is `finish` with `success: false` and still announces completion, while a
322
- * submission that never happened announces nothing.
323
- */
324
515
  cancel(event) {
325
516
  const form = event ? this.#eventForm(event) : this.#defaultForm();
326
517
  this.#complete(form, "canceled", false);
327
518
  }
328
- /** Disables a newly connected explicit submit target during its form's session. */
329
519
  submitTargetConnected(target) {
330
520
  const session = this.#sessionFor(target.form);
331
521
  if (!session || !this.#isNativeSubmitControl(target)) return;
332
522
  this.#enterControl(session, target);
333
523
  }
334
- /**
335
- * Ends one session, restores owned state, and reports the observable completion.
336
- *
337
- * @stimeoRuntimeOnly `announceReadyText` words the one announcement of this finished submission
338
- * and `restoreFocus` decides where its focus returns.
339
- */
340
524
  #complete(form, reason, success) {
341
525
  const session = this.#sessionFor(form);
342
526
  if (!session) return;
@@ -347,23 +531,15 @@ var SubmitOnceController = class extends Controller {
347
531
  this.#restoreFocus(session);
348
532
  if (reason !== "canceled") announce(this.announceReadyTextValue);
349
533
  }
350
- /** Applies the busy state to every current native/explicit control exactly once. */
351
534
  #syncControls(session, controls = this.#submitControls(session.form)) {
352
535
  for (const control of controls) this.#enterControl(session, control);
353
536
  }
354
- /** Temporarily disables one enabled submit control while retaining authored state. */
355
537
  #enterControl(session, control) {
356
538
  if (session.controls.has(control) || control.matches(":disabled")) return;
357
539
  session.controls.add(control);
358
540
  this.#disabled.write(control, "");
359
541
  this.#controlBusy.write(control, "true");
360
542
  }
361
- /**
362
- * Swaps only the triggering control's safe label channel.
363
- *
364
- * @stimeoRuntimeOnly `busyLabel` is the label this one submission shows until it ends; the idle
365
- * label comes back with the state it returns.
366
- */
367
543
  #enterLabel(session) {
368
544
  const control = session.submitter;
369
545
  if (!control) return;
@@ -379,13 +555,9 @@ var SubmitOnceController = class extends Controller {
379
555
  if (label.length === 0) return;
380
556
  if (control instanceof HTMLInputElement) {
381
557
  if (control.type !== "submit") return;
382
- session.labelWrite = {
383
- channel: "value",
384
- control,
385
- original: control.value,
386
- written: label
387
- };
388
- control.value = label;
558
+ const owner2 = new OwnedText(this.#labelRecord, "value");
559
+ session.labelWrite = { control, owner: owner2 };
560
+ owner2.write(control, label, control.value);
389
561
  return;
390
562
  }
391
563
  if (control.hasAttribute("aria-label")) {
@@ -393,30 +565,22 @@ var SubmitOnceController = class extends Controller {
393
565
  return;
394
566
  }
395
567
  if (control.childElementCount > 0) return;
396
- session.labelWrite = {
397
- channel: "text",
398
- control,
399
- original: control.textContent ?? "",
400
- written: label
401
- };
402
- control.textContent = label;
568
+ const owner = new OwnedText(this.#labelRecord);
569
+ session.labelWrite = { control, owner };
570
+ owner.write(control, label, control.textContent ?? "");
403
571
  }
404
- /** Returns labels, controls, and form hooks, respecting intervening consumer writes. */
405
- #returnSessionState(session) {
572
+ #returnSessionState(session, unfinished = false) {
406
573
  if (session.timeoutId !== null) {
407
574
  this.#timers.clear(session.timeoutId);
408
575
  session.timeoutId = null;
409
576
  }
410
- if (session.labelWrite?.channel === "value") {
411
- const { control, original, written } = session.labelWrite;
412
- if (control.value === written) control.value = original;
413
- } else if (session.labelWrite?.channel === "text") {
414
- const { control, original, written } = session.labelWrite;
415
- if (control.childElementCount === 0 && control.textContent === written) {
416
- control.textContent = original;
417
- }
577
+ if (session.labelWrite) {
578
+ const { control, owner } = session.labelWrite;
579
+ owner.restore(control, { source: "live" });
580
+ owner.forget(control);
418
581
  }
419
582
  session.labelWrite = null;
583
+ if (!unfinished) session.submitter?.removeAttribute(this.#submitterMark);
420
584
  if (session.submitter instanceof HTMLButtonElement) {
421
585
  this.#ariaLabel.return(session.submitter);
422
586
  }
@@ -428,11 +592,9 @@ var SubmitOnceController = class extends Controller {
428
592
  this.#controlBusy.return(control);
429
593
  this.#disabled.return(control);
430
594
  }
431
- session.controls.clear();
432
595
  this.#submitting.return(session.form);
433
596
  this.#formBusy.return(session.form);
434
597
  }
435
- /** Restores focus only when disabling the focused submitter actually stranded it. */
436
598
  #restoreFocus(session) {
437
599
  const submitter = session.submitter;
438
600
  if (!this.restoreFocusValue || !session.submitterHadFocus || !submitter?.isConnected) return;
@@ -441,30 +603,18 @@ var SubmitOnceController = class extends Controller {
441
603
  submitter.focus();
442
604
  }
443
605
  }
444
- /** Silently abandons every session before caching or on a real detach. */
445
606
  #abandonSessions() {
446
607
  this.#timers.clearAll();
608
+ const departed = !this.element.isConnected;
447
609
  for (const session of this.#sessions.values()) {
448
610
  session.timeoutId = null;
449
- this.#returnSessionState(session);
611
+ this.#returnSessionState(session, departed);
450
612
  }
451
613
  this.#sessions.clear();
452
614
  }
453
- /**
454
- * Keeps Turbo's cached snapshot idle without `end`, announcements, or focus
455
- * moves; the abandoned forms are reported once as `reconcile`.
456
- */
457
- #rewindForCache() {
458
- const forms = [...this.#sessions.keys()];
459
- this.#abandonSessions();
460
- if (forms.length > 0) this.dispatch("reconcile", { detail: { forms } });
461
- }
462
- /** Tears down a true detach; an in-page move is canceled by the next `connect()`. */
463
615
  #teardown() {
464
- this.#gate.cancel();
465
616
  this.#abandonSessions();
466
617
  }
467
- /** All native controls associated with `form`, plus valid explicit targets. */
468
618
  #submitControls(form) {
469
619
  const controls = /* @__PURE__ */ new Set();
470
620
  for (const element of Array.from(form.elements)) {
@@ -476,13 +626,11 @@ var SubmitOnceController = class extends Controller {
476
626
  }
477
627
  return Array.from(controls);
478
628
  }
479
- /** Resolves the real submitter, falling back to the first enabled submit control. */
480
629
  #resolveSubmitter(event, controls) {
481
630
  const submitter = this.#eventSubmitter(event);
482
631
  if (submitter && controls.includes(submitter)) return submitter;
483
632
  return controls.find((control) => !control.matches(":disabled")) ?? controls[0] ?? null;
484
633
  }
485
- /** Reads a native or Turbo submitter without assuming `SubmitEvent` exists. */
486
634
  #eventSubmitter(event) {
487
635
  if (typeof SubmitEvent !== "undefined" && event instanceof SubmitEvent) {
488
636
  return this.#isNativeSubmitControl(event.submitter) ? event.submitter : null;
@@ -491,21 +639,18 @@ var SubmitOnceController = class extends Controller {
491
639
  const submitter = detail?.formSubmission?.submitter;
492
640
  return this.#isNativeSubmitControl(submitter) ? submitter : null;
493
641
  }
494
- /** Finds the form represented by an action/Turbo event and verifies this scope owns it. */
495
642
  #eventForm(event) {
496
643
  const detail = event.detail;
497
644
  const target = event.target;
498
645
  const candidate = (target instanceof HTMLFormElement ? target : null) ?? (target instanceof Element ? target.closest("form") : null) ?? detail?.formSubmission?.formElement ?? detail?.form ?? this.#eventSubmitter(event)?.form ?? (this.element instanceof HTMLFormElement ? this.element : null);
499
646
  return candidate && this.#ownsForm(candidate) ? candidate : null;
500
647
  }
501
- /** The only unambiguous form for a direct `finish()` call. */
502
648
  #defaultForm() {
503
649
  if (this.element instanceof HTMLFormElement && this.#ownsForm(this.element))
504
650
  return this.element;
505
651
  if (this.#sessions.size === 1) return this.#sessions.keys().next().value ?? null;
506
652
  return null;
507
653
  }
508
- /** Prevents an ancestor controller from also taking over a nested instance's form. */
509
654
  #ownsForm(form) {
510
655
  let candidate = form;
511
656
  while (candidate) {
@@ -515,29 +660,33 @@ var SubmitOnceController = class extends Controller {
515
660
  }
516
661
  return false;
517
662
  }
518
- /** Returns the active session for a form-shaped external event value. */
519
663
  #sessionFor(candidate) {
520
664
  if (!(candidate instanceof HTMLFormElement)) return void 0;
521
665
  return this.#sessions.get(candidate);
522
666
  }
523
- /** Effective native submit controls, including implicit buttons and image inputs. */
524
667
  #isNativeSubmitControl(element) {
525
668
  if (element instanceof HTMLButtonElement) return element.type === "submit";
526
669
  return element instanceof HTMLInputElement && (element.type === "submit" || element.type === "image");
527
670
  }
528
- /** Finds a structured label part belonging to this button, not a nested control. */
529
671
  #partInside(control, parts) {
530
672
  return parts.find(
531
673
  (part) => control.contains(part) && part.closest("button, input[type=submit], input[type=image]") === control
532
674
  ) ?? null;
533
675
  }
534
- /** Reads an optional boolean `success` from Turbo or a manual completion event. */
535
676
  #eventSuccess(event) {
536
677
  const success = event.detail?.success;
537
678
  return typeof success === "boolean" ? success : void 0;
538
679
  }
680
+ get #safeTimeout() {
681
+ return this.#numbers.read(
682
+ this,
683
+ "timeout",
684
+ this.timeoutValue,
685
+ _SubmitOnceController.values.timeout.default,
686
+ _SubmitOnceController.valueConstraints.timeout
687
+ );
688
+ }
539
689
  };
540
690
 
541
691
  export { SubmitOnceController };
542
- //# sourceMappingURL=submit_once_controller.js.map
543
692
  //# sourceMappingURL=submit_once_controller.js.map