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/character_counter_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,183 +16,375 @@ 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/attribute_lease.ts
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
+ }
23
81
  var AttributeLease = class {
24
82
  #attribute;
25
- #records = /* @__PURE__ */ new Map();
26
- /** @param attribute - The attribute whose temporary values this lease owns. */
27
- constructor(attribute) {
83
+ #record;
84
+ #base;
85
+ #preserveAuthor;
86
+ #held = /* @__PURE__ */ new Set();
87
+ constructor(attribute, owner, options = {}) {
28
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));
29
94
  }
30
- /** Writes or removes the leased attribute while preserving its authored value. */
31
95
  write(element, value) {
32
- const existing = this.#records.get(element);
33
- if (existing) {
34
- existing.written = value;
35
- } else {
36
- this.#records.set(element, {
37
- original: element.getAttribute(this.#attribute),
38
- written: value
39
- });
40
- }
41
- this.#reflect(element, value);
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);
42
103
  }
43
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
44
104
  return(element) {
45
- const record = this.#records.get(element);
46
- if (!record) return;
47
- this.#records.delete(element);
48
- const stillOwned = element.getAttribute(this.#attribute) === record.written;
49
- if (stillOwned) this.#reflect(element, record.original);
50
- }
51
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
52
- #reflect(element, value) {
53
- if (element.getAttribute(this.#attribute) === value) return;
54
- if (value === null) element.removeAttribute(this.#attribute);
55
- else element.setAttribute(this.#attribute, value);
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;
56
121
  }
57
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
58
122
  returnAll() {
59
- for (const element of Array.from(this.#records.keys())) this.return(element);
60
- }
61
- };
62
-
63
- // src/utils/before_cache_reset.ts
64
- var BeforeCacheReset = class _BeforeCacheReset {
65
- /** Every subscribed instance, iterated by the one shared document listener. */
66
- static #subscribers = /* @__PURE__ */ new Set();
67
- /** The shared listener; installed while at least one instance is subscribed. */
68
- static #onBeforeCache = () => {
69
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
70
- };
71
- #rewind;
72
- /** @param rewind - the pass that returns this controller's state to its initial form. */
73
- constructor(rewind) {
74
- this.#rewind = rewind;
75
- }
76
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
77
- activate() {
78
- const first = _BeforeCacheReset.#subscribers.size === 0;
79
- _BeforeCacheReset.#subscribers.add(this);
80
- if (first) {
81
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
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);
82
166
  }
83
167
  }
84
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
85
- deactivate() {
86
- _BeforeCacheReset.#subscribers.delete(this);
87
- if (_BeforeCacheReset.#subscribers.size > 0) return;
88
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
168
+ #reflect(element, value) {
169
+ setAttributeIfChanged(element, this.#attribute, value);
89
170
  }
90
171
  };
91
172
 
92
- // src/utils/composition_tracker.ts
93
173
  var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
94
174
  "insertCompositionText",
95
175
  "insertFromComposition",
96
176
  "deleteCompositionText",
97
177
  "deleteByComposition"
98
178
  ]);
179
+ function isCompositionInputType(inputType) {
180
+ return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
181
+ }
182
+ var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
183
+ var textOf = (element) => element.value;
184
+ var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
99
185
  var CompositionTracker = class {
100
186
  #observedTargets = /* @__PURE__ */ new Set();
101
- #activeTargets = /* @__PURE__ */ new Set();
187
+ #composition = null;
102
188
  #onStart;
103
189
  #onEnd;
104
- /** The field whose confirming `input` is still owed, while the window is open. */
190
+ #onAbandon;
105
191
  #confirmedTarget = null;
192
+ #keyComposing = false;
193
+ #connected = false;
194
+ #abandoned = null;
106
195
  constructor(options = {}) {
107
196
  this.#onStart = options.onStart;
108
197
  this.#onEnd = options.onEnd;
198
+ this.#onAbandon = options.onAbandon;
109
199
  }
110
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
111
200
  observe(target) {
201
+ this.#connected = true;
112
202
  if (this.#observedTargets.has(target)) return;
113
203
  target.addEventListener("compositionstart", this.#handleStart);
114
204
  target.addEventListener("compositionend", this.#handleEnd);
115
- target.addEventListener("keydown", this.#handleKeydown);
205
+ target.addEventListener("keydown", this.#handleKeydown, true);
206
+ target.addEventListener("beforeinput", this.#handleBeforeInput, true);
207
+ target.addEventListener("input", this.#handleInput, true);
208
+ target.addEventListener("blur", this.#handleBlur, true);
209
+ target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
116
210
  this.#observedTargets.add(target);
117
211
  }
118
- /** Stops tracking one target and clears any active composition it owned. */
119
212
  unobserve(target) {
120
213
  if (!this.#observedTargets.delete(target)) return;
121
- target.removeEventListener("compositionstart", this.#handleStart);
122
- target.removeEventListener("compositionend", this.#handleEnd);
123
- target.removeEventListener("keydown", this.#handleKeydown);
124
- this.#activeTargets.delete(target);
214
+ this.#release(target);
215
+ if (this.#composition?.target === target) this.#letGo();
125
216
  if (this.#confirmedTarget === target) this.#confirmedTarget = null;
126
217
  }
127
- /** Releases every listener and clears state so reconnect starts cleanly. */
128
218
  disconnect() {
129
- for (const target of this.#observedTargets) {
130
- target.removeEventListener("compositionstart", this.#handleStart);
131
- target.removeEventListener("compositionend", this.#handleEnd);
132
- target.removeEventListener("keydown", this.#handleKeydown);
133
- }
219
+ this.#connected = false;
220
+ for (const target of this.#observedTargets) this.#release(target);
134
221
  this.#observedTargets.clear();
135
- this.#activeTargets.clear();
222
+ this.#letGo();
136
223
  this.#confirmedTarget = null;
224
+ this.#keyComposing = false;
225
+ this.#abandoned = null;
137
226
  }
138
- /** True when lifecycle tracking or the current event reports composition. */
139
227
  isComposing(event) {
140
- return this.#activeTargets.size > 0 || event?.isComposing === true;
141
- }
142
- /**
143
- * Whether `event` is the `input` echoing the composition just confirmed.
144
- *
145
- * Asking closes the window either way, so one confirmation is folded at most
146
- * once and a consumer asks once per `input`.
147
- */
228
+ this.#settle();
229
+ return event?.isComposing === true || this.#composition !== null;
230
+ }
231
+ composingElement() {
232
+ this.#settle();
233
+ return this.#composition?.element ?? null;
234
+ }
235
+ owns(event) {
236
+ const element = this.composingElement();
237
+ return event.isComposing === true || element !== null && element === event.target;
238
+ }
148
239
  consumesConfirmedInput(event) {
149
240
  const confirmed = this.#confirmedTarget;
150
241
  this.#confirmedTarget = null;
151
242
  if (confirmed === null || confirmed !== event.target) return false;
152
243
  const inputType = event.inputType;
153
- return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
244
+ return !inputType || isCompositionInputType(inputType);
245
+ }
246
+ #release(target) {
247
+ target.removeEventListener("compositionstart", this.#handleStart);
248
+ target.removeEventListener("compositionend", this.#handleEnd);
249
+ target.removeEventListener("keydown", this.#handleKeydown, true);
250
+ target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
251
+ target.removeEventListener("input", this.#handleInput, true);
252
+ target.removeEventListener("blur", this.#handleBlur, true);
253
+ target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
254
+ }
255
+ #letGo() {
256
+ const composition = this.#composition;
257
+ if (composition === null) return;
258
+ if (!holdsFocus(composition.element)) {
259
+ this.#composition = null;
260
+ return;
261
+ }
262
+ queueMicrotask(() => {
263
+ if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
264
+ return;
265
+ }
266
+ this.#composition = null;
267
+ if (!this.#connected) return;
268
+ this.#abandoned = { element: composition.element, inPlace: true };
269
+ this.#runAbandon();
270
+ });
271
+ }
272
+ #settle() {
273
+ const composition = this.#composition;
274
+ if (composition === null) return;
275
+ const { target, element, text } = composition;
276
+ if (target instanceof Node && element instanceof Node && !target.contains(element)) {
277
+ this.#composition = null;
278
+ } else if (textOf(element) !== text) {
279
+ this.#abandon(false);
280
+ }
281
+ }
282
+ #abandon(inPlace) {
283
+ this.#owe(inPlace);
284
+ this.#composition = null;
285
+ queueMicrotask(this.#runAbandon);
286
+ }
287
+ #owe(inPlace) {
288
+ const composition = this.#composition;
289
+ if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
154
290
  }
291
+ #runAbandon = () => {
292
+ const abandoned = this.#abandoned;
293
+ if (abandoned === null) return;
294
+ this.#abandoned = null;
295
+ this.#onAbandon?.(abandoned.element, abandoned.inPlace);
296
+ };
155
297
  #handleStart = (event) => {
156
298
  this.#confirmedTarget = null;
157
- if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
299
+ this.#settle();
300
+ this.#owe(true);
301
+ const target = event.currentTarget;
302
+ const element = event.target;
303
+ this.#composition = { target, element, text: textOf(element) };
304
+ this.#runAbandon();
158
305
  this.#onStart?.(event);
159
306
  };
160
307
  #handleEnd = (event) => {
161
- if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
308
+ this.#runAbandon();
309
+ if (this.#composition?.target === event.currentTarget) this.#composition = null;
162
310
  this.#confirmedTarget = event.target;
163
311
  this.#onEnd?.(event);
164
312
  };
165
- /** A key on an observed field opens an edit of its own, so no echo is owed. */
166
- #handleKeydown = () => {
313
+ #handleKeydown = (event) => {
314
+ this.#settle();
315
+ this.#runAbandon();
167
316
  this.#confirmedTarget = null;
317
+ this.#keyComposing = event.isComposing === true;
318
+ };
319
+ #handleBeforeInput = (event) => {
320
+ const keyComposing = this.#keyComposing;
321
+ this.#keyComposing = false;
322
+ this.#settle();
323
+ const { isComposing, inputType } = event;
324
+ if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
325
+ this.#abandon(true);
326
+ }
327
+ this.#runAbandon();
328
+ };
329
+ #handleInput = (event) => {
330
+ const composition = this.#composition;
331
+ const { isTrusted, isComposing, inputType } = event;
332
+ if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
333
+ composition.text = textOf(composition.element);
334
+ } else {
335
+ this.#settle();
336
+ }
337
+ this.#runAbandon();
338
+ };
339
+ #handleBlur = (event) => {
340
+ this.#settle();
341
+ this.#runAbandon();
342
+ const composition = this.#composition;
343
+ if (composition === null || composition.target === composition.element) return;
344
+ const { element } = composition;
345
+ if (!(element instanceof Node) || event.target !== element) return;
346
+ const root = element.getRootNode();
347
+ if (root.activeElement !== element) this.#composition = null;
348
+ };
349
+ #handleBeforeMorphAttribute = (event) => {
350
+ this.#settle();
351
+ const composition = this.#composition;
352
+ if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
353
+ const detail = event.detail;
354
+ if (detail?.attributeName === "value") event.preventDefault();
168
355
  };
169
356
  };
170
357
 
171
- // src/utils/microtask_coalescer.ts
358
+ var ListenerSet = class {
359
+ #abort = new AbortController();
360
+ add(target, type, handler, options) {
361
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
362
+ }
363
+ dispose() {
364
+ this.#abort.abort();
365
+ this.#abort = new AbortController();
366
+ }
367
+ };
368
+
172
369
  var MicrotaskCoalescer = class {
173
370
  #run;
174
371
  #queued = false;
175
372
  #active = false;
176
373
  #generation = 0;
177
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
178
374
  constructor(run) {
179
375
  this.#run = run;
180
376
  }
181
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
182
377
  activate() {
183
378
  this.#active = true;
184
379
  }
185
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
186
380
  cancel() {
187
381
  this.#active = false;
188
382
  this.#queued = false;
189
383
  this.#generation += 1;
190
384
  }
191
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
385
+ get pending() {
386
+ return this.#queued;
387
+ }
192
388
  schedule() {
193
389
  if (!this.#active || this.#queued) return;
194
390
  this.#queued = true;
@@ -201,43 +397,96 @@ var MicrotaskCoalescer = class {
201
397
  }
202
398
  };
203
399
 
204
- // src/utils/safe_timeout.ts
400
+ var MorphRenderWatcher = class {
401
+ #listeners = new ListenerSet();
402
+ #pass;
403
+ #element = null;
404
+ #onMorph = () => this.#pass.schedule();
405
+ constructor(run) {
406
+ this.#pass = new MicrotaskCoalescer(run);
407
+ }
408
+ observe(element) {
409
+ if (this.#element === element) return;
410
+ this.disconnect();
411
+ this.#element = element;
412
+ this.#pass.activate();
413
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
414
+ }
415
+ schedule() {
416
+ this.#pass.schedule();
417
+ }
418
+ get pending() {
419
+ return this.#pass.pending;
420
+ }
421
+ disconnect() {
422
+ this.#listeners.dispose();
423
+ this.#pass.cancel();
424
+ this.#element = null;
425
+ }
426
+ };
427
+
428
+ var NUMBER_BOUNDS = {
429
+ nonNegative: { finite: true, min: 0 }};
430
+ function matchesNumberBounds(value, bounds) {
431
+ if (!Number.isFinite(value)) {
432
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
433
+ if (direction === null) return false;
434
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
435
+ }
436
+ if (bounds.min !== void 0 && value < bounds.min) return false;
437
+ if (bounds.max !== void 0 && value > bounds.max) return false;
438
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
439
+ if (bounds.integer && !Number.isInteger(value)) return false;
440
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
441
+ return true;
442
+ }
443
+
444
+ function readNumber(raw, fallback, bounds) {
445
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
446
+ }
447
+
448
+ var NumberValueReader = class {
449
+ #lastRejected = /* @__PURE__ */ new Map();
450
+ read(owner, name, raw, fallback, bounds) {
451
+ const resolved = readNumber(raw, fallback, bounds);
452
+ if (matchesNumberBounds(raw, bounds)) {
453
+ this.#lastRejected.delete(name);
454
+ return resolved;
455
+ }
456
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
457
+ const literal = owner.element.getAttribute(attribute);
458
+ if (literal === null) {
459
+ this.#lastRejected.delete(name);
460
+ return resolved;
461
+ }
462
+ if (this.#lastRejected.get(name) !== literal) {
463
+ this.#lastRejected.set(name, literal);
464
+ console.warn(
465
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
466
+ );
467
+ }
468
+ return resolved;
469
+ }
470
+ };
471
+
205
472
  var TimerRegistry = class {
206
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
207
473
  ids = /* @__PURE__ */ new Set();
208
- /**
209
- * Cancels a single tracked timer.
210
- *
211
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
212
- * registry), so callers can clear defensively without guarding.
213
- */
214
474
  clear(id) {
215
475
  if (this.ids.delete(id)) {
216
476
  this.cancel(id);
217
477
  }
218
478
  }
219
- /**
220
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
221
- * to guarantee no timer outlives the element.
222
- */
223
479
  clearAll() {
224
480
  for (const id of this.ids) {
225
481
  this.cancel(id);
226
482
  }
227
483
  this.ids.clear();
228
484
  }
229
- /** Number of timers currently tracked (pending). */
230
485
  get size() {
231
486
  return this.ids.size;
232
487
  }
233
488
  };
234
489
  var SafeTimeout = class extends TimerRegistry {
235
- /**
236
- * Schedules `callback` after `delay` ms and returns the timer id.
237
- *
238
- * The id is removed from the registry automatically when the timeout fires,
239
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
240
- */
241
490
  set(callback, delay) {
242
491
  const id = this.schedule(() => {
243
492
  this.ids.delete(id);
@@ -254,8 +503,8 @@ var SafeTimeout = class extends TimerRegistry {
254
503
  }
255
504
  };
256
505
 
257
- // src/controllers/character_counter_controller.ts
258
506
  var CharacterCounterController = class _CharacterCounterController extends Controller {
507
+ #numbers = new NumberValueReader();
259
508
  static targets = ["input", "output"];
260
509
  static values = {
261
510
  max: { type: Number, default: 0 },
@@ -263,124 +512,143 @@ var CharacterCounterController = class _CharacterCounterController extends Contr
263
512
  mode: { type: String, default: "remaining" },
264
513
  announceText: { type: String, default: "" }
265
514
  };
515
+ static valueConstraints = {
516
+ max: NUMBER_BOUNDS.nonNegative,
517
+ warnAt: NUMBER_BOUNDS.nonNegative
518
+ };
266
519
  static events = ["change", "reconcile"];
267
- /** Delay (ms) before one settled count is sent to the shared announcer. */
268
520
  static #announceDelay = 200;
269
521
  #timeouts = new SafeTimeout();
270
- #ariaInvalid = new AttributeLease("aria-invalid");
271
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
272
- #repaint = new MicrotaskCoalescer(() => this.#reconcile(true));
522
+ #ariaInvalid = new AttributeLease(
523
+ "aria-invalid",
524
+ this.identifier
525
+ );
526
+ #repaint = new MorphRenderWatcher(() => this.#reconcile(true));
273
527
  #composition = new CompositionTracker({
274
- onEnd: (event) => this.#commitFrom(event.currentTarget)
528
+ onStart: (event) => {
529
+ if (event.target === this.#acceptedOwed && this.#field === this.#boundField) {
530
+ this.#reconcileAccepted();
531
+ }
532
+ },
533
+ onEnd: (event) => this.#commitFrom(event.currentTarget),
534
+ onAbandon: () => {
535
+ const first = this.#field === this.#boundField;
536
+ if (this.#reconcileHeld && first) this.#recount(true, this.#owedField);
537
+ else if (first && !this.#repaint.pending) this.#reconcileAccepted();
538
+ else {
539
+ if (!this.#reconcileHeld) this.#acceptedOwed = this.#boundField;
540
+ this.#repaint.schedule();
541
+ }
542
+ }
275
543
  });
544
+ #acceptedOwed = null;
545
+ #reconcileHeld = false;
546
+ #accepted = /* @__PURE__ */ new WeakMap();
276
547
  #announceId = null;
277
548
  #boundField = null;
278
- #lastLength = null;
549
+ #fieldListeners = new ListenerSet();
279
550
  #lastDetail = null;
280
551
  #reflectedOver = null;
281
552
  #onInput = (event) => {
282
553
  const field = this.#boundField;
283
554
  if (!field || event.currentTarget !== field || this.#field !== field) return;
284
555
  if (this.#composition.consumesConfirmedInput(event)) return;
285
- if (this.#composition.isComposing(event)) return;
556
+ const composing = this.#composition.composingElement() === field;
557
+ if (composing || event.isComposing === true) return;
286
558
  this.#commit(field);
287
559
  };
288
- /** Reflects the current DOM state and opens the mutation-reconciliation window. */
289
560
  connect() {
290
- this.#repaint.activate();
291
- this.#beforeCache.activate();
561
+ this.#observeFields();
562
+ this.#repaint.observe(this.element);
292
563
  this.#reconcile(false);
293
564
  }
294
- /** Releases listeners, timers, borrowed ARIA, and controller-owned state hooks. */
295
565
  disconnect() {
296
- this.#repaint.cancel();
297
- this.#beforeCache.deactivate();
298
- this.#cancelAnnouncement();
566
+ this.#repaint.disconnect();
299
567
  this.#bindField(null);
300
568
  this.#composition.disconnect();
301
569
  this.#clearStateHooks();
302
- this.#lastLength = null;
303
570
  this.#lastDetail = null;
304
571
  }
305
- /** Rebinds and repaints after an input target is added or replaced at runtime. */
306
572
  inputTargetConnected() {
573
+ this.#observeFields();
307
574
  this.#repaint.schedule();
308
575
  }
309
- /** Releases and repaints after an input target is removed or replaced at runtime. */
310
- inputTargetDisconnected() {
576
+ inputTargetDisconnected(element) {
577
+ this.#composition.unobserve(element);
578
+ if (element === this.#boundField) this.#composition.observe(element);
311
579
  this.#repaint.schedule();
312
580
  }
313
- /** Initializes a display target inserted after the controller connected. */
314
581
  outputTargetConnected() {
315
582
  this.#repaint.schedule();
316
583
  }
317
- /** Reconciles the optional display target set after a removal or replacement. */
318
584
  outputTargetDisconnected() {
319
585
  this.#repaint.schedule();
320
586
  }
321
- /** Repaints when application code or a Turbo morph changes `max`. */
322
587
  maxValueChanged() {
323
588
  this.#repaint.schedule();
324
589
  }
325
- /** Repaints when application code or a Turbo morph changes `warnAt`. */
326
590
  warnAtValueChanged() {
327
591
  this.#repaint.schedule();
328
592
  }
329
- /** Repaints when application code or a Turbo morph changes `mode`. */
330
593
  modeValueChanged() {
331
594
  this.#repaint.schedule();
332
595
  }
333
- /** Rewords a pending announcement with the template the consumer changed it to. */
334
- announceTextValueChanged() {
335
- this.#repaint.schedule();
336
- }
337
- /** Rebinds one mutation batch and reports a changed controller-derived state. */
338
596
  #reconcile(report) {
339
- const previous = this.#lastDetail;
340
- const owedField = this.#announceId === null ? null : this.#boundField;
341
- this.#cancelAnnouncement();
597
+ const owedField = this.#owedField;
342
598
  this.#bindField(this.#field);
343
- if (this.#composition.isComposing()) return;
599
+ this.#recount(report, owedField);
600
+ }
601
+ get #owedField() {
602
+ return this.#announceId === null ? null : this.#boundField;
603
+ }
604
+ #recount(report, owedField) {
605
+ const owed = this.#acceptedOwed;
606
+ this.#acceptedOwed = null;
607
+ const previous = this.#lastDetail;
344
608
  const field = this.#boundField;
609
+ if (field !== null && this.#composition.composingElement() === field) {
610
+ this.#reconcileHeld = this.#accepted.get(field)?.declared !== this.#declared;
611
+ this.#showAgain(field);
612
+ if (owed === field) this.#reconcileAccepted();
613
+ return;
614
+ }
615
+ this.#reconcileHeld = false;
345
616
  if (!field) {
346
617
  this.#renderEmpty();
347
- this.#lastLength = null;
348
618
  this.#lastDetail = null;
349
619
  return;
350
620
  }
351
621
  const reading = this.#render(field);
352
622
  const detail = this.#detail(reading);
353
- this.#lastLength = reading.length;
354
623
  this.#lastDetail = detail;
355
624
  if (owedField === field) this.#scheduleAnnouncement(reading);
356
625
  if (report && previous && this.#detailsDiffer(previous, detail)) {
357
626
  this.dispatch("reconcile", { detail });
358
627
  }
359
628
  }
360
- /** Commits a composition only when it came from the field still owned here. */
361
629
  #commitFrom(target) {
362
630
  const field = this.#boundField;
363
631
  if (!field || target !== field || this.#field !== field) return;
364
632
  this.#commit(field);
365
633
  }
366
- /** Reflects one confirmed user input and reports only a real length transition. */
367
634
  #commit(field) {
635
+ const held = this.#reconcileHeld;
636
+ this.#reconcileHeld = false;
637
+ const previousLength = this.#accepted.get(field)?.length ?? null;
368
638
  const reading = this.#render(field);
369
- const previousLength = this.#lastLength;
370
639
  const previousDetail = this.#lastDetail;
371
640
  const detail = this.#detail(reading);
372
- this.#lastLength = reading.length;
373
641
  this.#lastDetail = detail;
374
642
  if (previousLength === reading.length) {
375
643
  if (previousDetail && this.#detailsDiffer(previousDetail, detail)) {
376
644
  this.dispatch("reconcile", { detail });
377
645
  }
646
+ if (held && this.#owedField === field) this.#scheduleAnnouncement(reading);
378
647
  return;
379
648
  }
380
649
  this.dispatch("change", { detail });
381
- this.#scheduleAnnouncement(reading);
650
+ if (this.#boundField === field && this.#field === field) this.#scheduleAnnouncement(reading);
382
651
  }
383
- /** Selects the public event state from the richer internal reading. */
384
652
  #detail(reading) {
385
653
  return {
386
654
  length: reading.length,
@@ -388,19 +656,13 @@ var CharacterCounterController = class _CharacterCounterController extends Contr
388
656
  over: reading.over
389
657
  };
390
658
  }
391
- /** Compares exactly the state carried by `change` and `reconcile`. */
392
659
  #detailsDiffer(left, right) {
393
660
  return left.length !== right.length || left.remaining !== right.remaining || left.over !== right.over;
394
661
  }
395
- /**
396
- * Synchronizes visible count, state hooks, and temporary validation ARIA.
397
- *
398
- * @stimeoRenderRoot
399
- */
400
662
  #render(field) {
401
663
  const length = field.value.length;
402
- const max = this.#normalizeCount(this.maxValue);
403
- const warnAt = this.#normalizeCount(this.warnAtValue);
664
+ const max = this.#normalizeCount(this.#safeMax);
665
+ const warnAt = this.#normalizeCount(this.#safeWarnAt);
404
666
  const remaining = max > 0 ? max - length : null;
405
667
  const over = remaining !== null && remaining < 0;
406
668
  const near = remaining !== null && warnAt > 0 && !over && remaining <= warnAt;
@@ -409,39 +671,64 @@ var CharacterCounterController = class _CharacterCounterController extends Contr
409
671
  this.#reflectInvalid(field, over);
410
672
  const text = this.#format(length, remaining, max);
411
673
  this.#writeOutput(text);
674
+ this.#accepted.set(field, {
675
+ text,
676
+ over,
677
+ near,
678
+ length,
679
+ detail: { length, remaining, over },
680
+ declared: this.#declared
681
+ });
412
682
  return { length, remaining, over, max, text };
413
683
  }
414
- /** Clears derived output when the declarative input set has no usable field. */
684
+ #showAgain(field) {
685
+ const shown = this.#accepted.get(field);
686
+ if (shown === void 0) return;
687
+ this.#lastDetail ??= shown.detail;
688
+ this.#toggle("data-over-limit", shown.over);
689
+ this.#toggle("data-near-limit", shown.near);
690
+ this.#reflectInvalid(field, shown.over);
691
+ this.#writeOutput(shown.text);
692
+ }
693
+ #reconcileAccepted() {
694
+ const previous = this.#lastDetail;
695
+ const field = this.#boundField;
696
+ const accepted = field && this.#accepted.get(field)?.detail;
697
+ if (!previous || !accepted || !this.#detailsDiffer(previous, accepted)) return;
698
+ this.#lastDetail = accepted;
699
+ this.dispatch("reconcile", { detail: accepted });
700
+ }
415
701
  #renderEmpty() {
416
- this.#ariaInvalid.returnAll();
417
702
  this.#reflectedOver = null;
418
703
  this.#clearStateHooks();
419
704
  this.#writeOutput("");
420
705
  }
421
- /** Replaces the observed field symmetrically, returning state from the old one. */
706
+ #observeFields() {
707
+ for (const input of this.inputTargets) this.#composition.observe(input);
708
+ }
422
709
  #bindField(field) {
423
710
  if (field === this.#boundField) return;
424
711
  const previous = this.#boundField;
425
712
  if (previous) {
426
- previous.removeEventListener("input", this.#onInput);
427
- this.#composition.unobserve(previous);
713
+ this.#fieldListeners.dispose();
714
+ if (previous !== this.element && !this.inputTargets.includes(previous)) {
715
+ this.#composition.unobserve(previous);
716
+ }
428
717
  this.#ariaInvalid.return(previous);
429
718
  }
719
+ this.#cancelAnnouncement();
430
720
  this.#boundField = field;
431
- this.#lastLength = null;
432
721
  this.#reflectedOver = null;
433
722
  if (!field) return;
434
- field.addEventListener("input", this.#onInput);
723
+ this.#fieldListeners.add(field, "input", this.#onInput);
435
724
  this.#composition.observe(field);
436
725
  }
437
- /** Leases `aria-invalid` only on the edge into over-limit, then returns it. */
438
726
  #reflectInvalid(field, over) {
439
- if (this.#reflectedOver === over) return;
727
+ if (this.#reflectedOver === over && (!over || field.hasAttribute("aria-invalid"))) return;
440
728
  this.#reflectedOver = over;
441
729
  if (over) this.#ariaInvalid.write(field, "true");
442
730
  else this.#ariaInvalid.return(field);
443
731
  }
444
- /** Builds the visible count for the normalized display mode. */
445
732
  #format(length, remaining, max) {
446
733
  if (remaining === null) return String(length);
447
734
  switch (this.#mode) {
@@ -453,68 +740,50 @@ var CharacterCounterController = class _CharacterCounterController extends Contr
453
740
  return String(remaining);
454
741
  }
455
742
  }
456
- /**
457
- * Debounces one i18n-neutral message into the shared polite announcer.
458
- *
459
- * @stimeoRuntimeOnly `announceText` is the template of the one announcement this call schedules;
460
- * the count is painted by the marked renderer.
461
- */
462
743
  #scheduleAnnouncement(reading) {
463
744
  this.#cancelAnnouncement();
464
- const message = fillTemplate(this.announceTextValue, {
745
+ const message = () => fillTemplate(this.announceTextValue, {
465
746
  count: reading.text,
466
747
  length: reading.length,
467
748
  remaining: reading.remaining ?? "",
468
749
  max: reading.max,
469
750
  over: String(reading.over)
470
751
  });
471
- if (message.trim().length === 0) return;
752
+ if (message().trim().length === 0) return;
472
753
  this.#announceId = this.#timeouts.set(() => {
473
- announce(message);
754
+ announce(message());
474
755
  this.#announceId = null;
475
756
  }, _CharacterCounterController.#announceDelay);
476
757
  }
477
- /** Cancels the one outstanding announcement without touching visible output. */
478
758
  #cancelAnnouncement() {
479
759
  if (this.#announceId !== null) this.#timeouts.clear(this.#announceId);
480
760
  this.#announceId = null;
481
761
  }
482
- /** Writes the optional display only when its text actually changed. */
483
762
  #writeOutput(text) {
484
763
  if (!this.hasOutputTarget || this.outputTarget.textContent === text) return;
485
764
  this.outputTarget.textContent = text;
486
765
  }
487
- /** Reflects a presence-style state hook without redundant attribute writes. */
488
766
  #toggle(name, on) {
489
- if (on) {
490
- if (this.element.getAttribute(name) !== "true") this.element.setAttribute(name, "true");
491
- } else if (this.element.hasAttribute(name)) {
492
- this.element.removeAttribute(name);
493
- }
767
+ setAttributeIfChanged(this.element, name, on ? "true" : null);
494
768
  }
495
- /** Removes both state hooks owned by this controller. */
496
769
  #clearStateHooks() {
497
770
  this.#toggle("data-over-limit", false);
498
771
  this.#toggle("data-near-limit", false);
499
772
  }
500
- /** Returns borrowed ARIA and transient hooks before Turbo snapshots the page. */
501
- #rewindForCache() {
502
- this.#cancelAnnouncement();
503
- this.#ariaInvalid.returnAll();
504
- this.#reflectedOver = null;
505
- this.#clearStateHooks();
506
- }
507
- /** Converts a public count Value into a finite non-negative integer. */
508
773
  #normalizeCount(raw) {
509
- if (!Number.isFinite(raw)) return 0;
510
774
  return Math.max(0, Math.trunc(raw));
511
775
  }
512
- /** Falls back to `remaining` when an authored display mode is unknown. */
513
776
  get #mode() {
514
777
  if (this.modeValue === "used" || this.modeValue === "both") return this.modeValue;
515
778
  return "remaining";
516
779
  }
517
- /** The watched field: the first target, or a directly controlled form field. */
780
+ get #declared() {
781
+ return [
782
+ this.#normalizeCount(this.#safeMax),
783
+ this.#normalizeCount(this.#safeWarnAt),
784
+ this.#mode
785
+ ].join();
786
+ }
518
787
  get #field() {
519
788
  if (this.hasInputTarget) return this.inputTarget;
520
789
  const element = this.element;
@@ -522,8 +791,25 @@ var CharacterCounterController = class _CharacterCounterController extends Contr
522
791
  return element;
523
792
  return null;
524
793
  }
794
+ get #safeMax() {
795
+ return this.#numbers.read(
796
+ this,
797
+ "max",
798
+ this.maxValue,
799
+ _CharacterCounterController.values.max.default,
800
+ _CharacterCounterController.valueConstraints.max
801
+ );
802
+ }
803
+ get #safeWarnAt() {
804
+ return this.#numbers.read(
805
+ this,
806
+ "warnAt",
807
+ this.warnAtValue,
808
+ _CharacterCounterController.values.warnAt.default,
809
+ _CharacterCounterController.valueConstraints.warnAt
810
+ );
811
+ }
525
812
  };
526
813
 
527
814
  export { CharacterCounterController };
528
- //# sourceMappingURL=character_counter_controller.js.map
529
815
  //# sourceMappingURL=character_counter_controller.js.map