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
@@ -73,6 +73,16 @@ import { Controller } from '@hotwired/stimulus';
73
73
  * delegated to the opt-in `stimeo-ui/positioning` module (never imported here, so
74
74
  * the core stays zero-dependency). For an app command menu with arrow roving and
75
75
  * `role="menu"`, use `stimeo--menubar` instead.
76
+ *
77
+ * Each move of a panel's open state is reported: `stimeo--navigation-menu:open`
78
+ * and `stimeo--navigation-menu:close` dispatch
79
+ * `{ reason: StateReason, index: number, panel: HTMLElement }` — `index` is the
80
+ * trigger's position in `triggerTargets` — after the state attributes are
81
+ * written. Both are informational, so neither is cancelable. Switching panels
82
+ * reports the outgoing `close` before the incoming `open`, both with the reason
83
+ * that drove the switch. A call that leaves a panel where it already was, the
84
+ * normalization in {@link connect}, target churn, and {@link disconnect} are all
85
+ * silent.
76
86
  */
77
87
  declare class NavigationMenuController extends Controller<HTMLElement> {
78
88
  #private;
@@ -87,7 +97,15 @@ declare class NavigationMenuController extends Controller<HTMLElement> {
87
97
  default: number;
88
98
  };
89
99
  };
100
+ static valueConstraints: {
101
+ hoverDelay: {
102
+ readonly finite: true;
103
+ readonly min: 0;
104
+ readonly max: 2147483647;
105
+ };
106
+ };
90
107
  static actions: readonly ["onTriggerKeydown", "toggle"];
108
+ static events: readonly ["close", "open"];
91
109
  readonly triggerTargets: HTMLElement[];
92
110
  readonly panelTargets: HTMLElement[];
93
111
  readonly hoverAreaTargets: HTMLElement[];
