@rotki/ui-library 3.1.6 → 3.2.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 (261) hide show
  1. package/dist/components/accordions/accordion/RuiAccordion.js.map +1 -1
  2. package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js +3 -3
  3. package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  4. package/dist/components/accordions/accordion/accordion-styles.d.ts +1 -0
  5. package/dist/components/accordions/accordion/accordion-styles.js +7 -0
  6. package/dist/components/accordions/accordion/accordion-styles.js.map +1 -0
  7. package/dist/components/alerts/RuiAlert.js.map +1 -1
  8. package/dist/components/alerts/RuiAlert.vue_vue_type_script_setup_true_lang.js +1 -65
  9. package/dist/components/alerts/RuiAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  10. package/dist/components/alerts/alert-styles.d.ts +145 -0
  11. package/dist/components/alerts/alert-styles.js +70 -0
  12. package/dist/components/alerts/alert-styles.js.map +1 -0
  13. package/dist/components/avatars/RuiAvatar.js.map +1 -1
  14. package/dist/components/avatars/RuiAvatar.vue_vue_type_script_setup_true_lang.js +1 -38
  15. package/dist/components/avatars/RuiAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  16. package/dist/components/avatars/RuiAvatarGroup.js.map +1 -1
  17. package/dist/components/avatars/RuiAvatarGroup.vue_vue_type_script_setup_true_lang.js +1 -13
  18. package/dist/components/avatars/RuiAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/avatars/avatar-group-styles.d.ts +49 -0
  20. package/dist/components/avatars/avatar-group-styles.js +18 -0
  21. package/dist/components/avatars/avatar-group-styles.js.map +1 -0
  22. package/dist/components/avatars/avatar-styles.d.ts +181 -0
  23. package/dist/components/avatars/avatar-styles.js +43 -0
  24. package/dist/components/avatars/avatar-styles.js.map +1 -0
  25. package/dist/components/buttons/button/button-styles.js +1 -1
  26. package/dist/components/buttons/button/button-styles.js.map +1 -1
  27. package/dist/components/buttons/button-group/RuiButtonGroup.js.map +1 -1
  28. package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js +2 -138
  29. package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  30. package/dist/components/buttons/button-group/button-group-styles.d.ts +142 -0
  31. package/dist/components/buttons/button-group/button-group-styles.js +143 -0
  32. package/dist/components/buttons/button-group/button-group-styles.js.map +1 -0
  33. package/dist/components/calendar/RuiCalendar.js.map +1 -1
  34. package/dist/components/calendar/RuiCalendar.vue_vue_type_script_setup_true_lang.js +1 -8
  35. package/dist/components/calendar/RuiCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  36. package/dist/components/calendar/RuiCalendarGrid.js.map +1 -1
  37. package/dist/components/calendar/RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js +2 -22
  38. package/dist/components/calendar/RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  39. package/dist/components/calendar/RuiCalendarMenu.js.map +1 -1
  40. package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js +3 -14
  41. package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  42. package/dist/components/calendar/calendar-grid-styles.d.ts +43 -0
  43. package/dist/components/calendar/calendar-grid-styles.js +26 -0
  44. package/dist/components/calendar/calendar-grid-styles.js.map +1 -0
  45. package/dist/components/calendar/calendar-menu-styles.d.ts +34 -0
  46. package/dist/components/calendar/calendar-menu-styles.js +17 -0
  47. package/dist/components/calendar/calendar-menu-styles.js.map +1 -0
  48. package/dist/components/calendar/calendar-styles.d.ts +16 -0
  49. package/dist/components/calendar/calendar-styles.js +13 -0
  50. package/dist/components/calendar/calendar-styles.js.map +1 -0
  51. package/dist/components/cards/RuiCard.js.map +1 -1
  52. package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js +3 -58
  53. package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  54. package/dist/components/cards/RuiCardHeader.js.map +1 -1
  55. package/dist/components/cards/RuiCardHeader.vue_vue_type_script_setup_true_lang.js +2 -20
  56. package/dist/components/cards/RuiCardHeader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  57. package/dist/components/cards/card-header-styles.d.ts +46 -0
  58. package/dist/components/cards/card-header-styles.js +24 -0
  59. package/dist/components/cards/card-header-styles.js.map +1 -0
  60. package/dist/components/cards/card-styles.d.ts +175 -0
  61. package/dist/components/cards/card-styles.js +62 -0
  62. package/dist/components/cards/card-styles.js.map +1 -0
  63. package/dist/components/chips/RuiChip.js.map +1 -1
  64. package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js +2 -241
  65. package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  66. package/dist/components/chips/chip-styles.d.ts +223 -0
  67. package/dist/components/chips/chip-styles.js +246 -0
  68. package/dist/components/chips/chip-styles.js.map +1 -0
  69. package/dist/components/color-picker/RuiColorBoard.js.map +1 -1
  70. package/dist/components/color-picker/RuiColorBoard.vue_vue_type_script_setup_true_lang.js +3 -3
  71. package/dist/components/color-picker/RuiColorBoard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  72. package/dist/components/color-picker/RuiColorHue.js.map +1 -1
  73. package/dist/components/color-picker/RuiColorHue.vue_vue_type_script_setup_true_lang.js +3 -3
  74. package/dist/components/color-picker/RuiColorHue.vue_vue_type_script_setup_true_lang.js.map +1 -1
  75. package/dist/components/color-picker/RuiColorPicker.js.map +1 -1
  76. package/dist/components/color-picker/RuiColorPicker.vue_vue_type_script_setup_true_lang.js +3 -3
  77. package/dist/components/color-picker/RuiColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  78. package/dist/components/color-picker/color-board-styles.d.ts +1 -0
  79. package/dist/components/color-picker/color-board-styles.js +7 -0
  80. package/dist/components/color-picker/color-board-styles.js.map +1 -0
  81. package/dist/components/color-picker/color-hue-styles.d.ts +1 -0
  82. package/dist/components/color-picker/color-hue-styles.js +7 -0
  83. package/dist/components/color-picker/color-hue-styles.js.map +1 -0
  84. package/dist/components/color-picker/color-picker-styles.d.ts +1 -0
  85. package/dist/components/color-picker/color-picker-styles.js +7 -0
  86. package/dist/components/color-picker/color-picker-styles.js.map +1 -0
  87. package/dist/components/forms/field-label/RuiFieldLabel.js.map +1 -1
  88. package/dist/components/forms/field-label/RuiFieldLabel.vue_vue_type_script_setup_true_lang.js +1 -11
  89. package/dist/components/forms/field-label/RuiFieldLabel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  90. package/dist/components/forms/field-label/field-label-styles.d.ts +43 -0
  91. package/dist/components/forms/field-label/field-label-styles.js +16 -0
  92. package/dist/components/forms/field-label/field-label-styles.js.map +1 -0
  93. package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  94. package/dist/components/forms/select/RuiSimpleSelect.js.map +1 -1
  95. package/dist/components/forms/select/RuiSimpleSelect.vue_vue_type_script_setup_true_lang.js +2 -11
  96. package/dist/components/forms/select/RuiSimpleSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  97. package/dist/components/forms/select/simple-select-styles.d.ts +2 -0
  98. package/dist/components/forms/select/simple-select-styles.js +15 -0
  99. package/dist/components/forms/select/simple-select-styles.js.map +1 -0
  100. package/dist/components/forms/switch/RuiSwitch.js.map +1 -1
  101. package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js +1 -126
  102. package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  103. package/dist/components/forms/switch/switch-styles.d.ts +151 -0
  104. package/dist/components/forms/switch/switch-styles.js +131 -0
  105. package/dist/components/forms/switch/switch-styles.js.map +1 -0
  106. package/dist/components/icons/RuiIcon.js.map +1 -1
  107. package/dist/components/icons/RuiIcon.vue_vue_type_script_setup_true_lang.js +2 -28
  108. package/dist/components/icons/RuiIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  109. package/dist/components/icons/icon-styles.d.ts +44 -0
  110. package/dist/components/icons/icon-styles.js +33 -0
  111. package/dist/components/icons/icon-styles.js.map +1 -0
  112. package/dist/components/loaders/RuiSkeletonBase.js.map +1 -1
  113. package/dist/components/loaders/RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js +2 -12
  114. package/dist/components/loaders/RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js.map +1 -1
  115. package/dist/components/loaders/RuiSkeletonLoader.js.map +1 -1
  116. package/dist/components/loaders/RuiSkeletonLoader.vue_vue_type_script_setup_true_lang.js +3 -13
  117. package/dist/components/loaders/RuiSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  118. package/dist/components/loaders/skeleton-base-styles.d.ts +25 -0
  119. package/dist/components/loaders/skeleton-base-styles.js +16 -0
  120. package/dist/components/loaders/skeleton-base-styles.js.map +1 -0
  121. package/dist/components/loaders/skeleton-loader-styles.d.ts +37 -0
  122. package/dist/components/loaders/skeleton-loader-styles.js +18 -0
  123. package/dist/components/loaders/skeleton-loader-styles.js.map +1 -0
  124. package/dist/components/overlays/badge/RuiBadge.js.map +1 -1
  125. package/dist/components/overlays/badge/RuiBadge.vue_vue_type_script_setup_true_lang.js +1 -83
  126. package/dist/components/overlays/badge/RuiBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  127. package/dist/components/overlays/badge/badge-styles.d.ts +193 -0
  128. package/dist/components/overlays/badge/badge-styles.js +88 -0
  129. package/dist/components/overlays/badge/badge-styles.js.map +1 -0
  130. package/dist/components/overlays/dialog/RuiDialog.js.map +1 -1
  131. package/dist/components/overlays/dialog/RuiDialog.vue_vue_type_script_setup_true_lang.js +3 -14
  132. package/dist/components/overlays/dialog/RuiDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  133. package/dist/components/overlays/dialog/dialog-styles.d.ts +40 -0
  134. package/dist/components/overlays/dialog/dialog-styles.js +18 -0
  135. package/dist/components/overlays/dialog/dialog-styles.js.map +1 -0
  136. package/dist/components/overlays/menu/RuiMenu.js.map +1 -1
  137. package/dist/components/overlays/menu/RuiMenu.vue.d.ts +9 -0
  138. package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js +113 -58
  139. package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  140. package/dist/components/overlays/menu/menu-styles.d.ts +58 -0
  141. package/dist/components/overlays/menu/menu-styles.js +25 -0
  142. package/dist/components/overlays/menu/menu-styles.js.map +1 -0
  143. package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.js.map +1 -1
  144. package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue_vue_type_script_setup_true_lang.js +4 -69
  145. package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  146. package/dist/components/overlays/navigation-drawer/navigation-drawer-styles.d.ts +76 -0
  147. package/dist/components/overlays/navigation-drawer/navigation-drawer-styles.js +72 -0
  148. package/dist/components/overlays/navigation-drawer/navigation-drawer-styles.js.map +1 -0
  149. package/dist/components/overlays/notification/RuiNotification.js.map +1 -1
  150. package/dist/components/overlays/notification/RuiNotification.vue_vue_type_script_setup_true_lang.js +3 -3
  151. package/dist/components/overlays/notification/RuiNotification.vue_vue_type_script_setup_true_lang.js.map +1 -1
  152. package/dist/components/overlays/notification/notification-styles.d.ts +1 -0
  153. package/dist/components/overlays/notification/notification-styles.js +7 -0
  154. package/dist/components/overlays/notification/notification-styles.js.map +1 -0
  155. package/dist/components/overlays/tooltip/RuiTooltip.js.map +1 -1
  156. package/dist/components/overlays/tooltip/RuiTooltip.vue.d.ts +4 -2
  157. package/dist/components/overlays/tooltip/RuiTooltip.vue_vue_type_script_setup_true_lang.js +42 -25
  158. package/dist/components/overlays/tooltip/RuiTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  159. package/dist/components/overlays/tooltip/tooltip-styles.d.ts +2 -0
  160. package/dist/components/overlays/tooltip/tooltip-styles.js +3 -1
  161. package/dist/components/overlays/tooltip/tooltip-styles.js.map +1 -1
  162. package/dist/components/progress/RuiProgress.js.map +1 -1
  163. package/dist/components/progress/RuiProgress.vue_vue_type_script_setup_true_lang.js +1 -133
  164. package/dist/components/progress/RuiProgress.vue_vue_type_script_setup_true_lang.js.map +1 -1
  165. package/dist/components/progress/progress-styles.d.ts +181 -0
  166. package/dist/components/progress/progress-styles.js +139 -0
  167. package/dist/components/progress/progress-styles.js.map +1 -0
  168. package/dist/components/steppers/RuiFooterStepper.js.map +1 -1
  169. package/dist/components/steppers/RuiFooterStepper.vue_vue_type_script_setup_true_lang.js +3 -13
  170. package/dist/components/steppers/RuiFooterStepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
  171. package/dist/components/steppers/RuiStepper.js.map +1 -1
  172. package/dist/components/steppers/RuiStepper.vue_vue_type_script_setup_true_lang.js +4 -94
  173. package/dist/components/steppers/RuiStepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
  174. package/dist/components/steppers/RuiStepperIcon.js.map +1 -1
  175. package/dist/components/steppers/RuiStepperIcon.vue_vue_type_script_setup_true_lang.js +2 -34
  176. package/dist/components/steppers/RuiStepperIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  177. package/dist/components/steppers/footer-stepper-styles.d.ts +28 -0
  178. package/dist/components/steppers/footer-stepper-styles.js +16 -0
  179. package/dist/components/steppers/footer-stepper-styles.js.map +1 -0
  180. package/dist/components/steppers/stepper-icon-styles.d.ts +60 -0
  181. package/dist/components/steppers/stepper-icon-styles.js +39 -0
  182. package/dist/components/steppers/stepper-icon-styles.js.map +1 -0
  183. package/dist/components/steppers/stepper-styles.d.ts +202 -0
  184. package/dist/components/steppers/stepper-styles.js +98 -0
  185. package/dist/components/steppers/stepper-styles.js.map +1 -0
  186. package/dist/components/tables/RuiTable.js.map +1 -1
  187. package/dist/components/tables/RuiTable.vue_vue_type_script_setup_true_lang.js +1 -32
  188. package/dist/components/tables/RuiTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  189. package/dist/components/tables/RuiTableEmptyState.js.map +1 -1
  190. package/dist/components/tables/RuiTableEmptyState.vue_vue_type_script_setup_true_lang.js +1 -15
  191. package/dist/components/tables/RuiTableEmptyState.vue_vue_type_script_setup_true_lang.js.map +1 -1
  192. package/dist/components/tables/RuiTableErrorState.js.map +1 -1
  193. package/dist/components/tables/RuiTableErrorState.vue_vue_type_script_setup_true_lang.js +2 -5
  194. package/dist/components/tables/RuiTableErrorState.vue_vue_type_script_setup_true_lang.js.map +1 -1
  195. package/dist/components/tables/RuiTableHead.js.map +1 -1
  196. package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js +1 -34
  197. package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js.map +1 -1
  198. package/dist/components/tables/RuiTableLoadingState.js.map +1 -1
  199. package/dist/components/tables/RuiTableLoadingState.vue_vue_type_script_setup_true_lang.js +1 -8
  200. package/dist/components/tables/RuiTableLoadingState.vue_vue_type_script_setup_true_lang.js.map +1 -1
  201. package/dist/components/tables/RuiTablePagination.js.map +1 -1
  202. package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js +1 -30
  203. package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  204. package/dist/components/tables/table-empty-state-styles.d.ts +43 -0
  205. package/dist/components/tables/table-empty-state-styles.js +20 -0
  206. package/dist/components/tables/table-empty-state-styles.js.map +1 -0
  207. package/dist/components/tables/table-error-state-styles.d.ts +36 -0
  208. package/dist/components/tables/table-error-state-styles.js +15 -0
  209. package/dist/components/tables/table-error-state-styles.js.map +1 -0
  210. package/dist/components/tables/table-head-styles.d.ts +91 -0
  211. package/dist/components/tables/table-head-styles.js +39 -0
  212. package/dist/components/tables/table-head-styles.js.map +1 -0
  213. package/dist/components/tables/table-loading-state-styles.d.ts +34 -0
  214. package/dist/components/tables/table-loading-state-styles.js +13 -0
  215. package/dist/components/tables/table-loading-state-styles.js.map +1 -0
  216. package/dist/components/tables/table-pagination-styles.d.ts +82 -0
  217. package/dist/components/tables/table-pagination-styles.js +35 -0
  218. package/dist/components/tables/table-pagination-styles.js.map +1 -0
  219. package/dist/components/tables/table-styles.d.ts +62 -0
  220. package/dist/components/tables/table-styles.js +37 -0
  221. package/dist/components/tables/table-styles.js.map +1 -0
  222. package/dist/components/tabs/tab/RuiTab.js.map +1 -1
  223. package/dist/components/tabs/tab/RuiTab.vue_vue_type_script_setup_true_lang.js +3 -84
  224. package/dist/components/tabs/tab/RuiTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
  225. package/dist/components/tabs/tab/tab-styles.d.ts +153 -0
  226. package/dist/components/tabs/tab/tab-styles.js +89 -0
  227. package/dist/components/tabs/tab/tab-styles.js.map +1 -0
  228. package/dist/components/tabs/tabs/RuiTabs.js.map +1 -1
  229. package/dist/components/tabs/tabs/RuiTabs.vue_vue_type_script_setup_true_lang.js +2 -137
  230. package/dist/components/tabs/tabs/RuiTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  231. package/dist/components/tabs/tabs/tabs-styles.d.ts +168 -0
  232. package/dist/components/tabs/tabs/tabs-styles.js +142 -0
  233. package/dist/components/tabs/tabs/tabs-styles.js.map +1 -0
  234. package/dist/composables/floating.d.ts +3 -1
  235. package/dist/composables/floating.js +8 -9
  236. package/dist/composables/floating.js.map +1 -1
  237. package/dist/icons/icons_1.d.ts +1 -1
  238. package/dist/icons/icons_1.js +10 -9
  239. package/dist/icons/icons_1.js.map +1 -1
  240. package/dist/icons/icons_2.d.ts +1 -1
  241. package/dist/icons/icons_2.js +9 -11
  242. package/dist/icons/icons_2.js.map +1 -1
  243. package/dist/icons/icons_3.d.ts +1 -1
  244. package/dist/icons/icons_3.js +11 -5
  245. package/dist/icons/icons_3.js.map +1 -1
  246. package/dist/icons/icons_4.d.ts +1 -1
  247. package/dist/icons/icons_4.js +5 -10
  248. package/dist/icons/icons_4.js.map +1 -1
  249. package/dist/icons/icons_5.d.ts +1 -0
  250. package/dist/icons/icons_5.js +10 -1
  251. package/dist/icons/icons_5.js.map +1 -1
  252. package/dist/icons/index.d.ts +1 -1
  253. package/dist/icons/index.js +7 -6
  254. package/dist/icons/index.js.map +1 -1
  255. package/dist/index.js +6 -6
  256. package/dist/style.css +8 -0
  257. package/dist/utils/form-text-detail.d.ts +2 -0
  258. package/dist/utils/form-text-detail.js +5 -1
  259. package/dist/utils/form-text-detail.js.map +1 -1
  260. package/dist/web-types.json +11 -1
  261. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"RuiMenu.js","names":["_sfc_main"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type FormDetails, useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n /** With `showDetails`: `reserve` keeps the line while empty (default), `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n details = 'reserve',\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-rui-menu',\n // a hairline edge keeps the popup apart from a surface of nearly the same shade in dark mode\n content: 'rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1',\n details: '',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n 'aria-haspopup': get(ariaHasPopup),\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n *\n * Escape is swallowed only while the menu is open and not persistent\n * (`onLeave` stops the event itself); a closed or persistent menu must let it\n * through, or a consumer that closes its own editor on Escape never sees the\n * key. The root element listens for it.\n *\n * An Escape that arrives while an IME is composing belongs to the IME, which\n * cancels the candidate with it, so the menu leaves it alone the same way: a\n * search field inside the menu would otherwise lose the whole menu mid-word.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent || event.isComposing)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :details=\"details\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiMenu.js","names":["_sfc_main"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Reactive, Ref, WatchHandle } from 'vue';\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating, type UseFloatingReturn } from '@/composables/floating';\nimport { containsMessage, type FormDetails } from '@/utils/form-text-detail';\nimport { cn } from '@/utils/tv';\nimport { menuStyles } from './menu-styles';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n /** With `showDetails`: `reserve` keeps the line while empty (default), `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n /**\n * Defers the popover's setup (its positioning, the activator's size\n * observer and the click-outside listeners) to first use: a click or a\n * context menu on the activator, a hover with `openOnHover`, or the model\n * turning `true`. Until then the menu renders its root, the wrapper and the\n * activator, the same elements it keeps afterwards, so a table with a menu\n * on every row pays for the menus that are opened. Read at mount.\n */\n lazy?: boolean;\n}\n\n/** What the menu sets up at mount, or on first use when `lazy`, with its refs unwrapped. */\ntype MenuSetup = Reactive<{\n floating: UseFloatingReturn;\n width: Readonly<Ref<number>>;\n}>;\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n details = 'reserve',\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n lazy = false,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst activator = useTemplateRef<HTMLElement>('activator');\nconst menu = useTemplateRef<HTMLElement>('menu');\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\nconst instance = getCurrentInstance();\n// Owned by this component, so whatever the setup starts stops on unmount even when it ran after mount\nconst setupScope = effectScope();\nconst menuSetup = shallowRef<MenuSetup>();\nlet stopSetUpOnModel: WatchHandle | undefined;\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\n// Only the two flags, not the formatted lists useFormTextDetail builds, since a table may hold hundreds of menus\nconst hasError = computed<boolean>(() => containsMessage(errorMessages));\nconst hasSuccess = computed<boolean>(() => containsMessage(successMessages));\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\nconst open = computed<boolean>(() => get(menuSetup)?.floating.open ?? false);\nconst visible = computed<boolean>(() => get(menuSetup)?.floating.visible ?? false);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n setUp()?.onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n activeFloating()?.onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n // `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\n 'aria-haspopup': role === 'menu' ? 'true' : role,\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Sets a lazy menu up on its first use, on the wrapper so that it covers an\n * activator that opens the menu through the model rather than through\n * `attrs`. Hover and focus are left out: a cursor resting over a scrolling\n * table or Tab moving down it would set up every row it passes. They stay\n * after setup, where `setUp` only hands back what it already made.\n */\nconst firstUseListeners = { click: setUp, contextmenu: setUp };\n\n/**\n * Sets the popover up once and hands back its floating state. It runs at\n * mount, or for a `lazy` menu on first use, which can be after mount and\n * inside another component's lifecycle hook, so what it starts must not\n * attach itself to the current instance.\n */\nfunction setUp(): MenuSetup['floating'] | undefined {\n if (setupScope.active && !get(menuSetup)) {\n set(menuSetup, setupScope.run(createSetup));\n stopSetUpOnModel?.();\n }\n return activeFloating();\n}\n\n/**\n * The floating state while the menu is alive. An event can still reach the\n * activator after unmount, and must not start timers on a stopped menu.\n */\nfunction activeFloating(): MenuSetup['floating'] | undefined {\n return setupScope.active ? get(menuSetup)?.floating : undefined;\n}\n\n/** An element's content-box width, the box its resize observer reports. */\nfunction contentWidth(element: HTMLElement | null): number {\n if (!element)\n return 0;\n\n const style = getComputedStyle(element);\n const edges = [style.paddingLeft, style.paddingRight, style.borderLeftWidth, style.borderRightWidth];\n return edges.reduce((width, edge) => width - (Number.parseFloat(edge) || 0), element.offsetWidth);\n}\n\nfunction createSetup(): MenuSetup {\n const floating = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl ?? get(activator) ?? undefined,\n menu,\n );\n\n const width = shallowRef<number>(0);\n const readWidth = (): void => set(width, contentWidth(get(activator)));\n useResizeObserver(activator, ([entry]) => {\n if (entry)\n set(width, entry.contentRect.width);\n });\n // Only the menu's own setup waits for its mount; a later one may run inside another component's hook\n if (instance?.isMounted)\n readWidth();\n else\n onMounted(readWidth);\n\n // Immediate, so a model that is already `true` at setup opens the menu\n watch(modelValue, (value) => {\n if (get(floating.open) === value)\n return;\n\n if (value) {\n floating.onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n }, { immediate: true });\n\n watch(floating.open, (isOpen) => {\n set(modelValue, isOpen);\n if (isOpen) {\n focusMenu();\n }\n });\n\n onClickOutside(menu, () => {\n if (get(floating.open) && !persistent)\n onLeave();\n }, { ignore: [activator] });\n\n return reactive({ floating, width });\n}\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n const floating = activeFloating();\n if (!floating?.open)\n return;\n floating.onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n *\n * Escape is swallowed only while the menu is open and not persistent\n * (`onLeave` stops the event itself); a closed or persistent menu must let it\n * through, or a consumer that closes its own editor on Escape never sees the\n * key. The root element listens for it.\n *\n * An Escape that arrives while an IME is composing belongs to the IME, which\n * cancels the candidate with it, so the menu leaves it alone the same way: a\n * search field inside the menu would otherwise lose the whole menu mid-word.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent || event.isComposing)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n const floating = setUp();\n if (!floating)\n return;\n\n if (floating.open && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n floating.onOpen();\n set(click, true);\n }\n}\n\n// The setup's own watcher opens it; a disabled menu stays closed and unset, as an eager one stays closed\nif (!lazy || (get(modelValue) && !disabled)) {\n setUp();\n}\nelse {\n stopSetUpOnModel = watch(modelValue, (value) => {\n if (value && !disabled)\n setUp();\n });\n}\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n v-on=\"lazy && !disabled ? firstUseListeners : {}\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled && menuSetup\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"menuSetup.floating.currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"menuSetup?.floating.updatePosition()\"\n @after-leave=\"menuSetup?.floating.leavePending ? menuSetup.floating.onLeaveComplete() : undefined\"\n @before-leave=\"menuSetup?.floating.onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width: menuSetup.width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :details=\"details\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":""}
