stimeo-ui 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +27 -11
  3. package/dist/cable/index.d.ts +88 -31
  4. package/dist/cable/index.js +384 -449
  5. package/dist/cable/index.js.map +1 -1
  6. package/dist/coerce-JtZVNG5h.d.ts +20 -0
  7. package/dist/controllers/accordion_controller.d.ts +4 -2
  8. package/dist/controllers/accordion_controller.js +174 -55
  9. package/dist/controllers/accordion_controller.js.map +1 -1
  10. package/dist/controllers/alert_dialog_controller.d.ts +12 -2
  11. package/dist/controllers/alert_dialog_controller.js +903 -235
  12. package/dist/controllers/alert_dialog_controller.js.map +1 -1
  13. package/dist/controllers/announcer_controller.d.ts +37 -5
  14. package/dist/controllers/announcer_controller.js +238 -205
  15. package/dist/controllers/announcer_controller.js.map +1 -1
  16. package/dist/controllers/aspect_ratio_controller.js +0 -10
  17. package/dist/controllers/aspect_ratio_controller.js.map +1 -1
  18. package/dist/controllers/auto_submit_controller.d.ts +30 -6
  19. package/dist/controllers/auto_submit_controller.js +414 -145
  20. package/dist/controllers/auto_submit_controller.js.map +1 -1
  21. package/dist/controllers/avatar_controller.js +185 -107
  22. package/dist/controllers/avatar_controller.js.map +1 -1
  23. package/dist/controllers/breadcrumb_controller.js +0 -130
  24. package/dist/controllers/breadcrumb_controller.js.map +1 -1
  25. package/dist/controllers/bulk_select_controller.d.ts +21 -3
  26. package/dist/controllers/bulk_select_controller.js +282 -58
  27. package/dist/controllers/bulk_select_controller.js.map +1 -1
  28. package/dist/controllers/calendar_controller.d.ts +38 -16
  29. package/dist/controllers/calendar_controller.js +228 -260
  30. package/dist/controllers/calendar_controller.js.map +1 -1
  31. package/dist/controllers/carousel_controller.d.ts +43 -12
  32. package/dist/controllers/carousel_controller.js +536 -436
  33. package/dist/controllers/carousel_controller.js.map +1 -1
  34. package/dist/controllers/character_counter_controller.d.ts +44 -7
  35. package/dist/controllers/character_counter_controller.js +493 -207
  36. package/dist/controllers/character_counter_controller.js.map +1 -1
  37. package/dist/controllers/checkbox_controller.d.ts +7 -2
  38. package/dist/controllers/checkbox_controller.js +38 -74
  39. package/dist/controllers/checkbox_controller.js.map +1 -1
  40. package/dist/controllers/clipboard_controller.d.ts +16 -5
  41. package/dist/controllers/clipboard_controller.js +261 -149
  42. package/dist/controllers/clipboard_controller.js.map +1 -1
  43. package/dist/controllers/collapsible_controller.d.ts +17 -5
  44. package/dist/controllers/collapsible_controller.js +304 -120
  45. package/dist/controllers/collapsible_controller.js.map +1 -1
  46. package/dist/controllers/color_picker_controller.d.ts +18 -6
  47. package/dist/controllers/color_picker_controller.js +178 -146
  48. package/dist/controllers/color_picker_controller.js.map +1 -1
  49. package/dist/controllers/combobox_controller.d.ts +24 -6
  50. package/dist/controllers/combobox_controller.js +522 -156
  51. package/dist/controllers/combobox_controller.js.map +1 -1
  52. package/dist/controllers/command_palette_controller.d.ts +51 -20
  53. package/dist/controllers/command_palette_controller.js +1156 -395
  54. package/dist/controllers/command_palette_controller.js.map +1 -1
  55. package/dist/controllers/conditional_fields_controller.d.ts +15 -2
  56. package/dist/controllers/conditional_fields_controller.js +300 -171
  57. package/dist/controllers/conditional_fields_controller.js.map +1 -1
  58. package/dist/controllers/confirm_controller.d.ts +49 -12
  59. package/dist/controllers/confirm_controller.js +980 -262
  60. package/dist/controllers/confirm_controller.js.map +1 -1
  61. package/dist/controllers/context_menu_controller.d.ts +80 -18
  62. package/dist/controllers/context_menu_controller.js +346 -153
  63. package/dist/controllers/context_menu_controller.js.map +1 -1
  64. package/dist/controllers/count_up_controller.d.ts +13 -1
  65. package/dist/controllers/count_up_controller.js +73 -21
  66. package/dist/controllers/count_up_controller.js.map +1 -1
  67. package/dist/controllers/countdown_controller.d.ts +47 -3
  68. package/dist/controllers/countdown_controller.js +281 -158
  69. package/dist/controllers/countdown_controller.js.map +1 -1
  70. package/dist/controllers/currency_input_controller.d.ts +93 -10
  71. package/dist/controllers/currency_input_controller.js +575 -225
  72. package/dist/controllers/currency_input_controller.js.map +1 -1
  73. package/dist/controllers/data_grid_controller.d.ts +25 -12
  74. package/dist/controllers/data_grid_controller.js +248 -214
  75. package/dist/controllers/data_grid_controller.js.map +1 -1
  76. package/dist/controllers/date_range_picker_controller.d.ts +42 -14
  77. package/dist/controllers/date_range_picker_controller.js +338 -235
  78. package/dist/controllers/date_range_picker_controller.js.map +1 -1
  79. package/dist/controllers/dialog_controller.d.ts +17 -6
  80. package/dist/controllers/dialog_controller.js +899 -237
  81. package/dist/controllers/dialog_controller.js.map +1 -1
  82. package/dist/controllers/direct_upload_controller.d.ts +25 -8
  83. package/dist/controllers/direct_upload_controller.js +97 -167
  84. package/dist/controllers/direct_upload_controller.js.map +1 -1
  85. package/dist/controllers/dirty_form_controller.d.ts +6 -1
  86. package/dist/controllers/dirty_form_controller.js +72 -69
  87. package/dist/controllers/dirty_form_controller.js.map +1 -1
  88. package/dist/controllers/dismissible_controller.d.ts +19 -0
  89. package/dist/controllers/dismissible_controller.js +196 -24
  90. package/dist/controllers/dismissible_controller.js.map +1 -1
  91. package/dist/controllers/drawer_controller.d.ts +39 -11
  92. package/dist/controllers/drawer_controller.js +974 -355
  93. package/dist/controllers/drawer_controller.js.map +1 -1
  94. package/dist/controllers/dropdown_controller.d.ts +31 -1
  95. package/dist/controllers/dropdown_controller.js +339 -96
  96. package/dist/controllers/dropdown_controller.js.map +1 -1
  97. package/dist/controllers/editable_controller.d.ts +12 -1
  98. package/dist/controllers/editable_controller.js +315 -88
  99. package/dist/controllers/editable_controller.js.map +1 -1
  100. package/dist/controllers/empty_state_controller.js +79 -58
  101. package/dist/controllers/empty_state_controller.js.map +1 -1
  102. package/dist/controllers/file_dropzone_controller.d.ts +63 -18
  103. package/dist/controllers/file_dropzone_controller.js +322 -211
  104. package/dist/controllers/file_dropzone_controller.js.map +1 -1
  105. package/dist/controllers/filter_controller.js +77 -42
  106. package/dist/controllers/filter_controller.js.map +1 -1
  107. package/dist/controllers/flash_controller.d.ts +35 -12
  108. package/dist/controllers/flash_controller.js +238 -347
  109. package/dist/controllers/flash_controller.js.map +1 -1
  110. package/dist/controllers/focus_controller.d.ts +15 -6
  111. package/dist/controllers/focus_controller.js +679 -240
  112. package/dist/controllers/focus_controller.js.map +1 -1
  113. package/dist/controllers/form_field_controller.js +156 -132
  114. package/dist/controllers/form_field_controller.js.map +1 -1
  115. package/dist/controllers/form_validation_controller.d.ts +3 -3
  116. package/dist/controllers/form_validation_controller.js +31 -97
  117. package/dist/controllers/form_validation_controller.js.map +1 -1
  118. package/dist/controllers/frame_loading_controller.d.ts +47 -14
  119. package/dist/controllers/frame_loading_controller.js +275 -217
  120. package/dist/controllers/frame_loading_controller.js.map +1 -1
  121. package/dist/controllers/highlight_controller.d.ts +27 -6
  122. package/dist/controllers/highlight_controller.js +107 -75
  123. package/dist/controllers/highlight_controller.js.map +1 -1
  124. package/dist/controllers/hover_card_controller.d.ts +60 -4
  125. package/dist/controllers/hover_card_controller.js +327 -130
  126. package/dist/controllers/hover_card_controller.js.map +1 -1
  127. package/dist/controllers/idle_controller.d.ts +48 -6
  128. package/dist/controllers/idle_controller.js +271 -101
  129. package/dist/controllers/idle_controller.js.map +1 -1
  130. package/dist/controllers/input_mask_controller.d.ts +30 -13
  131. package/dist/controllers/input_mask_controller.js +483 -97
  132. package/dist/controllers/input_mask_controller.js.map +1 -1
  133. package/dist/controllers/intersection_controller.d.ts +32 -13
  134. package/dist/controllers/intersection_controller.js +150 -106
  135. package/dist/controllers/intersection_controller.js.map +1 -1
  136. package/dist/controllers/lazy_frame_controller.d.ts +19 -2
  137. package/dist/controllers/lazy_frame_controller.js +24 -68
  138. package/dist/controllers/lazy_frame_controller.js.map +1 -1
  139. package/dist/controllers/listbox_controller.d.ts +44 -9
  140. package/dist/controllers/listbox_controller.js +523 -240
  141. package/dist/controllers/listbox_controller.js.map +1 -1
  142. package/dist/controllers/local_time_controller.d.ts +9 -7
  143. package/dist/controllers/local_time_controller.js +102 -77
  144. package/dist/controllers/local_time_controller.js.map +1 -1
  145. package/dist/controllers/masonry_controller.d.ts +11 -3
  146. package/dist/controllers/masonry_controller.js +80 -96
  147. package/dist/controllers/masonry_controller.js.map +1 -1
  148. package/dist/controllers/menu_controller.d.ts +55 -11
  149. package/dist/controllers/menu_controller.js +302 -188
  150. package/dist/controllers/menu_controller.js.map +1 -1
  151. package/dist/controllers/menubar_controller.d.ts +51 -9
  152. package/dist/controllers/menubar_controller.js +232 -345
  153. package/dist/controllers/menubar_controller.js.map +1 -1
  154. package/dist/controllers/meter_controller.d.ts +33 -20
  155. package/dist/controllers/meter_controller.js +417 -87
  156. package/dist/controllers/meter_controller.js.map +1 -1
  157. package/dist/controllers/multi_select_controller.d.ts +48 -18
  158. package/dist/controllers/multi_select_controller.js +785 -393
  159. package/dist/controllers/multi_select_controller.js.map +1 -1
  160. package/dist/controllers/navigation_menu_controller.d.ts +27 -6
  161. package/dist/controllers/navigation_menu_controller.js +190 -250
  162. package/dist/controllers/navigation_menu_controller.js.map +1 -1
  163. package/dist/controllers/nested_form_controller.d.ts +10 -0
  164. package/dist/controllers/nested_form_controller.js +396 -192
  165. package/dist/controllers/nested_form_controller.js.map +1 -1
  166. package/dist/controllers/network_status_controller.d.ts +23 -0
  167. package/dist/controllers/network_status_controller.js +268 -62
  168. package/dist/controllers/network_status_controller.js.map +1 -1
  169. package/dist/controllers/number_input_controller.d.ts +50 -8
  170. package/dist/controllers/number_input_controller.js +610 -303
  171. package/dist/controllers/number_input_controller.js.map +1 -1
  172. package/dist/controllers/optimistic_controller.d.ts +8 -5
  173. package/dist/controllers/optimistic_controller.js +45 -87
  174. package/dist/controllers/optimistic_controller.js.map +1 -1
  175. package/dist/controllers/otp_controller.d.ts +55 -8
  176. package/dist/controllers/otp_controller.js +658 -255
  177. package/dist/controllers/otp_controller.js.map +1 -1
  178. package/dist/controllers/overflow_indicator_controller.d.ts +5 -0
  179. package/dist/controllers/overflow_indicator_controller.js +122 -113
  180. package/dist/controllers/overflow_indicator_controller.js.map +1 -1
  181. package/dist/controllers/overflow_menu_controller.d.ts +58 -20
  182. package/dist/controllers/overflow_menu_controller.js +470 -295
  183. package/dist/controllers/overflow_menu_controller.js.map +1 -1
  184. package/dist/controllers/pagination_controller.d.ts +37 -7
  185. package/dist/controllers/pagination_controller.js +332 -123
  186. package/dist/controllers/pagination_controller.js.map +1 -1
  187. package/dist/controllers/password_reveal_controller.d.ts +46 -8
  188. package/dist/controllers/password_reveal_controller.js +392 -121
  189. package/dist/controllers/password_reveal_controller.js.map +1 -1
  190. package/dist/controllers/password_strength_controller.d.ts +15 -6
  191. package/dist/controllers/password_strength_controller.js +111 -158
  192. package/dist/controllers/password_strength_controller.js.map +1 -1
  193. package/dist/controllers/persist_controller.d.ts +11 -4
  194. package/dist/controllers/persist_controller.js +165 -111
  195. package/dist/controllers/persist_controller.js.map +1 -1
  196. package/dist/controllers/pointer_drag_controller.d.ts +35 -12
  197. package/dist/controllers/pointer_drag_controller.js +434 -188
  198. package/dist/controllers/pointer_drag_controller.js.map +1 -1
  199. package/dist/controllers/popover_controller.d.ts +41 -5
  200. package/dist/controllers/popover_controller.js +269 -113
  201. package/dist/controllers/popover_controller.js.map +1 -1
  202. package/dist/controllers/portal_controller.d.ts +44 -4
  203. package/dist/controllers/portal_controller.js +215 -76
  204. package/dist/controllers/portal_controller.js.map +1 -1
  205. package/dist/controllers/preview_guard_controller.d.ts +10 -4
  206. package/dist/controllers/preview_guard_controller.js +243 -116
  207. package/dist/controllers/preview_guard_controller.js.map +1 -1
  208. package/dist/controllers/progress_controller.d.ts +20 -19
  209. package/dist/controllers/progress_controller.js +391 -78
  210. package/dist/controllers/progress_controller.js.map +1 -1
  211. package/dist/controllers/radio_group_controller.d.ts +21 -9
  212. package/dist/controllers/radio_group_controller.js +278 -211
  213. package/dist/controllers/radio_group_controller.js.map +1 -1
  214. package/dist/controllers/range_slider_controller.d.ts +47 -6
  215. package/dist/controllers/range_slider_controller.js +220 -113
  216. package/dist/controllers/range_slider_controller.js.map +1 -1
  217. package/dist/controllers/rating_controller.d.ts +20 -4
  218. package/dist/controllers/rating_controller.js +458 -211
  219. package/dist/controllers/rating_controller.js.map +1 -1
  220. package/dist/controllers/read_more_controller.d.ts +2 -0
  221. package/dist/controllers/read_more_controller.js +138 -63
  222. package/dist/controllers/read_more_controller.js.map +1 -1
  223. package/dist/controllers/reading_progress_controller.d.ts +1 -1
  224. package/dist/controllers/reading_progress_controller.js +166 -137
  225. package/dist/controllers/reading_progress_controller.js.map +1 -1
  226. package/dist/controllers/relative_time_controller.d.ts +20 -6
  227. package/dist/controllers/relative_time_controller.js +211 -112
  228. package/dist/controllers/relative_time_controller.js.map +1 -1
  229. package/dist/controllers/reset_on_restore_controller.d.ts +85 -0
  230. package/dist/controllers/{reset_before_cache_controller.js → reset_on_restore_controller.js} +103 -38
  231. package/dist/controllers/reset_on_restore_controller.js.map +1 -0
  232. package/dist/controllers/resizable_controller.d.ts +31 -2
  233. package/dist/controllers/resizable_controller.js +299 -150
  234. package/dist/controllers/resizable_controller.js.map +1 -1
  235. package/dist/controllers/roving_controller.d.ts +3 -3
  236. package/dist/controllers/roving_controller.js +49 -88
  237. package/dist/controllers/roving_controller.js.map +1 -1
  238. package/dist/controllers/scroll_area_controller.d.ts +7 -3
  239. package/dist/controllers/scroll_area_controller.js +490 -246
  240. package/dist/controllers/scroll_area_controller.js.map +1 -1
  241. package/dist/controllers/scroll_restore_controller.d.ts +3 -3
  242. package/dist/controllers/scroll_restore_controller.js +14 -55
  243. package/dist/controllers/scroll_restore_controller.js.map +1 -1
  244. package/dist/controllers/scroll_visibility_controller.d.ts +28 -12
  245. package/dist/controllers/scroll_visibility_controller.js +432 -149
  246. package/dist/controllers/scroll_visibility_controller.js.map +1 -1
  247. package/dist/controllers/scrollspy_controller.d.ts +6 -1
  248. package/dist/controllers/scrollspy_controller.js +193 -248
  249. package/dist/controllers/scrollspy_controller.js.map +1 -1
  250. package/dist/controllers/separator_controller.d.ts +20 -5
  251. package/dist/controllers/separator_controller.js +285 -142
  252. package/dist/controllers/separator_controller.js.map +1 -1
  253. package/dist/controllers/sidebar_controller.d.ts +65 -8
  254. package/dist/controllers/sidebar_controller.js +1069 -404
  255. package/dist/controllers/sidebar_controller.js.map +1 -1
  256. package/dist/controllers/skeleton_controller.d.ts +23 -0
  257. package/dist/controllers/skeleton_controller.js +259 -101
  258. package/dist/controllers/skeleton_controller.js.map +1 -1
  259. package/dist/controllers/slider_controller.d.ts +29 -2
  260. package/dist/controllers/slider_controller.js +187 -86
  261. package/dist/controllers/slider_controller.js.map +1 -1
  262. package/dist/controllers/smart_sticky_header_controller.d.ts +22 -4
  263. package/dist/controllers/smart_sticky_header_controller.js +173 -66
  264. package/dist/controllers/smart_sticky_header_controller.js.map +1 -1
  265. package/dist/controllers/sortable_controller.d.ts +4 -4
  266. package/dist/controllers/sortable_controller.js +26 -125
  267. package/dist/controllers/sortable_controller.js.map +1 -1
  268. package/dist/controllers/spinner_controller.d.ts +39 -4
  269. package/dist/controllers/spinner_controller.js +288 -173
  270. package/dist/controllers/spinner_controller.js.map +1 -1
  271. package/dist/controllers/step_indicator_controller.d.ts +9 -4
  272. package/dist/controllers/step_indicator_controller.js +105 -46
  273. package/dist/controllers/step_indicator_controller.js.map +1 -1
  274. package/dist/controllers/stepper_controller.d.ts +14 -6
  275. package/dist/controllers/stepper_controller.js +120 -73
  276. package/dist/controllers/stepper_controller.js.map +1 -1
  277. package/dist/controllers/stick_to_bottom_controller.d.ts +9 -1
  278. package/dist/controllers/stick_to_bottom_controller.js +275 -140
  279. package/dist/controllers/stick_to_bottom_controller.js.map +1 -1
  280. package/dist/controllers/sticky_observer_controller.d.ts +11 -0
  281. package/dist/controllers/sticky_observer_controller.js +220 -49
  282. package/dist/controllers/sticky_observer_controller.js.map +1 -1
  283. package/dist/controllers/submit_once_controller.d.ts +24 -8
  284. package/dist/controllers/submit_once_controller.js +376 -227
  285. package/dist/controllers/submit_once_controller.js.map +1 -1
  286. package/dist/controllers/switch_controller.d.ts +18 -4
  287. package/dist/controllers/switch_controller.js +201 -84
  288. package/dist/controllers/switch_controller.js.map +1 -1
  289. package/dist/controllers/tabs_controller.d.ts +9 -6
  290. package/dist/controllers/tabs_controller.js +41 -30
  291. package/dist/controllers/tabs_controller.js.map +1 -1
  292. package/dist/controllers/tags_input_controller.d.ts +16 -3
  293. package/dist/controllers/tags_input_controller.js +293 -173
  294. package/dist/controllers/tags_input_controller.js.map +1 -1
  295. package/dist/controllers/textarea_autosize_controller.d.ts +11 -1
  296. package/dist/controllers/textarea_autosize_controller.js +108 -74
  297. package/dist/controllers/textarea_autosize_controller.js.map +1 -1
  298. package/dist/controllers/theme_controller.d.ts +24 -11
  299. package/dist/controllers/theme_controller.js +140 -116
  300. package/dist/controllers/theme_controller.js.map +1 -1
  301. package/dist/controllers/time_picker_controller.d.ts +55 -5
  302. package/dist/controllers/time_picker_controller.js +331 -127
  303. package/dist/controllers/time_picker_controller.js.map +1 -1
  304. package/dist/controllers/toast_controller.d.ts +40 -16
  305. package/dist/controllers/toast_controller.js +244 -324
  306. package/dist/controllers/toast_controller.js.map +1 -1
  307. package/dist/controllers/toggle_group_controller.d.ts +14 -6
  308. package/dist/controllers/toggle_group_controller.js +252 -191
  309. package/dist/controllers/toggle_group_controller.js.map +1 -1
  310. package/dist/controllers/toolbar_controller.d.ts +8 -6
  311. package/dist/controllers/toolbar_controller.js +219 -120
  312. package/dist/controllers/toolbar_controller.js.map +1 -1
  313. package/dist/controllers/tooltip_controller.d.ts +49 -4
  314. package/dist/controllers/tooltip_controller.js +158 -124
  315. package/dist/controllers/tooltip_controller.js.map +1 -1
  316. package/dist/controllers/transition_controller.d.ts +22 -4
  317. package/dist/controllers/transition_controller.js +123 -119
  318. package/dist/controllers/transition_controller.js.map +1 -1
  319. package/dist/controllers/tree_view_controller.d.ts +22 -12
  320. package/dist/controllers/tree_view_controller.js +283 -369
  321. package/dist/controllers/tree_view_controller.js.map +1 -1
  322. package/dist/index.d.ts +10 -5
  323. package/dist/index.js +8573 -10410
  324. package/dist/index.js.map +1 -1
  325. package/dist/inspector/cli.d.ts +38 -14
  326. package/dist/inspector/cli.js +108 -40
  327. package/dist/inspector/cli.js.map +1 -1
  328. package/dist/inspector/cli_bin.js +145 -64
  329. package/dist/inspector/cli_bin.js.map +1 -1
  330. package/dist/inspector/examples.json +20 -20
  331. package/dist/inspector/manifest.json +802 -101
  332. package/dist/positioning/index.d.ts +15 -7
  333. package/dist/positioning/index.js +154 -47
  334. package/dist/positioning/index.js.map +1 -1
  335. package/package.json +11 -10
  336. package/dist/controllers/reset_before_cache_controller.d.ts +0 -76
  337. package/dist/controllers/reset_before_cache_controller.js.map +0 -1
