stimeo-ui 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +128 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +392 -417
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +34 -5
  7. package/dist/controllers/accordion_controller.js +228 -28
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +20 -9
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +38 -7
  13. package/dist/controllers/announcer_controller.js +135 -191
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +295 -110
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +172 -106
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  23. package/dist/controllers/breadcrumb_controller.js +0 -129
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +22 -4
  26. package/dist/controllers/bulk_select_controller.js +264 -53
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +109 -28
  29. package/dist/controllers/calendar_controller.js +346 -180
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +99 -27
  32. package/dist/controllers/carousel_controller.js +587 -300
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +18 -2
  35. package/dist/controllers/character_counter_controller.js +271 -143
  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 +77 -75
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +44 -7
  41. package/dist/controllers/clipboard_controller.js +187 -108
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +41 -5
  44. package/dist/controllers/collapsible_controller.js +374 -108
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +47 -14
  47. package/dist/controllers/color_picker_controller.js +183 -127
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +33 -6
  50. package/dist/controllers/combobox_controller.js +426 -109
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +45 -14
  53. package/dist/controllers/command_palette_controller.js +994 -350
  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 +212 -125
  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 +939 -245
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +36 -3
  62. package/dist/controllers/context_menu_controller.js +275 -127
  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 +75 -2
  68. package/dist/controllers/countdown_controller.js +235 -97
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -15
  71. package/dist/controllers/currency_input_controller.js +241 -118
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +78 -22
  74. package/dist/controllers/data_grid_controller.js +229 -135
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +64 -11
  77. package/dist/controllers/date_range_picker_controller.js +359 -165
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +22 -3
  80. package/dist/controllers/dialog_controller.js +887 -225
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  83. package/dist/controllers/direct_upload_controller.js +80 -142
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  86. package/dist/controllers/dirty_form_controller.js +26 -47
  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 +175 -20
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +52 -16
  92. package/dist/controllers/drawer_controller.js +963 -336
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +39 -3
  95. package/dist/controllers/dropdown_controller.js +342 -85
  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 +182 -58
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +67 -53
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  103. package/dist/controllers/file_dropzone_controller.js +426 -229
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.d.ts +10 -4
  106. package/dist/controllers/filter_controller.js +78 -36
  107. package/dist/controllers/filter_controller.js.map +1 -1
  108. package/dist/controllers/flash_controller.d.ts +57 -14
  109. package/dist/controllers/flash_controller.js +328 -211
  110. package/dist/controllers/flash_controller.js.map +1 -1
  111. package/dist/controllers/focus_controller.d.ts +12 -3
  112. package/dist/controllers/focus_controller.js +654 -239
  113. package/dist/controllers/focus_controller.js.map +1 -1
  114. package/dist/controllers/form_field_controller.js +153 -136
  115. package/dist/controllers/form_field_controller.js.map +1 -1
  116. package/dist/controllers/form_validation_controller.js +29 -96
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  119. package/dist/controllers/frame_loading_controller.js +263 -198
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +22 -2
  122. package/dist/controllers/highlight_controller.js +119 -70
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +65 -4
  125. package/dist/controllers/hover_card_controller.js +335 -118
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +58 -4
  128. package/dist/controllers/idle_controller.js +316 -65
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +5 -2
  131. package/dist/controllers/input_mask_controller.js +116 -61
  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 +147 -103
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  137. package/dist/controllers/lazy_frame_controller.js +9 -56
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +84 -10
  140. package/dist/controllers/listbox_controller.js +413 -153
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +1 -1
  143. package/dist/controllers/local_time_controller.js +54 -64
  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 +70 -95
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +40 -9
  149. package/dist/controllers/menu_controller.js +254 -167
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +15 -4
  152. package/dist/controllers/menubar_controller.js +206 -331
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +20 -0
  155. package/dist/controllers/meter_controller.js +159 -71
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +50 -7
  158. package/dist/controllers/multi_select_controller.js +797 -388
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +19 -1
  161. package/dist/controllers/navigation_menu_controller.js +156 -231
  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 +373 -174
  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 +241 -45
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +62 -10
  170. package/dist/controllers/number_input_controller.js +387 -227
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.js +28 -75
  173. package/dist/controllers/optimistic_controller.js.map +1 -1
  174. package/dist/controllers/otp_controller.d.ts +38 -7
  175. package/dist/controllers/otp_controller.js +344 -196
  176. package/dist/controllers/otp_controller.js.map +1 -1
  177. package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
  178. package/dist/controllers/overflow_indicator_controller.js +167 -93
  179. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  180. package/dist/controllers/overflow_menu_controller.d.ts +66 -9
  181. package/dist/controllers/overflow_menu_controller.js +408 -277
  182. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  183. package/dist/controllers/pagination_controller.d.ts +52 -15
  184. package/dist/controllers/pagination_controller.js +342 -120
  185. package/dist/controllers/pagination_controller.js.map +1 -1
  186. package/dist/controllers/password_reveal_controller.d.ts +60 -9
  187. package/dist/controllers/password_reveal_controller.js +424 -99
  188. package/dist/controllers/password_reveal_controller.js.map +1 -1
  189. package/dist/controllers/password_strength_controller.d.ts +14 -5
  190. package/dist/controllers/password_strength_controller.js +99 -153
  191. package/dist/controllers/password_strength_controller.js.map +1 -1
  192. package/dist/controllers/persist_controller.d.ts +11 -4
  193. package/dist/controllers/persist_controller.js +252 -96
  194. package/dist/controllers/persist_controller.js.map +1 -1
  195. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  196. package/dist/controllers/pointer_drag_controller.js +263 -164
  197. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  198. package/dist/controllers/popover_controller.d.ts +46 -4
  199. package/dist/controllers/popover_controller.js +267 -94
  200. package/dist/controllers/popover_controller.js.map +1 -1
  201. package/dist/controllers/portal_controller.d.ts +44 -4
  202. package/dist/controllers/portal_controller.js +209 -72
  203. package/dist/controllers/portal_controller.js.map +1 -1
  204. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  205. package/dist/controllers/preview_guard_controller.js +243 -112
  206. package/dist/controllers/preview_guard_controller.js.map +1 -1
  207. package/dist/controllers/progress_controller.d.ts +11 -0
  208. package/dist/controllers/progress_controller.js +132 -59
  209. package/dist/controllers/progress_controller.js.map +1 -1
  210. package/dist/controllers/radio_group_controller.d.ts +19 -8
  211. package/dist/controllers/radio_group_controller.js +138 -133
  212. package/dist/controllers/radio_group_controller.js.map +1 -1
  213. package/dist/controllers/range_slider_controller.d.ts +92 -4
  214. package/dist/controllers/range_slider_controller.js +245 -112
  215. package/dist/controllers/range_slider_controller.js.map +1 -1
  216. package/dist/controllers/rating_controller.d.ts +33 -6
  217. package/dist/controllers/rating_controller.js +429 -194
  218. package/dist/controllers/rating_controller.js.map +1 -1
  219. package/dist/controllers/read_more_controller.d.ts +26 -2
  220. package/dist/controllers/read_more_controller.js +185 -47
  221. package/dist/controllers/read_more_controller.js.map +1 -1
  222. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  223. package/dist/controllers/reading_progress_controller.js +147 -135
  224. package/dist/controllers/reading_progress_controller.js.map +1 -1
  225. package/dist/controllers/relative_time_controller.d.ts +18 -4
  226. package/dist/controllers/relative_time_controller.js +145 -110
  227. package/dist/controllers/relative_time_controller.js.map +1 -1
  228. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  229. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  230. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  231. package/dist/controllers/resizable_controller.d.ts +44 -4
  232. package/dist/controllers/resizable_controller.js +294 -118
  233. package/dist/controllers/resizable_controller.js.map +1 -1
  234. package/dist/controllers/roving_controller.js +39 -84
  235. package/dist/controllers/roving_controller.js.map +1 -1
  236. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  237. package/dist/controllers/scroll_area_controller.js +428 -221
  238. package/dist/controllers/scroll_area_controller.js.map +1 -1
  239. package/dist/controllers/scroll_restore_controller.js +0 -53
  240. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  241. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  242. package/dist/controllers/scroll_visibility_controller.js +395 -146
  243. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  244. package/dist/controllers/scrollspy_controller.d.ts +9 -3
  245. package/dist/controllers/scrollspy_controller.js +181 -236
  246. package/dist/controllers/scrollspy_controller.js.map +1 -1
  247. package/dist/controllers/separator_controller.d.ts +59 -17
  248. package/dist/controllers/separator_controller.js +288 -132
  249. package/dist/controllers/separator_controller.js.map +1 -1
  250. package/dist/controllers/sidebar_controller.d.ts +81 -7
  251. package/dist/controllers/sidebar_controller.js +1071 -375
  252. package/dist/controllers/sidebar_controller.js.map +1 -1
  253. package/dist/controllers/skeleton_controller.d.ts +23 -0
  254. package/dist/controllers/skeleton_controller.js +248 -92
  255. package/dist/controllers/skeleton_controller.js.map +1 -1
  256. package/dist/controllers/slider_controller.d.ts +72 -8
  257. package/dist/controllers/slider_controller.js +211 -96
  258. package/dist/controllers/slider_controller.js.map +1 -1
  259. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  260. package/dist/controllers/smart_sticky_header_controller.js +163 -54
  261. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  262. package/dist/controllers/sortable_controller.js +13 -110
  263. package/dist/controllers/sortable_controller.js.map +1 -1
  264. package/dist/controllers/spinner_controller.d.ts +39 -4
  265. package/dist/controllers/spinner_controller.js +277 -161
  266. package/dist/controllers/spinner_controller.js.map +1 -1
  267. package/dist/controllers/step_indicator_controller.d.ts +24 -17
  268. package/dist/controllers/step_indicator_controller.js +107 -53
  269. package/dist/controllers/step_indicator_controller.js.map +1 -1
  270. package/dist/controllers/stepper_controller.d.ts +43 -9
  271. package/dist/controllers/stepper_controller.js +171 -48
  272. package/dist/controllers/stepper_controller.js.map +1 -1
  273. package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
  274. package/dist/controllers/stick_to_bottom_controller.js +310 -116
  275. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  276. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  277. package/dist/controllers/sticky_observer_controller.js +214 -49
  278. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  279. package/dist/controllers/submit_once_controller.d.ts +22 -6
  280. package/dist/controllers/submit_once_controller.js +294 -192
  281. package/dist/controllers/submit_once_controller.js.map +1 -1
  282. package/dist/controllers/switch_controller.d.ts +40 -7
  283. package/dist/controllers/switch_controller.js +107 -43
  284. package/dist/controllers/switch_controller.js.map +1 -1
  285. package/dist/controllers/tabs_controller.d.ts +13 -1
  286. package/dist/controllers/tabs_controller.js +50 -25
  287. package/dist/controllers/tabs_controller.js.map +1 -1
  288. package/dist/controllers/tags_input_controller.d.ts +27 -3
  289. package/dist/controllers/tags_input_controller.js +290 -149
  290. package/dist/controllers/tags_input_controller.js.map +1 -1
  291. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  292. package/dist/controllers/textarea_autosize_controller.js +71 -58
  293. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  294. package/dist/controllers/theme_controller.d.ts +37 -8
  295. package/dist/controllers/theme_controller.js +148 -80
  296. package/dist/controllers/theme_controller.js.map +1 -1
  297. package/dist/controllers/time_picker_controller.d.ts +18 -1
  298. package/dist/controllers/time_picker_controller.js +162 -81
  299. package/dist/controllers/time_picker_controller.js.map +1 -1
  300. package/dist/controllers/toast_controller.d.ts +85 -23
  301. package/dist/controllers/toast_controller.js +365 -193
  302. package/dist/controllers/toast_controller.js.map +1 -1
  303. package/dist/controllers/toggle_group_controller.d.ts +60 -9
  304. package/dist/controllers/toggle_group_controller.js +194 -99
  305. package/dist/controllers/toggle_group_controller.js.map +1 -1
  306. package/dist/controllers/toolbar_controller.js +92 -90
  307. package/dist/controllers/toolbar_controller.js.map +1 -1
  308. package/dist/controllers/tooltip_controller.d.ts +52 -2
  309. package/dist/controllers/tooltip_controller.js +185 -125
  310. package/dist/controllers/tooltip_controller.js.map +1 -1
  311. package/dist/controllers/transition_controller.d.ts +22 -4
  312. package/dist/controllers/transition_controller.js +123 -115
  313. package/dist/controllers/transition_controller.js.map +1 -1
  314. package/dist/controllers/tree_view_controller.d.ts +59 -18
  315. package/dist/controllers/tree_view_controller.js +248 -290
  316. package/dist/controllers/tree_view_controller.js.map +1 -1
  317. package/dist/index.d.ts +33 -3
  318. package/dist/index.js +8649 -8572
  319. package/dist/index.js.map +1 -1
  320. package/dist/inspector/cli.d.ts +95 -9
  321. package/dist/inspector/cli.js +343 -77
  322. package/dist/inspector/cli.js.map +1 -1
  323. package/dist/inspector/cli_bin.js +390 -96
  324. package/dist/inspector/cli_bin.js.map +1 -1
  325. package/dist/inspector/examples.json +45 -45
  326. package/dist/inspector/manifest.json +1308 -155
  327. package/dist/positioning/index.d.ts +8 -0
  328. package/dist/positioning/index.js +148 -45
  329. package/dist/positioning/index.js.map +1 -1
  330. package/package.json +7 -7
  331. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  332. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,14 +1,21 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/otp_controller.ts
