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,87 +1,190 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/currency_input_controller.ts
4
-
5
- // src/utils/composition_tracker.ts
6
3
  var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
7
4
  "insertCompositionText",
8
5
  "insertFromComposition",
9
6
  "deleteCompositionText",
10
7
  "deleteByComposition"
11
8
  ]);
9
+ function isCompositionInputType(inputType) {
10
+ return inputType !== void 0 && COMPOSITION_INPUT_TYPES.has(inputType);
11
+ }
12
+ var BEFORE_MORPH_ATTRIBUTE = "turbo:before-morph-attribute";
13
+ var textOf = (element) => element.value;
14
+ var holdsFocus = (element) => element instanceof Node && element.getRootNode().activeElement === element;
12
15
  var CompositionTracker = class {
13
16
  #observedTargets = /* @__PURE__ */ new Set();
14
- #activeTargets = /* @__PURE__ */ new Set();
17
+ #composition = null;
15
18
  #onStart;
16
19
  #onEnd;
17
- /** The field whose confirming `input` is still owed, while the window is open. */
20
+ #onAbandon;
18
21
  #confirmedTarget = null;
22
+ #keyComposing = false;
23
+ #connected = false;
24
+ #abandoned = null;
19
25
  constructor(options = {}) {
20
26
  this.#onStart = options.onStart;
21
27
  this.#onEnd = options.onEnd;
28
+ this.#onAbandon = options.onAbandon;
22
29
  }
23
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
24
30
  observe(target) {
31
+ this.#connected = true;
25
32
  if (this.#observedTargets.has(target)) return;
26
33
  target.addEventListener("compositionstart", this.#handleStart);
27
34
  target.addEventListener("compositionend", this.#handleEnd);
28
- target.addEventListener("keydown", this.#handleKeydown);
35
+ target.addEventListener("keydown", this.#handleKeydown, true);
36
+ target.addEventListener("beforeinput", this.#handleBeforeInput, true);
37
+ target.addEventListener("input", this.#handleInput, true);
38
+ target.addEventListener("blur", this.#handleBlur, true);
39
+ target.addEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
29
40
  this.#observedTargets.add(target);
30
41
  }
31
- /** Stops tracking one target and clears any active composition it owned. */
32
42
  unobserve(target) {
33
43
  if (!this.#observedTargets.delete(target)) return;
34
- target.removeEventListener("compositionstart", this.#handleStart);
35
- target.removeEventListener("compositionend", this.#handleEnd);
36
- target.removeEventListener("keydown", this.#handleKeydown);
37
- this.#activeTargets.delete(target);
44
+ this.#release(target);
45
+ if (this.#composition?.target === target) this.#letGo();
38
46
  if (this.#confirmedTarget === target) this.#confirmedTarget = null;
39
47
  }
40
- /** Releases every listener and clears state so reconnect starts cleanly. */
41
48
  disconnect() {
42
- for (const target of this.#observedTargets) {
43
- target.removeEventListener("compositionstart", this.#handleStart);
44
- target.removeEventListener("compositionend", this.#handleEnd);
45
- target.removeEventListener("keydown", this.#handleKeydown);
46
- }
49
+ this.#connected = false;
50
+ for (const target of this.#observedTargets) this.#release(target);
47
51
  this.#observedTargets.clear();
48
- this.#activeTargets.clear();
52
+ this.#letGo();
49
53
  this.#confirmedTarget = null;
54
+ this.#keyComposing = false;
55
+ this.#abandoned = null;
50
56
  }
51
- /** True when lifecycle tracking or the current event reports composition. */
52
57
  isComposing(event) {
53
- return this.#activeTargets.size > 0 || event?.isComposing === true;
54
- }
55
- /**
56
- * Whether `event` is the `input` echoing the composition just confirmed.
57
- *
58
- * Asking closes the window either way, so one confirmation is folded at most
59
- * once and a consumer asks once per `input`.
60
- */
58
+ this.#settle();
59
+ return event?.isComposing === true || this.#composition !== null;
60
+ }
61
+ composingElement() {
62
+ this.#settle();
63
+ return this.#composition?.element ?? null;
64
+ }
65
+ owns(event) {
66
+ const element = this.composingElement();
67
+ return event.isComposing === true || element !== null && element === event.target;
68
+ }
61
69
  consumesConfirmedInput(event) {
62
70
  const confirmed = this.#confirmedTarget;
63
71
  this.#confirmedTarget = null;
64
72
  if (confirmed === null || confirmed !== event.target) return false;
65
73
  const inputType = event.inputType;
66
- return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
74
+ return !inputType || isCompositionInputType(inputType);
75
+ }
76
+ #release(target) {
77
+ target.removeEventListener("compositionstart", this.#handleStart);
78
+ target.removeEventListener("compositionend", this.#handleEnd);
79
+ target.removeEventListener("keydown", this.#handleKeydown, true);
80
+ target.removeEventListener("beforeinput", this.#handleBeforeInput, true);
81
+ target.removeEventListener("input", this.#handleInput, true);
82
+ target.removeEventListener("blur", this.#handleBlur, true);
83
+ target.removeEventListener(BEFORE_MORPH_ATTRIBUTE, this.#handleBeforeMorphAttribute);
84
+ }
85
+ #letGo() {
86
+ const composition = this.#composition;
87
+ if (composition === null) return;
88
+ if (!holdsFocus(composition.element)) {
89
+ this.#composition = null;
90
+ return;
91
+ }
92
+ queueMicrotask(() => {
93
+ if (this.#composition !== composition || this.#observedTargets.has(composition.target)) {
94
+ return;
95
+ }
96
+ this.#composition = null;
97
+ if (!this.#connected) return;
98
+ this.#abandoned = { element: composition.element, inPlace: true };
99
+ this.#runAbandon();
100
+ });
101
+ }
102
+ #settle() {
103
+ const composition = this.#composition;
104
+ if (composition === null) return;
105
+ const { target, element, text } = composition;
106
+ if (target instanceof Node && element instanceof Node && !target.contains(element)) {
107
+ this.#composition = null;
108
+ } else if (textOf(element) !== text) {
109
+ this.#abandon(false);
110
+ }
111
+ }
112
+ #abandon(inPlace) {
113
+ this.#owe(inPlace);
114
+ this.#composition = null;
115
+ queueMicrotask(this.#runAbandon);
67
116
  }
117
+ #owe(inPlace) {
118
+ const composition = this.#composition;
119
+ if (composition !== null) this.#abandoned = { element: composition.element, inPlace };
120
+ }
121
+ #runAbandon = () => {
122
+ const abandoned = this.#abandoned;
123
+ if (abandoned === null) return;
124
+ this.#abandoned = null;
125
+ this.#onAbandon?.(abandoned.element, abandoned.inPlace);
126
+ };
68
127
  #handleStart = (event) => {
69
128
  this.#confirmedTarget = null;
70
- if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
129
+ this.#settle();
130
+ this.#owe(true);
131
+ const target = event.currentTarget;
132
+ const element = event.target;
133
+ this.#composition = { target, element, text: textOf(element) };
134
+ this.#runAbandon();
71
135
  this.#onStart?.(event);
72
136
  };
73
137
  #handleEnd = (event) => {
74
- if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
138
+ this.#runAbandon();
139
+ if (this.#composition?.target === event.currentTarget) this.#composition = null;
75
140
  this.#confirmedTarget = event.target;
76
141
  this.#onEnd?.(event);
77
142
  };
78
- /** A key on an observed field opens an edit of its own, so no echo is owed. */
79
- #handleKeydown = () => {
143
+ #handleKeydown = (event) => {
144
+ this.#settle();
145
+ this.#runAbandon();
80
146
  this.#confirmedTarget = null;
147
+ this.#keyComposing = event.isComposing === true;
148
+ };
149
+ #handleBeforeInput = (event) => {
150
+ const keyComposing = this.#keyComposing;
151
+ this.#keyComposing = false;
152
+ this.#settle();
153
+ const { isComposing, inputType } = event;
154
+ if (this.#composition !== null && !keyComposing && !isComposing && inputType && !isCompositionInputType(inputType)) {
155
+ this.#abandon(true);
156
+ }
157
+ this.#runAbandon();
158
+ };
159
+ #handleInput = (event) => {
160
+ const composition = this.#composition;
161
+ const { isTrusted, isComposing, inputType } = event;
162
+ if (composition !== null && event.target === composition.element && (isTrusted || isComposing || isCompositionInputType(inputType))) {
163
+ composition.text = textOf(composition.element);
164
+ } else {
165
+ this.#settle();
166
+ }
167
+ this.#runAbandon();
168
+ };
169
+ #handleBlur = (event) => {
170
+ this.#settle();
171
+ this.#runAbandon();
172
+ const composition = this.#composition;
173
+ if (composition === null || composition.target === composition.element) return;
174
+ const { element } = composition;
175
+ if (!(element instanceof Node) || event.target !== element) return;
176
+ const root = element.getRootNode();
177
+ if (root.activeElement !== element) this.#composition = null;
178
+ };
179
+ #handleBeforeMorphAttribute = (event) => {
180
+ this.#settle();
181
+ const composition = this.#composition;
182
+ if (composition?.target !== event.currentTarget || event.target !== composition.element) return;
183
+ const detail = event.detail;
184
+ if (detail?.attributeName === "value") event.preventDefault();
81
185
  };
82
186
  };
83
187
 
84
- // src/utils/half_width.ts
85
188
  var FULL_WIDTH_SHIFT = 65248;
86
189
  function halfWidthChar(char) {
87
190
  if (char >= "\uFF01" && char <= "\uFF5E") {
@@ -90,7 +193,6 @@ function halfWidthChar(char) {
90
193
  return char === "\u3000" ? " " : char;
91
194
  }
92
195
 
93
- // src/utils/intl_format.ts
94
196
  var formatters = /* @__PURE__ */ new WeakMap();
95
197
  function intlFormatter(factory, locale, options, fallbackLocale) {
96
198
  const cache = cacheFor(factory);
@@ -120,65 +222,275 @@ function construct(factory, locale, options, fallbackLocale) {
120
222
  }
121
223
  }
122
224
 
123
- // src/utils/locale.ts
225
+ var ListenerSet = class {
226
+ #abort = new AbortController();
227
+ add(target, type, handler, options) {
228
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
229
+ }
230
+ dispose() {
231
+ this.#abort.abort();
232
+ this.#abort = new AbortController();
233
+ }
234
+ };
235
+
124
236
  function resolveLocale(element, declared = "") {
125
237
  return declared || element.closest("[lang]")?.getAttribute("lang") || void 0;
126
238
  }
127
239
 
128
- // src/controllers/currency_input_controller.ts
240
+ var MicrotaskCoalescer = class {
241
+ #run;
242
+ #queued = false;
243
+ #active = false;
244
+ #generation = 0;
245
+ constructor(run) {
246
+ this.#run = run;
247
+ }
248
+ activate() {
249
+ this.#active = true;
250
+ }
251
+ cancel() {
252
+ this.#active = false;
253
+ this.#queued = false;
254
+ this.#generation += 1;
255
+ }
256
+ get pending() {
257
+ return this.#queued;
258
+ }
259
+ schedule() {
260
+ if (!this.#active || this.#queued) return;
261
+ this.#queued = true;
262
+ const generation = this.#generation;
263
+ queueMicrotask(() => {
264
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
265
+ this.#queued = false;
266
+ this.#run();
267
+ });
268
+ }
269
+ };
270
+
271
+ var MorphRenderWatcher = class {
272
+ #listeners = new ListenerSet();
273
+ #pass;
274
+ #element = null;
275
+ #onMorph = () => this.#pass.schedule();
276
+ constructor(run) {
277
+ this.#pass = new MicrotaskCoalescer(run);
278
+ }
279
+ observe(element) {
280
+ if (this.#element === element) return;
281
+ this.disconnect();
282
+ this.#element = element;
283
+ this.#pass.activate();
284
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
285
+ }
286
+ schedule() {
287
+ this.#pass.schedule();
288
+ }
289
+ get pending() {
290
+ return this.#pass.pending;
291
+ }
292
+ disconnect() {
293
+ this.#listeners.dispose();
294
+ this.#pass.cancel();
295
+ this.#element = null;
296
+ }
297
+ };
298
+
299
+ function matchesNumberBounds(value, bounds) {
300
+ if (!Number.isFinite(value)) {
301
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
302
+ if (direction === null) return false;
303
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
304
+ }
305
+ if (bounds.min !== void 0 && value < bounds.min) return false;
306
+ if (bounds.max !== void 0 && value > bounds.max) return false;
307
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
308
+ if (bounds.integer && !Number.isInteger(value)) return false;
309
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
310
+ return true;
311
+ }
312
+
313
+ function readNumber(raw, fallback, bounds) {
314
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
315
+ }
316
+
317
+ var NumberValueReader = class {
318
+ #lastRejected = /* @__PURE__ */ new Map();
319
+ read(owner, name, raw, fallback, bounds) {
320
+ const resolved = readNumber(raw, fallback, bounds);
321
+ if (matchesNumberBounds(raw, bounds)) {
322
+ this.#lastRejected.delete(name);
323
+ return resolved;
324
+ }
325
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
326
+ const literal = owner.element.getAttribute(attribute);
327
+ if (literal === null) {
328
+ this.#lastRejected.delete(name);
329
+ return resolved;
330
+ }
331
+ if (this.#lastRejected.get(name) !== literal) {
332
+ this.#lastRejected.set(name, literal);
333
+ console.warn(
334
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
335
+ );
336
+ }
337
+ return resolved;
338
+ }
339
+ };
340
+
341
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
342
+ if (element.getAttribute(name) === value) return false;
343
+ if (value === null) element.removeAttribute(name);
344
+ else element.setAttribute(name, value);
345
+ return true;
346
+ }
347
+
348
+ var OwnedText = class {
349
+ #attribute;
350
+ #channel;
351
+ #guard;
352
+ #writes = /* @__PURE__ */ new WeakMap();
353
+ constructor(attribute, channel = "text", guard = () => true) {
354
+ this.#attribute = attribute;
355
+ this.#channel = channel;
356
+ this.#guard = guard;
357
+ }
358
+ hasRecord(element) {
359
+ return element.hasAttribute(this.#attribute);
360
+ }
361
+ read(element, evidence = {}) {
362
+ if (evidence.source === "live") return this.#writes.get(element) ?? null;
363
+ const raw = element.getAttribute(this.#attribute);
364
+ if (raw === null) return null;
365
+ try {
366
+ const record = JSON.parse(raw);
367
+ if (!Array.isArray(record) || record.length !== 3 || record[0] !== this.#channel || typeof record[1] !== "string" || typeof record[2] !== "string")
368
+ return null;
369
+ return { original: record[1], written: record[2] };
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+ owns(element, evidence = {}) {
375
+ return this.#ownedRecord(element, evidence) !== null;
376
+ }
377
+ #ownedRecord(element, evidence) {
378
+ const record = this.read(element, evidence);
379
+ if (record === null || !this.#guard(element)) return null;
380
+ if (this.#channel === "text" && element.firstElementChild !== null) return null;
381
+ const current = this.#current(element);
382
+ return current === record.written || evidence.source === "live" && evidence.allowOriginal === true && current === record.original ? record : null;
383
+ }
384
+ write(element, written, original, force = false) {
385
+ const current = this.#current(element);
386
+ if (current === null) return;
387
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
388
+ if (force || current !== written) this.#put(element, written);
389
+ this.remember(element, written, before);
390
+ }
391
+ remember(element, written, original) {
392
+ const current = this.#current(element);
393
+ if (current === null) return;
394
+ const before = original ?? this.#writes.get(element)?.original ?? this.read(element)?.original ?? current;
395
+ this.#writes.set(element, { original: before, written });
396
+ setAttributeIfChanged(
397
+ element,
398
+ this.#attribute,
399
+ JSON.stringify([this.#channel, before, written])
400
+ );
401
+ }
402
+ clear(element, evidence = {}) {
403
+ if (!this.owns(element, evidence)) return false;
404
+ if (this.#current(element) !== "") this.#put(element, "");
405
+ this.forget(element);
406
+ return true;
407
+ }
408
+ restore(element, evidence = {}) {
409
+ const record = this.#ownedRecord(element, evidence);
410
+ if (record === null) return false;
411
+ if (this.#current(element) !== record.original) this.#put(element, record.original);
412
+ this.forget(element);
413
+ return true;
414
+ }
415
+ forget(element) {
416
+ setAttributeIfChanged(element, this.#attribute, null);
417
+ this.#writes.delete(element);
418
+ }
419
+ #current(element) {
420
+ if (this.#channel === "text") return element.textContent ?? "";
421
+ return element instanceof HTMLInputElement ? element.value : null;
422
+ }
423
+ #put(element, text) {
424
+ if (this.#channel === "text") element.textContent = text;
425
+ else if (element instanceof HTMLInputElement) element.value = text;
426
+ }
427
+ };
428
+
129
429
  var FALLBACK_LOCALE = "en-US";
130
- var CurrencyInputController = class extends Controller {
430
+ var CurrencyInputController = class _CurrencyInputController extends Controller {
431
+ #numbers = new NumberValueReader();
131
432
  static targets = ["display", "field", "srValue"];
132
433
  static values = {
133
434
  locale: { type: String, default: "" },
134
435
  currency: { type: String, default: "" },
135
436
  precision: { type: Number, default: 2 }
136
437
  };
438
+ static valueConstraints = {
439
+ precision: { finite: true, integer: true, min: 0, max: 100 }
440
+ };
137
441
  static actions = ["format", "onInput"];
138
442
  static events = ["change", "reconcile"];
139
- /** The numeric value last committed; both events report only a move away from it. */
140
443
  #lastValue = null;
141
444
  #started = false;
142
- /** A declaration change that arrived mid-composition, applied once nothing composes. */
143
- #changeHeld = false;
144
- /** The display a composition is running on, until that composition ends. */
145
- #composing = null;
146
- /**
147
- * The display text this controller last rendered. It tells this controller's own
148
- * text, written with the separators in force, from text the page wrote.
149
- */
150
- #rendered = null;
151
- /** Validated mirrors of the Values; the hot path never reads a raw Value. */
445
+ #unsettled = null;
446
+ #configured = false;
447
+ #listeners = new ListenerSet();
448
+ #declarationsHeld = false;
449
+ #displayText = new OwnedText(
450
+ `data-${this.identifier}-display-text`,
451
+ "value"
452
+ );
453
+ #renderedDisplay = null;
454
+ #departed = null;
455
+ #formatHeld = null;
152
456
  #locale;
153
457
  #currency = "";
154
458
  #precision = 2;
155
- /** Formatters rebuilt when the declarations are taken in, never per keystroke. */
156
459
  #grouping;
157
460
  #fixed;
158
461
  #accessible;
159
462
  #group = ",";
160
463
  #decimal = ".";
161
- /** The locale's non-Latin digits mapped back to ASCII (empty for Latin locales). */
162
464
  #digits = /* @__PURE__ */ new Map();
163
- /**
164
- * Holds mid-composition input so the IME's uncommitted text is never rewritten.
165
- * The confirmed text is formatted once, and a declaration change the composition
166
- * held back is applied after it.
167
- */
168
465
  #composition = new CompositionTracker({
169
- onStart: (event) => {
170
- this.#composing = event.currentTarget;
466
+ onEnd: (event) => {
467
+ const display = event.currentTarget;
468
+ if (this.#unsettled === display) this.#unsettled = null;
469
+ const round2 = this.#takeFormatHeld(display);
470
+ if (this.#inCharge(display)) this.#reformat(round2, "edit");
471
+ if (this.#owed(display)) this.#applyValueChange();
472
+ if (round2 && !this.#inCharge(display)) this.#reformat(true, "edit");
473
+ },
474
+ onAbandon: (display, inPlace) => {
475
+ const next = this.#composition.composingElement();
476
+ if (inPlace && next !== null && next !== display) {
477
+ this.#endUntrackedComposition(display);
478
+ } else {
479
+ this.#formatHeld = null;
480
+ }
481
+ if (next === display && inPlace || !this.#owed(display)) return;
482
+ if (next !== null) {
483
+ this.#readPageText(next);
484
+ return;
485
+ }
486
+ if (inPlace) this.#endUntrackedComposition(display);
487
+ if (this.#owed(display)) this.#applyValueChange();
171
488
  },
172
- onEnd: () => {
173
- this.#composing = null;
174
- this.#reformat(false, "edit");
175
- this.#applyHeldChange();
489
+ onStart: (event) => {
490
+ if (this.#unsettled === event.target) this.#unsettled = null;
491
+ if (this.#formatHeld?.heard === false) this.#formatHeld = null;
176
492
  }
177
493
  });
178
- /**
179
- * Re-validates on declaration changes and re-renders the committed display,
180
- * reporting a value the re-render moved as `reconcile`.
181
- */
182
494
  localeValueChanged() {
183
495
  this.#applyValueChange();
184
496
  }
@@ -188,73 +500,57 @@ var CurrencyInputController = class extends Controller {
188
500
  precisionValueChanged() {
189
501
  this.#applyValueChange();
190
502
  }
191
- /**
192
- * Reads the display with the declarations its text was written for, then
193
- * re-renders it under the new ones — so a locale switch re-interprets the
194
- * value, never the old text with new separators, and text the page wrote for
195
- * the declarations it changes is never read with the old ones.
196
- *
197
- * While the display is composing, its text is the IME's: the change is held,
198
- * configuration and all, until the composition ends, so the confirmed text is
199
- * read with the separators it was typed against before the change applies.
200
- *
201
- * @stimeoRenderRoot
202
- */
203
503
  #applyValueChange() {
504
+ if (!this.#started) return;
505
+ if (this.#holdUnsettled(false)) return;
204
506
  if (this.#composition.isComposing()) {
205
- this.#changeHeld = true;
507
+ this.#declarationsHeld = this.#declarationsChanged();
508
+ this.#resync();
206
509
  return;
207
510
  }
208
- if (!this.#started || !this.hasDisplayTarget) return;
209
- const scan = this.#readDisplay();
511
+ if (!this.hasDisplayTarget) return;
512
+ const { scan, anchor } = this.#readDisplay();
210
513
  if (document.activeElement === this.displayTarget) {
211
514
  const formatted = this.#render(scan.parts);
212
- this.#show(formatted);
515
+ this.#showAtCaret(formatted, anchor);
213
516
  this.#reflect(scan.value, formatted, "reconcile");
214
517
  } else {
215
518
  this.#renderFixed(scan.value, "reconcile");
216
519
  }
217
520
  }
218
- /** Applies a declaration change a composition held back. */
219
- #applyHeldChange() {
220
- if (!this.#changeHeld) return;
221
- this.#changeHeld = false;
222
- this.#applyValueChange();
223
- }
224
- /**
225
- * Reads the display and takes in the declarations the page carries now. This
226
- * controller's own rendering was written with the separators in force, so it
227
- * is read before they change; any other text is the page's, written for the
228
- * declarations it carries, so it is read after.
229
- */
230
521
  #readDisplay() {
231
522
  const text = this.displayTarget.value;
232
- const own = text === this.#rendered ? this.#scan(text) : null;
233
- this.#takeInDeclarations();
234
- return own ?? this.#scan(text);
235
- }
236
- /** Takes in the declarations the page carries now, a change a composition held among them. */
237
- #takeInDeclarations() {
238
- this.#changeHeld = false;
523
+ const own = this.#showsOwnRendering(this.displayTarget) ? this.#read(text) : null;
239
524
  this.#revalidate();
525
+ return own ?? this.#read(text);
240
526
  }
241
- /**
242
- * Takes in the declarations the page carries now and normalizes the display
243
- * to its fixed-precision form.
244
- *
245
- * A display still showing this controller's own last rendering is not read
246
- * back: its separators may belong to declarations that changed while the
247
- * controller was away, and the committed value is known anyway. That value is
248
- * re-rendered at the current fixed precision, which rounds it whether
249
- * `precision` changed meanwhile or the entry was left unrounded mid-typing, and
250
- * a move the rounding causes is reported as `reconcile`. Any other text is the
251
- * page's, written for the declarations it carries, so it is read with them.
252
- */
527
+ #showsOwnRendering(display) {
528
+ return display === this.#renderedDisplay && this.#displayText.owns(display, { source: "live" });
529
+ }
530
+ #inCharge(source) {
531
+ return source === null || this.hasDisplayTarget && source === this.displayTarget;
532
+ }
533
+ #read(text) {
534
+ return {
535
+ scan: this.#scan(text),
536
+ anchor: this.#significantBefore(text, this.displayTarget.selectionStart)
537
+ };
538
+ }
539
+ #morph = new MorphRenderWatcher(() => this.#applyValueChange());
253
540
  connect() {
541
+ this.#morph.observe(this.element);
542
+ this.#listeners.add(this.element, "turbo:before-morph-attribute", this.#protectUnsettled);
254
543
  this.#started = true;
255
- this.#takeInDeclarations();
544
+ if (!this.#configured) this.#revalidate();
545
+ if (this.#composition.isComposing()) {
546
+ this.#applyValueChange();
547
+ return;
548
+ }
549
+ this.#formatHeld = null;
550
+ if (this.#holdUnsettled(true)) return;
551
+ this.#revalidate();
256
552
  if (!this.hasDisplayTarget) return;
257
- if (this.displayTarget.value === this.#rendered) {
553
+ if (this.#showsOwnRendering(this.displayTarget)) {
258
554
  this.#renderFixed(this.#lastValue, "reconcile");
259
555
  return;
260
556
  }
@@ -262,75 +558,112 @@ var CurrencyInputController = class extends Controller {
262
558
  this.#lastValue = value === null ? null : round(value, this.#precision);
263
559
  this.#renderFixed(value, "reconcile");
264
560
  }
265
- /**
266
- * Releases the composition listeners. A composition still running hears no
267
- * `compositionend` once they are gone, so it ends here; a change it held is
268
- * taken in on the next `connect()`.
269
- */
270
561
  disconnect() {
271
- this.#endUntrackedComposition();
562
+ this.#morph.disconnect();
563
+ this.#listeners.dispose();
564
+ const composing = this.#composition.composingElement();
272
565
  this.#composition.disconnect();
566
+ if (this.#composition.composingElement() !== composing) {
567
+ this.#endUntrackedComposition(composing);
568
+ }
273
569
  this.#started = false;
274
570
  }
275
- /**
276
- * Tracks composition on an arriving (or swapped-in) display and normalizes it
277
- * under the declarations the page carries now, reading this controller's own
278
- * rendering with the separators it was written under; an amount that differs
279
- * from the committed one is the page's, so it is reported as `reconcile`.
280
- */
281
571
  displayTargetConnected(target) {
282
572
  this.#composition.observe(target);
283
- if (!this.#started) return;
284
- this.#renderFixed(this.#readDisplay().value, "reconcile");
573
+ if (!this.#started || !this.hasDisplayTarget || this.displayTarget !== target) return;
574
+ this.#applyValueChange();
285
575
  }
286
- /**
287
- * Stops tracking a display that leaves. A composition running on it hears no
288
- * `compositionend` any more, so it ends here; a change it held waits for the
289
- * next display to be read.
290
- */
291
576
  displayTargetDisconnected(target) {
577
+ if (target === this.#renderedDisplay && !(this.#started && this.hasDisplayTarget && this.displayTarget === target)) {
578
+ this.#departed = target;
579
+ }
580
+ const composing = this.#composition.composingElement() === target;
292
581
  this.#composition.unobserve(target);
293
- this.#endUntrackedComposition();
294
- }
295
- /**
296
- * Ends a composition no `compositionend` will reach. A display still in place —
297
- * one a move re-inserted, or the display of a controller that moves — holds the
298
- * text the composition left, typed against the declarations in force, so it is
299
- * read as `compositionend` would read it and becomes this controller's own
300
- * rendering. The page's move ended the composition, so a value it moves is
301
- * reported as `reconcile`. A display that left takes its composition along.
302
- */
303
- #endUntrackedComposition() {
304
- const display = this.#composing;
305
- this.#composing = null;
582
+ if (composing && this.#composition.composingElement() !== target) {
583
+ this.#endUntrackedComposition(target);
584
+ }
585
+ if (this.#formatHeld?.display === target && this.#composition.composingElement() !== target) {
586
+ this.#formatHeld = null;
587
+ }
588
+ this.#applyValueChange();
589
+ }
590
+ #endUntrackedComposition(display) {
591
+ const round2 = this.#takeFormatHeld(display);
306
592
  if (this.hasDisplayTarget && this.displayTarget === display) {
307
- this.#reformat(false, "reconcile");
593
+ this.#reformat(round2, "reconcile");
308
594
  }
309
595
  }
310
- /** Syncs a late-arriving hidden field without touching the display or events. */
311
596
  fieldTargetConnected() {
312
597
  if (this.#started) this.#resync();
313
598
  }
314
- /** Syncs a late-arriving screen-reader span the same way. */
599
+ fieldTargetDisconnected() {
600
+ if (this.#started) this.#resync();
601
+ }
315
602
  srValueTargetConnected() {
316
603
  if (this.#started) this.#resync();
317
604
  }
318
- /** Re-groups digits as the user types, preserving the caret position. */
605
+ srValueTargetDisconnected() {
606
+ if (this.#started) this.#resync();
607
+ }
319
608
  onInput(event) {
320
- if (this.#composition.isComposing(event)) return;
609
+ const source = event?.target ?? null;
610
+ if (!this.#inCharge(source)) return;
611
+ this.#holdUnsettled(false);
612
+ if (event && this.#composition.consumesConfirmedInput(event)) return;
613
+ const input = event;
614
+ if (this.#composition.isComposing(input) || isCompositionInputType(input?.inputType)) {
615
+ if (this.#composition.composingElement() === null && this.hasDisplayTarget) {
616
+ this.#beginUnsettled(this.displayTarget);
617
+ }
618
+ return;
619
+ }
620
+ const unsettled = this.#unsettled !== null;
621
+ if (unsettled) {
622
+ if (source !== this.#unsettled || input?.isComposing !== false || !input.inputType) return;
623
+ this.#unsettled = null;
624
+ this.#formatHeld = null;
625
+ }
321
626
  this.#reformat(false, "edit");
627
+ if (unsettled && this.#owed(source)) this.#applyValueChange();
628
+ }
629
+ format(event) {
630
+ if (!this.#inCharge(event?.target ?? null)) return;
631
+ const returned = this.hasDisplayTarget && this.displayTarget === this.#departed;
632
+ this.#holdUnsettled(false);
633
+ if (this.hasDisplayTarget && (this.displayTarget !== this.#renderedDisplay || returned && !this.#showsOwnRendering(this.displayTarget)) && (event?.type === "blur" || event?.type === "focusout") && this.#composition.composingElement() !== this.displayTarget) {
634
+ this.#beginUnsettled(this.displayTarget);
635
+ }
636
+ if (this.#unsettled !== null) {
637
+ this.#formatHeld = { display: this.#unsettled, heard: false };
638
+ return;
639
+ }
640
+ if (this.#composition.isComposing(event)) {
641
+ const composing = this.#composition.composingElement();
642
+ const display = composing ?? event?.target ?? (this.hasDisplayTarget ? this.displayTarget : null);
643
+ this.#formatHeld = display === null ? null : { display, heard: composing !== null };
644
+ if (composing === null && this.hasDisplayTarget && display === this.displayTarget) {
645
+ this.#beginUnsettled(this.displayTarget);
646
+ }
647
+ } else {
648
+ this.#formatHeld = null;
649
+ this.#reformat(true, "edit");
650
+ }
322
651
  }
323
- /** Applies the fixed-precision rounding on blur. */
324
- format() {
325
- this.#reformat(true, "edit");
652
+ #takeFormatHeld(display) {
653
+ const held = this.#formatHeld;
654
+ this.#formatHeld = null;
655
+ return held !== null && held.display === display;
656
+ }
657
+ #readPageText(composing) {
658
+ if (this.#unsettled !== null || !this.#inCharge(composing) || this.#showsOwnRendering(this.displayTarget))
659
+ return;
660
+ const { scan } = this.#readDisplay();
661
+ this.#displayText.remember(this.displayTarget, this.displayTarget.value);
662
+ this.#renderedDisplay = this.displayTarget;
663
+ this.#reflect(scan.value, this.#render(scan.parts), "reconcile");
326
664
  }
327
- /**
328
- * Parses the display value, rewrites it grouped (optionally at fixed
329
- * precision), keeps the caret stable by significant characters, and syncs the
330
- * field, the screen-reader span, and the event `cause` selects.
331
- */
332
665
  #reformat(fixedPrecision, cause) {
333
- if (!this.hasDisplayTarget) return;
666
+ if (!this.hasDisplayTarget || this.#unsettled === this.displayTarget) return;
334
667
  const raw = this.displayTarget.value;
335
668
  const { parts, value } = this.#scan(raw);
336
669
  if (fixedPrecision) {
@@ -338,19 +671,17 @@ var CurrencyInputController = class extends Controller {
338
671
  return;
339
672
  }
340
673
  const formatted = this.#render(parts);
341
- if (formatted !== raw) {
342
- const caret = this.displayTarget.selectionStart;
343
- const anchor = caret === null ? null : this.#significantBefore(raw, caret);
674
+ this.#showAtCaret(formatted, this.#significantBefore(raw, this.displayTarget.selectionStart));
675
+ this.#reflect(value, formatted, cause);
676
+ }
677
+ #showAtCaret(formatted, anchor) {
678
+ if (formatted !== this.displayTarget.value) {
344
679
  this.displayTarget.value = formatted;
345
680
  if (anchor !== null) this.#restoreCaret(formatted, anchor);
346
681
  }
347
- this.#rendered = formatted;
348
- this.#reflect(value, formatted, cause);
682
+ this.#displayText.remember(this.displayTarget, formatted);
683
+ this.#renderedDisplay = this.displayTarget;
349
684
  }
350
- /**
351
- * Shows `value` at the fixed precision — an empty display for no value — and
352
- * reports a move under the event `cause` selects.
353
- */
354
685
  #renderFixed(value, cause) {
355
686
  if (value === null) {
356
687
  this.#show("");
@@ -362,20 +693,15 @@ var CurrencyInputController = class extends Controller {
362
693
  this.#show(formatted);
363
694
  this.#reflect(rounded, formatted, cause);
364
695
  }
365
- /** Writes `text` to the display as this controller's own rendering. */
366
696
  #show(text) {
367
- this.displayTarget.value = text;
368
- this.#rendered = text;
697
+ this.#displayText.write(this.displayTarget, text);
698
+ this.#renderedDisplay = this.displayTarget;
369
699
  }
370
- /**
371
- * The in-progress rendering: grouped integer, sign and fraction as typed.
372
- */
373
700
  #render(parts) {
374
701
  const int = parts.int === "" ? "" : this.#grouping.format(BigInt(parts.int));
375
702
  const frac = parts.hasDot ? this.#decimal + parts.frac : "";
376
703
  return parts.sign + int + frac;
377
704
  }
378
- /** Restores the caret to sit just after the n-th significant character. */
379
705
  #restoreCaret(formatted, significantBefore) {
380
706
  let seen = 0;
381
707
  let position = formatted.length;
@@ -391,12 +717,8 @@ var CurrencyInputController = class extends Controller {
391
717
  } catch {
392
718
  }
393
719
  }
394
- /**
395
- * Counts the characters before `caret` that survive into the rendering,
396
- * applying the same acceptance rules as {@link #scan} — a rejected keystroke
397
- * (a mid-string sign, a second decimal mark) must not shift the anchor.
398
- */
399
720
  #significantBefore(text, caret) {
721
+ if (caret === null) return null;
400
722
  let count = 0;
401
723
  let sawSign = false;
402
724
  let sawDigit = false;
@@ -415,13 +737,11 @@ var CurrencyInputController = class extends Controller {
415
737
  }
416
738
  return count;
417
739
  }
418
- /** Digits, signs, and the decimal mark anchor the caret; grouping does not. */
419
740
  #isSignificant(ch) {
420
741
  if (ch >= "0" && ch <= "9") return true;
421
742
  if (ch === "-" || ch === "+") return true;
422
743
  return ch === this.#decimal || ch === "." && this.#group !== ".";
423
744
  }
424
- /** Writes the normalized value to the field, the SR span, and the empty hook. */
425
745
  #write(value) {
426
746
  const isEmpty = value === null;
427
747
  if (this.hasFieldTarget) this.fieldTarget.value = isEmpty ? "" : String(value);
@@ -430,13 +750,6 @@ var CurrencyInputController = class extends Controller {
430
750
  }
431
751
  this.element.toggleAttribute(`data-${this.identifier}-empty`, isEmpty);
432
752
  }
433
- /**
434
- * {@link #write}, then reports a moved value under the event `cause` selects:
435
- * `change` for the user's edit, `reconcile` for a move the page caused. The
436
- * display may hold an in-progress `-`, but a `null` value always rides with an
437
- * empty `formatted`, whichever event reports it, so consumers can treat the
438
- * pair as cleared.
439
- */
440
753
  #reflect(value, formatted, cause) {
441
754
  this.#write(value);
442
755
  if (value === this.#lastValue) return;
@@ -445,13 +758,6 @@ var CurrencyInputController = class extends Controller {
445
758
  if (cause === "edit") this.dispatch("change", { detail });
446
759
  else this.dispatch("reconcile", { detail });
447
760
  }
448
- /**
449
- * Scans arbitrary input text into its number-shaped parts and value. Keeps
450
- * digits, one leading sign, one decimal mark, and the fraction verbatim;
451
- * everything else (grouping, symbols, words) is dropped. An ASCII "." is only
452
- * a decimal mark where it is not the locale's grouping separator. Precision
453
- * plays no part here — rounding belongs to the blur-time format.
454
- */
455
761
  #scan(text) {
456
762
  let sign = "";
457
763
  let int = "";
@@ -471,40 +777,82 @@ var CurrencyInputController = class extends Controller {
471
777
  const value = Number(`${sign}${int}.${frac}`);
472
778
  return { parts, value: Number.isFinite(value) ? value : null };
473
779
  }
474
- /** Whether `ch` reads as this locale's decimal mark. */
475
780
  #isDecimalMark(ch) {
476
781
  return ch === this.#decimal || ch === "." && this.#group !== ".";
477
782
  }
478
- /**
479
- * Re-syncs field / srValue / hook without dispatching: from the display, or —
480
- * while it is composing, when its text is not committed yet — from the value
481
- * last committed.
482
- */
483
783
  #resync() {
484
784
  if (!this.hasDisplayTarget) return;
485
- const value = this.#composition.isComposing() ? this.#lastValue : this.#scan(this.displayTarget.value).value;
785
+ const unsettled = this.#trackUnsettled(false);
786
+ if (this.#renderedDisplay === null) return;
787
+ const value = unsettled || this.#composition.isComposing() ? this.#lastValue : this.#scan(this.displayTarget.value).value;
486
788
  this.#write(value);
487
789
  }
488
- /**
489
- * Validates the declared Values, falling back to each Value's default when a
490
- * declaration cannot be interpreted (a malformed locale or currency tag, a
491
- * precision outside `Intl`'s 0–100 integer range), and rebuilds the cached
492
- * formatters from the validated set.
493
- */
494
- #revalidate() {
790
+ #holdUnsettled(arriving) {
791
+ if (!this.#trackUnsettled(arriving)) return false;
792
+ this.#declarationsHeld = this.#declarationsChanged();
793
+ this.#resync();
794
+ return true;
795
+ }
796
+ #trackUnsettled(arriving) {
797
+ this.#forgetUnsettledOwner();
798
+ if (!this.hasDisplayTarget) return false;
799
+ const display = this.displayTarget;
800
+ const returned = display === this.#departed;
801
+ if (returned) this.#departed = null;
802
+ if (this.#unsettled === null && document.activeElement === display && (arriving || display !== this.#renderedDisplay || returned && !this.#showsOwnRendering(display)) && this.#composition.composingElement() !== display) {
803
+ this.#beginUnsettled(display);
804
+ }
805
+ return this.#unsettled !== null;
806
+ }
807
+ #beginUnsettled(display) {
808
+ if (this.#unsettled === null && this.#renderedDisplay === null && this.#declarationsChanged()) {
809
+ this.#revalidate();
810
+ }
811
+ this.#unsettled = display;
812
+ }
813
+ #forgetUnsettledOwner() {
814
+ if (this.#unsettled !== null && this.hasDisplayTarget && this.#unsettled !== this.displayTarget) {
815
+ if (this.#formatHeld?.display === this.#unsettled) this.#formatHeld = null;
816
+ this.#unsettled = null;
817
+ }
818
+ }
819
+ #protectUnsettled = (event) => {
820
+ this.#holdUnsettled(false);
821
+ if (this.#unsettled === null || event.target !== this.#unsettled) return;
822
+ const detail = event.detail;
823
+ if (detail?.attributeName === "value") event.preventDefault();
824
+ };
825
+ #declared() {
495
826
  const resolved = resolveLocale(this.element, this.localeValue);
496
827
  const usable = intlFormatter(Intl.NumberFormat, resolved, {});
497
- this.#locale = usable === null ? FALLBACK_LOCALE : resolved;
498
- const precision = this.precisionValue;
499
- this.#precision = Number.isInteger(precision) && precision >= 0 && precision <= 100 ? precision : 2;
500
- this.#currency = "";
828
+ const locale = usable === null ? FALLBACK_LOCALE : resolved;
829
+ let currency = "";
501
830
  if (this.currencyValue !== "") {
502
- const declaredCurrency = intlFormatter(Intl.NumberFormat, this.#locale, {
831
+ const declaredCurrency = intlFormatter(Intl.NumberFormat, locale, {
503
832
  style: "currency",
504
833
  currency: this.currencyValue
505
834
  });
506
- if (declaredCurrency !== null) this.#currency = this.currencyValue;
835
+ if (declaredCurrency !== null) currency = this.currencyValue;
507
836
  }
837
+ return { locale, currency, precision: this.#safePrecision };
838
+ }
839
+ #owed(composed) {
840
+ if (this.#declarationsHeld) return true;
841
+ if (!this.hasDisplayTarget) return false;
842
+ const display = this.displayTarget;
843
+ return display !== this.#renderedDisplay || display !== composed && !this.#showsOwnRendering(display);
844
+ }
845
+ #declarationsChanged() {
846
+ const { locale, currency, precision } = this.#declared();
847
+ return locale !== this.#locale || currency !== this.#currency || precision !== this.#precision;
848
+ }
849
+ #revalidate() {
850
+ this.#configured = true;
851
+ this.#declarationsHeld = false;
852
+ const declared = this.#declared();
853
+ this.#locale = declared.locale;
854
+ this.#currency = declared.currency;
855
+ this.#precision = declared.precision;
508
856
  this.#grouping = intlFormatter(
509
857
  Intl.NumberFormat,
510
858
  this.#locale,
@@ -544,12 +892,6 @@ var CurrencyInputController = class extends Controller {
544
892
  if (digit !== String(i)) this.#digits.set(digit, String(i));
545
893
  }
546
894
  }
547
- /**
548
- * Maps the locale's own digits to ASCII, folds full-width forms (the digits,
549
- * signs, and marks an IME confirms as 0-9+-.,) through the shared
550
- * half-width mapping, and reads U+2212 MINUS SIGN as "-", which some locales'
551
- * formatted output uses for negatives.
552
- */
553
895
  #normalize(text) {
554
896
  let out = "";
555
897
  for (const ch of text) {
@@ -564,6 +906,15 @@ var CurrencyInputController = class extends Controller {
564
906
  }
565
907
  return out;
566
908
  }
909
+ get #safePrecision() {
910
+ return this.#numbers.read(
911
+ this,
912
+ "precision",
913
+ this.precisionValue,
914
+ _CurrencyInputController.values.precision.default,
915
+ _CurrencyInputController.valueConstraints.precision
916
+ );
917
+ }
567
918
  };
568
919
  function round(value, precision) {
569
920
  const factor = 10 ** Math.max(0, precision);
@@ -573,5 +924,4 @@ function round(value, precision) {
573
924
  }
574
925
 
575
926
  export { CurrencyInputController };
576
- //# sourceMappingURL=currency_input_controller.js.map
577
927
  //# sourceMappingURL=currency_input_controller.js.map