stimeo-ui 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +128 -1
  3. package/dist/cable/index.d.ts +79 -23
  4. package/dist/cable/index.js +392 -417
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/controllers/accordion_controller.d.ts +34 -5
  7. package/dist/controllers/accordion_controller.js +228 -28
  8. package/dist/controllers/accordion_controller.js.map +1 -1
  9. package/dist/controllers/alert_dialog_controller.d.ts +20 -9
  10. package/dist/controllers/alert_dialog_controller.js +862 -221
  11. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  12. package/dist/controllers/announcer_controller.d.ts +38 -7
  13. package/dist/controllers/announcer_controller.js +135 -191
  14. package/dist/controllers/announcer_controller.js.map +1 -1
  15. package/dist/controllers/aspect_ratio_controller.js +0 -10
  16. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  17. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  18. package/dist/controllers/auto_submit_controller.js +295 -110
  19. package/dist/controllers/auto_submit_controller.js.map +1 -1
  20. package/dist/controllers/avatar_controller.js +172 -106
  21. package/dist/controllers/avatar_controller.js.map +1 -1
  22. package/dist/controllers/breadcrumb_controller.d.ts +10 -7
  23. package/dist/controllers/breadcrumb_controller.js +0 -129
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +22 -4
  26. package/dist/controllers/bulk_select_controller.js +264 -53
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +109 -28
  29. package/dist/controllers/calendar_controller.js +346 -180
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +99 -27
  32. package/dist/controllers/carousel_controller.js +587 -300
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +18 -2
  35. package/dist/controllers/character_counter_controller.js +271 -143
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +77 -75
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +44 -7
  41. package/dist/controllers/clipboard_controller.js +187 -108
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +41 -5
  44. package/dist/controllers/collapsible_controller.js +374 -108
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +47 -14
  47. package/dist/controllers/color_picker_controller.js +183 -127
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +33 -6
  50. package/dist/controllers/combobox_controller.js +426 -109
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +45 -14
  53. package/dist/controllers/command_palette_controller.js +994 -350
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +212 -125
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +939 -245
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +36 -3
  62. package/dist/controllers/context_menu_controller.js +275 -127
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +75 -2
  68. package/dist/controllers/countdown_controller.js +235 -97
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -15
  71. package/dist/controllers/currency_input_controller.js +241 -118
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +78 -22
  74. package/dist/controllers/data_grid_controller.js +229 -135
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +64 -11
  77. package/dist/controllers/date_range_picker_controller.js +359 -165
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +22 -3
  80. package/dist/controllers/dialog_controller.js +887 -225
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +24 -7
  83. package/dist/controllers/direct_upload_controller.js +80 -142
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +2 -2
  86. package/dist/controllers/dirty_form_controller.js +26 -47
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +175 -20
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +52 -16
  92. package/dist/controllers/drawer_controller.js +963 -336
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +39 -3
  95. package/dist/controllers/dropdown_controller.js +342 -85
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +182 -58
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +67 -53
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +60 -18
  103. package/dist/controllers/file_dropzone_controller.js +426 -229
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.d.ts +10 -4
  106. package/dist/controllers/filter_controller.js +78 -36
  107. package/dist/controllers/filter_controller.js.map +1 -1
  108. package/dist/controllers/flash_controller.d.ts +57 -14
  109. package/dist/controllers/flash_controller.js +328 -211
  110. package/dist/controllers/flash_controller.js.map +1 -1
  111. package/dist/controllers/focus_controller.d.ts +12 -3
  112. package/dist/controllers/focus_controller.js +654 -239
  113. package/dist/controllers/focus_controller.js.map +1 -1
  114. package/dist/controllers/form_field_controller.js +153 -136
  115. package/dist/controllers/form_field_controller.js.map +1 -1
  116. package/dist/controllers/form_validation_controller.js +29 -96
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +41 -9
  119. package/dist/controllers/frame_loading_controller.js +263 -198
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +22 -2
  122. package/dist/controllers/highlight_controller.js +119 -70
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +65 -4
  125. package/dist/controllers/hover_card_controller.js +335 -118
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +58 -4
  128. package/dist/controllers/idle_controller.js +316 -65
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +5 -2
  131. package/dist/controllers/input_mask_controller.js +116 -61
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +147 -103
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +16 -0
  137. package/dist/controllers/lazy_frame_controller.js +9 -56
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +84 -10
  140. package/dist/controllers/listbox_controller.js +413 -153
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +1 -1
  143. package/dist/controllers/local_time_controller.js +54 -64
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +70 -95
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +40 -9
  149. package/dist/controllers/menu_controller.js +254 -167
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +15 -4
  152. package/dist/controllers/menubar_controller.js +206 -331
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +20 -0
  155. package/dist/controllers/meter_controller.js +159 -71
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +50 -7
  158. package/dist/controllers/multi_select_controller.js +797 -388
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +19 -1
  161. package/dist/controllers/navigation_menu_controller.js +156 -231
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +373 -174
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +241 -45
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +62 -10
  170. package/dist/controllers/number_input_controller.js +387 -227
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.js +28 -75
  173. package/dist/controllers/optimistic_controller.js.map +1 -1
  174. package/dist/controllers/otp_controller.d.ts +38 -7
  175. package/dist/controllers/otp_controller.js +344 -196
  176. package/dist/controllers/otp_controller.js.map +1 -1
  177. package/dist/controllers/overflow_indicator_controller.d.ts +13 -12
  178. package/dist/controllers/overflow_indicator_controller.js +167 -93
  179. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  180. package/dist/controllers/overflow_menu_controller.d.ts +66 -9
  181. package/dist/controllers/overflow_menu_controller.js +408 -277
  182. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  183. package/dist/controllers/pagination_controller.d.ts +52 -15
  184. package/dist/controllers/pagination_controller.js +342 -120
  185. package/dist/controllers/pagination_controller.js.map +1 -1
  186. package/dist/controllers/password_reveal_controller.d.ts +60 -9
  187. package/dist/controllers/password_reveal_controller.js +424 -99
  188. package/dist/controllers/password_reveal_controller.js.map +1 -1
  189. package/dist/controllers/password_strength_controller.d.ts +14 -5
  190. package/dist/controllers/password_strength_controller.js +99 -153
  191. package/dist/controllers/password_strength_controller.js.map +1 -1
  192. package/dist/controllers/persist_controller.d.ts +11 -4
  193. package/dist/controllers/persist_controller.js +252 -96
  194. package/dist/controllers/persist_controller.js.map +1 -1
  195. package/dist/controllers/pointer_drag_controller.d.ts +19 -7
  196. package/dist/controllers/pointer_drag_controller.js +263 -164
  197. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  198. package/dist/controllers/popover_controller.d.ts +46 -4
  199. package/dist/controllers/popover_controller.js +267 -94
  200. package/dist/controllers/popover_controller.js.map +1 -1
  201. package/dist/controllers/portal_controller.d.ts +44 -4
  202. package/dist/controllers/portal_controller.js +209 -72
  203. package/dist/controllers/portal_controller.js.map +1 -1
  204. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  205. package/dist/controllers/preview_guard_controller.js +243 -112
  206. package/dist/controllers/preview_guard_controller.js.map +1 -1
  207. package/dist/controllers/progress_controller.d.ts +11 -0
  208. package/dist/controllers/progress_controller.js +132 -59
  209. package/dist/controllers/progress_controller.js.map +1 -1
  210. package/dist/controllers/radio_group_controller.d.ts +19 -8
  211. package/dist/controllers/radio_group_controller.js +138 -133
  212. package/dist/controllers/radio_group_controller.js.map +1 -1
  213. package/dist/controllers/range_slider_controller.d.ts +92 -4
  214. package/dist/controllers/range_slider_controller.js +245 -112
  215. package/dist/controllers/range_slider_controller.js.map +1 -1
  216. package/dist/controllers/rating_controller.d.ts +33 -6
  217. package/dist/controllers/rating_controller.js +429 -194
  218. package/dist/controllers/rating_controller.js.map +1 -1
  219. package/dist/controllers/read_more_controller.d.ts +26 -2
  220. package/dist/controllers/read_more_controller.js +185 -47
  221. package/dist/controllers/read_more_controller.js.map +1 -1
  222. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  223. package/dist/controllers/reading_progress_controller.js +147 -135
  224. package/dist/controllers/reading_progress_controller.js.map +1 -1
  225. package/dist/controllers/relative_time_controller.d.ts +18 -4
  226. package/dist/controllers/relative_time_controller.js +145 -110
  227. package/dist/controllers/relative_time_controller.js.map +1 -1
  228. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  229. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +97 -38
  230. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  231. package/dist/controllers/resizable_controller.d.ts +44 -4
  232. package/dist/controllers/resizable_controller.js +294 -118
  233. package/dist/controllers/resizable_controller.js.map +1 -1
  234. package/dist/controllers/roving_controller.js +39 -84
  235. package/dist/controllers/roving_controller.js.map +1 -1
  236. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  237. package/dist/controllers/scroll_area_controller.js +428 -221
  238. package/dist/controllers/scroll_area_controller.js.map +1 -1
  239. package/dist/controllers/scroll_restore_controller.js +0 -53
  240. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  241. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  242. package/dist/controllers/scroll_visibility_controller.js +395 -146
  243. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  244. package/dist/controllers/scrollspy_controller.d.ts +9 -3
  245. package/dist/controllers/scrollspy_controller.js +181 -236
  246. package/dist/controllers/scrollspy_controller.js.map +1 -1
  247. package/dist/controllers/separator_controller.d.ts +59 -17
  248. package/dist/controllers/separator_controller.js +288 -132
  249. package/dist/controllers/separator_controller.js.map +1 -1
  250. package/dist/controllers/sidebar_controller.d.ts +81 -7
  251. package/dist/controllers/sidebar_controller.js +1071 -375
  252. package/dist/controllers/sidebar_controller.js.map +1 -1
  253. package/dist/controllers/skeleton_controller.d.ts +23 -0
  254. package/dist/controllers/skeleton_controller.js +248 -92
  255. package/dist/controllers/skeleton_controller.js.map +1 -1
  256. package/dist/controllers/slider_controller.d.ts +72 -8
  257. package/dist/controllers/slider_controller.js +211 -96
  258. package/dist/controllers/slider_controller.js.map +1 -1
  259. package/dist/controllers/smart_sticky_header_controller.d.ts +21 -3
  260. package/dist/controllers/smart_sticky_header_controller.js +163 -54
  261. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  262. package/dist/controllers/sortable_controller.js +13 -110
  263. package/dist/controllers/sortable_controller.js.map +1 -1
  264. package/dist/controllers/spinner_controller.d.ts +39 -4
  265. package/dist/controllers/spinner_controller.js +277 -161
  266. package/dist/controllers/spinner_controller.js.map +1 -1
  267. package/dist/controllers/step_indicator_controller.d.ts +24 -17
  268. package/dist/controllers/step_indicator_controller.js +107 -53
  269. package/dist/controllers/step_indicator_controller.js.map +1 -1
  270. package/dist/controllers/stepper_controller.d.ts +43 -9
  271. package/dist/controllers/stepper_controller.js +171 -48
  272. package/dist/controllers/stepper_controller.js.map +1 -1
  273. package/dist/controllers/stick_to_bottom_controller.d.ts +25 -0
  274. package/dist/controllers/stick_to_bottom_controller.js +310 -116
  275. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  276. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  277. package/dist/controllers/sticky_observer_controller.js +214 -49
  278. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  279. package/dist/controllers/submit_once_controller.d.ts +22 -6
  280. package/dist/controllers/submit_once_controller.js +294 -192
  281. package/dist/controllers/submit_once_controller.js.map +1 -1
  282. package/dist/controllers/switch_controller.d.ts +40 -7
  283. package/dist/controllers/switch_controller.js +107 -43
  284. package/dist/controllers/switch_controller.js.map +1 -1
  285. package/dist/controllers/tabs_controller.d.ts +13 -1
  286. package/dist/controllers/tabs_controller.js +50 -25
  287. package/dist/controllers/tabs_controller.js.map +1 -1
  288. package/dist/controllers/tags_input_controller.d.ts +27 -3
  289. package/dist/controllers/tags_input_controller.js +290 -149
  290. package/dist/controllers/tags_input_controller.js.map +1 -1
  291. package/dist/controllers/textarea_autosize_controller.d.ts +10 -0
  292. package/dist/controllers/textarea_autosize_controller.js +71 -58
  293. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  294. package/dist/controllers/theme_controller.d.ts +37 -8
  295. package/dist/controllers/theme_controller.js +148 -80
  296. package/dist/controllers/theme_controller.js.map +1 -1
  297. package/dist/controllers/time_picker_controller.d.ts +18 -1
  298. package/dist/controllers/time_picker_controller.js +162 -81
  299. package/dist/controllers/time_picker_controller.js.map +1 -1
  300. package/dist/controllers/toast_controller.d.ts +85 -23
  301. package/dist/controllers/toast_controller.js +365 -193
  302. package/dist/controllers/toast_controller.js.map +1 -1
  303. package/dist/controllers/toggle_group_controller.d.ts +60 -9
  304. package/dist/controllers/toggle_group_controller.js +194 -99
  305. package/dist/controllers/toggle_group_controller.js.map +1 -1
  306. package/dist/controllers/toolbar_controller.js +92 -90
  307. package/dist/controllers/toolbar_controller.js.map +1 -1
  308. package/dist/controllers/tooltip_controller.d.ts +52 -2
  309. package/dist/controllers/tooltip_controller.js +185 -125
  310. package/dist/controllers/tooltip_controller.js.map +1 -1
  311. package/dist/controllers/transition_controller.d.ts +22 -4
  312. package/dist/controllers/transition_controller.js +123 -115
  313. package/dist/controllers/transition_controller.js.map +1 -1
  314. package/dist/controllers/tree_view_controller.d.ts +59 -18
  315. package/dist/controllers/tree_view_controller.js +248 -290
  316. package/dist/controllers/tree_view_controller.js.map +1 -1
  317. package/dist/index.d.ts +33 -3
  318. package/dist/index.js +8649 -8572
  319. package/dist/index.js.map +1 -1
  320. package/dist/inspector/cli.d.ts +95 -9
  321. package/dist/inspector/cli.js +343 -77
  322. package/dist/inspector/cli.js.map +1 -1
  323. package/dist/inspector/cli_bin.js +390 -96
  324. package/dist/inspector/cli_bin.js.map +1 -1
  325. package/dist/inspector/examples.json +45 -45
  326. package/dist/inspector/manifest.json +1308 -155
  327. package/dist/positioning/index.d.ts +8 -0
  328. package/dist/positioning/index.js +148 -45
  329. package/dist/positioning/index.js.map +1 -1
  330. package/package.json +7 -7
  331. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  332. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,8 +1,27 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/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,101 +33,267 @@ function syncActiveOption(options, active) {
14
33
  return written;
15
34
  }