3
+ function sharedRegistry(key, create) {
4
+ const symbol = Symbol.for(key);
5
+ const scope = globalThis;
6
+ const existing = scope[symbol];
7
+ if (existing !== void 0) return existing;
8
+ const registry = create();
9
+ Object.defineProperty(globalThis, symbol, { value: registry });
10
+ return registry;
11
+ }
4
12
 
5
- // src/utils/aria_ids.ts
6
- var counter = 0;
13
+ var ids = sharedRegistry("stimeo-ui.aria-ids.registry.v1", () => ({ counter: 0 }));
7
14
  function uniqueId(prefix = "stimeo") {
8
15
  let candidate;
9
16
  do {
10
- counter += 1;
11
- candidate = `${prefix}-${counter}`;
17
+ ids.counter += 1;
18
+ candidate = `${prefix}-${ids.counter}`;
12
19
  } while (typeof document !== "undefined" && document.getElementById(candidate) !== null);
13
20
  return candidate;
14
21
  }
@@ -19,12 +26,10 @@ function ensureId(element, prefix = "stimeo") {
19
26
  return id;
20
27
  }
21
28
 
22
- // src/utils/logical_scroll.ts
23
29
  function isRtl(element) {
24
30
  return window.getComputedStyle(element).direction === "rtl";
25
31
  }
