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/command_palette_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,54 +246,54 @@ 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/before_cache_reset.ts
103
- var BeforeCacheReset = class _BeforeCacheReset {
104
- /** Every subscribed instance, iterated by the one shared document listener. */
105
- static #subscribers = /* @__PURE__ */ new Set();
106
- /** The shared listener; installed while at least one instance is subscribed. */
107
- static #onBeforeCache = () => {
108
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
109
- };
110
- #rewind;
111
- /** @param rewind - the pass that returns this controller's state to its initial form. */
112
- constructor(rewind) {
113
- this.#rewind = rewind;
114
- }
115
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
116
- activate() {
117
- const first = _BeforeCacheReset.#subscribers.size === 0;
118
- _BeforeCacheReset.#subscribers.add(this);
119
- if (first) {
120
- document.addEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
254
+ var DetachGate = class _DetachGate {
255
+ #pending = false;
256
+ get pending() {
257
+ return this.#pending;
258
+ }
259
+ static isDetached(host) {
260
+ return !host.element.isConnected || !_DetachGate.#listed(host);
261
+ }
262
+ disconnected(host, teardown) {
263
+ const carried = _DetachGate.#carried(host);
264
+ if (!carried && _DetachGate.isDetached(host)) {
265
+ this.#pending = false;
266
+ teardown();
267
+ return;
121
268
  }
269
+ this.#pending = true;
270
+ const probe = () => {
271
+ if (!this.#pending) return;
272
+ this.#pending = false;
273
+ teardown();
274
+ };
275
+ if (carried) setTimeout(probe, 0);
276
+ else queueMicrotask(probe);
122
277
  }
123
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
124
- deactivate() {
125
- _BeforeCacheReset.#subscribers.delete(this);
126
- if (_BeforeCacheReset.#subscribers.size > 0) return;
127
- document.removeEventListener("turbo:before-cache", _BeforeCacheReset.#onBeforeCache);
278
+ static #carried(host) {
279
+ const element = host.element;
280
+ return !element.isConnected && element.closest("[data-turbo-permanent]") !== null && _DetachGate.#listed(host);
281
+ }
282
+ static #listed(host) {
283
+ const tokens = (host.element.getAttribute("data-controller") ?? "").split(/\s+/);
284
+ return tokens.includes(host.identifier);
285
+ }
286
+ cancel() {
287
+ this.#pending = false;
128
288
  }
129
289
  };
130
290
 
131
- // src/utils/escape_layer.ts
132
291
  var EscapeLayer = class _EscapeLayer {
133
- static #registries = /* @__PURE__ */ new WeakMap();
134
- #ownerDocument = null;
135
- /** Dismissal callback while active; `null` when inactive. */
136
- #onDismiss = null;
137
- /** Live predicate deciding whether the layer claims a press; `null` = always. */
138
- #claims = null;
139
- /**
140
- * Activates this layer at the top of its document's Escape stack, installing
141
- * the document's shared resolver listener if this is its first layer.
142
- * Re-activating an already-active layer moves it to the top.
143
- */
292
+ static #registries = sharedRegistry(
293
+ "stimeo-ui.escape-layer.registry.v1",
294
+ () => /* @__PURE__ */ new WeakMap()
295
+ );
296
+ #active = null;
144
297
  activate(ownerDocument = document, options) {
145
298
  this.deactivate();
146
299
  let registry = _EscapeLayer.#registries.get(ownerDocument);
@@ -149,44 +302,31 @@ var EscapeLayer = class _EscapeLayer {
149
302
  _EscapeLayer.#registries.set(ownerDocument, registry);
150
303
  ownerDocument.addEventListener("keydown", registry.onKeydown);
151
304
  }
152
- registry.stack.push(this);
153
- this.#ownerDocument = ownerDocument;
154
- this.#onDismiss = options.onDismiss;
155
- this.#claims = options.claims ?? null;
305
+ const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
306
+ registry.stack.push(entry);
307
+ this.#active = { document: ownerDocument, entry };
156
308
  }
157
- /**
158
- * Removes this layer from its document's Escape stack, uninstalling the
159
- * shared listener when the stack empties. Safe to call when inactive.
160
- */
161
309
  deactivate() {
162
- const ownerDocument = this.#ownerDocument;
163
- if (!ownerDocument) return;
164
- const registry = _EscapeLayer.#registries.get(ownerDocument);
310
+ const active = this.#active;
311
+ if (!active) return;
312
+ const registry = _EscapeLayer.#registries.get(active.document);
165
313
  if (registry) {
166
- const index = registry.stack.lastIndexOf(this);
314
+ const index = registry.stack.lastIndexOf(active.entry);
167
315
  if (index >= 0) registry.stack.splice(index, 1);
168
316
  if (registry.stack.length === 0) {
169
- ownerDocument.removeEventListener("keydown", registry.onKeydown);
170
- _EscapeLayer.#registries.delete(ownerDocument);
317
+ active.document.removeEventListener("keydown", registry.onKeydown);
318
+ _EscapeLayer.#registries.delete(active.document);
171
319
  }
172
320
  }
173
- this.#ownerDocument = null;
174
- this.#onDismiss = null;
175
- this.#claims = null;
321
+ this.#active = null;
176
322
  }
177
- /**
178
- * Whether this active layer would own a press right now: it is the topmost
179
- * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
180
- * and diagnostics — production dismissal goes through the shared listener.
181
- */
182
323
  get ownsEscape() {
183
- const ownerDocument = this.#ownerDocument;
184
- if (!ownerDocument) return false;
185
- const registry = _EscapeLayer.#registries.get(ownerDocument);
324
+ const active = this.#active;
325
+ if (!active) return false;
326
+ const registry = _EscapeLayer.#registries.get(active.document);
186
327
  if (!registry) return false;
187
- return _EscapeLayer.#resolveOwner(registry.stack) === this;
328
+ return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
188
329
  }
189
- /** Builds a document's registry with its shared resolver listener. */
190
330
  static #createRegistry() {
191
331
  const registry = {
192
332
  stack: [],
@@ -195,24 +335,22 @@ var EscapeLayer = class _EscapeLayer {
195
335
  const owner = _EscapeLayer.#resolveOwner(registry.stack);
196
336
  if (!owner) return;
197
337
  event.preventDefault();
198
- owner.#onDismiss?.();
338
+ owner.onDismiss();
199
339
  }
200
340
  };
201
341
  return registry;
202
342
  }
203
- /** The topmost stack layer whose claims predicate passes, or `null`. */
204
343
  static #resolveOwner(stack) {
205
344
  for (let index = stack.length - 1; index >= 0; index--) {
206
345
  const layer = stack[index];
207
346
  if (!layer) continue;
208
- if (layer.#claims && !layer.#claims()) continue;
347
+ if (layer.claims && !layer.claims()) continue;
209
348
  return layer;
210
349
  }
211
350
  return null;
212
351
  }
213
352
  };
214
353
 
215
- // src/utils/focus_candidate.ts
216
354
  function inheritsFieldsetDisabled(control) {
217
355
  let fieldset = control.closest("fieldset[disabled]");
218
356
  while (fieldset) {
@@ -222,8 +360,34 @@ function inheritsFieldsetDisabled(control) {
222
360
  }
223
361
  return false;
224
362
  }
363
+ function unslotted(element) {
364
+ return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
365
+ }
366
+ function flatTreeParent(element) {
367
+ if (unslotted(element)) return null;
368
+ const parent = element.assignedSlot ?? element.parentNode;
369
+ if (parent instanceof ShadowRoot) return parent.host;
370
+ return parent instanceof Element ? parent : null;
371
+ }
372
+ function flatTreeContains(ancestor, element) {
373
+ for (let node = element; node; node = flatTreeParent(node)) {
374
+ if (node === ancestor) return true;
375
+ }
376
+ return false;
377
+ }
378
+ function closestInFlatTree(element, selector) {
379
+ for (let node = element; node; node = flatTreeParent(node)) {
380
+ if (node.matches(selector)) return node;
381
+ }
382
+ return null;
383
+ }
384
+ function flatTreeChildren(element) {
385
+ return ownedScope(element) ?? Array.from(element.children);
386
+ }
225
387
  function canTakeFocus(element) {
226
- if (element.closest("[hidden], [inert]")) return false;
388
+ for (let node = element; node; node = flatTreeParent(node)) {
389
+ if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
390
+ }
227
391
  if (element instanceof HTMLInputElement && element.type === "hidden") return false;
228
392
  if (!("disabled" in element)) return true;
229
393
  if (element.disabled) return false;
@@ -280,222 +444,676 @@ function isTabStop(element) {
280
444
  if (tabindex !== null) return tabindex >= 0;
281
445
  return hasNativeTabStop(element) || hasEditableTabStop(element);
282
446
  }
283
- function tabStopsWithin(root) {
284
- return Array.from(root.querySelectorAll(TAB_STOP_CANDIDATE_SELECTOR)).filter(
285
- isTabStop
447
+ function isRadio(element) {
448
+ return element instanceof HTMLInputElement && element.type === "radio";
449
+ }
450
+ function sameRadioGroup(a, b) {
451
+ return a.name !== "" && a.name === b.name && a.form === b.form && a.getRootNode() === b.getRootNode();
452
+ }
453
+ function isInEditableContent(element) {
454
+ for (let node = element.parentElement; node; node = node.parentElement) {
455
+ if (node.getAttribute("contenteditable")?.toLowerCase() === "false") return false;
456
+ if (hasEditableTabStop(node)) return true;
457
+ }
458
+ return false;
459
+ }
460
+ var XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
461
+ function isSvgLink(element) {
462
+ return element instanceof SVGElement && element.localName === "a" && (element.hasAttribute("href") || element.hasAttributeNS(XLINK_NAMESPACE, "href"));
463
+ }
464
+ function hasRenderedImage(area) {
465
+ const map = area.closest("map");
466
+ const key = map?.name || map?.id;
467
+ if (!map || !key) return false;
468
+ const root = map.getRootNode();
469
+ return Array.from(root.querySelectorAll("img[usemap]")).some(
470
+ (image) => image.getAttribute("usemap") === `#${key}` && isRenderedForFocus(image)
286
471
  );
287
472
  }
473
+ function isFocusableInOrder(element) {
474
+ const tabindex = authoredTabindex(element);
475
+ return (tabindex === null || tabindex >= 0) && canTakeFocus(element);
476
+ }
477
+ function extraStop(element) {
478
+ if (element instanceof HTMLAreaElement) {
479
+ const focusable = element.hasAttribute("href") || authoredTabindex(element) !== null;
480
+ return focusable && isFocusableInOrder(element) && hasRenderedImage(element);
481
+ }
482
+ if (element instanceof HTMLObjectElement) {
483
+ return isFocusableInOrder(element) && isRenderedForFocus(element) && element.contentWindow !== null;
484
+ }
485
+ if (isSvgLink(element))
486
+ return isFocusableInOrder(element) && isRenderedForFocus(element);
487
+ return null;
488
+ }
489
+ function isSequentialStop(element) {
490
+ const stop = extraStop(element) ?? (element.matches(TAB_STOP_CANDIDATE_SELECTOR) && isTabStop(element));
491
+ if (!stop) return false;
492
+ const link = element instanceof HTMLAnchorElement || element instanceof HTMLAreaElement || isSvgLink(element);
493
+ return !(link && authoredTabindex(element) === null && isInEditableContent(element));
494
+ }
495
+ function ownedScope(element) {
496
+ if (element.shadowRoot) return Array.from(element.shadowRoot.children);
497
+ if (element instanceof HTMLSlotElement) {
498
+ const assigned = element.assignedElements();
499
+ return assigned.length > 0 ? assigned : Array.from(element.children);
500
+ }
501
+ return null;
502
+ }
503
+ function orderScope(elements, walk) {
504
+ const entries = [];
505
+ const visit = (element) => {
506
+ if (element === walk.from) walk.fromAt = walk.tree.length;
507
+ const tabindex = authoredTabindex(element) ?? 0;
508
+ const stop = (element instanceof HTMLElement || element instanceof SVGElement) && isSequentialStop(element) ? element : null;
509
+ if (stop) walk.tree.push(stop);
510
+ const owned = ownedScope(element);
511
+ if (owned) {
512
+ if (tabindex >= 0) {
513
+ entries.push({ tabindex, stops: [...stop ? [stop] : [], ...orderScope(owned, walk)] });
514
+ }
515
+ return;
516
+ }
517
+ if (stop) entries.push({ tabindex, stops: [stop] });
518
+ for (const child of Array.from(element.children)) visit(child);
519
+ };
520
+ for (const element of elements) visit(element);
521
+ const positive = entries.filter((entry) => entry.tabindex > 0);
522
+ positive.sort((a, b) => a.tabindex - b.tabindex);
523
+ const rest = entries.filter((entry) => entry.tabindex === 0);
524
+ return [...positive, ...rest].flatMap((entry) => entry.stops);
525
+ }
526
+ function passesRadioRule(element, tree, focusedRadio) {
527
+ if (!isRadio(element) || element.checked) return true;
528
+ const group = tree.filter(
529
+ (stop) => stop === element || isRadio(stop) && sameRadioGroup(stop, element)
530
+ );
531
+ if (group.some((radio) => radio.checked)) return false;
532
+ if (group[0] !== element) return false;
533
+ return focusedRadio === null || !sameRadioGroup(focusedRadio, element);
534
+ }
535
+ function startIndex(order, walk, backward, isStop) {
536
+ if (walk.fromAt < 0) return backward ? order.length - 1 : 0;
537
+ const at = order.indexOf(walk.from);
538
+ if (at >= 0) return backward ? at - 1 : at + 1;
539
+ const neighbour = backward ? walk.tree.slice(0, walk.fromAt).reverse().find(isStop) : walk.tree.slice(walk.fromAt).find(isStop);
540
+ if (neighbour) return order.indexOf(neighbour);
541
+ return backward ? order.length - 1 : 0;
542
+ }
543
+ function sequentialTabStops(root, from = null, backward = false) {
544
+ const walk = { from, tree: [], fromAt: from === root ? 0 : -1 };
545
+ const order = orderScope(ownedScope(root) ?? Array.from(root.children), walk);
546
+ const focusedRadio = walk.fromAt >= 0 && isRadio(from) ? from : null;
547
+ const isStop = (element) => passesRadioRule(element, walk.tree, focusedRadio);
548
+ const start = startIndex(order, walk, backward, isStop);
549
+ const visited = backward ? [...order.slice(0, start + 1).reverse(), ...order.slice(start + 1).reverse()] : [...order.slice(start), ...order.slice(0, start)];
550
+ return visited.filter((element) => element !== from && isStop(element));
551
+ }
552
+ function deepActiveElement(ownerDocument) {
553
+ let active = ownerDocument.activeElement;
554
+ while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
555
+ return active;
556
+ }
288
557
 
289
- // src/utils/focus_trap.ts
290
- var FocusTrap = class {
291
- /** The element focused before activation, restored on deactivation. */
292
- #previouslyFocused = null;
293
- /** The body's inline `overflow` before locking, restored on deactivation. */
294
- #previousBodyOverflow = "";
295
- /** Whether scroll was locked this activation (so it is only restored if applied). */
296
- #scrollLocked = false;
297
- /** Background siblings made `inert` while active, restored on deactivation. */
298
- #inertedSiblings = [];
299
- /** Whether the modal side effects are currently applied. */
558
+ var MicrotaskCoalescer = class {
559
+ #run;
560
+ #queued = false;
561
+ #active = false;
562
+ #generation = 0;
563
+ constructor(run) {
564
+ this.#run = run;
565
+ }
566
+ activate() {
567
+ this.#active = true;
568
+ }
569
+ cancel() {
570
+ this.#active = false;
571
+ this.#queued = false;
572
+ this.#generation += 1;
573
+ }
574
+ schedule() {
575
+ if (!this.#active || this.#queued) return;
576
+ this.#queued = true;
577
+ const generation = this.#generation;
578
+ queueMicrotask(() => {
579
+ if (generation !== this.#generation || !this.#queued || !this.#active) return;
580
+ this.#queued = false;
581
+ this.#run();
582
+ });
583
+ }
584
+ };
585
+
586
+ function queryOpenDescendants(root, selector) {
587
+ const matches = [];
588
+ const roots = [root];
589
+ for (const scope of roots) {
590
+ matches.push(...scope.querySelectorAll(selector));
591
+ if (scope.nodeType === Node.ELEMENT_NODE) {
592
+ const shadow = scope.shadowRoot;
593
+ if (shadow) roots.push(shadow);
594
+ }
595
+ for (const element of scope.querySelectorAll("*")) {
596
+ if (element.shadowRoot) roots.push(element.shadowRoot);
597
+ }
598
+ }
599
+ return matches;
600
+ }
601
+
602
+ var TabindexLoan = class _TabindexLoan {
603
+ static #registry = sharedRegistry(
604
+ "stimeo-ui.tabindex-loan.registry.v1",
605
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Set() })
606
+ );
607
+ #value;
608
+ #record;
609
+ #lent = /* @__PURE__ */ new Set();
610
+ constructor(value, owner) {
611
+ this.#value = value;
612
+ this.#record = `data-${owner}-tabindex-loan`;
613
+ _TabindexLoan.#registry.names.add(this.#record);
614
+ }
615
+ lend(element) {
616
+ const stack = this.#stack(element);
617
+ const current = element.getAttribute("tabindex");
618
+ const held = stack.find((hold) => hold.loan === this) ?? stack.find(
619
+ (hold) => hold.loan === null && hold.record === this.#record && hold.value === this.#value
620
+ );
621
+ const linked = stack.at(-1)?.value === current;
622
+ if (!held && current !== null && !linked) return;
623
+ this.#lent.add(element);
624
+ if (held) held.loan = this;
625
+ else stack.push({ loan: this, record: this.#record, value: this.#value, linked });
626
+ this.#mark(element, stack);
627
+ if (current === null || !held && current !== this.#value) {
628
+ element.setAttribute("tabindex", this.#value);
629
+ }
630
+ }
631
+ returnAll() {
632
+ for (const element of this.#lent) {
633
+ const stack = this.#stack(element);
634
+ const hold = stack.find((held) => held.loan === this);
635
+ if (hold) this.#release(element, stack, hold);
636
+ }
637
+ this.#lent.clear();
638
+ }
639
+ reclaim(element) {
640
+ if (element.getAttribute(this.#record) !== this.#value) return;
641
+ const stack = this.#stack(element);
642
+ const hold = stack.find((held) => held.loan === null && held.record === this.#record);
643
+ if (hold && !element.matches(":focus")) this.#release(element, stack, hold);
644
+ }
645
+ reclaimWithin(root) {
646
+ for (const element of queryOpenDescendants(root, `[${this.#record}]`)) {
647
+ this.reclaim(element);
648
+ }
649
+ }
650
+ #stack(element) {
651
+ const stacks = _TabindexLoan.#registry.stacks;
652
+ const stack = stacks.get(element) ?? this.#seed(element);
653
+ stacks.set(element, stack);
654
+ return stack;
655
+ }
656
+ #seed(element) {
657
+ const current = element.getAttribute("tabindex");
658
+ const holds = [];
659
+ for (const record of _TabindexLoan.#registry.names) {
660
+ const value = element.getAttribute(record);
661
+ if (value !== null) holds.push({ loan: null, record, value, linked: true });
662
+ }
663
+ holds.sort((one, other) => Number(one.value === current) - Number(other.value === current));
664
+ for (const [index, hold] of holds.entries()) hold.linked = index > 0;
665
+ return holds;
666
+ }
667
+ #release(element, stack, hold) {
668
+ const current = element.getAttribute("tabindex");
669
+ const index = stack.indexOf(hold);
670
+ const owns = current === hold.value && !stack.slice(index + 1).some((above2) => above2.value === current);
671
+ stack.splice(index, 1);
672
+ const above = stack[index];
673
+ if (above) above.linked &&= hold.linked;
674
+ this.#mark(element, stack);
675
+ if (!owns) return;
676
+ const below = hold.linked ? stack[index - 1] : void 0;
677
+ if (!below) element.removeAttribute("tabindex");
678
+ else if (below.value !== current) element.setAttribute("tabindex", below.value);
679
+ }
680
+ #mark(element, stack) {
681
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
682
+ for (const hold of stack) records.set(hold.record, hold.value);
683
+ for (const [name, recorded] of records) {
684
+ if (element.getAttribute(name) === recorded) continue;
685
+ if (recorded === null) element.removeAttribute(name);
686
+ else element.setAttribute(name, recorded);
687
+ }
688
+ }
689
+ };
690
+
691
+ var FOCUS_TRAP_OWNER = "stimeo-focus-trap";
692
+ var INERT_MARK = `data-${FOCUS_TRAP_OWNER}-inert`;
693
+ var OVERFLOW_MARK = `data-${FOCUS_TRAP_OWNER}-overflow`;
694
+ var FocusTrap = class _FocusTrap {
695
+ static #registries = sharedRegistry(
696
+ "stimeo-ui.focus-trap.registry.v2",
697
+ () => /* @__PURE__ */ new WeakMap()
698
+ );
699
+ static #TEXT_ENTRY_TYPES = /* @__PURE__ */ new Set([
700
+ "text",
701
+ "search",
702
+ "email",
703
+ "url",
704
+ "tel",
705
+ "password",
706
+ "number"
707
+ ]);
708
+ #openers = [];
709
+ #entry;
710
+ #tabindexLoans = /* @__PURE__ */ new Map();
300
711
  #activeState = false;
301
- /** Registers the trap on the shared Escape stack while active (see {@link EscapeLayer}). */
302
712
  #escapeLayer = new EscapeLayer();
303
- /** Returns the trapped element; called on every operation for the live target. */
713
+ #gate = new DetachGate();
304
714
  #getContainer;
305
- /** Closing/focus hooks; see {@link FocusTrapOptions}. */
306
715
  #options;
307
- /**
308
- * @param getContainer - Returns the trapped element. Called on every operation
309
- * so the live target is always used.
310
- * @param options - Closing/focus hooks; see {@link FocusTrapOptions}.
311
- */
312
716
  constructor(getContainer, options = {}) {
313
717
  this.#getContainer = getContainer;
314
718
  this.#options = options;
719
+ this.#entry = {
720
+ container: () => this.#readContainer(),
721
+ moveFocus: (backward) => this.#moveFocus(backward),
722
+ refocus: () => this.#refocus(),
723
+ isolated: false,
724
+ lockedBody: null,
725
+ openedFrom: null,
726
+ handedBack: []
727
+ };
315
728
  }
316
- /** Whether the trap is currently active. */
317
729
  get active() {
318
730
  return this.#activeState;
319
731
  }
320
- /**
321
- * Applies the trap: records the current focus, optionally locks background scroll
322
- * and makes background siblings `inert`, listens for `Tab`/`Escape`, and (unless
323
- * `autoFocus` is off) moves focus inside. No-ops if already active.
324
- */
325
732
  activate() {
326
733
  if (this.#activeState) return;
734
+ _FocusTrap.#sweep();
327
735
  this.#activeState = true;
328
- const active = document.activeElement;
329
- this.#previouslyFocused = active instanceof HTMLElement && active !== document.body ? active : null;
330
- if (this.#flag(this.#options.lockScroll, true)) {
331
- this.#previousBodyOverflow = document.body.style.overflow;
332
- document.body.style.overflow = "hidden";
333
- this.#scrollLocked = true;
334
- }
335
- if (this.#flag(this.#options.isolate, true)) this.#isolateBackground();
336
- document.addEventListener("keydown", this.#onKeydown);
337
- this.#beforeCache.activate();
736
+ this.#openers = _FocusTrap.#focusPath();
737
+ this.#entry.openedFrom = _FocusTrap.#trapHolding(this.#openers[0] ?? null);
738
+ if (this.#flag(this.#options.lockScroll, true)) this.#lockScroll();
739
+ this.#entry.isolated = this.#flag(this.#options.isolate, true);
740
+ this.#joinStack();
741
+ _FocusTrap.#reconcileBackground(document);
338
742
  const onEscape = this.#options.onEscape;
339
743
  if (onEscape) this.#escapeLayer.activate(document, { onDismiss: () => onEscape() });
340
744
  if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
341
745
  }
342
- /**
343
- * Reverts every side effect applied by {@link activate}. No-ops if inactive, so
344
- * a controller can call it defensively from both `close()` and `disconnect()`.
345
- *
346
- * @param restoreFocus - Move focus back to the opener (default `true`). Pass
347
- * `false` on teardown (`disconnect`), where yanking focus is undesirable.
348
- */
349
746
  deactivate({ restoreFocus = true } = {}) {
350
- if (!this.#activeState) return;
747
+ if (!this.#activeState) {
748
+ this.#returnTabindexes();
749
+ return;
750
+ }
351
751
  this.#activeState = false;
352
752
  this.#escapeLayer.deactivate();
353
- document.removeEventListener("keydown", this.#onKeydown);
354
- this.#beforeCache.deactivate();
355
- if (this.#scrollLocked) {
356
- document.body.style.overflow = this.#previousBodyOverflow;
357
- this.#scrollLocked = false;
358
- }
359
- this.#releaseBackground();
360
- if (restoreFocus) {
361
- const target = this.#previouslyFocused ?? this.#options.fallbackFocus?.() ?? null;
362
- target?.focus();
363
- }
753
+ const above = this.#leaveStack();
754
+ this.#unlockScroll();
755
+ _FocusTrap.#reconcileBackground(document);
756
+ const wayBack = this.#handOver(above);
757
+ if (restoreFocus) this.#restore(wayBack, above);
758
+ this.#returnTabindexes();
759
+ }
760
+ connect() {
761
+ this.#gate.cancel();
762
+ _FocusTrap.#sweep();
763
+ new TabindexLoan("-1", FOCUS_TRAP_OWNER).reclaimWithin(document);
764
+ }
765
+ disconnect(host) {
766
+ if (this.#activeState) this.deactivate({ restoreFocus: false });
767
+ this.#gate.disconnected(host, () => this.#returnTabindexes());
768
+ }
769
+ refreshIsolation() {
770
+ if (!this.#activeState) return;
771
+ const isolate = this.#flag(this.#options.isolate, true);
772
+ if (isolate === this.#entry.isolated) return;
773
+ this.#entry.isolated = isolate;
774
+ const stranded = _FocusTrap.#reconcileBackground(document);
775
+ const active = deepActiveElement(document);
776
+ if (stranded.some((element) => flatTreeContains(element, active))) this.#focusInitial();
777
+ }
778
+ refreshContainer() {
779
+ if (!this.#activeState) return;
780
+ _FocusTrap.#reconcileBackground(document);
781
+ this.#returnTabindexes();
782
+ if (this.#ownsTab()) this.#refocus();
364
783
  }
365
- /** Resolves a boolean-or-getter option, defaulting when it was not provided. */
366
784
  #flag(option, fallback) {
367
785
  if (option === void 0) return fallback;
368
786
  return typeof option === "function" ? option() : option;
369
787
  }
370
- /**
371
- * Reverts the side effects just before Turbo caches the page snapshot, so an
372
- * overlay left open does not bake the scroll lock into `body[style]` — a
373
- * restored page would feed that locked value back into {@link activate} as the
374
- * baseline, and closing would then never unlock the page. Markup state stays
375
- * untouched (restore-open designs reopen against a clean baseline), and focus
376
- * is left alone mid-navigation. The listener lives only while active.
377
- */
378
- #beforeCache = new BeforeCacheReset(() => this.deactivate({ restoreFocus: false }));
379
- /**
380
- * Handles `Tab` (focus trap) while active. `Escape` dismissal is owned by the
381
- * shared {@link EscapeLayer} resolver, so Tab trapping stays independent of
382
- * which layer currently owns Escape.
383
- */
384
- #onKeydown = (event) => {
385
- if (event.key === "Tab") this.#trapTab(event);
386
- };
387
- /** Keeps `Tab` focus cycling within the container's focusable elements. */
388
- #trapTab(event) {
389
- const focusable = this.#focusableElements();
390
- if (focusable.length === 0) {
391
- event.preventDefault();
392
- return;
788
+ #joinStack() {
789
+ const registry = _FocusTrap.#registry(document);
790
+ if (registry.stack.length === 0) {
791
+ document.addEventListener("keydown", registry.onKeydown);
792
+ document.addEventListener("turbo:morph", registry.onMorph);
793
+ document.addEventListener("turbo:morph-element", registry.onMorph);
393
794
  }
394
- const first = focusable[0];
395
- const last = focusable[focusable.length - 1];
396
- const active = document.activeElement;
397
- if (!(active instanceof Node) || !this.#getContainer().contains(active)) {
398
- event.preventDefault();
399
- first?.focus();
400
- return;
795
+ registry.stack.push(this.#entry);
796
+ }
797
+ #leaveStack() {
798
+ const registry = _FocusTrap.#registry(document);
799
+ const index = registry.stack.indexOf(this.#entry);
800
+ const above = registry.stack.slice(index + 1);
801
+ registry.stack.splice(index, 1);
802
+ if (registry.stack.length === 0) {
803
+ document.removeEventListener("keydown", registry.onKeydown);
804
+ document.removeEventListener("turbo:morph", registry.onMorph);
805
+ document.removeEventListener("turbo:morph-element", registry.onMorph);
401
806
  }
402
- if (event.shiftKey && active === first) {
403
- event.preventDefault();
404
- last?.focus();
405
- } else if (!event.shiftKey && active === last) {
406
- event.preventDefault();
407
- first?.focus();
408
- }
409
- }
410
- /**
411
- * Marks every element outside the container's subtree as `inert` so background
412
- * content cannot be focused or reached by assistive technology, honoring the
413
- * `aria-modal="true"` contract. An element that was *already* `inert` is left
414
- * untracked so `#releaseBackground` does not wrongly clear it.
415
- *
416
- * The walk climbs from the container to `body` and inerts each ancestor's other
417
- * children. Scanning only `body`'s children would skip the branch the container
418
- * sits in — everything beside it inside that branch is background too, and a
419
- * nested container is the ordinary case.
420
- */
421
- #isolateBackground() {
422
- const container = this.#getContainer();
423
- this.#inertedSiblings = [];
424
- for (let node = container; node !== document.body; ) {
425
- const parent = node.parentElement;
426
- if (!parent) break;
427
- for (const sibling of Array.from(parent.children)) {
428
- if (!(sibling instanceof HTMLElement)) continue;
429
- if (sibling === node || sibling.inert) continue;
430
- sibling.inert = true;
431
- this.#inertedSiblings.push(sibling);
807
+ return above;
808
+ }
809
+ static #focusPath() {
810
+ const path = [];
811
+ for (let active = document.activeElement; active; active = active.shadowRoot?.activeElement ?? null) {
812
+ if (active instanceof HTMLElement && active !== document.body) path.unshift(active);
813
+ }
814
+ return path;
815
+ }
816
+ static #trapHolding(element) {
817
+ for (const trap of [..._FocusTrap.#registry(document).stack].reverse()) {
818
+ const container = trap.container();
819
+ if (container && flatTreeContains(container, element)) return trap;
820
+ }
821
+ return null;
822
+ }
823
+ #handOver(above) {
824
+ const entry = this.#entry;
825
+ const wayBack = [
826
+ ...this.#openers,
827
+ this.#options.fallbackFocus?.() ?? null,
828
+ ...entry.handedBack
829
+ ].filter((element) => element !== null);
830
+ for (const trap of above) {
831
+ if (trap.openedFrom !== entry) continue;
832
+ trap.handedBack = [...trap.handedBack, ...wayBack];
833
+ trap.openedFrom = entry.openedFrom;
834
+ }
835
+ this.#openers = [];
836
+ entry.openedFrom = null;
837
+ entry.handedBack = [];
838
+ return wayBack;
839
+ }
840
+ #restore(wayBack, above) {
841
+ const active = deepActiveElement(document);
842
+ const released = this.#readContainer();
843
+ const holds = (trap) => {
844
+ const container = trap.container();
845
+ if (!container || !_FocusTrap.#takesTab(trap) || !flatTreeContains(container, active)) {
846
+ return false;
847
+ }
848
+ return !released || !flatTreeContains(released, active) || flatTreeContains(released, container);
849
+ };
850
+ if (above.some(holds)) return;
851
+ for (const element of wayBack) {
852
+ if (this.#activeState) return;
853
+ if (!element.isConnected || !canTakeFocus(element) || !isRenderedForFocus(element)) continue;
854
+ const before = deepActiveElement(document);
855
+ element.focus();
856
+ const after = deepActiveElement(document);
857
+ if (after === element || after !== before) return;
858
+ }
859
+ _FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack)?.refocus();
860
+ }
861
+ #refocus() {
862
+ if (!this.#flag(this.#options.autoFocus, true)) return;
863
+ if (flatTreeContains(this.#getContainer(), deepActiveElement(document))) return;
864
+ this.#focusInitial();
865
+ }
866
+ #lendTabindex(container) {
867
+ const loan = this.#tabindexLoans.get(container) ?? new TabindexLoan("-1", FOCUS_TRAP_OWNER);
868
+ loan.lend(container);
869
+ this.#tabindexLoans.set(container, loan);
870
+ }
871
+ #returnTabindexes() {
872
+ const focused = deepActiveElement(document);
873
+ for (const [element, loan] of [...this.#tabindexLoans]) {
874
+ if (element === focused) continue;
875
+ loan.returnAll();
876
+ this.#tabindexLoans.delete(element);
877
+ }
878
+ }
879
+ #lockScroll() {
880
+ const registry = _FocusTrap.#registry(document);
881
+ const body = document.body;
882
+ if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
883
+ registry.scrollLocks.add(this.#entry);
884
+ this.#entry.lockedBody = body;
885
+ body.style.overflow = "hidden";
886
+ }
887
+ #unlockScroll() {
888
+ const registry = _FocusTrap.#registry(document);
889
+ const body = this.#entry.lockedBody;
890
+ this.#entry.lockedBody = null;
891
+ if (!registry.scrollLocks.delete(this.#entry) || !body) return;
892
+ if (!_FocusTrap.#isLocked(registry, body)) _FocusTrap.#unlock(body);
893
+ }
894
+ static #isLocked(registry, body) {
895
+ return [...registry.scrollLocks].some((trap) => trap.lockedBody === body);
896
+ }
897
+ static #unlock(body) {
898
+ const saved = body.getAttribute(OVERFLOW_MARK);
899
+ if (saved === null) return;
900
+ body.removeAttribute(OVERFLOW_MARK);
901
+ body.style.overflow = saved;
902
+ }
903
+ static #sweep() {
904
+ const registry = _FocusTrap.#registry(document);
905
+ for (const element of queryOpenDescendants(document, `[${INERT_MARK}]`)) {
906
+ if (registry.inerted.has(element)) continue;
907
+ element.removeAttribute(INERT_MARK);
908
+ element.inert = false;
909
+ }
910
+ if (!_FocusTrap.#isLocked(registry, document.body)) _FocusTrap.#unlock(document.body);
911
+ }
912
+ static #registry(ownerDocument) {
913
+ const existing = _FocusTrap.#registries.get(ownerDocument);
914
+ if (existing) return existing;
915
+ const morphs = new MicrotaskCoalescer(() => _FocusTrap.#reapply(registry));
916
+ morphs.activate();
917
+ const registry = {
918
+ stack: [],
919
+ onKeydown: (event) => _FocusTrap.#resolveTab(registry.stack, event),
920
+ onMorph: () => morphs.schedule(),
921
+ inerted: /* @__PURE__ */ new Set(),
922
+ scrollLocks: /* @__PURE__ */ new Set()
923
+ };
924
+ _FocusTrap.#registries.set(ownerDocument, registry);
925
+ return registry;
926
+ }
927
+ static #reapply(registry) {
928
+ _FocusTrap.#reconcileBackground(document);
929
+ const body = document.body;
930
+ if (!_FocusTrap.#isLocked(registry, body)) return;
931
+ if (!body.hasAttribute(OVERFLOW_MARK)) body.setAttribute(OVERFLOW_MARK, body.style.overflow);
932
+ body.style.overflow = "hidden";
933
+ }
934
+ static #reconcileBackground(ownerDocument) {
935
+ const registry = _FocusTrap.#registry(ownerDocument);
936
+ for (const element of registry.inerted) {
937
+ if (!element.hasAttribute(INERT_MARK)) registry.inerted.delete(element);
938
+ }
939
+ const background = _FocusTrap.#backgroundOf(registry);
940
+ for (const element of registry.inerted) {
941
+ if (background.has(element)) continue;
942
+ element.removeAttribute(INERT_MARK);
943
+ element.inert = false;
944
+ registry.inerted.delete(element);
945
+ }
946
+ const added = [];
947
+ for (const element of background) {
948
+ if (registry.inerted.has(element)) continue;
949
+ element.setAttribute(INERT_MARK, "");
950
+ element.inert = true;
951
+ registry.inerted.add(element);
952
+ added.push(element);
953
+ }
954
+ return added;
955
+ }
956
+ static #backgroundOf(registry) {
957
+ const containers = registry.stack.map((trap) => trap.container());
958
+ let top = -1;
959
+ registry.stack.forEach((trap, index) => {
960
+ if (trap.isolated && containers[index]) top = index;
961
+ });
962
+ const background = /* @__PURE__ */ new Set();
963
+ if (top < 0) return background;
964
+ const kept = /* @__PURE__ */ new Set();
965
+ const keep = (path) => {
966
+ for (const node of path) {
967
+ kept.add(node);
968
+ background.delete(node);
432
969
  }
433
- node = parent;
970
+ for (const node of path) {
971
+ const parent = flatTreeParent(node);
972
+ for (const sibling of parent ? flatTreeChildren(parent) : []) {
973
+ if (!(sibling instanceof HTMLElement) || kept.has(sibling)) continue;
974
+ if (sibling.inert && !registry.inerted.has(sibling)) continue;
975
+ background.add(sibling);
976
+ }
977
+ }
978
+ };
979
+ containers.slice(top).forEach((container, index) => {
980
+ const path = [];
981
+ for (let node = container; node && node !== document.body && !kept.has(node); ) {
982
+ path.push(node);
983
+ node = flatTreeParent(node);
984
+ }
985
+ if (index === 0 || path.some((node) => background.has(node))) keep(path);
986
+ });
987
+ return background;
988
+ }
989
+ #readContainer() {
990
+ try {
991
+ return this.#getContainer();
992
+ } catch {
993
+ return null;
434
994
  }
435
995
  }
436
- /** Reverts the `inert` flags applied by `#isolateBackground`. */
437
- #releaseBackground() {
438
- for (const sibling of this.#inertedSiblings) {
439
- sibling.inert = false;
996
+ static #resolveTab(stack, event) {
997
+ if (event.key !== "Tab" || event.defaultPrevented) return;
998
+ if (event.isComposing || event.keyCode === 229) return;
999
+ if (event.ctrlKey || event.metaKey) return;
1000
+ const owner = _FocusTrap.#tabOwner(stack);
1001
+ if (!owner) return;
1002
+ event.preventDefault();
1003
+ owner.moveFocus(event.shiftKey);
1004
+ }
1005
+ static #tabOwner(stack) {
1006
+ for (const trap of [...stack].reverse()) {
1007
+ if (_FocusTrap.#takesTab(trap)) return trap;
1008
+ }
1009
+ return null;
1010
+ }
1011
+ static #takesTab(trap) {
1012
+ const container = trap.container();
1013
+ if (!container) return false;
1014
+ return container.isConnected && !closestInFlatTree(container, "[hidden]") && isRenderedForFocus(container);
1015
+ }
1016
+ #ownsTab() {
1017
+ return _FocusTrap.#tabOwner(_FocusTrap.#registry(document).stack) === this.#entry;
1018
+ }
1019
+ #moveFocus(backward) {
1020
+ const from = deepActiveElement(document);
1021
+ for (const stop of sequentialTabStops(this.#getContainer(), from, backward)) {
1022
+ if (!this.#ownsTab()) return;
1023
+ const before = deepActiveElement(document);
1024
+ stop.focus();
1025
+ const after = deepActiveElement(document);
1026
+ if (after === before) continue;
1027
+ if (after === stop) _FocusTrap.#selectOnEntry(stop);
1028
+ return;
1029
+ }
1030
+ }
1031
+ static #selectOnEntry(stop) {
1032
+ if (stop instanceof HTMLInputElement && _FocusTrap.#TEXT_ENTRY_TYPES.has(stop.type)) {
1033
+ stop.select();
440
1034
  }
441
- this.#inertedSiblings = [];
442
1035
  }
443
- /** Moves focus to the initial target, the first focusable, or the container. */
444
1036
  #focusInitial() {
445
1037
  const preferred = this.#options.initialFocus?.();
446
1038
  if (preferred) {
447
1039
  preferred.focus();
448
1040
  return;
449
1041
  }
450
- const focusable = this.#focusableElements();
451
- if (focusable[0]) {
452
- focusable[0].focus();
1042
+ const container = this.#getContainer();
1043
+ const [first] = sequentialTabStops(container);
1044
+ if (first) {
1045
+ first.focus();
453
1046
  return;
454
1047
  }
455
- const container = this.#getContainer();
456
- container.tabIndex = -1;
1048
+ this.#lendTabindex(container);
457
1049
  container.focus();
458
1050
  }
459
- /** Collects the container's currently focusable descendants in DOM order. */
460
- #focusableElements() {
461
- return tabStopsWithin(this.#getContainer());
1051
+ };
1052
+
1053
+ var ListenerSet = class {
1054
+ #abort = new AbortController();
1055
+ add(target, type, handler, options) {
1056
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
1057
+ }
1058
+ dispose() {
1059
+ this.#abort.abort();
1060
+ this.#abort = new AbortController();
462
1061
  }
463
1062
  };
464
1063
 
465
- // src/utils/microtask_coalescer.ts
466
- var MicrotaskCoalescer = class {
467
- #run;
468
- #queued = false;
469
- #active = false;
470
- #generation = 0;
471
- /** @param run - the single reconciliation pass, invoked at most once per batch. */
1064
+ var MorphRenderWatcher = class {
1065
+ #listeners = new ListenerSet();
1066
+ #pass;
1067
+ #element = null;
1068
+ #onMorph = () => this.#pass.schedule();
472
1069
  constructor(run) {
473
- this.#run = run;
1070
+ this.#pass = new MicrotaskCoalescer(run);
474
1071
  }
475
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
476
- activate() {
477
- this.#active = true;
1072
+ observe(element) {
1073
+ if (this.#element === element) return;
1074
+ this.disconnect();
1075
+ this.#element = element;
1076
+ this.#pass.activate();
1077
+ this.#listeners.add(element, "turbo:morph-element", this.#onMorph);
478
1078
  }
479
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
480
- cancel() {
481
- this.#active = false;
482
- this.#queued = false;
483
- this.#generation += 1;
484
- }
485
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
486
1079
  schedule() {
487
- if (!this.#active || this.#queued) return;
488
- this.#queued = true;
489
- const generation = this.#generation;
490
- queueMicrotask(() => {
491
- if (generation !== this.#generation || !this.#queued || !this.#active) return;
492
- this.#queued = false;
493
- this.#run();
494
- });
1080
+ this.#pass.schedule();
1081
+ }
1082
+ disconnect() {
1083
+ this.#listeners.dispose();
1084
+ this.#pass.cancel();
1085
+ this.#element = null;
1086
+ }
1087
+ };
1088
+
1089
+ var LivedMark = class {
1090
+ #name;
1091
+ #lived = false;
1092
+ #element = null;
1093
+ #morph = new MorphRenderWatcher(() => this.#write());
1094
+ constructor(identifier) {
1095
+ this.#name = `data-${identifier}-lived`;
1096
+ }
1097
+ connect(element) {
1098
+ const connection = this.#lived ? "reconnect" : element.hasAttribute(this.#name) ? "restored" : "fresh";
1099
+ this.#lived = true;
1100
+ this.#element = element;
1101
+ this.#write();
1102
+ this.#morph.observe(element);
1103
+ return connection;
1104
+ }
1105
+ disconnect() {
1106
+ this.#morph.disconnect();
1107
+ }
1108
+ #write() {
1109
+ this.#element?.setAttribute(this.#name, "");
495
1110
  }
496
1111
  };
497
1112
 
498
- // src/controllers/command_palette_controller.ts
1113
+ function targetSelector(identifier, name) {
1114
+ return `[data-${identifier}-target~="${name}"]`;
1115
+ }
1116
+
499
1117
  var CommandPaletteController = class _CommandPaletteController extends Controller {
500
1118
  static #ORIGINAL_ARIA_DISABLED = "data-command-palette-original-aria-disabled";
501
1119
  static #ABSENT_ARIA_DISABLED = "absent";
@@ -510,85 +1128,103 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
510
1128
  "filter",
511
1129
  "onKeydown",
512
1130
  "open",
513
- "selectByClick",
1131
+ "select",
514
1132
  "toggle"
515
1133
  ];
516
1134
  static events = ["select"];
517
- /** Stable ID of the active option; the live target is resolved before every use. */
518
1135
  #activeId = null;
519
- /** Visible/selectable target ID order captured for removal fallback. */
520
1136
  #activeOrder = [];
521
1137
  #connected = false;
522
- /** Collapses one mutation batch of target callbacks into a single pass. */
523
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileActive());
524
- /** Owns IME lifecycle state; confirmed text re-filters an open palette once. */
1138
+ #lived = new LivedMark(this.identifier);
1139
+ #dialog = null;
1140
+ #hidden = new AttributeLease("hidden", this.identifier);
1141
+ #expanded = new AttributeLease("aria-expanded", this.identifier);
1142
+ #activeDescendant = new AttributeLease(
1143
+ "aria-activedescendant",
1144
+ this.identifier
1145
+ );
1146
+ #reconcile = new MicrotaskCoalescer(() => {
1147
+ if (this.hasInputTarget) this.#expanded.write(this.inputTarget, String(this.#isOpen));
1148
+ this.#reconcileActive();
1149
+ });
1150
+ #morphRender = new MorphRenderWatcher(() => this.#repair());
525
1151
  #composition = new CompositionTracker({
526
1152
  onEnd: () => {
527
1153
  if (this.#isOpen) this.filter();
528
1154
  }
529
1155
  });
530
- /**
531
- * Owns the modal side effects (focus trap, scroll lock, background `inert`, focus
532
- * restore). Escape closes; focus on open goes to the input, and is restored to
533
- * whatever opened the palette on close.
534
- */
535
1156
  #trap = new FocusTrap(() => this.dialogTarget, {
536
1157
  onEscape: () => this.close(),
537
- initialFocus: () => this.hasInputTarget ? this.inputTarget : null
1158
+ initialFocus: () => {
1159
+ const dialog = this.dialogTarget;
1160
+ return this.inputTargets.find((input) => dialog.contains(input)) ?? null;
1161
+ }
538
1162
  });
539
- /**
540
- * Initializes the global hotkey handler and establishes the initial open state.
541
- *
542
- * The DOM is the source of truth on reconnect (Turbo cache restore / morph): if
543
- * the restored snapshot already shows the dialog open, honor that rather than
544
- * re-deriving from the declarative `open` Value (which would slam a user-opened
545
- * palette shut). The `open` Value only seeds the initial state of a genuinely
546
- * fresh render. We normalize to a clean closed baseline first so
547
- * {@link CommandPaletteController.open | open} runs its full setup — the
548
- * `FocusTrap` is a fresh instance after a reconnect and must be re-activated.
549
- */
550
1163
  connect() {
1164
+ const connection = this.#lived.connect(this.element);
1165
+ this.#trap.connect();
551
1166
  document.addEventListener("keydown", this.#onGlobalKeydown);
552
1167
  if (this.hasInputTarget) this.#composition.observe(this.inputTarget);
553
1168
  this.#syncOptionSemantics();
554
- const shouldOpen = this.#isOpen || this.openValue;
555
- this.#resetToClosedState();
556
- if (shouldOpen) this.open();
1169
+ if (connection === "reconnect" && this.#isOpen) {
1170
+ this.#trap.activate();
1171
+ } else {
1172
+ const shouldOpen = connection !== "restored" && (this.#isOpen || this.openValue);
1173
+ this.#resetToClosedState();
1174
+ if (shouldOpen) this.open();
1175
+ }
1176
+ this.#dialog = this.hasDialogTarget ? this.dialogTarget : null;
557
1177
  this.#connected = true;
558
1178
  this.#reconcile.activate();
1179
+ this.#morphRender.observe(this.element);
559
1180
  }
560
- /** Tears down the global hotkey listener and reverts the modal side effects. */
561
1181
  disconnect() {
562
1182
  this.#connected = false;
1183
+ this.#lived.disconnect();
1184
+ this.#morphRender.disconnect();
563
1185
  this.#reconcile.cancel();
564
1186
  document.removeEventListener("keydown", this.#onGlobalKeydown);
565
1187
  this.#composition.disconnect();
566
- this.#trap.deactivate({ restoreFocus: false });
567
- this.#resetToClosedState();
1188
+ this.#trap.disconnect(this);
568
1189
  }
569
- /** Initializes semantics for an option added before or after the controller connects. */
570
1190
  optionTargetConnected(option) {
571
1191
  this.#syncOptionSemanticsFor(option);
572
1192
  option.setAttribute("aria-selected", "false");
573
1193
  option.removeAttribute("data-active");
574
1194
  if (this.#connected) this.#queueOptionReconciliation();
575
1195
  }
576
- /** Clears active residue on the old node and reconciles the surviving targets. */
577
1196
  optionTargetDisconnected(option) {
578
1197
  option.setAttribute("aria-selected", "false");
579
1198
  option.removeAttribute("data-active");
580
1199
  if (this.#connected) this.#queueOptionReconciliation();
581
1200
  }
582
- /** Tracks an input added initially or after connect without extra consumer actions. */
583
1201
  inputTargetConnected(input) {
584
1202
  this.#composition.observe(input);
585
1203
  if (this.#connected) this.#queueOptionReconciliation();
586
1204
  }
587
- /** Removes controller-owned listeners when the input target is replaced or removed. */
588
1205
  inputTargetDisconnected(input) {
589
1206
  this.#composition.unobserve(input);
1207
+ if (!this.inputTargets.includes(input)) {
1208
+ this.#expanded.return(input);
1209
+ this.#activeDescendant.return(input);
1210
+ }
1211
+ this.#queueOptionReconciliation();
1212
+ }
1213
+ dialogTargetConnected() {
1214
+ if (this.#connected) this.#adoptDialog();
1215
+ }
1216
+ dialogTargetDisconnected(dialog) {
1217
+ if (this.dialogTargets.includes(dialog)) return;
1218
+ this.#hidden.return(dialog);
1219
+ if (this.#connected) this.#adoptDialog();
1220
+ }
1221
+ emptyTargetConnected() {
1222
+ this.#queueOptionReconciliation();
1223
+ }
1224
+ emptyTargetDisconnected(empty) {
1225
+ if (!this.emptyTargets.includes(empty)) this.#hidden.return(empty);
1226
+ this.#queueOptionReconciliation();
590
1227
  }
591
- /** Toggles the open state of the command palette. */
592
1228
  toggle() {
593
1229
  if (this.#isOpen) {
594
1230
  this.close();
@@ -596,25 +1232,43 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
596
1232
  this.open();
597
1233
  }
598
1234
  }
599
- /** Opens the palette, traps focus, and shifts focus to the input. */
600
1235
  open() {
601
1236
  if (!this.hasDialogTarget || this.#isOpen) return;
602
- this.dialogTarget.hidden = false;
1237
+ this.#hidden.write(this.dialogTarget, null);
603
1238
  this.openValue = true;
604
- if (this.hasInputTarget) this.inputTarget.setAttribute("aria-expanded", "true");
1239
+ if (this.hasInputTarget) this.#expanded.write(this.inputTarget, "true");
605
1240
  this.#resetFilter();
606
1241
  this.#trap.activate();
607
1242
  }
608
- /** Closes the palette and restores focus back to the opener. */
609
1243
  close() {
1244
+ this.#close(true);
1245
+ }
1246
+ #adoptDialog() {
1247
+ const dialog = this.hasDialogTarget ? this.dialogTarget : null;
1248
+ if (dialog === this.#dialog) return;
1249
+ this.#dialog = dialog;
1250
+ if (!this.#trap.active) {
1251
+ if (dialog) this.#hidden.write(dialog, "");
1252
+ return;
1253
+ }
1254
+ if (!dialog) {
1255
+ this.#resetToClosedState();
1256
+ this.#trap.deactivate();
1257
+ return;
1258
+ }
1259
+ this.#hidden.write(dialog, null);
1260
+ this.#trap.refreshContainer();
1261
+ }
1262
+ #close(restoreFocus) {
610
1263
  if (!this.hasDialogTarget || !this.#isOpen) return;
611
1264
  this.#resetToClosedState();
612
- this.#trap.deactivate();
1265
+ this.#trap.deactivate({ restoreFocus });
613
1266
  }
614
- /** Filters option elements in-memory matching the input value. Bound to input target. */
615
1267
  filter(event) {
616
1268
  if (!this.hasInputTarget || this.#composition.isComposing(event)) return;
617
- this.#syncOptionSemantics();
1269
+ this.#setActiveIndex(this.#applyQuery() ? 0 : -1);
1270
+ }
1271
+ #applyQuery() {
618
1272
  const query = this.inputTarget.value.trim().toLowerCase();
619
1273
  let hasSelectableMatch = false;
620
1274
  for (const option of this.optionTargets) {
@@ -628,31 +1282,35 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
628
1282
  }
629
1283
  }
630
1284
  if (this.hasEmptyTarget) {
631
- this.emptyTarget.hidden = hasSelectableMatch;
1285
+ this.#hidden.write(this.emptyTarget, hasSelectableMatch ? "" : null);
632
1286
  }
633
- this.#setActiveIndex(hasSelectableMatch ? 0 : -1);
1287
+ return hasSelectableMatch;
1288
+ }
1289
+ #repair() {
1290
+ if (!this.hasDialogTarget) return;
1291
+ const open = this.#trap.active;
1292
+ this.#hidden.write(this.dialogTarget, open ? null : "");
1293
+ if (open && this.hasInputTarget) this.#applyQuery();
1294
+ this.#reconcile.schedule();
634
1295
  }
635
- /** Selects the clicked option. Bound to option targets. */
636
- selectByClick(event) {
637
- const target = event.currentTarget;
638
- if (!target) return;
639
- const option = target.closest("[role='option']");
1296
+ select(source) {
1297
+ const { event, host, origin, reason } = actionSource(source);
1298
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
1299
+ return;
1300
+ const option = host?.closest(targetSelector(this.identifier, "option"));
640
1301
  if (!option || !this.optionTargets.includes(option)) return;
641
1302
  this.#syncOptionSemanticsFor(option);
642
1303
  if (!option.hasAttribute("hidden") && !this.#isDisabled(option)) {
643
- this.#confirmSelection(option);
1304
+ this.#confirmSelection(
1305
+ option,
1306
+ reason,
1307
+ event !== null || this.element.contains(document.activeElement)
1308
+ );
644
1309
  }
645
1310
  }
646
- /** Closes when the backdrop (the dialog target itself) is clicked, ignoring inner clicks. */
647
1311
  closeOnBackdrop(event) {
648
1312
  if (event.target === this.dialogTarget) this.close();
649
1313
  }
650
- /**
651
- * Combobox navigation keys (arrows / Home / End / Enter), bound to the input.
652
- * `Tab` (focus trap) and `Escape` (close) are owned by the shared `FocusTrap` at
653
- * the document level, so they work no matter which element inside the dialog has
654
- * focus — not only the input.
655
- */
656
1314
  onKeydown(event) {
657
1315
  if (event.defaultPrevented) return;
658
1316
  if (isReservedArrowChord(event)) return;
@@ -708,14 +1366,9 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
708
1366
  this.#activeId = activeOption?.id || null;
709
1367
  this.#activeOrder = activeOption ? visible.map((option) => option.id).filter(Boolean) : [];
710
1368
  if (this.hasInputTarget) {
711
- if (activeOption?.id) {
712
- this.inputTarget.setAttribute("aria-activedescendant", activeOption.id);
713
- } else {
714
- this.inputTarget.removeAttribute("aria-activedescendant");
715
- }
1369
+ this.#activeDescendant.write(this.inputTarget, activeOption?.id || null);
716
1370
  }
717
1371
  }
718
- /** Re-resolves active identity and component-specific fallback against live targets. */
719
1372
  #reconcileActive() {
720
1373
  if (!this.#isOpen) {
721
1374
  this.#setActiveIndex(-1);
@@ -742,7 +1395,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
742
1395
  this.#setActiveIndex(activeId ? this.#findFallbackIndex(visible, activeId) : -1);
743
1396
  this.#reflectEmptyState();
744
1397
  }
745
- /** Resolves stable ID first, then active markers for the existing ID-less case. */
746
1398
  #findActiveIndex(options) {
747
1399
  const activeId = (this.hasInputTarget ? this.inputTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
748
1400
  if (activeId) return options.findIndex((option) => option.id === activeId);
@@ -750,7 +1402,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
750
1402
  (option) => option.hasAttribute("data-active") || option.getAttribute("aria-selected") === "true"
751
1403
  );
752
1404
  }
753
- /** Chooses a surviving former successor, then predecessor, from selectable targets only. */
754
1405
  #findFallbackIndex(options, activeId) {
755
1406
  const oldIndex = this.#activeOrder.indexOf(activeId);
756
1407
  if (oldIndex < 0) return -1;
@@ -765,28 +1416,21 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
765
1416
  }
766
1417
  return -1;
767
1418
  }
768
- /** Reflects whether any visible, navigable command remains. */
769
1419
  #reflectEmptyState() {
770
- if (this.hasEmptyTarget) this.emptyTarget.hidden = this.#visibleOptions.length > 0;
1420
+ if (this.hasEmptyTarget) {
1421
+ this.#hidden.write(this.emptyTarget, this.#visibleOptions.length > 0 ? "" : null);
1422
+ }
771
1423
  }
772
- /** Coalesces all target callbacks from one MutationObserver batch. */
773
1424
  #queueOptionReconciliation() {
774
1425
  this.#reconcile.schedule();
775
1426
  }
776
- /**
777
- * Runs `option`: dispatches `select` and closes.
778
- *
779
- * **Activation is refused here, not at the call sites.** Every path that runs a
780
- * command funnels through this method, so one check covers keyboard, pointer,
781
- * and anything added later. Per-call-site guards would leave `aria-disabled`
782
- * options — which stay within the keyboard's reach — runnable from whichever
783
- * path forgets to consult the predicate.
784
- */
785
- #confirmSelection(option) {
1427
+ #confirmSelection(option, reason = "user", restoreFocus = true) {
786
1428
  if (this.#isDisabled(option)) return;
787
1429
  const value = option.dataset.value || option.textContent || "";
788
- this.dispatch("select", { detail: { value, option } });
789
- this.close();
1430
+ this.dispatch("select", { detail: { value, option, reason } });
1431
+ this.#close(
1432
+ restoreFocus && (reason !== "api" || this.element.contains(document.activeElement))
1433
+ );
790
1434
  }
791
1435
  #resetFilter() {
792
1436
  if (this.hasInputTarget) this.inputTarget.value = "";
@@ -802,43 +1446,15 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
802
1446
  (option) => !option.hasAttribute("hidden") && this.#isNavigable(option)
803
1447
  );
804
1448
  }
805
- /**
806
- * Whether virtual focus may land on `option`.
807
- *
808
- * Only `data-disabled` disqualifies. That attribute is this controller's own —
809
- * authors reach for it to mark a row that is not a destination at all, such as
810
- * a group heading — so it can mean "skip entirely" without contradicting ARIA.
811
- *
812
- * **`aria-disabled` does not disqualify.** It marks a command that must stay
813
- * *discoverable*: `aria-activedescendant` names it, so a reader announces it as
814
- * unavailable rather than hiding its existence. Virtual focus does
815
- * not change that — pointing `aria-activedescendant` at a disabled option is
816
- * ordinary ARIA. Keeping the two attributes distinct is what leaves both
817
- * meanings expressible; collapsing them would take "show it, say it is
818
- * unavailable, do not run it" away from consumers.
819
- */
820
1449
  #isNavigable(option) {
821
1450
  return option.dataset.disabled !== "true";
822
1451
  }
823
- /** Whether activating `option` may run. Suppressed for either disabled marker. */
824
1452
  #isDisabled(option) {
825
1453
  return option.dataset.disabled === "true" || option.getAttribute("aria-disabled") === "true";
826
1454
  }
827
- /** Synchronizes every option's controller-owned selection and disabled semantics. */
828
1455
  #syncOptionSemantics() {
829
1456
  for (const option of this.optionTargets) this.#syncOptionSemanticsFor(option);
830
1457
  }
831
- /**
832
- * Reflects `data-disabled` to ARIA without losing a pre-existing authored value,
833
- * and puts `aria-selected` back to its baseline.
834
- *
835
- * Here `aria-selected` marks the *active* option, not a committed choice — the
836
- * palette runs a command and keeps nothing selected — so an authored value is
837
- * meaningless and is overwritten rather than preserved. Every caller either
838
- * establishes the active option straight after (`filter`, `#setActiveIndex`) or
839
- * is handling an option that cannot be the active one yet
840
- * (`optionTargetConnected`).
841
- */
842
1458
  #syncOptionSemanticsFor(option) {
843
1459
  const originalAttribute = _CommandPaletteController.#ORIGINAL_ARIA_DISABLED;
844
1460
  const originalValue = option.getAttribute(originalAttribute);
@@ -868,7 +1484,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
868
1484
  event.preventDefault();
869
1485
  this.toggle();
870
1486
  };
871
- /** Matches the configured `mod+key` or bare-key hotkey without extra modifiers. */
872
1487
  #matchesHotkey(event) {
873
1488
  const hotkey = this.#parseHotkey();
874
1489
  if (!hotkey || event.altKey || event.shiftKey) return false;
@@ -880,7 +1495,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
880
1495
  }
881
1496
  return event.key.toLowerCase() === hotkey.key;
882
1497
  }
