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/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,265 @@ function announce(message, options = {}) {
13
10
  );
14
11
  }
15
12
 
16
- // src/utils/attribute_lease.ts
17
- var AttributeLease = class {
13
+ function sharedRegistry(key, create) {
14
+ const symbol = Symbol.for(key);
15
+ const scope = globalThis;
16
+ const existing = scope[symbol];
17
+ if (existing !== void 0) return existing;
18
+ const registry = create();
19
+ Object.defineProperty(globalThis, symbol, { value: registry });
20
+ return registry;
21
+ }
22
+
23
+ function isAttributeValue(value) {
24
+ return value === null || typeof value === "string";
25
+ }
26
+ function readLeaseRecord(element, record) {
27
+ try {
28
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
29
+ if (!Array.isArray(parsed)) return void 0;
30
+ const [original, written, ...rest] = parsed;
31
+ const linked = rest[0] !== false;
32
+ const beneath = linked ? rest : rest.slice(1);
33
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
34
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
35
+ } catch {
36
+ return void 0;
37
+ }
38
+ }
39
+ var AttributeLease = class _AttributeLease {
40
+ static #registry = sharedRegistry(
41
+ "stimeo-ui.attribute-lease.registry.v1",
42
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
43
+ );
18
44
  #attribute;
19
- #records = /* @__PURE__ */ new Map();
20
- /** @param attribute - The attribute whose temporary values this lease owns. */
21
- constructor(attribute) {
45
+ #record;
46
+ #base;
47
+ #held = /* @__PURE__ */ new Set();
48
+ constructor(attribute, owner, options = {}) {
22
49
  this.#attribute = attribute;
50
+ this.#record = `data-${owner}-${attribute}-lease`;
51
+ this.#base = options.base === true;
52
+ const names = _AttributeLease.#registry.names;
53
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
23
54
  }
24
- /** Writes or removes the leased attribute while preserving its authored value. */
25
55
  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);
56
+ const stack = this.#stack(element);
57
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
58
+ this.#held.add(element);
59
+ const shows = !this.#covered(element, stack, hold);
60
+ hold.written = value;
61
+ this.#mark(element, stack);
62
+ if (shows) this.#reflect(element, value);
36
63
  }
37
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
38
64
  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);
65
+ const held = this.#held.delete(element);
66
+ const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
67
+ const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
68
+ const hold = this.#take(stack);
69
+ if (!hold) return false;
70
+ const carries = element.getAttribute(this.#attribute) === hold.written;
71
+ const owns = carries && !this.#covered(element, stack, hold);
72
+ const index = stack.indexOf(hold);
73
+ stack.splice(index, 1);
74
+ const above = stack[index];
75
+ if (above) above.linked &&= hold.linked;
76
+ this.#mark(element, stack);
77
+ if (!owns) return true;
78
+ const below = hold.linked ? stack[index - 1] : void 0;
79
+ this.#reflect(element, below ? below.written : hold.original);
80
+ return true;
81
+ }
82
+ returnAll() {
83
+ for (const element of Array.from(this.#held)) this.return(element);
84
+ }
85
+ #stack(element) {
86
+ const stacks = _AttributeLease.#registry.stacks;
87
+ const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
88
+ stacks.set(element, byAttribute);
89
+ const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
90
+ byAttribute.set(this.#attribute, stack);
91
+ return stack;
92
+ }
93
+ #seed(element) {
94
+ const found = [];
95
+ for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
96
+ const read = readLeaseRecord(element, record);
97
+ if (read) found.push({ ...read, record });
98
+ }
99
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
100
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
101
+ lease: null,
102
+ record,
103
+ written,
104
+ original,
105
+ linked: linked && index > 0
106
+ }));
107
+ }
108
+ #take(stack) {
109
+ const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
110
+ if (hold) hold.lease = this;
111
+ return hold;
112
+ }
113
+ #acquire(element, stack) {
114
+ const current = element.getAttribute(this.#attribute);
115
+ const top = stack.at(-1);
116
+ const bottom = stack[0];
117
+ const over = top?.written === current ? top : void 0;
118
+ const from = this.#base ? bottom : over;
119
+ const hold = {
120
+ lease: this,
121
+ record: this.#record,
122
+ written: current,
123
+ original: from ? from.original : current,
124
+ linked: !this.#base && over !== void 0
125
+ };
126
+ if (this.#base && bottom) {
127
+ bottom.linked = true;
128
+ stack.unshift(hold);
129
+ } else stack.push(hold);
130
+ return hold;
131
+ }
132
+ #covered(element, stack, hold) {
133
+ const current = element.getAttribute(this.#attribute);
134
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
135
+ }
136
+ #mark(element, stack) {
137
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
138
+ stack.forEach((hold, index) => {
139
+ const names = stack.slice(0, index).map((under) => under.record);
140
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
141
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
142
+ records.set(
143
+ hold.record,
144
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
145
+ );
146
+ });
147
+ for (const [name, recorded] of records) {
148
+ if (element.getAttribute(name) === recorded) continue;
149
+ if (recorded === null) element.removeAttribute(name);
150
+ else element.setAttribute(name, recorded);
151
+ }
44
152
  }