26
32
 
27
- // src/utils/arrow_step.ts
28
33
  function logicalArrowKey(key, element) {
29
34
  if (key !== "ArrowRight" && key !== "ArrowLeft") return key;
30
35
  if (!isRtl(element)) return key;
@@ -35,124 +40,209 @@ function isReservedArrowChord(event, allow = []) {
35
40
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
36
41
  }
37
42
 
38
- // src/utils/attribute_lease.ts
39
- var AttributeLease = class {
43
+ function isAttributeValue(value) {
44
+ return value === null || typeof value === "string";
45
+ }
46
+ function readLeaseRecord(element, record) {
47
+ try {
48
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
49
+ if (!Array.isArray(parsed)) return void 0;
50
+ const [original, written, ...rest] = parsed;
51
+ const linked = rest[0] !== false;
52
+ const beneath = linked ? rest : rest.slice(1);
53
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
54
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
55
+ } catch {
56
+ return void 0;
57
+ }
58
+ }
59
+ var AttributeLease = class _AttributeLease {
60
+ static #registry = sharedRegistry(
61
+ "stimeo-ui.attribute-lease.registry.v1",
62
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
63
+ );
40
64
  #attribute;
41
- #records = /* @__PURE__ */ new Map();
42
- /** @param attribute - The attribute whose temporary values this lease owns. */
43
- constructor(attribute) {
65
+ #record;
66
+ #base;
67
+ #held = /* @__PURE__ */ new Set();
68
+ constructor(attribute, owner, options = {}) {
44
69
  this.#attribute = attribute;
70
+ this.#record = `data-${owner}-${attribute}-lease`;
71
+ this.#base = options.base === true;
72
+ const names = _AttributeLease.#registry.names;
73
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
45
74
  }
46
- /** Writes or removes the leased attribute while preserving its authored value. */
47
75
  write(element, value) {
48
- const existing = this.#records.get(element);
49
- if (existing) {
50
- existing.written = value;
51
- } else {
52
- this.#records.set(element, {
53
- original: element.getAttribute(this.#attribute),
54
- written: value
55
- });
56
- }
57
- this.#reflect(element, value);
76
+ const stack = this.#stack(element);
77
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
78
+ this.#held.add(element);
79
+ const shows = !this.#covered(element, stack, hold);
80
+ hold.written = value;
81
+ this.#mark(element, stack);
82
+ if (shows) this.#reflect(element, value);
58
83
  }
59
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
60
84
  return(element) {
61
- const record = this.#records.get(element);
62
- if (!record) return;
63
- this.#records.delete(element);
64
- const stillOwned = element.getAttribute(this.#attribute) === record.written;
65
- if (stillOwned) this.#reflect(element, record.original);
85
+ const held = this.#held.delete(element);
86
+ const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
87
+ const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
88
+ const hold = this.#take(stack);
89
+ if (!hold) return false;
90
+ const carries = element.getAttribute(this.#attribute) === hold.written;
91
+ const owns = carries && !this.#covered(element, stack, hold);
92
+ const index = stack.indexOf(hold);
93
+ stack.splice(index, 1);
94
+ const above = stack[index];
95
+ if (above) above.linked &&= hold.linked;
96
+ this.#mark(element, stack);
97
+ if (!owns) return true;
98
+ const below = hold.linked ? stack[index - 1] : void 0;
99
+ this.#reflect(element, below ? below.written : hold.original);
100
+ return true;
101
+ }
102
+ returnAll() {
103
+ for (const element of Array.from(this.#held)) this.return(element);
104
+ }
105
+ #stack(element) {
106
+ const stacks = _AttributeLease.#registry.stacks;
107
+ const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
108
+ stacks.set(element, byAttribute);
109
+ const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
110
+ byAttribute.set(this.#attribute, stack);
111
+ return stack;
112
+ }
113
+ #seed(element) {
114
+ const found = [];
115
+ for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
116
+ const read = readLeaseRecord(element, record);
117
+ if (read) found.push({ ...read, record });
118
+ }
119
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
120
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
121
+ lease: null,
122
+ record,
123
+ written,
124
+ original,
125
+ linked: linked && index > 0
126
+ }));
127
+ }
128
+ #take(stack) {
129
+ const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
130
+ if (hold) hold.lease = this;
131
+ return hold;
132
+ }
133
+ #acquire(element, stack) {
134
+ const current = element.getAttribute(this.#attribute);
135
+ const top = stack.at(-1);
136
+ const bottom = stack[0];
137
+ const over = top?.written === current ? top : void 0;
138
+ const from = this.#base ? bottom : over;
139
+ const hold = {
140
+ lease: this,
141
+ record: this.#record,
142
+ written: current,
143
+ original: from ? from.original : current,
144
+ linked: !this.#base && over !== void 0
145
+ };
146
+ if (this.#base && bottom) {
147
+ bottom.linked = true;
148
+ stack.unshift(hold);
149
+ } else stack.push(hold);
150
+ return hold;
151
+ }
152
+ #covered(element, stack, hold) {
153
+ const current = element.getAttribute(this.#attribute);
154
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
155
+ }
156
+ #mark(element, stack) {
157
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
158
+ stack.forEach((hold, index) => {
159
+ const names = stack.slice(0, index).map((under) => under.record);
160
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
161
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
162
+ records.set(
163
+ hold.record,
164
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
165
+ );
166
+ });
167
+ for (const [name, recorded] of records) {
168
+ if (element.getAttribute(name) === recorded) continue;
169
+ if (recorded === null) element.removeAttribute(name);
170
+ else element.setAttribute(name, recorded);
171
+ }
66
172
  }