16
35
 
17
- // src/utils/arrow_step.ts
18
36
  function isReservedArrowChord(event, allow = []) {
19
37
  if (!event.key.startsWith("Arrow")) return false;
20
38
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
21
39
  }
22
40
 
23
- // src/utils/composition_tracker.ts
41
+ function sharedRegistry(key, create) {
42
+ const symbol = Symbol.for(key);
43
+ const scope = globalThis;
44
+ const existing = scope[symbol];
45
+ if (existing !== void 0) return existing;
46
+ const registry = create();
47
+ Object.defineProperty(globalThis, symbol, { value: registry });
48
+ return registry;
49
+ }
50
+
51
+ function isAttributeValue(value) {
52
+ return value === null || typeof value === "string";
53
+ }
54
+ function readLeaseRecord(element, record) {
55
+ try {
56
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
57
+ if (!Array.isArray(parsed)) return void 0;
58
+ const [original, written, ...rest] = parsed;
59
+ const linked = rest[0] !== false;
60
+ const beneath = linked ? rest : rest.slice(1);
61
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
62
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
63
+ } catch {
64
+ return void 0;
65
+ }
66
+ }
67
+ var AttributeLease = class _AttributeLease {
68
+ static #registry = sharedRegistry(
69
+ "stimeo-ui.attribute-lease.registry.v1",
70
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
71
+ );
72
+ #attribute;
73
+ #record;
74
+ #base;
75
+ #held = /* @__PURE__ */ new Set();
76
+ constructor(attribute, owner, options = {}) {
77
+ this.#attribute = attribute;
78
+ this.#record = `data-${owner}-${attribute}-lease`;
79
+ this.#base = options.base === true;
80
+ const names = _AttributeLease.#registry.names;
81
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
82
+ }
83
+ write(element, value) {
84
+ const stack = this.#stack(element);
85
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
86
+ this.#held.add(element);
87
+ const shows = !this.#covered(element, stack, hold);
88
+ hold.written = value;
89
+ this.#mark(element, stack);
90
+ if (shows) this.#reflect(element, value);
91
+ }
92
+ return(element) {
93
+ const held = this.#held.delete(element);
94
+ const known = _AttributeLease.#registry.stacks.get(element)?.has(this.#attribute);
95
+ const stack = held || known || element.hasAttribute(this.#record) ? this.#stack(element) : [];
96
+ const hold = this.#take(stack);
97
+ if (!hold) return false;
98
+ const carries = element.getAttribute(this.#attribute) === hold.written;
99
+ const owns = carries && !this.#covered(element, stack, hold);
100
+ const index = stack.indexOf(hold);
101
+ stack.splice(index, 1);
102
+ const above = stack[index];
103
+ if (above) above.linked &&= hold.linked;
104
+ this.#mark(element, stack);
105
+ if (!owns) return true;
106
+ const below = hold.linked ? stack[index - 1] : void 0;
107
+ this.#reflect(element, below ? below.written : hold.original);
108
+ return true;
109
+ }
110
+ returnAll() {
111
+ for (const element of Array.from(this.#held)) this.return(element);
112
+ }
113
+ #stack(element) {
114
+ const stacks = _AttributeLease.#registry.stacks;
115
+ const byAttribute = stacks.get(element) ?? /* @__PURE__ */ new Map();
116
+ stacks.set(element, byAttribute);
117
+ const stack = byAttribute.get(this.#attribute) ?? this.#seed(element);
118
+ byAttribute.set(this.#attribute, stack);
119
+ return stack;
120
+ }
121
+ #seed(element) {
122
+ const found = [];
123
+ for (const record of _AttributeLease.#registry.names.get(this.#attribute) ?? []) {
124
+ const read = readLeaseRecord(element, record);
125
+ if (read) found.push({ ...read, record });
126
+ }
127
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
128
+ return found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
129
+ lease: null,
130
+ record,
131
+ written,
132
+ original,
133
+ linked: linked && index > 0
134
+ }));
135
+ }
136
+ #take(stack) {
137
+ const hold = stack.find((held) => held.lease === this) ?? stack.find((held) => held.lease === null && held.record === this.#record);
138
+ if (hold) hold.lease = this;
139
+ return hold;
140
+ }
141
+ #acquire(element, stack) {
142
+ const current = element.getAttribute(this.#attribute);
143
+ const top = stack.at(-1);
144
+ const bottom = stack[0];
145
+ const over = top?.written === current ? top : void 0;
146
+ const from = this.#base ? bottom : over;
147
+ const hold = {
148
+ lease: this,
149
+ record: this.#record,
150
+ written: current,
151
+ original: from ? from.original : current,
152
+ linked: !this.#base && over !== void 0
153
+ };
154
+ if (this.#base && bottom) {
155
+ bottom.linked = true;
156
+ stack.unshift(hold);
157
+ } else stack.push(hold);
158
+ return hold;
159
+ }
160
+ #covered(element, stack, hold) {
161
+ const current = element.getAttribute(this.#attribute);
162
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
163
+ }
164
+ #mark(element, stack) {
165
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
166
+ stack.forEach((hold, index) => {
167
+ const names = stack.slice(0, index).map((under) => under.record);
168
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
169
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
170
+ records.set(
171
+ hold.record,
172
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
173
+ );
174
+ });
175
+ for (const [name, recorded] of records) {
176
+ if (element.getAttribute(name) === recorded) continue;
177
+ if (recorded === null) element.removeAttribute(name);
178
+ else element.setAttribute(name, recorded);
179
+ }
180
+ }
181
+ #reflect(element, value) {
182
+ if (element.getAttribute(this.#attribute) === value) return;
183
+ if (value === null) element.removeAttribute(this.#attribute);
184
+ else element.setAttribute(this.#attribute, value);
185
+ }
186
+ };
187
+
188
+ var COMPOSITION_INPUT_TYPES = /* @__PURE__ */ new Set([
189
+ "insertCompositionText",
190
+ "insertFromComposition",
191
+ "deleteCompositionText",
192
+ "deleteByComposition"
193
+ ]);
24
194
  var CompositionTracker = class {
25
195
  #observedTargets = /* @__PURE__ */ new Set();
26
196
  #activeTargets = /* @__PURE__ */ new Set();
27
197
  #onStart;
28
198
  #onEnd;
199
+ #confirmedTarget = null;
29
200
  constructor(options = {}) {
30
201
  this.#onStart = options.onStart;
31
202
  this.#onEnd = options.onEnd;
32
203
  }
33
- /** Starts lifecycle tracking for `target`; repeated calls are idempotent. */
34
204
  observe(target) {
35
205
  if (this.#observedTargets.has(target)) return;
36
206
  target.addEventListener("compositionstart", this.#handleStart);
37
207
  target.addEventListener("compositionend", this.#handleEnd);
208
+ target.addEventListener("keydown", this.#handleKeydown);
38
209
  this.#observedTargets.add(target);
39
210
  }
40
- /** Stops tracking one target and clears any active composition it owned. */
41
211
  unobserve(target) {
42
212
  if (!this.#observedTargets.delete(target)) return;
43
213
  target.removeEventListener("compositionstart", this.#handleStart);
44
214
  target.removeEventListener("compositionend", this.#handleEnd);
215
+ target.removeEventListener("keydown", this.#handleKeydown);
45
216
  this.#activeTargets.delete(target);
217
+ if (this.#confirmedTarget === target) this.#confirmedTarget = null;
46
218
  }
47
- /** Releases every listener and clears state so reconnect starts cleanly. */
48
219
  disconnect() {
49
220
  for (const target of this.#observedTargets) {
50
221
  target.removeEventListener("compositionstart", this.#handleStart);
51
222
  target.removeEventListener("compositionend", this.#handleEnd);
223
+ target.removeEventListener("keydown", this.#handleKeydown);
52
224
  }
53
225
  this.#observedTargets.clear();
54
226
  this.#activeTargets.clear();
227
+ this.#confirmedTarget = null;
55
228
  }
56
- /** True when lifecycle tracking or the current event reports composition. */
57
229
  isComposing(event) {
58
230
  return this.#activeTargets.size > 0 || event?.isComposing === true;
59
231
  }
232
+ consumesConfirmedInput(event) {
233
+ const confirmed = this.#confirmedTarget;
234
+ this.#confirmedTarget = null;
235
+ if (confirmed === null || confirmed !== event.target) return false;
236
+ const inputType = event.inputType;
237
+ return !inputType || COMPOSITION_INPUT_TYPES.has(inputType);
238
+ }
60
239
  #handleStart = (event) => {
240
+ this.#confirmedTarget = null;
61
241
  if (event.currentTarget) this.#activeTargets.add(event.currentTarget);
62
242
  this.#onStart?.(event);
63
243
  };
64
244
  #handleEnd = (event) => {
65
245
  if (event.currentTarget) this.#activeTargets.delete(event.currentTarget);
246
+ this.#confirmedTarget = event.target;
66
247
  this.#onEnd?.(event);
67
248
  };
249
+ #handleKeydown = () => {
250
+ this.#confirmedTarget = null;
251
+ };
68
252
  };
69
253
 
70
- // src/utils/before_cache_reset.ts
71
- var BeforeCacheReset = class _BeforeCacheReset {
72
- /** Every subscribed instance, iterated by the one shared document listener. */
73
- static #subscribers = /* @__PURE__ */ new Set();
74
- /** The shared listener; installed while at least one instance is subscribed. */
75
- static #onBeforeCache = () => {
76
- for (const subscriber of _BeforeCacheReset.#subscribers) subscriber.#rewind();
77
- };
78
- #rewind;
79
- /** @param rewind - the pass that returns this controller's state to its initial form. */
80
- constructor(rewind) {
81
- this.#rewind = rewind;
82
- }
83
- /** Subscribes to `turbo:before-cache`; call from `connect()`. Idempotent. */
84
- activate() {
85
- const first = _BeforeCacheReset.#subscribers.size === 0;
86
- _BeforeCacheReset.#subscribers.add(this);
87
- if (first) {
88
- 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;
89
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);
90
277
  }
91
- /** Unsubscribes; call from `disconnect()`. Safe when never subscribed. */
92
- deactivate() {
93
- _BeforeCacheReset.#subscribers.delete(this);
94
- if (_BeforeCacheReset.#subscribers.size > 0) return;
95
- 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;
96
288
  }
97
289
  };
98
290
 
99
- // src/utils/escape_layer.ts
100
291
  var EscapeLayer = class _EscapeLayer {
101
- static #registries = /* @__PURE__ */ new WeakMap();
102
- #ownerDocument = null;
103
- /** Dismissal callback while active; `null` when inactive. */
104
- #onDismiss = null;
105
- /** Live predicate deciding whether the layer claims a press; `null` = always. */
106
- #claims = null;
107
- /**
108
- * Activates this layer at the top of its document's Escape stack, installing
109
- * the document's shared resolver listener if this is its first layer.
110
- * Re-activating an already-active layer moves it to the top.
111
- */
292
+ static #registries = sharedRegistry(
293
+ "stimeo-ui.escape-layer.registry.v1",
294
+ () => /* @__PURE__ */ new WeakMap()
295
+ );
296
+ #active = null;
112
297
  activate(ownerDocument = document, options) {
113
298
  this.deactivate();
114
299
  let registry = _EscapeLayer.#registries.get(ownerDocument);
@@ -117,44 +302,31 @@ var EscapeLayer = class _EscapeLayer {
117
302
  _EscapeLayer.#registries.set(ownerDocument, registry);
118
303
  ownerDocument.addEventListener("keydown", registry.onKeydown);
119
304
  }
120
- registry.stack.push(this);
121
- this.#ownerDocument = ownerDocument;
122
- this.#onDismiss = options.onDismiss;
123
- 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 };
124
308
  }
125
- /**
126
- * Removes this layer from its document's Escape stack, uninstalling the
127
- * shared listener when the stack empties. Safe to call when inactive.
128
- */
129
309
  deactivate() {
130
- const ownerDocument = this.#ownerDocument;
131
- if (!ownerDocument) return;
132
- const registry = _EscapeLayer.#registries.get(ownerDocument);
310
+ const active = this.#active;
311
+ if (!active) return;
312
+ const registry = _EscapeLayer.#registries.get(active.document);
133
313
  if (registry) {
134
- const index = registry.stack.lastIndexOf(this);
314
+ const index = registry.stack.lastIndexOf(active.entry);
135
315
  if (index >= 0) registry.stack.splice(index, 1);
136
316
  if (registry.stack.length === 0) {
137
- ownerDocument.removeEventListener("keydown", registry.onKeydown);
138
- _EscapeLayer.#registries.delete(ownerDocument);
317
+ active.document.removeEventListener("keydown", registry.onKeydown);
318
+ _EscapeLayer.#registries.delete(active.document);
139
319
  }
140
320
  }
141
- this.#ownerDocument = null;
142
- this.#onDismiss = null;
143
- this.#claims = null;
321
+ this.#active = null;
144
322
  }
145
- /**
146
- * Whether this active layer would own a press right now: it is the topmost
147
- * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
148
- * and diagnostics — production dismissal goes through the shared listener.
149
- */
150
323
  get ownsEscape() {
151
- const ownerDocument = this.#ownerDocument;
152
- if (!ownerDocument) return false;
153
- const registry = _EscapeLayer.#registries.get(ownerDocument);
324
+ const active = this.#active;
325
+ if (!active) return false;
326
+ const registry = _EscapeLayer.#registries.get(active.document);
154
327
  if (!registry) return false;
155
- return _EscapeLayer.#resolveOwner(registry.stack) === this;
328
+ return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
156
329
  }
157
- /** Builds a document's registry with its shared resolver listener. */
158
330
  static #createRegistry() {
159
331
  const registry = {
160
332
  stack: [],
@@ -163,24 +335,22 @@ var EscapeLayer = class _EscapeLayer {
163
335
  const owner = _EscapeLayer.#resolveOwner(registry.stack);
164
336
  if (!owner) return;
165
337
  event.preventDefault();
166
- owner.#onDismiss?.();
338
+ owner.onDismiss();
167
339
  }
168
340
  };
169
341
  return registry;
170
342
  }
171
- /** The topmost stack layer whose claims predicate passes, or `null`. */
172
343
  static #resolveOwner(stack) {
173
344
  for (let index = stack.length - 1; index >= 0; index--) {
174
345
  const layer = stack[index];
175
346
  if (!layer) continue;
176
- if (layer.#claims && !layer.#claims()) continue;
347
+ if (layer.claims && !layer.claims()) continue;
177
348
  return layer;
178
349
  }
179
350
  return null;
180
351
  }
181
352
  };
182
353
 
183
- // src/utils/focus_candidate.ts
184
354
  function inheritsFieldsetDisabled(control) {
185
355
  let fieldset = control.closest("fieldset[disabled]");
186
356
  while (fieldset) {
@@ -190,8 +360,34 @@ function inheritsFieldsetDisabled(control) {
190
360
  }
191
361
  return false;
192
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
+ }
193
387
  function canTakeFocus(element) {
194
- 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
+ }
195
391
  if (element instanceof HTMLInputElement && element.type === "hidden") return false;
196
392
  if (!("disabled" in element)) return true;
197
393
  if (element.disabled) return false;
@@ -248,222 +444,676 @@ function isTabStop(element) {
248
444
  if (tabindex !== null) return tabindex >= 0;
249
445
  return hasNativeTabStop(element) || hasEditableTabStop(element);
250
446
  }
251
- function tabStopsWithin(root) {
252
- return Array.from(root.querySelectorAll(TAB_STOP_CANDIDATE_SELECTOR)).filter(
253
- 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)
471
+ );
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)
254
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
+ }
557
+
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;
255
600
  }
256
601
 
257
- // src/utils/focus_trap.ts
258
- var FocusTrap = class {
259
- /** The element focused before activation, restored on deactivation. */
260
- #previouslyFocused = null;
261
- /** The body's inline `overflow` before locking, restored on deactivation. */
262
- #previousBodyOverflow = "";
263
- /** Whether scroll was locked this activation (so it is only restored if applied). */
264
- #scrollLocked = false;
265
- /** Background siblings made `inert` while active, restored on deactivation. */
266
- #inertedSiblings = [];
267
- /** Whether the modal side effects are currently applied. */
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();
268
711
  #activeState = false;
269
- /** Registers the trap on the shared Escape stack while active (see {@link EscapeLayer}). */
270
712
  #escapeLayer = new EscapeLayer();
271
- /** Returns the trapped element; called on every operation for the live target. */
713
+ #gate = new DetachGate();
272
714
  #getContainer;
273
- /** Closing/focus hooks; see {@link FocusTrapOptions}. */
274
715
  #options;
275
- /**
276
- * @param getContainer - Returns the trapped element. Called on every operation
277
- * so the live target is always used.
278
- * @param options - Closing/focus hooks; see {@link FocusTrapOptions}.
279
- */
280
716
  constructor(getContainer, options = {}) {
281
717
  this.#getContainer = getContainer;
282
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
+ };
283
728
  }
284
- /** Whether the trap is currently active. */
285
729
  get active() {
286
730
  return this.#activeState;
287
731
  }
288
- /**
289
- * Applies the trap: records the current focus, optionally locks background scroll
290
- * and makes background siblings `inert`, listens for `Tab`/`Escape`, and (unless
291
- * `autoFocus` is off) moves focus inside. No-ops if already active.
292
- */
293
732
  activate() {
294
733
  if (this.#activeState) return;
734
+ _FocusTrap.#sweep();
295
735
  this.#activeState = true;
296
- const active = document.activeElement;
297
- this.#previouslyFocused = active instanceof HTMLElement && active !== document.body ? active : null;
298
- if (this.#flag(this.#options.lockScroll, true)) {
299
- this.#previousBodyOverflow = document.body.style.overflow;
300
- document.body.style.overflow = "hidden";
301
- this.#scrollLocked = true;
302
- }
303
- if (this.#flag(this.#options.isolate, true)) this.#isolateBackground();
304
- document.addEventListener("keydown", this.#onKeydown);
305
- 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);
306
742
  const onEscape = this.#options.onEscape;
307
743
  if (onEscape) this.#escapeLayer.activate(document, { onDismiss: () => onEscape() });
308
744
  if (this.#flag(this.#options.autoFocus, true)) this.#focusInitial();
309
745
  }
310
- /**
311
- * Reverts every side effect applied by {@link activate}. No-ops if inactive, so
312
- * a controller can call it defensively from both `close()` and `disconnect()`.
313
- *
314
- * @param restoreFocus - Move focus back to the opener (default `true`). Pass
315
- * `false` on teardown (`disconnect`), where yanking focus is undesirable.
316
- */
317
746
  deactivate({ restoreFocus = true } = {}) {
318
- if (!this.#activeState) return;
747
+ if (!this.#activeState) {
748
+ this.#returnTabindexes();
749
+ return;
750
+ }
319
751
  this.#activeState = false;
320
752
  this.#escapeLayer.deactivate();
321
- document.removeEventListener("keydown", this.#onKeydown);
322
- this.#beforeCache.deactivate();
323
- if (this.#scrollLocked) {
324
- document.body.style.overflow = this.#previousBodyOverflow;
325
- this.#scrollLocked = false;
326
- }
327
- this.#releaseBackground();
328
- if (restoreFocus) {
329
- const target = this.#previouslyFocused ?? this.#options.fallbackFocus?.() ?? null;
330
- target?.focus();
331
- }
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();
332
783
  }
333
- /** Resolves a boolean-or-getter option, defaulting when it was not provided. */
334
784
  #flag(option, fallback) {
335
785
  if (option === void 0) return fallback;
336
786
  return typeof option === "function" ? option() : option;
337
787
  }
338
- /**
339
- * Reverts the side effects just before Turbo caches the page snapshot, so an
340
- * overlay left open does not bake the scroll lock into `body[style]` — a
341
- * restored page would feed that locked value back into {@link activate} as the
342
- * baseline, and closing would then never unlock the page. Markup state stays
343
- * untouched (restore-open designs reopen against a clean baseline), and focus
344
- * is left alone mid-navigation. The listener lives only while active.
345
- */
346
- #beforeCache = new BeforeCacheReset(() => this.deactivate({ restoreFocus: false }));
347
- /**
348
- * Handles `Tab` (focus trap) while active. `Escape` dismissal is owned by the
349
- * shared {@link EscapeLayer} resolver, so Tab trapping stays independent of
350
- * which layer currently owns Escape.
351
- */
352
- #onKeydown = (event) => {
353
- if (event.key === "Tab") this.#trapTab(event);
354
- };
355
- /** Keeps `Tab` focus cycling within the container's focusable elements. */
356
- #trapTab(event) {
357
- const focusable = this.#focusableElements();
358
- if (focusable.length === 0) {
359
- event.preventDefault();
360
- 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);
361
794
  }
362
- const first = focusable[0];
363
- const last = focusable[focusable.length - 1];
364
- const active = document.activeElement;
365
- if (!(active instanceof Node) || !this.#getContainer().contains(active)) {
366
- event.preventDefault();
367
- first?.focus();
368
- 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);
369
806
  }
370
- if (event.shiftKey && active === first) {
371
- event.preventDefault();
372
- last?.focus();
373
- } else if (!event.shiftKey && active === last) {
374
- event.preventDefault();
375
- first?.focus();
376
- }
377
- }
378
- /**
379
- * Marks every element outside the container's subtree as `inert` so background
380
- * content cannot be focused or reached by assistive technology, honoring the
381
- * `aria-modal="true"` contract. An element that was *already* `inert` is left
382
- * untracked so `#releaseBackground` does not wrongly clear it.
383
- *
384
- * The walk climbs from the container to `body` and inerts each ancestor's other
385
- * children. Scanning only `body`'s children would skip the branch the container
386
- * sits in — everything beside it inside that branch is background too, and a
387
- * nested container is the ordinary case.
388
- */
389
- #isolateBackground() {
390
- const container = this.#getContainer();
391
- this.#inertedSiblings = [];
392
- for (let node = container; node !== document.body; ) {
393
- const parent = node.parentElement;
394
- if (!parent) break;
395
- for (const sibling of Array.from(parent.children)) {
396
- if (!(sibling instanceof HTMLElement)) continue;
397
- if (sibling === node || sibling.inert) continue;
398
- sibling.inert = true;
399
- 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);
969
+ }
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);
400
984
  }
401
- node = parent;
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;
402
994
  }
403
995
  }
404
- /** Reverts the `inert` flags applied by `#isolateBackground`. */
405
- #releaseBackground() {
406
- for (const sibling of this.#inertedSiblings) {
407
- 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();
408
1034
  }
409
- this.#inertedSiblings = [];
410
1035
  }
411
- /** Moves focus to the initial target, the first focusable, or the container. */
412
1036
  #focusInitial() {
413
1037
  const preferred = this.#options.initialFocus?.();
414
1038
  if (preferred) {
415
1039
  preferred.focus();
416
1040
  return;
417
1041
  }
418
- const focusable = this.#focusableElements();
419
- if (focusable[0]) {
420
- focusable[0].focus();
1042
+ const container = this.#getContainer();
1043
+ const [first] = sequentialTabStops(container);
1044
+ if (first) {
1045
+ first.focus();
421
1046
  return;
422
1047
  }
423
- const container = this.#getContainer();
424
- container.tabIndex = -1;
1048
+ this.#lendTabindex(container);
425
1049
  container.focus();
426
1050
  }
427
- /** Collects the container's currently focusable descendants in DOM order. */
428
- #focusableElements() {
429
- 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();
430
1061
  }
431
1062
  };
