@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.
- package/dist/components/accordions/accordion/RuiAccordion.js.map +1 -1
- package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/accordions/accordion/accordion-styles.d.ts +1 -0
- package/dist/components/accordions/accordion/accordion-styles.js +7 -0
- package/dist/components/accordions/accordion/accordion-styles.js.map +1 -0
- package/dist/components/alerts/RuiAlert.js.map +1 -1
- package/dist/components/alerts/RuiAlert.vue_vue_type_script_setup_true_lang.js +1 -65
- package/dist/components/alerts/RuiAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/alerts/alert-styles.d.ts +145 -0
- package/dist/components/alerts/alert-styles.js +70 -0
- package/dist/components/alerts/alert-styles.js.map +1 -0
- package/dist/components/avatars/RuiAvatar.js.map +1 -1
- package/dist/components/avatars/RuiAvatar.vue_vue_type_script_setup_true_lang.js +1 -38
- package/dist/components/avatars/RuiAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/avatars/RuiAvatarGroup.js.map +1 -1
- package/dist/components/avatars/RuiAvatarGroup.vue_vue_type_script_setup_true_lang.js +1 -13
- package/dist/components/avatars/RuiAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/avatars/avatar-group-styles.d.ts +49 -0
- package/dist/components/avatars/avatar-group-styles.js +18 -0
- package/dist/components/avatars/avatar-group-styles.js.map +1 -0
- package/dist/components/avatars/avatar-styles.d.ts +181 -0
- package/dist/components/avatars/avatar-styles.js +43 -0
- package/dist/components/avatars/avatar-styles.js.map +1 -0
- package/dist/components/buttons/button/button-styles.js +1 -1
- package/dist/components/buttons/button/button-styles.js.map +1 -1
- package/dist/components/buttons/button-group/RuiButtonGroup.js.map +1 -1
- package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js +2 -138
- package/dist/components/buttons/button-group/RuiButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/buttons/button-group/button-group-styles.d.ts +142 -0
- package/dist/components/buttons/button-group/button-group-styles.js +143 -0
- package/dist/components/buttons/button-group/button-group-styles.js.map +1 -0
- package/dist/components/calendar/RuiCalendar.js.map +1 -1
- package/dist/components/calendar/RuiCalendar.vue_vue_type_script_setup_true_lang.js +1 -8
- package/dist/components/calendar/RuiCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/calendar/RuiCalendarGrid.js.map +1 -1
- package/dist/components/calendar/RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js +2 -22
- package/dist/components/calendar/RuiCalendarGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/calendar/RuiCalendarMenu.js.map +1 -1
- package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js +3 -14
- package/dist/components/calendar/RuiCalendarMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/calendar/calendar-grid-styles.d.ts +43 -0
- package/dist/components/calendar/calendar-grid-styles.js +26 -0
- package/dist/components/calendar/calendar-grid-styles.js.map +1 -0
- package/dist/components/calendar/calendar-menu-styles.d.ts +34 -0
- package/dist/components/calendar/calendar-menu-styles.js +17 -0
- package/dist/components/calendar/calendar-menu-styles.js.map +1 -0
- package/dist/components/calendar/calendar-styles.d.ts +16 -0
- package/dist/components/calendar/calendar-styles.js +13 -0
- package/dist/components/calendar/calendar-styles.js.map +1 -0
- package/dist/components/cards/RuiCard.js.map +1 -1
- package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js +3 -58
- package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/cards/RuiCardHeader.js.map +1 -1
- package/dist/components/cards/RuiCardHeader.vue_vue_type_script_setup_true_lang.js +2 -20
- package/dist/components/cards/RuiCardHeader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/cards/card-header-styles.d.ts +46 -0
- package/dist/components/cards/card-header-styles.js +24 -0
- package/dist/components/cards/card-header-styles.js.map +1 -0
- package/dist/components/cards/card-styles.d.ts +175 -0
- package/dist/components/cards/card-styles.js +62 -0
- package/dist/components/cards/card-styles.js.map +1 -0
- package/dist/components/chips/RuiChip.js.map +1 -1
- package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js +2 -241
- package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chips/chip-styles.d.ts +223 -0
- package/dist/components/chips/chip-styles.js +246 -0
- package/dist/components/chips/chip-styles.js.map +1 -0
- package/dist/components/color-picker/RuiColorBoard.js.map +1 -1
- package/dist/components/color-picker/RuiColorBoard.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/color-picker/RuiColorBoard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-picker/RuiColorHue.js.map +1 -1
- package/dist/components/color-picker/RuiColorHue.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/color-picker/RuiColorHue.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-picker/RuiColorPicker.js.map +1 -1
- package/dist/components/color-picker/RuiColorPicker.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/color-picker/RuiColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/color-picker/color-board-styles.d.ts +1 -0
- package/dist/components/color-picker/color-board-styles.js +7 -0
- package/dist/components/color-picker/color-board-styles.js.map +1 -0
- package/dist/components/color-picker/color-hue-styles.d.ts +1 -0
- package/dist/components/color-picker/color-hue-styles.js +7 -0
- package/dist/components/color-picker/color-hue-styles.js.map +1 -0
- package/dist/components/color-picker/color-picker-styles.d.ts +1 -0
- package/dist/components/color-picker/color-picker-styles.js +7 -0
- package/dist/components/color-picker/color-picker-styles.js.map +1 -0
- package/dist/components/forms/field-label/RuiFieldLabel.js.map +1 -1
- package/dist/components/forms/field-label/RuiFieldLabel.vue_vue_type_script_setup_true_lang.js +1 -11
- package/dist/components/forms/field-label/RuiFieldLabel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/field-label/field-label-styles.d.ts +43 -0
- package/dist/components/forms/field-label/field-label-styles.js +16 -0
- package/dist/components/forms/field-label/field-label-styles.js.map +1 -0
- package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/select/RuiSimpleSelect.js.map +1 -1
- package/dist/components/forms/select/RuiSimpleSelect.vue_vue_type_script_setup_true_lang.js +2 -11
- package/dist/components/forms/select/RuiSimpleSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/select/simple-select-styles.d.ts +2 -0
- package/dist/components/forms/select/simple-select-styles.js +15 -0
- package/dist/components/forms/select/simple-select-styles.js.map +1 -0
- package/dist/components/forms/switch/RuiSwitch.js.map +1 -1
- package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js +1 -126
- package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/switch/switch-styles.d.ts +151 -0
- package/dist/components/forms/switch/switch-styles.js +131 -0
- package/dist/components/forms/switch/switch-styles.js.map +1 -0
- package/dist/components/icons/RuiIcon.js.map +1 -1
- package/dist/components/icons/RuiIcon.vue_vue_type_script_setup_true_lang.js +2 -28
- package/dist/components/icons/RuiIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/icons/icon-styles.d.ts +44 -0
- package/dist/components/icons/icon-styles.js +33 -0
- package/dist/components/icons/icon-styles.js.map +1 -0
- package/dist/components/loaders/RuiSkeletonBase.js.map +1 -1
- package/dist/components/loaders/RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js +2 -12
- package/dist/components/loaders/RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/loaders/RuiSkeletonLoader.js.map +1 -1
- package/dist/components/loaders/RuiSkeletonLoader.vue_vue_type_script_setup_true_lang.js +3 -13
- package/dist/components/loaders/RuiSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/loaders/skeleton-base-styles.d.ts +25 -0
- package/dist/components/loaders/skeleton-base-styles.js +16 -0
- package/dist/components/loaders/skeleton-base-styles.js.map +1 -0
- package/dist/components/loaders/skeleton-loader-styles.d.ts +37 -0
- package/dist/components/loaders/skeleton-loader-styles.js +18 -0
- package/dist/components/loaders/skeleton-loader-styles.js.map +1 -0
- package/dist/components/overlays/badge/RuiBadge.js.map +1 -1
- package/dist/components/overlays/badge/RuiBadge.vue_vue_type_script_setup_true_lang.js +1 -83
- package/dist/components/overlays/badge/RuiBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/badge/badge-styles.d.ts +193 -0
- package/dist/components/overlays/badge/badge-styles.js +88 -0
- package/dist/components/overlays/badge/badge-styles.js.map +1 -0
- package/dist/components/overlays/dialog/RuiDialog.js.map +1 -1
- package/dist/components/overlays/dialog/RuiDialog.vue_vue_type_script_setup_true_lang.js +3 -14
- package/dist/components/overlays/dialog/RuiDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/dialog/dialog-styles.d.ts +40 -0
- package/dist/components/overlays/dialog/dialog-styles.js +18 -0
- package/dist/components/overlays/dialog/dialog-styles.js.map +1 -0
- package/dist/components/overlays/menu/RuiMenu.js.map +1 -1
- package/dist/components/overlays/menu/RuiMenu.vue.d.ts +9 -0
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js +113 -58
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/menu/menu-styles.d.ts +58 -0
- package/dist/components/overlays/menu/menu-styles.js +25 -0
- package/dist/components/overlays/menu/menu-styles.js.map +1 -0
- package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.js.map +1 -1
- package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue_vue_type_script_setup_true_lang.js +4 -69
- package/dist/components/overlays/navigation-drawer/RuiNavigationDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/navigation-drawer/navigation-drawer-styles.d.ts +76 -0
- package/dist/components/overlays/navigation-drawer/navigation-drawer-styles.js +72 -0
- package/dist/components/overlays/navigation-drawer/navigation-drawer-styles.js.map +1 -0
- package/dist/components/overlays/notification/RuiNotification.js.map +1 -1
- package/dist/components/overlays/notification/RuiNotification.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/overlays/notification/RuiNotification.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/notification/notification-styles.d.ts +1 -0
- package/dist/components/overlays/notification/notification-styles.js +7 -0
- package/dist/components/overlays/notification/notification-styles.js.map +1 -0
- package/dist/components/overlays/tooltip/RuiTooltip.js.map +1 -1
- package/dist/components/overlays/tooltip/RuiTooltip.vue.d.ts +4 -2
- package/dist/components/overlays/tooltip/RuiTooltip.vue_vue_type_script_setup_true_lang.js +42 -25
- package/dist/components/overlays/tooltip/RuiTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/tooltip/tooltip-styles.d.ts +2 -0
- package/dist/components/overlays/tooltip/tooltip-styles.js +3 -1
- package/dist/components/overlays/tooltip/tooltip-styles.js.map +1 -1
- package/dist/components/progress/RuiProgress.js.map +1 -1
- package/dist/components/progress/RuiProgress.vue_vue_type_script_setup_true_lang.js +1 -133
- package/dist/components/progress/RuiProgress.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/progress/progress-styles.d.ts +181 -0
- package/dist/components/progress/progress-styles.js +139 -0
- package/dist/components/progress/progress-styles.js.map +1 -0
- package/dist/components/steppers/RuiFooterStepper.js.map +1 -1
- package/dist/components/steppers/RuiFooterStepper.vue_vue_type_script_setup_true_lang.js +3 -13
- package/dist/components/steppers/RuiFooterStepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/steppers/RuiStepper.js.map +1 -1
- package/dist/components/steppers/RuiStepper.vue_vue_type_script_setup_true_lang.js +4 -94
- package/dist/components/steppers/RuiStepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/steppers/RuiStepperIcon.js.map +1 -1
- package/dist/components/steppers/RuiStepperIcon.vue_vue_type_script_setup_true_lang.js +2 -34
- package/dist/components/steppers/RuiStepperIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/steppers/footer-stepper-styles.d.ts +28 -0
- package/dist/components/steppers/footer-stepper-styles.js +16 -0
- package/dist/components/steppers/footer-stepper-styles.js.map +1 -0
- package/dist/components/steppers/stepper-icon-styles.d.ts +60 -0
- package/dist/components/steppers/stepper-icon-styles.js +39 -0
- package/dist/components/steppers/stepper-icon-styles.js.map +1 -0
- package/dist/components/steppers/stepper-styles.d.ts +202 -0
- package/dist/components/steppers/stepper-styles.js +98 -0
- package/dist/components/steppers/stepper-styles.js.map +1 -0
- package/dist/components/tables/RuiTable.js.map +1 -1
- package/dist/components/tables/RuiTable.vue_vue_type_script_setup_true_lang.js +1 -32
- package/dist/components/tables/RuiTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTableEmptyState.js.map +1 -1
- package/dist/components/tables/RuiTableEmptyState.vue_vue_type_script_setup_true_lang.js +1 -15
- package/dist/components/tables/RuiTableEmptyState.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTableErrorState.js.map +1 -1
- package/dist/components/tables/RuiTableErrorState.vue_vue_type_script_setup_true_lang.js +2 -5
- package/dist/components/tables/RuiTableErrorState.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTableHead.js.map +1 -1
- package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js +1 -34
- package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTableLoadingState.js.map +1 -1
- package/dist/components/tables/RuiTableLoadingState.vue_vue_type_script_setup_true_lang.js +1 -8
- package/dist/components/tables/RuiTableLoadingState.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTablePagination.js.map +1 -1
- package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js +1 -30
- package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/table-empty-state-styles.d.ts +43 -0
- package/dist/components/tables/table-empty-state-styles.js +20 -0
- package/dist/components/tables/table-empty-state-styles.js.map +1 -0
- package/dist/components/tables/table-error-state-styles.d.ts +36 -0
- package/dist/components/tables/table-error-state-styles.js +15 -0
- package/dist/components/tables/table-error-state-styles.js.map +1 -0
- package/dist/components/tables/table-head-styles.d.ts +91 -0
- package/dist/components/tables/table-head-styles.js +39 -0
- package/dist/components/tables/table-head-styles.js.map +1 -0
- package/dist/components/tables/table-loading-state-styles.d.ts +34 -0
- package/dist/components/tables/table-loading-state-styles.js +13 -0
- package/dist/components/tables/table-loading-state-styles.js.map +1 -0
- package/dist/components/tables/table-pagination-styles.d.ts +82 -0
- package/dist/components/tables/table-pagination-styles.js +35 -0
- package/dist/components/tables/table-pagination-styles.js.map +1 -0
- package/dist/components/tables/table-styles.d.ts +62 -0
- package/dist/components/tables/table-styles.js +37 -0
- package/dist/components/tables/table-styles.js.map +1 -0
- package/dist/components/tabs/tab/RuiTab.js.map +1 -1
- package/dist/components/tabs/tab/RuiTab.vue_vue_type_script_setup_true_lang.js +3 -84
- package/dist/components/tabs/tab/RuiTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tabs/tab/tab-styles.d.ts +153 -0
- package/dist/components/tabs/tab/tab-styles.js +89 -0
- package/dist/components/tabs/tab/tab-styles.js.map +1 -0
- package/dist/components/tabs/tabs/RuiTabs.js.map +1 -1
- package/dist/components/tabs/tabs/RuiTabs.vue_vue_type_script_setup_true_lang.js +2 -137
- package/dist/components/tabs/tabs/RuiTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tabs/tabs/tabs-styles.d.ts +168 -0
- package/dist/components/tabs/tabs/tabs-styles.js +142 -0
- package/dist/components/tabs/tabs/tabs-styles.js.map +1 -0
- package/dist/composables/floating.d.ts +3 -1
- package/dist/composables/floating.js +8 -9
- package/dist/composables/floating.js.map +1 -1
- package/dist/icons/icons_1.d.ts +1 -1
- package/dist/icons/icons_1.js +10 -9
- package/dist/icons/icons_1.js.map +1 -1
- package/dist/icons/icons_2.d.ts +1 -1
- package/dist/icons/icons_2.js +9 -11
- package/dist/icons/icons_2.js.map +1 -1
- package/dist/icons/icons_3.d.ts +1 -1
- package/dist/icons/icons_3.js +11 -5
- package/dist/icons/icons_3.js.map +1 -1
- package/dist/icons/icons_4.d.ts +1 -1
- package/dist/icons/icons_4.js +5 -10
- package/dist/icons/icons_4.js.map +1 -1
- package/dist/icons/icons_5.d.ts +1 -0
- package/dist/icons/icons_5.js +10 -1
- package/dist/icons/icons_5.js.map +1 -1
- package/dist/icons/index.d.ts +1 -1
- package/dist/icons/index.js +7 -6
- package/dist/icons/index.js.map +1 -1
- package/dist/index.js +6 -6
- package/dist/style.css +8 -0
- package/dist/utils/form-text-detail.d.ts +2 -0
- package/dist/utils/form-text-detail.js +5 -1
- package/dist/utils/form-text-detail.js.map +1 -1
- package/dist/web-types.json +11 -1
- 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,
|
|
3
|
-
import { cn
|
|
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 {
|
|
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 {
|
|
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
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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":
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
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:
|
|
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
|
-
})))],
|
|
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
|
|
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":"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"}
|
|
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
|
|
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":""}
|