45
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
46
153
  #reflect(element, value) {
47
154
  if (element.getAttribute(this.#attribute) === value) return;
48
155
  if (value === null) element.removeAttribute(this.#attribute);
49
156
  else element.setAttribute(this.#attribute, value);
50
157
  }
51
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
52
- 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);
76
- }
77
- }
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);
83
- }
84
158
  };
85
159
 
86
- // src/utils/detach_gate.ts
87
160
  var DetachGate = class _DetachGate {
88
- /** Set while a probe is queued, waiting for a reconnect to cancel it. */
89
161
  #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
162
  get pending() {
98
163
  return this.#pending;
99
164
  }
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
165
  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
- */
166
+ return !host.element.isConnected || !_DetachGate.#listed(host);
167
+ }
117
168
  disconnected(host, teardown) {
118
- if (_DetachGate.isDetached(host)) {
169
+ const carried = _DetachGate.#carried(host);
170
+ if (!carried && _DetachGate.isDetached(host)) {
119
171
  this.#pending = false;
120
172
  teardown();
121
173
  return;
122
174
  }
123
175
  this.#pending = true;
124
- queueMicrotask(() => {
176
+ const probe = () => {
125
177
  if (!this.#pending) return;
126
178
  this.#pending = false;
127
179
  teardown();
128
- });
180
+ };
181
+ if (carried) setTimeout(probe, 0);
182
+ else queueMicrotask(probe);
183
+ }
184
+ static #carried(host) {
185
+ const element = host.element;
186
+ return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
187
+ }
188
+ static #listed(host) {
189
+ const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
190
+ return tokens.includes(host.identifier);
129
191
  }
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
192
  cancel() {
137
193
  this.#pending = false;
138
194
  }
139
195
  };
140
196
 
141
- // src/utils/listener_set.ts
142
197
  var ListenerSet = class {
143
- /** The generation every `add` joins until the next `dispose()`. */
144
198
  #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
199
  add(target, type, handler, options) {
153
200
  target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
154
201
  }
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
202
  dispose() {
160
203
  this.#abort.abort();
161
204
  this.#abort = new AbortController();
162
205
  }
163
206
  };
164
207
 