@@ -129,7 +147,7 @@ declare class NavigationMenuController extends Controller<HTMLElement> {
129
147
  * activation (pointer elsewhere) still toggles it closed, and `Escape` and
130
148
  * outside clicks dismiss either way.
131
149
  */
132
- toggle(event: Event): void;
150
+ toggle(source: Event | HTMLElement): void;
133
151
  /**
134
152
  * `ArrowLeft`/`ArrowRight` move focus between triggers (keeping Tab order).
135
153
  *
@@ -1,19 +1,46 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/navigation_menu_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/logical_scroll.ts
6
25
  function isRtl(element) {
7
26
  return window.getComputedStyle(element).direction === "rtl";
8
27
  }
9
28
 
10
- // src/utils/arrow_step.ts
11
29
  function isReservedArrowChord(event, allow = []) {
12
30
  if (!event.key.startsWith("Arrow")) return false;
13
31
  return event.altKey && !allow.includes("alt") || event.ctrlKey && !allow.includes("ctrl") || event.metaKey && !allow.includes("meta") || event.shiftKey && !allow.includes("shift");
14
32
  }
15
33
 
16
- // src/utils/escape_layer.ts
34
+ function sharedRegistry(key, create) {
35
+ const symbol = Symbol.for(key);
36
+ const scope = globalThis;
37
+ const existing = scope[symbol];
38
+ if (existing !== void 0) return existing;
39
+ const registry = create();
40
+ Object.defineProperty(globalThis, symbol, { value: registry });
41
+ return registry;
42
+ }
43
+
17
44
  function claimsWhileFocusWithin(element) {
18
45
  return () => {
19
46
  const active = element.ownerDocument.activeElement;
@@ -21,17 +48,11 @@ function claimsWhileFocusWithin(element) {
21
48
  };
22
49
  }
23
50
  var EscapeLayer = class _EscapeLayer {
24
- static #registries = /* @__PURE__ */ new WeakMap();
25
- #ownerDocument = null;
26
- /** Dismissal callback while active; `null` when inactive. */
27
- #onDismiss = null;
28
- /** Live predicate deciding whether the layer claims a press; `null` = always. */
29
- #claims = null;
30
- /**
31
- * Activates this layer at the top of its document's Escape stack, installing
32
- * the document's shared resolver listener if this is its first layer.
33
- * Re-activating an already-active layer moves it to the top.
34
- */
51
+ static #registries = sharedRegistry(
52
+ "stimeo-ui.escape-layer.registry.v1",
53
+ () => /* @__PURE__ */ new WeakMap()
54
+ );
55
+ #active = null;
35
56
  activate(ownerDocument = document, options) {
36
57
  this.deactivate();
37
58
  let registry = _EscapeLayer.#registries.get(ownerDocument);
@@ -40,44 +61,31 @@ var EscapeLayer = class _EscapeLayer {
40
61
  _EscapeLayer.#registries.set(ownerDocument, registry);
41
62
  ownerDocument.addEventListener("keydown", registry.onKeydown);
42
63
  }
43
- registry.stack.push(this);
44
- this.#ownerDocument = ownerDocument;
45
- this.#onDismiss = options.onDismiss;
46
- this.#claims = options.claims ?? null;
47
- }
48
- /**
49
- * Removes this layer from its document's Escape stack, uninstalling the
50
- * shared listener when the stack empties. Safe to call when inactive.
51
- */
64
+ const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
65
+ registry.stack.push(entry);
66
+ this.#active = { document: ownerDocument, entry };
67
+ }
52
68
  deactivate() {
53
- const ownerDocument = this.#ownerDocument;
54
- if (!ownerDocument) return;
55
- const registry = _EscapeLayer.#registries.get(ownerDocument);
69
+ const active = this.#active;
70
+ if (!active) return;
71
+ const registry = _EscapeLayer.#registries.get(active.document);
56
72
  if (registry) {
57
- const index = registry.stack.lastIndexOf(this);
73
+ const index = registry.stack.lastIndexOf(active.entry);
58
74
  if (index >= 0) registry.stack.splice(index, 1);
59
75
  if (registry.stack.length === 0) {
60
- ownerDocument.removeEventListener("keydown", registry.onKeydown);
61
- _EscapeLayer.#registries.delete(ownerDocument);
76
+ active.document.removeEventListener("keydown", registry.onKeydown);
77
+ _EscapeLayer.#registries.delete(active.document);
62
78
  }
63
79
  }
64
- this.#ownerDocument = null;
65
- this.#onDismiss = null;
66
- this.#claims = null;
67
- }
68
- /**
69
- * Whether this active layer would own a press right now: it is the topmost
70
- * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
71
- * and diagnostics — production dismissal goes through the shared listener.
72
- */
80
+ this.#active = null;
81
+ }
73
82
  get ownsEscape() {
74
- const ownerDocument = this.#ownerDocument;
75
- if (!ownerDocument) return false;
76
- const registry = _EscapeLayer.#registries.get(ownerDocument);
83
+ const active = this.#active;
84
+ if (!active) return false;
85
+ const registry = _EscapeLayer.#registries.get(active.document);
77
86
  if (!registry) return false;
78
- return _EscapeLayer.#resolveOwner(registry.stack) === this;
87
+ return _EscapeLayer.#resolveOwner(registry.stack) === active.entry;
79
88
  }
80
- /** Builds a document's registry with its shared resolver listener. */
81
89
  static #createRegistry() {
82
90
  const registry = {
83
91
  stack: [],
@@ -86,66 +94,91 @@ var EscapeLayer = class _EscapeLayer {
86
94
  const owner = _EscapeLayer.#resolveOwner(registry.stack);
87
95
  if (!owner) return;
88
96
  event.preventDefault();
89
- owner.#onDismiss?.();
97
+ owner.onDismiss();
90
98
  }
91
99
  };
92
100
  return registry;
93
101
  }
94
- /** The topmost stack layer whose claims predicate passes, or `null`. */
95
102
  static #resolveOwner(stack) {
96
103
  for (let index = stack.length - 1; index >= 0; index--) {
97
104
  const layer = stack[index];
98
105
  if (!layer) continue;
99
- if (layer.#claims && !layer.#claims()) continue;
106
+ if (layer.claims && !layer.claims()) continue;
100
107
  return layer;
101
108
  }
102
109
  return null;
103
110
  }
