stimeo-ui 0.16.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 (328) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +8 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +363 -436
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +4 -2
  7. package/dist/controllers/accordion_controller.js +166 -55
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  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 +35 -5
  13. package/dist/controllers/announcer_controller.js +135 -198
  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 +271 -127
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +171 -105
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.js +0 -130
  23. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  24. package/dist/controllers/bulk_select_controller.d.ts +18 -0
  25. package/dist/controllers/bulk_select_controller.js +262 -52
  26. package/dist/controllers/bulk_select_controller.js.map +1 -1
  27. package/dist/controllers/calendar_controller.d.ts +24 -5
  28. package/dist/controllers/calendar_controller.js +175 -209
  29. package/dist/controllers/calendar_controller.js.map +1 -1
  30. package/dist/controllers/carousel_controller.d.ts +34 -7
  31. package/dist/controllers/carousel_controller.js +461 -399
  32. package/dist/controllers/carousel_controller.js.map +1 -1
  33. package/dist/controllers/character_counter_controller.d.ts +17 -1
  34. package/dist/controllers/character_counter_controller.js +246 -156
  35. package/dist/controllers/character_counter_controller.js.map +1 -1
  36. package/dist/controllers/checkbox_controller.d.ts +7 -2
  37. package/dist/controllers/checkbox_controller.js +32 -74
  38. package/dist/controllers/checkbox_controller.js.map +1 -1
  39. package/dist/controllers/clipboard_controller.d.ts +16 -5
  40. package/dist/controllers/clipboard_controller.js +168 -147
  41. package/dist/controllers/clipboard_controller.js.map +1 -1
  42. package/dist/controllers/collapsible_controller.d.ts +16 -4
  43. package/dist/controllers/collapsible_controller.js +301 -120
  44. package/dist/controllers/collapsible_controller.js.map +1 -1
  45. package/dist/controllers/color_picker_controller.d.ts +18 -6
  46. package/dist/controllers/color_picker_controller.js +149 -139
  47. package/dist/controllers/color_picker_controller.js.map +1 -1
  48. package/dist/controllers/combobox_controller.d.ts +23 -5
  49. package/dist/controllers/combobox_controller.js +360 -133
  50. package/dist/controllers/combobox_controller.js.map +1 -1
  51. package/dist/controllers/command_palette_controller.d.ts +45 -14
  52. package/dist/controllers/command_palette_controller.js +970 -358
  53. package/dist/controllers/command_palette_controller.js.map +1 -1
  54. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  55. package/dist/controllers/conditional_fields_controller.js +166 -124
  56. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  57. package/dist/controllers/confirm_controller.d.ts +49 -12
  58. package/dist/controllers/confirm_controller.js +939 -248
  59. package/dist/controllers/confirm_controller.js.map +1 -1
  60. package/dist/controllers/context_menu_controller.d.ts +31 -4
  61. package/dist/controllers/context_menu_controller.js +256 -128
  62. package/dist/controllers/context_menu_controller.js.map +1 -1
  63. package/dist/controllers/count_up_controller.d.ts +13 -1
  64. package/dist/controllers/count_up_controller.js +73 -21
  65. package/dist/controllers/count_up_controller.js.map +1 -1
  66. package/dist/controllers/countdown_controller.d.ts +45 -1
  67. package/dist/controllers/countdown_controller.js +183 -148
  68. package/dist/controllers/countdown_controller.js.map +1 -1
  69. package/dist/controllers/currency_input_controller.d.ts +21 -2
  70. package/dist/controllers/currency_input_controller.js +134 -158
  71. package/dist/controllers/currency_input_controller.js.map +1 -1
  72. package/dist/controllers/data_grid_controller.d.ts +15 -4
  73. package/dist/controllers/data_grid_controller.js +114 -186
  74. package/dist/controllers/data_grid_controller.js.map +1 -1
  75. package/dist/controllers/date_range_picker_controller.d.ts +30 -5
  76. package/dist/controllers/date_range_picker_controller.js +288 -206
  77. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  78. package/dist/controllers/dialog_controller.d.ts +13 -1
  79. package/dist/controllers/dialog_controller.js +858 -223
  80. package/dist/controllers/dialog_controller.js.map +1 -1
  81. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  82. package/dist/controllers/direct_upload_controller.js +73 -153
  83. package/dist/controllers/direct_upload_controller.js.map +1 -1
  84. package/dist/controllers/dirty_form_controller.js +14 -48
  85. package/dist/controllers/dirty_form_controller.js.map +1 -1
  86. package/dist/controllers/dismissible_controller.d.ts +19 -0
  87. package/dist/controllers/dismissible_controller.js +175 -21
  88. package/dist/controllers/dismissible_controller.js.map +1 -1
  89. package/dist/controllers/drawer_controller.d.ts +39 -11
  90. package/dist/controllers/drawer_controller.js +933 -341
  91. package/dist/controllers/drawer_controller.js.map +1 -1
  92. package/dist/controllers/dropdown_controller.d.ts +31 -1
  93. package/dist/controllers/dropdown_controller.js +312 -82
  94. package/dist/controllers/dropdown_controller.js.map +1 -1
  95. package/dist/controllers/editable_controller.d.ts +12 -1
  96. package/dist/controllers/editable_controller.js +158 -68
  97. package/dist/controllers/editable_controller.js.map +1 -1
  98. package/dist/controllers/empty_state_controller.js +67 -53
  99. package/dist/controllers/empty_state_controller.js.map +1 -1
  100. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  101. package/dist/controllers/file_dropzone_controller.js +311 -207
  102. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  103. package/dist/controllers/filter_controller.js +67 -39
  104. package/dist/controllers/filter_controller.js.map +1 -1
  105. package/dist/controllers/flash_controller.d.ts +31 -8
  106. package/dist/controllers/flash_controller.js +172 -314
  107. package/dist/controllers/flash_controller.js.map +1 -1
  108. package/dist/controllers/focus_controller.d.ts +12 -3
  109. package/dist/controllers/focus_controller.js +654 -240
  110. package/dist/controllers/focus_controller.js.map +1 -1
  111. package/dist/controllers/form_field_controller.js +147 -132
  112. package/dist/controllers/form_field_controller.js.map +1 -1
  113. package/dist/controllers/form_validation_controller.js +13 -86
  114. package/dist/controllers/form_validation_controller.js.map +1 -1
  115. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  116. package/dist/controllers/frame_loading_controller.js +264 -213
  117. package/dist/controllers/frame_loading_controller.js.map +1 -1
  118. package/dist/controllers/highlight_controller.d.ts +22 -2
  119. package/dist/controllers/highlight_controller.js +107 -75
  120. package/dist/controllers/highlight_controller.js.map +1 -1
  121. package/dist/controllers/hover_card_controller.d.ts +56 -3
  122. package/dist/controllers/hover_card_controller.js +309 -118
  123. package/dist/controllers/hover_card_controller.js.map +1 -1
  124. package/dist/controllers/idle_controller.d.ts +42 -1
  125. package/dist/controllers/idle_controller.js +267 -101
  126. package/dist/controllers/idle_controller.js.map +1 -1
  127. package/dist/controllers/input_mask_controller.js +71 -72
  128. package/dist/controllers/input_mask_controller.js.map +1 -1
  129. package/dist/controllers/intersection_controller.d.ts +32 -13
  130. package/dist/controllers/intersection_controller.js +147 -106
  131. package/dist/controllers/intersection_controller.js.map +1 -1
  132. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  133. package/dist/controllers/lazy_frame_controller.js +9 -65
  134. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  135. package/dist/controllers/listbox_controller.d.ts +34 -2
  136. package/dist/controllers/listbox_controller.js +306 -204
  137. package/dist/controllers/listbox_controller.js.map +1 -1
  138. package/dist/controllers/local_time_controller.d.ts +1 -1
  139. package/dist/controllers/local_time_controller.js +54 -64
  140. package/dist/controllers/local_time_controller.js.map +1 -1
  141. package/dist/controllers/masonry_controller.d.ts +11 -3
  142. package/dist/controllers/masonry_controller.js +70 -93
  143. package/dist/controllers/masonry_controller.js.map +1 -1
  144. package/dist/controllers/menu_controller.d.ts +32 -7
  145. package/dist/controllers/menu_controller.js +214 -156
  146. package/dist/controllers/menu_controller.js.map +1 -1
  147. package/dist/controllers/menubar_controller.d.ts +4 -4
  148. package/dist/controllers/menubar_controller.js +170 -329
  149. package/dist/controllers/menubar_controller.js.map +1 -1
  150. package/dist/controllers/meter_controller.d.ts +20 -0
  151. package/dist/controllers/meter_controller.js +151 -67
  152. package/dist/controllers/meter_controller.js.map +1 -1
  153. package/dist/controllers/multi_select_controller.d.ts +32 -4
  154. package/dist/controllers/multi_select_controller.js +593 -358
  155. package/dist/controllers/multi_select_controller.js.map +1 -1
  156. package/dist/controllers/navigation_menu_controller.d.ts +8 -1
  157. package/dist/controllers/navigation_menu_controller.js +124 -232
  158. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  159. package/dist/controllers/nested_form_controller.d.ts +10 -0
  160. package/dist/controllers/nested_form_controller.js +352 -182
  161. package/dist/controllers/nested_form_controller.js.map +1 -1
  162. package/dist/controllers/network_status_controller.d.ts +23 -0
  163. package/dist/controllers/network_status_controller.js +242 -54
  164. package/dist/controllers/network_status_controller.js.map +1 -1
  165. package/dist/controllers/number_input_controller.d.ts +26 -2
  166. package/dist/controllers/number_input_controller.js +320 -263
  167. package/dist/controllers/number_input_controller.js.map +1 -1
  168. package/dist/controllers/optimistic_controller.js +16 -76
  169. package/dist/controllers/optimistic_controller.js.map +1 -1
  170. package/dist/controllers/otp_controller.d.ts +20 -4
  171. package/dist/controllers/otp_controller.js +198 -193
  172. package/dist/controllers/otp_controller.js.map +1 -1
  173. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  174. package/dist/controllers/overflow_indicator_controller.js +116 -113
  175. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  176. package/dist/controllers/overflow_menu_controller.d.ts +48 -11
  177. package/dist/controllers/overflow_menu_controller.js +343 -283
  178. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  179. package/dist/controllers/pagination_controller.d.ts +35 -6
  180. package/dist/controllers/pagination_controller.js +295 -119
  181. package/dist/controllers/pagination_controller.js.map +1 -1
  182. package/dist/controllers/password_reveal_controller.d.ts +45 -8
  183. package/dist/controllers/password_reveal_controller.js +389 -121
  184. package/dist/controllers/password_reveal_controller.js.map +1 -1
  185. package/dist/controllers/password_strength_controller.d.ts +14 -5
  186. package/dist/controllers/password_strength_controller.js +99 -153
  187. package/dist/controllers/password_strength_controller.js.map +1 -1
  188. package/dist/controllers/persist_controller.d.ts +11 -4
  189. package/dist/controllers/persist_controller.js +237 -103
  190. package/dist/controllers/persist_controller.js.map +1 -1
  191. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  192. package/dist/controllers/pointer_drag_controller.js +185 -165
  193. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  194. package/dist/controllers/popover_controller.d.ts +38 -2
  195. package/dist/controllers/popover_controller.js +236 -97
  196. package/dist/controllers/popover_controller.js.map +1 -1
  197. package/dist/controllers/portal_controller.d.ts +44 -4
  198. package/dist/controllers/portal_controller.js +209 -76
  199. package/dist/controllers/portal_controller.js.map +1 -1
  200. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  201. package/dist/controllers/preview_guard_controller.js +232 -116
  202. package/dist/controllers/preview_guard_controller.js.map +1 -1
  203. package/dist/controllers/progress_controller.d.ts +11 -0
  204. package/dist/controllers/progress_controller.js +125 -56
  205. package/dist/controllers/progress_controller.js.map +1 -1
  206. package/dist/controllers/radio_group_controller.d.ts +14 -5
  207. package/dist/controllers/radio_group_controller.js +117 -137
  208. package/dist/controllers/radio_group_controller.js.map +1 -1
  209. package/dist/controllers/range_slider_controller.d.ts +43 -3
  210. package/dist/controllers/range_slider_controller.js +192 -105
  211. package/dist/controllers/range_slider_controller.js.map +1 -1
  212. package/dist/controllers/rating_controller.d.ts +20 -4
  213. package/dist/controllers/rating_controller.js +418 -207
  214. package/dist/controllers/rating_controller.js.map +1 -1
  215. package/dist/controllers/read_more_controller.d.ts +2 -0
  216. package/dist/controllers/read_more_controller.js +134 -63
  217. package/dist/controllers/read_more_controller.js.map +1 -1
  218. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  219. package/dist/controllers/reading_progress_controller.js +147 -135
  220. package/dist/controllers/reading_progress_controller.js.map +1 -1
  221. package/dist/controllers/relative_time_controller.d.ts +18 -4
  222. package/dist/controllers/relative_time_controller.js +145 -110
  223. package/dist/controllers/relative_time_controller.js.map +1 -1
  224. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  225. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  226. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  227. package/dist/controllers/resizable_controller.d.ts +28 -2
  228. package/dist/controllers/resizable_controller.js +255 -139
  229. package/dist/controllers/resizable_controller.js.map +1 -1
  230. package/dist/controllers/roving_controller.js +39 -84
  231. package/dist/controllers/roving_controller.js.map +1 -1
  232. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  233. package/dist/controllers/scroll_area_controller.js +428 -225
  234. package/dist/controllers/scroll_area_controller.js.map +1 -1
  235. package/dist/controllers/scroll_restore_controller.js +0 -53
  236. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  237. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  238. package/dist/controllers/scroll_visibility_controller.js +395 -145
  239. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  240. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  241. package/dist/controllers/scrollspy_controller.js +181 -242
  242. package/dist/controllers/scrollspy_controller.js.map +1 -1
  243. package/dist/controllers/separator_controller.d.ts +19 -4
  244. package/dist/controllers/separator_controller.js +267 -140
  245. package/dist/controllers/separator_controller.js.map +1 -1
  246. package/dist/controllers/sidebar_controller.d.ts +64 -7
  247. package/dist/controllers/sidebar_controller.js +1023 -386
  248. package/dist/controllers/sidebar_controller.js.map +1 -1
  249. package/dist/controllers/skeleton_controller.d.ts +23 -0
  250. package/dist/controllers/skeleton_controller.js +248 -97
  251. package/dist/controllers/skeleton_controller.js.map +1 -1
  252. package/dist/controllers/slider_controller.d.ts +27 -1
  253. package/dist/controllers/slider_controller.js +159 -79
  254. package/dist/controllers/slider_controller.js.map +1 -1
  255. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  256. package/dist/controllers/smart_sticky_header_controller.js +162 -63
  257. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  258. package/dist/controllers/sortable_controller.js +0 -112
  259. package/dist/controllers/sortable_controller.js.map +1 -1
  260. package/dist/controllers/spinner_controller.d.ts +39 -4
  261. package/dist/controllers/spinner_controller.js +277 -169
  262. package/dist/controllers/spinner_controller.js.map +1 -1
  263. package/dist/controllers/step_indicator_controller.d.ts +5 -0
  264. package/dist/controllers/step_indicator_controller.js +99 -46
  265. package/dist/controllers/step_indicator_controller.js.map +1 -1
  266. package/dist/controllers/stepper_controller.d.ts +12 -3
  267. package/dist/controllers/stepper_controller.js +114 -73
  268. package/dist/controllers/stepper_controller.js.map +1 -1
  269. package/dist/controllers/stick_to_bottom_controller.d.ts +6 -0
  270. package/dist/controllers/stick_to_bottom_controller.js +262 -138
  271. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  272. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  273. package/dist/controllers/sticky_observer_controller.js +214 -49
  274. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  275. package/dist/controllers/submit_once_controller.d.ts +23 -8
  276. package/dist/controllers/submit_once_controller.js +294 -205
  277. package/dist/controllers/submit_once_controller.js.map +1 -1
  278. package/dist/controllers/switch_controller.d.ts +12 -1
  279. package/dist/controllers/switch_controller.js +33 -60
  280. package/dist/controllers/switch_controller.js.map +1 -1
  281. package/dist/controllers/tabs_controller.d.ts +2 -2
  282. package/dist/controllers/tabs_controller.js +35 -29
  283. package/dist/controllers/tabs_controller.js.map +1 -1
  284. package/dist/controllers/tags_input_controller.d.ts +12 -0
  285. package/dist/controllers/tags_input_controller.js +136 -145
  286. package/dist/controllers/tags_input_controller.js.map +1 -1
  287. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  288. package/dist/controllers/textarea_autosize_controller.js +71 -58
  289. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  290. package/dist/controllers/theme_controller.d.ts +21 -9
  291. package/dist/controllers/theme_controller.js +130 -112
  292. package/dist/controllers/theme_controller.js.map +1 -1
  293. package/dist/controllers/time_picker_controller.d.ts +18 -1
  294. package/dist/controllers/time_picker_controller.js +146 -80
  295. package/dist/controllers/time_picker_controller.js.map +1 -1
  296. package/dist/controllers/toast_controller.d.ts +36 -11
  297. package/dist/controllers/toast_controller.js +186 -297
  298. package/dist/controllers/toast_controller.js.map +1 -1
  299. package/dist/controllers/toggle_group_controller.d.ts +12 -3
  300. package/dist/controllers/toggle_group_controller.js +95 -136
  301. package/dist/controllers/toggle_group_controller.js.map +1 -1
  302. package/dist/controllers/toolbar_controller.js +68 -98
  303. package/dist/controllers/toolbar_controller.js.map +1 -1
  304. package/dist/controllers/tooltip_controller.d.ts +45 -3
  305. package/dist/controllers/tooltip_controller.js +158 -124
  306. package/dist/controllers/tooltip_controller.js.map +1 -1
  307. package/dist/controllers/transition_controller.d.ts +22 -4
  308. package/dist/controllers/transition_controller.js +123 -119
  309. package/dist/controllers/transition_controller.js.map +1 -1
  310. package/dist/controllers/tree_view_controller.d.ts +21 -11
  311. package/dist/controllers/tree_view_controller.js +150 -345
  312. package/dist/controllers/tree_view_controller.js.map +1 -1
  313. package/dist/index.d.ts +8 -5
  314. package/dist/index.js +6370 -9346
  315. package/dist/index.js.map +1 -1
  316. package/dist/inspector/cli.d.ts +23 -3
  317. package/dist/inspector/cli.js +92 -37
  318. package/dist/inspector/cli.js.map +1 -1
  319. package/dist/inspector/cli_bin.js +92 -56
  320. package/dist/inspector/cli_bin.js.map +1 -1
  321. package/dist/inspector/examples.json +8 -8
  322. package/dist/inspector/manifest.json +787 -100
  323. package/dist/positioning/index.d.ts +8 -0
  324. package/dist/positioning/index.js +148 -47
  325. package/dist/positioning/index.js.map +1 -1
  326. package/package.json +6 -6
  327. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  328. 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,13 +33,158 @@ 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