67
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
68
173
  #reflect(element, value) {
69
174
  if (element.getAttribute(this.#attribute) === value) return;
70
175
  if (value === null) element.removeAttribute(this.#attribute);
71
176
  else element.setAttribute(this.#attribute, value);
72
177
  }
73
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
74
- returnAll() {
75
- for (const element of Array.from(this.#records.keys())) this.return(element);
76
- }
77
- };
78
-
79
- // src/utils/before_cache_reset.ts
80
- var BeforeCacheReset = class _BeforeCacheReset {
81
- /** Every subscribed instance, iterated by the one shared document listener. */
82
- static #subscribers = /* @__PURE__ */ new Set();
83
- /** The shared listener; installed while at least one instance is subscribed. */
84
- static #onBeforeCache = () => {
85
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
86
- };
87
- #rewind;
88
- /** @param rewind - the pass that returns this controller's state to its initial form. */
89
- constructor(rewind) {
90
- this.#rewind = rewind;
91
- }
92
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
93
- activate() {
94
- const first = _BeforeCacheReset.#subscribers.size === 0;
95
- _BeforeCacheReset.#subscribers.add(this);
96
- if (first) {
97
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
98
- }
99
- }
100
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
101
- deactivate() {
102
- _BeforeCacheReset.#subscribers.delete(this);
103
- if (_BeforeCacheReset.#subscribers.size > 0) return;
104
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
105
- }
106
178
  };
107
179
 
108
- // src/utils/composition_tracker.ts
180
+ var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
181
+ "insertCompositionText",
182
+ "insertFromComposition",
183
+ "deleteCompositionText",
184
+ "deleteByComposition"
185
+ ]);
109
186
  var CompositionTracker = class {
110
187
  #observedTargets = /* @__PURE__ */ new Set();
111
188
  #activeTargets = /* @__PURE__ */ new Set();
112
189
  #onStart;
113
190
  #onEnd;
191
+ #confirmedTarget = null;
114
192
  constructor(options = {}) {
115
193
  this.#onStart = options.onStart;
116
194
  this.#onEnd = options.onEnd;
117
195
  }
118
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
119
196
  observe(target) {
120
197
  if (this.#observedTargets.has(target)) return;
121
198
  target.addEventListener("compositionstart", this.#handleStart);
122
199
  target.addEventListener("compositionend", this.#handleEnd);
200
+ target.addEventListener("keydown", this.#handleKeydown);
123
201
  this.#observedTargets.add(target);
124
202
  }
125
- /** Stops tracking one target and clears any active composition it owned. */
126
203
  unobserve(target) {
127
204
  if (!this.#observedTargets.delete(target)) return;
128
205
  target.removeEventListener("compositionstart", this.#handleStart);
129
206
  target.removeEventListener("compositionend", this.#handleEnd);
207
+ target.removeEventListener("keydown", this.#handleKeydown);
130
208
  this.#activeTargets.delete(target);
209
+ if (this.#confirmedTarget === target) this.#confirmedTarget = null;
131
210
  }
132
- /** Releases every listener and clears state so reconnect starts cleanly. */
133
211
  disconnect() {
134
212
  for (const target of this.#observedTargets) {
135
213
  target.removeEventListener("compositionstart", this.#handleStart);
136
214
  target.removeEventListener("compositionend", this.#handleEnd);
215
+ target.removeEventListener("keydown", this.#handleKeydown);
137
216
  }
138
217
  this.#observedTargets.clear();
139
218
  this.#activeTargets.clear();
219
+ this.#confirmedTarget = null;
140
220
  }
141
- /** True when lifecycle tracking or the current event reports composition. */
142
221
  isComposing(event) {
143
222
  return this.#activeTargets.size > 0 || event?.isComposing === true;
144
223
  }
224
+ consumesConfirmedInput(event) {
225
+ const confirmed = this.#confirmedTarget;
226
+ this.#confirmedTarget = null;
227
+ if (confirmed === null || confirmed !== event.target) return false;
228
+ const inputType = event.inputType;
229
+ return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
230
+ }
145
231
  #handleStart = (event) => {
232
+ this.#confirmedTarget = null;
146
233
  if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
147
234
  this.#onStart?.(event);
148
235
  };
149
236
  #handleEnd = (event) => {
150
237
  if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
238
+ this.#confirmedTarget = event.target;
151
239
  this.#onEnd?.(event);
152
240
  };
241
+ #handleKeydown = () => {
242
+ this.#confirmedTarget = null;
243
+ };
153
244
  };
154
245
 
155
- // src/utils/declared_value.ts
156
246
  function parseDeclared(raw, parse, fallback) {
157
247
  try {
158
248
  return parse(raw);
@@ -171,7 +261,46 @@ function compileRegExp(source, anchor = "none") {
171
261
  );
172
262
  }
173
263
 
174
- // src/utils/half_width.ts
264
+ var FormResetWatcher = class {
265
+ #owns;
266
+ #onReset;
267
+ #frames = /* @__PURE__ */ new Set();
268
+ #generation = 0;
269
+ constructor(owns, onReset) {
270
+ this.#owns = owns;
271
+ this.#onReset = onReset;
272
+ }
273
+ observe() {
274
+ document.addEventListener("reset", this.#handleReset, true);
275
+ }
276
+ disconnect() {
277
+ document.removeEventListener("reset", this.#handleReset, true);
278
+ this.#generation += 1;
279
+ for (const frame of this.#frames) cancelAnimationFrame(frame);
280
+ this.#frames.clear();
281
+ }
282
+ #handleReset = (event) => {
283
+ const form = event.target;
284
+ if (!(form instanceof HTMLFormElement) || !this.#owns(form)) return;
285
+ const generation = this.#generation;
286
+ queueMicrotask(() => {
287
+ if (generation !== this.#generation) return;
288
+ if (event.eventPhase === Event.NONE) {
289
+ this.#settle(event);
290
+ return;
291
+ }
292
+ const frame = requestAnimationFrame(() => {
293
+ this.#frames.delete(frame);
294
+ this.#settle(event);
295
+ });
296
+ this.#frames.add(frame);
297
+ });
298
+ };
299
+ #settle(event) {
300
+ if (!event.defaultPrevented) this.#onReset();
301
+ }
302
+ };
303
+
175
304
  var FULL_WIDTH_SHIFT = 65248;
176
305
  function halfWidthChar(char) {
177
306
  if (char >= "\uFF01" && char <= "\uFF5E") {
@@ -185,27 +314,22 @@ function toHalfWidth(text) {
185
314
  return out;
186
315
  }
187
316
 
188
- // src/utils/microtask_coalescer.ts
189
317
  var MicrotaskCoalescer = class {
190
318
  #run;
191
319
  #queued = false;
192
320
  #active = false;
193
321
  #generation = 0;
194
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
195
322
  constructor(run) {
196
323
  this.#run = run;
197
324
  }
198
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
199
325
  activate() {
200
326
  this.#active = true;
201
327
  }
202
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
203
328
  cancel() {
204
329
  this.#active = false;
205
330
  this.#queued = false;
206
331
  this.#generation += 1;
207
332
  }
208
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
209
333
  schedule() {
210
334
  if (!this.#active || this.#queued) return;
211
335
  this.#queued = true;
@@ -218,7 +342,38 @@ var MicrotaskCoalescer = class {
218
342
  }
219
343
  };
220
344
 
221
- // src/controllers/otp_controller.ts
345
+ var MoveCounter = class {
346
+ #sequence = 0;
347
+ record() {
348
+ this.#sequence += 1;
349
+ return this.#sequence;
350
+ }
351
+ isLatest(token) {
352
+ return token === this.#sequence;
353
+ }
354
+ };
355
+
356
+ var LEASE_MARKERS = {
357
+ "aria-invalid": "data-otp-invalid-lease",
358
+ "aria-errormessage": "data-otp-errormessage-lease",
359
+ "aria-describedby": "data-otp-describedby-lease"
360
+ };
361
+ function isErrorLease(value) {
362
+ const lease = value;
363
+ return typeof lease?.written === "string" && (lease.authored === null || typeof lease.authored === "string");
364
+ }
365
+ function readLease(field, marker) {
366
+ try {
367
+ const parsed = JSON.parse(field.getAttribute(marker) ?? "null");
368
+ return isErrorLease(parsed) ? parsed : null;
369
+ } catch {
370
+ return null;
371
+ }
372
+ }
373
+ function describedByWith(described, errorId, earlier) {
374
+ const tokens = (described ?? "").split(/\s+/).filter((token) => token.length > 0 && token !== errorId && token !== earlier);
375
+ return [...tokens, errorId].join(" ");
376
+ }
222
377
  var DEFAULT_PATTERN = "[0-9]";
223
378
  var DEFAULT_MATCHER = new RegExp(`^(?:${DEFAULT_PATTERN})$`);
224
379
  function hasModifier(event) {
@@ -228,51 +383,53 @@ function statesDiffer(left, right) {
228
383
  return left.value !== right.value || left.state !== right.state;
229
384
  }
230
385
  var OtpController = class extends Controller {
386
+ #moves = new MoveCounter();
231
387
  static targets = ["field", "value", "error"];
232
388
  static values = {
233
389
  pattern: { type: String, default: DEFAULT_PATTERN }
234
390
  };
235
391
  static actions = ["onInput", "onKeydown", "onPaste", "onPointerDown", "clear"];
236
392
  static events = ["change", "complete", "invalid", "reconcile"];
237
- /** Validated matcher; the hot path never compiles a raw declaration. */
238
393
  #pattern = DEFAULT_MATCHER;
239
- /** Source of {@link #pattern}, reported in `invalid` so consumers can word it. */
240
394
  #patternSource = DEFAULT_PATTERN;
241
- /** Public state carried by the last dispatch; keeps a no-op sync silent. */
242
395
  #published = null;
243
- /** Field whose confirming `input` after `compositionend` is already handled. */
244
- #confirmedField = null;
245
- /** True between connect and disconnect, so pre-connect Value changes stay silent. */
246
396
  #connected = false;
247
- /** Digit each field last committed, restored when rejected input replaced it. */
248
397
  #committed = /* @__PURE__ */ new WeakMap();
249
- /** Collapses one batch of field target callbacks into a single reconciliation. */
398
+ #composing = null;
399
+ #dropHeld = false;
250
400
  #reconcile = new MicrotaskCoalescer(() => this.#reconcileFields());
251
- #ariaInvalid = new AttributeLease("aria-invalid");
252
- #ariaErrorMessage = new AttributeLease("aria-errormessage");
253
- #ariaDescribedBy = new AttributeLease("aria-describedby");
254
- #errorHidden = new AttributeLease("hidden");
255
- #state = new AttributeLease("data-state");
256
- /** Rewinds the transient error surface before Turbo freezes the page. */
257
- #beforeCache = new BeforeCacheReset(() => this.#clearError());
258
- /** Owns IME lifecycle state across every digit field. */
401
+ #errorHidden = new AttributeLease("hidden", this.identifier);
402
+ #state = new AttributeLease("data-state", this.identifier);
403
+ #formReset = new FormResetWatcher(
404
+ (form) => this.#ownedBy(form),
405
+ () => {
406
+ this.#adopt();
407
+ this.#reconcile.schedule();
408
+ }
409
+ );
259
410
  #composition = new CompositionTracker({
260
- onStart: () => {
261
- this.#confirmedField = null;
411
+ onStart: (event) => {
412
+ const field = this.#fieldFrom(event);
413
+ if (field) this.#composing = { field, before: field.value };
262
414
  },
263
415
  onEnd: (event) => {
416
+ const composing = this.#composing;
417
+ this.#composing = null;
264
418
  const input = this.#fieldFrom(event);
265
- if (!input) return;
266
- this.#confirmedField = input;
267
- const committed = event.data ?? "";
268
- this.#accept(input, committed.length > input.value.length ? committed : input.value);
419
+ const data = event.data ?? "";
420
+ const text = input && data.length <= input.value.length ? input.value : data;
421
+ const commit = input !== null && (composing?.field !== input || text !== composing.before);
422
+ if (this.#dropHeld) {
423
+ if (input && commit) input.value = this.#committed.get(input) ?? "";
424
+ this.#reconcileFields();
425
+ }
426
+ if (input && commit) this.#accept(input, text);
269
427
  }
270
428
  });
271
429
  connect() {
272
430
  this.#connected = true;
273
431
  for (const field of this.fieldTargets) this.#bind(field);
274
- document.addEventListener("reset", this.#onReset, true);
275
- this.#beforeCache.activate();
432
+ this.#formReset.observe();
276
433
  this.#reconcile.activate();
277
434
  this.#adopt();
278
435
  this.#sync();
@@ -281,37 +438,47 @@ var OtpController = class extends Controller {
281
438
  this.#connected = false;
282
439
  for (const field of this.fieldTargets) this.#unbind(field);
283
440
  this.#composition.disconnect();
284
- document.removeEventListener("reset", this.#onReset, true);
285
- this.#beforeCache.deactivate();
441
+ this.#formReset.disconnect();
286
442
  this.#reconcile.cancel();
443
+ this.#dropHeld = false;
287
444
  this.#clearError();
288
445
  this.#state.return(this.element);
289
446
  }
290
- /**
291
- * Stimulus lifecycle callback when a new field target enters the DOM.
292
- * Wires the new field and folds the wider digit count into one reconciliation,
293
- * which stays inert until `connect()` opens the window.
294
- */
295
447
  fieldTargetConnected(element) {
296
448
  this.#bind(element);
297
449
  this.#adoptField(element);
298
450
  this.#reconcile.schedule();
299
451
  }
300
- /** Releases a dropped field's listeners and leases, then reconciles the rest. */
301
452
  fieldTargetDisconnected(element) {
302
453
  this.#unbind(element);
303
- this.#returnFieldLeases(element);
454
+ this.#returnLeases(element);
455
+ this.#reconcile.schedule();
456
+ }
457
+ valueTargetConnected() {
458
+ this.#reconcile.schedule();
459
+ }
460
+ valueTargetDisconnected() {
304
461
  this.#reconcile.schedule();
305
462
  }
306
- /**
307
- * Re-validates a changed `pattern` declaration once and drops any entered digit
308
- * the new pattern no longer accepts, so the combined value stays interpretable.
309
- */
463
+ errorTargetConnected() {
464
+ this.#followError();
465
+ }
466
+ errorTargetDisconnected(element) {
467
+ if (!this.errorTargets.includes(element)) this.#errorHidden.return(element);
468
+ this.#followError();
469
+ }
310
470
  patternValueChanged() {
311
471
  const compiled = compileRegExp(this.patternValue, "exact");
312
472
  this.#patternSource = compiled ? this.patternValue : DEFAULT_PATTERN;
313
473
  this.#pattern = compiled ?? DEFAULT_MATCHER;
314
474
  if (!this.#connected) return;
475
+ if (this.#composing !== null) {
476
+ this.#dropHeld = true;
477
+ return;
478
+ }
479
+ if (this.#dropRejected()) this.#reconcile.schedule();
480
+ }
481
+ #dropRejected() {
315
482
  let dropped = false;
316
483
  for (const field of this.fieldTargets) {
317
484
  if (field.value === "" || !this.#isWritable(field)) continue;
@@ -319,24 +486,19 @@ var OtpController = class extends Controller {
319
486
  this.#writeField(field, "");
320
487
  dropped = true;
321
488
  }
322
- if (dropped) this.#syncAndDispatch();
489
+ return dropped;
323
490
  }
324
- /** Handles keystroke inputs, distributes autofilled text, and advances focus. */
325
491
  onInput(event) {
326
492
  const input = this.#fieldFrom(event);
327
493
  if (!input) return;
328
- const confirmed = this.#confirmedField;
329
- this.#confirmedField = null;
330
- if (confirmed === input) return;
494
+ if (this.#composition.consumesConfirmedInput(event)) return;
331
495
  if (this.#composition.isComposing(event)) return;
332
496
  this.#accept(input);
333
497
  }
334
- /** Handles Backspace clearing, arrows, and home/end navigation. */
335
498
  onKeydown(event) {
336
499
  if (isReservedArrowChord(event)) return;
337
500
  const input = this.#fieldFrom(event);
338
501
  if (!input) return;
339
- this.#confirmedField = null;
340
502
  if (this.#composition.isComposing(event)) return;
341
503
  const index = this.fieldTargets.indexOf(input);
342
504
  const fields = this.fieldTargets;
@@ -395,19 +557,12 @@ var OtpController = class extends Controller {
395
557
  }
396
558
  }
397
559
  }
398
- /** Divides pasted string characters across the available input fields. */
399
560
  onPaste(event) {
400
561
  const input = this.#fieldFrom(event);
401
562
  if (!input) return;
402
563
  event.preventDefault();
403
- this.#confirmedField = null;
404
564
  this.#distribute(input, toHalfWidth(event.clipboardData?.getData("text") ?? ""));
405
565
  }
406
- /**
407
- * Redirects a pointer landing on an empty field to the earliest empty one, so
408
- * a passcode is entered in order. Filled fields stay directly reachable for
409
- * correction, and keyboard focus is left alone.
410
- */
411
566
  onPointerDown(event) {
412
567
  const input = this.#fieldFrom(event);
413
568
  if (input?.value !== "") return;
@@ -416,7 +571,6 @@ var OtpController = class extends Controller {
416
571
  event.preventDefault();
417
572
  first.focus();
418
573
  }
419
- /** Empties every writable field and restarts entry at the first of them. */
420
574
  clear() {
421
575
  for (const field of this.fieldTargets) {
422
576
  if (this.#isWritable(field)) this.#writeField(field, "");
@@ -431,17 +585,6 @@ var OtpController = class extends Controller {
431
585
  input.select();
432
586
  }
433
587
  };
434
- /** Reconciles derived state after a non-cancelled reset restores the fields. */
435
- #onReset = (event) => {
436
- const form = event.target;
437
- if (!(form instanceof HTMLFormElement) || !this.#ownedBy(form)) return;
438
- queueMicrotask(() => {
439
- if (event.defaultPrevented) return;
440
- this.#adopt();
441
- this.#syncAndDispatch();
442
- });
443
- };
444
- /** Whether a form owns at least one field or the hidden combined value. */
445
588
  #ownedBy(form) {
446
589
  if (this.fieldTargets.some((field) => field.form === form)) return true;
447
590
  return this.hasValueTarget && this.valueTarget.form === form;
@@ -453,43 +596,44 @@ var OtpController = class extends Controller {
453
596
  #unbind(field) {
454
597
  field.removeEventListener("focus", this.#onFieldFocus);
455
598
  this.#composition.unobserve(field);
456
- if (this.#confirmedField === field) this.#confirmedField = null;
599
+ if (this.#composing?.field === field) this.#composing = null;
457
600
  }
458
- /** Reads every field back so a restored or reset group starts consistent. */
459
601
  #adopt() {
460
602
  for (const field of this.fieldTargets) this.#adoptField(field);
461
603
  this.#clearError();
462
- if (this.hasErrorTarget) this.errorTarget.setAttribute("hidden", "");
463
604
  }
464
- /** Takes one field's current value as the truth behind its derived state. */
605
+ #lease(field, attribute, value) {
606
+ const marker = LEASE_MARKERS[attribute];
607
+ const held = readLease(field, marker);
608
+ const authored = held ? held.authored : field.getAttribute(attribute);
609
+ field.setAttribute(marker, JSON.stringify({ authored, written: value }));
610
+ field.setAttribute(attribute, value);
611
+ }
612
+ #returnLeases(field) {
613
+ for (const [attribute, marker] of Object.entries(LEASE_MARKERS)) {
614
+ const held = readLease(field, marker);
615
+ if (!held) continue;
616
+ field.removeAttribute(marker);
617
+ if (field.getAttribute(attribute) !== held.written) continue;
618
+ if (held.authored === null) field.removeAttribute(attribute);
619
+ else field.setAttribute(attribute, held.authored);
620
+ }
621
+ }
465
622
  #adoptField(field) {
466
623
  this.#committed.set(field, field.value);
467
624
  this.#markFilled(field, field.value);
468
625
  }
469
- /**
470
- * Absorbs a batch of field additions or removals as one state transition.
471
- *
472
- * The page, not the user, moved the state here, so it is reported as
473
- * `reconcile`: automation listening for `change` must not read a re-render as
474
- * an edit, and a passcode that happens to end up full must not fire the
475
- * `complete` that submits it.
476
- *
477
- * Completeness moves on its own when the field count changes: dropping a
478
- * trailing empty field completes a passcode whose combined value never moved,
479
- * and adding one un-completes it. Comparing the whole derived state, not the
480
- * string it contains, is what makes those transitions reportable.
481
- */
482
626
  #reconcileFields() {
627
+ if (this.#dropHeld && this.#composing === null) {
628
+ this.#dropHeld = false;
629
+ this.#dropRejected();
630
+ }
483
631
  const previous = this.#published;
484
632
  const current = this.#sync();
485
633
  if (previous && !statesDiffer(previous, current)) return;
634
+ this.#moves.record();
486
635
  this.dispatch("reconcile", { detail: { value: current.value } });
487
636
  }
488
- /**
489
- * Validates the text an entry point received and distributes what it accepts.
490
- * `text` defaults to the field's own value; a confirmation passes the string it
491
- * committed, which `maxlength` would otherwise have truncated.
492
- */
493
637
  #accept(input, text = input.value) {
494
638
  const raw = toHalfWidth(text);
495
639
  if (raw === "") {
@@ -500,10 +644,6 @@ var OtpController = class extends Controller {
500
644
  }
501
645
  this.#distribute(input, raw);
502
646
  }
503
- /**
504
- * Fills `text`'s accepted characters into the writable fields at and after the
505
- * entry point, then leaves focus on the field after the last one filled.
506
- */
507
647
  #distribute(from, text) {
508
648
  const accepted = Array.from(text).filter((char) => this.#pattern.test(char));
509
649
  let reached = false;
@@ -528,11 +668,9 @@ var OtpController = class extends Controller {
528
668
  this.#clearError();
529
669
  this.#syncAndDispatch();
530
670
  }
531
- /** Restores the digit a field committed before rejected input replaced it. */
532
671
  #restore(field) {
533
672
  this.#writeField(field, this.#committed.get(field) ?? "");
534
673
  }
535
- /** Commits one field's value and the derived hook that reports it as entered. */
536
674
  #writeField(field, value) {
537
675
  field.value = value;
538
676
  this.#committed.set(field, value);
@@ -561,27 +699,23 @@ var OtpController = class extends Controller {
561
699
  #focusableBefore(index) {
562
700
  return this.#before(index).find((field) => this.#isFocusable(field)) ?? null;
563
701
  }
564
- /** Fields before `index`, nearest first; empty at the first field. */
565
702
  #before(index) {
566
703
  return this.fieldTargets.slice(0, Math.max(index, 0)).reverse();
567
704
  }
568
- /** The event's field target, or `null` when the wiring points somewhere else. */
569
705
  #fieldFrom(event) {
570
706
  const input = event.currentTarget;
571
707
  return this.fieldTargets.find((field) => field === input) ?? null;
572
708
  }
573
709
  #combinedValue() {
574
- return this.fieldTargets.map((field) => field.value).join("");
710
+ return this.fieldTargets.map((field) => this.#publishedText(field)).join("");
575
711
  }
576
- /** Every field carries a character, and there is at least one field. */
577
712
  #isComplete() {
578
713
  const fields = this.fieldTargets;
579
- return fields.length > 0 && fields.every((field) => field.value.length > 0);
714
+ return fields.length > 0 && fields.every((field) => this.#publishedText(field).length > 0);
715
+ }
716
+ #publishedText(field) {
717
+ return field === this.#composing?.field ? this.#committed.get(field) ?? "" : field.value;
580
718
  }
581
- /**
582
- * Mirrors the combined value into the form and the root's readable state, and
583
- * records what was published so the next pass can compare against it.
584
- */
585
719
  #sync() {
586
720
  const combined = this.#combinedValue();
587
721
  if (this.hasValueTarget) {
@@ -601,42 +735,56 @@ var OtpController = class extends Controller {
601
735
  const previous = this.#published;
602
736
  const { value: combined } = this.#sync();
603
737
  if (previous?.value === combined) return;
738
+ const token = this.#moves.record();
604
739
  this.dispatch("change", { detail: { value: combined } });
740
+ if (!this.#moves.isLatest(token)) return;
605
741
  if (this.#isComplete()) {
606
742
  this.dispatch("complete", { detail: { value: combined } });
607
743
  }
608
744
  }
609
- /** Surfaces rejected input on every field and on the optional error target. */
610
745
  #showError() {
611
746
  const errorId = this.hasErrorTarget ? ensureId(this.errorTarget, "stimeo--otp-error") : null;
612
747
  for (const field of this.fieldTargets) {
613
- this.#ariaInvalid.write(field, "true");
614
- if (!errorId) continue;
615
- this.#ariaErrorMessage.write(field, errorId);
616
- this.#ariaDescribedBy.write(field, this.#describedByWith(field, errorId));
748
+ this.#lease(field, "aria-invalid", "true");
749
+ if (errorId) this.#pointAtError(field, errorId);
617
750
  }
618
751
  if (this.hasErrorTarget) this.#errorHidden.write(this.errorTarget, null);
619
752
  this.dispatch("invalid", { detail: { pattern: this.#patternSource } });
620
753
  }
621
- /** Returns every error lease, restoring the authored error surface. */
622
- #clearError() {
623
- this.#ariaInvalid.returnAll();
624
- this.#ariaErrorMessage.returnAll();
625
- this.#ariaDescribedBy.returnAll();
626
- this.#errorHidden.returnAll();
754
+ #pointAtError(field, errorId) {
755
+ const earlier = readLease(field, LEASE_MARKERS["aria-errormessage"])?.written ?? null;
756
+ this.#lease(field, "aria-errormessage", errorId);
757
+ const described = field.getAttribute("aria-describedby");
758
+ this.#lease(field, "aria-describedby", describedByWith(described, errorId, earlier));
759
+ }
760
+ #followError() {
761
+ if (!this.#connected) return;
762
+ const reported = this.fieldTargets.filter(
763
+ (field) => readLease(field, LEASE_MARKERS["aria-invalid"]) !== null
764
+ );
765
+ if (reported.length === 0) {
766
+ this.#hideErrors();
767
+ return;
768
+ }
769
+ if (!this.hasErrorTarget) return;
770
+ const errorId = ensureId(this.errorTarget, "stimeo--otp-error");
771
+ for (const field of reported) this.#pointAtError(field, errorId);
772
+ this.#errorHidden.write(this.errorTarget, null);
627
773
  }
628
- #returnFieldLeases(field) {
629
- this.#ariaInvalid.return(field);
630
- this.#ariaErrorMessage.return(field);
631
- this.#ariaDescribedBy.return(field);
774
+ #clearError() {
775
+ for (const field of this.fieldTargets) this.#returnLeases(field);
776
+ if (!this.#connected) {
777
+ this.#errorHidden.returnAll();
778
+ return;
779
+ }
780
+ this.#hideErrors();
632
781
  }
633
- /** The field's own description tokens with the error id appended once. */
634
- #describedByWith(field, errorId) {
635
- const tokens = (field.getAttribute("aria-describedby") ?? "").split(/\s+/).filter((token) => token.length > 0 && token !== errorId);
636
- return [...tokens, errorId].join(" ");
782
+ #hideErrors() {
783
+ const [first, ...others] = this.errorTargets;
784
+ for (const other of others) this.#errorHidden.return(other);
785
+ if (first) this.#errorHidden.write(first, "");
637
786
  }
638
787
  };
639
788
 
640
789
  export { OtpController };
641
- //# sourceMappingURL=otp_controller.js.map
642
790
  //# sourceMappingURL=otp_controller.js.map