432
1063
 
433
- // src/utils/microtask_coalescer.ts
434
- var MicrotaskCoalescer = class {
435
- #run;
436
- #queued = false;
437
- #active = false;
438
- #generation = 0;
439
- /** @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();
440
1069
  constructor(run) {
441
- this.#run = run;
1070
+ this.#pass = new MicrotaskCoalescer(run);
442
1071
  }
443
- /** Opens the window in which {@link schedule} is honoured; call from `connect()`. */
444
- activate() {
445
- 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);
446
1078
  }
447
- /** Closes the window and drops any pending pass; call from `disconnect()`. */
448
- cancel() {
449
- this.#active = false;
450
- this.#queued = false;
451
- this.#generation += 1;
452
- }
453
- /** Requests one pass after the batch settles. Idempotent; inert outside the window. */
454
1079
  schedule() {
455
- if (!this.#active || this.#queued) return;
456
- this.#queued = true;
457
- const generation = this.#generation;
458
- queueMicrotask(() => {
459
- if (generation !== this.#generation || !this.#queued || !this.#active) return;
460
- this.#queued = false;
461
- this.#run();
462
- });
1080
+ this.#pass.schedule();
1081
+ }
1082
+ disconnect() {
1083
+ this.#listeners.dispose();
1084
+ this.#pass.cancel();
1085
+ this.#element = null;
463
1086
  }
464
1087
  };
465
1088
 
466
- // src/controllers/command_palette_controller.ts
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, "");
1110
+ }
1111
+ };
1112
+
1113
+ function targetSelector(identifier, name) {
1114
+ return `[data-${identifier}-target~="${name}"]`;
1115
+ }
1116
+
467
1117
  var CommandPaletteController = class _CommandPaletteController extends Controller {
468
1118
  static #ORIGINAL_ARIA_DISABLED = "data-command-palette-original-aria-disabled";
469
1119
  static #ABSENT_ARIA_DISABLED = "absent";
@@ -478,85 +1128,103 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
478
1128
  "filter",
479
1129
  "onKeydown",
480
1130
  "open",
481
- "selectByClick",
1131
+ "select",
482
1132
  "toggle"
483
1133
  ];
