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
@@ -44,12 +44,23 @@ import { Controller } from '@hotwired/stimulus';
44
44
  * - Opt-in **dismiss on scroll** (`closeOnScroll`): while open, scrolling a tracked
45
45
  * scroll-parent ancestor (or the window) closes the panel, the usual convention for
46
46
  * anchored popups. Closes without restoring focus (like the modeless `focusout` path)
47
- * so the close never fights the user's scroll. Off by default.
47
+ * so the close never fights the user's scroll. Off by default. Flipping it while the
48
+ * panel is open wires or releases the dismissal in place.
49
+ * - The open panel holds its Escape layer and scroll dismissal only while it is in the
50
+ * DOM: once it leaves (a removal, or a morph that swaps in another panel) both are
51
+ * released and the trigger reads `aria-expanded="false"`. When the page hides the
52
+ * panel itself, the next outside click, focus leaving, Escape, dismissing scroll or
53
+ * `close` releases them the same way; the panel already reads closed, so nothing is
54
+ * reported.
55
+ * - A trigger that takes over — in one task, or after an earlier one leaves in a later
56
+ * task — reads the open state, and one that stops resolving as the trigger gets back
57
+ * the `aria-expanded` it carried before this controller wrote on it.
48
58
  * - Each move of the open state is reported: `stimeo--popover:open` and
49
59
  * `stimeo--popover:close` dispatch `{ reason: StateReason }`, after the state
50
60
  * attributes are written. Both are informational, so neither is cancelable. A
51
61
  * call that leaves the state where it already was, the normalization in
52
- * {@link connect}, and {@link disconnect} are all silent.
62
+ * {@link connect}, the open panel leaving the DOM, a trigger that takes over, and
63
+ * {@link disconnect} are all silent.
53
64
  */