883
- /** Parses the intentionally small public hotkey grammar. */
884
1498
  #parseHotkey() {
885
1499
  const parts = this.hotkeyValue.toLowerCase().split("+");
886
1500
  if (parts.length === 1 && parts[0] && parts[0] !== "mod") {
@@ -891,7 +1505,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
891
1505
  }
892
1506
  return null;
893
1507
  }
894
- /** Resets transient open state so reconnect starts from a predictable closed snapshot. */
895
1508
  #resetToClosedState() {
896
1509
  this.#activeId = null;
897
1510
  this.#activeOrder = [];
@@ -901,15 +1514,14 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
901
1514
  option.removeAttribute("data-active");
902
1515
  }
903
1516
  if (this.hasDialogTarget) {
904
- this.dialogTarget.hidden = true;
1517
+ this.#hidden.write(this.dialogTarget, "");
905
1518
  }
906
1519
  if (this.hasInputTarget) {
907
- this.inputTarget.setAttribute("aria-expanded", "false");
908
- this.inputTarget.removeAttribute("aria-activedescendant");
1520
+ this.#expanded.write(this.inputTarget, "false");
1521
+ this.#activeDescendant.write(this.inputTarget, null);
909
1522
  }
910
1523
  }
911
1524
  };
912
1525
 
913
1526
  export { CommandPaletteController };
914
- //# sourceMappingURL=command_palette_controller.js.map
915
1527
  //# sourceMappingURL=command_palette_controller.js.map