484
1134
  static events = ["select"];
485
- /** Stable ID of the active option; the live target is resolved before every use. */
486
1135
  #activeId = null;
487
- /** Visible/selectable target ID order captured for removal fallback. */
488
1136
  #activeOrder = [];
489
1137
  #connected = false;
490
- /** Collapses one mutation batch of target callbacks into a single pass. */
491
- #reconcile = new MicrotaskCoalescer(() => this.#reconcileActive());
492
- /** 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());
493
1151
  #composition = new CompositionTracker({
494
1152
  onEnd: () => {
495
1153
  if (this.#isOpen) this.filter();
496
1154
  }
497
1155
  });
498
- /**
499
- * Owns the modal side effects (focus trap, scroll lock, background `inert`, focus
500
- * restore). Escape closes; focus on open goes to the input, and is restored to
501
- * whatever opened the palette on close.
502
- */
503
1156
  #trap = new FocusTrap(() => this.dialogTarget, {
504
1157
  onEscape: () => this.close(),
505
- initialFocus: () => this.hasInputTarget ? this.inputTarget : null
1158
+ initialFocus: () => {
1159
+ const dialog = this.dialogTarget;
1160
+ return this.inputTargets.find((input) => dialog.contains(input)) ?? null;
1161
+ }
506
1162
  });
