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,169 +1,398 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/number_input_controller.ts
4
-
5
- // src/utils/arrow_step.ts
6
3
  function isReservedArrowChord(event, allow = []) {
7
4
  if (!event.key.startsWith("Arrow")) return false;
8
5
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
9
6
  }
7
+ function hasModifierChord(event) {
8
+ return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
9
+ }
10
+
11
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
12
+ if (element.getAttribute(name) === value) return false;
13
+ if (value === null) element.removeAttribute(name);
14
+ else element.setAttribute(name, value);
15
+ return true;
16
+ }
10
17
 
11
- // src/utils/attribute_lease.ts
18
+ function sharedRegistry(key, create) {
19
+ const symbol = Symbol.for(key);
20
+ const scope = globalThis;
21
+ const existing = scope[symbol];
22
+ if (existing !== void 0) return existing;
23
+ const registry2 = create();
24
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
25
+ return registry2;
26
+ }
27
+
28
+ var registry = sharedRegistry(
29
+ "stimeo-ui.attribute-lease.registry.v1",
30
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
31
+ );
32
+ function isAttributeValue(value) {
33
+ return value === null || typeof value === "string";
34
+ }
35
+ function readLeaseRecord(element, record) {
36
+ try {
37
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
38
+ if (!Array.isArray(parsed)) return void 0;
39
+ const [original, written, ...rest] = parsed;
40
+ const linked = rest[0] !== false;
41
+ const beneath = linked ? rest : rest.slice(1);
42
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
43
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
44
+ } catch {
45
+ return void 0;
46
+ }
47
+ }
48
+ function attributeStack(element, attribute) {
49
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
50
+ registry.stacks.set(element, byAttribute);
51
+ const known = byAttribute.get(attribute);
52
+ if (known) return known;
53
+ const found = [];
54
+ for (const record of registry.names.get(attribute) ?? []) {
55
+ const read = readLeaseRecord(element, record);
56
+ if (read) found.push({ ...read, record });
57
+ }
58
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
59
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
60
+ lease: null,
61
+ record,
62
+ written,
63
+ original,
64
+ linked: linked && index > 0
65
+ }));
66
+ byAttribute.set(attribute, stack);
67
+ return stack;
68
+ }
69
+ function findHold(stack, lease, record) {
70
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
71
+ }
12
72
  var AttributeLease = class {
13
73
  #attribute;
14
- #records = /* @__PURE__ */ new Map();
15
- /** @param attribute - The attribute whose temporary values this lease owns. */
16
- constructor(attribute) {
74
+ #record;
75
+ #base;
76
+ #preserveAuthor;
77
+ #held = /* @__PURE__ */ new Set();
78
+ constructor(attribute, owner, options = {}) {
17
79
  this.#attribute = attribute;
80
+ this.#record = `data-${owner}-${attribute}-lease`;
81
+ this.#base = options.base === true;
82
+ this.#preserveAuthor = options.preserveAuthor === true;
83
+ const names = registry.names;
84
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
18
85
  }
19
- /** Writes or removes the leased attribute while preserving its authored value. */
20
86
  write(element, value) {
21
- const existing = this.#records.get(element);
22
- if (existing) {
23
- existing.written = value;
24
- } else {
25
- this.#records.set(element, {
26
- original: element.getAttribute(this.#attribute),
27
- written: value
28
- });
29
- }
30
- this.#reflect(element, value);
87
+ const stack = attributeStack(element, this.#attribute);
88
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
89
+ this.#held.add(element);
90
+ const shows = !this.#covered(element, stack, hold);
91
+ hold.written = value;
92
+ this.#mark(element, stack);
93
+ if (shows) this.#reflect(element, value);
31
94
  }
32
- /** Returns one lease without overwriting a value subsequently authored by a consumer. */
33
95
  return(element) {
34
- const record = this.#records.get(element);
35
- if (!record) return;
36
- this.#records.delete(element);
37
- const stillOwned = element.getAttribute(this.#attribute) === record.written;
38
- if (stillOwned) this.#reflect(element, record.original);
39
- }
40
- /** Reflects only a real value transition, avoiding self-triggered mutation work. */
41
- #reflect(element, value) {
42
- if (element.getAttribute(this.#attribute) === value) return;
43
- if (value === null) element.removeAttribute(this.#attribute);
44
- else element.setAttribute(this.#attribute, value);
96
+ const held = this.#held.delete(element);
97
+ const known = registry.stacks.get(element)?.has(this.#attribute);
98
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
99
+ const hold = this.#take(stack);
100
+ if (!hold) return false;
101
+ const carries = element.getAttribute(this.#attribute) === hold.written;
102
+ const owns = carries && !this.#covered(element, stack, hold);
103
+ const index = stack.indexOf(hold);
104
+ stack.splice(index, 1);
105
+ const above = stack[index];
106
+ if (above) above.linked &&= hold.linked;
107
+ this.#mark(element, stack);
108
+ if (!owns) return true;
109
+ const below = hold.linked ? stack[index - 1] : void 0;
110
+ this.#reflect(element, below ? below.written : hold.original);
111
+ return true;
45
112
  }
46
- /** Returns every outstanding lease using the same ownership check as {@link return}. */
47
113
  returnAll() {
48
- for (const element of Array.from(this.#records.keys())) this.return(element);
114
+ for (const element of Array.from(this.#held)) this.return(element);
115
+ }
116
+ #take(stack) {
117
+ const hold = findHold(stack, this, this.#record);
118
+ if (hold) hold.lease = this;
119
+ return hold;
120
+ }
121
+ #acquire(element, stack) {
122
+ const current = element.getAttribute(this.#attribute);
123
+ const top = stack.at(-1);
124
+ const bottom = stack[0];
125
+ const over = top?.written === current ? top : void 0;
126
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
127
+ const hold = {
128
+ lease: this,
129
+ record: this.#record,
130
+ written: current,
131
+ original: from ? from.original : current,
132
+ linked: !this.#base && over !== void 0
133
+ };
134
+ if (this.#base && bottom) {
135
+ bottom.linked = true;
136
+ stack.unshift(hold);
137
+ } else stack.push(hold);
138
+ return hold;
139
+ }
140
+ #covered(element, stack, hold) {
141
+ const current = element.getAttribute(this.#attribute);
142
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
143
+ }
144
+ #mark(element, stack) {
145
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
146
+ stack.forEach((hold, index) => {
147
+ const names = stack.slice(0, index).map((under) => under.record);
148
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
149
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
150
+ records.set(
151
+ hold.record,
152
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
153
+ );
154
+ });
155
+ for (const [name, recorded] of records) {
156
+ setAttributeIfChanged(element, name, recorded);
157
+ }
158
+ }
159
+ #reflect(element, value) {
160
+ setAttributeIfChanged(element, this.#attribute, value);
49
161
  }
50
162
  };
51
163
 
52
- // src/utils/before_cache_reset.ts
53
- var BeforeCacheReset = class _BeforeCacheReset {
54
- /** Every subscribed instance, iterated by the one shared document listener. */
55
- static #subscribers = /* @__PURE__ */ new Set();
56
- /** The shared listener; installed while at least one instance is subscribed. */
57
- static #onBeforeCache = () => {
58
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
59
- };
60
- #rewind;
61
- /** @param rewind - the pass that returns this controller's state to its initial form. */
62
- constructor(rewind) {
63
- this.#rewind = rewind;
164
+ var BlurDeferral = class {
165
+ #pending = /* @__PURE__ */ new Map();
166
+ #onRelease;
167
+ constructor(onRelease) {
168
+ this.#onRelease = onRelease;
64
169
  }
65
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
66
- activate() {
67
- const first = _BeforeCacheReset.#subscribers.size === 0;
68
- _BeforeCacheReset.#subscribers.add(this);
69
- if (first) {
70
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
170
+ get size() {
171
+ return this.#pending.size;
172
+ }
173
+ get elements() {
174
+ return [...this.#pending.keys()];
175
+ }
176
+ has(element) {
177
+ return this.#pending.has(element);
178
+ }
179
+ defer(element) {
180
+ if (this.#pending.has(element)) return;
181
+ const onBlur = () => {
182
+ this.#detach(element);
183
+ this.#onRelease(element);
184
+ };
185
+ this.#pending.set(element, onBlur);
186
+ element.addEventListener("blur", onBlur);
187
+ }
188
+ deferOnly(element) {
189
+ for (const pending of this.elements) {
190
+ if (pending !== element) this.#detach(pending);
71
191
  }
192
+ this.defer(element);
193
+ }
194
+ release(element) {
195
+ this.#detach(element);
72
196
  }
73
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
74
- deactivate() {
75
- _BeforeCacheReset.#subscribers.delete(this);
76
- if (_BeforeCacheReset.#subscribers.size > 0) return;
77
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
197
+ releaseAll() {
198
+ for (const element of this.elements) this.#detach(element);
199
+ }
200
+ #detach(element) {
201
+ const onBlur = this.#pending.get(element);
202
+ if (onBlur) element.removeEventListener("blur", onBlur);
203
+ this.#pending.delete(element);
78
204
  }
79
205
  };
80
206
 
81
- // src/utils/composition_tracker.ts
82
207
  var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
83
208
  "insertCompositionText",
84
209
  "insertFromComposition",
85
210
  "deleteCompositionText",
86
211
  "deleteByComposition"
87
212
  ]);
213
+ function isCompositionInputType(inputType) {
214
+ return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
215
+ }
216
+ var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
217
+ var textOf = (element) => element.value;
218
+ var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
88
219
  var CompositionTracker = class {
89
220
  #observedTargets = /* @__PURE__ */ new Set();
90
- #activeTargets = /* @__PURE__ */ new Set();
221
+ #composition = null;
91
222
  #onStart;
92
223
  #onEnd;
93
- /** The field whose confirming `input` is still owed, while the window is open. */
224
+ #onAbandon;
94
225
  #confirmedTarget = null;
226
+ #keyComposing = false;
227
+ #connected = false;
228
+ #abandoned = null;
95
229
  constructor(options = {}) {
96
230
  this.#onStart = options.onStart;
97
231
  this.#onEnd = options.onEnd;
232
+ this.#onAbandon = options.onAbandon;
98
233
  }
99
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
100
234
  observe(target) {
235
+ this.#connected = true;
101
236
  if (this.#observedTargets.has(target)) return;
102
237
  target.addEventListener("compositionstart", this.#handleStart);
103
238
  target.addEventListener("compositionend", this.#handleEnd);
104
- target.addEventListener("keydown", this.#handleKeydown);
239
+ target.addEventListener("keydown", this.#handleKeydown, true);
240
+ target.addEventListener("beforeinput", this.#handleBeforeInput, true);
241
+ target.addEventListener("input", this.#handleInput, true);
242
+ target.addEventListener("blur", this.#handleBlur, true);
243
+ target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
105
244
  this.#observedTargets.add(target);
106
245
  }
107
- /** Stops tracking one target and clears any active composition it owned. */
108
246
  unobserve(target) {
109
247
  if (!this.#observedTargets.delete(target)) return;
110
- target.removeEventListener("compositionstart", this.#handleStart);
111
- target.removeEventListener("compositionend", this.#handleEnd);
112
- target.removeEventListener("keydown", this.#handleKeydown);
113
- this.#activeTargets.delete(target);
248
+ this.#release(target);
249
+ if (this.#composition?.target === target) this.#letGo();
114
250
  if (this.#confirmedTarget === target) this.#confirmedTarget = null;
115
251
  }
116
- /** Releases every listener and clears state so reconnect starts cleanly. */
117
252
  disconnect() {
118
- for (const target of this.#observedTargets) {
119
- target.removeEventListener("compositionstart", this.#handleStart);
120
- target.removeEventListener("compositionend", this.#handleEnd);
121
- target.removeEventListener("keydown", this.#handleKeydown);
122
- }
253
+ this.#connected = false;
254
+ for (const target of this.#observedTargets) this.#release(target);
123
255
  this.#observedTargets.clear();
124
- this.#activeTargets.clear();
256
+ this.#letGo();
125
257
  this.#confirmedTarget = null;
258
+ this.#keyComposing = false;
259
+ this.#abandoned = null;
126
260
  }
127
- /** True when lifecycle tracking or the current event reports composition. */
128
261
  isComposing(event) {
129
- return this.#activeTargets.size > 0 || event?.isComposing === true;
130
- }
131
- /**
132
- * Whether `event` is the `input` echoing the composition just confirmed.
133
- *
134
- * Asking closes the window either way, so one confirmation is folded at most
135
- * once and a consumer asks once per `input`.
136
- */
262
+ this.#settle();
263
+ return event?.isComposing === true || this.#composition !== null;
264
+ }
265
+ composingElement() {
266
+ this.#settle();
267
+ return this.#composition?.element ?? null;
268
+ }
269
+ owns(event) {
270
+ const element = this.composingElement();
271
+ return event.isComposing === true || element !== null && element === event.target;
272
+ }
137
273
  consumesConfirmedInput(event) {
138
274
  const confirmed = this.#confirmedTarget;
139
275
  this.#confirmedTarget = null;
140
276
  if (confirmed === null || confirmed !== event.target) return false;
141
277
  const inputType = event.inputType;
142
- return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
278
+ return !inputType || isCompositionInputType(inputType);
279
+ }
280
+ #release(target) {
281
+ target.removeEventListener("compositionstart", this.#handleStart);
282
+ target.removeEventListener("compositionend", this.#handleEnd);
283
+ target.removeEventListener("keydown", this.#handleKeydown, true);
284
+ target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
285
+ target.removeEventListener("input", this.#handleInput, true);
286
+ target.removeEventListener("blur", this.#handleBlur, true);
287
+ target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
288
+ }
289
+ #letGo() {
290
+ const composition = this.#composition;
291
+ if (composition === null) return;
292
+ if (!holdsFocus(composition.element)) {
293
+ this.#composition = null;
294
+ return;
295
+ }
296
+ queueMicrotask(() => {
297
+ if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
298
+ return;
299
+ }
300
+ this.#composition = null;
301
+ if (!this.#connected) return;
302
+ this.#abandoned = { element: composition.element, inPlace: true };
303
+ this.#runAbandon();
304
+ });
305
+ }
306
+ #settle() {
307
+ const composition = this.#composition;
308
+ if (composition === null) return;
309
+ const { target, element, text } = composition;
310
+ if (target instanceof Node && element instanceof Node && !target.contains(element)) {
311
+ this.#composition = null;
312
+ } else if (textOf(element) !== text) {
313
+ this.#abandon(false);
314
+ }
315
+ }
316
+ #abandon(inPlace) {
317
+ this.#owe(inPlace);
318
+ this.#composition = null;
319
+ queueMicrotask(this.#runAbandon);
320
+ }
321
+ #owe(inPlace) {
322
+ const composition = this.#composition;
323
+ if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
143
324
  }
325
+ #runAbandon = () => {
326
+ const abandoned = this.#abandoned;
327
+ if (abandoned === null) return;
328
+ this.#abandoned = null;
329
+ this.#onAbandon?.(abandoned.element, abandoned.inPlace);
330
+ };
144
331
  #handleStart = (event) => {
145
332
  this.#confirmedTarget = null;
146
- if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
333
+ this.#settle();
334
+ this.#owe(true);
335
+ const target = event.currentTarget;
336
+ const element = event.target;
337
+ this.#composition = { target, element, text: textOf(element) };
338
+ this.#runAbandon();
147
339
  this.#onStart?.(event);
148
340
  };
149
341
  #handleEnd = (event) => {
150
- if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
342
+ this.#runAbandon();
343
+ if (this.#composition?.target === event.currentTarget) this.#composition = null;
151
344
  this.#confirmedTarget = event.target;
152
345
  this.#onEnd?.(event);
153
346
  };
154
- /** A key on an observed field opens an edit of its own, so no echo is owed. */
155
- #handleKeydown = () => {
347
+ #handleKeydown = (event) => {
348
+ this.#settle();
349
+ this.#runAbandon();
156
350
  this.#confirmedTarget = null;
351
+ this.#keyComposing = event.isComposing === true;
352
+ };
353
+ #handleBeforeInput = (event) => {
354
+ const keyComposing = this.#keyComposing;
355
+ this.#keyComposing = false;
356
+ this.#settle();
357
+ const { isComposing, inputType } = event;
358
+ if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
359
+ this.#abandon(true);
360
+ }
361
+ this.#runAbandon();
362
+ };
363
+ #handleInput = (event) => {
364
+ const composition = this.#composition;
365
+ const { isTrusted, isComposing, inputType } = event;
366
+ if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
367
+ composition.text = textOf(composition.element);
368
+ } else {
369
+ this.#settle();
370
+ }
371
+ this.#runAbandon();
372
+ };
373
+ #handleBlur = (event) => {
374
+ this.#settle();
375
+ this.#runAbandon();
376
+ const composition = this.#composition;
377
+ if (composition === null || composition.target === composition.element) return;
378
+ const { element } = composition;
379
+ if (!(element instanceof Node) || event.target !== element) return;
380
+ const root = element.getRootNode();
381
+ if (root.activeElement !== element) this.#composition = null;
382
+ };
383
+ #handleBeforeMorphAttribute = (event) => {
384
+ this.#settle();
385
+ const composition = this.#composition;
386
+ if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
387
+ const detail = event.detail;
388
+ if (detail?.attributeName === "value") event.preventDefault();
157
389
  };
158
390
  };
159
391
 
160
- // src/utils/field_mirror.ts
161
- function commitEdit(control) {
162
- control.dispatchEvent(new Event("input", { bubbles: true }));
163
- control.dispatchEvent(new Event("change", { bubbles: true }));
392
+ function commitField(target) {
393
+ target.dispatchEvent(new Event("change", { bubbles: true }));
164
394
  }
165
395
 
166
- // src/utils/half_width.ts
167
396
  var FULL_WIDTH_SHIFT = 65248;
168
397
  function halfWidthChar(char) {
169
398
  if (char >= "\uFF01" && char <= "\uFF5E") {
@@ -177,27 +406,36 @@ function toHalfWidth(text) {
177
406
  return out;
178
407
  }
179
408
 
180
- // src/utils/microtask_coalescer.ts
409
+ var ListenerSet = class {
410
+ #abort = new AbortController();
411
+ add(target, type, handler, options) {
412
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
413
+ }
414
+ dispose() {
415
+ this.#abort.abort();
416
+ this.#abort = new AbortController();
417
+ }
418
+ };
419
+
181
420
  var MicrotaskCoalescer = class {
182
421
  #run;
183
422
  #queued = false;
184
423
  #active = false;
185
424
  #generation = 0;
186
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
187
425
  constructor(run) {
188
426
  this.#run = run;
189
427
  }
190
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
191
428
  activate() {
192
429
  this.#active = true;
193
430
  }
194
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
195
431
  cancel() {
196
432
  this.#active = false;
197
433
  this.#queued = false;
198
434
  this.#generation += 1;
199
435
  }
200
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
436
+ get pending() {
437
+ return this.#queued;
438
+ }
201
439
  schedule() {
202
440
  if (!this.#active || this.#queued) return;
203
441
  this.#queued = true;
@@ -210,43 +448,110 @@ var MicrotaskCoalescer = class {
210
448
  }
211
449
  };
212
450
 
213
- // src/utils/safe_timeout.ts
451
+ var MorphRenderWatcher = class {
452
+ #listeners = new ListenerSet();
453
+ #pass;
454
+ #element = null;
455
+ #onMorph = () => this.#pass.schedule();
456
+ constructor(run) {
457
+ this.#pass = new MicrotaskCoalescer(run);
458
+ }
459
+ observe(element) {
460
+ if (this.#element === element) return;
461
+ this.disconnect();
462
+ this.#element = element;
463
+ this.#pass.activate();
464
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
465
+ }
466
+ schedule() {
467
+ this.#pass.schedule();
468
+ }
469
+ get pending() {
470
+ return this.#pass.pending;
471
+ }
472
+ disconnect() {
473
+ this.#listeners.dispose();
474
+ this.#pass.cancel();
475
+ this.#element = null;
476
+ }
477
+ };
478
+
479
+ var MoveCounter = class {
480
+ #sequence = 0;
481
+ record() {
482
+ this.#sequence += 1;
483
+ return this.#sequence;
484
+ }
485
+ isLatest(token) {
486
+ return token === this.#sequence;
487
+ }
488
+ };
489
+
490
+ var NUMBER_BOUNDS = {
491
+ nonNegative: { finite: true, min: 0 },
492
+ positive: { finite: true, exclusiveMin: 0 },
493
+ lowerBound: { finite: true, allowInfinity: "negative" },
494
+ upperBound: { finite: true, allowInfinity: "positive" }};
495
+ function matchesNumberBounds(value, bounds) {
496
+ if (!Number.isFinite(value)) {
497
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
498
+ if (direction === null) return false;
499
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
500
+ }
501
+ if (bounds.min !== void 0 && value < bounds.min) return false;
502
+ if (bounds.max !== void 0 && value > bounds.max) return false;
503
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
504
+ if (bounds.integer && !Number.isInteger(value)) return false;
505
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
506
+ return true;
507
+ }
508
+
509
+ function readNumber(raw, fallback, bounds) {
510
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
511
+ }
512
+
513
+ var NumberValueReader = class {
514
+ #lastRejected = /* @__PURE__ */ new Map();
515
+ read(owner, name, raw, fallback, bounds) {
516
+ const resolved = readNumber(raw, fallback, bounds);
517
+ if (matchesNumberBounds(raw, bounds)) {
518
+ this.#lastRejected.delete(name);
519
+ return resolved;
520
+ }
521
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
522
+ const literal = owner.element.getAttribute(attribute);
523
+ if (literal === null) {
524
+ this.#lastRejected.delete(name);
525
+ return resolved;
526
+ }
527
+ if (this.#lastRejected.get(name) !== literal) {
528
+ this.#lastRejected.set(name, literal);
529
+ console.warn(
530
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
531
+ );
532
+ }
533
+ return resolved;
534
+ }
535
+ };
536
+
214
537
  var TimerRegistry = class {
215
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
216
538
  ids = /* @__PURE__ */ new Set();
217
- /**
218
- * Cancels a single tracked timer.
219
- *
220
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
221
- * registry), so callers can clear defensively without guarding.
222
- */
223
539
  clear(id) {
224
540
  if (this.ids.delete(id)) {
225
541
  this.cancel(id);
226
542
  }
227
543
  }
228
- /**
229
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
230
- * to guarantee no timer outlives the element.
231
- */
232
544
  clearAll() {
233
545
  for (const id of this.ids) {
234
546
  this.cancel(id);
235
547
  }
236
548
  this.ids.clear();
237
549
  }
238
- /** Number of timers currently tracked (pending). */
239
550
  get size() {
240
551
  return this.ids.size;
241
552
  }
242
553
  };
243
554
  var SafeTimeout = class extends TimerRegistry {
244
- /**
245
- * Schedules `callback` after `delay` ms and returns the timer id.
246
- *
247
- * The id is removed from the registry automatically when the timeout fires,
248
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
249
- */
250
555
  set(callback, delay) {
251
556
  const id = this.schedule(() => {
252
557
  this.ids.delete(id);
@@ -263,7 +568,6 @@ var SafeTimeout = class extends TimerRegistry {
263
568
  }
264
569
  };
265
570
  var SafeInterval = class extends TimerRegistry {
266
- /** Schedules a repeating `callback` every `delay` ms and returns the timer id. */
267
571
  set(callback, delay) {
268
572
  const id = this.schedule(callback, delay);
269
573
  this.ids.add(id);
@@ -277,7 +581,6 @@ var SafeInterval = class extends TimerRegistry {
277
581
  }
278
582
  };
279
583
 
280
- // src/utils/stepped_value.ts
281
584
  function effectiveStep(step) {
282
585
  return Number.isFinite(step) && step > 0 ? step : 1;
283
586
  }
@@ -383,9 +686,12 @@ function finiteFallback(min, max) {
383
686
  return 0;
384
687
  }
385
688
 
386
- // src/controllers/number_input_controller.ts
387
689
  var OWNED_DISABLED = "data-number-input-disabled";
690
+ var sameRange = (shown, declared) => shown?.min === declared.min && shown.max === declared.max && shown.step === declared.step;
388
691
  var NumberInputController = class _NumberInputController extends Controller {
692
+ #numbers = new NumberValueReader();
693
+ #moves = new MoveCounter();
694
+ #move = 0;
389
695
  static targets = ["input", "increment", "decrement"];
390
696
  static values = {
391
697
  min: { type: Number, default: Number.NEGATIVE_INFINITY },
@@ -393,93 +699,70 @@ var NumberInputController = class _NumberInputController extends Controller {
393
699
  step: { type: Number, default: 1 },
394
700
  pageStep: { type: Number, default: 0 }
395
701
  };
702
+ static valueConstraints = {
703
+ min: NUMBER_BOUNDS.lowerBound,
704
+ max: NUMBER_BOUNDS.upperBound,
705
+ step: NUMBER_BOUNDS.positive,
706
+ pageStep: NUMBER_BOUNDS.nonNegative
707
+ };
396
708
  static actions = ["decrement", "increment", "onInput", "onKeydown"];
397
709
  static events = ["change", "reconcile"];
398
- /** Delay (ms) a button must be held before auto-repeat starts. */
399
710
  static #HOLD_DELAY_MS = 400;
400
- /** Interval (ms) between auto-repeat steps once a hold has started. */
401
711
  static #HOLD_REPEAT_MS = 80;
402
- /**
403
- * Window (ms) after a hold ends during which the trailing synthetic `click` is
404
- * swallowed. A safety net for the (rare) case where that click never arrives
405
- * (e.g. the pointer was released off the button): the flag self-clears instead
406
- * of poisoning the next legitimate click.
407
- */
408
712
  static #SUPPRESS_RESET_MS = 250;
409
- /** Aborts global pointer guards on disconnect so none outlive the element. */
410
- #globalGuards = null;
411
- /** Per-target guards allow an old button to be released without touching its replacement. */
713
+ #listeners = new ListenerSet();
412
714
  #buttonGuards = /* @__PURE__ */ new Map();
413
- /**
414
- * Tracks IME lifecycle on the current input, including confirming keys without a
415
- * signal, and runs a reconciliation the composition held back once it ends.
416
- */
417
715
  #composition = new CompositionTracker({
418
- onEnd: () => {
419
- if (this.#reconcileHeld) this.#repaint.schedule();
716
+ onEnd: (event) => {
717
+ if (this.hasInputTarget && event.target === this.inputTarget) {
718
+ this.#recordDraft(this.inputTarget);
719
+ }
720
+ },
721
+ onAbandon: () => {
722
+ if (this.#reconcileHeld) this.#reconcile();
420
723
  }
421
724
  });
422
- /** A reconciliation that arrived while the input was composing. */
423
725
  #reconcileHeld = false;
424
- /** Restores authored custom-spinbutton ARIA when a target leaves or the controller stops. */
425
- #beforeCache = new BeforeCacheReset(() => this.#rewindForCache());
426
- #ariaValueNow = new AttributeLease("aria-valuenow");
427
- #ariaValueMin = new AttributeLease("aria-valuemin");
428
- #ariaValueMax = new AttributeLease("aria-valuemax");
429
- /**
430
- * The number shown last — reconciled or committed by the user — which the next
431
- * move is measured from; `null` while the field is blank, and before this
432
- * instance has shown one. A disconnect keeps it for the same instance
433
- * connecting again.
434
- */
726
+ #ariaValueNow = new AttributeLease("aria-valuenow", this.identifier);
727
+ #ariaValueMin = new AttributeLease("aria-valuemin", this.identifier);
728
+ #ariaValueMax = new AttributeLease("aria-valuemax", this.identifier);
435
729
  #lastValue = null;
436
- /**
437
- * Whether `connect()` has run and `disconnect()` has not since. The pass
438
- * `connect()` runs takes the baseline without reporting it.
439
- */
730
+ #accepted = null;
731
+ #draft = null;
732
+ #parked = new BlurDeferral((input) => {
733
+ if (document.activeElement === input) this.#parked.defer(input);
734
+ else if (this.#draft?.input === input) this.#draft = null;
735
+ });
736
+ #shown = null;
440
737
  #connected = false;
441
- /** Timers for the hold delay and the suppress-reset safety net. */
442
738
  #holdTimeouts = new SafeTimeout();
443
- /** The running auto-repeat interval (one at a time). */
444
739
  #holdIntervals = new SafeInterval();
445
- /** True while a hold is armed/running, making `#stopHold` idempotent. */
446
740
  #holdActive = false;
447
- /** True once a hold actually produced a repeated step (vs. a quick click). */
741
+ #holdGeneration = 0;
448
742
  #repeatedDuringHold = false;
449
- /** True when the next `click` is the trailing one after a hold and must be ignored. */
450
743
  #suppressNextClick = false;
451
- /** Button and pointer that own the current hold, so other instances/pointers cannot end it. */
452
744
  #holdButton = null;
453
745
  #holdPointerId = null;
454
- /** Collapses runtime range/step changes into one input reconciliation. */
455
- #repaint = new MicrotaskCoalescer(() => this.#reconcile());
456
- /** Normalizes any initial value and wires the focus/hold pointer guards. */
746
+ #repaint = new MorphRenderWatcher(() => this.#reconcile());
457
747
  connect() {
458
- this.#repaint.activate();
459
- this.#beforeCache.activate();
460
- this.#globalGuards = new AbortController();
461
- const { signal } = this.#globalGuards;
748
+ this.#repaint.observe(this.element);
462
749
  for (const button of this.incrementTargets) this.#wireButton(button, 1);
463
750
  for (const button of this.decrementTargets) this.#wireButton(button, -1);
464
- window.addEventListener("pointerup", this.#onPointerEnd, { signal });
465
- window.addEventListener("pointercancel", this.#onPointerEnd, { signal });
466
- window.addEventListener("blur", this.#onWindowBlur, { signal });
751
+ this.#listeners.add(window, "pointerup", this.#onPointerEnd);
752
+ this.#listeners.add(window, "pointercancel", this.#onPointerEnd);
753
+ this.#listeners.add(window, "blur", this.#onWindowBlur);
754
+ this.#listeners.add(this.element, "input", this.#onDraftInput);
755
+ this.#listeners.add(this.element, "turbo:before-morph-attribute", this.#onBeforeMorphAttribute);
467
756
  this.#reconcile();
468
757
  this.#connected = true;
469
758
  }
470
- /**
471
- * Releases listeners, derived attributes, timers, and the hold state. The number
472
- * shown last stays: the pass the next `connect()` runs takes a new one without a
473
- * report from an input that is present, and with none it stays the number an
474
- * input arriving later is compared with.
475
- */
476
759
  disconnect() {
477
760
  this.#connected = false;
478
- this.#repaint.cancel();
761
+ this.#repaint.disconnect();
479
762
  this.#composition.disconnect();
480
- this.#beforeCache.deactivate();
481
- this.#globalGuards?.abort();
482
- this.#globalGuards = null;
763
+ this.#listeners.dispose();
764
+ if (this.#draft?.input !== document.activeElement) this.#draft = null;
765
+ if (this.#draft !== null) this.#parked.deferOnly(this.#draft.input);
483
766
  const buttons = /* @__PURE__ */ new Set([
484
767
  ...this.#buttonGuards.keys(),
485
768
  ...this.incrementTargets,
@@ -492,74 +775,78 @@ var NumberInputController = class _NumberInputController extends Controller {
492
775
  this.#repeatedDuringHold = false;
493
776
  this.#suppressNextClick = false;
494
777
  this.#holdTimeouts.clearAll();
495
- this.#holdIntervals.clearAll();
496
778
  this.#ariaValueNow.returnAll();
497
779
  this.#ariaValueMin.returnAll();
498
780
  this.#ariaValueMax.returnAll();
499
781
  }
500
- /** Reconciles the value against a minimum changed by application code or a Turbo morph. */
501
782
  minValueChanged() {
502
783
  this.#repaint.schedule();
503
784
  }
504
- /** Reconciles the value against a maximum changed by application code or a Turbo morph. */
505
785
  maxValueChanged() {
506
786
  this.#repaint.schedule();
507
787
  }
508
- /** Reconciles the value against a step changed by application code or a Turbo morph. */
509
788
  stepValueChanged() {
510
789
  this.#repaint.schedule();
511
790
  }
512
- /** Tracks and reconciles an input inserted or replaced at runtime. */
513
791
  inputTargetConnected(input) {
514
792
  this.#composition.observe(input);
515
793
  this.#repaint.schedule();
516
794
  }
517
- /**
518
- * Releases ARIA and composition state owned by an input that left the controller.
519
- * The number it showed stays the baseline, so an input swapped in holding another
520
- * number is reported as `reconcile`.
521
- */
522
795
  inputTargetDisconnected(input) {
796
+ const composing = this.#composition.composingElement() === input;
523
797
  this.#composition.unobserve(input);
798
+ if (composing && this.#composition.composingElement() === input && this.inputTargets.includes(input)) {
799
+ return;
800
+ }
524
801
  this.#releaseInputAria(input);
525
802
  this.#stopHold(false);
526
803
  this.#repaint.schedule();
527
804
  }
528
- /** Wires focus preservation and hold behavior on a runtime increment target. */
529
805
  incrementTargetConnected(button) {
530
806
  this.#wireButton(button, 1);
531
807
  this.#repaint.schedule();
532
808
  }
533
- /** Releases only the increment target that actually disconnected. */
534
809
  incrementTargetDisconnected(button) {
535
810
  this.#unwireButton(button);
536
- this.#repaint.schedule();
537
811
  }
538
- /** Wires focus preservation and hold behavior on a runtime decrement target. */
539
812
  decrementTargetConnected(button) {
540
813
  this.#wireButton(button, -1);
541
814
  this.#repaint.schedule();
542
815
  }
543
- /** Releases only the decrement target that actually disconnected. */
544
816
  decrementTargetDisconnected(button) {
545
817
  this.#unwireButton(button);
546
- this.#repaint.schedule();
547
818
  }
548
- /** Increases by one step. Bound via `data-action` (click). */
549
819
  increment() {
550
820
  if (this.#consumeSuppressedClick()) return;
551
821
  if (!this.hasInputTarget) return;
552
822
  this.#commitStep(1);
553
823
  this.inputTarget.focus();
554
824
  }
555
- /** Decreases by one step. Bound via `data-action` (click). */
556
825
  decrement() {
557
826
  if (this.#consumeSuppressedClick()) return;
558
827
  if (!this.hasInputTarget) return;
559
828
  this.#commitStep(-1);
560
829
  this.inputTarget.focus();
561
830
  }
562
- /** Clamps and snaps a typed value. Bound via `data-action` (change). */
831
+ #onDraftInput = (event) => {
832
+ if (!this.hasInputTarget) return;
833
+ const input = this.inputTarget;
834
+ if (event.target !== input || document.activeElement !== input) return;
835
+ this.#recordDraft(input);
836
+ };
837
+ #recordDraft(input) {
838
+ this.#draft = this.#accepted?.input === input && input.value !== this.#accepted.text ? { input, text: input.value } : null;
839
+ if (this.#draft === null) this.#resumeHeldReconcile();
840
+ }
841
+ #onBeforeMorphAttribute = (event) => {
842
+ if (!this.hasInputTarget || event.target !== this.inputTarget) return;
843
+ const detail = event.detail;
844
+ if (detail?.attributeName === "value" && this.#isEditing(this.inputTarget))
845
+ event.preventDefault();
846
+ };
847
+ #isEditing(input) {
848
+ return this.#draft?.input === input && this.#accepted?.input === input && document.activeElement === input && input.value === this.#draft.text && input.value !== this.#accepted.text;
849
+ }
563
850
  onInput() {
564
851
  if (!this.hasInputTarget) return;
565
852
  if (this.inputTarget.value.trim() === "") {
@@ -569,11 +856,12 @@ var NumberInputController = class _NumberInputController extends Controller {
569
856
  }
570
857
  this.#commit(this.#currentValue());
571
858
  }
572
- /** Keyboard stepping per the APG spinbutton model. */
573
859
  onKeydown(event) {
574
860
  if (!this.hasInputTarget) return;
575
861
  if (this.#composition.isComposing(event)) return;
576
862
  if (isReservedArrowChord(event)) return;
863
+ if ((event.key === "Home" || event.key === "End" || event.key === "PageUp" || event.key === "PageDown") && hasModifierChord(event))
864
+ return;
577
865
  let next = null;
578
866
  switch (event.key) {
579
867
  case "ArrowUp":
@@ -583,18 +871,18 @@ var NumberInputController = class _NumberInputController extends Controller {
583
871
  next = stepSteppedValue(this.#currentValue(), -1, this.#steppedRange);
584
872
  break;
585
873
  case "PageUp":
586
- next = this.pageStepValue > 0 ? this.#currentValue() + this.pageStepValue : stepSteppedValue(this.#currentValue(), 10, this.#steppedRange);
874
+ next = this.#safePageStep > 0 ? this.#currentValue() + this.#safePageStep : stepSteppedValue(this.#currentValue(), 10, this.#steppedRange);
587
875
  break;
588
876
  case "PageDown":
589
- next = this.pageStepValue > 0 ? this.#currentValue() - this.pageStepValue : stepSteppedValue(this.#currentValue(), -10, this.#steppedRange);
877
+ next = this.#safePageStep > 0 ? this.#currentValue() - this.#safePageStep : stepSteppedValue(this.#currentValue(), -10, this.#steppedRange);
590
878
  break;
591
879
  case "Home":
592
- if (!Number.isFinite(this.minValue)) return;
593
- next = this.minValue;
880
+ if (!Number.isFinite(this.#safeMin)) return;
881
+ next = this.#safeMin;
594
882
  break;
595
883
  case "End":
596
- if (!Number.isFinite(this.maxValue)) return;
597
- next = this.maxValue;
884
+ if (!Number.isFinite(this.#safeMax)) return;
885
+ next = this.#safeMax;
598
886
  break;
599
887
  default:
600
888
  return;
@@ -602,11 +890,6 @@ var NumberInputController = class _NumberInputController extends Controller {
602
890
  event.preventDefault();
603
891
  this.#commit(next, true);
604
892
  }
605
- /**
606
- * Wires a step button: `pointerdown` keeps focus on the input and arms the
607
- * hold; leaving the button while held stops it (the global listeners cover
608
- * release/cancel/blur).
609
- */
610
893
  #wireButton(button, direction) {
611
894
  if (this.#buttonGuards.has(button)) return;
612
895
  const guard = new AbortController();
@@ -622,14 +905,12 @@ var NumberInputController = class _NumberInputController extends Controller {
622
905
  { signal: guard.signal }
623
906
  );
624
907
  }
625
- /** Aborts one target's listeners and returns any state derived onto that button. */
626
908
  #unwireButton(button) {
627
909
  this.#buttonGuards.get(button)?.abort();
628
910
  this.#buttonGuards.delete(button);
629
911
  if (this.#holdButton === button) this.#stopHold(false);
630
912
  this.#releaseButtonState(button);
631
913
  }
632
- /** Starts a hold: focus the input, then schedule the first repeat after a delay. */
633
914
  #armHold(event, button, direction) {
634
915
  const pointerButton = event.button;
635
916
  if (typeof pointerButton === "number" && pointerButton !== 0) return;
@@ -642,25 +923,19 @@ var NumberInputController = class _NumberInputController extends Controller {
642
923
  this.#holdPointerId = this.#pointerId(event);
643
924
  this.#repeatedDuringHold = false;
644
925
  this.#suppressNextClick = false;
926
+ const generation = ++this.#holdGeneration;
645
927
  this.#holdTimeouts.set(() => {
646
- if (!this.#commitStep(direction)) {
647
- this.#stopHold();
648
- return;
649
- }
928
+ if (!this.#commitStep(direction) || generation !== this.#holdGeneration) return;
650
929
  this.#repeatedDuringHold = true;
651
930
  this.#holdIntervals.set(() => {
652
931
  if (!this.#commitStep(direction)) this.#stopHold();
653
932
  }, _NumberInputController.#HOLD_REPEAT_MS);
654
933
  }, _NumberInputController.#HOLD_DELAY_MS);
655
934
  }
656
- /**
657
- * Stops the current hold (idempotent). When the hold actually repeated, the
658
- * trailing synthetic `click` must be ignored, so it is suppressed until the
659
- * click consumes it or a short safety-net timeout clears it.
660
- */
661
935
  #stopHold(suppressTrailingClick = true) {
662
936
  if (!this.#holdActive) return;
663
937
  this.#holdActive = false;
938
+ this.#holdGeneration += 1;
664
939
  this.#holdButton = null;
665
940
  this.#holdPointerId = null;
666
941
  this.#holdTimeouts.clearAll();
@@ -670,133 +945,145 @@ var NumberInputController = class _NumberInputController extends Controller {
670
945
  this.#holdTimeouts.set(() => {
671
946
  this.#suppressNextClick = false;
672
947
  }, _NumberInputController.#SUPPRESS_RESET_MS);
673
- } else if (!suppressTrailingClick) {
674
- this.#suppressNextClick = false;
675
948
  }
676
949
  }
677
- /** Ends a hold only for its initiating pointer (legacy/synthetic events remain accepted). */
678
950
  #onPointerEnd = (event) => {
679
951
  if (this.#ownsPointer(event)) this.#stopHold();
680
952
  };
681
- /** Window deactivation is global and always ends the active hold. */
682
953
  #onWindowBlur = () => {
683
954
  this.#stopHold();
684
955
  };
685
- /** Extracts pointer identity when the runtime event exposes it. */
686
956
  #pointerId(event) {
687
957
  return event.pointerId ?? null;
688
958
  }
689
- /** Whether `event` belongs to the active pointer, with a synthetic-event fallback. */
690
959
  #ownsPointer(event) {
691
960
  const pointerId = this.#pointerId(event);
692
961
  return this.#holdActive && (this.#holdPointerId === null || pointerId === null || pointerId === this.#holdPointerId);
693
962
  }
694
- /** Consumes a pending trailing-click suppression; returns true if the click was swallowed. */
695
963
  #consumeSuppressedClick() {
696
964
  if (!this.#suppressNextClick) return false;
697
965
  this.#suppressNextClick = false;
698
966
  this.#holdTimeouts.clearAll();
699
967
  return true;
700
968
  }
701
- /**
702
- * Normalizes `raw`, reflects it, and dispatches `{ value: number }` only when
703
- * the committed numeric value differs from the last reconciled value.
704
- *
705
- * @returns Whether the value changed (drives the auto-repeat's bound stop).
706
- */
707
969
  #commit(raw, report = false) {
708
970
  const value = this.#normalize(raw);
709
971
  const changed = this.#lastValue === null || value !== this.#lastValue;
710
972
  const shown = this.inputTarget.value;
711
- this.#write(value);
712
973
  this.#lastValue = value;
713
- if (report && shown !== this.inputTarget.value) commitEdit(this.inputTarget);
974
+ if (changed) this.#move = this.#moves.record();
975
+ const move = this.#move;
976
+ this.#write(value);
977
+ if (!this.#moves.isLatest(move)) return changed;
978
+ if (report && shown !== this.inputTarget.value) {
979
+ const input = this.inputTarget;
980
+ input.dispatchEvent(new Event("input", { bubbles: true }));
981
+ if (!this.#moves.isLatest(move)) return changed;
982
+ commitField(input);
983
+ }
984
+ if (!this.#moves.isLatest(move)) return changed;
714
985
  if (changed) this.dispatch("change", { detail: { value } });
715
986
  return changed;
716
987
  }
717
- /** Commits an adjacent endpoint/grid value and reports whether it moved. */
718
988
  #commitStep(count) {
719
989
  if (!this.hasInputTarget) return false;
720
990
  return this.#commit(stepSteppedValue(this.#currentValue(), count, this.#steppedRange), true);
721
991
  }
722
- /**
723
- * Reflects the current value after a range, step or input change, reporting a
724
- * number that moved from the one shown last as `reconcile`. While the input is
725
- * composing, its text is the IME's: the pass waits for the composition to end
726
- * instead of reading and rewriting it. With no input there is nothing to show,
727
- * and the number shown last stays the baseline for the input that comes next.
728
- *
729
- * @stimeoRenderRoot
730
- */
992
+ #resumeHeldReconcile() {
993
+ if (this.#reconcileHeld) this.#repaint.schedule();
994
+ }
731
995
  #reconcile() {
732
- if (!this.hasInputTarget) return;
733
- if (this.#composition.isComposing()) {
734
- this.#reconcileHeld = true;
996
+ if (!this.hasInputTarget) {
997
+ if (this.#draft?.input !== document.activeElement) this.#draft = null;
998
+ if (this.#draft !== null) this.#parked.deferOnly(this.#draft.input);
735
999
  return;
736
1000
  }
737
- this.#reconcileHeld = false;
1001
+ this.#parked.releaseAll();
738
1002
  const previous = this.#lastValue;
739
- if (this.inputTarget.value.trim() !== "") {
740
- const value = this.#normalize(this.#currentValue());
741
- this.#write(value);
742
- this.#lastValue = value;
1003
+ const input = this.inputTarget;
1004
+ if (this.#composition.composingElement() === input || this.#isEditing(input)) {
1005
+ const shown = this.#shown?.input === input ? this.#shown : null;
1006
+ this.#reconcileHeld = shown === null || !sameRange(shown.range, this.#steppedRange);
1007
+ if (shown !== null) this.#show(shown.value, shown.at, shown.range);
743
1008
  } else {
744
- this.#lastValue = null;
745
- this.#reflectEmpty();
1009
+ this.#draft = null;
1010
+ this.#reconcileHeld = false;
1011
+ const shown = this.#shown;
1012
+ if (shown !== null && this.#accepted?.input === input && input.value === this.#accepted.text && sameRange(shown.range, this.#steppedRange)) {
1013
+ const move = this.#move;
1014
+ this.#show(shown.value, shown.at, shown.range);
1015
+ if (!this.#moves.isLatest(move)) return;
1016
+ } else if (input.value.trim() !== "") {
1017
+ const value = this.#normalize(this.#currentValue());
1018
+ this.#lastValue = value;
1019
+ const move = this.#move;
1020
+ this.#write(value, true);
1021
+ if (!this.#moves.isLatest(move)) return;
1022
+ } else {
1023
+ this.#lastValue = null;
1024
+ this.#reflectEmpty();
1025
+ }
746
1026
  }
747
1027
  this.#reportMove(previous);
748
1028
  }
749
- /**
750
- * Reports the number shown as `reconcile` when a change the page made moved it
751
- * from `previous`. The page moved it, so it is reported apart from `change`,
752
- * which stays reserved for user edits. A blank field reports nothing, the way a
753
- * user blanking it does: both events carry a number.
754
- */
755
1029
  #reportMove(previous) {
756
1030
  const value = this.#lastValue;
757
1031
  if (!this.#connected || value === null || value === previous) return;
758
1032
  this.dispatch("reconcile", { detail: { value } });
759
1033
  }
760
- /** Reflects `value` on the input (and ARIA for non-native hosts) and the buttons. */
761
- #write(value) {
1034
+ #write(value, keepCaret = false) {
762
1035
  const input = this.inputTarget;
763
- input.value = String(value);
764
- this.#syncInputAria(input, value);
765
- this.#updateButtons(value);
1036
+ const text = String(value);
1037
+ if (input.value !== text) {
1038
+ const caret = keepCaret && input === document.activeElement ? input.selectionStart : null;
1039
+ input.value = text;
1040
+ if (caret !== null) {
1041
+ const position = Math.min(caret, text.length);
1042
+ input.setSelectionRange(position, position);
1043
+ }
1044
+ }
1045
+ this.#draft = null;
1046
+ this.#reconcileHeld = false;
1047
+ this.#accepted = { input, text };
1048
+ this.#show(value, value);
1049
+ }
1050
+ #show(value, at, range = this.#steppedRange) {
1051
+ this.#shown = { input: this.inputTarget, value, at, range };
1052
+ this.#syncInputAria(this.inputTarget, value, range);
1053
+ this.#updateButtons(at, range);
766
1054
  }
767
- /** Reflects a blank value without inventing a numeric `aria-valuenow`. */
768
1055
  #reflectEmpty() {
769
- this.#syncInputAria(this.inputTarget, null);
770
- this.#updateButtons(this.#currentValue());
1056
+ this.#draft = null;
1057
+ this.#reconcileHeld = false;
1058
+ this.#accepted = { input: this.inputTarget, text: this.inputTarget.value };
1059
+ this.#show(null, this.#currentValue());
771
1060
  }
772
- /** Synchronizes controller-derived ARIA while preserving authored teardown values. */
773
- #syncInputAria(input, value) {
1061
+ #syncInputAria(input, value, range) {
774
1062
  if (input.getAttribute("role") !== "spinbutton") {
775
1063
  this.#releaseInputAria(input);
776
1064
  return;
777
1065
  }
778
1066
  this.#ariaValueNow.write(input, value === null ? null : String(value));
779
- this.#ariaValueMin.write(input, Number.isFinite(this.minValue) ? String(this.minValue) : null);
780
- this.#ariaValueMax.write(input, Number.isFinite(this.maxValue) ? String(this.maxValue) : null);
1067
+ this.#ariaValueMin.write(input, Number.isFinite(range.min) ? String(range.min) : null);
1068
+ this.#ariaValueMax.write(input, Number.isFinite(range.max) ? String(range.max) : null);
781
1069
  }
782
- /** Returns all custom-spinbutton ARIA leased on `input`. */
783
1070
  #releaseInputAria(input) {
784
1071
  this.#ariaValueNow.return(input);
785
1072
  this.#ariaValueMin.return(input);
786
1073
  this.#ariaValueMax.return(input);
787
1074
  }
788
- /** Disables a step button at its bound, returning focus to the input first. */
789
- #updateButtons(value) {
790
- for (const button of this.incrementTargets) this.#toggleButton(button, value < this.maxValue);
791
- for (const button of this.decrementTargets) this.#toggleButton(button, value > this.minValue);
792
- }
793
- /**
794
- * Enables or disables a step button at its bound, never disabling one while it
795
- * holds focus. Owns only the `disabled` it sets itself via a marker
796
- * (`data-number-input-disabled`, like `conditional-fields`/`submit-once`), so an
797
- * author-disabled button (e.g. the whole control disabled) is never re-enabled.
798
- */
799
- #toggleButton(button, enabled) {
1075
+ #updateButtons(value, range) {
1076
+ const move = this.#move;
1077
+ for (const button of this.incrementTargets) {
1078
+ this.#toggleButton(button, value < range.max, move);
1079
+ if (!this.#moves.isLatest(move)) return;
1080
+ }
1081
+ for (const button of this.decrementTargets) {
1082
+ this.#toggleButton(button, value > range.min, move);
1083
+ if (!this.#moves.isLatest(move)) return;
1084
+ }
1085
+ }
1086
+ #toggleButton(button, enabled, move) {
800
1087
  if (enabled) {
801
1088
  if (button.hasAttribute(OWNED_DISABLED)) {
802
1089
  button.disabled = false;
@@ -806,44 +1093,64 @@ var NumberInputController = class _NumberInputController extends Controller {
806
1093
  }
807
1094
  if (button.disabled) return;
808
1095
  if (document.activeElement === button && this.hasInputTarget) this.inputTarget.focus();
1096
+ if (!this.#moves.isLatest(move)) return;
809
1097
  button.disabled = true;
810
1098
  button.setAttribute(OWNED_DISABLED, "");
811
1099
  }
812
- /** Returns `disabled` only when its explicit marker still identifies controller ownership. */
813
1100
  #releaseButtonState(button) {
814
1101
  if (!button.hasAttribute(OWNED_DISABLED)) return;
815
1102
  button.disabled = false;
816
1103
  button.removeAttribute(OWNED_DISABLED);
817
1104
  }
818
- /**
819
- * The current numeric value, falling back to a finite min (else 0) when blank.
820
- * Every read of the field's number comes through here, and the text is read
821
- * through the shared half-width mapping: a text-type spinbutton holds whatever an
822
- * IME confirmed, full-width digits and signs included (`34`, `-5`), while a
823
- * native number field's value is already half-width or empty.
824
- */
825
1105
  #currentValue() {
826
1106
  const text = toHalfWidth(this.inputTarget.value);
827
1107
  const parsed = Number(text);
828
1108
  if (Number.isFinite(parsed) && text.trim() !== "") return parsed;
829
- return Number.isFinite(this.minValue) ? this.minValue : 0;
1109
+ return Number.isFinite(this.#safeMin) ? this.#safeMin : 0;
830
1110
  }
831
- /** Clamps to `[min, max]` and snaps to the step grid anchored at a finite min (else 0). */
832
1111
  #normalize(raw) {
833
1112
  return snapSteppedValue(raw, this.#steppedRange);
834
1113
  }
835
- /** Shared range configuration; finite endpoints remain allowed off the grid. */
836
1114
  get #steppedRange() {
837
- return { min: this.minValue, max: this.maxValue, step: this.stepValue };
1115
+ return { min: this.#safeMin, max: this.#safeMax, step: this.#safeStep };
1116
+ }
1117
+ get #safeMin() {
1118
+ return this.#numbers.read(
1119
+ this,
1120
+ "min",
1121
+ this.minValue,
1122
+ _NumberInputController.values.min.default,
1123
+ _NumberInputController.valueConstraints.min
1124
+ );
838
1125
  }
839
- /** Returns borrowed range ARIA before Turbo snapshots the page. */
840
- #rewindForCache() {
841
- this.#ariaValueNow.returnAll();
842
- this.#ariaValueMin.returnAll();
843
- this.#ariaValueMax.returnAll();
1126
+ get #safeMax() {
1127
+ return this.#numbers.read(
1128
+ this,
1129
+ "max",
1130
+ this.maxValue,
1131
+ _NumberInputController.values.max.default,
1132
+ _NumberInputController.valueConstraints.max
1133
+ );
1134
+ }
1135
+ get #safeStep() {
1136
+ return this.#numbers.read(
1137
+ this,
1138
+ "step",
1139
+ this.stepValue,
1140
+ _NumberInputController.values.step.default,
1141
+ _NumberInputController.valueConstraints.step
1142
+ );
1143
+ }
1144
+ get #safePageStep() {
1145
+ return this.#numbers.read(
1146
+ this,
1147
+ "pageStep",
1148
+ this.pageStepValue,
1149
+ _NumberInputController.values.pageStep.default,
1150
+ _NumberInputController.valueConstraints.pageStep
1151
+ );
844
1152
  }
845
1153
  };
846
1154
 
847
1155
  export { NumberInputController };
848
- //# sourceMappingURL=number_input_controller.js.map
849
1156
  //# sourceMappingURL=number_input_controller.js.map