104
111
  };
105
112
 
106
- // src/utils/event_owner.ts
107
113
  function ownerIndex(candidates, node) {
108
114
  if (!(node instanceof Node)) return -1;
109
115
  return candidates.findIndex((candidate) => candidate.contains(node));
110
116
  }
111
117
 
112
- // src/utils/safe_timeout.ts
118
+ var MAX_TIMER_DELAY_MS = 2147483647;
119
+
120
+ var NUMBER_BOUNDS = {
121
+ timer: { finite: true, min: 0, max: MAX_TIMER_DELAY_MS }};
122
+ function matchesNumberBounds(value, bounds) {
123
+ if (!Number.isFinite(value)) {
124
+ const direction = value === Infinity ? "positive" : value === -Infinity ? "negative" : null;
125
+ if (direction === null) return false;
126
+ if (bounds.allowInfinity !== "both" && bounds.allowInfinity !== direction) return false;
127
+ }
128
+ if (bounds.min !== void 0 && value < bounds.min) return false;
129
+ if (bounds.max !== void 0 && value > bounds.max) return false;
130
+ if (bounds.exclusiveMin !== void 0 && value <= bounds.exclusiveMin) return false;
131
+ if (bounds.integer && !Number.isInteger(value)) return false;
132
+ if (bounds.allowedValues !== void 0 && !bounds.allowedValues.includes(value)) return false;
133
+ return true;
134
+ }
135
+
136
+ function readNumber(raw, fallback, bounds) {
137
+ return matchesNumberBounds(raw, bounds) ? raw : fallback;
138
+ }
139
+
140
+ var NumberValueReader = class {
141
+ #lastRejected = /* @__PURE__ */ new Map();
142
+ read(owner, name, raw, fallback, bounds) {
143
+ const resolved = readNumber(raw, fallback, bounds);
144
+ if (matchesNumberBounds(raw, bounds)) {
145
+ this.#lastRejected.delete(name);
146
+ return resolved;
147
+ }
148
+ const attribute = `data-${owner.identifier}-${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-value`;
149
+ const literal = owner.element.getAttribute(attribute);
150
+ if (literal === null) {
151
+ this.#lastRejected.delete(name);
152
+ return resolved;
153
+ }
154
+ if (this.#lastRejected.get(name) !== literal) {
155
+ this.#lastRejected.set(name, literal);
156
+ console.warn(
157
+ `Stimeo UI: "${owner.identifier}" has an invalid number Value "${name}" declaration ${JSON.stringify(literal)}; using ${fallback}.`
158
+ );
159
+ }
160
+ return resolved;
161
+ }
162
+ };
163
+
113
164
  var TimerRegistry = class {
114
- /** Live timer ids that have not yet been cleared (or, for timeouts, fired). */
115
165
  ids = /* @__PURE__ */ new Set();
116
- /**
117
- * Cancels a single tracked timer.
118
- *
119
- * No-ops if the id is unknown (already cleared, fired, or never owned by this
120
- * registry), so callers can clear defensively without guarding.
121
- */
122
166
  clear(id) {
123
167
  if (this.ids.delete(id)) {
124
168
  this.cancel(id);
125
169
  }
126
170
  }
127
- /**
128
- * Cancels every tracked timer. Call this from a controller's `disconnect()`
129
- * to guarantee no timer outlives the element.
130
- */
131
171
  clearAll() {
132
172
  for (const id of this.ids) {
133
173
  this.cancel(id);
134
174
  }
135
175
  this.ids.clear();
136
176
  }
137
- /** Number of timers currently tracked (pending). */
138
177
  get size() {
139
178
  return this.ids.size;
140
179
  }
141
180
  };
