stimeo-ui 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +27 -11
  3. package/dist/cable/index.d.ts +88 -31
  4. package/dist/cable/index.js +384 -449
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.d.ts +4 -2
  8. package/dist/controllers/accordion_controller.js +174 -55
  9. package/dist/controllers/accordion_controller.js.map +1 -1
  10. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  11. package/dist/controllers/alert_dialog_controller.js +903 -235
  12. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  13. package/dist/controllers/announcer_controller.d.ts +37 -5
  14. package/dist/controllers/announcer_controller.js +238 -205
  15. package/dist/controllers/announcer_controller.js.map +1 -1
  16. package/dist/controllers/aspect_ratio_controller.js +0 -10
  17. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  18. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  19. package/dist/controllers/auto_submit_controller.js +414 -145
  20. package/dist/controllers/auto_submit_controller.js.map +1 -1
  21. package/dist/controllers/avatar_controller.js +185 -107
  22. package/dist/controllers/avatar_controller.js.map +1 -1
  23. package/dist/controllers/breadcrumb_controller.js +0 -130
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +21 -3
  26. package/dist/controllers/bulk_select_controller.js +282 -58
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +38 -16
  29. package/dist/controllers/calendar_controller.js +228 -260
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +43 -12
  32. package/dist/controllers/carousel_controller.js +536 -436
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +44 -7
  35. package/dist/controllers/character_counter_controller.js +493 -207
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +38 -74
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +16 -5
  41. package/dist/controllers/clipboard_controller.js +261 -149
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +17 -5
  44. package/dist/controllers/collapsible_controller.js +304 -120
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +18 -6
  47. package/dist/controllers/color_picker_controller.js +178 -146
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +24 -6
  50. package/dist/controllers/combobox_controller.js +522 -156
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +51 -20
  53. package/dist/controllers/command_palette_controller.js +1156 -395
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +300 -171
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +980 -262
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +80 -18
  62. package/dist/controllers/context_menu_controller.js +346 -153
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +47 -3
  68. package/dist/controllers/countdown_controller.js +281 -158
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -10
  71. package/dist/controllers/currency_input_controller.js +575 -225
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +25 -12
  74. package/dist/controllers/data_grid_controller.js +248 -214
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +42 -14
  77. package/dist/controllers/date_range_picker_controller.js +338 -235
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +17 -6
  80. package/dist/controllers/dialog_controller.js +899 -237
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +25 -8
  83. package/dist/controllers/direct_upload_controller.js +97 -167
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  86. package/dist/controllers/dirty_form_controller.js +72 -69
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +196 -24
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +39 -11
  92. package/dist/controllers/drawer_controller.js +974 -355
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +31 -1
  95. package/dist/controllers/dropdown_controller.js +339 -96
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +315 -88
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +79 -58
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +63 -18
  103. package/dist/controllers/file_dropzone_controller.js +322 -211
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.js +77 -42
  106. package/dist/controllers/filter_controller.js.map +1 -1
  107. package/dist/controllers/flash_controller.d.ts +35 -12
  108. package/dist/controllers/flash_controller.js +238 -347
  109. package/dist/controllers/flash_controller.js.map +1 -1
  110. package/dist/controllers/focus_controller.d.ts +15 -6
  111. package/dist/controllers/focus_controller.js +679 -240
  112. package/dist/controllers/focus_controller.js.map +1 -1
  113. package/dist/controllers/form_field_controller.js +156 -132
  114. package/dist/controllers/form_field_controller.js.map +1 -1
  115. package/dist/controllers/form_validation_controller.d.ts +3 -3
  116. package/dist/controllers/form_validation_controller.js +31 -97
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +47 -14
  119. package/dist/controllers/frame_loading_controller.js +275 -217
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +27 -6
  122. package/dist/controllers/highlight_controller.js +107 -75
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +60 -4
  125. package/dist/controllers/hover_card_controller.js +327 -130
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +48 -6
  128. package/dist/controllers/idle_controller.js +271 -101
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +30 -13
  131. package/dist/controllers/input_mask_controller.js +483 -97
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +150 -106
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +19 -2
  137. package/dist/controllers/lazy_frame_controller.js +24 -68
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +44 -9
  140. package/dist/controllers/listbox_controller.js +523 -240
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +9 -7
  143. package/dist/controllers/local_time_controller.js +102 -77
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +80 -96
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +55 -11
  149. package/dist/controllers/menu_controller.js +302 -188
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +51 -9
  152. package/dist/controllers/menubar_controller.js +232 -345
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +33 -20
  155. package/dist/controllers/meter_controller.js +417 -87
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +48 -18
  158. package/dist/controllers/multi_select_controller.js +785 -393
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +27 -6
  161. package/dist/controllers/navigation_menu_controller.js +190 -250
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +396 -192
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +268 -62
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +50 -8
  170. package/dist/controllers/number_input_controller.js +610 -303
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.d.ts +8 -5
  173. package/dist/controllers/optimistic_controller.js +45 -87
  174. package/dist/controllers/optimistic_controller.js.map +1 -1
  175. package/dist/controllers/otp_controller.d.ts +55 -8
  176. package/dist/controllers/otp_controller.js +658 -255
  177. package/dist/controllers/otp_controller.js.map +1 -1
  178. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  179. package/dist/controllers/overflow_indicator_controller.js +122 -113
  180. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  181. package/dist/controllers/overflow_menu_controller.d.ts +58 -20
  182. package/dist/controllers/overflow_menu_controller.js +470 -295
  183. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  184. package/dist/controllers/pagination_controller.d.ts +37 -7
  185. package/dist/controllers/pagination_controller.js +332 -123
  186. package/dist/controllers/pagination_controller.js.map +1 -1
  187. package/dist/controllers/password_reveal_controller.d.ts +46 -8
  188. package/dist/controllers/password_reveal_controller.js +392 -121
  189. package/dist/controllers/password_reveal_controller.js.map +1 -1
  190. package/dist/controllers/password_strength_controller.d.ts +15 -6
  191. package/dist/controllers/password_strength_controller.js +111 -158
  192. package/dist/controllers/password_strength_controller.js.map +1 -1
  193. package/dist/controllers/persist_controller.d.ts +11 -4
  194. package/dist/controllers/persist_controller.js +165 -111
  195. package/dist/controllers/persist_controller.js.map +1 -1
  196. package/dist/controllers/pointer_drag_controller.d.ts +35 -12
  197. package/dist/controllers/pointer_drag_controller.js +434 -188
  198. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  199. package/dist/controllers/popover_controller.d.ts +41 -5
  200. package/dist/controllers/popover_controller.js +269 -113
  201. package/dist/controllers/popover_controller.js.map +1 -1
  202. package/dist/controllers/portal_controller.d.ts +44 -4
  203. package/dist/controllers/portal_controller.js +215 -76
  204. package/dist/controllers/portal_controller.js.map +1 -1
  205. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  206. package/dist/controllers/preview_guard_controller.js +243 -116
  207. package/dist/controllers/preview_guard_controller.js.map +1 -1
  208. package/dist/controllers/progress_controller.d.ts +20 -19
  209. package/dist/controllers/progress_controller.js +391 -78
  210. package/dist/controllers/progress_controller.js.map +1 -1
  211. package/dist/controllers/radio_group_controller.d.ts +21 -9
  212. package/dist/controllers/radio_group_controller.js +278 -211
  213. package/dist/controllers/radio_group_controller.js.map +1 -1
  214. package/dist/controllers/range_slider_controller.d.ts +47 -6
  215. package/dist/controllers/range_slider_controller.js +220 -113
  216. package/dist/controllers/range_slider_controller.js.map +1 -1
  217. package/dist/controllers/rating_controller.d.ts +20 -4
  218. package/dist/controllers/rating_controller.js +458 -211
  219. package/dist/controllers/rating_controller.js.map +1 -1
  220. package/dist/controllers/read_more_controller.d.ts +2 -0
  221. package/dist/controllers/read_more_controller.js +138 -63
  222. package/dist/controllers/read_more_controller.js.map +1 -1
  223. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  224. package/dist/controllers/reading_progress_controller.js +166 -137
  225. package/dist/controllers/reading_progress_controller.js.map +1 -1
  226. package/dist/controllers/relative_time_controller.d.ts +20 -6
  227. package/dist/controllers/relative_time_controller.js +211 -112
  228. package/dist/controllers/relative_time_controller.js.map +1 -1
  229. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  230. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  231. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  232. package/dist/controllers/resizable_controller.d.ts +31 -2
  233. package/dist/controllers/resizable_controller.js +299 -150
  234. package/dist/controllers/resizable_controller.js.map +1 -1
  235. package/dist/controllers/roving_controller.d.ts +3 -3
  236. package/dist/controllers/roving_controller.js +49 -88
  237. package/dist/controllers/roving_controller.js.map +1 -1
  238. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  239. package/dist/controllers/scroll_area_controller.js +490 -246
  240. package/dist/controllers/scroll_area_controller.js.map +1 -1
  241. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  242. package/dist/controllers/scroll_restore_controller.js +14 -55
  243. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  244. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  245. package/dist/controllers/scroll_visibility_controller.js +432 -149
  246. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  247. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  248. package/dist/controllers/scrollspy_controller.js +193 -248
  249. package/dist/controllers/scrollspy_controller.js.map +1 -1
  250. package/dist/controllers/separator_controller.d.ts +20 -5
  251. package/dist/controllers/separator_controller.js +285 -142
  252. package/dist/controllers/separator_controller.js.map +1 -1
  253. package/dist/controllers/sidebar_controller.d.ts +65 -8
  254. package/dist/controllers/sidebar_controller.js +1069 -404
  255. package/dist/controllers/sidebar_controller.js.map +1 -1
  256. package/dist/controllers/skeleton_controller.d.ts +23 -0
  257. package/dist/controllers/skeleton_controller.js +259 -101
  258. package/dist/controllers/skeleton_controller.js.map +1 -1
  259. package/dist/controllers/slider_controller.d.ts +29 -2
  260. package/dist/controllers/slider_controller.js +187 -86
  261. package/dist/controllers/slider_controller.js.map +1 -1
  262. package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
  263. package/dist/controllers/smart_sticky_header_controller.js +173 -66
  264. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  265. package/dist/controllers/sortable_controller.d.ts +4 -4
  266. package/dist/controllers/sortable_controller.js +26 -125
  267. package/dist/controllers/sortable_controller.js.map +1 -1
  268. package/dist/controllers/spinner_controller.d.ts +39 -4
  269. package/dist/controllers/spinner_controller.js +288 -173
  270. package/dist/controllers/spinner_controller.js.map +1 -1
  271. package/dist/controllers/step_indicator_controller.d.ts +9 -4
  272. package/dist/controllers/step_indicator_controller.js +105 -46
  273. package/dist/controllers/step_indicator_controller.js.map +1 -1
  274. package/dist/controllers/stepper_controller.d.ts +14 -6
  275. package/dist/controllers/stepper_controller.js +120 -73
  276. package/dist/controllers/stepper_controller.js.map +1 -1
  277. package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
  278. package/dist/controllers/stick_to_bottom_controller.js +275 -140
  279. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  280. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  281. package/dist/controllers/sticky_observer_controller.js +220 -49
  282. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  283. package/dist/controllers/submit_once_controller.d.ts +24 -8
  284. package/dist/controllers/submit_once_controller.js +376 -227
  285. package/dist/controllers/submit_once_controller.js.map +1 -1
  286. package/dist/controllers/switch_controller.d.ts +18 -4
  287. package/dist/controllers/switch_controller.js +201 -84
  288. package/dist/controllers/switch_controller.js.map +1 -1
  289. package/dist/controllers/tabs_controller.d.ts +9 -6
  290. package/dist/controllers/tabs_controller.js +41 -30
  291. package/dist/controllers/tabs_controller.js.map +1 -1
  292. package/dist/controllers/tags_input_controller.d.ts +16 -3
  293. package/dist/controllers/tags_input_controller.js +293 -173
  294. package/dist/controllers/tags_input_controller.js.map +1 -1
  295. package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
  296. package/dist/controllers/textarea_autosize_controller.js +108 -74
  297. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  298. package/dist/controllers/theme_controller.d.ts +24 -11
  299. package/dist/controllers/theme_controller.js +140 -116
  300. package/dist/controllers/theme_controller.js.map +1 -1
  301. package/dist/controllers/time_picker_controller.d.ts +55 -5
  302. package/dist/controllers/time_picker_controller.js +331 -127
  303. package/dist/controllers/time_picker_controller.js.map +1 -1
  304. package/dist/controllers/toast_controller.d.ts +40 -16
  305. package/dist/controllers/toast_controller.js +244 -324
  306. package/dist/controllers/toast_controller.js.map +1 -1
  307. package/dist/controllers/toggle_group_controller.d.ts +14 -6
  308. package/dist/controllers/toggle_group_controller.js +252 -191
  309. package/dist/controllers/toggle_group_controller.js.map +1 -1
  310. package/dist/controllers/toolbar_controller.d.ts +8 -6
  311. package/dist/controllers/toolbar_controller.js +219 -120
  312. package/dist/controllers/toolbar_controller.js.map +1 -1
  313. package/dist/controllers/tooltip_controller.d.ts +49 -4
  314. package/dist/controllers/tooltip_controller.js +158 -124
  315. package/dist/controllers/tooltip_controller.js.map +1 -1
  316. package/dist/controllers/transition_controller.d.ts +22 -4
  317. package/dist/controllers/transition_controller.js +123 -119
  318. package/dist/controllers/transition_controller.js.map +1 -1
  319. package/dist/controllers/tree_view_controller.d.ts +22 -12
  320. package/dist/controllers/tree_view_controller.js +283 -369
  321. package/dist/controllers/tree_view_controller.js.map +1 -1
  322. package/dist/index.d.ts +10 -5
  323. package/dist/index.js +8573 -10410
  324. package/dist/index.js.map +1 -1
  325. package/dist/inspector/cli.d.ts +38 -14
  326. package/dist/inspector/cli.js +108 -40
  327. package/dist/inspector/cli.js.map +1 -1
  328. package/dist/inspector/cli_bin.js +145 -64
  329. package/dist/inspector/cli_bin.js.map +1 -1
  330. package/dist/inspector/examples.json +20 -20
  331. package/dist/inspector/manifest.json +802 -101
  332. package/dist/positioning/index.d.ts +15 -7
  333. package/dist/positioning/index.js +154 -47
  334. package/dist/positioning/index.js.map +1 -1
  335. package/package.json +11 -10
  336. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  337. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,8 +1,12 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/frame_loading_controller.ts