@@ -57,6 +57,15 @@ export interface MenuProps {
57
57
  * the other roles take it from their activator or options.
58
58
  */
59
59
  menuLabel?: string;
60
+ /**
61
+ * Defers the popover's setup (its positioning, the activator's size
62
+ * observer and the click-outside listeners) to first use: a click or a
63
+ * context menu on the activator, a hover with `openOnHover`, or the model
64
+ * turning `true`. Until then the menu renders its root, the wrapper and the
65
+ * activator, the same elements it keeps afterwards, so a table with a menu
66
+ * on every row pays for the menus that are opened. Read at mount.
67
+ */
68
+ lazy?: boolean;
60
69
  }
61
70
  type __VLS_Props = MenuProps;
62
71
  type __VLS_Slots = {
@@ -1,11 +1,12 @@
1
1
  import { get, set } from "../../../vendor/@vueuse/shared/dist/index.js";
2
- import { onClickOutside, useElementSize } from "../../../vendor/@vueuse/core/dist/index.js";
3
- import { cn, tv } from "../../../utils/tv.js";
2
+ import { onClickOutside, useResizeObserver } from "../../../vendor/@vueuse/core/dist/index.js";
3
+ import { cn } from "../../../utils/tv.js";
4
4
  import { ButtonGroupBoundary } from "../../buttons/button-group/button-group-context.js";
5
- import { useFormTextDetail } from "../../../utils/form-text-detail.js";
5
+ import { containsMessage } from "../../../utils/form-text-detail.js";
6
6
  import RuiFormTextDetail_default from "../../helpers/RuiFormTextDetail.js";
7
7
  import { useFloating } from "../../../composables/floating.js";
8
- import { Teleport, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, guardReactiveProps, mergeModels, mergeProps, nextTick, normalizeClass, normalizeProps, openBlock, ref, renderSlot, unref, useId, useModel, useTemplateRef, watch, withCtx, withKeys } from "vue";
8
+ import { menuStyles } from "./menu-styles.js";
9
+ import { Teleport, TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, effectScope, getCurrentInstance, guardReactiveProps, mergeModels, mergeProps, nextTick, normalizeClass, normalizeProps, onMounted, openBlock, reactive, ref, renderSlot, shallowRef, toHandlers, unref, useId, useModel, useTemplateRef, watch, withCtx, withKeys } from "vue";
9
10
  //#region src/components/overlays/menu/RuiMenu.vue?vue&type=script&setup=true&lang.ts
10
11
  var _hoisted_1 = ["data-menu-disabled"];
11
12
  var _hoisted_2 = [
@@ -65,7 +66,11 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
65
66
  },
66
67
  anchorEl: {},
67
68
  role: { default: "menu" },
68
- menuLabel: {}
69
+ menuLabel: {},
70
+ lazy: {
71
+ type: Boolean,
72
+ default: false
73
+ }
69
74
  }, {
70
75
  "modelValue": {
71
76
  type: Boolean,
@@ -77,35 +82,22 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
77
82
  setup(__props) {
78
83
  const modelValue = useModel(__props, "modelValue");
79
84
  const click = ref(false);
85
+ const activator = useTemplateRef("activator");
86
+ const menu = useTemplateRef("menu");
80
87
  const menuContent = useTemplateRef("menuContent");
81
88
  const menuId = useId();
82
- const { reference: activator, popover: menu, open, visible, currentPlacement, leavePending, onLeavePending, onOpen, onClose, onLeaveComplete, updatePosition } = useFloating(() => __props.options ?? {}, () => __props.disabled, () => __props.openDelay, () => __props.closeDelay, () => __props.anchorEl);
83
- const { width } = useElementSize(activator);
84
- const { hasError, hasSuccess } = useFormTextDetail(() => __props.errorMessages, () => __props.successMessages);
85
- const menuStyles = tv({
86
- slots: {
87
- wrapper: "relative inline-flex max-w-full",
88
- popover: "w-max z-rui-menu",
89
- content: "rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1",
90
- details: ""
91
- },
92
- variants: {
93
- fullWidth: { true: { wrapper: "w-full" } },
94
- dense: {
95
- true: { details: "px-2" },
96
- false: { details: "px-4" }
97
- }
98
- },
99
- defaultVariants: {
100
- fullWidth: false,
101
- dense: false
102
- }
103
- });
89
+ const instance = getCurrentInstance();
90
+ const setupScope = effectScope();
91
+ const menuSetup = shallowRef();
92
+ let stopSetUpOnModel;
93
+ const hasError = computed(() => containsMessage(__props.errorMessages));
94
+ const hasSuccess = computed(() => containsMessage(__props.successMessages));
104
95
  const ui = computed(() => menuStyles({
105
96
  fullWidth: __props.fullWidth,
106
97
  dense: __props.dense
107
98
  }));
108
- const ariaHasPopup = computed(() => __props.role === "menu" ? "true" : __props.role);
99
+ const open = computed(() => get(menuSetup)?.floating.open ?? false);
100
+ const visible = computed(() => get(menuSetup)?.floating.visible ?? false);
109
101
  /**
110
102
  * The activator attributes.
111
103
  *
@@ -119,21 +111,91 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
119
111
  const clickVal = get(click);
120
112
  return {
121
113
  onMouseover: __props.disabled ? void 0 : () => {
122
- if (__props.openOnHover) onOpen();
114
+ if (__props.openOnHover) setUp()?.onOpen();
123
115
  },
124
116
  onMouseleave: __props.disabled ? void 0 : () => {
125
- if (__props.openOnHover && !clickVal) onClose();
117
+ if (__props.openOnHover && !clickVal) activeFloating()?.onClose();
126
118
  }
127
119
  };
128
120
  });
129
121
  const menuAttrs = computed(() => ({
130
122
  ...get(baseMenuAttrs),
131
123
  "onClick": __props.disabled ? void 0 : checkClick,
132
- "aria-haspopup": get(ariaHasPopup),
124
+ "aria-haspopup": __props.role === "menu" ? "true" : __props.role,
133
125
  "aria-expanded": get(open),
134
126
  "aria-controls": get(visible) ? menuId : void 0
135
127
  }));
136
128
  /**
129
+ * Sets a lazy menu up on its first use, on the wrapper so that it covers an
130
+ * activator that opens the menu through the model rather than through
131
+ * `attrs`. Hover and focus are left out: a cursor resting over a scrolling
132
+ * table or Tab moving down it would set up every row it passes. They stay
133
+ * after setup, where `setUp` only hands back what it already made.
134
+ */
135
+ const firstUseListeners = {
136
+ click: setUp,
137
+ contextmenu: setUp
138
+ };
139
+ /**
140
+ * Sets the popover up once and hands back its floating state. It runs at
141
+ * mount, or for a `lazy` menu on first use, which can be after mount and
142
+ * inside another component's lifecycle hook, so what it starts must not
143
+ * attach itself to the current instance.
144
+ */
145
+ function setUp() {
146
+ if (setupScope.active && !get(menuSetup)) {
147
+ set(menuSetup, setupScope.run(createSetup));
148
+ stopSetUpOnModel?.();
149
+ }
150
+ return activeFloating();
151
+ }
152
+ /**
153
+ * The floating state while the menu is alive. An event can still reach the
154
+ * activator after unmount, and must not start timers on a stopped menu.
155
+ */
156
+ function activeFloating() {
157
+ return setupScope.active ? get(menuSetup)?.floating : void 0;
158
+ }
159
+ /** An element's content-box width, the box its resize observer reports. */
160
+ function contentWidth(element) {
161
+ if (!element) return 0;
162
+ const style = getComputedStyle(element);
163
+ return [
164
+ style.paddingLeft,
165
+ style.paddingRight,
166
+ style.borderLeftWidth,
167
+ style.borderRightWidth
168
+ ].reduce((width, edge) => width - (Number.parseFloat(edge) || 0), element.offsetWidth);
169
+ }
170
+ function createSetup() {
171
+ const floating = useFloating(() => __props.options ?? {}, () => __props.disabled, () => __props.openDelay, () => __props.closeDelay, () => __props.anchorEl ?? get(activator) ?? void 0, menu);
172
+ const width = shallowRef(0);
173
+ const readWidth = () => set(width, contentWidth(get(activator)));
174
+ useResizeObserver(activator, ([entry]) => {
175
+ if (entry) set(width, entry.contentRect.width);
176
+ });
177
+ if (instance?.isMounted) readWidth();
178
+ else onMounted(readWidth);
179
+ watch(modelValue, (value) => {
180
+ if (get(floating.open) === value) return;
181
+ if (value) {
182
+ floating.onOpen();
183
+ set(click, true);
184
+ } else onLeave();
185
+ }, { immediate: true });
186
+ watch(floating.open, (isOpen) => {
187
+ set(modelValue, isOpen);
188
+ if (isOpen) focusMenu();
189
+ });
190
+ onClickOutside(menu, () => {
191
+ if (get(floating.open) && !__props.persistent) onLeave();
192
+ }, { ignore: [activator] });
193
+ return reactive({
194
+ floating,
195
+ width
196
+ });
197
+ }
198
+ /**
137
199
  * Moves focus into the open menu without scrolling the page. A menu taller than the space below its
138
200
  * activator would otherwise make the browser scroll the whole document to reveal the focused
139
201
  * container, sliding the page out from under the activator.
@@ -155,8 +217,9 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
155
217
  if (focusableEl) focusableEl.focus({ preventScroll: true });
156
218
  }
157
219
  function onLeave(event) {
158
- if (!get(open)) return;
159
- onClose();
220
+ const floating = activeFloating();
221
+ if (!floating?.open) return;
222
+ floating.onClose();
160
223
  set(click, false);
161
224
  event?.stopPropagation();
162
225
  if (__props.disableAutoFocus) return;
@@ -187,46 +250,38 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
187
250
  onLeave(event);
188
251
  }
189
252
  function checkClick() {
190
- if (get(open) && get(click)) {
253
+ const floating = setUp();
254
+ if (!floating) return;
255
+ if (floating.open && get(click)) {
191
256
  if (!__props.persistOnActivatorClick) onLeave();
192
257
  } else {
193
- onOpen();
258
+ floating.onOpen();
194
259
  set(click, true);
195
260
  }
196
261
  }
197
- watch(modelValue, (value) => {
198
- if (get(open) === value) return;
199
- if (value) {
200
- onOpen();
201
- set(click, true);
202
- } else onLeave();
203
- });
204
- watch(open, (open) => {
205
- set(modelValue, open);
206
- if (open) focusMenu();
262
+ if (!__props.lazy || get(modelValue) && !__props.disabled) setUp();
263
+ else stopSetUpOnModel = watch(modelValue, (value) => {
264
+ if (value && !__props.disabled) setUp();
207
265
  });
208
- onClickOutside(menu, () => {
209
- if (get(open) && !__props.persistent) onLeave();
210
- }, { ignore: [activator] });
211
266
  return (_ctx, _cache) => {
212
267
  return openBlock(), createElementBlock("div", {
213
268
  class: normalizeClass(__props.classNames?.root),
214
269
  onKeydown: _cache[5] || (_cache[5] = withKeys(($event) => onEscape($event), ["esc"]))
215
270
  }, [
216
271
  renderSlot(_ctx.$slots, "label"),
217
- createElementVNode("div", {
272
+ createElementVNode("div", mergeProps({
218
273
  ref_key: "activator",
219
274
  ref: activator,
220
- class: normalizeClass(unref(ui).wrapper({ class: unref(cn)(__props.classNames?.wrapper) })),
275
+ class: unref(ui).wrapper({ class: unref(cn)(__props.classNames?.wrapper) }),
221
276
  "data-menu-disabled": __props.disabled
222
- }, [renderSlot(_ctx.$slots, "activator", normalizeProps(guardReactiveProps({
277
+ }, toHandlers(__props.lazy && !__props.disabled ? firstUseListeners : {}, true)), [renderSlot(_ctx.$slots, "activator", normalizeProps(guardReactiveProps({
223
278
  attrs: unref(menuAttrs),
224
279
  open: unref(open),
225
280
  disabled: __props.disabled,
226
281
  hasError: unref(hasError),
227
282
  hasSuccess: unref(hasSuccess)
228
- })))], 10, _hoisted_1),
229
- !__props.disabled ? (openBlock(), createBlock(Teleport, {
283
+ })))], 16, _hoisted_1),
284
+ !__props.disabled && unref(menuSetup) ? (openBlock(), createBlock(Teleport, {
230
285
  key: 0,
231
286
  to: "body"
232
287
  }, [unref(visible) ? (openBlock(), createElementBlock("div", {
@@ -237,7 +292,7 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
237
292
  class: normalizeClass(unref(ui).popover({ class: unref(cn)(__props.classNames?.menu) })),
238
293
  role: __props.role,
239
294
  "aria-label": __props.menuLabel,
240
- "data-placement": unref(currentPlacement),
295
+ "data-placement": unref(menuSetup).floating.currentPlacement,
241
296
  onClick: _cache[3] || (_cache[3] = ($event) => __props.closeOnContentClick ? onLeave() : void 0),
242
297
  onKeydown: _cache[4] || (_cache[4] = withKeys(($event) => onEscape($event), ["esc"]))
243
298
  }, [createVNode(TransitionGroup, {
@@ -247,9 +302,9 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
247
302
  "leave-active-class": "transition ease-in duration-150",
248
303
  "leave-from-class": "opacity-100 translate-y-0",
249
304
  "leave-to-class": "opacity-0 translate-y-1",
250
- onBeforeEnter: _cache[0] || (_cache[0] = ($event) => unref(updatePosition)()),
251
- onAfterLeave: _cache[1] || (_cache[1] = ($event) => unref(leavePending) ? unref(onLeaveComplete)() : void 0),
252
- onBeforeLeave: _cache[2] || (_cache[2] = ($event) => unref(onLeavePending)())
305
+ onBeforeEnter: _cache[0] || (_cache[0] = ($event) => unref(menuSetup)?.floating.updatePosition()),
306
+ onAfterLeave: _cache[1] || (_cache[1] = ($event) => unref(menuSetup)?.floating.leavePending ? unref(menuSetup).floating.onLeaveComplete() : void 0),
307
+ onBeforeLeave: _cache[2] || (_cache[2] = ($event) => unref(menuSetup)?.floating.onLeavePending())
253
308
  }, {
254
309
  default: withCtx(() => [unref(open) ? (openBlock(), createElementBlock("div", mergeProps({
255
310
  ref_key: "menuContent",
@@ -259,7 +314,7 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
259
314
  class: unref(ui).content({ class: unref(cn)(__props.classNames?.content) }),
260
315
  tabindex: "-1"
261
316
  }, unref(baseMenuAttrs)), [createVNode(unref(ButtonGroupBoundary), null, {
262
- default: withCtx(() => [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps({ width: unref(width) })))]),
317
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps({ width: unref(menuSetup).width })))]),
263
318
  _: 3
264
319
  })], 16)) : createCommentVNode("", true)]),
265
320
  _: 3
@@ -1 +1 @@
1
- {"version":3,"file":"RuiMenu.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_withKeys","_renderSlot","_createElementVNode","_unref","_normalizeProps","_guardReactiveProps","_createBlock","_Teleport","_createVNode","_TransitionGroup","_withCtx","_mergeProps","_createCommentVNode","RuiFormTextDetail"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type FormDetails, useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n /** With `showDetails`: `reserve` keeps the line while empty (default), `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n details = 'reserve',\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-rui-menu',\n // a hairline edge keeps the popup apart from a surface of nearly the same shade in dark mode\n content: 'rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1',\n details: '',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n 'aria-haspopup': get(ariaHasPopup),\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n *\n * Escape is swallowed only while the menu is open and not persistent\n * (`onLeave` stops the event itself); a closed or persistent menu must let it\n * through, or a consumer that closes its own editor on Escape never sees the\n * key. The root element listens for it.\n *\n * An Escape that arrives while an IME is composing belongs to the IME, which\n * cancels the candidate with it, so the menu leaves it alone the same way: a\n * search field inside the menu would otherwise lose the whole menu mid-word.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent || event.isComposing)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :details=\"details\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;AAwIA,IAAM,8BACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAnDF,MAAM,aAAaA,SAAoB,SAAA,YAAmB;EA8C1D,MAAM,QAAQ,IAAa,KAAK;EAChC,MAAM,cAAc,eAA4B,aAAa;EAC7D,MAAM,SAAS,MAAM;EAKrB,MAAM,EACJ,WAAW,WACX,SAAS,MACT,MACA,SACA,kBACA,cACA,gBACA,QACA,SACA,iBACA,mBACE,kBACI,QAAA,WAAW,CAAC,SACZ,QAAA,gBACA,QAAA,iBACA,QAAA,kBACA,QAAA,QACR;EAEA,MAAM,EAAE,UAAU,eAAe,SAAS;EAE1C,MAAM,EAAE,UAAU,eAAe,wBACzB,QAAA,qBACA,QAAA,eACR;EAEA,MAAM,aAAa,GAAG;GACpB,OAAO;IACL,SAAS;IACT,SAAS;IAET,SAAS;IACT,SAAS;GACX;GACA,UAAU;IACR,WAAW,EACT,MAAM,EAAE,SAAS,SAAS,EAC5B;IACA,OAAO;KACL,MAAM,EAAE,SAAS,OAAO;KACxB,OAAO,EAAE,SAAS,OAAO;IAC3B;GACF;GACA,iBAAiB;IAAE,WAAW;IAAO,OAAO;GAAM;EACpD,CAAC;EAED,MAAM,KAAK,eAA8C,WAAW;GAClE,WAAQ,QAAA;GACR,OAAI,QAAA;EACN,CAAC,CAAC;EAGF,MAAM,eAAe,eAAqC,QAAA,SAAS,SAAS,SAAS,QAAA,IAAI;;;;;;;;;;EAWzF,MAAM,gBAAgB,eAA8B;GAClD,MAAM,WAAW,IAAI,KAAK;GAC1B,OAAO;IACL,aAAa,QAAA,WACT,KAAA,UACM;KACJ,IAAI,QAAA,aACF,OAAO;IACX;IACJ,cAAc,QAAA,WACV,KAAA,UACM;KACJ,IAAI,QAAA,eAAe,CAAC,UAClB,QAAQ;IACZ;GACN;EACF,CAAC;EAED,MAAM,YAAY,gBAA2B;GAC3C,GAAG,IAAI,aAAa;GACpB,WAAW,QAAA,WAAW,KAAA,IAAY;GAClC,iBAAiB,IAAI,YAAY;GACjC,iBAAiB,IAAI,IAAI;GAEzB,iBAAiB,IAAI,OAAO,IAAI,SAAS,KAAA;EAC3C,EAAE;;;;;;EAOF,SAAS,iBAAiB;GACxB,MAAM,UAAU,IAAI,WAAW;GAC/B,IAAI,CAAC,SACH;GAEF,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;EACvC;EAEA,SAAS,YAAkB;GACzB,IAAI,QAAA,kBACF;GAEF,eAAe,eAAe,CAAC;EACjC;;EAGA,SAAS,mBAAmB;GAC1B,MAAM,cAAc,IAAI,SAAS;GACjC,IAAI,CAAC,aACH;GAEF,MAAM,cAAc,YAAY,cAA2B,2BAA2B;GACtF,IAAI,aACF,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;EAE7C;EAEA,SAAS,QAAQ,OAA6B;GAC5C,IAAI,CAAC,IAAI,IAAI,GACX;GACF,QAAQ;GACR,IAAI,OAAO,KAAK;GAChB,OAAO,gBAAgB;GAGvB,IAAI,QAAA,kBACF;GAGF,eAAe,iBAAiB,CAAC;EACnC;;;;;;;;;;;;;;;;;;;;;EAsBA,SAAS,SAAS,OAA4B;GAC5C,IAAI,QAAA,cAAc,MAAM,aACtB;GAEF,QAAQ,KAAK;EACf;EAEA,SAAS,aAAmB;GAC1B,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK,GACpB;QAAA,CAAC,QAAA,yBACH,QAAQ;GAAA,OAEP;IACH,OAAO;IACP,IAAI,OAAO,IAAI;GACjB;EACF;EAEA,MAAM,aAAa,UAAU;GAC3B,IAAI,IAAI,IAAI,MAAM,OAChB;GAEF,IAAI,OAAO;IACT,OAAO;IACP,IAAI,OAAO,IAAI;GACjB,OAEE,QAAQ;EAEZ,CAAC;EAED,MAAM,OAAO,SAAS;GACpB,IAAI,YAAY,IAAI;GACpB,IAAI,MACF,UAAU;EAEd,CAAC;EAED,eAAe,YAAY;GACzB,IAAI,IAAI,IAAI,KAAK,CAAC,QAAA,YAChB,QAAQ;EACZ,GAAG,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC;;GAIxB,OAAAC,UAAA,GAAAC,mBAiEM,OAAA;IAhEH,OAAKC,eAAE,QAAA,YAAY,IAAI;IACvB,WAAO,OAAA,OAAA,OAAA,KAAAC,UAAA,WAAM,SAAS,MAAM,GAAA,CAAA,KAAA,CAAA;;IAE7BC,WAAqB,KAAA,QAAA,OAAA;IACrBC,mBASM,OAAA;KARA,SAAA;KAAJ,KAAI;KACH,OAAKH,eAAEI,MAAA,EAAA,CAAE,CAAC,QAAO,EAAA,OAAUA,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,OAAO,EAAA,CAAA,CAAA;KACjD,sBAAoB,QAAA;IAErBF,GAAAA,CAAAA,WAGE,KAAA,QAAA,aAAAG,eAAAC,mBAAA;KAAA,OADiBF,MAAA,SAAA;KAAS,MAAEA,MAAA,IAAA;KAAI,UAAE,QAAA;KAAQ,UAAEA,MAAA,QAAA;KAAQ,YAAEA,MAAA,UAAA;IAAU,CAAA,CAAA,CAAA,CAAA,GAAA,IAAA,UAAA;IAI3D,CAAA,QAAA,YADTN,UAAA,GAAAS,YAyCWC,UAAA;;KAvCT,IAAG;IAGKJ,GAAAA,CAAAA,MAAA,OAAA,KADRN,UAAA,GAAAC,mBAoCM,OAAA;;KAlCH,IAAIK,MAAA,MAAA;KACD,SAAA;KAAJ,KAAI;KACH,OAAKJ,eAAEI,MAAA,EAAA,CAAE,CAAC,QAAO,EAAA,OAAUA,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,IAAI,EAAA,CAAA,CAAA;KAC9C,MAAM,QAAA;KACN,cAAY,QAAA;KACZ,kBAAgBA,MAAA,gBAAA;KAChB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,QAAA,sBAAsB,QAAO,IAAK,KAAA;KACzC,WAAO,OAAA,OAAA,OAAA,KAAAH,UAAA,WAAM,SAAS,MAAM,GAAA,CAAA,KAAA,CAAA;IAE7BQ,GAAAA,CAAAA,YAwBkBC,iBAAA;KAvBhB,sBAAmB;KACnB,oBAAiB;KACjB,kBAAe;KACf,sBAAmB;KACnB,oBAAiB;KACjB,kBAAe;KACd,eAAY,OAAA,OAAA,OAAA,MAAA,WAAEN,MAAA,cAAA,CAAc,CAAA;KAC5B,cAAW,OAAA,OAAA,OAAA,MAAA,WAAEA,MAAA,YAAA,IAAeA,MAAA,eAAA,CAAe,CAAA,IAAK,KAAA;KAChD,eAAY,OAAA,OAAA,OAAA,MAAA,WAAEA,MAAA,cAAA,CAAc,CAAA;;KAE7B,SAAAO,cAYM,CAXEP,MAAA,IAAA,KADRN,UAAA,GAAAC,mBAYM,OAZNa,WAYM;MAVA,SAAA;MAAJ,KAAI;MACJ,KAAI;MACJ,WAAQ;MACP,OAAOR,MAAA,EAAA,CAAE,CAAC,QAAO,EAAA,OAAUA,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,OAAO,EAAA,CAAA;MAClD,UAAS;KACD,GAAAA,MAAA,aAAA,CAAa,GAAA,CAErBK,YAEsBL,MAAA,mBAAA,GAAA,MAAA;MADpB,SAAAO,cAA2B,CAA3BT,WAA2B,KAAA,QAAA,WAAAG,eAAAC,mBAAA,EAAA,OAAXF,MAAA,KAAA,EAAK,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;IAOvB,QAAA,eADRN,UAAA,GAAAS,YAOEO,2BAAA;;KALC,SAAS,QAAA;KACT,OAAKd,eAAEI,MAAA,EAAA,CAAE,CAAC,QAAO,EAAA,OAAUA,MAAA,EAAA,CAAE,CAAC,QAAA,YAAY,OAAO,EAAA,CAAA,CAAA;KACjD,kBAAgB,QAAA;KAChB,oBAAkB,QAAA;KAClB,MAAM,QAAA"}
1
+ {"version":3,"file":"RuiMenu.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_withKeys","_renderSlot","_createElementVNode","_mergeProps","_unref","_toHandlers","_normalizeProps","_guardReactiveProps","_createBlock","_Teleport","_createVNode","_TransitionGroup","_withCtx","_createCommentVNode","RuiFormTextDetail"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { Reactive, Ref, WatchHandle } from 'vue';\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating, type UseFloatingReturn } from '@/composables/floating';\nimport { containsMessage, type FormDetails } from '@/utils/form-text-detail';\nimport { cn } from '@/utils/tv';\nimport { menuStyles } from './menu-styles';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n /** With `showDetails`: `reserve` keeps the line while empty (default), `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n /**\n * Defers the popover's setup (its positioning, the activator's size\n * observer and the click-outside listeners) to first use: a click or a\n * context menu on the activator, a hover with `openOnHover`, or the model\n * turning `true`. Until then the menu renders its root, the wrapper and the\n * activator, the same elements it keeps afterwards, so a table with a menu\n * on every row pays for the menus that are opened. Read at mount.\n */\n lazy?: boolean;\n}\n\n/** What the menu sets up at mount, or on first use when `lazy`, with its refs unwrapped. */\ntype MenuSetup = Reactive<{\n floating: UseFloatingReturn;\n width: Readonly<Ref<number>>;\n}>;\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n details = 'reserve',\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n lazy = false,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst activator = useTemplateRef<HTMLElement>('activator');\nconst menu = useTemplateRef<HTMLElement>('menu');\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\nconst instance = getCurrentInstance();\n// Owned by this component, so whatever the setup starts stops on unmount even when it ran after mount\nconst setupScope = effectScope();\nconst menuSetup = shallowRef<MenuSetup>();\nlet stopSetUpOnModel: WatchHandle | undefined;\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\n// Only the two flags, not the formatted lists useFormTextDetail builds, since a table may hold hundreds of menus\nconst hasError = computed<boolean>(() => containsMessage(errorMessages));\nconst hasSuccess = computed<boolean>(() => containsMessage(successMessages));\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\nconst open = computed<boolean>(() => get(menuSetup)?.floating.open ?? false);\nconst visible = computed<boolean>(() => get(menuSetup)?.floating.visible ?? false);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n setUp()?.onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n activeFloating()?.onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n // `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\n 'aria-haspopup': role === 'menu' ? 'true' : role,\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Sets a lazy menu up on its first use, on the wrapper so that it covers an\n * activator that opens the menu through the model rather than through\n * `attrs`. Hover and focus are left out: a cursor resting over a scrolling\n * table or Tab moving down it would set up every row it passes. They stay\n * after setup, where `setUp` only hands back what it already made.\n */\nconst firstUseListeners = { click: setUp, contextmenu: setUp };\n\n/**\n * Sets the popover up once and hands back its floating state. It runs at\n * mount, or for a `lazy` menu on first use, which can be after mount and\n * inside another component's lifecycle hook, so what it starts must not\n * attach itself to the current instance.\n */\nfunction setUp(): MenuSetup['floating'] | undefined {\n if (setupScope.active && !get(menuSetup)) {\n set(menuSetup, setupScope.run(createSetup));\n stopSetUpOnModel?.();\n }\n return activeFloating();\n}\n\n/**\n * The floating state while the menu is alive. An event can still reach the\n * activator after unmount, and must not start timers on a stopped menu.\n */\nfunction activeFloating(): MenuSetup['floating'] | undefined {\n return setupScope.active ? get(menuSetup)?.floating : undefined;\n}\n\n/** An element's content-box width, the box its resize observer reports. */\nfunction contentWidth(element: HTMLElement | null): number {\n if (!element)\n return 0;\n\n const style = getComputedStyle(element);\n const edges = [style.paddingLeft, style.paddingRight, style.borderLeftWidth, style.borderRightWidth];\n return edges.reduce((width, edge) => width - (Number.parseFloat(edge) || 0), element.offsetWidth);\n}\n\nfunction createSetup(): MenuSetup {\n const floating = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl ?? get(activator) ?? undefined,\n menu,\n );\n\n const width = shallowRef<number>(0);\n const readWidth = (): void => set(width, contentWidth(get(activator)));\n useResizeObserver(activator, ([entry]) => {\n if (entry)\n set(width, entry.contentRect.width);\n });\n // Only the menu's own setup waits for its mount; a later one may run inside another component's hook\n if (instance?.isMounted)\n readWidth();\n else\n onMounted(readWidth);\n\n // Immediate, so a model that is already `true` at setup opens the menu\n watch(modelValue, (value) => {\n if (get(floating.open) === value)\n return;\n\n if (value) {\n floating.onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n }, { immediate: true });\n\n watch(floating.open, (isOpen) => {\n set(modelValue, isOpen);\n if (isOpen) {\n focusMenu();\n }\n });\n\n onClickOutside(menu, () => {\n if (get(floating.open) && !persistent)\n onLeave();\n }, { ignore: [activator] });\n\n return reactive({ floating, width });\n}\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n const floating = activeFloating();\n if (!floating?.open)\n return;\n floating.onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n *\n * Escape is swallowed only while the menu is open and not persistent\n * (`onLeave` stops the event itself); a closed or persistent menu must let it\n * through, or a consumer that closes its own editor on Escape never sees the\n * key. The root element listens for it.\n *\n * An Escape that arrives while an IME is composing belongs to the IME, which\n * cancels the candidate with it, so the menu leaves it alone the same way: a\n * search field inside the menu would otherwise lose the whole menu mid-word.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent || event.isComposing)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n const floating = setUp();\n if (!floating)\n return;\n\n if (floating.open && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n floating.onOpen();\n set(click, true);\n }\n}\n\n// The setup's own watcher opens it; a disabled menu stays closed and unset, as an eager one stays closed\nif (!lazy || (get(modelValue) && !disabled)) {\n setUp();\n}\nelse {\n stopSetUpOnModel = watch(modelValue, (value) => {\n if (value && !disabled)\n setUp();\n });\n}\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n v-on=\"lazy && !disabled ? firstUseListeners : {}\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled && menuSetup\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"menuSetup.floating.currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"menuSetup?.floating.updatePosition()\"\n @after-leave=\"menuSetup?.floating.leavePending ? menuSetup.floating.onLeaveComplete() : undefined\"\n @before-leave=\"menuSetup?.floating.onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width: menuSetup.width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :details=\"details\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ export declare const menuStyles: import("tailwind-variants").TVReturnType<{
2
+ fullWidth: {
3
+ true: {
4
+ wrapper: string;
5
+ };
6
+ };
7
+ dense: {
8
+ true: {
9
+ details: string;
10
+ };
11
+ false: {
12
+ details: string;
13
+ };
14
+ };
15
+ }, {
16
+ wrapper: string;
17
+ popover: string;
18
+ content: string;
19
+ details: string;
20
+ }, undefined, {
21
+ fullWidth: {
22
+ true: {
23
+ wrapper: string;
24
+ };
25
+ };
26
+ dense: {
27
+ true: {
28
+ details: string;
29
+ };
30
+ false: {
31
+ details: string;
32
+ };
33
+ };
34
+ }, {
35
+ wrapper: string;
36
+ popover: string;
37
+ content: string;
38
+ details: string;
39
+ }, import("tailwind-variants").TVReturnTypeLike<{
40
+ fullWidth: {
41
+ true: {
42
+ wrapper: string;
43
+ };
44
+ };
45
+ dense: {
46
+ true: {
47
+ details: string;
48
+ };
49
+ false: {
50
+ details: string;
51
+ };
52
+ };
53
+ }, {
54
+ wrapper: string;
55
+ popover: string;
56
+ content: string;
57
+ details: string;
58
+ }>>;
@@ -0,0 +1,25 @@
1
+ import { tv } from "../../../utils/tv.js";
2
+ //#region src/components/overlays/menu/menu-styles.ts
3
+ var menuStyles = tv({
4
+ slots: {
5
+ wrapper: "relative inline-flex max-w-full",
6
+ popover: "w-max z-rui-menu",
7
+ content: "rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1",
8
+ details: ""
9
+ },
10
+ variants: {
11
+ fullWidth: { true: { wrapper: "w-full" } },
12
+ dense: {
13
+ true: { details: "px-2" },
14
+ false: { details: "px-4" }
15
+ }
16
+ },
17
+ defaultVariants: {
18
+ fullWidth: false,
19
+ dense: false
20
+ }
21
+ });
22
+ //#endregion
23
+ export { menuStyles };
24
+
25
+ //# sourceMappingURL=menu-styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menu-styles.js","names":[],"sources":["../../../../src/components/overlays/menu/menu-styles.ts"],"sourcesContent":["import { tv } from '@/utils/tv';\n\nexport const menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-rui-menu',\n // a hairline edge keeps the popup apart from a surface of nearly the same shade in dark mode\n content: 'rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1',\n details: '',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n"],"mappings":";;AAEA,IAAa,aAAa,GAAG;CAC3B,OAAO;EACL,SAAS;EACT,SAAS;EAET,SAAS;EACT,SAAS;CACX;CACA,UAAU;EACR,WAAW,EACT,MAAM,EAAE,SAAS,SAAS,EAC5B;EACA,OAAO;GACL,MAAM,EAAE,SAAS,OAAO;GACxB,OAAO,EAAE,SAAS,OAAO;EAC3B;CACF;CACA,iBAAiB;EAAE,WAAW;EAAO,OAAO;CAAM;AACpD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"RuiNavigationDrawer.js","names":["_sfc_main"],"sources":["../../../../src/components/overlays/navigation-drawer/RuiNavigationDrawer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ClassValue } from 'vue';\nimport type { VueClassValue } from '@/types/class-value';\nimport type { MaybeElement } from '@/types/dom';\nimport { useTimeoutManager } from '@/composables/timeout-manager';\nimport { getRootAttrs, transformPropsUnit } from '@/utils/helpers';\nimport { cn, tv } from '@/utils/tv';\n\nexport interface RuiNavigationDrawerClassNames {\n root?: VueClassValue;\n content?: VueClassValue;\n}\n\nexport interface NavigationDrawerProps {\n temporary?: boolean;\n stateless?: boolean;\n width?: string | number;\n miniVariant?: boolean;\n overlay?: boolean;\n position?: 'left' | 'right';\n classNames?: RuiNavigationDrawerClassNames;\n ariaLabel?: string;\n /**\n * Starts the drawer, and its overlay, below the app bar (`--rui-app-bar-height`), so the app bar\n * stays uncovered and usable while the drawer is open. Without it the drawer runs the full height and\n * a modal one covers the app bar.\n */\n belowAppBar?: boolean;\n}\n\ndefineOptions({\n name: 'RuiNavigationDrawer',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n temporary = false,\n stateless = false,\n width = 360,\n miniVariant = false,\n overlay = false,\n position = 'left',\n classNames,\n ariaLabel,\n belowAppBar = false,\n} = defineProps<NavigationDrawerProps>();\n\nconst emit = defineEmits<{\n closed: [];\n}>();\n\ndefineSlots<{\n activator?: (props: { open: boolean; attrs: { onClick: () => void } }) => any;\n default?: (props: { attrs: { onClick: () => void }; close: () => void }) => any;\n}>();\n\nconst transitioning = ref<boolean>(false);\nconst content = useTemplateRef<MaybeElement>('content');\nconst leaveTimeout = useTimeoutManager();\nconst clickOutsideTimeout = useTimeoutManager();\n\nconst alive = computed<boolean>(() => get(modelValue) || get(transitioning) || miniVariant);\n\nconst style = computed<{ width: string | undefined }>(() => ({\n width: transformPropsUnit(width),\n}));\n\nconst activatorAttrs: { onClick: () => void } = {\n onClick: toggle,\n};\n\nconst drawer = tv({\n base: 'transition-[transform,translate,width] duration-200 ease-in-out bottom-0 fixed text-rui-text',\n variants: {\n belowAppBar: {\n true: 'top-rui-app-bar',\n false: 'top-0',\n },\n // a temporary drawer floats on the overlay surface; a docked one is part of the page, edged by a hairline\n temporary: {\n true: 'bg-rui-overlay',\n false: 'bg-rui-surface',\n },\n position: {\n left: 'left-0',\n right: 'right-0',\n },\n visible: {\n true: 'translate-x-0',\n false: '',\n },\n mini: {\n true: 'translate-x-0',\n false: '',\n },\n // a modal drawer covers the app bar; a docked one sits level with it\n withOverlay: {\n true: 'z-rui-drawer',\n false: 'z-rui-app-bar',\n },\n },\n compoundVariants: [\n { position: 'left', visible: false, mini: false, class: '-translate-x-full' },\n { position: 'right', visible: false, mini: false, class: 'translate-x-full' },\n { mini: true, visible: false, class: 'w-14!' },\n { temporary: false, position: 'left', class: 'border-r border-rui-divider' },\n { temporary: false, position: 'right', class: 'border-l border-rui-divider' },\n ],\n defaultVariants: { position: 'left', visible: false, mini: false, withOverlay: false, temporary: false, belowAppBar: false },\n});\n\n/**\n * Where the drawer starts its slide in: it mounts open, so the closed position has to be put on it for\n * its first frame. Important, because the open position is already on the element.\n */\nconst enterFromClass = computed<string>(() => (position === 'left' ? '-translate-x-full!' : 'translate-x-full!'));\n\n// the overlay dims what the drawer covers: the whole view, or only the part below the app bar\nconst overlayClass = computed<string>(() => cn([\n 'fixed inset-x-0 bottom-0 backdrop-blur-sm bg-rui-neutral-950/40 dark:bg-black/60 z-rui-drawer',\n belowAppBar ? 'top-rui-app-bar' : 'top-0',\n]) ?? '');\n\n/**\n * Consumer classes go through `drawer()` rather than alongside it, so\n * tailwind-variants' twMerge resolves conflicts in the consumer's favour.\n * Passed as an argument instead of read off `$attrs` here, so the template\n * keeps using `$attrs` directly.\n */\nfunction rootClass(attrsClass: ClassValue): string {\n return drawer({\n position,\n visible: modelValue.value,\n mini: miniVariant,\n withOverlay: overlay,\n temporary,\n belowAppBar,\n class: cn([\n temporary && modelValue.value && 'shadow-rui-drawer',\n classNames?.content,\n classNames?.root,\n attrsClass,\n ]),\n });\n}\n\nfunction toggle(): void {\n set(modelValue, !get(modelValue));\n}\n\nfunction close(): void {\n set(modelValue, false);\n}\n\nfunction onLeaveComplete(): void {\n leaveTimeout.clear();\n if (get(transitioning)) {\n set(transitioning, false);\n emit('closed');\n }\n}\n\nwatch(modelValue, (value) => {\n if (!value) {\n set(transitioning, true);\n // Match CSS transition duration (200ms) + buffer\n leaveTimeout.create(onLeaveComplete, 250);\n }\n});\n\n// Debounced, so the activator's own click, which bubbles to the body, does not close it again\nonClickOutside(content, () => {\n if (get(modelValue) && temporary && !stateless) {\n clickOutsideTimeout.create(close, 50);\n }\n});\n</script>\n\n<template>\n <div>\n <slot\n name=\"activator\"\n v-bind=\"{ open: modelValue, attrs: activatorAttrs }\"\n />\n <Teleport to=\"body\">\n <Transition\n v-if=\"overlay\"\n enter-from-class=\"opacity-0\"\n enter-active-class=\"transition-opacity ease-out duration-200\"\n enter-to-class=\"opacity-100\"\n leave-from-class=\"opacity-100\"\n leave-active-class=\"transition-opacity ease-in duration-200\"\n leave-to-class=\"opacity-0\"\n >\n <div\n v-if=\"modelValue\"\n data-id=\"overlay\"\n :class=\"overlayClass\"\n @click.stop=\"close()\"\n />\n </Transition>\n <!-- leaves with no transition: it has already slid out, and its own would hold the removal for a transitionend that never comes -->\n <Transition\n :enter-from-class=\"enterFromClass\"\n leave-active-class=\"duration-0!\"\n >\n <aside\n v-if=\"alive\"\n ref=\"content\"\n data-id=\"drawer-content\"\n :style=\"style\"\n :data-visible=\"modelValue || undefined\"\n :data-position=\"position\"\n :data-mini=\"miniVariant || undefined\"\n :class=\"rootClass($attrs.class)\"\n :aria-label=\"ariaLabel\"\n v-bind=\"getRootAttrs($attrs, [])\"\n >\n <slot v-bind=\"{ attrs: activatorAttrs, close }\" />\n </aside>\n </Transition>\n </Teleport>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"RuiNavigationDrawer.js","names":["_sfc_main"],"sources":["../../../../src/components/overlays/navigation-drawer/RuiNavigationDrawer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ClassValue } from 'vue';\nimport type { VueClassValue } from '@/types/class-value';\nimport type { MaybeElement } from '@/types/dom';\nimport { useTimeoutManager } from '@/composables/timeout-manager';\nimport { getRootAttrs, transformPropsUnit } from '@/utils/helpers';\nimport { cn } from '@/utils/tv';\nimport { navigationDrawerStyles } from './navigation-drawer-styles';\n\nexport interface RuiNavigationDrawerClassNames {\n root?: VueClassValue;\n content?: VueClassValue;\n}\n\nexport interface NavigationDrawerProps {\n temporary?: boolean;\n stateless?: boolean;\n width?: string | number;\n miniVariant?: boolean;\n overlay?: boolean;\n position?: 'left' | 'right';\n classNames?: RuiNavigationDrawerClassNames;\n ariaLabel?: string;\n /**\n * Starts the drawer, and its overlay, below the app bar (`--rui-app-bar-height`), so the app bar\n * stays uncovered and usable while the drawer is open. Without it the drawer runs the full height and\n * a modal one covers the app bar.\n */\n belowAppBar?: boolean;\n}\n\ndefineOptions({\n name: 'RuiNavigationDrawer',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n temporary = false,\n stateless = false,\n width = 360,\n miniVariant = false,\n overlay = false,\n position = 'left',\n classNames,\n ariaLabel,\n belowAppBar = false,\n} = defineProps<NavigationDrawerProps>();\n\nconst emit = defineEmits<{\n closed: [];\n}>();\n\ndefineSlots<{\n activator?: (props: { open: boolean; attrs: { onClick: () => void } }) => any;\n default?: (props: { attrs: { onClick: () => void }; close: () => void }) => any;\n}>();\n\nconst transitioning = ref<boolean>(false);\nconst content = useTemplateRef<MaybeElement>('content');\nconst leaveTimeout = useTimeoutManager();\nconst clickOutsideTimeout = useTimeoutManager();\n\nconst alive = computed<boolean>(() => get(modelValue) || get(transitioning) || miniVariant);\n\nconst style = computed<{ width: string | undefined }>(() => ({\n width: transformPropsUnit(width),\n}));\n\nconst activatorAttrs: { onClick: () => void } = {\n onClick: toggle,\n};\n\n/**\n * Where the drawer starts its slide in: it mounts open, so the closed position has to be put on it for\n * its first frame. Important, because the open position is already on the element.\n */\nconst enterFromClass = computed<string>(() => (position === 'left' ? '-translate-x-full!' : 'translate-x-full!'));\n\n// the overlay dims what the drawer covers: the whole view, or only the part below the app bar\nconst overlayClass = computed<string>(() => cn([\n 'fixed inset-x-0 bottom-0 backdrop-blur-sm bg-rui-neutral-950/40 dark:bg-black/60 z-rui-drawer',\n belowAppBar ? 'top-rui-app-bar' : 'top-0',\n]) ?? '');\n\n/**\n * Consumer classes go through `navigationDrawerStyles()` rather than alongside it, so\n * tailwind-variants' twMerge resolves conflicts in the consumer's favour.\n * Passed as an argument instead of read off `$attrs` here, so the template\n * keeps using `$attrs` directly.\n */\nfunction rootClass(attrsClass: ClassValue): string {\n return navigationDrawerStyles({\n position,\n visible: modelValue.value,\n mini: miniVariant,\n withOverlay: overlay,\n temporary,\n belowAppBar,\n class: cn([\n temporary && modelValue.value && 'shadow-rui-drawer',\n classNames?.content,\n classNames?.root,\n attrsClass,\n ]),\n });\n}\n\nfunction toggle(): void {\n set(modelValue, !get(modelValue));\n}\n\nfunction close(): void {\n set(modelValue, false);\n}\n\nfunction onLeaveComplete(): void {\n leaveTimeout.clear();\n if (get(transitioning)) {\n set(transitioning, false);\n emit('closed');\n }\n}\n\nwatch(modelValue, (value) => {\n if (!value) {\n set(transitioning, true);\n // Match CSS transition duration (200ms) + buffer\n leaveTimeout.create(onLeaveComplete, 250);\n }\n});\n\n// Debounced, so the activator's own click, which bubbles to the body, does not close it again\nonClickOutside(content, () => {\n if (get(modelValue) && temporary && !stateless) {\n clickOutsideTimeout.create(close, 50);\n }\n});\n</script>\n\n<template>\n <div>\n <slot\n name=\"activator\"\n v-bind=\"{ open: modelValue, attrs: activatorAttrs }\"\n />\n <Teleport to=\"body\">\n <Transition\n v-if=\"overlay\"\n enter-from-class=\"opacity-0\"\n enter-active-class=\"transition-opacity ease-out duration-200\"\n enter-to-class=\"opacity-100\"\n leave-from-class=\"opacity-100\"\n leave-active-class=\"transition-opacity ease-in duration-200\"\n leave-to-class=\"opacity-0\"\n >\n <div\n v-if=\"modelValue\"\n data-id=\"overlay\"\n :class=\"overlayClass\"\n @click.stop=\"close()\"\n />\n </Transition>\n <!-- leaves with no transition: it has already slid out, and its own would hold the removal for a transitionend that never comes -->\n <Transition\n :enter-from-class=\"enterFromClass\"\n leave-active-class=\"duration-0!\"\n >\n <aside\n v-if=\"alive\"\n ref=\"content\"\n data-id=\"drawer-content\"\n :style=\"style\"\n :data-visible=\"modelValue || undefined\"\n :data-position=\"position\"\n :data-mini=\"miniVariant || undefined\"\n :class=\"rootClass($attrs.class)\"\n :aria-label=\"ariaLabel\"\n v-bind=\"getRootAttrs($attrs, [])\"\n >\n <slot v-bind=\"{ attrs: activatorAttrs, close }\" />\n </aside>\n </Transition>\n </Teleport>\n </div>\n</template>\n"],"mappings":""}