507
- /**
508
- * Initializes the global hotkey handler and establishes the initial open state.
509
- *
510
- * The DOM is the source of truth on reconnect (Turbo cache restore / morph): if
511
- * the restored snapshot already shows the dialog open, honor that rather than
512
- * re-deriving from the declarative `open` Value (which would slam a user-opened
513
- * palette shut). The `open` Value only seeds the initial state of a genuinely
514
- * fresh render. We normalize to a clean closed baseline first so {@link open}
515
- * runs its full setup — the `FocusTrap` is a fresh instance after a
516
- * reconnect and must be re-activated.
517
- */
518
1163
  connect() {
1164
+ const connection = this.#lived.connect(this.element);
1165
+ this.#trap.connect();
519
1166
  document.addEventListener("keydown", this.#onGlobalKeydown);
520
1167
  if (this.hasInputTarget) this.#composition.observe(this.inputTarget);
521
1168
  this.#syncOptionSemantics();
522
- const shouldOpen = this.#isOpen || this.openValue;
523
- this.#resetToClosedState();
524
- 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;
525
1177
  this.#connected = true;
526
1178
  this.#reconcile.activate();
1179
+ this.#morphRender.observe(this.element);
527
1180
  }
528
- /** Tears down the global hotkey listener and reverts the modal side effects. */
529
1181
  disconnect() {
530
1182
  this.#connected = false;
1183
+ this.#lived.disconnect();
1184
+ this.#morphRender.disconnect();
531
1185
  this.#reconcile.cancel();
532
1186
  document.removeEventListener("keydown", this.#onGlobalKeydown);
533
1187
  this.#composition.disconnect();
534
- this.#trap.deactivate({ restoreFocus: false });
535
- this.#resetToClosedState();
1188
+ this.#trap.disconnect(this);
536
1189
  }
537
- /** Initializes semantics for an option added before or after the controller connects. */
538
1190
  optionTargetConnected(option) {
539
1191
  this.#syncOptionSemanticsFor(option);
540
1192
  option.setAttribute("aria-selected", "false");
541
1193
  option.removeAttribute("data-active");
542
1194
  if (this.#connected) this.#queueOptionReconciliation();
543
1195
  }
544
- /** Clears active residue on the old node and reconciles the surviving targets. */
545
1196
  optionTargetDisconnected(option) {
546
1197
  option.setAttribute("aria-selected", "false");
547
1198
  option.removeAttribute("data-active");
548
1199
  if (this.#connected) this.#queueOptionReconciliation();
549
1200
  }
550
- /** Tracks an input added initially or after connect without extra consumer actions. */
551
1201
  inputTargetConnected(input) {
552
1202
  this.#composition.observe(input);
553
1203
  if (this.#connected) this.#queueOptionReconciliation();
554
1204
  }
555
- /** Removes controller-owned listeners when the input target is replaced or removed. */
556
1205
  inputTargetDisconnected(input) {
557
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();
558
1227
  }
559
- /** Toggles the open state of the command palette. */
560
1228
  toggle() {
561
1229
  if (this.#isOpen) {
562
1230
  this.close();
@@ -564,25 +1232,43 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
564
1232
  this.open();
565
1233
  }
566
1234
  }
567
- /** Opens the palette, traps focus, and shifts focus to the input. */
568
1235
  open() {
569
1236
  if (!this.hasDialogTarget || this.#isOpen) return;
570
- this.dialogTarget.hidden = false;
1237
+ this.#hidden.write(this.dialogTarget, null);
571
1238
  this.openValue = true;
572
- if (this.hasInputTarget) this.inputTarget.setAttribute("aria-expanded", "true");
1239
+ if (this.hasInputTarget) this.#expanded.write(this.inputTarget, "true");
573
1240
  this.#resetFilter();
574
1241
  this.#trap.activate();
575
1242
  }
576
- /** Closes the palette and restores focus back to the opener. */
577
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) {
578
1263
  if (!this.hasDialogTarget || !this.#isOpen) return;
579
1264
  this.#resetToClosedState();
580
- this.#trap.deactivate();
1265
+ this.#trap.deactivate({ restoreFocus });
581
1266
  }
582
- /** Filters option elements in-memory matching the input value. Bound to input target. */
583
1267
  filter(event) {
584
1268
  if (!this.hasInputTarget || this.#composition.isComposing(event)) return;
585
- this.#syncOptionSemantics();
1269
+ this.#setActiveIndex(this.#applyQuery() ? 0 : -1);
1270
+ }
1271
+ #applyQuery() {
586
1272
  const query = this.inputTarget.value.trim().toLowerCase();
587
1273
  let hasSelectableMatch = false;
588
1274
  for (const option of this.optionTargets) {
@@ -596,31 +1282,35 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
596
1282
  }
597
1283
  }
598
1284
  if (this.hasEmptyTarget) {
599
- this.emptyTarget.hidden = hasSelectableMatch;
1285
+ this.#hidden.write(this.emptyTarget, hasSelectableMatch ? "" : null);
600
1286
  }
601
- 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();
602
1295
  }
603
- /** Selects the clicked option. Bound to option targets. */
604
- selectByClick(event) {
605
- const target = event.currentTarget;
606
- if (!target) return;
607
- 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"));
608
1301
  if (!option || !this.optionTargets.includes(option)) return;
609
1302
  this.#syncOptionSemanticsFor(option);
610
1303
  if (!option.hasAttribute("hidden") && !this.#isDisabled(option)) {
611
- this.#confirmSelection(option);
1304
+ this.#confirmSelection(
1305
+ option,
1306
+ reason,
1307
+ event !== null || this.element.contains(document.activeElement)
1308
+ );
612
1309
  }
613
1310
  }
614
- /** Closes when the backdrop (the dialog target itself) is clicked, ignoring inner clicks. */
615
1311
  closeOnBackdrop(event) {
616
1312
  if (event.target === this.dialogTarget) this.close();
617
1313
  }
618
- /**
619
- * Combobox navigation keys (arrows / Home / End / Enter), bound to the input.
620
- * `Tab` (focus trap) and `Escape` (close) are owned by the shared `FocusTrap` at
621
- * the document level, so they work no matter which element inside the dialog has
622
- * focus — not only the input.
623
- */
624
1314
  onKeydown(event) {
625
1315
  if (event.defaultPrevented) return;
626
1316
  if (isReservedArrowChord(event)) return;
@@ -676,14 +1366,9 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
676
1366
  this.#activeId = activeOption?.id || null;
677
1367
  this.#activeOrder = activeOption ? visible.map((option) => option.id).filter(Boolean) : [];
678
1368
  if (this.hasInputTarget) {
679
- if (activeOption?.id) {
680
- this.inputTarget.setAttribute("aria-activedescendant", activeOption.id);
681
- } else {
682
- this.inputTarget.removeAttribute("aria-activedescendant");
683
- }
1369
+ this.#activeDescendant.write(this.inputTarget, activeOption?.id || null);
684
1370
  }
685
1371
  }
686
- /** Re-resolves active identity and component-specific fallback against live targets. */
687
1372
  #reconcileActive() {
688
1373
  if (!this.#isOpen) {
689
1374
  this.#setActiveIndex(-1);
@@ -710,7 +1395,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
710
1395
  this.#setActiveIndex(activeId ? this.#findFallbackIndex(visible, activeId) : -1);
711
1396
  this.#reflectEmptyState();
712
1397
  }
713
- /** Resolves stable ID first, then active markers for the existing ID-less case. */
714
1398
  #findActiveIndex(options) {
715
1399
  const activeId = (this.hasInputTarget ? this.inputTarget.getAttribute("aria-activedescendant") : null) ?? this.#activeId;
716
1400
  if (activeId) return options.findIndex((option) => option.id === activeId);
@@ -718,7 +1402,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
718
1402
  (option) => option.hasAttribute("data-active") || option.getAttribute("aria-selected") === "true"
719
1403
  );
720
1404
  }
721
- /** Chooses a surviving former successor, then predecessor, from selectable targets only. */
722
1405
  #findFallbackIndex(options, activeId) {
723
1406
  const oldIndex = this.#activeOrder.indexOf(activeId);
724
1407
  if (oldIndex < 0) return -1;
@@ -733,28 +1416,21 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
733
1416
  }
734
1417
  return -1;
735
1418
  }
