stimeo-ui 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +128 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +392 -417
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +34 -5
  7. package/dist/controllers/accordion_controller.js +228 -28
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +20 -9
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +38 -7
  13. package/dist/controllers/announcer_controller.js +135 -191
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +295 -110
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +172 -106
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  23. package/dist/controllers/breadcrumb_controller.js +0 -129
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +22 -4
  26. package/dist/controllers/bulk_select_controller.js +264 -53
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +109 -28
  29. package/dist/controllers/calendar_controller.js +346 -180
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +99 -27
  32. package/dist/controllers/carousel_controller.js +587 -300
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +18 -2
  35. package/dist/controllers/character_counter_controller.js +271 -143
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +77 -75
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +44 -7
  41. package/dist/controllers/clipboard_controller.js +187 -108
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +41 -5
  44. package/dist/controllers/collapsible_controller.js +374 -108
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +47 -14
  47. package/dist/controllers/color_picker_controller.js +183 -127
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +33 -6
  50. package/dist/controllers/combobox_controller.js +426 -109
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +45 -14
  53. package/dist/controllers/command_palette_controller.js +994 -350
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +212 -125
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +939 -245
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +36 -3
  62. package/dist/controllers/context_menu_controller.js +275 -127
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +75 -2
  68. package/dist/controllers/countdown_controller.js +235 -97
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -15
  71. package/dist/controllers/currency_input_controller.js +241 -118
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +78 -22
  74. package/dist/controllers/data_grid_controller.js +229 -135
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +64 -11
  77. package/dist/controllers/date_range_picker_controller.js +359 -165
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +22 -3
  80. package/dist/controllers/dialog_controller.js +887 -225
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  83. package/dist/controllers/direct_upload_controller.js +80 -142
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  86. package/dist/controllers/dirty_form_controller.js +26 -47
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +175 -20
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +52 -16
  92. package/dist/controllers/drawer_controller.js +963 -336
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +39 -3
  95. package/dist/controllers/dropdown_controller.js +342 -85
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +182 -58
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +67 -53
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  103. package/dist/controllers/file_dropzone_controller.js +426 -229
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.d.ts +10 -4
  106. package/dist/controllers/filter_controller.js +78 -36
  107. package/dist/controllers/filter_controller.js.map +1 -1
  108. package/dist/controllers/flash_controller.d.ts +57 -14
  109. package/dist/controllers/flash_controller.js +328 -211
  110. package/dist/controllers/flash_controller.js.map +1 -1
  111. package/dist/controllers/focus_controller.d.ts +12 -3
  112. package/dist/controllers/focus_controller.js +654 -239
  113. package/dist/controllers/focus_controller.js.map +1 -1
  114. package/dist/controllers/form_field_controller.js +153 -136
  115. package/dist/controllers/form_field_controller.js.map +1 -1
  116. package/dist/controllers/form_validation_controller.js +29 -96
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  119. package/dist/controllers/frame_loading_controller.js +263 -198
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +22 -2
  122. package/dist/controllers/highlight_controller.js +119 -70
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +65 -4
  125. package/dist/controllers/hover_card_controller.js +335 -118
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +58 -4
  128. package/dist/controllers/idle_controller.js +316 -65
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +5 -2
  131. package/dist/controllers/input_mask_controller.js +116 -61
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +147 -103
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  137. package/dist/controllers/lazy_frame_controller.js +9 -56
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +84 -10
  140. package/dist/controllers/listbox_controller.js +413 -153
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +1 -1
  143. package/dist/controllers/local_time_controller.js +54 -64
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +70 -95
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +40 -9
  149. package/dist/controllers/menu_controller.js +254 -167
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +15 -4
  152. package/dist/controllers/menubar_controller.js +206 -331
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +20 -0
  155. package/dist/controllers/meter_controller.js +159 -71
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +50 -7
  158. package/dist/controllers/multi_select_controller.js +797 -388
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +19 -1
  161. package/dist/controllers/navigation_menu_controller.js +156 -231
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +373 -174
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +241 -45
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +62 -10
  170. package/dist/controllers/number_input_controller.js +387 -227
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.js +28 -75
  173. package/dist/controllers/optimistic_controller.js.map +1 -1
  174. package/dist/controllers/otp_controller.d.ts +38 -7
  175. package/dist/controllers/otp_controller.js +344 -196
  176. package/dist/controllers/otp_controller.js.map +1 -1
  177. package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
  178. package/dist/controllers/overflow_indicator_controller.js +167 -93
  179. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  180. package/dist/controllers/overflow_menu_controller.d.ts +66 -9
  181. package/dist/controllers/overflow_menu_controller.js +408 -277
  182. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  183. package/dist/controllers/pagination_controller.d.ts +52 -15
  184. package/dist/controllers/pagination_controller.js +342 -120
  185. package/dist/controllers/pagination_controller.js.map +1 -1
  186. package/dist/controllers/password_reveal_controller.d.ts +60 -9
  187. package/dist/controllers/password_reveal_controller.js +424 -99
  188. package/dist/controllers/password_reveal_controller.js.map +1 -1
  189. package/dist/controllers/password_strength_controller.d.ts +14 -5
  190. package/dist/controllers/password_strength_controller.js +99 -153
  191. package/dist/controllers/password_strength_controller.js.map +1 -1
  192. package/dist/controllers/persist_controller.d.ts +11 -4
  193. package/dist/controllers/persist_controller.js +252 -96
  194. package/dist/controllers/persist_controller.js.map +1 -1
  195. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  196. package/dist/controllers/pointer_drag_controller.js +263 -164
  197. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  198. package/dist/controllers/popover_controller.d.ts +46 -4
  199. package/dist/controllers/popover_controller.js +267 -94
  200. package/dist/controllers/popover_controller.js.map +1 -1
  201. package/dist/controllers/portal_controller.d.ts +44 -4
  202. package/dist/controllers/portal_controller.js +209 -72
  203. package/dist/controllers/portal_controller.js.map +1 -1
  204. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  205. package/dist/controllers/preview_guard_controller.js +243 -112
  206. package/dist/controllers/preview_guard_controller.js.map +1 -1
  207. package/dist/controllers/progress_controller.d.ts +11 -0
  208. package/dist/controllers/progress_controller.js +132 -59
  209. package/dist/controllers/progress_controller.js.map +1 -1
  210. package/dist/controllers/radio_group_controller.d.ts +19 -8
  211. package/dist/controllers/radio_group_controller.js +138 -133
  212. package/dist/controllers/radio_group_controller.js.map +1 -1
  213. package/dist/controllers/range_slider_controller.d.ts +92 -4
  214. package/dist/controllers/range_slider_controller.js +245 -112
  215. package/dist/controllers/range_slider_controller.js.map +1 -1
  216. package/dist/controllers/rating_controller.d.ts +33 -6
  217. package/dist/controllers/rating_controller.js +429 -194
  218. package/dist/controllers/rating_controller.js.map +1 -1
  219. package/dist/controllers/read_more_controller.d.ts +26 -2
  220. package/dist/controllers/read_more_controller.js +185 -47
  221. package/dist/controllers/read_more_controller.js.map +1 -1
  222. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  223. package/dist/controllers/reading_progress_controller.js +147 -135
  224. package/dist/controllers/reading_progress_controller.js.map +1 -1
  225. package/dist/controllers/relative_time_controller.d.ts +18 -4
  226. package/dist/controllers/relative_time_controller.js +145 -110
  227. package/dist/controllers/relative_time_controller.js.map +1 -1
  228. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  229. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  230. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  231. package/dist/controllers/resizable_controller.d.ts +44 -4
  232. package/dist/controllers/resizable_controller.js +294 -118
  233. package/dist/controllers/resizable_controller.js.map +1 -1
  234. package/dist/controllers/roving_controller.js +39 -84
  235. package/dist/controllers/roving_controller.js.map +1 -1
  236. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  237. package/dist/controllers/scroll_area_controller.js +428 -221
  238. package/dist/controllers/scroll_area_controller.js.map +1 -1
  239. package/dist/controllers/scroll_restore_controller.js +0 -53
  240. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  241. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  242. package/dist/controllers/scroll_visibility_controller.js +395 -146
  243. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  244. package/dist/controllers/scrollspy_controller.d.ts +9 -3
  245. package/dist/controllers/scrollspy_controller.js +181 -236
  246. package/dist/controllers/scrollspy_controller.js.map +1 -1
  247. package/dist/controllers/separator_controller.d.ts +59 -17
  248. package/dist/controllers/separator_controller.js +288 -132
  249. package/dist/controllers/separator_controller.js.map +1 -1
  250. package/dist/controllers/sidebar_controller.d.ts +81 -7
  251. package/dist/controllers/sidebar_controller.js +1071 -375
  252. package/dist/controllers/sidebar_controller.js.map +1 -1
  253. package/dist/controllers/skeleton_controller.d.ts +23 -0
  254. package/dist/controllers/skeleton_controller.js +248 -92
  255. package/dist/controllers/skeleton_controller.js.map +1 -1
  256. package/dist/controllers/slider_controller.d.ts +72 -8
  257. package/dist/controllers/slider_controller.js +211 -96
  258. package/dist/controllers/slider_controller.js.map +1 -1
  259. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  260. package/dist/controllers/smart_sticky_header_controller.js +163 -54
  261. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  262. package/dist/controllers/sortable_controller.js +13 -110
  263. package/dist/controllers/sortable_controller.js.map +1 -1
  264. package/dist/controllers/spinner_controller.d.ts +39 -4
  265. package/dist/controllers/spinner_controller.js +277 -161
  266. package/dist/controllers/spinner_controller.js.map +1 -1
  267. package/dist/controllers/step_indicator_controller.d.ts +24 -17
  268. package/dist/controllers/step_indicator_controller.js +107 -53
  269. package/dist/controllers/step_indicator_controller.js.map +1 -1
  270. package/dist/controllers/stepper_controller.d.ts +43 -9
  271. package/dist/controllers/stepper_controller.js +171 -48
  272. package/dist/controllers/stepper_controller.js.map +1 -1
  273. package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
  274. package/dist/controllers/stick_to_bottom_controller.js +310 -116
  275. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  276. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  277. package/dist/controllers/sticky_observer_controller.js +214 -49
  278. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  279. package/dist/controllers/submit_once_controller.d.ts +22 -6
  280. package/dist/controllers/submit_once_controller.js +294 -192
  281. package/dist/controllers/submit_once_controller.js.map +1 -1
  282. package/dist/controllers/switch_controller.d.ts +40 -7
  283. package/dist/controllers/switch_controller.js +107 -43
  284. package/dist/controllers/switch_controller.js.map +1 -1
  285. package/dist/controllers/tabs_controller.d.ts +13 -1
  286. package/dist/controllers/tabs_controller.js +50 -25
  287. package/dist/controllers/tabs_controller.js.map +1 -1
  288. package/dist/controllers/tags_input_controller.d.ts +27 -3
  289. package/dist/controllers/tags_input_controller.js +290 -149
  290. package/dist/controllers/tags_input_controller.js.map +1 -1
  291. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  292. package/dist/controllers/textarea_autosize_controller.js +71 -58
  293. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  294. package/dist/controllers/theme_controller.d.ts +37 -8
  295. package/dist/controllers/theme_controller.js +148 -80
  296. package/dist/controllers/theme_controller.js.map +1 -1
  297. package/dist/controllers/time_picker_controller.d.ts +18 -1
  298. package/dist/controllers/time_picker_controller.js +162 -81
  299. package/dist/controllers/time_picker_controller.js.map +1 -1
  300. package/dist/controllers/toast_controller.d.ts +85 -23
  301. package/dist/controllers/toast_controller.js +365 -193
  302. package/dist/controllers/toast_controller.js.map +1 -1
  303. package/dist/controllers/toggle_group_controller.d.ts +60 -9
  304. package/dist/controllers/toggle_group_controller.js +194 -99
  305. package/dist/controllers/toggle_group_controller.js.map +1 -1
  306. package/dist/controllers/toolbar_controller.js +92 -90
  307. package/dist/controllers/toolbar_controller.js.map +1 -1
  308. package/dist/controllers/tooltip_controller.d.ts +52 -2
  309. package/dist/controllers/tooltip_controller.js +185 -125
  310. package/dist/controllers/tooltip_controller.js.map +1 -1
  311. package/dist/controllers/transition_controller.d.ts +22 -4
  312. package/dist/controllers/transition_controller.js +123 -115
  313. package/dist/controllers/transition_controller.js.map +1 -1
  314. package/dist/controllers/tree_view_controller.d.ts +59 -18
  315. package/dist/controllers/tree_view_controller.js +248 -290
  316. package/dist/controllers/tree_view_controller.js.map +1 -1
  317. package/dist/index.d.ts +33 -3
  318. package/dist/index.js +8649 -8572
  319. package/dist/index.js.map +1 -1
  320. package/dist/inspector/cli.d.ts +95 -9
  321. package/dist/inspector/cli.js +343 -77
  322. package/dist/inspector/cli.js.map +1 -1
  323. package/dist/inspector/cli_bin.js +390 -96
  324. package/dist/inspector/cli_bin.js.map +1 -1
  325. package/dist/inspector/examples.json +45 -45
  326. package/dist/inspector/manifest.json +1308 -155
  327. package/dist/positioning/index.d.ts +8 -0
  328. package/dist/positioning/index.js +148 -45
  329. package/dist/positioning/index.js.map +1 -1
  330. package/package.json +7 -7
  331. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  332. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,8 +1,27 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/combobox_controller.ts