+
24
188
  var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
25
189
  "insertCompositionText",
26
190
  "insertFromComposition",
@@ -32,13 +196,11 @@ var CompositionTracker = class {
32
196
  #activeTargets = /* @__PURE__ */ new Set();
33
197
  #onStart;
34
198
  #onEnd;
35
- /** The field whose confirming `input` is still owed, while the window is open. */
36
199
  #confirmedTarget = null;
37
200
  constructor(options = {}) {
38
201
  this.#onStart = options.onStart;
39
202
  this.#onEnd = options.onEnd;
40
203
  }
41
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
42
204
  observe(target) {
43
205
  if (this.#observedTargets.has(target)) return;
44
206
  target.addEventListener("compositionstart", this.#handleStart);
@@ -46,7 +208,6 @@ var CompositionTracker = class {
46
208
  target.addEventListener("keydown", this.#handleKeydown);
47
209
  this.#observedTargets.add(target);
48
210
  }
49
- /** Stops tracking one target and clears any active composition it owned. */
50
211
  unobserve(target) {
51
212
  if (!this.#observedTargets.delete(target)) return;
52
213
  target.removeEventListener("compositionstart", this.#handleStart);
@@ -55,7 +216,6 @@ var CompositionTracker = class {
55
216
  this.#activeTargets.delete(target);
56
217
  if (this.#confirmedTarget === target) this.#confirmedTarget = null;
57
218
  }
58
- /** Releases every listener and clears state so reconnect starts cleanly. */
59
219
  disconnect() {
60
220
  for (const target of this.#observedTargets) {
61
221
  target.removeEventListener("compositionstart", this.#handleStart);
@@ -66,16 +226,9 @@ var CompositionTracker = class {
66
226
  this.#activeTargets.clear();
67
227
  this.#confirmedTarget = null;
68
228
  }
69
- /** True when lifecycle tracking or the current event reports composition. */
70
229
  isComposing(event) {
71
230
  return this.#activeTargets.size > 0 || event?.isComposing === true;
72
231
  }
73
- /**
74
- * Whether `event` is the `input` echoing the composition just confirmed.
75
- *
76
- * Asking closes the window either way, so one confirmation is folded at most
77
- * once and a consumer asks once per `input`.
78
- */
79
232
  consumesConfirmedInput(event) {
80
233
  const confirmed = this.#confirmedTarget;
81
234
  this.#confirmedTarget = null;
@@ -93,38 +246,31 @@ var CompositionTracker = class {
93
246
  this.#confirmedTarget = event.target;
94
247
  this.#onEnd?.(event);
95
248
  };
96
- /** A key on an observed field opens an edit of its own, so no echo is owed. */
97
249
  #handleKeydown = () => {
98
250
  this.#confirmedTarget = null;
99
251
  };
100
252
  };
101
253
 
102
- // src/utils/field_mirror.ts
103
254
  function commitField(target) {
104
255
  target.dispatchEvent(new Event("change", { bubbles: true }));
105
256
  }
106
257
 
107
- // src/utils/microtask_coalescer.ts
108
258
  var MicrotaskCoalescer = class {
109
259
  #run;
110
260
  #queued = false;
111
261
  #active = false;
112
262
  #generation = 0;
113
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
114
263
  constructor(run) {
115
264
  this.#run = run;
116
265
  }
117
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
118
266
  activate() {
119
267
  this.#active = true;
120
268
  }
121
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
122
269
  cancel() {
123
270
  this.#active = false;
124
271
  this.#queued = false;
125
272
  this.#generation += 1;
126
273
  }
127
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
128
274
  schedule() {
129
275
  if (!this.#active || this.#queued) return;
130
276
  this.#queued = true;
@@ -137,7 +283,17 @@ var MicrotaskCoalescer = class {
137
283
  }
138
284
  };
139
285
 
140
- // 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
+
141
297
  function scrollOptionIntoView(list, option) {
142
298
  if (list.scrollHeight <= list.clientHeight) return;
143
299
  const listRect = list.getBoundingClientRect();
@@ -149,16 +305,20 @@ function scrollOptionIntoView(list, option) {
149
305
  }
150
306
  }
151
307
 
152
- // src/utils/state_regions.ts
153
- var StateRegions = class {
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
+ );
154
313
  #whenTrue;
155
314
  #whenFalse;
156
- #taken = /* @__PURE__ */ new WeakSet();
157
- constructor(options) {
315
+ #record;
316
+ constructor(options, owner) {
158
317
  this.#whenTrue = options.whenTrue;
159
318
  this.#whenFalse = options.whenFalse ?? null;
319
+ this.#record = `data-${owner}-hidden-region`;
320
+ _StateRegions.#registry.names.add(this.#record);
160
321
  }
161
- /** Shows the regions inside `host` that belong to `isTrue` and hides the others. */
162
322
  reflect(host, isTrue) {
163
323
  const shown = this.#inside(host, this.#whenTrue());
164
324
  if (!this.#whenFalse) {
@@ -174,103 +334,201 @@ var StateRegions = class {
174
334
  for (const region of shown) this.#write(region, !isTrue);
175
335
  for (const region of hiddenSide) this.#write(region, isTrue);
176
336
  }
177
- /** Writes `hidden` where it moves, noting which regions it takes out of view. */
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
+ }
178
360
  #write(region, hidden) {
179
- if (hidden) this.#taken.add(region);
180
- else this.#taken.delete(region);
181
- if (region.hidden !== hidden) region.hidden = 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);
182
367
  }
183
- /** Returns a region this instance hid; one it never hid keeps what it carries. */
184
368
  #give(region) {
185
- if (this.#taken.delete(region)) region.hidden = false;
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
+ }
186
447
  }
187
- /** The declared regions that sit within `host`, which may be the host itself. */
188
448
  #inside(host, regions) {
189
449
  return regions.filter((region) => host.contains(region));
190
450
  }
191
451
  };
192
452
 
193
- // src/controllers/combobox_controller.ts
453
+ function targetSelector(identifier, name) {
454
+ return `[data-${identifier}-target~="${name}"]`;
455
+ }
456
+
194
457
  var ComboboxController = class extends Controller {
458
+ #moves = new MoveCounter();
459
+ #moveToken = 0;
195
460
  static targets = ["input", "list", "option", "empty"];
196
- static actions = ["close", "filter", "onKeydown", "open", "selectByClick"];
461
+ static actions = ["close", "filter", "onKeydown", "open", "select"];
197
462
  static events = ["selected"];
198
- /** Owns `hidden` on the regions declared for the empty state. */
199
- #emptyRegion = new StateRegions({ whenTrue: () => this.emptyTargets });
200
- /** Stable ID of the active option; the live element is resolved before every use. */
463
+ #emptyRegion = new StateRegions({ whenTrue: () => this.emptyTargets }, this.identifier);
201
464
  #activeId = null;
202
465
  #connected = false;
203
- /** 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
+ );
204
473
  #reconcile = new MicrotaskCoalescer(() => this.#reconcileOptions());
205
- /**
206
- * Suppresses {@link ComboboxController.open | open} for the duration of the
207
- * programmatic re-focus in `#select`, so committing a value (which returns focus
208
- * to the input) does not immediately re-open the listbox via a `focus`-bound
209
- * action.
210
- */
211
474
  #suppressOpen = false;
212
- /** Owns IME lifecycle state; confirmed text re-filters the list once. */
213
475
  #composition = new CompositionTracker({ onEnd: () => this.filter() });
214
- /** Starts closed with no active option and registers the outside-click listener. */
215
476
  connect() {
216
477
  if (this.hasInputTarget) this.#composition.observe(this.inputTarget);
217
478
  this.close();
479
+ this.#list = this.hasListTarget ? this.listTarget : null;
218
480
  document.addEventListener("click", this.#onOutsideClick, true);
219
481
  this.#connected = true;
220
482
  this.#reconcile.activate();
221
483
  }
222
- /** Removes the document-level listener registered in {@link connect}. */
223
484
  disconnect() {
224
485
  this.#connected = false;
225
486
  this.#reconcile.cancel();
226
487
  this.#composition.disconnect();
227
488
  document.removeEventListener("click", this.#onOutsideClick, true);
228
489
  }
229
- /** Tracks an input added initially or after connect. */
230
490
  inputTargetConnected(input) {
231
491
  this.#composition.observe(input);
232
492
  if (this.#connected) this.#queueOptionReconciliation();
233
493
  }
234
- /** Removes composition listeners when the active input is replaced or removed. */
235
494
  inputTargetDisconnected(input) {
236
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);
237
508
  }
238
- /** Settles a connecting region on the side the empty state is on. */
239
509
  emptyTargetConnected() {
240
510
  this.#reflectEmptyState();
241
511
  }
242
- /** Establishes the active-state baseline, then reconciles a runtime addition. */
243
512
  optionTargetConnected(option) {
244
513
  option.setAttribute("aria-selected", "false");
245
514
  if (this.#connected) this.#queueOptionReconciliation();
246
515
  }
247
- /** Clears the disconnected node's active state, then reconciles the survivors. */
248
516
  optionTargetDisconnected(option) {
249
517
  option.setAttribute("aria-selected", "false");
250
518
  if (this.#connected) this.#queueOptionReconciliation();
251
519
  }
252
- /** Filters confirmed input text and opens the listbox. */
253
520
  filter(event) {
254
521
  if (this.#composition.isComposing(event)) return;
255
522
  this.open();
256
523
  }
257
- /**
258
- * Opens the listbox, re-filtering the options against the current input value
259
- * so the visible options and empty state always match what is typed (e.g.
260
- * re-opening with a stale non-matching value still surfaces the empty state).
261
- */
262
524
  open() {
263
525
  if (!this.hasListTarget || !this.hasInputTarget || this.#suppressOpen) return;
264
526
  this.#applyFilter();
265
- this.listTarget.hidden = false;
266
- this.inputTarget.setAttribute("aria-expanded", "true");
527
+ this.#listHidden.write(this.listTarget, null);
528
+ this.#expanded.write(this.inputTarget, "true");
267
529
  this.#setActive(-1);
268
530
  this.#reflectEmptyState();
269
531
  }
270
- /**
271
- * Hides options that don't match the current input value (case-insensitive
272
- * substring). An empty query shows every option. Does not change open state.
273
- */
274
532
  #applyFilter() {
275
533
  const query = this.inputTarget.value.trim().toLowerCase();
276
534
  for (const option of this.optionTargets) {
@@ -278,24 +536,13 @@ var ComboboxController = class extends Controller {
278
536
  option.hidden = query.length > 0 && !text.includes(query);
279
537
  }
280
538
  }
281
- /**
282
- * Closes the listbox, clears the active option, and updates ARIA state.
283
- *
284
- * Survives a missing input in both directions. {@link inputTargetConnected}
285
- * accepts an input added after connect, so dereferencing it here would abort
286
- * `connect()` before it registers the outside-click listener, and nothing
287
- * re-registers it later. Symmetrically, an input removed while the popup is
288
- * open must still let the popup come down — so hiding the list is
289
- * unconditional and only the ARIA half is guarded.
290
- */
291
539
  close() {
292
540
  if (!this.hasListTarget) return;
293
- this.listTarget.hidden = true;
541
+ this.#listHidden.write(this.listTarget, "");
294
542
  this.#reflectEmptyState();
295
- if (this.hasInputTarget) this.inputTarget.setAttribute("aria-expanded", "false");
543
+ if (this.hasInputTarget) this.#expanded.write(this.inputTarget, "false");
296
544
  this.#setActive(-1);
297
545
  }
298
- /** Routes keyboard interaction per the APG combobox model. */
299
546
  onKeydown(event) {
300
547
  if (this.#composition.isComposing(event)) return;
301
548
  if (event.defaultPrevented) return;
@@ -368,56 +615,41 @@ var ComboboxController = class extends Controller {
368
615
  break;
369
616
  }
370
617
  }
371
- /**
372
- * Closes the listbox when a click lands outside the combobox. Mirrors the menu
373
- * button's outside-click behavior; clicks on an option are inside the element,
374
- * so `#select` (not this handler) closes the popup after committing.
375
- */
376
618
  #onOutsideClick = (event) => {
377
619
  if (!this.#isClosed && !this.element.contains(event.target)) this.close();
378
620
  };
379
- /** Selects the clicked option. Bound via `data-action` (click). */
380
- selectByClick(event) {
381
- const option = event.currentTarget.closest('[role="option"]');
382
- if (option && this.optionTargets.includes(option)) this.#select(option);
383
- }
384
- /**
385
- * Commits an option: fills the input, closes the listbox, notifies listeners.
386
- *
387
- * An input removed while the popup is open leaves the options clickable, and
388
- * {@link ComboboxController.close | close} already survives that state. Selection
389
- * does too: the popup comes down and listeners still hear the choice, with only
390
- * the field-bound half — the value write, the focus return, and the native
391
- * `change` — skipped, because there is no field to carry them.
392
- */
393
- #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) {
394
631
  const value = option.dataset.value ?? (option.textContent ?? "").trim();
395
632
  if (!this.hasInputTarget) {
396
633
  this.close();
397
- this.dispatch("selected", { detail: { value } });
634
+ this.dispatch("selected", { detail: { value, reason } });
398
635
  return;
399
636
  }
400
637
  const changed = this.inputTarget.value !== value;
638
+ if (changed) this.#moveToken = this.#moves.record();
639
+ const token = this.#moveToken;
401
640
  this.inputTarget.value = value;
402
641
  this.close();
403
642
  this.#suppressOpen = true;
404
- this.inputTarget.focus();
643
+ if (focus && (reason !== "api" || this.element.contains(document.activeElement)))
644
+ this.inputTarget.focus();
405
645
  this.#suppressOpen = false;
646
+ if (!this.#moves.isLatest(token)) return;
406
647
  if (changed) {
407
648
  commitField(this.inputTarget);
408
649
  }
409
- this.dispatch("selected", { detail: { value } });
410
- }
411
- /**
412
- * Writes whether the open listbox has zero matching options to the state hook
413
- * `data-stimeo--combobox-empty` on the root element and to the regions declared
414
- * for that state. Behavior only: consumers decide how to present the empty state
415
- * via CSS keyed off the attribute, while the `empty` targets carry the copy.
416
- *
417
- * Every path that can change the answer — opening, filtering, reconciling a
418
- * runtime option change, and closing — ends here, so the hook and the regions
419
- * are a pure function of the state rather than of the order things happened in.
420
- */
650
+ if (!this.#moves.isLatest(token)) return;
651
+ this.dispatch("selected", { detail: { value, reason } });
652
+ }
421
653
  #reflectEmptyState() {
422
654
  const empty = !this.#isClosed && this.#visibleOptions().length === 0;
423
655
  if (empty) {
@@ -427,31 +659,31 @@ var ComboboxController = class extends Controller {
427
659
  }
428
660
  this.#emptyRegion.reflect(this.element, empty);
429
661
  }
430
- /**
431
- * Marks the visible option at `index` active via `aria-selected` and the
432
- * input's `aria-activedescendant`. Pass `-1` to clear the active option.
433
- */
434
662
  #setActive(index) {
435
663
  const visible = this.#visibleOptions();
436
664
  const active = index === -1 ? null : visible[index] ?? null;
437
665
  syncActiveOption(this.optionTargets, active);
438
666
  this.#activeId = active?.id || null;
439
- if (this.hasInputTarget) {
440
- if (active?.id) {
441
- this.inputTarget.setAttribute("aria-activedescendant", active.id);
442
- } else {
443
- this.inputTarget.removeAttribute("aria-activedescendant");
444
- }
445
- }
667
+ if (this.hasInputTarget) this.#activeDescendant.write(this.inputTarget, active?.id || null);
446
668
  if (active && this.hasListTarget) scrollOptionIntoView(this.listTarget, active);
447
669
  }
448
- /** 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
+ }
449
679
  #reconcileOptions() {
450
- 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
+ }
451
684
  this.#reconcileActive();
452
685
  this.#reflectEmptyState();
453
686
  }
454
- /** Preserves a surviving active ID; disappearance deliberately clears active state. */
455
687
  #reconcileActive() {
456
688
  if (!this.hasInputTarget || this.#isClosed) {
457
689
  this.#setActive(-1);
@@ -467,26 +699,21 @@ var ComboboxController = class extends Controller {
467
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;
468
700
  if (!stateMatches) this.#setActive(index);
469
701
  }
470
- /** Resolves the stable ID, with `aria-selected` as the ID-less compatibility marker. */
471
702
  #findActiveIndex(options) {
472
703
  const activeId = (this.hasInputTarget ? this.inputTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
473
704
  if (activeId) return options.findIndex((option) => option.id === activeId);
474
705
  return options.findIndex((option) => option.getAttribute("aria-selected") === "true");
475
706
  }
476
- /** Coalesces all target callbacks from one MutationObserver batch. */
477
707
  #queueOptionReconciliation() {
478
708
  this.#reconcile.schedule();
479
709
  }
480
- /** The options currently shown (not filtered out). */
481
710
  #visibleOptions() {
482
711
  return this.optionTargets.filter((option) => !option.hidden);
483
712
  }
484
- /** Whether the listbox is currently hidden. */
485
713
  get #isClosed() {
486
714
  return !this.hasListTarget || this.listTarget.hidden !== false;
487
715
  }
488
716
  };
489
717
 
490
718
  export { ComboboxController };
491
- //# sourceMappingURL=combobox_controller.js.map
492
719
  //# sourceMappingURL=combobox_controller.js.map