736
- /** Reflects whether any visible, navigable command remains. */
737
1419
  #reflectEmptyState() {
738
- 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
+ }
739
1423
  }
740
- /** Coalesces all target callbacks from one MutationObserver batch. */
741
1424
  #queueOptionReconciliation() {
742
1425
  this.#reconcile.schedule();
743
1426
  }
744
- /**
745
- * Runs `option`: dispatches `select` and closes.
746
- *
747
- * **Activation is refused here, not at the call sites.** Every path that runs a
748
- * command funnels through this method, so one check covers keyboard, pointer,
749
- * and anything added later. Per-call-site guards would leave `aria-disabled`
750
- * options — which stay within the keyboard's reach — runnable from whichever
751
- * path forgets to consult the predicate.
752
- */
753
- #confirmSelection(option) {
1427
+ #confirmSelection(option, reason = "user", restoreFocus = true) {
754
1428
  if (this.#isDisabled(option)) return;
755
1429
  const value = option.dataset.value || option.textContent || "";
756
- this.dispatch("select", { detail: { value, option } });
757
- this.close();
1430
+ this.dispatch("select", { detail: { value, option, reason } });
1431
+ this.#close(
1432
+ restoreFocus && (reason !== "api" || this.element.contains(document.activeElement))
1433
+ );
758
1434
  }