3
+ var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
4
+ var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
5
+ function stateReasonFor(event) {
6
+ if (!event) return "api";
7
+ if (FOCUS_EVENTS.has(event.type)) return "focus";
8
+ if (POINTER_EVENTS.has(event.type)) return "pointer";
9
+ return "user";
10
+ }
11
+
12
+ function actionSource(source) {
13
+ if (source instanceof HTMLElement) {
14
+ return { event: null, host: source, origin: source, reason: "api" };
15
+ }
16
+ const event = source ?? null;
17
+ return {
18
+ event,
19
+ host: event?.currentTarget instanceof Element ? event.currentTarget : null,
20
+ origin: event?.target instanceof Element ? event.target : null,
21
+ reason: stateReasonFor(event)
22
+ };
23
+ }
4
24
 
5
- // src/utils/active_option.ts
6
25
  function syncActiveOption(options, active) {
7
26
  let written = 0;
8
27
  for (const option of options) {
@@ -14,80 +33,244 @@ function syncActiveOption(options, active) {
14
33
  return written;
15
34
  }
16
35
 
17
- // src/utils/arrow_step.ts
18
36
  function isReservedArrowChord(event, allow = []) {
19
37
  if (!event.key.startsWith("Arrow")) return false;
20
38
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
21
39
  }
22
40
 
23
- // src/utils/composition_tracker.ts
41
+ function sharedRegistry(key, create) {
42
+ const symbol = Symbol.for(key);
43
+ const scope = globalThis;
44
+ const existing = scope[symbol];
45
+ if (existing !== void 0) return existing;
46
+ const registry = create();
47
+ Object.defineProperty(globalThis, symbol, { value: registry });
48
+ return registry;
49
+ }
50
+
51
+ function isAttributeValue(value) {
52
+ return value === null || typeof value === "string";
53
+ }
54
+ function readLeaseRecord(element, record) {
55
+ try {
56
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
57
+ if (!Array.isArray(parsed)) return void 0;
58
+ const [original, written, ...rest] = parsed;
59
+ const linked = rest[0] !== false;
60
+ const beneath = linked ? rest : rest.slice(1);
61
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
62
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
63
+ } catch {
64
+ return void 0;
65
+ }
66
+ }
67
+ var AttributeLease = class _AttributeLease {
68
+ static #registry = sharedRegistry(
69
+ "stimeo-ui.attribute-lease.registry.v1",
70
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
71
+ );
72
+ #attribute;
73
+ #record;
74
+ #base;
75
+ #held = /* @__PURE__ */ new Set();
76
+ constructor(attribute, owner, options = {}) {
77
+ this.#attribute = attribute;
78
+ this.#record = `data-${owner}-${attribute}-lease`;
79
+ this.#base = options.base === true;
80
+ const names = _AttributeLease.#registry.names;
81
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
82
+ }
83
+ write(element, value) {
84
+ const stack = this.#stack(element);
85
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
86
+ this.#held.add(element);
87
+ const shows = !this.#covered(element, stack, hold);
88
+ hold.written = value;
89
+ this.#mark(element, stack);
90
+ if (shows) this.#reflect(element, value);
91
+ }
92
+ return(element) {
93
+ const held = this.#held.delete(element);
94
+ const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
95
+ const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
96
+ const hold = this.#take(stack);
97
+ if (!hold) return false;
98
+ const carries = element.getAttribute(this.#attribute) === hold.written;
99
+ const owns = carries && !this.#covered(element, stack, hold);
100
+ const index = stack.indexOf(hold);
101
+ stack.splice(index, 1);
102
+ const above = stack[index];
103
+ if (above) above.linked &&= hold.linked;
104
+ this.#mark(element, stack);
105
+ if (!owns) return true;
106
+ const below = hold.linked ? stack[index - 1] : void 0;
107
+ this.#reflect(element, below ? below.written : hold.original);
108
+ return true;
109
+ }
110
+ returnAll() {
111
+ for (const element of Array.from(this.#held)) this.return(element);
112
+ }
113
+ #stack(element) {
114
+ const stacks = _AttributeLease.#registry.stacks;
115
+ const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
116
+ stacks.set(element, byAttribute);
117
+ const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
118
+ byAttribute.set(this.#attribute, stack);
119
+ return stack;
120
+ }
121
+ #seed(element) {
122
+ const found = [];
123
+ for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
124
+ const read = readLeaseRecord(element, record);
125
+ if (read) found.push({ ...read, record });
126
+ }
127
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
128
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
129
+ lease: null,
130
+ record,
131
+ written,
132
+ original,
133
+ linked: linked && index > 0
134
+ }));
135
+ }
136
+ #take(stack) {
137
+ const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
138
+ if (hold) hold.lease = this;
139
+ return hold;
140
+ }
141
+ #acquire(element, stack) {
142
+ const current = element.getAttribute(this.#attribute);
143
+ const top = stack.at(-1);
144
+ const bottom = stack[0];
145
+ const over = top?.written === current ? top : void 0;
146
+ const from = this.#base ? bottom : over;
147
+ const hold = {
148
+ lease: this,
149
+ record: this.#record,
150
+ written: current,
151
+ original: from ? from.original : current,
152
+ linked: !this.#base && over !== void 0
153
+ };
154
+ if (this.#base && bottom) {
155
+ bottom.linked = true;
156
+ stack.unshift(hold);
157
+ } else stack.push(hold);
158
+ return hold;
159
+ }
160
+ #covered(element, stack, hold) {
161
+ const current = element.getAttribute(this.#attribute);
162
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
163
+ }
164
+ #mark(element, stack) {
165
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
166
+ stack.forEach((hold, index) => {
167
+ const names = stack.slice(0, index).map((under) => under.record);
168
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
169
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
170
+ records.set(
171
+ hold.record,
172
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
173
+ );
174
+ });
175
+ for (const [name, recorded] of records) {
176
+ if (element.getAttribute(name) === recorded) continue;
177
+ if (recorded === null) element.removeAttribute(name);
178
+ else element.setAttribute(name, recorded);
179
+ }
180
+ }
181
+ #reflect(element, value) {
182
+ if (element.getAttribute(this.#attribute) === value) return;
183
+ if (value === null) element.removeAttribute(this.#attribute);
184
+ else element.setAttribute(this.#attribute, value);
185
+ }
186
+ };
187
+
188
+ var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
189
+ "insertCompositionText",
190
+ "insertFromComposition",
191
+ "deleteCompositionText",
192
+ "deleteByComposition"
193
+ ]);
24
194
  var CompositionTracker = class {
25
195
  #observedTargets = /* @__PURE__ */ new Set();
26
196
  #activeTargets = /* @__PURE__ */ new Set();
27
197
  #onStart;
28
198
  #onEnd;
199
+ #confirmedTarget = null;
29
200
  constructor(options = {}) {
30
201
  this.#onStart = options.onStart;
31
202
  this.#onEnd = options.onEnd;
32
203
  }
33
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
34
204
  observe(target) {
35
205
  if (this.#observedTargets.has(target)) return;
36
206
  target.addEventListener("compositionstart", this.#handleStart);
37
207
  target.addEventListener("compositionend", this.#handleEnd);
208
+ target.addEventListener("keydown", this.#handleKeydown);
38
209
  this.#observedTargets.add(target);
39
210
  }
40
- /** Stops tracking one target and clears any active composition it owned. */
41
211
  unobserve(target) {
42
212
  if (!this.#observedTargets.delete(target)) return;
43
213
  target.removeEventListener("compositionstart", this.#handleStart);
44
214
  target.removeEventListener("compositionend", this.#handleEnd);
215
+ target.removeEventListener("keydown", this.#handleKeydown);
45
216
  this.#activeTargets.delete(target);
217
+ if (this.#confirmedTarget === target) this.#confirmedTarget = null;
46
218
  }
47
- /** Releases every listener and clears state so reconnect starts cleanly. */
48
219
  disconnect() {
49
220
  for (const target of this.#observedTargets) {
50
221
  target.removeEventListener("compositionstart", this.#handleStart);
51
222
  target.removeEventListener("compositionend", this.#handleEnd);
223
+ target.removeEventListener("keydown", this.#handleKeydown);
52
224
  }
53
225
  this.#observedTargets.clear();
54
226
  this.#activeTargets.clear();
227
+ this.#confirmedTarget = null;
55
228
  }
56
- /** True when lifecycle tracking or the current event reports composition. */
57
229
  isComposing(event) {
58
230
  return this.#activeTargets.size > 0 || event?.isComposing === true;
59
231
  }
232
+ consumesConfirmedInput(event) {
233
+ const confirmed = this.#confirmedTarget;
234
+ this.#confirmedTarget = null;
235
+ if (confirmed === null || confirmed !== event.target) return false;
236
+ const inputType = event.inputType;
237
+ return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
238
+ }
60
239
  #handleStart = (event) => {
240
+ this.#confirmedTarget = null;
61
241
  if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
62
242
  this.#onStart?.(event);
63
243
  };
64
244
  #handleEnd = (event) => {
65
245
  if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
246
+ this.#confirmedTarget = event.target;
66
247
  this.#onEnd?.(event);
67
248
  };
249
+ #handleKeydown = () => {
250
+ this.#confirmedTarget = null;
251
+ };
68
252
  };
69
253
 
70
- // src/utils/microtask_coalescer.ts
254
+ function commitField(target) {
255
+ target.dispatchEvent(new Event("change", { bubbles: true }));
256
+ }
257
+
71
258
  var MicrotaskCoalescer = class {
72
259
  #run;
73
260
  #queued = false;
74
261
  #active = false;
75
262
  #generation = 0;
76
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
77
263
  constructor(run) {
78
264
  this.#run = run;
79
265
  }
80
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
81
266
  activate() {
82
267
  this.#active = true;
83
268
  }
84
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
85
269
  cancel() {
86
270
  this.#active = false;
87
271
  this.#queued = false;
88
272
  this.#generation += 1;
89
273
  }
90
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
91
274
  schedule() {
92
275
  if (!this.#active || this.#queued) return;
93
276
  this.#queued = true;
@@ -100,7 +283,17 @@ var MicrotaskCoalescer = class {
100
283
  }
101
284
  };
102
285
 
103
- // src/utils/option_scroll.ts
286
+ var MoveCounter = class {
287
+ #sequence = 0;
288
+ record() {
289
+ this.#sequence += 1;
290
+ return this.#sequence;
291
+ }
292
+ isLatest(token) {
293
+ return token === this.#sequence;
294
+ }
295
+ };
296
+
104
297
  function scrollOptionIntoView(list, option) {
105
298
  if (list.scrollHeight <= list.clientHeight) return;
106
299
  const listRect = list.getBoundingClientRect();
@@ -112,80 +305,230 @@ function scrollOptionIntoView(list, option) {
112
305
  }
113
306
  }
114
307
 
115
- // src/controllers/combobox_controller.ts
308
+ var StateRegions = class _StateRegions {
309
+ static #registry = sharedRegistry(
310
+ "stimeo-ui.state-regions.registry.v1",
311
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
312
+ );
313
+ #whenTrue;
314
+ #whenFalse;
315
+ #record;
316
+ constructor(options, owner) {
317
+ this.#whenTrue = options.whenTrue;
318
+ this.#whenFalse = options.whenFalse ?? null;
319
+ this.#record = `data-${owner}-hidden-region`;
320
+ _StateRegions.#registry.names.add(this.#record);
321
+ }
322
+ reflect(host, isTrue) {
323
+ const shown = this.#inside(host, this.#whenTrue());
324
+ if (!this.#whenFalse) {
325
+ for (const region of shown) this.#write(region, !isTrue);
326
+ return;
327
+ }
328
+ const hiddenSide = this.#inside(host, this.#whenFalse());
329
+ if (shown.length === 0 || hiddenSide.length === 0) {
330
+ for (const region of shown) this.#give(region);
331
+ for (const region of hiddenSide) this.#give(region);
332
+ return;
333
+ }
334
+ for (const region of shown) this.#write(region, !isTrue);
335
+ for (const region of hiddenSide) this.#write(region, isTrue);
336
+ }
337
+ release(host) {
338
+ for (const region of [host, ...host.querySelectorAll("*")]) {
339
+ if (!(region instanceof HTMLElement)) continue;
340
+ const stack = this.#held(region);
341
+ const hold = stack && this.#take(stack);
342
+ if (!stack || !hold) continue;
343
+ const owns = region.hidden === hold.written && !this.#covered(region, stack, hold);
344
+ const index = stack.indexOf(hold);
345
+ stack.splice(index, 1);
346
+ const above = stack[index];
347
+ if (above) above.linked &&= hold.linked;
348
+ if (owns) {
349
+ const below = hold.linked ? stack[index - 1] : void 0;
350
+ if (below) {
351
+ if (region.hidden !== below.written) region.hidden = below.written;
352
+ } else if (region.getAttribute("hidden") !== hold.authored) {
353
+ if (hold.authored === null) region.removeAttribute("hidden");
354
+ else region.setAttribute("hidden", hold.authored);
355
+ }
356
+ }
357
+ this.#mark(region, stack);
358
+ }
359
+ }
360
+ #write(region, hidden) {
361
+ const stack = this.#stack(region);
362
+ const hold = this.#take(stack) ?? this.#acquire(region, stack);
363
+ const shows = !this.#covered(region, stack, hold);
364
+ hold.written = hidden;
365
+ if (shows && region.hidden !== hidden) region.hidden = hidden;
366
+ this.#mark(region, stack);
367
+ }
368
+ #give(region) {
369
+ const stack = this.#held(region);
370
+ if (stack && this.#take(stack)?.written) this.#write(region, false);
371
+ }
372
+ #held(region) {
373
+ return _StateRegions.#registry.stacks.has(region) || region.hasAttribute(this.#record) ? this.#stack(region) : void 0;
374
+ }
375
+ #stack(region) {
376
+ const stacks = _StateRegions.#registry.stacks;
377
+ const stack = stacks.get(region) ?? this.#seed(region);
378
+ stacks.set(region, stack);
379
+ return stack;
380
+ }
381
+ #seed(region) {
382
+ const found = [];
383
+ for (const record of _StateRegions.#registry.names) {
384
+ const read = this.#recorded(region, record);
385
+ if (read) found.push({ ...read, record });
386
+ }
387
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
388
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, authored, written, linked }, index) => ({
389
+ instance: null,
390
+ record,
391
+ authored,
392
+ written,
393
+ linked: linked && index > 0
394
+ }));
395
+ }
396
+ #take(stack) {
397
+ const hold = stack.find((held) => held.instance === this) ?? stack.find((held) => held.instance === null && held.record === this.#record);
398
+ if (hold) hold.instance = this;
399
+ return hold;
400
+ }
401
+ #covered(region, stack, hold) {
402
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === region.hidden);
403
+ }
404
+ #acquire(region, stack) {
405
+ const top = stack.at(-1);
406
+ const over = top && region.hidden === top.written ? top : void 0;
407
+ const hold = {
408
+ instance: this,
409
+ record: this.#record,
410
+ authored: over ? over.authored : region.getAttribute("hidden"),
411
+ written: false,
412
+ linked: over !== void 0
413
+ };
414
+ stack.push(hold);
415
+ return hold;
416
+ }
417
+ #mark(region, stack) {
418
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
419
+ stack.forEach((hold, index) => {
420
+ const names = stack.slice(0, index).map((under) => under.record);
421
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
422
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
423
+ records.set(
424
+ hold.record,
425
+ region.getAttribute("hidden") === hold.authored && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.authored, hold.written, ...marker, ...beneath])
426
+ );
427
+ });
428
+ for (const [name, recorded] of records) {
429
+ if (region.getAttribute(name) === recorded) continue;
430
+ if (recorded === null) region.removeAttribute(name);
431
+ else region.setAttribute(name, recorded);
432
+ }
433
+ }
434
+ #recorded(region, name) {
435
+ try {
436
+ const parsed = JSON.parse(region.getAttribute(name) ?? "null");
437
+ if (!Array.isArray(parsed)) return null;
438
+ const [authored, written, ...rest] = parsed;
439
+ if (authored !== null && typeof authored !== "string") return null;
440
+ const linked = rest[0] !== false;
441
+ const beneath = linked ? rest : rest.slice(1);
442
+ if (!beneath.every((under) => typeof under === "string")) return null;
443
+ return typeof written === "boolean" ? { authored, written, linked, beneath } : null;
444
+ } catch {
445
+ return null;
446
+ }
447
+ }
448
+ #inside(host, regions) {
449
+ return regions.filter((region) => host.contains(region));
450
+ }
451
+ };
452
+
453
+ function targetSelector(identifier, name) {
454
+ return `[data-${identifier}-target~="${name}"]`;
455
+ }
456
+
116
457
  var ComboboxController = class extends Controller {
117
- static targets = ["input", "list", "option"];
118
- static actions = ["close", "filter", "onKeydown", "open", "selectByClick"];
458
+ #moves = new MoveCounter();
459
+ #moveToken = 0;
460
+ static targets = ["input", "list", "option", "empty"];
461
+ static actions = ["close", "filter", "onKeydown", "open", "select"];
119
462
  static events = ["selected"];
120
- /** Stable ID of the active option; the live element is resolved before every use. */
463
+ #emptyRegion = new StateRegions({ whenTrue: () => this.emptyTargets }, this.identifier);
121
464
  #activeId = null;
122
465
  #connected = false;
123
- /** Collapses one mutation batch of target callbacks into a single pass. */
466
+ #list = null;
467
+ #listHidden = new AttributeLease("hidden", this.identifier);
468
+ #expanded = new AttributeLease("aria-expanded", this.identifier);
469
+ #activeDescendant = new AttributeLease(
470
+ "aria-activedescendant",
471
+ this.identifier
472
+ );
124
473
  #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());
