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/file_dropzone_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,98 +16,197 @@ 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/element_part.ts
107
210
  function matchingPart(root, selector) {
108
211
  return root.matches(selector) ? root : root.querySelector(selector);
109
212
  }
@@ -122,7 +225,14 @@ function writeLabel(slot, text) {
122
225
  else slot.prepend(text);
123
226
  }
124
227
 
125
- // src/utils/focus_candidate.ts
228
+ function ownerIndex(candidates, node) {
229
+ if (!(node instanceof Node)) return -1;
230
+ return candidates.findIndex((candidate) => candidate.contains(node));
231
+ }
232
+ function ownerOf(candidates, node) {
233
+ return candidates[ownerIndex(candidates, node)] ?? null;
234
+ }
235
+
126
236
  function inheritsFieldsetDisabled(control) {
127
237
  let fieldset = control.closest("fieldset[disabled]");
128
238
  while (fieldset) {
@@ -133,12 +243,54 @@ function inheritsFieldsetDisabled(control) {
133
243
  return false;
134
244
  }
135
245
 
136
- // src/utils/target_selector.ts
246
+ var NUMBER_BOUNDS = {
247
+ nonNegative: { finite: true, min: 0 }};
248
+ function matchesNumberBounds(value, bounds) {
249
+ if (!Number.isFinite(value)) {
250
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
251
+ if (direction === null) return false;
252
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
253
+ }
254
+ if (bounds.min !== void 0 && value < bounds.min) return false;
255
+ if (bounds.max !== void 0 && value > bounds.max) return false;
256
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
257
+ if (bounds.integer && !Number.isInteger(value)) return false;
258
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
259
+ return true;
260
+ }
261
+
262
+ function readNumber(raw, fallback, bounds) {
263
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
264
+ }
265
+
266
+ var NumberValueReader = class {
267
+ #lastRejected = /* @__PURE__ */ new Map();
268
+ read(owner, name, raw, fallback, bounds) {
269
+ const resolved = readNumber(raw, fallback, bounds);
270
+ if (matchesNumberBounds(raw, bounds)) {
271
+ this.#lastRejected.delete(name);
272
+ return resolved;
273
+ }
274
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
275
+ const literal = owner.element.getAttribute(attribute);
276
+ if (literal === null) {
277
+ this.#lastRejected.delete(name);
278
+ return resolved;
279
+ }
280
+ if (this.#lastRejected.get(name) !== literal) {
281
+ this.#lastRejected.set(name, literal);
282
+ console.warn(
283
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
284
+ );
285
+ }
286
+ return resolved;
287
+ }
288
+ };
289
+
137
290
  function targetSelector(identifier, name) {
138
291
  return `[data-${identifier}-target~="${name}"]`;
139
292
  }
140
293
 
141
- // src/utils/template_row.ts
142
294
  function cloneTemplateRoot(template) {
143
295
  const root = template.content.firstElementChild;
144
296
  return root instanceof HTMLElement ? root.cloneNode(true) : null;
@@ -149,22 +301,15 @@ var TemplateRow = class {
149
301
  constructor(options) {
150
302
  this.#options = options;
151
303
  }
152
- /** The attribute selector for one declared part, in this controller's namespace. */
153
304
  selector(name) {
154
305
  return targetSelector(this.#options.identifier, name);
155
306
  }
156
- /** Re-arms the once-per-connection diagnostic. */
157
307
  connect() {
158
308
  this.#warned = false;
159
309
  }
160
- /**
161
- * Names one missing part on the console, at most once per connection, and
162
- * returns `null` so a caller can hand it straight back.
163
- */
164
310
  report(missing) {
165
311
  return this.#say(`lacks ${missing}`);
166
312
  }
167
- /** Writes one diagnostic per connection, saying what the template got wrong. */
168
313
  #say(problem) {
169
314
  if (this.#warned) return null;
170
315
  this.#warned = true;
@@ -172,10 +317,6 @@ var TemplateRow = class {
172
317
  console.warn(`Stimeo UI: "${identifier}" ${outcome} because its ${noun} ${problem}.`);
173
318
  return null;
174
319
  }
175
- /**
176
- * Clones the row and resolves its parts, or names what the template got wrong
177
- * and returns `null`. `values` fills the button's authored `aria-label`.
178
- */
179
320
  instantiate(template, values) {
180
321
  const { root: rootName, required, optional, button: buttonName } = this.#options;
181
322
  const root = cloneTemplateRoot(template);
@@ -203,7 +344,6 @@ var TemplateRow = class {
203
344
  button.setAttribute("aria-label", fillTemplate(name, values));
204
345
  return { root, slots, button };
205
346
  }
206
- /** The row may be its own part; otherwise the first descendant carrying the target. */
207
347
  #resolve(root, name) {
208
348
  return matchingPart(root, this.selector(name));
209
349
  }
@@ -212,11 +352,11 @@ var TemplateRow = class {
212
352
  }
213
353
  };
214
354
 
215
- // src/controllers/file_dropzone_controller.ts
216
355
  var DRAGOVER_ATTRIBUTE = "data-dragover";
217
356
  var INVALID_ATTRIBUTE = "invalid";
218
- var FileDropzoneController = class extends Controller {
219
- /** The hook above, in the namespace this controller is registered under. */
357
+ var GENERATED_ATTRIBUTE = "generated";
358
+ var FileDropzoneController = class _FileDropzoneController extends Controller {
359
+ #numbers = new NumberValueReader();
220
360
  get #invalidAttribute() {
221
361
  return `data-${this.identifier}-${INVALID_ATTRIBUTE}`;
222
362
  }
@@ -243,13 +383,17 @@ var FileDropzoneController = class extends Controller {
243
383
  announceRejectedDuplicateText: { type: String, default: "" },
244
384
  announceRejectedCountText: { type: String, default: "" }
245
385
  };
386
+ static valueConstraints = {
387
+ maxSize: NUMBER_BOUNDS.nonNegative,
388
+ maxFiles: NUMBER_BOUNDS.nonNegative
389
+ };
246
390
  static actions = ["onChange", "onDragLeave", "onDragOver", "onDrop", "openDialog"];
247
391
  static events = ["change", "reject", "reconcile"];
248
- /** Selected files paired with their rendered item and any preview objectURL. */
249
392
  #entries = [];
250
- /** Whether a drag is currently over the zone; the source for `data-dragover`. */
251
393
  #dragging = false;
252
- /** Builds one preview item from the authored template and owns its diagnostic. */
394
+ #invalid = false;
395
+ #dragoverHook = new AttributeLease(DRAGOVER_ATTRIBUTE, this.identifier);
396
+ #invalidHook = new AttributeLease(this.#invalidAttribute, this.identifier);
253
397
  #rows = new TemplateRow({
254
398
  identifier: this.identifier,
255
399
  root: "item",
@@ -260,84 +404,97 @@ var FileDropzoneController = class extends Controller {
260
404
  noun: "item template"
261
405
  });
262
406
  #gate = new DetachGate();
263
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
264
- /** Subscribes to the Turbo cache rewind and re-arms the template diagnostic. */
407
+ #listWatch = new MutationObserver((records) => {
408
+ const lost = records.some(
409
+ (record) => Array.from(record.removedNodes).some(
410
+ (node) => this.#entries.some((entry) => entry.item === node)
411
+ )
412
+ );
413
+ if (!lost) return;
414
+ this.#rehome();
415
+ this.#paintZone();
416
+ });
417
+ #connected = false;
418
+ get #generatedAttribute() {
419
+ return `data-${this.identifier}-${GENERATED_ATTRIBUTE}`;
420
+ }
265
421
  connect() {
422
+ const moved = this.#gate.pending;
266
423
  this.#gate.cancel();
424
+ this.#connected = true;
267
425
  this.#rows.connect();
268
- this.#beforeCache.activate();
426
+ if (!moved) this.#discardInherited();
269
427
  }
270
- /**
271
- * Releases the delegated listeners. The selection itself survives an in-page
272
- * move and is released only once the gate proves a real detach — revoking a
273
- * preview URL on a move would leave a live item pointing at a dead `blob:`.
274
- */
275
428
  disconnect() {
429
+ this.#connected = false;
276
430
  for (const list of this.listTargets) list.removeEventListener("click", this.#onItemClick);
277
- this.#beforeCache.deactivate();
431
+ this.#listWatch.disconnect();
278
432
  this.#gate.disconnected(this, () => this.#teardown());
279
433
  }
280
- /**
281
- * Binds removal and restores client-only previews for every list this controller
282
- * renders into — the one present at connect and any Turbo puts in its place.
283
- * This is the only place the listener is attached, so the pair with
284
- * {@link listTargetDisconnected} keeps it from outliving the element it is on.
285
- */
434
+ #discardInherited() {
435
+ let discarded = false;
436
+ for (const list of this.listTargets) {
437
+ for (const item of Array.from(list.children)) {
438
+ if (!item.hasAttribute(this.#generatedAttribute)) continue;
439
+ item.remove();
440
+ discarded = true;
441
+ }
442
+ }
443
+ for (const zone of this.zoneTargets) {
444
+ this.#dragoverHook.return(zone);
445
+ this.#invalidHook.return(zone);
446
+ }
447
+ if (!discarded) return;
448
+ this.#dragging = false;
449
+ this.#invalid = false;
450
+ this.#syncInput();
451
+ this.dispatch("reconcile", { detail: { files: this.#files } });
452
+ }
286
453
  listTargetConnected(list) {
287
- this.#bindList(list);
454
+ list.addEventListener("click", this.#onItemClick);
455
+ this.#listWatch.observe(list, { childList: true });
456
+ this.#rehome();
288
457
  }
289
- /** Releases only the list target that actually disconnected. */
290
458
  listTargetDisconnected(list) {
291
459
  list.removeEventListener("click", this.#onItemClick);
460
+ if (this.#connected) this.#rehome();
292
461
  }
293
- /** Binds delegated removal once and moves live preview items into the current list. */
294
- #bindList(list) {
295
- list.addEventListener("click", this.#onItemClick);
296
- for (const entry of this.#entries) {
297
- if (!list.contains(entry.item)) list.appendChild(entry.item);
298
- }
462
+ inputTargetConnected() {
463
+ if (this.#connected) this.#syncInput();
464
+ }
465
+ inputTargetDisconnected() {
466
+ if (this.#connected) this.#syncInput();
467
+ }
468
+ zoneTargetConnected() {
469
+ if (this.#connected) this.#paintZone();
470
+ }
471
+ zoneTargetDisconnected(zone) {
472
+ if (this.zoneTargets.includes(zone)) return;
473
+ this.#dragoverHook.return(zone);
474
+ this.#invalidHook.return(zone);
475
+ if (this.#connected) this.#paintZone();
299
476
  }
300
- /** Opens the native file dialog. Bound via `data-action` (trigger click). */
301
477
  openDialog() {
302
478
  if (this.#isDisabled) return;
303
479
  this.inputTarget.click();
304
480
  }
305
- /**
306
- * Adds the files chosen through the native dialog. The input holds only what
307
- * the dialog just returned, so the accepted set is written back over it once
308
- * the batch is validated.
309
- */
310
- onChange() {
311
- const files = this.inputTarget.files;
481
+ onChange(event) {
482
+ const files = (ownerOf(this.inputTargets, event?.target) ?? this.inputTarget).files;
312
483
  if (files) this.#addFiles(files);
313
- else this.#syncInput();
314
- }
315
- /**
316
- * Marks the zone as a drop target and announces the affordance once per drag.
317
- * Bound via `data-action` (dragover). Leaving the default alone is what refuses
318
- * the drop, so a disabled field and a drag an inner dropzone already claimed
319
- * both fall through untouched.
320
- */
484
+ }
321
485
  onDragOver(event) {
322
486
  if (event.defaultPrevented || this.#isDisabled) return;
323
487
  event.preventDefault();
324
488
  if (this.#dragging) return;
325
489
  this.#dragging = true;
326
- if (this.hasZoneTarget) this.zoneTarget.setAttribute(DRAGOVER_ATTRIBUTE, "");
490
+ this.#paintZone();
327
491
  announce(fillTemplate(this.announceDragTextValue, { total: this.#entries.length }));
328
492
  }
329
- /**
330
- * Clears the drag-over flag when the pointer leaves the zone. Bound via
331
- * `data-action` (dragleave). `dragleave` bubbles from every descendant the
332
- * pointer crosses, so the flag only drops when the element being entered is
333
- * outside the zone (or there is none, the pointer having left the window).
334
- */
335
493
  onDragLeave(event) {
336
494
  const next = event.relatedTarget;
337
495
  if (this.hasZoneTarget && next instanceof Node && this.zoneTarget.contains(next)) return;
338
496
  this.#endDrag();
339
497
  }
340
- /** Accepts dropped files, clearing the drag-over state. Bound via `data-action` (drop). */
341
498
  onDrop(event) {
342
499
  if (event.defaultPrevented) return;
343
500
  this.#endDrag();
@@ -345,15 +502,6 @@ var FileDropzoneController = class extends Controller {
345
502
  event.preventDefault();
346
503
  if (event.dataTransfer?.files) this.#addFiles(event.dataTransfer.files);
347
504
  }
348
- /**
349
- * Removes the file whose remove button was clicked. Delegated on the list
350
- * container rather than bound per item via `data-action`, so it works the instant
351
- * an item is appended without waiting on Stimulus to wire a freshly created element.
352
- * Only the item's declared `remove` target counts, so an authored second control
353
- * inside an item does what it says instead of silently discarding the file. The
354
- * button has to belong to a tracked item, which is what makes a list this
355
- * controller no longer renders into inert — its items moved out with it.
356
- */
357
505
  #onItemClick = (event) => {
358
506
  const button = event.target.closest(
359
507
  `button${this.#rows.selector("remove")}`
@@ -361,15 +509,9 @@ var FileDropzoneController = class extends Controller {
361
509
  const index = this.#entries.findIndex((entry) => entry.item.contains(button));
362
510
  if (index !== -1) this.#removeAt(index);
363
511
  };
364
- /**
365
- * Validates each incoming file, renders the accepted ones, and reports the batch.
366
- *
367
- * @stimeoRuntimeOnly The limits decide which files of this one drop are taken, and the texts word
368
- * the announcements it makes.
369
- */
370
512
  #addFiles(files) {
371
513
  this.#rehome();
372
- if (this.hasZoneTarget) this.zoneTarget.removeAttribute(this.#invalidAttribute);
514
+ this.#invalid = false;
373
515
  const rejected = /* @__PURE__ */ new Map();
374
516
  const turnedAway = [];
375
517
  let addedName = "";
@@ -377,7 +519,7 @@ var FileDropzoneController = class extends Controller {
377
519
  for (const file of Array.from(files)) {
378
520
  const reason = this.#validate(file);
379
521
  if (reason !== null) {
380
- if (this.hasZoneTarget) this.zoneTarget.setAttribute(this.#invalidAttribute, "");
522
+ this.#invalid = true;
381
523
  const batch = rejected.get(reason);
382
524
  if (batch) batch.count += 1;
383
525
  else rejected.set(reason, { name: file.name, count: 1 });
@@ -388,6 +530,7 @@ var FileDropzoneController = class extends Controller {
388
530
  if (added === 0) addedName = file.name;
389
531
  added += 1;
390
532
  }
533
+ this.#paintZone();
391
534
  this.#syncInput();
392
535
  if (added > 0) {
393
536
  this.#announce(this.announceAddedTextValue, addedName, added);
@@ -400,14 +543,9 @@ var FileDropzoneController = class extends Controller {
400
543
  this.#announce(this.#rejectText(reason), batch.name, batch.count);
401
544
  }
402
545
  }
403
- /**
404
- * Returns the rejection reason for `file`, or `null` when it is acceptable.
405
- * The file's own defects are decided first, so a full list still tells the user
406
- * which files it would never have taken.
407
- */
408
546
  #validate(file) {
409
547
  if (!this.#matchesAccept(file)) return "type";
410
- if (this.maxSizeValue > 0 && file.size > this.maxSizeValue) return "size";
548
+ if (this.#safeMaxSize > 0 && file.size > this.#safeMaxSize) return "size";
411
549
  if (!this.allowDuplicatesValue && this.#entries.some((entry) => this.#isSame(entry.file, file))) {
412
550
  return "duplicate";
413
551
  }
@@ -415,21 +553,9 @@ var FileDropzoneController = class extends Controller {
415
553
  if (limit > 0 && this.#entries.length >= limit) return "count";
416
554
  return null;
417
555
  }
418
- /**
419
- * Whether two files are the same selection. `File` objects from separate picks
420
- * are never the same reference, so identity is the triple the platform exposes.
421
- */
422
556
  #isSame(a, b) {
423
557
  return a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;
424
558
  }
425
- /**
426
- * Builds one preview item (name, optional thumbnail, remove button) and reports
427
- * whether it was rendered.
428
- *
429
- * A refused row leaves the addition a no-op — nothing about the selection, the
430
- * native input, the announcements, or the events changes — and the row reports
431
- * why on the console once per connection.
432
- */
433
559
  #appendFile(file) {
434
560
  if (!this.hasListTarget) {
435
561
  this.#rows.report('a "list" target to render into');
@@ -442,6 +568,7 @@ var FileDropzoneController = class extends Controller {
442
568
  const row = this.#rows.instantiate(this.itemTemplateTarget, { name: file.name });
443
569
  if (!row) return false;
444
570
  const item = row.root;
571
+ item.setAttribute(this.#generatedAttribute, "");
445
572
  writeLabel(row.slots.name, file.name);
446
573
  const thumb = row.slots.thumb;
447
574
  let url;
@@ -457,7 +584,6 @@ var FileDropzoneController = class extends Controller {
457
584
  this.#entries.push({ file, item, url });
458
585
  return true;
459
586
  }
460
- /** Removes entry `index`, revokes its preview, and re-homes focus. */
461
587
  #removeAt(index) {
462
588
  const entry = this.#entries[index];
463
589
  if (!entry) return;
@@ -474,7 +600,6 @@ var FileDropzoneController = class extends Controller {
474
600
  (buttons[index] ?? buttons[buttons.length - 1])?.focus();
475
601
  }
476
602
  }
477
- /** Whether `file` satisfies the input's `accept` list (empty accepts all). */
478
603
  #matchesAccept(file) {
479
604
  const accept = this.inputTarget.accept.trim();
480
605
  if (accept === "") return true;
@@ -488,15 +613,9 @@ var FileDropzoneController = class extends Controller {
488
613
  return type === token;
489
614
  });
490
615
  }
491
- /**
492
- * Sends one consumer-worded message to the page's shared announcer. `{name}` is
493
- * the file the message is about, `{count}` how many files it covers, and
494
- * `{total}` how many are selected once the batch has settled.
495
- */
496
616
  #announce(template, name, count) {
497
617
  announce(fillTemplate(template, { name, count, total: this.#entries.length }));
498
618
  }
499
- /** The consumer's wording for one rejection reason. */
500
619
  #rejectText(reason) {
501
620
  switch (reason) {
502
621
  case "type":
@@ -509,11 +628,6 @@ var FileDropzoneController = class extends Controller {
509
628
  return this.announceRejectedCountTextValue;
510
629
  }
511
630
  }
512
- /**
513
- * Mirrors the accepted set onto the native input, so a plain form submit carries
514
- * the dropped files. Skipped where `DataTransfer` cannot be constructed: the
515
- * widget keeps working and the consumer still receives every `File` on `change`.
516
- */
517
631
  #syncInput() {
518
632
  if (!this.hasInputTarget) return;
519
633
  const transfer = this.#newTransfer();
@@ -521,7 +635,6 @@ var FileDropzoneController = class extends Controller {
521
635
  for (const entry of this.#entries) transfer.items.add(entry.file);
522
636
  this.inputTarget.files = transfer.files;
523
637
  }
524
- /** A usable empty `DataTransfer`, or `null` where the platform has none. */
525
638
  #newTransfer() {
526
639
  try {
527
640
  return new DataTransfer();
@@ -529,35 +642,23 @@ var FileDropzoneController = class extends Controller {
529
642
  return null;
530
643
  }
531
644
  }
532
- /**
533
- * Moves surviving preview items back under the current list. A morph that
534
- * empties the list in place leaves the selection with no rendering, and the
535
- * files it holds cannot be rebuilt from the DOM, so the items are re-homed
536
- * rather than forgotten.
537
- */
538
645
  #rehome() {
539
646
  if (!this.hasListTarget) return;
540
647
  for (const entry of this.#entries) {
541
648
  if (!this.listTarget.contains(entry.item)) this.listTarget.appendChild(entry.item);
542
649
  }
543
650
  }
544
- /** Drops the drag-over state, whether the drag ended in a drop or left the zone. */
545
651
  #endDrag() {
546
652
  this.#dragging = false;
547
- if (this.hasZoneTarget) this.zoneTarget.removeAttribute(DRAGOVER_ATTRIBUTE);
548
- }
549
- /**
550
- * Discards the selection and every state attribute this controller wrote, so
551
- * neither a cached snapshot nor a stranded subtree keeps items whose files are
552
- * gone. Silent: `change` means a selection the user changed, and the cache
553
- * rewind reports itself as `reconcile` instead.
554
- */
555
- #rewindForCache() {
556
- const had = this.#entries.length > 0;
557
- this.#reset();
558
- if (had) this.dispatch("reconcile", { detail: { files: this.#files } });
653
+ this.#paintZone();
654
+ }
655
+ #paintZone() {
656
+ if (!this.hasZoneTarget) return;
657
+ this.#dragoverHook.write(this.zoneTarget, this.#dragging ? "" : null);
658
+ this.#invalidHook.write(this.zoneTarget, this.#invalid ? "" : null);
559
659
  }
560
660
  #reset() {
661
+ this.#invalid = false;
561
662
  for (const entry of this.#entries) {
562
663
  if (entry.url) URL.revokeObjectURL(entry.url);
563
664
  entry.item.remove();
@@ -565,23 +666,17 @@ var FileDropzoneController = class extends Controller {
565
666
  this.#entries.length = 0;
566
667
  this.#syncInput();
567
668
  this.#endDrag();
568
- if (this.hasZoneTarget) this.zoneTarget.removeAttribute(this.#invalidAttribute);
569
669
  }
570
- /** Releases the selection once the disconnect is known to be a real detach. */
571
670
  #teardown() {
572
- this.#gate.cancel();
573
671
  this.#reset();
574
672
  }
575
- /** Whether the field refuses input, natively or through an ancestor `fieldset`. */
576
673
  get #isDisabled() {
577
674
  return this.inputTarget.disabled || inheritsFieldsetDisabled(this.inputTarget);
578
675
  }
579
- /** Effective file cap: `maxFiles`, or 1 when the input is single-select. */
580
676
  get #effectiveMaxFiles() {
581
- if (this.maxFilesValue > 0) return this.maxFilesValue;
677
+ if (this.#safeMaxFiles > 0) return this.#safeMaxFiles;
582
678
  return this.inputTarget.multiple ? 0 : 1;
583
679
  }
584
- /** The declared remove button of each rendered item, in selection order. */
585
680
  get #removeButtons() {
586
681
  const buttons = [];
587
682
  for (const entry of this.#entries) {
@@ -593,12 +688,28 @@ var FileDropzoneController = class extends Controller {
593
688
  }
594
689
  return buttons;
595
690
  }
596
- /** The accepted files in selection order. */
597
691
  get #files() {
598
692
  return this.#entries.map((entry) => entry.file);
599
693
  }
694
+ get #safeMaxSize() {
695
+ return this.#numbers.read(
696
+ this,
697
+ "maxSize",
698
+ this.maxSizeValue,
699
+ _FileDropzoneController.values.maxSize.default,
700
+ _FileDropzoneController.valueConstraints.maxSize
701
+ );
702
+ }
703
+ get #safeMaxFiles() {
704
+ return this.#numbers.read(
705
+ this,
706
+ "maxFiles",
707
+ this.maxFilesValue,
708
+ _FileDropzoneController.values.maxFiles.default,
709
+ _FileDropzoneController.valueConstraints.maxFiles
710
+ );
711
+ }
600
712
  };
601
713
 
602
714
  export { FileDropzoneController };
603
- //# sourceMappingURL=file_dropzone_controller.js.map
604
715
  //# sourceMappingURL=file_dropzone_controller.js.map