759
1435
  #resetFilter() {
760
1436
  if (this.hasInputTarget) this.inputTarget.value = "";
@@ -770,43 +1446,15 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
770
1446
  (option) => !option.hasAttribute("hidden") && this.#isNavigable(option)
771
1447
  );
772
1448
  }
773
- /**
774
- * Whether virtual focus may land on `option`.
775
- *
776
- * Only `data-disabled` disqualifies. That attribute is this controller's own —
777
- * authors reach for it to mark a row that is not a destination at all, such as
778
- * a group heading — so it can mean "skip entirely" without contradicting ARIA.
779
- *
780
- * **`aria-disabled` does not disqualify.** It marks a command that must stay
781
- * *discoverable*: `aria-activedescendant` names it, so a reader announces it as
782
- * unavailable rather than hiding its existence. Virtual focus does
783
- * not change that — pointing `aria-activedescendant` at a disabled option is
784
- * ordinary ARIA. Keeping the two attributes distinct is what leaves both
785
- * meanings expressible; collapsing them would take "show it, say it is
786
- * unavailable, do not run it" away from consumers.
787
- */
788
1449
  #isNavigable(option) {
789
1450
  return option.dataset.disabled !== "true";
790
1451
  }
791
- /** Whether activating `option` may run. Suppressed for either disabled marker. */
792
1452
  #isDisabled(option) {