125
- /**
126
- * Suppresses {@link open} for the duration of the programmatic re-focus in
127
- * `#select`, so committing a value (which returns focus to the input)
128
- * does not immediately re-open the listbox via a `focus`-bound action.
129
- */
130
474
  #suppressOpen = false;
131
- /** Owns IME lifecycle state; confirmed text re-filters the list once. */
132
475
  #composition = new CompositionTracker({ onEnd: () => this.filter() });
133
- /** Starts closed with no active option and registers the outside-click listener. */
134
476
  connect() {
135
477
  if (this.hasInputTarget) this.#composition.observe(this.inputTarget);
136
478
  this.close();
479
+ this.#list = this.hasListTarget ? this.listTarget : null;
137
480
  document.addEventListener("click", this.#onOutsideClick, true);
138
481
  this.#connected = true;
139
482
  this.#reconcile.activate();
140
483
  }
141
- /** Removes the document-level listener registered in {@link connect}. */
142
484
  disconnect() {
143
485
  this.#connected = false;
144
486
  this.#reconcile.cancel();
145
487
  this.#composition.disconnect();
146
488
  document.removeEventListener("click", this.#onOutsideClick, true);
147
489
  }
148
- /** Tracks an input added initially or after connect. */
149
490
  inputTargetConnected(input) {
150
491
  this.#composition.observe(input);
151
492
  if (this.#connected) this.#queueOptionReconciliation();
152
493
  }
153
- /** Removes composition listeners when the active input is replaced or removed. */
154
494
  inputTargetDisconnected(input) {
155
495
  this.#composition.unobserve(input);
496
+ if (!this.inputTargets.includes(input)) {
497
+ this.#expanded.return(input);
498
+ this.#activeDescendant.return(input);
499
+ }
500
+ this.#queueOptionReconciliation();
501
+ }
502
+ listTargetConnected() {
503
+ this.#adoptList();
504
+ }
505
+ listTargetDisconnected(list) {
506
+ this.#adoptList();
507
+ if (!this.listTargets.includes(list)) this.#listHidden.return(list);
508
+ }
509
+ emptyTargetConnected() {
510
+ this.#reflectEmptyState();
156
511
  }
157
- /** Establishes the active-state baseline, then reconciles a runtime addition. */
158
512
  optionTargetConnected(option) {
159
513
  option.setAttribute("aria-selected", "false");
160
514
  if (this.#connected) this.#queueOptionReconciliation();
161
515
  }
162
- /** Clears the disconnected node's active state, then reconciles the survivors. */
163
516
  optionTargetDisconnected(option) {
164
517
  option.setAttribute("aria-selected", "false");
165
518
  if (this.#connected) this.#queueOptionReconciliation();
166
519
  }
167
- /** Filters confirmed input text and opens the listbox. */
168
520
  filter(event) {
169
521
  if (this.#composition.isComposing(event)) return;
170
522
  this.open();
171
523
  }
172
- /**
173
- * Opens the listbox, re-filtering the options against the current input value
174
- * so the visible options and empty state always match what is typed (e.g.
175
- * re-opening with a stale non-matching value still surfaces the empty state).
176
- */
177
524
  open() {
178
525
  if (!this.hasListTarget || !this.hasInputTarget || this.#suppressOpen) return;
179
526
  this.#applyFilter();
180
- this.listTarget.hidden = false;
181
- this.inputTarget.setAttribute("aria-expanded", "true");
527
+ this.#listHidden.write(this.listTarget, null);
528
+ this.#expanded.write(this.inputTarget, "true");
182
529
  this.#setActive(-1);
183
530
  this.#reflectEmptyState();
184
531
  }
185
- /**
186
- * Hides options that don't match the current input value (case-insensitive
187
- * substring). An empty query shows every option. Does not change open state.
188
- */
189
532
  #applyFilter() {
190
533
  const query = this.inputTarget.value.trim().toLowerCase();
191
534
  for (const option of this.optionTargets) {
@@ -193,24 +536,13 @@ var ComboboxController = class extends Controller {
193
536
  option.hidden = query.length > 0 && !text.includes(query);
194
537
  }
195
538
  }
196
- /**
197
- * Closes the listbox, clears the active option, and updates ARIA state.
198
- *
199
- * Survives a missing input in both directions. {@link inputTargetConnected}
200
- * accepts an input added after connect, so dereferencing it here would abort
201
- * `connect()` before it registers the outside-click listener, and nothing
202
- * re-registers it later. Symmetrically, an input removed while the popup is
203
- * open must still let the popup come down — so hiding the list is
204
- * unconditional and only the ARIA half is guarded.
205
- */
206
539
  close() {
207
540
  if (!this.hasListTarget) return;
208
- this.listTarget.hidden = true;
209
- this.element.removeAttribute("data-stimeo--combobox-empty");
210
- if (this.hasInputTarget) this.inputTarget.setAttribute("aria-expanded", "false");
541
+ this.#listHidden.write(this.listTarget, "");
542
+ this.#reflectEmptyState();
543
+ if (this.hasInputTarget) this.#expanded.write(this.inputTarget, "false");
211
544
  this.#setActive(-1);
212
545
  }
213
- /** Routes keyboard interaction per the APG combobox model. */
214
546
  onKeydown(event) {
215
547
  if (this.#composition.isComposing(event)) return;
216
548
  if (event.defaultPrevented) return;
@@ -283,85 +615,75 @@ var ComboboxController = class extends Controller {
283
615
  break;
284
616
  }
285
617
  }
286
- /**
287
- * Closes the listbox when a click lands outside the combobox. Mirrors the menu
288
- * button's outside-click behavior; clicks on an option are inside the element,
289
- * so `#select` (not this handler) closes the popup after committing.
290
- */
291
618
  #onOutsideClick = (event) => {
292
619
  if (!this.#isClosed && !this.element.contains(event.target)) this.close();
293
620
  };
294
- /** Selects the clicked option. Bound via `data-action` (click). */
295
- selectByClick(event) {
296
- const option = event.currentTarget.closest('[role="option"]');
297
- if (option && this.optionTargets.includes(option)) this.#select(option);
298
- }
299
- /**
300
- * Commits an option: fills the input, closes the listbox, notifies listeners.
301
- *
302
- * An input removed while the popup is open leaves the options clickable, and
303
- * {@link close} already survives that state. Selection does too: the popup
304
- * comes down and listeners still hear the choice, with only the field-bound
305
- * half — the value write, the focus return, and the native `change` — skipped,
306
- * because there is no field to carry them.
307
- */
308
- #select(option) {
621
+ select(source) {
622
+ const { event, host, origin, reason } = actionSource(source);
623
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
624
+ return;
625
+ const option = host?.closest(targetSelector(this.identifier, "option"));
626
+ if (!option || !this.optionTargets.includes(option)) return;
627
+ const focus = event !== null || this.element.contains(document.activeElement);
628
+ this.#select(option, reason, focus);
629
+ }
630
+ #select(option, reason = "user", focus = true) {
309
631
  const value = option.dataset.value ?? (option.textContent ?? "").trim();
310
632
  if (!this.hasInputTarget) {
311
633
  this.close();
312
- this.dispatch("selected", { detail: { value } });
634
+ this.dispatch("selected", { detail: { value, reason } });
313
635
  return;
314
636
  }
315
637
  const changed = this.inputTarget.value !== value;
638
+ if (changed) this.#moveToken = this.#moves.record();
639
+ const token = this.#moveToken;
316
640
  this.inputTarget.value = value;
317
641
  this.close();
318
642
  this.#suppressOpen = true;
319
- this.inputTarget.focus();
643
+ if (focus && (reason !== "api" || this.element.contains(document.activeElement)))
644
+ this.inputTarget.focus();
320
645
  this.#suppressOpen = false;
646
+ if (!this.#moves.isLatest(token)) return;
321
647
  if (changed) {
322
- this.inputTarget.dispatchEvent(new Event("change", { bubbles: true }));
648
+ commitField(this.inputTarget);
323
649
  }
324
- this.dispatch("selected", { detail: { value } });
325
- }
326
- /**
327
- * Reflects whether the open listbox currently has zero matching options by
328
- * toggling `data-stimeo--combobox-empty` on the root element. Behavior only:
329
- * consumers decide how to present the empty state (hide the list, show a
330
- * "no results" node, etc.) via CSS keyed off this attribute.
331
- */
650
+ if (!this.#moves.isLatest(token)) return;
651
+ this.dispatch("selected", { detail: { value, reason } });
652
+ }
332
653
  #reflectEmptyState() {
333
654
  const empty = !this.#isClosed && this.#visibleOptions().length === 0;
334
655
  if (empty) {
335
- this.element.setAttribute("data-stimeo--combobox-empty", "");
656
+ this.element.setAttribute(`data-${this.identifier}-empty`, "");
336
657
  } else {
337
- this.element.removeAttribute("data-stimeo--combobox-empty");
658
+ this.element.removeAttribute(`data-${this.identifier}-empty`);
338
659
  }
660
+ this.#emptyRegion.reflect(this.element, empty);
339
661
  }
340
- /**
341
- * Marks the visible option at `index` active via `aria-selected` and the
342
- * input's `aria-activedescendant`. Pass `-1` to clear the active option.
343
- */
344
662
  #setActive(index) {
345
663
  const visible = this.#visibleOptions();
346
664
  const active = index === -1 ? null : visible[index] ?? null;
347
665
  syncActiveOption(this.optionTargets, active);
348
666
  this.#activeId = active?.id || null;
349
- if (this.hasInputTarget) {
350
- if (active?.id) {
351
- this.inputTarget.setAttribute("aria-activedescendant", active.id);
352
- } else {
353
- this.inputTarget.removeAttribute("aria-activedescendant");
354
- }
355
- }
667
+ if (this.hasInputTarget) this.#activeDescendant.write(this.inputTarget, active?.id || null);
356
668
  if (active && this.hasListTarget) scrollOptionIntoView(this.listTarget, active);
357
669
  }
358
- /** Re-applies filtering and active state after a target collection change. */
670
+ #adoptList() {
671
+ if (!this.#connected) return;
672
+ const list = this.hasListTarget ? this.listTarget : null;
673
+ const previous = this.#list;
674
+ if (list === previous) return;
675
+ this.#list = list;
676
+ if (list) this.#listHidden.write(list, previous?.hidden ?? true ? "" : null);
677
+ this.#queueOptionReconciliation();
678
+ }
359
679
  #reconcileOptions() {
360
- if (this.hasInputTarget && !this.#isClosed) this.#applyFilter();
680
+ if (this.hasInputTarget) {
681
+ this.#expanded.write(this.inputTarget, String(!this.#isClosed));
682
+ if (!this.#isClosed) this.#applyFilter();
683
+ }
361
684
  this.#reconcileActive();
362
685
  this.#reflectEmptyState();
363
686
  }
364
- /** Preserves a surviving active ID; disappearance deliberately clears active state. */
365
687
  #reconcileActive() {
366
688
  if (!this.hasInputTarget || this.#isClosed) {
367
689
  this.#setActive(-1);
@@ -377,26 +699,21 @@ var ComboboxController = class extends Controller {
377
699
  const stateMatches = active ? this.#activeId === (active.id || null) && selected.length === 1 && selected[0] === active && idref === (active.id || null) : this.#activeId === null && selected.length === 0 && idref === null;
378
700
  if (!stateMatches) this.#setActive(index);
379
701
  }
380
- /** Resolves the stable ID, with `aria-selected` as the ID-less compatibility marker. */
381
702
  #findActiveIndex(options) {
382
703
  const activeId = (this.hasInputTarget ? this.inputTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
383
704
  if (activeId) return options.findIndex((option) => option.id === activeId);
384
705
  return options.findIndex((option) => option.getAttribute("aria-selected") === "true");
385
706
  }
386
- /** Coalesces all target callbacks from one MutationObserver batch. */
387
707
  #queueOptionReconciliation() {
388
708
  this.#reconcile.schedule();
389
709
  }
390
- /** The options currently shown (not filtered out). */
391
710
  #visibleOptions() {
392
711
  return this.optionTargets.filter((option) => !option.hidden);
393
712
  }
394
- /** Whether the listbox is currently hidden. */
395
713
  get #isClosed() {
396
714
  return !this.hasListTarget || this.listTarget.hidden !== false;
397
715
  }
398
716
  };
399
717
 
400
718
  export { ComboboxController };
401
- //# sourceMappingURL=combobox_controller.js.map
402
719
  //# sourceMappingURL=combobox_controller.js.map