142
181
  var SafeTimeout = class extends TimerRegistry {
143
- /**
144
- * Schedules `callback` after `delay` ms and returns the timer id.
145
- *
146
- * The id is removed from the registry automatically when the timeout fires,
147
- * so {@link TimerRegistry.size | size} reflects only still-pending timers.
148
- */
149
182
  set(callback, delay) {
150
183
  const id = this.schedule(() => {
151
184
  this.ids.delete(id);
@@ -162,121 +195,82 @@ var SafeTimeout = class extends TimerRegistry {
162
195
  }
163
196
  };
164
197
 
165
- // src/controllers/navigation_menu_controller.ts
166
- var NavigationMenuController = class extends Controller {
198
+ function targetSelector(identifier, name) {
199
+ return `[data-${identifier}-target~="${name}"]`;
200
+ }
201
+
202
+ var NavigationMenuController = class _NavigationMenuController extends Controller {
203
+ #numbers = new NumberValueReader();
167
204
  static targets = ["trigger", "panel", "hoverArea"];
168
205
  static values = {
169
206
  openOnHover: { type: Boolean, default: false },
170
207
  hoverDelay: { type: Number, default: 150 }
171
208
  };
209
+ static valueConstraints = {
210
+ hoverDelay: NUMBER_BOUNDS.timer
211
+ };
172
212
  static actions = ["onTriggerKeydown", "toggle"];
173
- /** Open/close delay timers for hover mode; cleared together on disconnect. */
213
+ static events = ["close", "open"];
174
214
  #hoverTimers = new SafeTimeout();
175
- /**
176
- * Elements currently carrying hover listeners. Removal always mirrors this
177
- * set (not a recomputed target snapshot), so a rebuild driven by target churn
178
- * can never strand a listener on an element that stopped being a target.
179
- */
180
215
  #hoverWired = /* @__PURE__ */ new Set();
181
- /**
182
- * The trigger whose hover region the pointer currently occupies, if any. Only
183
- * the click semantics under `openOnHover` read it (see {@link toggle}).
184
- */
185
216
  #hoveredTrigger = null;
186
- /**
187
- * Whether the controller is between `connect()` and `disconnect()`. Stimulus
188
- * disconnects the controller **before** disconnecting its targets, so the
189
- * target callbacks below also fire during teardown; without this flag their
190
- * rebuild would re-wire hover listeners onto an already-disconnected nav.
191
- */
192
217
  #connected = false;
193
- /** Escape-stack membership while any panel is open; the shared resolver dismisses via it. */
218
+ #reporting = false;
194
219
  #escapeLayer = new EscapeLayer();
195
- /** Establishes the closed baseline and the dismissal listeners. */
196
220
  connect() {
197
221
  this.#connected = true;
198
- this.#closeAll();
222
+ this.#closeAll("api");
199
223
  document.addEventListener("click", this.#onOutsideClick, true);
200
224
  this.element.addEventListener("focusout", this.#onFocusOut);
201
225
  if (this.openOnHoverValue) this.#addHoverListeners();
226
+ this.#reporting = true;
202
227
  }
203
- /** Removes every listener, pending hover timer, and stack membership taken while connected. */
204
228
  disconnect() {
205
229
  this.#connected = false;
230
+ this.#reporting = false;
206
231
  this.#escapeLayer.deactivate();
207
232
  document.removeEventListener("click", this.#onOutsideClick, true);
208
233
  this.element.removeEventListener("focusout", this.#onFocusOut);
209
234
  this.#removeHoverListeners();
210
235
  this.#hoverTimers.clearAll();
211
236
  }
212
- /**
213
- * Follows a runtime flip of `openOnHover` (a morph or a scripted attribute
214
- * change) by wiring or unwiring the hover listeners in place. Stimulus also
215
- * fires this once before `connect()`, which the `#connected` guard skips —
216
- * `connect()` owns the initial wiring.
217
- */
218
237
  openOnHoverValueChanged() {
219
238
  if (!this.#connected) return;
220
239
  if (this.openOnHoverValue) this.#addHoverListeners();
221
240
  else this.#removeHoverListeners();
222
241
  }
223
- /** Re-wires hover listeners when a target is added after connect (Turbo Streams etc.). */
224
242
  triggerTargetConnected() {
225
243
  this.#rewireHoverListeners();
226
244
  }
227
- /** Re-wires hover listeners when a target is removed while connected. */
228
245
  triggerTargetDisconnected() {
229
246
  this.#rewireHoverListeners();
230
247
  }
231
- /** See {@link NavigationMenuController.triggerTargetConnected}. */
232
248
  panelTargetConnected() {
233
249
  this.#rewireHoverListeners();
234
250
  }
235
- /** See {@link NavigationMenuController.triggerTargetDisconnected}. */
236
251
  panelTargetDisconnected() {
237
252
  this.#rewireHoverListeners();
238
253
  }
239
- /** See {@link NavigationMenuController.triggerTargetConnected}. */
240
254
  hoverAreaTargetConnected() {
241
255
  this.#rewireHoverListeners();
242
256
  }
243
- /** See {@link NavigationMenuController.triggerTargetDisconnected}. */
244
257
  hoverAreaTargetDisconnected() {
245
258
  this.#rewireHoverListeners();
246
259
  }
247
- /**
248
- * Toggles a trigger's panel (single-open). Bound via `data-action` (click).
249
- *
250
- * An explicit activation always wins over a scheduled hover open/close, so any
251
- * pending hover timer is dropped first — otherwise a close scheduled just
252
- * before the click would fire into the freshly opened panel.
253
- *
254
- * Under `openOnHover`, closing a panel the pointer itself is holding open is
255
- * refused: the pointer stays put, `mouseenter` does not re-fire, and the panel
256
- * would be unopenable until the pointer left and came back. Keyboard
257
- * activation (pointer elsewhere) still toggles it closed, and `Escape` and
258
- * outside clicks dismiss either way.
259
- */
260
- toggle(event) {
261
- const trigger = event.currentTarget;
260
+ toggle(source) {
261
+ const { host, origin, reason } = actionSource(source);
262
+ const trigger = host?.closest(targetSelector(this.identifier, "trigger"));
263
+ if (!trigger || !this.triggerTargets.includes(trigger)) return;
264
+ if (origin && origin.closest(`[data-controller~="${this.identifier}"]`) !== this.element)
265
+ return;
262
266
  this.#hoverTimers.clearAll();
263
267
  if (!this.#isExpanded(trigger)) {
264
- this.#openPanel(trigger);
268
+ this.#openPanel(trigger, reason);
265
269
  return;
266
270
  }
267
271
  if (this.openOnHoverValue && this.#hoveredTrigger === trigger) return;
268
- this.#closePanel(trigger);
269
- }
270
- /**
271
- * `ArrowLeft`/`ArrowRight` move focus between triggers (keeping Tab order).
272
- *
273
- * Only navigable triggers are destinations, so a `hidden` or disabled item
274
- * never swallows the press. A modified arrow belongs to the browser or OS
275
- * (`Alt+←`/`Alt+→` is history back/forward), and with no destination at all
276
- * (a single-trigger nav) the press is left to the page — `preventDefault()` is
277
- * called only when focus actually moves, keeping the scroll-suppression
278
- * contract honest.
279
- */
272
+ this.#closePanel(trigger, reason);
273
+ }
280
274
  onTriggerKeydown(event) {
281
275
  if (event.defaultPrevented) return;
282
276
  if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
@@ -292,60 +286,42 @@ var NavigationMenuController = class extends Controller {
292
286
  const step = event.key === forward ? 1 : -1;
293
287
  triggers[(index + step + triggers.length) % triggers.length]?.focus();
294
288
  }
295
- /**
296
- * Opens `trigger`'s panel, closing any other open panel first. Re-opening an
297
- * already-open panel only re-asserts the Escape layer: the close/open
298
- * round-trip would rewrite `aria-expanded` and `hidden` for no state change.
299
- */
300
- #openPanel(trigger) {
289
+ #openPanel(trigger, reason) {
301
290
  if (trigger.getAttribute("aria-disabled") === "true") return;
302
291
  if (this.#isExpanded(trigger)) {
303
292
  this.#syncEscapeLayer();
304
293
  return;
305
294
  }
306
- this.#closeAll();
295
+ this.#closeAll(reason);
307
296
  const panel = this.#panelFor(trigger);
308
297
  if (!panel) return;
309
298
  panel.hidden = false;
310
299
  trigger.setAttribute("aria-expanded", "true");
300
+ this.#report("open", trigger, panel, reason);
311
301
  this.#syncEscapeLayer();
312
302
  }
313
- /** Closes `trigger`'s panel and reflects the collapsed state. */
314
- #closePanel(trigger) {
303
+ #closePanel(trigger, reason) {
304
+ const was = this.#isExpanded(trigger);
315
305
  const panel = this.#panelFor(trigger);
316
306
  if (panel) panel.hidden = true;
317
307
  trigger.setAttribute("aria-expanded", "false");
308
+ if (was && panel) this.#report("close", trigger, panel, reason);
318
309
  this.#syncEscapeLayer();
319
310
  }
320
- /**
321
- * Closes every open panel and normalizes `aria-expanded="false"` on **all**
322
- * triggers, so a trigger whose panel was hidden by other means cannot keep
323
- * advertising an open panel.
324
- */
325
- #closeAll() {
326
- for (const trigger of this.triggerTargets) this.#closePanel(trigger);
327
- }
328
- /**
329
- * Whether a trigger can receive arrow-key focus: not `hidden`, not a natively
330
- * `disabled` control (mirrors `toolbar`). CSS-only visibility cannot be
331
- * detected headlessly and stays the consumer's responsibility.
332
- *
333
- * **`aria-disabled="true"` stays reachable.** APG separates the attributes by
334
- * intent: `disabled` is for controls a neighbour makes inferable, while
335
- * `aria-disabled` marks one that must remain *discoverable* — and a nav
336
- * section the user cannot even arrow to is a section they cannot learn exists.
337
- * Opening is suppressed separately, so the trigger announces itself and does
338
- * nothing.
339
- */
311
+ #closeAll(reason) {
312
+ for (const trigger of this.triggerTargets) this.#closePanel(trigger, reason);
313
+ }
314
+ #report(name, trigger, panel, reason) {
315
+ if (!this.#reporting) return;
316
+ const index = this.triggerTargets.indexOf(trigger);
317
+ const detail = { reason, index, panel };
318
+ if (name === "open") this.dispatch("open", { detail, cancelable: false });
319
+ else this.dispatch("close", { detail, cancelable: false });
320
+ }
340
321
  #isNavigable(trigger) {
341
322
  if (trigger.hasAttribute("hidden")) return false;
342
323
  return !trigger.disabled;
343
324
  }
344
- /**
345
- * Aligns Escape-stack membership with the open state: joins (or re-asserts to
346
- * the top) while a panel is open, leaves once none is. Re-asserting when the
347
- * user switches panels is deliberate — the nav is again the newest layer.
348
- */
349
325
  #syncEscapeLayer() {
350
326
  if (this.#isAnyOpen) {
351
327
  this.#escapeLayer.activate(document, {
@@ -356,84 +332,49 @@ var NavigationMenuController = class extends Controller {
356
332
  this.#escapeLayer.deactivate();
357
333
  }
358
334
  }
359
- /**
360
- * Closes any open panel and returns focus to its trigger (Escape path). A
361
- * pending hover open would otherwise re-open the panel the user just
362
- * dismissed, so the reservation is dropped with it.
363
- */
364
335
  #closeAndRestore() {
365
336
  const open = this.#openTrigger;
366
337
  if (!open) return;
367
338
  this.#hoverTimers.clearAll();
368
- this.#closePanel(open);
339
+ this.#closePanel(open, "escape");
369
340
  open.focus();
370
341
  }
371
- /** Closes panels when a click lands outside the nav element. */
372
342
  #onOutsideClick = (event) => {
373
- if (this.#isAnyOpen && !this.element.contains(event.target)) this.#closeAll();
343
+ if (this.#isAnyOpen && !this.element.contains(event.target)) this.#closeAll("outside");
374
344
  };
375
- /**
376
- * Closes (without restoring focus) when focus leaves the nav for a known
377
- * external destination. A null/non-Node destination is indeterminate:
378
- * browsers use it for clicks on non-focusable content and for window
379
- * deactivation, so those never close the nav here (matching the popover
380
- * convention) — the outside-click handler decides pointer dismissal, and the
381
- * Escape stack's body-focus claim keeps keyboard dismissal working.
382
- */
383
345
  #onFocusOut = (event) => {
384
346
  const next = event.relatedTarget;
385
347
  if (!(next instanceof Node) || this.element.contains(next)) return;
386
- this.#closeAll();
348
+ this.#closeAll("focus");
387
349
  };
388
- /** Opens a trigger's panel after the hover delay (hover mode). */
389
350
  #onPointerEnter = (event) => {
390
351
  const trigger = this.#triggerForHover(event.currentTarget);
391
352
  if (!trigger) return;
392
353
  this.#hoveredTrigger = trigger;
393
354
  this.#hoverTimers.clearAll();
394
- this.#hoverTimers.set(() => this.#openFromHover(trigger), this.hoverDelayValue);
355
+ this.#hoverTimers.set(() => this.#openFromHover(trigger), this.#safeHoverDelay);
395
356
  };
396
- /**
397
- * Closes the open panel after the hover delay (hover mode) — unless the
398
- * pointer moved directly into another part of the hover region (an adjacent
399
- * trigger, panel, or hoverArea): crossing a shared edge must not schedule a
400
- * spurious close, e.g. a hoverArea whose panel sits outside it as a sibling.
401
- */
402
357
  #onPointerLeave = (event) => {
403
358
  const next = event instanceof MouseEvent ? event.relatedTarget : null;
404
359
  if (ownerIndex(this.#hoverElements, next) !== -1) return;
405
360
  this.#hoveredTrigger = null;
406
361
  this.#hoverTimers.clearAll();
407
- this.#hoverTimers.set(() => this.#closeFromHover(), this.hoverDelayValue);
362
+ this.#hoverTimers.set(() => this.#closeFromHover(), this.#safeHoverDelay);
408
363
  };
409
- /**
410
- * Hover-driven open. Switching panels would hide the outgoing one, so focus
411
- * standing inside it is handed back to its trigger first: pointer movement
412
- * must never send focus to the body (the keyboard user would lose their
413
- * place). Focus outside the closing panel is left alone.
414
- */
415
364
  #openFromHover(trigger) {
416
365
  if (trigger.getAttribute("aria-disabled") === "true") return;
417
366
  const open = this.#openTrigger;
418
367
  if (open && open !== trigger && this.#holdsFocus(this.#panelFor(open))) open.focus();
419
- this.#openPanel(trigger);
420
- }
421
- /**
422
- * Hover-driven close: skipped while focus sits inside the panel it would hide,
423
- * for the same no-focus-loss reason (cf. `hover_card`'s delayed close). The
424
- * panel then stays open until `Escape`, an outside click, or focus leaving the
425
- * nav dismisses it — all of which restore or keep focus deliberately.
426
- */
368
+ this.#openPanel(trigger, "pointer");
369
+ }
427
370
  #closeFromHover() {
428
371
  const open = this.#openTrigger;
429
372
  if (open && this.#holdsFocus(this.#panelFor(open))) return;
430
- this.#closeAll();
373
+ this.#closeAll("pointer");
431
374
  }
432
- /** Whether `panel` exists and currently contains the focused element. */
433
375
  #holdsFocus(panel) {
434
376
  return panel?.contains(document.activeElement) ?? false;
435
377
  }
436
- /** Wires hover open/close on each hover element (opt-in), tracking what was wired. */
437
378
  #addHoverListeners() {
438
379
  for (const element of this.#hoverElements) {
439
380
  element.addEventListener("mouseenter", this.#onPointerEnter);
@@ -441,7 +382,6 @@ var NavigationMenuController = class extends Controller {
441
382
  this.#hoverWired.add(element);
442
383
  }
443
384
  }
444
- /** Removes the hover listeners from exactly the elements that were wired. */
445
385
  #removeHoverListeners() {
446
386
  for (const element of this.#hoverWired) {
447
387
  element.removeEventListener("mouseenter", this.#onPointerEnter);
@@ -450,25 +390,11 @@ var NavigationMenuController = class extends Controller {
450
390
  this.#hoverWired.clear();
451
391
  this.#hoveredTrigger = null;
452
392
  }
453
- /**
454
- * Rebuilds the hover wiring from the current targets. Target callbacks call
455
- * this so items added or removed after connect (e.g. a Turbo Stream append)
456
- * participate in hover; the wired-set removal keeps the rebuild symmetric.
457
- * Stimulus fires those callbacks during teardown too, hence the `#connected`
458
- * guard — a rebuild after `disconnect()` would resurrect the listeners.
459
- */
460
393
  #rewireHoverListeners() {
461
394
  if (!this.#connected || !this.openOnHoverValue) return;
462
395
  this.#removeHoverListeners();
463
396
  this.#addHoverListeners();
464
397
  }
465
- /**
466
- * Elements that participate in hover: each `hoverArea`, plus every trigger and
467
- * panel **not** wrapped by one. A wrapped trigger/panel must defer to its
468
- * wrapper — its own mouseleave would otherwise schedule a close while the
469
- * pointer is still inside the area (mouseenter does not re-fire on the wrapper
470
- * when moving among its descendants), flickering the panel shut.
471
- */
472
398
  get #hoverElements() {
473
399
  const areas = this.hoverAreaTargets;
474
400
  const covered = (element) => ownerIndex(areas, element) !== -1;
@@ -478,11 +404,6 @@ var NavigationMenuController = class extends Controller {
478
404
  ...this.panelTargets.filter((panel) => !covered(panel))
479
405
  ];
480
406
  }
481
- /**
482
- * Resolves the trigger for a hovered element: the trigger itself, the trigger
483
- * controlling a hovered panel, or the first trigger contained in a hovered
484
- * `hoverArea` wrapper.
485
- */
486
407
  #triggerForHover(element) {
487
408
  if (this.triggerTargets.includes(element)) return element;
488
409
  if (this.panelTargets.includes(element)) {
@@ -493,25 +414,29 @@ var NavigationMenuController = class extends Controller {
493
414
  }
494
415
  return null;
495
416
  }
496
- /** The panel controlled by `trigger` (matched by `aria-controls`/`id`). */
497
417
  #panelFor(trigger) {
498
418
  const id = trigger.getAttribute("aria-controls");
499
419
  return id ? this.panelTargets.find((panel) => panel.id === id) ?? null : null;
500
420
  }
501
- /** Whether `trigger`'s panel is currently expanded. */
502
421
  #isExpanded(trigger) {
503
422
  return trigger.getAttribute("aria-expanded") === "true";
504
423
  }
505
- /** The trigger whose panel is currently open, if any. */
506
424
  get #openTrigger() {
507
425
  return this.triggerTargets.find((trigger) => this.#isExpanded(trigger)) ?? null;
508
426
  }
509
- /** Whether any panel is currently open. */
510
427
  get #isAnyOpen() {
511
428
  return this.#openTrigger !== null;
512
429
  }
430
+ get #safeHoverDelay() {
431
+ return this.#numbers.read(
432
+ this,
433
+ "hoverDelay",
434
+ this.hoverDelayValue,
435
+ _NavigationMenuController.values.hoverDelay.default,
436
+ _NavigationMenuController.valueConstraints.hoverDelay
437
+ );
438
+ }
513
439
  };
514
440
 
515
441
  export { NavigationMenuController };
516
- //# sourceMappingURL=navigation_menu_controller.js.map
517
442
  //# sourceMappingURL=navigation_menu_controller.js.map