793
1453
  return option.dataset.disabled === "true" || option.getAttribute("aria-disabled") === "true";
794
1454
  }
795
- /** Synchronizes every option's controller-owned selection and disabled semantics. */
796
1455
  #syncOptionSemantics() {
797
1456
  for (const option of this.optionTargets) this.#syncOptionSemanticsFor(option);
798
1457
  }
799
- /**
800
- * Reflects `data-disabled` to ARIA without losing a pre-existing authored value,
801
- * and puts `aria-selected` back to its baseline.
802
- *
803
- * Here `aria-selected` marks the *active* option, not a committed choice — the
804
- * palette runs a command and keeps nothing selected — so an authored value is
805
- * meaningless and is overwritten rather than preserved. Every caller either
806
- * establishes the active option straight after (`filter`, `#setActiveIndex`) or
807
- * is handling an option that cannot be the active one yet
808
- * (`optionTargetConnected`).
809
- */
810
1458
  #syncOptionSemanticsFor(option) {
811
1459
  const originalAttribute = _CommandPaletteController.#ORIGINAL_ARIA_DISABLED;
812
1460
  const originalValue = option.getAttribute(originalAttribute);
@@ -836,7 +1484,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
836
1484
  event.preventDefault();
837
1485
  this.toggle();
838
1486
  };
839
- /** Matches the configured `mod+key` or bare-key hotkey without extra modifiers. */
840
1487
  #matchesHotkey(event) {
841
1488
  const hotkey = this.#parseHotkey();
842
1489
  if (!hotkey || event.altKey || event.shiftKey) return false;
@@ -848,7 +1495,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
848
1495
  }
849
1496
  return event.key.toLowerCase() === hotkey.key;
850
1497
  }
851
- /** Parses the intentionally small public hotkey grammar. */
852
1498
  #parseHotkey() {
853
1499
  const parts = this.hotkeyValue.toLowerCase().split("+");
854
1500
  if (parts.length === 1 && parts[0] && parts[0] !== "mod") {
@@ -859,7 +1505,6 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
859
1505
  }
860
1506
  return null;
861
1507
  }
862
- /** Resets transient open state so reconnect starts from a predictable closed snapshot. */
863
1508
  #resetToClosedState() {
864
1509
  this.#activeId = null;
865
1510
  this.#activeOrder = [];
@@ -869,15 +1514,14 @@ var CommandPaletteController = class _CommandPaletteController extends Controlle
869
1514
  option.removeAttribute("data-active");
870
1515
  }
871
1516
  if (this.hasDialogTarget) {
872
- this.dialogTarget.hidden = true;
1517
+ this.#hidden.write(this.dialogTarget, "");
873
1518
  }
874
1519
  if (this.hasInputTarget) {
875
- this.inputTarget.setAttribute("aria-expanded", "false");
876
- this.inputTarget.removeAttribute("aria-activedescendant");
1520
+ this.#expanded.write(this.inputTarget, "false");
1521
+ this.#activeDescendant.write(this.inputTarget, null);
877
1522
  }
878
1523
  }
879
1524
  };
880
1525
 
881
1526
  export { CommandPaletteController };
882
- //# sourceMappingURL=command_palette_controller.js.map
883
1527
  //# sourceMappingURL=command_palette_controller.js.map