54
65
  declare class PopoverController extends Controller<HTMLElement> {
55
66
  #private;
@@ -63,7 +74,9 @@ declare class PopoverController extends Controller<HTMLElement> {
63
74
  static actions: readonly ["close", "open", "toggle"];
64
75
  static events: readonly ["close", "open"];
65
76
  readonly triggerTarget: HTMLButtonElement;
77
+ readonly triggerTargets: HTMLButtonElement[];
66
78
  readonly panelTarget: HTMLElement;
79
+ readonly panelTargets: HTMLElement[];
67
80
  readonly hasTriggerTarget: boolean;
68
81
  readonly hasPanelTarget: boolean;
69
82
  readonly closeOnScrollValue: boolean;
@@ -71,11 +84,34 @@ declare class PopoverController extends Controller<HTMLElement> {
71
84
  connect(): void;
72
85
  /**
73
86
  * Removes every standing listener registered in {@link connect} plus any active
74
- * dismiss-on-scroll observers. `removeEventListener` is a no-op when it was
75
- * never added, so this is safe in the closed state too — no listener outlives
76
- * the element after a Turbo navigation.
87
+ * dismiss-on-scroll observers. Releasing the set is a no-op for what it does not
88
+ * hold, so this is safe in the closed state too — no listener outlives the
89
+ * element after a Turbo navigation.
77
90
  */
78
91
  disconnect(): void;
92
+ /**
93
+ * Follows a runtime flip of `closeOnScroll` while the panel is open, wiring or releasing
94
+ * the scroll dismissal in place. A closed panel holds nothing, so its next opening reads
95
+ * the declaration as it is then; the delivery Stimulus makes ahead of `connect()` finds
96
+ * nothing held either.
97
+ */
98
+ closeOnScrollValueChanged(): void;
99
+ /**
100
+ * Releases what the open panel holds once it leaves the DOM — a removal, or a morph that
101
+ * swaps in another panel — and writes the closed state onto it and onto the trigger and
102
+ * any panel that remains. Focus is left where the removal put it. Nothing is dispatched:
103
+ * target churn is not a state move anyone made. After {@link disconnect}, which already
104
+ * released everything, this leaves the DOM alone.
105
+ */
106
+ panelTargetDisconnected(panel: HTMLElement): void;
107
+ /** Brings a trigger that arrives after connect to the open state. */
108
+ triggerTargetConnected(): void;
109
+ /**
110
+ * Gives a trigger that no longer resolves as one its own `aria-expanded` back — after
111
+ * `disconnect()` too, since dropping the identifier leaves the element on the page — and
112
+ * brings the trigger that stays to the open state.
113
+ */
114
+ triggerTargetDisconnected(trigger: HTMLButtonElement): void;
79
115
  /** Toggles the popover. Bound via `data-action` (click on the trigger). */
80
116
  toggle(event?: Event): void;
81
117
  /** Opens the panel, reflects state, and moves focus inside it. */
@@ -1,8 +1,158 @@
1
1
  import { Controller } from '@hotwired/stimulus';
2
2
 
3
- // src/controllers/popover_controller.ts
3
+ function setAttributeIfChanged(element, name, value, beforeWrite) {
4
+ if (element.getAttribute(name) === value) return false;
5
+ if (value === null) element.removeAttribute(name);
6
+ else element.setAttribute(name, value);
7
+ return true;
8
+ }
9
+
10
+ function sharedRegistry(key, create) {
11
+ const symbol = Symbol.for(key);
12
+ const scope = globalThis;
13
+ const existing = scope[symbol];
14
+ if (existing !== void 0) return existing;
15
+ const registry2 = create();
16
+ Object.defineProperty(globalThis, symbol, { value: registry2 });
17
+ return registry2;
18
+ }
19
+
20
+ var registry = sharedRegistry(
21
+ "stimeo-ui.attribute-lease.registry.v1",
22
+ () => ({ stacks: /* @__PURE__ */ new WeakMap(), names: /* @__PURE__ */ new Map() })
23
+ );
24
+ function isAttributeValue(value) {
25
+ return value === null || typeof value === "string";
26
+ }
27
+ function readLeaseRecord(element, record) {
28
+ try {
29
+ const parsed = JSON.parse(element.getAttribute(record) ?? "null");
30
+ if (!Array.isArray(parsed)) return void 0;
31
+ const [original, written, ...rest] = parsed;
32
+ const linked = rest[0] !== false;
33
+ const beneath = linked ? rest : rest.slice(1);
34
+ if (!beneath.every((under) => typeof under === "string")) return void 0;
35
+ return isAttributeValue(original) && isAttributeValue(written) ? { original, written, linked, beneath } : void 0;
36
+ } catch {
37
+ return void 0;
38
+ }
39
+ }
40
+ function attributeStack(element, attribute) {
41
+ const byAttribute = registry.stacks.get(element) ?? /* @__PURE__ */ new Map();
42
+ registry.stacks.set(element, byAttribute);
43
+ const known = byAttribute.get(attribute);
44
+ if (known) return known;
45
+ const found = [];
46
+ for (const record of registry.names.get(attribute) ?? []) {
47
+ const read = readLeaseRecord(element, record);
48
+ if (read) found.push({ ...read, record });
49
+ }
50
+ const depth = ({ beneath }) => beneath.filter((name) => found.some((other) => other.record === name)).length;
51
+ const stack = found.sort((one, other) => depth(one) - depth(other)).map(({ record, written, original, linked }, index) => ({
52
+ lease: null,
53
+ record,
54
+ written,
55
+ original,
56
+ linked: linked && index > 0
57
+ }));
58
+ byAttribute.set(attribute, stack);
59
+ return stack;
60
+ }
61
+ function findHold(stack, lease, record) {
62
+ return stack.find((held) => held.lease === lease) ?? stack.find((held) => held.lease === null && held.record === record);
63
+ }
64
+ var AttributeLease = class {
65
+ #attribute;
66
+ #record;
67
+ #base;
68
+ #preserveAuthor;
69
+ #held = /* @__PURE__ */ new Set();
70
+ constructor(attribute, owner, options = {}) {
71
+ this.#attribute = attribute;
72
+ this.#record = `data-${owner}-${attribute}-lease`;
73
+ this.#base = options.base === true;
74
+ this.#preserveAuthor = options.preserveAuthor === true;
75
+ const names = registry.names;
76
+ names.set(attribute, (names.get(attribute) ?? /* @__PURE__ */ new Set()).add(this.#record));
77
+ }
78
+ write(element, value) {
79
+ const stack = attributeStack(element, this.#attribute);
80
+ const hold = this.#take(stack) ?? this.#acquire(element, stack);
81
+ this.#held.add(element);
82
+ const shows = !this.#covered(element, stack, hold);
83
+ hold.written = value;
84
+ this.#mark(element, stack);
85
+ if (shows) this.#reflect(element, value);
86
+ }
87
+ return(element) {
88
+ const held = this.#held.delete(element);
89
+ const known = registry.stacks.get(element)?.has(this.#attribute);
90
+ const stack = held || known || element.hasAttribute(this.#record) ? attributeStack(element, this.#attribute) : [];
91
+ const hold = this.#take(stack);
92
+ if (!hold) return false;
93
+ const carries = element.getAttribute(this.#attribute) === hold.written;
94
+ const owns = carries && !this.#covered(element, stack, hold);
95
+ const index = stack.indexOf(hold);
96
+ stack.splice(index, 1);
97
+ const above = stack[index];
98
+ if (above) above.linked &&= hold.linked;
99
+ this.#mark(element, stack);
100
+ if (!owns) return true;
101
+ const below = hold.linked ? stack[index - 1] : void 0;
102
+ this.#reflect(element, below ? below.written : hold.original);
103
+ return true;
104
+ }
105
+ returnAll() {
106
+ for (const element of Array.from(this.#held)) this.return(element);
107
+ }
108
+ #take(stack) {
109
+ const hold = findHold(stack, this, this.#record);
110
+ if (hold) hold.lease = this;
111
+ return hold;
112
+ }
113
+ #acquire(element, stack) {
114
+ const current = element.getAttribute(this.#attribute);
115
+ const top = stack.at(-1);
116
+ const bottom = stack[0];
117
+ const over = top?.written === current ? top : void 0;
118
+ const from = this.#base ? this.#preserveAuthor && !stack.some((held) => held.written === current) ? void 0 : bottom : over;
119
+ const hold = {
120
+ lease: this,
121
+ record: this.#record,
122
+ written: current,
123
+ original: from ? from.original : current,
124
+ linked: !this.#base && over !== void 0
125
+ };
126
+ if (this.#base && bottom) {
127
+ bottom.linked = true;
128
+ stack.unshift(hold);
129
+ } else stack.push(hold);
130
+ return hold;
131
+ }
132
+ #covered(element, stack, hold) {
133
+ const current = element.getAttribute(this.#attribute);
134
+ return stack.slice(stack.indexOf(hold) + 1).some((above) => above.written === current);
135
+ }
136
+ #mark(element, stack) {
137
+ const records = /* @__PURE__ */ new Map([[this.#record, null]]);
138
+ stack.forEach((hold, index) => {
139
+ const names = stack.slice(0, index).map((under) => under.record);
140
+ const beneath = [...new Set(names.reverse())].filter((name) => name !== hold.record);
141
+ const marker = hold.linked || beneath.length === 0 ? [] : [false];
142
+ records.set(
143
+ hold.record,
144
+ hold.written === hold.original && beneath.length === 0 && index === stack.length - 1 ? null : JSON.stringify([hold.original, hold.written, ...marker, ...beneath])
145
+ );
146
+ });
147
+ for (const [name, recorded] of records) {
148
+ setAttributeIfChanged(element, name, recorded);
149
+ }
150
+ }
151
+ #reflect(element, value) {
152
+ setAttributeIfChanged(element, this.#attribute, value);
153
+ }
154
+ };
4
155
 
5
- // src/utils/escape_layer.ts
6
156
  function claimsWhileFocusWithin(element) {
7
157
  return () => {
8
158
  const active = element.ownerDocument.activeElement;
@@ -10,89 +160,68 @@ function claimsWhileFocusWithin(element) {
10
160
  };
11
161
  }
12
162
  var EscapeLayer = class _EscapeLayer {
13
- static #registries = /* @__PURE__ */ new WeakMap();
14
- #ownerDocument = null;
15
- /** Dismissal callback while active; `null` when inactive. */
16
- #onDismiss = null;
17
- /** Live predicate deciding whether the layer claims a press; `null` = always. */
18
- #claims = null;
19
- /**
20
- * Activates this layer at the top of its document's Escape stack, installing
21
- * the document's shared resolver listener if this is its first layer.
22
- * Re-activating an already-active layer moves it to the top.
23
- */
163
+ static #registries = sharedRegistry(
164
+ "stimeo-ui.escape-layer.registry.v1",
165
+ () => /* @__PURE__ */ new WeakMap()
166
+ );
167
+ #active = null;
24
168
  activate(ownerDocument = document, options) {
25
169
  this.deactivate();
26
- let registry = _EscapeLayer.#registries.get(ownerDocument);
27
- if (!registry) {
28
- registry = _EscapeLayer.#createRegistry();
29
- _EscapeLayer.#registries.set(ownerDocument, registry);
30
- ownerDocument.addEventListener("keydown", registry.onKeydown);
170
+ let registry2 = _EscapeLayer.#registries.get(ownerDocument);
171
+ if (!registry2) {
172
+ registry2 = _EscapeLayer.#createRegistry();
173
+ _EscapeLayer.#registries.set(ownerDocument, registry2);
174
+ ownerDocument.addEventListener("keydown", registry2.onKeydown);
31
175
  }
32
- registry.stack.push(this);
33
- this.#ownerDocument = ownerDocument;
34
- this.#onDismiss = options.onDismiss;
35
- this.#claims = options.claims ?? null;
36
- }
37
- /**
38
- * Removes this layer from its document's Escape stack, uninstalling the
39
- * shared listener when the stack empties. Safe to call when inactive.
40
- */
176
+ const entry = { onDismiss: options.onDismiss, claims: options.claims ?? null };
177
+ registry2.stack.push(entry);
178
+ this.#active = { document: ownerDocument, entry };
179
+ }
41
180
  deactivate() {
42
- const ownerDocument = this.#ownerDocument;
43
- if (!ownerDocument) return;
44
- const registry = _EscapeLayer.#registries.get(ownerDocument);
45
- if (registry) {
46
- const index = registry.stack.lastIndexOf(this);
47
- if (index >= 0) registry.stack.splice(index, 1);
48
- if (registry.stack.length === 0) {
49
- ownerDocument.removeEventListener("keydown", registry.onKeydown);
50
- _EscapeLayer.#registries.delete(ownerDocument);
181
+ const active = this.#active;
182
+ if (!active) return;
183
+ const registry2 = _EscapeLayer.#registries.get(active.document);
184
+ if (registry2) {
185
+ const index = registry2.stack.lastIndexOf(active.entry);
186
+ if (index >= 0) registry2.stack.splice(index, 1);
187
+ if (registry2.stack.length === 0) {
188
+ active.document.removeEventListener("keydown", registry2.onKeydown);
189
+ _EscapeLayer.#registries.delete(active.document);
51
190
  }
52
191
  }
53
- this.#ownerDocument = null;
54
- this.#onDismiss = null;
55
- this.#claims = null;
56
- }
57
- /**
58
- * Whether this active layer would own a press right now: it is the topmost
59
- * layer whose {@link EscapeLayerOptions.claims} passes. Exposed for tests
60
- * and diagnostics — production dismissal goes through the shared listener.
61
- */
192
+ this.#active = null;
193
+ }
62
194
  get ownsEscape() {
63
- const ownerDocument = this.#ownerDocument;
64
- if (!ownerDocument) return false;
65
- const registry = _EscapeLayer.#registries.get(ownerDocument);
66
- if (!registry) return false;
67
- return _EscapeLayer.#resolveOwner(registry.stack) === this;
195
+ const active = this.#active;
196
+ if (!active) return false;
197
+ const registry2 = _EscapeLayer.#registries.get(active.document);
198
+ if (!registry2) return false;
199
+ return _EscapeLayer.#resolveOwner(registry2.stack) === active.entry;
68
200
  }
69
- /** Builds a document's registry with its shared resolver listener. */
70
201
  static #createRegistry() {
71
- const registry = {
202
+ const registry2 = {
72
203
  stack: [],
73
204
  onKeydown: (event) => {
74
205
  if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
75
- const owner = _EscapeLayer.#resolveOwner(registry.stack);
206
+ const owner = _EscapeLayer.#resolveOwner(registry2.stack);
76
207
  if (!owner) return;
77
208
  event.preventDefault();
78
- owner.#onDismiss?.();
209
+ owner.onDismiss();
79
210
  }
80
211
  };
81
- return registry;
212
+ return registry2;
82
213
  }
83
- /** The topmost stack layer whose claims predicate passes, or `null`. */
84
214
  static #resolveOwner(stack) {
85
215
  for (let index = stack.length - 1; index >= 0; index--) {
86
216
  const layer = stack[index];
87
217
  if (!layer) continue;
88
- if (layer.#claims && !layer.#claims()) continue;
218
+ if (layer.claims && !layer.claims()) continue;
89
219
  return layer;
90
220
  }
91
221
  return null;
92
222
  }
93
223
  };
94
224
 
95
- // src/utils/focus_candidate.ts
96
225
  function inheritsFieldsetDisabled(control) {
97
226
  let fieldset = control.closest("fieldset[disabled]");
98
227
  while (fieldset) {
@@ -102,8 +231,19 @@ function inheritsFieldsetDisabled(control) {
102
231
  }
103
232
  return false;
104
233
  }
234
+ function unslotted(element) {
235
+ return element.assignedSlot === null && element.parentElement?.shadowRoot != null;
236
+ }
237
+ function flatTreeParent(element) {
238
+ if (unslotted(element)) return null;
239
+ const parent = element.assignedSlot ?? element.parentNode;
240
+ if (parent instanceof ShadowRoot) return parent.host;
241
+ return parent instanceof Element ? parent : null;
242
+ }
105
243
  function canTakeFocus(element) {
106
- if (element.closest("[hidden], [inert]")) return false;
244
+ for (let node = element; node; node = flatTreeParent(node)) {
245
+ if (unslotted(node) || node.matches("[hidden], [inert]")) return false;
246
+ }
107
247
  if (element instanceof HTMLInputElement && element.type === "hidden") return false;
108
248
  if (!("disabled" in element)) return true;
109
249
  if (element.disabled) return false;
@@ -167,7 +307,17 @@ function firstTabStop(root) {
167
307
  return null;
168
308
  }
169
309
 
170
- // src/utils/scroll_dismiss.ts
310
+ var ListenerSet = class {
311
+ #abort = new AbortController();
312
+ add(target, type, handler, options) {
313
+ target.addEventListener(type, handler, { ...options, signal: this.#abort.signal });
314
+ }
315
+ dispose() {
316
+ this.#abort.abort();
317
+ this.#abort = new AbortController();
318
+ }
319
+ };
320
+
171
321
  function observeScrollDismiss(element, onScroll) {
172
322
  const targets = [...scrollParents(element), window];
173
323
  const handler = () => onScroll();
@@ -192,7 +342,6 @@ function isScrollable(element) {
192
342
  return /auto|scroll|overlay/.test(style.overflow + style.overflowX + style.overflowY);
193
343
  }
194
344
 
195
- // src/utils/state_reason.ts
196
345
  var FOCUS_EVENTS = /* @__PURE__ */ new Set(["blur", "focus", "focusin", "focusout"]);
197
346
  var POINTER_EVENTS = /* @__PURE__ */ new Set(["mouseenter", "mouseleave", "pointerenter", "pointerleave"]);
198
347
  function stateReasonFor(event) {
@@ -202,7 +351,6 @@ function stateReasonFor(event) {
202
351
  return "user";
203
352
  }
204
353
 
205
- // src/controllers/popover_controller.ts
206
354
  var PopoverController = class extends Controller {
207
355
  static targets = ["trigger", "panel"];
208
356
  static values = {
@@ -210,34 +358,43 @@ var PopoverController = class extends Controller {
210
358
  };
211
359
  static actions = ["close", "open", "toggle"];
212
360
  static events = ["close", "open"];
213
- /** Cleanup for the dismiss-on-scroll listeners while open, or `null`. */
214
361
  #stopScrollDismiss = null;
215
- /** Escape-stack membership while open; the shared resolver dismisses via it. */
216
362
  #escapeLayer = new EscapeLayer();
217
- /** Whether state moves are reported: set once `connect()` settled the baseline. */
363
+ #expanded = new AttributeLease("aria-expanded", this.identifier);
364
+ #shownPanel = null;
365
+ #connected = false;
218
366
  #reporting = false;
219
- /** Starts closed and registers the standing dismissal listeners. */
367
+ #listeners = new ListenerSet();
220
368
  connect() {
221
369
  this.#close("api");
222
- document.addEventListener("click", this.#onOutsideClick, true);
223
- this.element.addEventListener("focusout", this.#onFocusOut);
370
+ this.#listeners.add(document, "click", this.#onOutsideClick, { capture: true });
371
+ this.#listeners.add(this.element, "focusout", this.#onFocusOut);
372
+ this.#connected = true;
224
373
  this.#reporting = true;
225
374
  }
226
- /**
227
- * Removes every standing listener registered in {@link connect} plus any active
228
- * dismiss-on-scroll observers. `removeEventListener` is a no-op when it was
229
- * never added, so this is safe in the closed state too — no listener outlives
230
- * the element after a Turbo navigation.
231
- */
232
375
  disconnect() {
376
+ this.#connected = false;
233
377
  this.#reporting = false;
234
- this.#escapeLayer.deactivate();
235
- document.removeEventListener("click", this.#onOutsideClick, true);
236
- this.element.removeEventListener("focusout", this.#onFocusOut);
237
- this.#stopScrollDismiss?.();
238
- this.#stopScrollDismiss = null;
378
+ this.#release();
379
+ this.#listeners.dispose();
380
+ }
381
+ closeOnScrollValueChanged() {
382
+ if (this.#shownPanel) this.#syncScrollDismiss();
383
+ }
384
+ panelTargetDisconnected(panel) {
385
+ if (panel !== this.#shownPanel) return;
386
+ if (this.panelTargets.includes(panel)) return;
387
+ this.#release();
388
+ panel.hidden = true;
389
+ this.#reflectClosed();
390
+ }
391
+ triggerTargetConnected() {
392
+ if (this.#connected) this.#reflectTrigger();
393
+ }
394
+ triggerTargetDisconnected(trigger) {
395
+ if (!this.triggerTargets.includes(trigger)) this.#expanded.return(trigger);
396
+ if (this.#connected) this.#reflectTrigger();
239
397
  }
240
- /** Toggles the popover. Bound via `data-action` (click on the trigger). */
241
398
  toggle(event) {
242
399
  if (this.#isOpen) {
243
400
  this.close(event);
@@ -245,46 +402,54 @@ var PopoverController = class extends Controller {
245
402
  this.open(event);
246
403
  }
247
404
  }
248
- /** Opens the panel, reflects state, and moves focus inside it. */
249
405
  open(event) {
250
406
  this.#open(stateReasonFor(event));
251
407
  }
252
- /** Closes the panel and reflects the collapsed state. Bound via `data-action`. */
253
408
  close(event) {
254
409
  this.#close(stateReasonFor(event));
255
410
  }
256
- /**
257
- * Opens the panel, reflects state, reports a move, and moves focus inside it.
258
- *
259
- * @stimeoRuntimeOnly `closeOnScroll` decides whether this opening wires the scroll dismissal;
260
- * what is shown does not depend on it.
261
- */
262
411
  #open(reason) {
263
412
  if (!this.hasPanelTarget || this.#isOpen) return;
264
- this.panelTarget.hidden = false;
265
- if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", "true");
413
+ const panel = this.panelTarget;
414
+ panel.hidden = false;
415
+ if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, "true");
416
+ this.#shownPanel = panel;
266
417
  if (this.#reporting) this.dispatch("open", { detail: { reason }, cancelable: false });
267
- if (!this.#isOpen) return;
418
+ if (this.#shownPanel !== panel || !this.#isOpen) return;
268
419
  this.#escapeLayer.activate(document, {
269
420
  onDismiss: () => this.#closeAndRestore(),
270
421
  claims: claimsWhileFocusWithin(this.element)
271
422
  });
272
- if (this.closeOnScrollValue && !this.#stopScrollDismiss) {
273
- this.#stopScrollDismiss = observeScrollDismiss(this.element, () => this.#close("scroll"));
274
- }
423
+ this.#syncScrollDismiss();
275
424
  this.#focusFirst();
276
425
  }
277
- /** Closes the panel, reflects the collapsed state, and reports a move. */
278
426
  #close(reason) {
279
427
  const was = this.#isOpen;
428
+ this.#release();
429
+ this.#reflectClosed();
430
+ if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
431
+ }
432
+ #reflectClosed() {
433
+ if (this.hasPanelTarget) this.panelTarget.hidden = true;
434
+ if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, "false");
435
+ }
436
+ #reflectTrigger() {
437
+ if (this.hasTriggerTarget) this.#expanded.write(this.triggerTarget, String(this.#isOpen));
438
+ }
439
+ #release() {
440
+ this.#shownPanel = null;
280
441
  this.#escapeLayer.deactivate();
281
442
  this.#stopScrollDismiss?.();
282
443
  this.#stopScrollDismiss = null;
283
- if (this.hasPanelTarget) this.panelTarget.hidden = true;
284
- if (this.hasTriggerTarget) this.triggerTarget.setAttribute("aria-expanded", "false");
285
- if (was && this.#reporting) this.dispatch("close", { detail: { reason }, cancelable: false });
286
444
  }
287
- /** Moves focus to the first focusable element in the panel, or the panel itself. */
445
+ #syncScrollDismiss() {
446
+ if (this.closeOnScrollValue) {
447
+ this.#stopScrollDismiss ??= observeScrollDismiss(this.element, () => this.#close("scroll"));
448
+ return;
449
+ }
450
+ this.#stopScrollDismiss?.();
451
+ this.#stopScrollDismiss = null;
452
+ }
288
453
  #focusFirst() {
289
454
  const first = firstTabStop(this.panelTarget);
290
455
  if (first) {
@@ -294,38 +459,29 @@ var PopoverController = class extends Controller {
294
459
  if (!this.panelTarget.hasAttribute("tabindex")) this.panelTarget.tabIndex = -1;
295
460
  this.panelTarget.focus();
296
461
  }
297
- /** Closes and restores focus to the trigger for explicit keyboard dismissal. */
298
462
  #closeAndRestore() {
299
463
  this.#close("escape");
300
464
  if (this.hasTriggerTarget) this.triggerTarget.focus();
301
465
  }
302
- /** Closes without moving focus when a click lands outside the controller element. */
303
466
  #onOutsideClick = (event) => {
304
467
  const target = event.target;
305
- if (this.#isOpen && target instanceof Node && !this.element.contains(target)) {
468
+ if (this.#closable && target instanceof Node && !this.element.contains(target)) {
306
469
  this.#close("outside");
307
470
  }
308
471
  };
309
- /**
310
- * Closes when focus leaves the controller for a known external destination
311
- * (e.g. forward Tab past the panel or reverse Tab past the trigger). Focus is
312
- * not restored — the natural destination is kept, which is the modeless
313
- * contract. A null/non-Node destination is indeterminate: browsers use it for
314
- * clicks on non-focusable content and window deactivation, so the later outside
315
- * click handler decides pointer dismissal instead.
316
- */
317
472
  #onFocusOut = (event) => {
318
- if (!this.#isOpen) return;
473
+ if (!this.#closable) return;
319
474
  const next = event.relatedTarget;
320
475
  if (!(next instanceof Node) || this.element.contains(next)) return;
321
476
  this.#close("focus");
322
477
  };
323
- /** Whether the panel is currently visible. */
324
478
  get #isOpen() {
325
479
  return this.hasPanelTarget && !this.panelTarget.hidden;
326
480
  }
481
+ get #closable() {
482
+ return this.#isOpen || this.#shownPanel !== null;
483
+ }
327
484
  };
328
485
 
329
486
  export { PopoverController };
330
- //# sourceMappingURL=popover_controller.js.map
331
487
  //# sourceMappingURL=popover_controller.js.map