3
+ function fillTemplate(template, values) {
4
+ return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
5
+ const replacement = Object.hasOwn(values, name) ? values[name] : void 0;
6
+ return replacement === void 0 ? match : String(replacement);
7
+ });
8
+ }
4
9
 
5
- // src/utils/announce.ts
6
10
  function announce(message, options = {}) {
7
11
  const text = message.trim();
8
12
  if (text.length === 0) return;
@@ -12,148 +16,221 @@ function announce(message, options = {}) {
12
16
  })
13
17
  );
14
18
  }
15
- function fillTemplate(template, values) {
16
- return template.replace(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g, (match, name) => {
17
- const replacement = values[name];
18
- return replacement === void 0 ? match : String(replacement);
19
- });
19
+
20
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
21
+ if (element.getAttribute(name) === value) return false;
22
+ if (value === null) element.removeAttribute(name);
23
+ else element.setAttribute(name, value);
24
+ return true;
20
25
  }
21
26
 
22
- // src/utils/before_cache_reset.ts
23
- var BeforeCacheReset = class _BeforeCacheReset {
24
- /** Every subscribed instance, iterated by the one shared document listener. */
25
- static #subscribers = /* @__PURE__ */ new Set();
26
- /** The shared listener; installed while at least one instance is subscribed. */
27
- static #onBeforeCache = () => {
28
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
29
- };
30
- #rewind;
31
- /** @param rewind - the pass that returns this controller's state to its initial form. */
32
- constructor(rewind) {
33
- this.#rewind = rewind;
34
- }
35
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
36
- activate() {
37
- const first = _BeforeCacheReset.#subscribers.size === 0;
38
- _BeforeCacheReset.#subscribers.add(this);
39
- if (first) {
40
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
27
+ function sharedRegistry(key, create) {
28
+ const symbol = Symbol.for(key);
29
+ const scope = globalThis;
30
+ const existing = scope[symbol];
31
+ if (existing !== void 0) return existing;
32
+ const registry2 = create();
33
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
34
+ return registry2;
35
+ }
36
+
37
+ var registry = sharedRegistry(
38
+ "stimeo-ui.attribute-lease.registry.v1",
39
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
40
+ );
41
+ function isAttributeValue(value) {
42
+ return value === null || typeof value === "string";
43
+ }
44
+ function readLeaseRecord(element, record) {
45
+ try {
46
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
47
+ if (!Array.isArray(parsed)) return void 0;
48
+ const [original, written, ...rest] = parsed;
49
+ const linked = rest[0] !== false;
50
+ const beneath = linked ? rest : rest.slice(1);
51
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
52
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
53
+ } catch {
54
+ return void 0;
55
+ }
56
+ }
57
+ function attributeStack(element, attribute) {
58
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
59
+ registry.stacks.set(element, byAttribute);
60
+ const known = byAttribute.get(attribute);
61
+ if (known) return known;
62
+ const found = [];
63
+ for (const record of registry.names.get(attribute) ?? []) {
64
+ const read = readLeaseRecord(element, record);
65
+ if (read) found.push({ ...read, record });
66
+ }
67
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
68
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
69
+ lease: null,
70
+ record,
71
+ written,
72
+ original,
73
+ linked: linked && index > 0
74
+ }));
75
+ byAttribute.set(attribute, stack);
76
+ return stack;
77
+ }
78
+ function findHold(stack, lease, record) {
79
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
80
+ }
81
+ var AttributeLease = class {
82
+ #attribute;
83
+ #record;
84
+ #base;
85
+ #preserveAuthor;
86
+ #held = /* @__PURE__ */ new Set();
87
+ constructor(attribute, owner, options = {}) {
88
+ this.#attribute = attribute;
89
+ this.#record = `data-${owner}-${attribute}-lease`;
90
+ this.#base = options.base === true;
91
+ this.#preserveAuthor = options.preserveAuthor === true;
92
+ const names = registry.names;
93
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
94
+ }
95
+ write(element, value) {
96
+ const stack = attributeStack(element, this.#attribute);
97
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
98
+ this.#held.add(element);
99
+ const shows = !this.#covered(element, stack, hold);
100
+ hold.written = value;
101
+ this.#mark(element, stack);
102
+ if (shows) this.#reflect(element, value);
103
+ }
104
+ return(element) {
105
+ const held = this.#held.delete(element);
106
+ const known = registry.stacks.get(element)?.has(this.#attribute);
107
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
108
+ const hold = this.#take(stack);
109
+ if (!hold) return false;
110
+ const carries = element.getAttribute(this.#attribute) === hold.written;
111
+ const owns = carries && !this.#covered(element, stack, hold);
112
+ const index = stack.indexOf(hold);
113
+ stack.splice(index, 1);
114
+ const above = stack[index];
115
+ if (above) above.linked &&= hold.linked;
116
+ this.#mark(element, stack);
117
+ if (!owns) return true;
118
+ const below = hold.linked ? stack[index - 1] : void 0;
119
+ this.#reflect(element, below ? below.written : hold.original);
120
+ return true;
121
+ }
122
+ returnAll() {
123
+ for (const element of Array.from(this.#held)) this.return(element);
124
+ }
125
+ #take(stack) {
126
+ const hold = findHold(stack, this, this.#record);
127
+ if (hold) hold.lease = this;
128
+ return hold;
129
+ }
130
+ #acquire(element, stack) {
131
+ const current = element.getAttribute(this.#attribute);
132
+ const top = stack.at(-1);
133
+ const bottom = stack[0];
134
+ const over = top?.written === current ? top : void 0;
135
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
136
+ const hold = {
137
+ lease: this,
138
+ record: this.#record,
139
+ written: current,
140
+ original: from ? from.original : current,
141
+ linked: !this.#base && over !== void 0
142
+ };
143
+ if (this.#base && bottom) {
144
+ bottom.linked = true;
145
+ stack.unshift(hold);
146
+ } else stack.push(hold);
147
+ return hold;
148
+ }
149
+ #covered(element, stack, hold) {
150
+ const current = element.getAttribute(this.#attribute);
151
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
152
+ }
153
+ #mark(element, stack) {
154
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
155
+ stack.forEach((hold, index) => {
156
+ const names = stack.slice(0, index).map((under) => under.record);
157
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
158
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
159
+ records.set(
160
+ hold.record,
161
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
162
+ );
163
+ });
164
+ for (const [name, recorded] of records) {
165
+ setAttributeIfChanged(element, name, recorded);
41
166
  }
42
167
  }
43
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
44
- deactivate() {
45
- _BeforeCacheReset.#subscribers.delete(this);
46
- if (_BeforeCacheReset.#subscribers.size > 0) return;
47
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
168
+ #reflect(element, value) {
169
+ setAttributeIfChanged(element, this.#attribute, value);
48
170
  }
49
171
  };
50
172
 
51
- // src/utils/detach_gate.ts
52
173
  var DetachGate = class _DetachGate {
53
- /** Set while a probe is queued, waiting for a reconnect to cancel it. */
54
174
  #pending = false;
55
- /**
56
- * True while a probe is queued — the last disconnect was ambiguous and no
57
- * reconnect has cancelled it yet. Read it from `connect()` to tell the
58
- * reconnect half of an in-page move from a first connect: a controller whose
59
- * initialisation restarts a measurement (a min-duration floor, an elapsed
60
- * counter) must skip it for the move, where nothing actually restarted.
61
- */
62
175
  get pending() {
63
176
  return this.#pending;
64
177
  }
65
- /**
66
- * True when the disconnect is definitely a real detach — the element left
67
- * the document, or `data-controller` no longer lists the identifier. False
68
- * means ambiguous (in-page move or observed-root exit), NOT "alive".
69
- */
70
178
  static isDetached(host) {
71
- if (!host.element.isConnected) return true;
72
- const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
73
- return !tokens.includes(host.identifier);
74
- }
75
- /**
76
- * Call from `disconnect()`: runs `teardown` synchronously on a definite
77
- * detach (fast path), otherwise defers it one microtask — a reconnect
78
- * ({@link cancel} from `connect()`) keeps the state, no reconnect runs it.
79
- * One microtask is the whole probe window: Stimulus reconnects a moved
80
- * element within the same mutation batch, before the checkpoint drains.
81
- */
179
+ return !host.element.isConnected || !_DetachGate.#listed(host);
180
+ }
82
181
  disconnected(host, teardown) {
83
- if (_DetachGate.isDetached(host)) {
182
+ const carried = _DetachGate.#carried(host);
183
+ if (!carried && _DetachGate.isDetached(host)) {
84
184
  this.#pending = false;
85
185
  teardown();
86
186
  return;
87
187
  }
88
188
  this.#pending = true;
89
- queueMicrotask(() => {
189
+ const probe = () => {
90
190
  if (!this.#pending) return;
91
191
  this.#pending = false;
92
192
  teardown();
93
- });
193
+ };
194
+ if (carried) setTimeout(probe, 0);
195
+ else queueMicrotask(probe);
196
+ }
197
+ static #carried(host) {
198
+ const element = host.element;
199
+ return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
200
+ }
201
+ static #listed(host) {
202
+ const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
203
+ return tokens.includes(host.identifier);
94
204
  }
95
- /**
96
- * Disarms a pending probe. Call from `connect()` (the reconnect that proves
97
- * an in-page move) and from the head of any teardown path not routed through
98
- * {@link disconnected} (disabled-toggle, Escape), so an orphaned probe can
99
- * never run the teardown a second time.
100
- */
101
205
  cancel() {
102
206
  this.#pending = false;
103
207
  }
104
208
  };
105
209
 
106
- // src/utils/listener_set.ts
107
210
  var ListenerSet = class {
108
- /** The generation every `add` joins until the next `dispose()`. */
109
211
  #abort = new AbortController();
110
- /**
111
- * Attaches `handler` to the open generation, exactly as the caller spelled it.
112
- *
113
- * The set supplies the signal, so `options` carries everything else the DOM
114
- * accepts — `capture` included, which has to match at release time and no
115
- * longer has a second place to drift from.
116
- */
117
212
  add(target, type, handler, options) {
118
213
  target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
119
214
  }
120
- /**
121
- * Releases every listener of the open generation, synchronously, and opens the
122
- * next one. Idempotent, and safe before anything has been added.
123
- */
124
215
  dispose() {
125
216
  this.#abort.abort();
126
217
  this.#abort = new AbortController();
127
218
  }
128
219
  };
129
220
 
130
- // src/utils/min_duration_floor.ts
131
221
  var MinDurationFloor = class {
132
222
  #timers;
133
- /** Pending finish timer id, or `null` when nothing is held back. */
134
223
  #timerId = null;
135
- /** Epoch ms the floor is measured from. */
136
224
  #since = 0;
137
- /** @param timers - the controller's registry; the floor schedules into it. */
138
225
  constructor(timers) {
139
226
  this.#timers = timers;
140
227
  }
141
- /** Starts the floor: call when the state being held becomes visible. */
142
228
  begin() {
143
229
  this.#since = Date.now();
144
230
  }
145
- /** True while a finish is held back waiting for the floor to elapse. */
146
231
  get pending() {
147
232
  return this.#timerId !== null;
148
233
  }
149
- /**
150
- * Runs `finish` once the floor has elapsed, immediately when it already has.
151
- *
152
- * A held-back finish is **replaced**, never stacked: only the most recently
153
- * queued id is cancellable, so a second timer would outlive every cancel and
154
- * end a state that has since restarted. Controllers that want the first signal
155
- * to win guard on {@link pending} before calling.
156
- */
157
234
  schedule(minDuration, finish) {
158
235
  this.cancel();
159
236
  const remaining = minDuration - (Date.now() - this.#since);
@@ -166,7 +243,6 @@ var MinDurationFloor = class {
166
243
  finish();
167
244
  }
168
245
  }
169
- /** Drops a held-back finish. Safe when none is queued, or after a bulk clear. */
170
246
  cancel() {
171
247
  if (this.#timerId !== null) {
172
248
  this.#timers.clear(this.#timerId);
@@ -175,43 +251,70 @@ var MinDurationFloor = class {
175
251
  }
176
252
  };
177
253
 
178
- // src/utils/safe_timeout.ts
254
+ var MAX_TIMER_DELAY_MS = 2147483647;
255
+
256
+ var NUMBER_BOUNDS = {
257
+ timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
258
+ function matchesNumberBounds(value, bounds) {
259
+ if (!Number.isFinite(value)) {
260
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
261
+ if (direction === null) return false;
262
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
263
+ }
264
+ if (bounds.min !== void 0 && value < bounds.min) return false;
265
+ if (bounds.max !== void 0 && value > bounds.max) return false;
266
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
267
+ if (bounds.integer && !Number.isInteger(value)) return false;
268
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
269
+ return true;
270
+ }
271
+
272
+ function readNumber(raw, fallback, bounds) {
273
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
274
+ }
275
+
276
+ var NumberValueReader = class {
277
+ #lastRejected = /* @__PURE__ */ new Map();
278
+ read(owner, name, raw, fallback, bounds) {
279
+ const resolved = readNumber(raw, fallback, bounds);
280
+ if (matchesNumberBounds(raw, bounds)) {
281
+ this.#lastRejected.delete(name);
282
+ return resolved;
283
+ }
284
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
285
+ const literal = owner.element.getAttribute(attribute);
286
+ if (literal === null) {
287
+ this.#lastRejected.delete(name);
288
+ return resolved;
289
+ }
290
+ if (this.#lastRejected.get(name) !== literal) {
291
+ this.#lastRejected.set(name, literal);
292
+ console.warn(
293
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
294
+ );
295
+ }
296
+ return resolved;
297
+ }
298
+ };
299
+
179
300
  var TimerRegistry = class {
180
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
181
301
  ids = /* @__PURE__ */ new Set();
182
- /**
183
- * Cancels a single tracked timer.
184
- *
185
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
186
- * registry), so callers can clear defensively without guarding.
187
- */
188
302
  clear(id) {
189
303
  if (this.ids.delete(id)) {
190
304
  this.cancel(id);
191
305
  }
192
306
  }
193
- /**
194
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
195
- * to guarantee no timer outlives the element.
196
- */
197
307
  clearAll() {
198
308
  for (const id of this.ids) {
199
309
  this.cancel(id);
200
310
  }
201
311
  this.ids.clear();
202
312
  }
203
- /** Number of timers currently tracked (pending). */
204
313
  get size() {
205
314
  return this.ids.size;
206
315
  }
207
316
  };
208
317
  var SafeTimeout = class extends TimerRegistry {
209
- /**
210
- * Schedules `callback` after `delay` ms and returns the timer id.
211
- *
212
- * The id is removed from the registry automatically when the timeout fires,
213
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
214
- */
215
318
  set(callback, delay) {
216
319
  const id = this.schedule(() => {
217
320
  this.ids.delete(id);
@@ -228,8 +331,8 @@ var SafeTimeout = class extends TimerRegistry {
228
331
  }
229
332
  };
230
333
 
231
- // src/controllers/frame_loading_controller.ts
232
- var FrameLoadingController = class extends Controller {
334
+ var FrameLoadingController = class _FrameLoadingController extends Controller {
335
+ #numbers = new NumberValueReader();
233
336
  static targets = ["content", "skeleton", "overlay"];
234
337
  static values = {
235
338
  announceText: { type: String, default: "" },
@@ -237,147 +340,109 @@ var FrameLoadingController = class extends Controller {
237
340
  minDuration: { type: Number, default: 0 },
238
341
  restoreFocus: { type: Boolean, default: true }
239
342
  };
343
+ static valueConstraints = {
344
+ minDuration: NUMBER_BOUNDS.timer
345
+ };
240
346
  static events = ["start", "end", "reconcile"];
241
347
  #timeouts = new SafeTimeout();
242
348
  #floor = new MinDurationFloor(this.#timeouts);
243
349
  #gate = new DetachGate();
244
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
245
350
  #listeners = new ListenerSet();
351
+ #busy = new AttributeLease("aria-busy", this.identifier);
352
+ #loadingHook = new AttributeLease("data-frame-loading", this.identifier);
353
+ #hidden = new AttributeLease("hidden", this.identifier);
354
+ #inert = new AttributeLease("inert", this.identifier);
246
355
  #loading = false;
247
- /**
248
- * The optional targets this controller revealed, and the content it marked inert.
249
- * Held as references rather than re-resolved on the way out: a detach that keeps
250
- * the element takes the identifier off `data-controller` first, and a scope
251
- * without its identifier stops resolving targets — the elements to tidy would be
252
- * unreachable exactly when the tidying matters. They double as the ownership
253
- * marker, so a `hidden` or an `inert` the consumer wrote is never taken over.
254
- */
255
356
  #revealedSkeleton = null;
256
357
  #revealedOverlay = null;
257
358
  #inertTarget = null;
258
359
  #previousFocus = null;
259
- /** The id of the retreated element, used to re-find it if the load replaced it. */
260
360
  #previousFocusId = "";
261
361
  #onStart = () => {
262
362
  this.#floor.cancel();
263
363
  if (!this.#loading) this.#begin();
264
364
  };
265
- /**
266
- * @stimeoRuntimeOnly `minDuration` holds the loading state at least this long for the one load
267
- * that ended.
268
- */
269
365
  #onEnd = () => {
270
366
  if (!this.#loading) return;
271
- this.#floor.schedule(this.minDurationValue, () => this.#finish());
367
+ this.#floor.schedule(this.#safeMinDuration, () => this.#finish());
272
368
  };
273
369
  connect() {
274
370
  this.#gate.cancel();
275
- this.#beforeCache.activate();
276
371
  this.#listeners.add(this.element, "turbo:before-fetch-request", this.#onStart);
277
372
  this.#listeners.add(this.element, "turbo:frame-load", this.#onEnd);
278
373
  this.#listeners.add(this.element, "turbo:fetch-request-error", this.#onEnd);
374
+ if (this.#loading) return;
375
+ const stale = this.element.hasAttribute("data-frame-loading");
376
+ this.#returnCopied();
377
+ if (stale) this.dispatch("reconcile", { detail: {} });
378
+ }
379
+ #returnCopied() {
380
+ this.#busy.return(this.element);
381
+ this.#loadingHook.return(this.element);
382
+ for (const region of [...this.skeletonTargets, ...this.overlayTargets]) {
383
+ this.#hidden.return(region);
384
+ }
385
+ for (const content of this.contentTargets) this.#inert.return(content);
279
386
  }
280
387
  disconnect() {
281
388
  this.#listeners.dispose();
282
- this.#beforeCache.deactivate();
283
389
  this.#gate.disconnected(this, () => this.#teardown());
284
390
  }
285
- /**
286
- * Drops the held finish and the loading bookkeeping on a real detach, returning
287
- * the frame to its idle form. No reconnect is coming, so nothing is left that
288
- * could finish the load and clear the hooks — a detach that keeps the element
289
- * (a morph dropping the identifier, an exit from a scoped observed root) would
290
- * otherwise strand it busy and inert. Focus is left where it is: the element is
291
- * leaving this controller's care, and moving it now would be an unexplained jump.
292
- */
293
391
  #teardown() {
294
- this.#gate.cancel();
295
392
  this.#timeouts.clearAll();
296
- this.#floor.cancel();
297
393
  if (this.#loading) this.#rewindHooks();
298
394
  this.#loading = false;
299
395
  this.#previousFocus = null;
300
396
  }
301
- /**
302
- * Returns the frame to its idle form for the snapshot Turbo is about to take, so
303
- * a page reached with the Back button does not restore a frame that is busy and
304
- * inert with nothing left to finish it. State only — no `end` event and no focus
305
- * move, because the load did not actually complete.
306
- *
307
- * The load is abandoned rather than paused, so the flag and any finish the floor
308
- * still holds drop along with the hooks. A kept finish would surface after the
309
- * rewind as exactly the three things this pass exists to avoid — an `end`, a
310
- * completion announcement, and a focus move — and a kept flag would leave the
311
- * next fetch on a page that survives a cancelled visit skipping the loading
312
- * state, its idempotence guard already satisfied.
313
- */
314
- #rewindForCache() {
315
- if (!this.#loading) return;
316
- this.#loading = false;
317
- this.#floor.cancel();
318
- this.#rewindHooks();
319
- this.dispatch("reconcile", { detail: {} });
320
- }
321
- /**
322
- * Clears every hook the loading state writes. Shared by the three ways a load can
323
- * stop — completion, detach, snapshot — so none of them can drift into tidying
324
- * only part of it.
325
- */
326
397
  #rewindHooks() {
327
- this.element.removeAttribute("aria-busy");
328
- this.element.removeAttribute("data-frame-loading");
329
- if (this.#revealedSkeleton) this.#revealedSkeleton.hidden = true;
330
- if (this.#revealedOverlay) this.#revealedOverlay.hidden = true;
398
+ this.#busy.return(this.element);
399
+ this.#loadingHook.return(this.element);
400
+ if (this.#revealedSkeleton) this.#hidden.return(this.#revealedSkeleton);
401
+ if (this.#revealedOverlay) this.#hidden.return(this.#revealedOverlay);
331
402
  this.#revealedSkeleton = null;
332
403
  this.#revealedOverlay = null;
333
404
  this.#clearInert();
334
405
  }
335
- /**
336
- * Re-shows a `skeleton` that arrived mid-load. Turbo's frame renderer empties the
337
- * frame and re-inserts the response's children, so a response's authored (hidden)
338
- * skeleton can land while a later fetch is still running, and only the controller
339
- * knows the frame is still busy.
340
- */
341
406
  skeletonTargetConnected() {
342
407
  if (this.#loading) this.#revealSkeleton();
343
408
  }
344
- /** Re-shows an `overlay` that arrived mid-load — the same swap as the skeleton. */
409
+ skeletonTargetDisconnected(skeleton) {
410
+ if (this.#loading && !this.skeletonTargets.includes(skeleton)) this.#revealSkeleton();
411
+ }
345
412
  overlayTargetConnected() {
346
413
  if (this.#loading) this.#revealOverlay();
347
414
  }
348
- /**
349
- * Re-blocks a `content` that arrived mid-load, so the stale copy stays unusable.
350
- * The element that left is released first and ownership is then decided afresh, so
351
- * an `inert` the replacement authored stays the consumer's.
352
- */
415
+ overlayTargetDisconnected(overlay) {
416
+ if (this.#loading && !this.overlayTargets.includes(overlay)) this.#revealOverlay();
417
+ }
353
418
  contentTargetConnected() {
354
419
  if (!this.#loading) return;
355
420
  this.#clearInert();
356
421
  this.#applyInert();
357
422
  }
358
- /** Reveals the optional `skeleton`, noting it as this controller's to hide again. */
423
+ contentTargetDisconnected(content) {
424
+ if (!this.#loading || this.contentTargets.includes(content)) return;
425
+ this.#clearInert();
426
+ this.#applyInert();
427
+ }
359
428
  #revealSkeleton() {
360
- if (!this.hasSkeletonTarget) return;
361
- this.#revealedSkeleton = this.skeletonTarget;
362
- this.skeletonTarget.hidden = false;
429
+ const next = this.hasSkeletonTarget ? this.skeletonTarget : null;
430
+ this.#revealedSkeleton = this.#reveal(this.#revealedSkeleton, next);
363
431
  }
364
- /** Reveals the optional `overlay`, noting it as this controller's to hide again. */
365
432
  #revealOverlay() {
366
- if (!this.hasOverlayTarget) return;
367
- this.#revealedOverlay = this.overlayTarget;
368
- this.overlayTarget.hidden = false;
369
- }
370
- /**
371
- * Enters the loading state: hooks, skeleton/overlay, inert content, focus retreat.
372
- *
373
- * @stimeoRuntimeOnly `announceText` words the one announcement of this load and `restoreFocus`
374
- * decides whether focus leaves the frame it covers.
375
- */
433
+ const next = this.hasOverlayTarget ? this.overlayTarget : null;
434
+ this.#revealedOverlay = this.#reveal(this.#revealedOverlay, next);
435
+ }
436
+ #reveal(revealed, next) {
437
+ if (revealed && revealed !== next) this.#hidden.return(revealed);
438
+ if (next) this.#hidden.write(next, null);
439
+ return next;
440
+ }
376
441
  #begin() {
377
442
  this.#loading = true;
378
443
  this.#floor.begin();
379
- this.element.setAttribute("aria-busy", "true");
380
- this.element.setAttribute("data-frame-loading", "true");
444
+ this.#busy.write(this.element, "true");
445
+ this.#loadingHook.write(this.element, "true");
381
446
  this.#revealSkeleton();
382
447
  this.#revealOverlay();
383
448
  this.#applyInert();
@@ -385,12 +450,6 @@ var FrameLoadingController = class extends Controller {
385
450
  this.dispatch("start", { detail: {} });
386
451
  announce(fillTemplate(this.announceTextValue, {}));
387
452
  }
388
- /**
389
- * Leaves the loading state: restore hooks, hide skeleton/overlay, restore focus.
390
- *
391
- * @stimeoRuntimeOnly `announceReadyText` words the one announcement of this finished load and
392
- * `restoreFocus` decides whether focus goes back.
393
- */
394
453
  #finish() {
395
454
  this.#loading = false;
396
455
  this.#rewindHooks();
@@ -398,17 +457,15 @@ var FrameLoadingController = class extends Controller {
398
457
  this.dispatch("end", { detail: {} });
399
458
  announce(fillTemplate(this.announceReadyTextValue, {}));
400
459
  }
401
- /** Marks the content inert to block double-submits while stale (if we own it). */
402
460
  #applyInert() {
403
461
  if (!this.hasContentTarget || this.contentTarget.hasAttribute("inert")) return;
404
- this.contentTarget.setAttribute("inert", "");
462
+ this.#inert.write(this.contentTarget, "");
405
463
  this.#inertTarget = this.contentTarget;
406
464
  }
407
465
  #clearInert() {
408
- this.#inertTarget?.removeAttribute("inert");
466
+ if (this.#inertTarget) this.#inert.return(this.#inertTarget);
409
467
  this.#inertTarget = null;
410
468
  }
411
- /** Saves and blurs focus if it sits inside the frame about to go stale. */
412
469
  #retreatFocus() {
413
470
  this.#previousFocus = null;
414
471
  this.#previousFocusId = "";
@@ -420,13 +477,6 @@ var FrameLoadingController = class extends Controller {
420
477
  active.blur();
421
478
  }
422
479
  }
423
- /**
424
- * Restores focus after the load. The same node when it survived (e.g. a
425
- * non-replacing update), else the element re-rendered with the same id inside the
426
- * frame — Turbo frames typically re-emit the same controls. When neither is present
427
- * (an anonymous control was replaced) focus is left where the browser put it, to
428
- * avoid an unexpected jump (WCAG 3.2.x).
429
- */
430
480
  #restoreFocus() {
431
481
  const target = this.#previousFocus;
432
482
  const id = this.#previousFocusId;
@@ -442,8 +492,16 @@ var FrameLoadingController = class extends Controller {
442
492
  if (replacement && this.element.contains(replacement)) replacement.focus();
443
493
  }
444
494
  }
495
+ get #safeMinDuration() {
496
+ return this.#numbers.read(
497
+ this,
498
+ "minDuration",
499
+ this.minDurationValue,
500
+ _FrameLoadingController.values.minDuration.default,
501
+ _FrameLoadingController.valueConstraints.minDuration
502
+ );
503
+ }
445
504
  };
446
505
 
447
506
  export { FrameLoadingController };
448
- //# sourceMappingURL=frame_loading_controller.js.map
449
507
  //# sourceMappingURL=frame_loading_controller.js.map