@@ -1,14 +1,185 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/menu_controller.ts
3
+ function isAriaDisabled(element) {
4
+ for (let current = element; current; current = current.parentElement) {
5
+ if (current.getAttribute("aria-disabled") === "true") return true;
6
+ }
7
+ return false;
8
+ }
4
9
 
5
- // src/utils/arrow_step.ts
6
10
  function isReservedArrowChord(event, allow = []) {
7
11
  if (!event.key.startsWith("Arrow")) return false;
8
12
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
9
13
  }
14
+ function hasModifierChord(event) {
15
+ return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
16
+ }
17
+
18
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
19
+ if (element.getAttribute(name) === value) return false;
20
+ if (value === null) element.removeAttribute(name);
21
+ else element.setAttribute(name, value);
22
+ return true;
23
+ }
24
+
25
+ function sharedRegistry(key, create) {
26
+ const symbol = Symbol.for(key);
27
+ const scope = globalThis;
28
+ const existing = scope[symbol];
29
+ if (existing !== void 0) return existing;
30
+ const registry2 = create();
31
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
32
+ return registry2;
33
+ }
34
+
35
+ var registry = sharedRegistry(
36
+ "stimeo-ui.attribute-lease.registry.v1",
37
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
38
+ );
39
+ function isAttributeValue(value) {
40
+ return value === null || typeof value === "string";
41
+ }
42
+ function readLeaseRecord(element, record) {
43
+ try {
44
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
45
+ if (!Array.isArray(parsed)) return void 0;
46
+ const [original, written, ...rest] = parsed;
47
+ const linked = rest[0] !== false;
48
+ const beneath = linked ? rest : rest.slice(1);
49
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
50
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
51
+ } catch {
52
+ return void 0;
53
+ }
54
+ }
55
+ function attributeStack(element, attribute) {
56
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
57
+ registry.stacks.set(element, byAttribute);
58
+ const known = byAttribute.get(attribute);
59
+ if (known) return known;
60
+ const found = [];
61
+ for (const record of registry.names.get(attribute) ?? []) {
62
+ const read = readLeaseRecord(element, record);
63
+ if (read) found.push({ ...read, record });
64
+ }
65
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
66
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
67
+ lease: null,
68
+ record,
69
+ written,
70
+ original,
71
+ linked: linked && index > 0
72
+ }));
73
+ byAttribute.set(attribute, stack);
74
+ return stack;
75
+ }
76
+ function findHold(stack, lease, record) {
77
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
78
+ }
79
+ var AttributeLease = class {
80
+ #attribute;
81
+ #record;
82
+ #base;
83
+ #preserveAuthor;
84
+ #held = /* @__PURE__ */ new Set();
85
+ constructor(attribute, owner, options = {}) {
86
+ this.#attribute = attribute;
87
+ this.#record = `data-${owner}-${attribute}-lease`;
88
+ this.#base = options.base === true;
89
+ this.#preserveAuthor = options.preserveAuthor === true;
90
+ const names = registry.names;
91
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
92
+ }
93
+ write(element, value) {
94
+ const stack = attributeStack(element, this.#attribute);
95
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
96
+ this.#held.add(element);
97
+ const shows = !this.#covered(element, stack, hold);
98
+ hold.written = value;
99
+ this.#mark(element, stack);
100
+ if (shows) this.#reflect(element, value);
101
+ }
102
+ return(element) {
103
+ const held = this.#held.delete(element);
104
+ const known = registry.stacks.get(element)?.has(this.#attribute);
105
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
106
+ const hold = this.#take(stack);
107
+ if (!hold) return false;
108
+ const carries = element.getAttribute(this.#attribute) === hold.written;
109
+ const owns = carries && !this.#covered(element, stack, hold);
110
+ const index = stack.indexOf(hold);
111
+ stack.splice(index, 1);
112
+ const above = stack[index];
113
+ if (above) above.linked &&= hold.linked;
114
+ this.#mark(element, stack);
115
+ if (!owns) return true;
116
+ const below = hold.linked ? stack[index - 1] : void 0;
117
+ this.#reflect(element, below ? below.written : hold.original);
118
+ return true;
119
+ }
120
+ returnAll() {
121
+ for (const element of Array.from(this.#held)) this.return(element);
122
+ }
123
+ #take(stack) {
124
+ const hold = findHold(stack, this, this.#record);
125
+ if (hold) hold.lease = this;
126
+ return hold;
127
+ }
128
+ #acquire(element, stack) {
129
+ const current = element.getAttribute(this.#attribute);
130
+ const top = stack.at(-1);
131
+ const bottom = stack[0];
132
+ const over = top?.written === current ? top : void 0;
133
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
134
+ const hold = {
135
+ lease: this,
136
+ record: this.#record,
137
+ written: current,
138
+ original: from ? from.original : current,
139
+ linked: !this.#base && over !== void 0
140
+ };
141
+ if (this.#base && bottom) {
142
+ bottom.linked = true;
143
+ stack.unshift(hold);
144
+ } else stack.push(hold);
145
+ return hold;
146
+ }
147
+ #covered(element, stack, hold) {
148
+ const current = element.getAttribute(this.#attribute);
149
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
150
+ }
151
+ #mark(element, stack) {
152
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
153
+ stack.forEach((hold, index) => {
154
+ const names = stack.slice(0, index).map((under) => under.record);
155
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
156
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
157
+ records.set(
158
+ hold.record,
159
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
160
+ );
161
+ });
162
+ for (const [name, recorded] of records) {
163
+ setAttributeIfChanged(element, name, recorded);
164
+ }
165
+ }
166
+ #reflect(element, value) {
167
+ setAttributeIfChanged(element, this.#attribute, value);
168
+ }
169
+ };
170
+
171
+ var ClickVerdicts = class {
172
+ #verdicts = /* @__PURE__ */ new WeakMap();
173
+ record(event, entries, refused) {
174
+ this.#verdicts.set(event, { entries, refused });
175
+ }
176
+ for(event, entry) {
177
+ const verdict = event && this.#verdicts.get(event);
178
+ if (!verdict?.entries.includes(entry)) return void 0;
179
+ return verdict.refused;
180
+ }
181
+ };
10
182
 
11
- // src/utils/escape_layer.ts
12
183
  function claimsWhileFocusWithin(element) {
13
184
  return () => {
14
185
  const active = element.ownerDocument.activeElement;
@@ -16,89 +187,68 @@ function claimsWhileFocusWithin(element) {
16
187
  };
17
188
  }
18
189
  var EscapeLayer = class _EscapeLayer {
19
- static #registries = /* @__PURE__ */ new WeakMap();
20
- #ownerDocument = null;
21
- /** Dismissal callback while active; `null` when inactive. */
22
- #onDismiss = null;
23
- /** Live predicate deciding whether the layer claims a press; `null` = always. */
24
- #claims = null;
25
- /**
26
- * Activates this layer at the top of its document's Escape stack, installing
27
- * the document's shared resolver listener if this is its first layer.
28
- * Re-activating an already-active layer moves it to the top.
29
- */
190
+ static #registries = sharedRegistry(
191
+ "stimeo-ui.escape-layer.registry.v1",
192
+ () => /* @__PURE__ */ new WeakMap()
193
+ );
194
+ #active = null;
30
195
  activate(ownerDocument = document, options) {
31
196
  this.deactivate();
32
- let registry = _EscapeLayer.#registries.get(ownerDocument);
33
- if (!registry) {
34
- registry = _EscapeLayer.#createRegistry();
35
- _EscapeLayer.#registries.set(ownerDocument, registry);
36
- ownerDocument.addEventListener("keydown", registry.onKeydown);
197
+ let registry2 = _EscapeLayer.#registries.get(ownerDocument);
198
+ if (!registry2) {
199
+ registry2 = _EscapeLayer.#createRegistry();
200
+ _EscapeLayer.#registries.set(ownerDocument, registry2);
201
+ ownerDocument.addEventListener("keydown", registry2.onKeydown);
37
202
  }
38
- registry.stack.push(this);
39
- this.#ownerDocument = ownerDocument;
40
- this.#onDismiss = options.onDismiss;
41
- this.#claims = options.claims ?? null;
42
- }
43
- /**
44
- * Removes this layer from its document's Escape stack, uninstalling the
45
- * shared listener when the stack empties. Safe to call when inactive.
46
- */
203
+ const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
204
+ registry2.stack.push(entry);
205
+ this.#active = { document: ownerDocument, entry };
206
+ }
47
207
  deactivate() {
48
- const ownerDocument = this.#ownerDocument;
49
- if (!ownerDocument) return;
50
- const registry = _EscapeLayer.#registries.get(ownerDocument);
51
- if (registry) {
52
- const index = registry.stack.lastIndexOf(this);
53
- if (index >= 0) registry.stack.splice(index, 1);
54
- if (registry.stack.length === 0) {
55
- ownerDocument.removeEventListener("keydown", registry.onKeydown);
56
- _EscapeLayer.#registries.delete(ownerDocument);
208
+ const active = this.#active;
209
+ if (!active) return;
210
+ const registry2 = _EscapeLayer.#registries.get(active.document);
211
+ if (registry2) {
212
+ const index = registry2.stack.lastIndexOf(active.entry);
213
+ if (index >= 0) registry2.stack.splice(index, 1);
214
+ if (registry2.stack.length === 0) {
215
+ active.document.removeEventListener("keydown", registry2.onKeydown);
216
+ _EscapeLayer.#registries.delete(active.document);
57
217
  }
58
218
  }
59
- this.#ownerDocument = null;
60
- this.#onDismiss = null;
61
- this.#claims = null;
62
- }
63
- /**
64
- * Whether this active layer would own a press right now: it is the topmost
65
- * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
66
- * and diagnostics — production dismissal goes through the shared listener.
67
- */
219
+ this.#active = null;
220
+ }
68
221
  get ownsEscape() {
69
- const ownerDocument = this.#ownerDocument;
70
- if (!ownerDocument) return false;
71
- const registry = _EscapeLayer.#registries.get(ownerDocument);
72
- if (!registry) return false;
73
- return _EscapeLayer.#resolveOwner(registry.stack) === this;
222
+ const active = this.#active;
223
+ if (!active) return false;
224
+ const registry2 = _EscapeLayer.#registries.get(active.document);
225
+ if (!registry2) return false;
226
+ return _EscapeLayer.#resolveOwner(registry2.stack) === active.entry;
74
227
  }
75
- /** Builds a document's registry with its shared resolver listener. */
76
228
  static #createRegistry() {
77
- const registry = {
229
+ const registry2 = {
78
230
  stack: [],
79
231
  onKeydown: (event) => {
80
232
  if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
81
- const owner = _EscapeLayer.#resolveOwner(registry.stack);
233
+ const owner = _EscapeLayer.#resolveOwner(registry2.stack);
82
234
  if (!owner) return;
83
235
  event.preventDefault();
84
- owner.#onDismiss?.();
236
+ owner.onDismiss();
85
237
  }
86
238
  };
87
- return registry;
239
+ return registry2;
88
240
  }
89
- /** The topmost stack layer whose claims predicate passes, or `null`. */
90
241
  static #resolveOwner(stack) {
91
242
  for (let index = stack.length - 1; index >= 0; index--) {
92
243
  const layer = stack[index];
93
244
  if (!layer) continue;
94
- if (layer.#claims && !layer.#claims()) continue;
245
+ if (layer.claims && !layer.claims()) continue;
95
246
  return layer;
96
247
  }
97
248
  return null;
98
249
  }
99
250
  };
100
251
 
101
- // src/utils/event_owner.ts
102
252
  function ownerIndex(candidates, node) {
103
253
  if (!(node instanceof Node)) return -1;
104
254
  return candidates.findIndex((candidate) => candidate.contains(node));
@@ -106,44 +256,40 @@ function ownerIndex(candidates, node) {
106
256
  function ownerOf(candidates, node) {
107
257
  return candidates[ownerIndex(candidates, node)] ?? null;
108
258
  }
259
+ function ownersOf(candidates, node) {
260
+ if (!(node instanceof Node)) return [];
261
+ return candidates.filter((candidate) => candidate.contains(node));
262
+ }
263
+
264
+ var ListenerSet = class {
265
+ #abort = new AbortController();
266
+ add(target, type, handler, options) {
267
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
268
+ }
269
+ dispose() {
270
+ this.#abort.abort();
271
+ this.#abort = new AbortController();
272
+ }
273
+ };
109
274
 
110
- // src/utils/safe_timeout.ts
111
275
  var TimerRegistry = class {
112
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
113
276
  ids = /* @__PURE__ */ new Set();
114
- /**
115
- * Cancels a single tracked timer.
116
- *
117
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
118
- * registry), so callers can clear defensively without guarding.
119
- */
120
277
  clear(id) {
121
278
  if (this.ids.delete(id)) {
122
279
  this.cancel(id);
123
280
  }
124
281
  }
125
- /**
126
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
127
- * to guarantee no timer outlives the element.
128
- */
129
282
  clearAll() {
130
283
  for (const id of this.ids) {
131
284
  this.cancel(id);
132
285
  }
133
286
  this.ids.clear();
134
287
  }
135
- /** Number of timers currently tracked (pending). */
136
288
  get size() {
137
289
  return this.ids.size;
138
290
  }
139
291
  };
140
292
  var SafeTimeout = class extends TimerRegistry {
141
- /**
142
- * Schedules `callback` after `delay` ms and returns the timer id.
143
- *
144
- * The id is removed from the registry automatically when the timeout fires,
145
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
146
- */
147
293
  set(callback, delay) {
148
294
  const id = this.schedule(() => {
149
295
  this.ids.delete(id);
@@ -160,7 +306,6 @@ var SafeTimeout = class extends TimerRegistry {
160
306
  }
161
307
  };
162
308
 
163
- // src/utils/state_reason.ts
164
309
  var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
165
310
  var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
166
311
  function stateReasonFor(event) {
@@ -170,7 +315,6 @@ function stateReasonFor(event) {
170
315
  return "user";
171
316
  }
172
317
 
173
- // src/controllers/menu_controller.ts
174
318
  var MenuController = class extends Controller {
175
319
  static targets = ["trigger", "menu", "item"];
176
320
  static actions = [
@@ -183,38 +327,46 @@ var MenuController = class extends Controller {
183
327
  ];
184
328
  static events = ["close", "open"];
185
329
  #timers = new SafeTimeout();
186
- /** Escape-stack membership while open; the shared resolver dismisses via it. */
187
330
  #escapeLayer = new EscapeLayer();
188
- /**
189
- * The item a click started on, recorded in the capture pass. Read once by the
190
- * delegated bubble listener, which must not re-derive it: consumer handlers run
191
- * in between and may detach the item.
192
- */
331
+ #menuHidden = new AttributeLease("hidden", this.identifier);
332
+ #expanded = new AttributeLease("aria-expanded", this.identifier);
333
+ #menu = null;
193
334
  #clickOwner = null;
194
- /** Clicks already turned into an activation, so the two paths run it once. */
335
+ #clickVerdicts = new ClickVerdicts();
195
336
  #activated = /* @__PURE__ */ new WeakSet();
196
- /** Whether state moves are reported: set once `connect()` settled the baseline. */
337
+ #connected = false;
197
338
  #reporting = false;
198
- /** Starts closed and registers activation / outside-click listeners. */
339
+ #listeners = new ListenerSet();
199
340
  connect() {
200
341
  this.#close("api");
201
- this.element.addEventListener("click", this.#onItemClickCapture, true);
202
- this.element.addEventListener("click", this.#onDelegatedItemClick);
203
- this.element.addEventListener("keydown", this.#onDelegatedItemKeydown);
204
- document.addEventListener("click", this.#onOutsideClick, true);
342
+ this.#listeners.add(this.element, "click", this.#onItemClickCapture, { capture: true });
343
+ this.#listeners.add(this.element, "click", this.#onDelegatedItemClick);
344
+ this.#listeners.add(this.element, "keydown", this.#onDelegatedItemKeydown);
345
+ this.#listeners.add(document, "click", this.#onOutsideClick, { capture: true });
346
+ this.#connected = true;
205
347
  this.#reporting = true;
206
348
  }
207
- /** Releases the listeners, stack membership, and any pending Tab-close task. */
208
349
  disconnect() {
350
+ this.#connected = false;
209
351
  this.#reporting = false;
210
352
  this.#timers.clearAll();
211
353
  this.#escapeLayer.deactivate();
212
- this.element.removeEventListener("click", this.#onItemClickCapture, true);
213
- this.element.removeEventListener("click", this.#onDelegatedItemClick);
214
- this.element.removeEventListener("keydown", this.#onDelegatedItemKeydown);
215
- document.removeEventListener("click", this.#onOutsideClick, true);
354
+ this.#listeners.dispose();
355
+ }
356
+ triggerTargetConnected() {
357
+ if (this.#connected) this.#reflectTrigger();
358
+ }
359
+ triggerTargetDisconnected(trigger) {
360
+ if (!this.triggerTargets.includes(trigger)) this.#expanded.return(trigger);
361
+ if (this.#connected) this.#reflectTrigger();
362
+ }
363
+ menuTargetConnected() {
364
+ this.#adoptMenu();
365
+ }
366
+ menuTargetDisconnected(menu) {
367
+ this.#adoptMenu();
368
+ if (!this.menuTargets.includes(menu)) this.#menuHidden.return(menu);
216
369
  }
217
- /** Toggles the menu open/closed. Bound via `data-action` (click). */
218
370
  toggle(event) {
219
371
  if (this.#isOpen) {
220
372
  this.close(event);
@@ -223,45 +375,52 @@ var MenuController = class extends Controller {
223
375
  this.#focusFirst();
224
376
  }
225
377
  }
226
- /** Opens the menu and reflects the expanded state on the trigger. */
227
378
  open(event) {
228
379
  this.#open(stateReasonFor(event));
229
380
  }
230
- /** Closes the menu and reflects the collapsed state on the trigger. */
231
381
  close(event) {
232
382
  this.#close(stateReasonFor(event));
233
383
  }
234
- /** Opens the menu, reflects the expanded state, and reports a move. */
235
384
  #open(reason) {
236
385
  this.#timers.clearAll();
237
386
  if (!this.hasMenuTarget) return;
387
+ const menu = this.menuTarget;
238
388
  const was = this.#isOpen;
239
389
  this.#escapeLayer.activate(document, {
240
390
  onDismiss: () => this.#closeAndRestore("escape"),
241
391
  claims: claimsWhileFocusWithin(this.element)
242
392
  });
243
- this.menuTarget.hidden = false;
244
- if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", "true");
393
+ this.#menuHidden.write(menu, null);
394
+ this.#menu = menu;
395
+ if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, "true");
245
396
  if (!was && this.#reporting) this.dispatch("open", { detail: { reason }, cancelable: false });
246
397
  }
247
- /** Closes the menu, reflects the collapsed state, and reports a move. */
248
398
  #close(reason) {
249
399
  this.#timers.clearAll();
250
400
  this.#escapeLayer.deactivate();
251
- if (!this.hasMenuTarget) return;
401
+ const menu = this.hasMenuTarget ? this.menuTarget : null;
402
+ this.#menu = menu;
403
+ if (!menu) return;
252
404
  const was = this.#isOpen;
253
- this.menuTarget.hidden = true;
254
- if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", "false");
405
+ this.#menuHidden.write(menu, "");
406
+ if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, "false");
255
407
  if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
256
408
  }
257
- /**
258
- * Opens the menu with the keyboard per the APG (Down → first, Up → last).
259
- *
260
- * Enter/Space are intentionally not handled here: on a native `<button>`
261
- * trigger the browser turns them into a click, which already runs
262
- * {@link toggle} (open + focus first item). Handling them again here would
263
- * open and then immediately re-toggle the menu.
264
- */
409
+ #adoptMenu() {
410
+ if (!this.#connected) return;
411
+ const menu = this.hasMenuTarget ? this.menuTarget : null;
412
+ const previous = this.#menu;
413
+ this.#menu = menu;
414
+ if (!menu) {
415
+ this.#escapeLayer.deactivate();
416
+ } else if (menu !== previous) {
417
+ this.#menuHidden.write(menu, previous?.hidden ?? true ? "" : null);
418
+ }
419
+ this.#reflectTrigger();
420
+ }
421
+ #reflectTrigger() {
422
+ if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, String(this.#isOpen));
423
+ }
265
424
  onTriggerKeydown(event) {
266
425
  if (event.defaultPrevented) return;
267
426
  if (isReservedArrowChord(event)) return;
@@ -275,18 +434,13 @@ var MenuController = class extends Controller {
275
434
  this.#focusLast();
276
435
  }
277
436
  }
278
- /** Implements roving focus and closing keys inside the menu. */
279
437
  onItemKeydown(event) {
280
438
  this.#handleItemKeydown(event, event.currentTarget);
281
439
  }
282
- /**
283
- * The roving-focus body, shared by the per-element action and its delegated
284
- * twin. `from` is the item the key belongs to: the bound element for the
285
- * action, the resolved owner of the event target for the delegate.
286
- */
287
440
  #handleItemKeydown(event, from) {
288
441
  if (event.defaultPrevented) return;
289
442
  if (isReservedArrowChord(event)) return;
443
+ if ((event.key === "Home" || event.key === "End") && hasModifierChord(event)) return;
290
444
  const items = this.#navigableItems;
291
445
  const currentIndex = items.indexOf(from);
292
446
  switch (event.key) {
@@ -312,120 +466,80 @@ var MenuController = class extends Controller {
312
466
  break;
313
467
  }
314
468
  }
315
- /**
316
- * Closes the menu after an item is activated. Bound via `data-action`, and
317
- * also reached from the delegated listener.
318
- *
319
- * Markup that carries the per-element action *and* gets the delegate would run
320
- * this twice for one gesture. `close()` writes the state hooks (`hidden`,
321
- * `aria-expanded`), and an identical reassign still queues a MutationRecord,
322
- * so a second pass is observable to anyone watching them. The event is
323
- * therefore claimed: the path that gets there first does the work, the other
324
- * one finds it claimed and returns. A programmatic call with no event always
325
- * runs.
326
- */
327
469
  activate(event) {
328
- if (event !== void 0) {
329
- if (this.#activated.has(event)) return;
330
- this.#activated.add(event);
331
- }
470
+ if (event === void 0) this.#closeAndRestore("select");
471
+ else this.#activateItem(this.#itemFrom(event.target), event);
472
+ }
473
+ #activateItem(item, event) {
474
+ if (item !== null && this.#refuses(item, event)) return;
475
+ if (this.#activated.has(event)) return;
476
+ this.#activated.add(event);
332
477
  this.#closeAndRestore("select");
333
478
  }
334
- /** Closes and returns focus to the trigger (Escape / item-activation path). */
479
+ #refuses(item, event) {
480
+ const recorded = this.#clickVerdicts.for(event, item);
481
+ if (recorded !== void 0) return recorded;
482
+ return ownersOf(this.itemTargets, event.target).some(
483
+ (owner) => this.#isActivationDisabled(owner)
484
+ );
485
+ }
335
486
  #closeAndRestore(reason) {
336
487
  this.#close(reason);
337
488
  if (this.hasTriggerTarget) this.triggerTarget.focus();
338
489
  }
339
- /** Closes the menu when a click lands outside the controller's element. */
340
490
  #onOutsideClick = (event) => {
341
491
  if (this.#isOpen && !this.element.contains(event.target)) this.#close("outside");
342
492
  };
343
- /**
344
- * Delegated twin of {@link onItemKeydown}, bound on the controller element.
345
- *
346
- * An item that arrives *after* connect — `stimeo--overflow-menu` moves toolbar controls
347
- * into this menu at runtime — carries whatever `data-action` its author wrote,
348
- * and that is exactly the binding a consumer forgets, because the markup that
349
- * declares the item lives nowhere near the menu. Listening on the container
350
- * makes membership in the `item` target enough to be operable. The per-element
351
- * action stays supported and is not double-handled: see
352
- * {@link #handleItemKeydown}.
353
- */
354
493
  #onDelegatedItemKeydown = (event) => {
355
494
  const item = this.#itemFrom(event.target);
356
495
  if (item === null) return;
357
496
  this.#handleItemKeydown(event, item);
358
497
  };
359
- /**
360
- * Delegated twin of {@link activate}, for the same reason as the keydown one.
361
- *
362
- * It works from the item the capture pass recorded, not from a fresh lookup:
363
- * by the time a click bubbles up here, a consumer handler may already have
364
- * detached the row it lives in, and `itemTargets` is a live query that would
365
- * then resolve nothing — the activation would be lost and the menu left open
366
- * over an item that no longer exists. `aria-disabled` items never reach this
367
- * listener at all: the capture pass stops the click outright.
368
- */
369
498
  #onDelegatedItemClick = (event) => {
370
499
  const item = this.#clickOwner;
371
500
  this.#clickOwner = null;
372
501
  if (item === null) return;
373
- this.activate(event);
502
+ this.#activateItem(item, event);
374
503
  };
375
- /** The item target that is, or contains, `node`; `null` when it is neither. */
376
504
  #itemFrom(node) {
377
505
  return ownerOf(this.itemTargets, node);
378
506
  }
379
- /**
380
- * First look at every click inside the controller, and the only point at which
381
- * the DOM is still the one the user clicked. Two jobs: record which item owns
382
- * the click for the delegate above, and stop an `aria-disabled` command before
383
- * it reaches consumer handlers (native Enter/Space activation synthesizes a
384
- * click and is blocked here too).
385
- */
386
507
  #onItemClickCapture = (event) => {
387
- const target = event.target;
388
- if (!(target instanceof Node)) {
389
- this.#clickOwner = null;
390
- return;
391
- }
392
- const item = this.#itemFrom(target);
393
- this.#clickOwner = item;
394
- if (item === null || item.getAttribute("aria-disabled") !== "true") return;
508
+ const items = ownersOf(this.itemTargets, event.target);
509
+ this.#clickOwner = items[0] ?? null;
510
+ this.#clickVerdicts.record(
511
+ event,
512
+ items,
513
+ items.some((item) => this.#isActivationDisabled(item))
514
+ );
515
+ if (!items.some((item) => isAriaDisabled(item))) return;
395
516
  this.#clickOwner = null;
396
517
  event.preventDefault();
397
518
  event.stopImmediatePropagation();
398
519
  };
399
- /** Moves focus to the first navigable item (no-op if none). */
400
520
  #focusFirst() {
401
521
  if (!this.#isOpen) return;
402
522
  this.#navigableItems[0]?.focus();
403
523
  }
404
- /** Moves focus to the last navigable item (no-op if none). */
405
524
  #focusLast() {
406
525
  if (!this.#isOpen) return;
407
526
  const items = this.#navigableItems;
408
527
  items[items.length - 1]?.focus();
409
528
  }
410
- /** Menu items eligible for roving focus (excludes disabled / hidden). */
411
529
  get #navigableItems() {
412
530
  return this.itemTargets.filter((item) => this.#isNavigable(item));
413
531
  }
414
- /**
415
- * An item can take roving focus unless it is `hidden` or a natively `disabled`
416
- * form control. `aria-disabled` remains focusable for discoverability; capture
417
- * suppresses activation. CSS-only visibility is the consumer's responsibility.
418
- */
419
532
  #isNavigable(item) {
420
533
  if (item.hasAttribute("hidden")) return false;
421
534
  return !item.disabled;
422
535
  }
423
- /** Whether the menu is currently visible. */
536
+ #isActivationDisabled(item) {
537
+ return !this.#isNavigable(item) || isAriaDisabled(item);
538
+ }
424
539
  get #isOpen() {
425
540
  return this.hasMenuTarget && !this.menuTarget.hidden;
426
541
  }
427
542
  };
428
543
 
429
544
  export { MenuController };
430
- //# sourceMappingURL=menu_controller.js.map
431
545
  //# sourceMappingURL=menu_controller.js.map