165
- // src/utils/safe_timeout.ts
208
+ var MAX_TIMER_DELAY_MS = 2147483647;
209
+
210
+ var NUMBER_BOUNDS = {
211
+ timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
212
+ function matchesNumberBounds(value, bounds) {
213
+ if (!Number.isFinite(value)) {
214
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
215
+ if (direction === null) return false;
216
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
217
+ }
218
+ if (bounds.min !== void 0 && value < bounds.min) return false;
219
+ if (bounds.max !== void 0 && value > bounds.max) return false;
220
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
221
+ if (bounds.integer && !Number.isInteger(value)) return false;
222
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
223
+ return true;
224
+ }
225
+
226
+ function readNumber(raw, fallback, bounds) {
227
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
228
+ }
229
+
230
+ var NumberValueReader = class {
231
+ #lastRejected = /* @__PURE__ */ new Map();
232
+ read(owner, name, raw, fallback, bounds) {
233
+ const resolved = readNumber(raw, fallback, bounds);
234
+ if (matchesNumberBounds(raw, bounds)) {
235
+ this.#lastRejected.delete(name);
236
+ return resolved;
237
+ }
238
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
239
+ const literal = owner.element.getAttribute(attribute);
240
+ if (literal === null) {
241
+ this.#lastRejected.delete(name);
242
+ return resolved;
243
+ }
244
+ if (this.#lastRejected.get(name) !== literal) {
245
+ this.#lastRejected.set(name, literal);
246
+ console.warn(
247
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
248
+ );
249
+ }
250
+ return resolved;
251
+ }
252
+ };
253
+
166
254
  var TimerRegistry = class {
167
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
168
255
  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
256
  clear(id) {
176
257
  if (this.ids.delete(id)) {
177
258
  this.cancel(id);
178
259
  }
179
260
  }
180
- /**
181
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
182
- * to guarantee no timer outlives the element.
183
- */
184
261
  clearAll() {
185
262
  for (const id of this.ids) {
186
263
  this.cancel(id);
187
264
  }
188
265
  this.ids.clear();
189
266
  }
190
- /** Number of timers currently tracked (pending). */
191
267
  get size() {
192
268
  return this.ids.size;
193
269
  }
194
270
  };
195
271
  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
272
  set(callback, delay) {
203
273
  const id = this.schedule(() => {
204
274
  this.ids.delete(id);
@@ -215,9 +285,11 @@ var SafeTimeout = class extends TimerRegistry {
215
285
  }
216
286
  };
217
287
 
218
- // src/controllers/submit_once_controller.ts
219
288
  var BUTTON_BUSY_LABEL = "data-submit-once-busy-label";
220
- var SubmitOnceController = class extends Controller {
289
+ var LABEL_RECORD = "label";
290
+ var SUBMITTER_MARK = "submitter";
291
+ var SubmitOnceController = class _SubmitOnceController extends Controller {
292
+ #numbers = new NumberValueReader();
221
293
  static targets = ["submit", "idle", "busy"];
222
294
  static values = {
223
295
  announceText: { type: String, default: "" },
@@ -226,26 +298,27 @@ var SubmitOnceController = class extends Controller {
226
298
  timeout: { type: Number, default: 0 },
227
299
  restoreFocus: { type: Boolean, default: false }
228
300
  };
301
+ static valueConstraints = {
302
+ timeout: NUMBER_BOUNDS.timer
303
+ };
229
304
  static actions = ["cancel", "finish", "start"];
230
305
  static events = ["start", "end", "reconcile"];
231
306
  #timers = new SafeTimeout();
232
307
  #gate = new DetachGate();
233
308
  #listeners = new ListenerSet();
234
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
235
309
  #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");
310
+ #disabled = new AttributeLease("disabled", this.identifier);
311
+ #controlBusy = new AttributeLease("aria-busy", this.identifier);
312
+ #formBusy = new AttributeLease("aria-busy", this.identifier);
313
+ #submitting = new AttributeLease("data-submitting", this.identifier);
314
+ #ariaLabel = new AttributeLease("aria-label", this.identifier);
315
+ #hidden = new AttributeLease("hidden", this.identifier);
242
316
  #onSubmitStart = (event) => {
243
317
  this.start(event);
244
318
  };
245
319
  #onSubmitEnd = (event) => {
246
320
  this.#complete(this.#eventForm(event), "turbo", this.#eventSuccess(event));
247
321
  };
248
- /** Blocks a later native submit while one for the same form is still active. */
249
322
  #onNativeSubmit = (event) => {
250
323
  const session = this.#sessionFor(event.target);
251
324
  if (!session) return;
@@ -254,24 +327,80 @@ var SubmitOnceController = class extends Controller {
254
327
  };
255
328
  connect() {
256
329
  this.#gate.cancel();
257
- this.#beforeCache.activate();
258
330
  this.#listeners.add(this.element, "submit", this.#onNativeSubmit, { capture: true });
259
331
  this.#listeners.add(this.element, "turbo:submit-start", this.#onSubmitStart);
260
332
  this.#listeners.add(this.element, "turbo:submit-end", this.#onSubmitEnd);
333
+ if (this.#sessions.size === 0) this.#releaseInherited();
261
334
  }
262
335
  disconnect() {
263
336
  this.#listeners.dispose();
264
- this.#beforeCache.deactivate();
265
337
  this.#gate.disconnected(this, () => this.#teardown());
266
338
  }
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
- */
339
+ get #labelRecord() {
340
+ return `data-${this.identifier}-${LABEL_RECORD}`;
341
+ }
342
+ get #submitterMark() {
343
+ return `data-${this.identifier}-${SUBMITTER_MARK}`;
344
+ }
345
+ #releaseInherited() {
346
+ const forms = this.#ownedForms();
347
+ const controls = new Set(
348
+ this.element.querySelectorAll(`[${this.#labelRecord}]`)
349
+ );
350
+ for (const form of forms) {
351
+ for (const control of this.#submitControls(form)) {
352
+ if (control.hasAttribute(this.#labelRecord)) controls.add(control);
353
+ }
354
+ }
355
+ for (const control of controls) {
356
+ if (control.form && !this.#ownsForm(control.form)) continue;
357
+ this.#restoreLabel(control);
358
+ }
359
+ const copied = forms.filter((form) => this.#returnCopied(form));
360
+ if (copied.length > 0) this.dispatch("reconcile", { detail: { forms: copied } });
361
+ }
362
+ #returnCopied(form) {
363
+ let copied = this.#submitting.return(form);
364
+ this.#formBusy.return(form);
365
+ const parts = [...this.idleTargets, ...this.busyTargets];
366
+ for (const control of this.#submitControls(form)) {
367
+ this.#disabled.return(control);
368
+ this.#controlBusy.return(control);
369
+ if (control instanceof HTMLButtonElement) this.#ariaLabel.return(control);
370
+ for (const part of parts) if (control.contains(part)) this.#hidden.return(part);
371
+ if (!control.hasAttribute(this.#submitterMark)) continue;
372
+ control.removeAttribute(this.#submitterMark);
373
+ control.disabled = false;
374
+ if (control.getAttribute("aria-disabled") === "true")
375
+ control.removeAttribute("aria-disabled");
376
+ copied = true;
377
+ }
378
+ if (copied && form.getAttribute("aria-busy") === "true") form.removeAttribute("aria-busy");
379
+ return copied;
380
+ }
381
+ #restoreLabel(control) {
382
+ const raw = control.getAttribute(this.#labelRecord);
383
+ control.removeAttribute(this.#labelRecord);
384
+ let record;
385
+ try {
386
+ record = JSON.parse(raw ?? "");
387
+ } catch {
388
+ return;
389
+ }
390
+ if (!Array.isArray(record) || record.length !== 2) return;
391
+ const [original, written] = record;
392
+ if (typeof original !== "string" || typeof written !== "string") return;
393
+ if (control instanceof HTMLInputElement) {
394
+ if (control.value === written) control.value = original;
395
+ } else if (control.childElementCount === 0 && control.textContent === written) {
396
+ control.textContent = original;
397
+ }
398
+ }
399
+ #ownedForms() {
400
+ const forms = Array.from(this.element.querySelectorAll("form"));
401
+ if (this.element instanceof HTMLFormElement) forms.unshift(this.element);
402
+ return forms.filter((form) => this.#ownsForm(form));
403
+ }
275
404
  start(event) {
276
405
  if (event.defaultPrevented) return;
277
406
  const form = this.#eventForm(event);
@@ -289,54 +418,33 @@ var SubmitOnceController = class extends Controller {
289
418
  busyPart: null
290
419
  };
291
420
  this.#sessions.set(form, session);
421
+ submitter?.setAttribute(this.#submitterMark, "");
292
422
  this.#formBusy.write(form, "true");
293
423
  this.#submitting.write(form, "true");
294
424
  this.#syncControls(session, controls);
295
425
  this.#enterLabel(session);
296
426
  this.dispatch("start", { detail: { form, submitter } });
297
- if (this.timeoutValue > 0) {
427
+ if (this.#safeTimeout > 0) {
298
428
  session.timeoutId = this.#timers.set(
299
429
  () => this.#complete(form, "timeout", false),
300
- this.timeoutValue
430
+ this.#safeTimeout
301
431
  );
302
432
  }
303
433
  announce(this.announceTextValue);
304
434
  }
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
435
  finish(event) {
311
436
  const form = event ? this.#eventForm(event) : this.#defaultForm();
312
437
  this.#complete(form, "manual", event ? this.#eventSuccess(event) : void 0);
313
438
  }
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
439
  cancel(event) {
325
440
  const form = event ? this.#eventForm(event) : this.#defaultForm();
326
441
  this.#complete(form, "canceled", false);
327
442
  }
328
- /** Disables a newly connected explicit submit target during its form's session. */
329
443
  submitTargetConnected(target) {
330
444
  const session = this.#sessionFor(target.form);
331
445
  if (!session || !this.#isNativeSubmitControl(target)) return;
332
446
  this.#enterControl(session, target);
333
447
  }
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
448
  #complete(form, reason, success) {
341
449
  const session = this.#sessionFor(form);
342
450
  if (!session) return;
@@ -347,23 +455,15 @@ var SubmitOnceController = class extends Controller {
347
455
  this.#restoreFocus(session);
348
456
  if (reason !== "canceled") announce(this.announceReadyTextValue);
349
457
  }
350
- /** Applies the busy state to every current native/explicit control exactly once. */
351
458
  #syncControls(session, controls = this.#submitControls(session.form)) {
352
459
  for (const control of controls) this.#enterControl(session, control);
353
460
  }
354
- /** Temporarily disables one enabled submit control while retaining authored state. */
355
461
  #enterControl(session, control) {
356
462
  if (session.controls.has(control) || control.matches(":disabled")) return;
357
463
  session.controls.add(control);
358
464
  this.#disabled.write(control, "");
359
465
  this.#controlBusy.write(control, "true");
360
466
  }
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
467
  #enterLabel(session) {
368
468
  const control = session.submitter;
369
469
  if (!control) return;
@@ -385,6 +485,7 @@ var SubmitOnceController = class extends Controller {
385
485
  original: control.value,
386
486
  written: label
387
487
  };
488
+ control.setAttribute(this.#labelRecord, JSON.stringify([control.value, label]));
388
489
  control.value = label;
389
490
  return;
390
491
  }
@@ -399,24 +500,27 @@ var SubmitOnceController = class extends Controller {
399
500
  original: control.textContent ?? "",
400
501
  written: label
401
502
  };
503
+ control.setAttribute(this.#labelRecord, JSON.stringify([control.textContent ?? "", label]));
402
504
  control.textContent = label;
403
505
  }
404
- /** Returns labels, controls, and form hooks, respecting intervening consumer writes. */
405
- #returnSessionState(session) {
506
+ #returnSessionState(session, unfinished = false) {
406
507
  if (session.timeoutId !== null) {
407
508
  this.#timers.clear(session.timeoutId);
408
509
  session.timeoutId = null;
409
510
  }
410
511
  if (session.labelWrite?.channel === "value") {
411
512
  const { control, original, written } = session.labelWrite;
513
+ control.removeAttribute(this.#labelRecord);
412
514
  if (control.value === written) control.value = original;
413
515
  } else if (session.labelWrite?.channel === "text") {
414
516
  const { control, original, written } = session.labelWrite;
517
+ control.removeAttribute(this.#labelRecord);
415
518
  if (control.childElementCount === 0 && control.textContent === written) {
416
519
  control.textContent = original;
417
520
  }
418
521
  }
419
522
  session.labelWrite = null;
523
+ if (!unfinished) session.submitter?.removeAttribute(this.#submitterMark);
420
524
  if (session.submitter instanceof HTMLButtonElement) {
421
525
  this.#ariaLabel.return(session.submitter);
422
526
  }
@@ -428,11 +532,9 @@ var SubmitOnceController = class extends Controller {
428
532
  this.#controlBusy.return(control);
429
533
  this.#disabled.return(control);
430
534
  }
431
- session.controls.clear();
432
535
  this.#submitting.return(session.form);
433
536
  this.#formBusy.return(session.form);
434
537
  }
435
- /** Restores focus only when disabling the focused submitter actually stranded it. */
436
538
  #restoreFocus(session) {
437
539
  const submitter = session.submitter;
438
540
  if (!this.restoreFocusValue || !session.submitterHadFocus || !submitter?.isConnected) return;
@@ -441,30 +543,18 @@ var SubmitOnceController = class extends Controller {
441
543
  submitter.focus();
442
544
  }
443
545
  }
444
- /** Silently abandons every session before caching or on a real detach. */
445
546
  #abandonSessions() {
446
547
  this.#timers.clearAll();
548
+ const departed = !this.element.isConnected;
447
549
  for (const session of this.#sessions.values()) {
448
550
  session.timeoutId = null;
449
- this.#returnSessionState(session);
551
+ this.#returnSessionState(session, departed);
450
552
  }
451
553
  this.#sessions.clear();
452
554
  }
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
555
  #teardown() {
464
- this.#gate.cancel();
465
556
  this.#abandonSessions();
466
557
  }
467
- /** All native controls associated with `form`, plus valid explicit targets. */
468
558
  #submitControls(form) {
469
559
  const controls = /* @__PURE__ */ new Set();
470
560
  for (const element of Array.from(form.elements)) {
@@ -476,13 +566,11 @@ var SubmitOnceController = class extends Controller {
476
566
  }
477
567
  return Array.from(controls);
478
568
  }
479
- /** Resolves the real submitter, falling back to the first enabled submit control. */
480
569
  #resolveSubmitter(event, controls) {
481
570
  const submitter = this.#eventSubmitter(event);
482
571
  if (submitter && controls.includes(submitter)) return submitter;
483
572
  return controls.find((control) => !control.matches(":disabled")) ?? controls[0] ?? null;
484
573
  }
485
- /** Reads a native or Turbo submitter without assuming `SubmitEvent` exists. */
486
574
  #eventSubmitter(event) {
487
575
  if (typeof SubmitEvent !== "undefined" && event instanceof SubmitEvent) {
488
576
  return this.#isNativeSubmitControl(event.submitter) ? event.submitter : null;
@@ -491,21 +579,18 @@ var SubmitOnceController = class extends Controller {
491
579
  const submitter = detail?.formSubmission?.submitter;
492
580
  return this.#isNativeSubmitControl(submitter) ? submitter : null;
493
581
  }
494
- /** Finds the form represented by an action/Turbo event and verifies this scope owns it. */
495
582
  #eventForm(event) {
496
583
  const detail = event.detail;
497
584
  const target = event.target;
498
585
  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
586
  return candidate && this.#ownsForm(candidate) ? candidate : null;
500
587
  }
501
- /** The only unambiguous form for a direct `finish()` call. */
502
588
  #defaultForm() {
503
589
  if (this.element instanceof HTMLFormElement && this.#ownsForm(this.element))
504
590
  return this.element;
505
591
  if (this.#sessions.size === 1) return this.#sessions.keys().next().value ?? null;
506
592
  return null;
507
593
  }
508
- /** Prevents an ancestor controller from also taking over a nested instance's form. */
509
594
  #ownsForm(form) {
510
595
  let candidate = form;
511
596
  while (candidate) {
@@ -515,29 +600,33 @@ var SubmitOnceController = class extends Controller {
515
600
  }
516
601
  return false;
517
602
  }
518
- /** Returns the active session for a form-shaped external event value. */
519
603
  #sessionFor(candidate) {
520
604
  if (!(candidate instanceof HTMLFormElement)) return void 0;
521
605
  return this.#sessions.get(candidate);
522
606
  }
523
- /** Effective native submit controls, including implicit buttons and image inputs. */
524
607
  #isNativeSubmitControl(element) {
525
608
  if (element instanceof HTMLButtonElement) return element.type === "submit";
526
609
  return element instanceof HTMLInputElement && (element.type === "submit" || element.type === "image");
527
610
  }
528
- /** Finds a structured label part belonging to this button, not a nested control. */
529
611
  #partInside(control, parts) {
530
612
  return parts.find(
531
613
  (part) => control.contains(part) && part.closest("button, input[type=submit], input[type=image]") === control
532
614
  ) ?? null;
533
615
  }
534
- /** Reads an optional boolean `success` from Turbo or a manual completion event. */
535
616
  #eventSuccess(event) {
536
617
  const success = event.detail?.success;
537
618
  return typeof success === "boolean" ? success : void 0;
538
619
  }
620
+ get #safeTimeout() {
621
+ return this.#numbers.read(
622
+ this,
623
+ "timeout",
624
+ this.timeoutValue,
625
+ _SubmitOnceController.values.timeout.default,
626
+ _SubmitOnceController.valueConstraints.timeout
627
+ );
628
+ }
539
629
  };
540
630
 
541
631
  export { SubmitOnceController };
542
- //# sourceMappingURL=submit_once_controller.js.map
543
632
  //# sourceMappingURL=submit_once_controller.js.map