vectis-ui 0.8.1 → 0.9.1
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/README.md +3 -3
- package/dist/components/VAccordion/VAccordionItem.d.ts +4 -6
- package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js +21 -19
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/context.d.ts +6 -1
- package/dist/components/VAccordion/context.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.css +1 -1
- package/dist/components/VAvatar/VAvatar.d.ts +5 -3
- package/dist/components/VAvatar/VAvatar.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +53 -39
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.d.ts +12 -3
- package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +27 -20
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/context.d.ts +2 -2
- package/dist/components/VAvatar/context.js.map +1 -1
- package/dist/components/VBadge/VBadge.css +1 -1
- package/dist/components/VBadge/VBadge.d.ts +8 -4
- package/dist/components/VBadge/VBadge.js.map +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +5 -4
- package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +8 -3
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButton.css +1 -1
- package/dist/components/VButton/VButton.d.ts +4 -1
- package/dist/components/VButton/VButton.js.map +1 -1
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +8 -7
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.css +1 -1
- package/dist/components/VCalendar/VCalendar.d.ts +15 -6
- package/dist/components/VCalendar/VCalendar.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +144 -126
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.css +1 -1
- package/dist/components/VCalendar/VCalendarEvent.d.ts +1 -1
- package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.css +1 -1
- package/dist/components/VCalendar/VCalendarMonth.d.ts +1 -1
- package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +99 -90
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.css +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +3 -3
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +256 -246
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.css +1 -1
- package/dist/components/VCalendar/VCalendarYear.d.ts +8 -1
- package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js +39 -32
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/color.d.ts +1 -1
- package/dist/components/VCalendar/color.js.map +1 -1
- package/dist/components/VCalendar/edgeStep.d.ts +3 -3
- package/dist/components/VCalendar/edgeStep.js +5 -0
- package/dist/components/VCalendar/edgeStep.js.map +1 -1
- package/dist/components/VCalendar/gesture.d.ts +10 -9
- package/dist/components/VCalendar/gesture.js +60 -45
- package/dist/components/VCalendar/gesture.js.map +1 -1
- package/dist/components/VCalendar/keyboard.d.ts +22 -5
- package/dist/components/VCalendar/keyboard.js +17 -16
- package/dist/components/VCalendar/keyboard.js.map +1 -1
- package/dist/components/VCalendar/layout.d.ts +47 -37
- package/dist/components/VCalendar/layout.js +47 -37
- package/dist/components/VCalendar/layout.js.map +1 -1
- package/dist/components/VCalendar/timeGrid.d.ts +4 -4
- package/dist/components/VCalendar/timeGrid.js +6 -6
- package/dist/components/VCalendar/timeGrid.js.map +1 -1
- package/dist/components/VCalendar/types.d.ts +5 -5
- package/dist/components/VCarousel/VCarousel.css +1 -1
- package/dist/components/VCarousel/VCarousel.d.ts +59 -38
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +161 -132
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.d.ts +1 -1
- package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/context.d.ts +1 -1
- package/dist/components/VCarousel/context.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.css +1 -1
- package/dist/components/VCheckbox/VCheckbox.d.ts +2 -2
- package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +10 -8
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VChip/VChip.css +1 -1
- package/dist/components/VChip/VChip.d.ts +7 -2
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +69 -55
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.css +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +50 -36
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +167 -161
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.css +1 -1
- package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/infiniteScroll.d.ts +12 -3
- package/dist/components/VCombobox/infiniteScroll.js +18 -12
- package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.css +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +55 -24
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +192 -155
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.css +1 -1
- package/dist/components/VDateInput/VDateInput.d.ts +15 -12
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +180 -160
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/mask.d.ts +95 -0
- package/dist/components/VDateInput/mask.js +118 -0
- package/dist/components/VDateInput/mask.js.map +1 -0
- package/dist/components/VDatePicker/VDatePicker.css +1 -1
- package/dist/components/VDatePicker/VDatePicker.d.ts +6 -5
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +204 -173
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/keyboard.d.ts +3 -3
- package/dist/components/VDatePicker/keyboard.js.map +1 -1
- package/dist/components/VDatePicker/names.d.ts +34 -0
- package/dist/components/VDatePicker/names.js +42 -0
- package/dist/components/VDatePicker/names.js.map +1 -0
- package/dist/components/VDialog/VDialog.css +1 -1
- package/dist/components/VDialog/VDialog.d.ts +2 -2
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +63 -41
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +38 -25
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +74 -71
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/truncate.d.ts +4 -4
- package/dist/components/VFileInput/truncate.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.css +1 -1
- package/dist/components/VFilePicker/VFilePicker.d.ts +54 -37
- package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +152 -135
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/fileKind.d.ts +2 -2
- package/dist/components/VFilePicker/fileKind.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.css +1 -1
- package/dist/components/VHotkeys/VHotkeys.d.ts +10 -8
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +22 -20
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.d.ts +9 -7
- package/dist/components/VHotkeys/platform.js +41 -30
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VIcon/VIcon.css +1 -1
- package/dist/components/VIcon/VIcon.d.ts +6 -6
- package/dist/components/VIcon/VIcon.js.map +1 -1
- package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VIcon/iconProps.d.ts +4 -4
- package/dist/components/VIcon/iconProps.js.map +1 -1
- package/dist/components/VIcon/icons/index.d.ts +1 -1
- package/dist/components/VIcon/icons/index.js.map +1 -1
- package/dist/components/VIcon/icons/names.d.ts +1 -1
- package/dist/components/VIcon/icons/names.js.map +1 -1
- package/dist/components/VIcon/resolver.d.ts +3 -3
- package/dist/components/VIcon/resolver.js +23 -20
- package/dist/components/VIcon/resolver.js.map +1 -1
- package/dist/components/VIcon/types.d.ts +4 -4
- package/dist/components/VIconButton/VIconButton.d.ts +4 -4
- package/dist/components/VIconButton/VIconButton.js.map +1 -1
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInput.d.ts +16 -8
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +22 -16
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.css +1 -1
- package/dist/components/VInput/VInputGroup.js.map +1 -1
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +43 -28
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.css +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +3 -3
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +152 -85
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenu.d.ts +4 -4
- package/dist/components/VMenu/VMenu.js.map +1 -1
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.css +1 -1
- package/dist/components/VMenu/VMenuGroup.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js +18 -11
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.css +1 -1
- package/dist/components/VMenu/VMenuItem.d.ts +6 -6
- package/dist/components/VMenu/VMenuItem.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +83 -69
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.css +1 -1
- package/dist/components/VMenu/VMenuPanel.d.ts +2 -2
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +56 -55
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/context.d.ts +2 -2
- package/dist/components/VMenu/context.js.map +1 -1
- package/dist/components/VPagination/VPagination.css +1 -1
- package/dist/components/VPagination/VPagination.d.ts +31 -9
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +82 -52
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VPopover/VPopover.css +1 -1
- package/dist/components/VPopover/VPopover.d.ts +1 -1
- package/dist/components/VPopover/VPopover.js.map +1 -1
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.css +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.d.ts +12 -7
- package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.css +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.d.ts +15 -10
- package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VRadio/VRadio.css +1 -1
- package/dist/components/VRadio/VRadio.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.d.ts +1 -1
- package/dist/components/VSeparator/VSeparator.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.d.ts +2 -2
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +12 -13
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +87 -68
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.css +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +10 -7
- package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSlider/VSlider.css +1 -1
- package/dist/components/VSlider/VSlider.d.ts +21 -7
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +159 -140
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.css +1 -1
- package/dist/components/VSnackbar/VSnackbar.d.ts +4 -4
- package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +85 -59
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/state.d.ts +7 -7
- package/dist/components/VSnackbar/state.js +13 -11
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.css +1 -1
- package/dist/components/VSpinner/VSpinner.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.css +1 -1
- package/dist/components/VSwitch/VSwitch.d.ts +1 -1
- package/dist/components/VSwitch/VSwitch.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTab.css +1 -1
- package/dist/components/VTabs/VTab.d.ts +4 -0
- package/dist/components/VTabs/VTab.js.map +1 -1
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +9 -8
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabs.css +1 -1
- package/dist/components/VTabs/VTabs.d.ts +17 -7
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +84 -77
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +8 -8
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +71 -69
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.css +1 -1
- package/dist/components/VTimeInput/VTimeInput.d.ts +21 -12
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +230 -206
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/search.d.ts +17 -2
- package/dist/components/VTimeInput/search.js +19 -9
- package/dist/components/VTimeInput/search.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.css +1 -1
- package/dist/components/VTimePicker/VTimePicker.d.ts +8 -7
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +128 -106
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/limits.d.ts +11 -4
- package/dist/components/VTimePicker/limits.js +3 -2
- package/dist/components/VTimePicker/limits.js.map +1 -1
- package/dist/components/VToast/VToast.css +1 -1
- package/dist/components/VToast/VToast.js.map +1 -1
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +33 -37
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/VToaster.js.map +1 -1
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +91 -61
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/state.d.ts +8 -6
- package/dist/components/VToast/state.js +15 -13
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/components/VToggle/VToggle.css +1 -1
- package/dist/components/VToggle/VToggle.d.ts +9 -4
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.d.ts +4 -0
- package/dist/components/VToggle/VToggleItem.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.css +1 -1
- package/dist/components/VTooltip/VTooltip.d.ts +17 -10
- package/dist/components/VTooltip/VTooltip.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +46 -26
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTypography/VTypography.css +1 -1
- package/dist/components/VTypography/VTypography.d.ts +6 -4
- package/dist/components/VTypography/VTypography.js.map +1 -1
- package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/useAriaLabel.js +1 -1
- package/dist/composables/useAriaLabel.js.map +1 -1
- package/dist/composables/useClearable.d.ts +2 -2
- package/dist/composables/useClearable.js.map +1 -1
- package/dist/composables/useDetailsOpen.d.ts +3 -3
- package/dist/composables/useDetailsOpen.js +14 -12
- package/dist/composables/useDetailsOpen.js.map +1 -1
- package/dist/composables/useFieldIds.d.ts +2 -1
- package/dist/composables/useFieldIds.js +6 -5
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.d.ts +2 -2
- package/dist/composables/useFieldPanel.js +47 -37
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileDrop.js +3 -3
- package/dist/composables/useFileDrop.js.map +1 -1
- package/dist/composables/useFileField.d.ts +1 -1
- package/dist/composables/useFileField.js +62 -41
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useIconClickHandlers.d.ts +3 -3
- package/dist/composables/useIconClickHandlers.js +14 -13
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/composables/useInertLink.js +12 -10
- package/dist/composables/useInertLink.js.map +1 -1
- package/dist/composables/useLiveAnnouncer.d.ts +5 -0
- package/dist/composables/useLiveAnnouncer.js +23 -0
- package/dist/composables/useLiveAnnouncer.js.map +1 -0
- package/dist/composables/useMaskedField.d.ts +5 -5
- package/dist/composables/useMaskedField.js.map +1 -1
- package/dist/composables/usePopover.d.ts +3 -3
- package/dist/composables/usePopover.js +6 -4
- package/dist/composables/usePopover.js.map +1 -1
- package/dist/composables/useProgressValue.js +11 -2
- package/dist/composables/useProgressValue.js.map +1 -1
- package/dist/composables/useRootAttrs.d.ts +1 -1
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useSlotNodes.d.ts +18 -0
- package/dist/composables/useSlotNodes.js +13 -0
- package/dist/composables/useSlotNodes.js.map +1 -0
- package/dist/composables/useTextLimit.d.ts +2 -1
- package/dist/composables/useTextLimit.js +2 -2
- package/dist/composables/useTextLimit.js.map +1 -1
- package/dist/composables/useTimer.d.ts +5 -0
- package/dist/composables/useTimer.js +22 -11
- package/dist/composables/useTimer.js.map +1 -1
- package/dist/i18n/en.js +1 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js +14 -14
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.d.ts +7 -7
- package/dist/i18n/state.js +37 -28
- package/dist/i18n/state.js.map +1 -1
- package/dist/i18n/types.d.ts +12 -12
- package/dist/icons.d.ts +2 -2
- package/dist/index.d.ts +12 -11
- package/dist/styles.css +1 -1
- package/dist/tokens/css.d.ts +1 -1
- package/dist/tokens/css.js.map +1 -1
- package/dist/tokens/index.d.ts +6 -0
- package/dist/tokens/semantic.d.ts +6 -0
- package/dist/tokens/semantic.js +66 -60
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.js +3 -3
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens.json +130 -66
- package/dist/types.d.ts +1 -1
- package/dist/utils/arrowNav.d.ts +4 -1
- package/dist/utils/arrowNav.js +7 -7
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/chip.d.ts +1 -1
- package/dist/utils/chip.js.map +1 -1
- package/dist/utils/clock.d.ts +113 -0
- package/dist/utils/clock.js +84 -0
- package/dist/utils/clock.js.map +1 -0
- package/dist/utils/css.d.ts +6 -4
- package/dist/utils/css.js +2 -2
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/date.d.ts +7 -102
- package/dist/utils/date.js +33 -160
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/direction.d.ts +1 -1
- package/dist/utils/direction.js.map +1 -1
- package/dist/utils/env.d.ts +12 -3
- package/dist/utils/env.js +6 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/file.d.ts +3 -3
- package/dist/utils/file.js +1 -1
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/focus.d.ts +1 -1
- package/dist/utils/focus.js.map +1 -1
- package/dist/utils/hostWarns.d.ts +1 -1
- package/dist/utils/hostWarns.js.map +1 -1
- package/dist/utils/ids.d.ts +1 -1
- package/dist/utils/ids.js.map +1 -1
- package/dist/utils/matcher.d.ts +2 -2
- package/dist/utils/matcher.js.map +1 -1
- package/dist/utils/memo.d.ts +1 -1
- package/dist/utils/memo.js.map +1 -1
- package/dist/utils/text.d.ts +3 -3
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/time.d.ts +2 -101
- package/dist/utils/time.js +22 -98
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/vnode.d.ts +5 -3
- package/dist/utils/vnode.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMenuGroup.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VMenu/VMenuGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n
|
|
1
|
+
{"version":3,"file":"VMenuGroup.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VMenu/VMenuGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A named section inside a menu: \"Sort by\", \"Recent files\". The name is announced as\n * the group's label rather than being read as one more item, and it is plain text: it\n * cannot be chosen, and the arrow keys walk straight past it.\n */\n\nimport { useId, useSlots, watchEffect } from 'vue'\n\nimport { isDev } from '../../utils/env'\n\ninterface MenuGroupProps {\n /**\n * The name of the section, replaced by the `#label` slot. One of the two is REQUIRED:\n * it is what names the group, and a section named by neither points its\n * `aria-labelledby` at an empty element. It is a heading, not a command: nothing\n * happens on click.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<MenuGroupProps>(), {\n label: undefined,\n})\n\ndefineSlots<{\n /** The commands belonging to this section. */\n default(): unknown\n /** A name made of markup, replacing the `label` prop. */\n label?(): unknown\n}>()\n\n// @a11y @ssr\n// The generated id ties the group to its own label, and matches between server and\n// client by construction, Vue's `useId` guaranteeing exactly that.\nconst labelId = useId()\n\n// @devwarn\n// A group named by neither points its `aria-labelledby` at an empty element, and a\n// screen reader announces an unnamed group with nothing on screen to say so.\nif (isDev) {\n const slots = useSlots()\n watchEffect(() => {\n if (!props.label && !slots.label)\n console.warn('[VMenuGroup] needs a name: give it a `label` or a `#label` slot.')\n })\n}\n</script>\n\n<template>\n <div role=\"group\" class=\"v-menu-group\" :aria-labelledby=\"labelId\">\n <span :id=\"labelId\" class=\"v-menu-group-label\"\n ><slot name=\"label\">{{ label }}</slot></span\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-menu-group {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n }\n\n .v-menu-group-label {\n display: flex;\n align-items: center;\n min-block-size: var(--control-height);\n padding-block: var(--vectis-space-1);\n padding-inline: var(--control-padding-inline);\n font-size: var(--vectis-text-overline-size);\n font-weight: var(--vectis-text-overline-weight);\n letter-spacing: var(--vectis-text-overline-tracking);\n color: var(--vectis-color-text-muted);\n }\n}\n</style>\n"],"mappings":";;;;;;;EAqBA,IAAM,IAAQ,GAcR,IAAU,EAAM;EAKtB,IAAI,GAAO;GACT,IAAM,IAAQ,EAAS;GACvB,QAAkB;IAChB,AAAI,CAAC,EAAM,SAAS,CAAC,EAAM,SACzB,QAAQ,KAAK,kEAAkE;GACnF,CAAC;EACH;yBAIE,EAKM,OAAA;GALD,MAAK;GAAQ,OAAM;GAAgB,mBAAiB,EAAA,CAAA;MACvD,EAEC,QAAA;GAFM,IAAI,EAAA,CAAA;GAAS,OAAM;MACvB,EAAqC,EAAA,QAAA,SAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,GAAA,CAAA,GAE9B,EAAQ,EAAA,QAAA,SAAA,CAAA,GAAA,GAAA,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
@layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
|
|
2
|
-
@layer vectis.components{.v-menu-item{align-items:center;gap:var(--control-gap);inline-size:100%;min-block-size:var(--control-height);padding-block:var(--vectis-space-1);padding-inline:var(--control-padding-inline);color:var(--vectis-color-text);border-radius:min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));font-family:inherit;font-size:var(--control-font-size);line-height:var(--vectis-text-body-md-leading);text-align:start;cursor:pointer;background:0 0;border:none;text-decoration:none;display:flex}.v-menu-item-content{flex-direction:column;flex:1;min-inline-size:0;display:flex}.v-menu-item-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.v-menu-item-sublabel{font-size:var(--vectis-text-caption-size);color:var(--vectis-color-text-muted);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.v-menu-item-icon,.v-menu-item-chevron{color:var(--vectis-color-text-muted);flex:none}.v-menu-item:hover:not(:disabled,[aria-disabled=true]),.v-menu-item:focus,.v-menu-item[aria-expanded=true]{background:var(--vectis-color-surface-muted);outline:none}.v-menu-item[data-selected]{background:var(--vectis-color-accent-surface);color:var(--vectis-color-accent-text)}.v-menu-item[data-selected]:hover:not(:disabled,[aria-disabled=true]),.v-menu-item[data-selected]:focus,.v-menu-item[data-selected][aria-expanded=true]{background:color-mix(in oklab, var(--vectis-color-accent-surface), var(--vectis-color-accent-text) 8%)}.v-menu-item[data-tone=danger]{color:var(--vectis-color-danger-text)}.v-menu-item[data-tone=danger]:hover:not(:disabled,[aria-disabled=true]),.v-menu-item[data-tone=danger]:focus,.v-menu-item[data-tone=danger][aria-expanded=true]{background:var(--vectis-color-danger-surface)}.v-menu-item:disabled,.v-menu-item[aria-disabled=true]{color:var(--vectis-color-text-subtle);cursor:not-allowed;background:0 0}.v-menu-item:is([data-selected],[data-tone=danger],:disabled,[aria-disabled=true]) :is(.v-menu-item-icon,.v-menu-item-sublabel){color:inherit}}
|
|
2
|
+
@layer vectis.components{.v-menu-item{align-items:center;gap:var(--control-gap);inline-size:100%;min-block-size:var(--control-height);padding-block:var(--vectis-space-1);padding-inline:var(--control-padding-inline);color:var(--vectis-color-text);border-radius:min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));font-family:inherit;font-size:var(--control-font-size);line-height:var(--vectis-text-body-md-leading);text-align:start;cursor:pointer;background:0 0;border:none;text-decoration:none;display:flex}.v-menu-item-content{flex-direction:column;flex:1;min-inline-size:0;display:flex}.v-menu-item-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.v-menu-item-sublabel{font-size:var(--vectis-text-caption-size);color:var(--vectis-color-text-muted);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.v-menu-item-icon,.v-menu-item-chevron{color:var(--vectis-color-text-muted);flex:none}.v-menu-item:hover:not(:disabled,[aria-disabled=true]),.v-menu-item:focus,.v-menu-item[aria-expanded=true]{background:var(--vectis-color-surface-muted);outline:none}.v-menu-item[data-selected]{background:var(--vectis-color-accent-surface);color:var(--vectis-color-accent-text)}.v-menu-item[data-selected]:hover:not(:disabled,[aria-disabled=true]),.v-menu-item[data-selected]:focus,.v-menu-item[data-selected][aria-expanded=true]{background:color-mix(in oklab, var(--vectis-color-accent-surface), var(--vectis-color-accent-text) 8%)}.v-menu-item[data-tone=danger]{color:var(--vectis-color-danger-text)}.v-menu-item[data-tone=danger]:hover:not(:disabled,[aria-disabled=true]),.v-menu-item[data-tone=danger]:focus,.v-menu-item[data-tone=danger][aria-expanded=true]{background:var(--vectis-color-danger-surface)}.v-menu-item:disabled,.v-menu-item[aria-disabled=true]{color:var(--vectis-color-text-subtle);cursor:not-allowed;background:0 0}.v-menu-item:is([data-selected],[data-tone=danger],:disabled,[aria-disabled=true]) :is(.v-menu-item-icon,.v-menu-item-sublabel){color:inherit}@media (forced-colors:active){.v-menu-item:focus,.v-menu-item[aria-expanded=true]{outline:var(--vectis-focus-ring-width) solid Highlight;outline-offset:calc(-1 * var(--vectis-focus-ring-width))}.v-menu-item.v-menu-item[data-selected]{forced-color-adjust:none;color:highlighttext;background-color:highlight}}}
|
|
@@ -17,13 +17,13 @@ interface MenuItemProps {
|
|
|
17
17
|
/** An icon after the label, in the same forms. The `#end` slot replaces it. */
|
|
18
18
|
iconEnd?: IconSource;
|
|
19
19
|
/**
|
|
20
|
-
* Marks this item as the one currently in effect
|
|
21
|
-
*
|
|
20
|
+
* Marks this item as the one currently in effect: the chosen sort order, the active
|
|
21
|
+
* view. It is coloured and announced as such.
|
|
22
22
|
*/
|
|
23
23
|
selected?: boolean;
|
|
24
24
|
/**
|
|
25
|
-
* What the command means, in colour. `danger` marks it destructive
|
|
26
|
-
*
|
|
25
|
+
* What the command means, in colour. `danger` marks it destructive (deleting something
|
|
26
|
+
* belongs there), and `neutral`, the default, is every other command.
|
|
27
27
|
*
|
|
28
28
|
* A row is an action, so it takes the vocabulary of one: the same word on the same
|
|
29
29
|
* prop as a VButton, rather than a boolean of its own. There is no `accent` here,
|
|
@@ -48,8 +48,8 @@ type __VLS_Slots = {
|
|
|
48
48
|
/** Free content after the label, which takes the place of `iconEnd`. */
|
|
49
49
|
end?(): unknown;
|
|
50
50
|
/**
|
|
51
|
-
* The contents of a submenu
|
|
52
|
-
*
|
|
51
|
+
* The contents of a submenu: items, groups and separators, this component included,
|
|
52
|
+
* so menus may nest as deep as needed.
|
|
53
53
|
*/
|
|
54
54
|
submenu?(): unknown;
|
|
55
55
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMenuItem.js","names":[],"sources":["../../../src/components/VMenu/VMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One command in a menu. The panel moves the focus between items, and choosing one closes\n * the whole menu, submenus included.\n *\n * With an `href` it becomes a link, made inert by hand when disabled — a link has no\n * `disabled` attribute, so the address is removed and `aria-disabled` says why.\n *\n * With the `#submenu` slot it becomes the trigger of a nested panel rendered INSIDE the\n * parent one. That nesting is what buys the submenu behaviour from the browser: the panels\n * form a native stack, a click outside closes all of them, and opening one branch closes its\n * sibling.\n *\n * The JS covers only the two openings the browser does not: the keyboard, and a hover held\n * long enough to show it was meant. A click already opens it natively.\n */\n\nimport { inject, ref, useId } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport type { IconSource } from '../VIcon/types'\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuKey, SUBMENU_HOVER_DELAY } from './context'\n\nimport { useTimer } from '../../composables/useTimer'\n\n/**\n * What a command means, in colour. It is the subset of VButton's tones a menu row can\n * carry: an ordinary command, or a destructive one.\n */\nexport type MenuItemTone = 'neutral' | 'danger'\n\ninterface MenuItemProps {\n /** What the command says. The default slot replaces it. */\n label?: string\n /** A second line under the label, for a shortcut or a short explanation. */\n sublabel?: string\n /**\n * An icon before the label: an icon name, an image address, or an explicit render.\n * The `#start` slot replaces it.\n */\n iconStart?: IconSource\n /** An icon after the label, in the same forms. The `#end` slot replaces it. */\n iconEnd?: IconSource\n /**\n * Marks this item as the one currently in effect — the chosen sort order, the\n * active view. It is coloured and announced as such.\n */\n selected?: boolean\n /**\n * What the command means, in colour. `danger` marks it destructive — deleting\n * something belongs there — and `neutral`, the default, is every other command.\n *\n * A row is an action, so it takes the vocabulary of one: the same word on the same\n * prop as a VButton, rather than a boolean of its own. There is no `accent` here,\n * a menu having no primary command among its rows.\n */\n tone?: MenuItemTone\n /** Makes the item unusable: it no longer responds and the arrows skip over it. */\n disabled?: boolean\n /**\n * Turns the item into a link pointing at this address, for a menu that navigates\n * rather than acts. A disabled link is made inert by hand.\n */\n href?: string\n}\n\n// With a submenu this component renders two elements — the item and the nested panel\n// — so there is no single root for Vue to put the consumer's attributes on. They are\n// placed on the item explicitly.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<MenuItemProps>(), {\n label: undefined,\n sublabel: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n selected: false,\n tone: 'neutral',\n disabled: false,\n href: undefined,\n})\n\nconst emit = defineEmits<{\n /**\n * The command was chosen, by click or by keyboard. It is emitted before the menu\n * closes, so the handler runs while the menu is still open.\n */\n select: []\n}>()\n\nconst slots = defineSlots<{\n /** The label, replacing the `label` prop. */\n default?(): unknown\n /** The second line, replacing the `sublabel` prop. */\n sublabel?(): unknown\n /** Free content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Free content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n /**\n * The contents of a submenu — items, groups and separators, this component\n * included, so menus may nest as deep as needed.\n */\n submenu?(): unknown\n}>()\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasSubmenu() {\n return !!slots.submenu\n}\nfunction tag() {\n return !hasSubmenu() && props.href !== undefined ? ('a' as const) : ('button' as const)\n}\n\nconst menu = inject(menuKey, null)\n\nfunction onClick() {\n if (props.disabled || hasSubmenu()) return\n emit('select')\n menu?.closeAll()\n}\n\nconst subId = useId()\nconst subOpen = ref(false)\nconst subPanel = ref<InstanceType<typeof VMenuPanel> | null>(null)\n// TRAP — when a submenu is opened from code rather than by a click, the item must be\n// handed to the browser as the source of that opening. The implicit anchor is only\n// established natively, on click; without it the panel has nothing to position itself\n// against and lands at the corner of the viewport, with no error anywhere.\nconst itemEl = ref<HTMLElement | null>(null)\n\n// @keyboard\n// Opening a submenu from the keyboard, which the browser's own toggle does not cover\n// — it only reacts to a click.\nfunction onKeydown(event: KeyboardEvent) {\n if (!hasSubmenu() || props.disabled) return\n if (!['ArrowRight', 'Enter', ' '].includes(event.key)) return\n // The button's native activation has to be stopped: it would fire a click of its\n // own, and that click would toggle the panel shut again right behind the opening\n // below.\n event.preventDefault()\n subPanel.value?.show(itemEl.value ?? undefined)\n subPanel.value?.focusFirst()\n}\n\n// A submenu opens on hover, but only once the pointer has stayed long enough to show\n// it was meant — otherwise every panel crossed on the way to another one would flash\n// open. ONE timer serves both directions, since opening and closing are mutually\n// exclusive: arming either always cancels the other.\nconst hoverTimer = useTimer()\n\n// @a11y @core\nfunction onPointerEnter() {\n if (props.disabled) return\n // Hovering also moves the focus, so that the mouse and the keyboard never highlight\n // two different items at once — in a menu there is only ever one current item.\n itemEl.value?.focus({ preventScroll: true })\n if (!hasSubmenu()) return\n hoverTimer.start(() => subPanel.value?.show(itemEl.value ?? undefined), SUBMENU_HOVER_DELAY)\n}\n\n// @a11y @core — the test on where the focus currently is, is the accessibility half:\n// a pointer drifting off the item must not close a submenu a keyboard user is\n// standing inside.\nfunction onPointerLeave() {\n if (!hasSubmenu()) return\n hoverTimer.start(() => {\n if (subPanel.value?.el?.contains(document.activeElement)) return\n subPanel.value?.close()\n }, SUBMENU_HOVER_DELAY)\n}\n</script>\n\n<template>\n <component\n :is=\"tag()\"\n ref=\"itemEl\"\n v-bind=\"$attrs\"\n role=\"menuitem\"\n tabindex=\"-1\"\n class=\"v-menu-item\"\n :type=\"tag() === 'button' ? 'button' : undefined\"\n :disabled=\"tag() === 'button' ? disabled : undefined\"\n :href=\"tag() === 'a' && !disabled ? href : undefined\"\n :aria-disabled=\"tag() === 'a' && disabled ? 'true' : undefined\"\n :data-tone=\"tone\"\n :data-selected=\"selected ? '' : undefined\"\n :aria-current=\"selected ? 'true' : undefined\"\n :aria-haspopup=\"hasSubmenu() ? 'menu' : undefined\"\n :aria-expanded=\"hasSubmenu() ? subOpen : undefined\"\n :aria-controls=\"hasSubmenu() ? subId : undefined\"\n :popovertarget=\"hasSubmenu() ? subId : undefined\"\n @click=\"onClick\"\n @keydown=\"onKeydown\"\n @pointerenter=\"onPointerEnter\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"start\">\n <VIcon v-if=\"iconStart\" class=\"v-menu-item-icon\" v-bind=\"iconProps(iconStart)\" />\n </slot>\n <span class=\"v-menu-item-content\">\n <span class=\"v-menu-item-label\"\n ><slot>{{ label }}</slot></span\n >\n <span v-if=\"sublabel !== undefined || $slots.sublabel\" class=\"v-menu-item-sublabel\">\n <slot name=\"sublabel\">{{ sublabel }}</slot>\n </span>\n </span>\n <!-- An item opening a submenu always shows the chevron announcing it, and never\n the end icon: the sideways opening is what the reader needs to be told -->\n <VIcon v-if=\"hasSubmenu()\" :name=\"chevronRightIcon\" class=\"v-menu-item-chevron\" mirrored />\n <slot v-else name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" />\n </slot>\n </component>\n <VMenuPanel\n v-if=\"hasSubmenu()\"\n :id=\"subId\"\n ref=\"subPanel\"\n placement=\"right-start\"\n submenu\n @toggle=\"subOpen = $event\"\n @pointerenter=\"hoverTimer.cancel()\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"submenu\" />\n </VMenuPanel>\n</template>\n\n<style>\n@layer vectis.components {\n .v-menu-item {\n /*\n * Every dimension comes from the `--control-*` variables the root panel sets and\n * this row inherits — one size table for the whole design system. The icons follow\n * with nothing written for them, their own variables belonging to that same block.\n *\n * The type is the one composite part: the SIZE comes from the scale, but the line\n * height stays that of body text — a unitless ratio, so it still follows the size\n * — and the weight stays regular. The full `control` type role would mean a medium\n * weight and lines set tight against each other, and a row may carry a second line\n * under its label.\n *\n * The label and that second line TRUNCATE rather than wrap, the VSideNavigationItem\n * recipe: the panel has a ceiling, and a command that grew a line would break the\n * rhythm of the list. `width` on VMenu is the way to give long labels more room.\n *\n * The corner is VSideNavigationItem's: the control radius capped at half a control\n * height, so a pill override paints every row of a menu alike, a row carrying a\n * sublabel included, instead of rounding each one to half of its own height.\n */\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n inline-size: 100%;\n min-block-size: var(--control-height);\n padding-block: var(--vectis-space-1);\n padding-inline: var(--control-padding-inline);\n border: none;\n background: transparent;\n color: var(--vectis-color-text);\n border-radius: min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));\n font-family: inherit;\n font-size: var(--control-font-size);\n line-height: var(--vectis-text-body-md-leading);\n text-align: start;\n text-decoration: none;\n cursor: pointer;\n }\n\n .v-menu-item-content {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n flex-direction: column;\n }\n\n .v-menu-item-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .v-menu-item-sublabel {\n overflow: hidden;\n font-size: var(--vectis-text-caption-size);\n color: var(--vectis-color-text-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* The start icon is muted, as on every row of the design system (VSideNavigationItem,\n VAccordionItem): the label carries the command, the icon only helps find it. */\n .v-menu-item-icon,\n .v-menu-item-chevron {\n flex: none;\n color: var(--vectis-color-text-muted);\n }\n\n /* In a menu the focus IS the highlight, so it is drawn on `:focus` and not on\n `:focus-visible`: the focus is moved from code — by the arrows, and by hovering —\n and the browser would not call that a keyboard focus worth showing. While a submenu is\n open its parent item keeps the highlight too, so the path followed through the levels\n stays visible. */\n .v-menu-item:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item:focus,\n .v-menu-item[aria-expanded='true'] {\n background: var(--vectis-color-surface-muted);\n outline: none;\n }\n\n .v-menu-item[data-selected] {\n background: var(--vectis-color-accent-surface);\n color: var(--vectis-color-accent-text);\n }\n\n .v-menu-item[data-selected]:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-selected]:focus,\n .v-menu-item[data-selected][aria-expanded='true'] {\n /* The selected row is already tinted, so its hover deepens that tint rather than\n replacing it with the neutral highlight. */\n background: color-mix(\n in oklab,\n var(--vectis-color-accent-surface),\n var(--vectis-color-accent-text) 8%\n );\n }\n\n .v-menu-item[data-tone='danger'] {\n color: var(--vectis-color-danger-text);\n }\n\n .v-menu-item[data-tone='danger']:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-tone='danger']:focus,\n .v-menu-item[data-tone='danger'][aria-expanded='true'] {\n background: var(--vectis-color-danger-surface);\n }\n\n /* Both forms have to be matched: `:disabled` only ever applies to a `<button>`, and\n an item rendered as a link is made inert through `aria-disabled` instead. */\n .v-menu-item:disabled,\n .v-menu-item[aria-disabled='true'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n cursor: not-allowed;\n }\n\n /* On a row painted in a colour of its own, the start icon and the second line take the\n label's colour rather than the muted grey: muted would clash with the selected and the\n danger tints, and on a disabled row it is DARKER than the subtle grey the label takes,\n so they would outshine it. Each `:is()` counts for its most specific member, which\n keeps the rule at (0,3,0), above the two (0,1,0) base rules. */\n .v-menu-item:is([data-selected], [data-tone='danger'], :disabled, [aria-disabled='true'])\n :is(.v-menu-item-icon, .v-menu-item-sublabel) {\n color: inherit;\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VMenuItem.js","names":[],"sources":["../../../src/components/VMenu/VMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One command in a menu. The panel moves the focus between items, and choosing one closes\n * the whole menu, submenus included.\n *\n * With an `href` it becomes a link, made inert through `useInertLink` when disabled: a\n * link has no `disabled` attribute, so the address is removed, `aria-disabled` says why\n * and the consumer's click listeners are dropped (an `<a>` without `href` still clicks).\n *\n * With the `#submenu` slot it becomes the trigger of a nested panel rendered INSIDE the\n * parent one. That nesting is what buys the submenu behaviour from the browser: the panels\n * form a native stack, a click outside closes all of them, and opening one branch closes its\n * sibling.\n *\n * The JS covers only the two openings the browser does not: the keyboard, and a hover held\n * long enough to show it was meant. A click already opens it natively, and ONLY opens it\n * (`popovertargetaction=\"show\"`): the hover has usually opened it a moment before the\n * click lands, and the default toggle would shut what the reader was reaching for.\n */\n\nimport { inject, ref, useAttrs, useId } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { useInertLink } from '../../composables/useInertLink'\nimport { isRtl } from '../../utils/direction'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport type { IconSource } from '../VIcon/types'\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuKey, SUBMENU_HOVER_DELAY } from './context'\n\nimport { useTimer } from '../../composables/useTimer'\n\n/**\n * What a command means, in colour. It is the subset of VButton's tones a menu row can\n * carry: an ordinary command, or a destructive one.\n */\nexport type MenuItemTone = 'neutral' | 'danger'\n\ninterface MenuItemProps {\n /** What the command says. The default slot replaces it. */\n label?: string\n /** A second line under the label, for a shortcut or a short explanation. */\n sublabel?: string\n /**\n * An icon before the label: an icon name, an image address, or an explicit render.\n * The `#start` slot replaces it.\n */\n iconStart?: IconSource\n /** An icon after the label, in the same forms. The `#end` slot replaces it. */\n iconEnd?: IconSource\n /**\n * Marks this item as the one currently in effect: the chosen sort order, the active\n * view. It is coloured and announced as such.\n */\n selected?: boolean\n /**\n * What the command means, in colour. `danger` marks it destructive (deleting something\n * belongs there), and `neutral`, the default, is every other command.\n *\n * A row is an action, so it takes the vocabulary of one: the same word on the same\n * prop as a VButton, rather than a boolean of its own. There is no `accent` here,\n * a menu having no primary command among its rows.\n */\n tone?: MenuItemTone\n /** Makes the item unusable: it no longer responds and the arrows skip over it. */\n disabled?: boolean\n /**\n * Turns the item into a link pointing at this address, for a menu that navigates\n * rather than acts. A disabled link is made inert by hand.\n */\n href?: string\n}\n\n// With a submenu this component renders two elements — the item and the nested panel\n// — so there is no single root for Vue to put the consumer's attributes on. They are\n// placed on the item explicitly.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<MenuItemProps>(), {\n label: undefined,\n sublabel: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n selected: false,\n tone: 'neutral',\n disabled: false,\n href: undefined,\n})\n\nconst emit = defineEmits<{\n /**\n * The command was chosen, by click or by keyboard. It is emitted before the menu\n * closes, so the handler runs while the menu is still open.\n */\n select: []\n}>()\n\nconst slots = defineSlots<{\n /** The label, replacing the `label` prop. */\n default?(): unknown\n /** The second line, replacing the `sublabel` prop. */\n sublabel?(): unknown\n /** Free content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Free content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n /**\n * The contents of a submenu: items, groups and separators, this component included,\n * so menus may nest as deep as needed.\n */\n submenu?(): unknown\n}>()\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasSubmenu() {\n return !!slots.submenu\n}\nfunction tag() {\n return !hasSubmenu() && props.href !== undefined ? ('a' as const) : ('button' as const)\n}\n\nconst attrs = useAttrs()\nconst link = useInertLink({\n href: () => (hasSubmenu() ? undefined : props.href),\n inert: () => props.disabled,\n attrs: () => attrs,\n})\n\nconst menu = inject(menuKey, null)\n\nfunction onClick() {\n if (props.disabled || hasSubmenu()) return\n emit('select')\n menu?.closeAll()\n}\n\nconst subId = useId()\nconst subOpen = ref(false)\nconst subPanel = ref<InstanceType<typeof VMenuPanel> | null>(null)\n// TRAP — when a submenu is opened from code rather than by a click, the item must be\n// handed to the browser as the source of that opening. The implicit anchor is only\n// established natively, on click; without it the panel has nothing to position itself\n// against and lands at the corner of the viewport, with no error anywhere.\nconst itemEl = ref<HTMLElement | null>(null)\n\n// @keyboard\n// Opening a submenu from the keyboard, which the browser's own toggle does not cover\n// — it only reacts to a click.\nfunction onKeydown(event: KeyboardEvent) {\n // A key held with a modifier is the browser's: Alt+Right is Forward.\n if (event.altKey || event.ctrlKey || event.metaKey) return\n // @keyboard @a11y\n // A menu item is activated by Space as well as Enter, and a LINK answers Enter alone:\n // Space would scroll the page instead.\n if (!hasSubmenu()) {\n if (event.key === ' ' && tag() === 'a') {\n event.preventDefault()\n itemEl.value?.click()\n }\n return\n }\n if (props.disabled) return\n // The submenu opens towards the end of the line, so the arrow that points at it is the\n // one that opens it: the left one in a right-to-left page.\n const toward = isRtl(itemEl.value) ? 'ArrowLeft' : 'ArrowRight'\n if (![toward, 'Enter', ' '].includes(event.key)) return\n // The button's native activation has to be stopped: it would fire a click of its\n // own, and that click would toggle the panel shut again right behind the opening\n // below.\n event.preventDefault()\n subPanel.value?.show(itemEl.value ?? undefined)\n subPanel.value?.focusFirst()\n}\n\n// A submenu opens on hover, but only once the pointer has stayed long enough to show\n// it was meant — otherwise every panel crossed on the way to another one would flash\n// open. ONE timer serves both directions, since opening and closing are mutually\n// exclusive: arming either always cancels the other.\nconst hoverTimer = useTimer()\n\n// @a11y @core\n// TRAP — a TOUCH tap sends `pointerenter` and `pointerleave` before its `click`, so the\n// leave below armed the close and the submenu the tap opened shut 150 ms later. Hover\n// intent means nothing to a finger: only a pointer that can hover takes this route, the\n// tap opening the submenu natively through the click.\nfunction onPointerEnter(event: PointerEvent) {\n if (props.disabled || event.pointerType === 'touch') return\n // Hovering also moves the focus, so that the mouse and the keyboard never highlight\n // two different items at once — in a menu there is only ever one current item.\n itemEl.value?.focus({ preventScroll: true })\n if (!hasSubmenu()) return\n hoverTimer.start(() => subPanel.value?.show(itemEl.value ?? undefined), SUBMENU_HOVER_DELAY)\n}\n\n// @a11y @core\n// The test on where the focus currently is, is the accessibility half:\n// a pointer drifting off the item must not close a submenu a keyboard user is\n// standing inside.\nfunction onPointerLeave(event: PointerEvent) {\n if (!hasSubmenu() || event.pointerType === 'touch') return\n hoverTimer.start(() => {\n if (subPanel.value?.el?.contains(document.activeElement)) return\n subPanel.value?.close()\n }, SUBMENU_HOVER_DELAY)\n}\n</script>\n\n<template>\n <component\n :is=\"tag()\"\n ref=\"itemEl\"\n :aria-disabled=\"link.isInertLink.value ? 'true' : undefined\"\n :aria-current=\"selected ? 'true' : undefined\"\n :aria-haspopup=\"hasSubmenu() ? 'menu' : undefined\"\n :aria-expanded=\"hasSubmenu() ? subOpen : undefined\"\n :aria-controls=\"hasSubmenu() ? subId : undefined\"\n v-bind=\"link.attrs.value\"\n role=\"menuitem\"\n tabindex=\"-1\"\n class=\"v-menu-item\"\n :type=\"tag() === 'button' ? 'button' : undefined\"\n :disabled=\"tag() === 'button' ? disabled : undefined\"\n :href=\"link.linkHref.value\"\n :data-tone=\"tone\"\n :data-selected=\"selected ? '' : undefined\"\n :popovertarget=\"hasSubmenu() ? subId : undefined\"\n :popovertargetaction=\"hasSubmenu() ? 'show' : undefined\"\n @click=\"onClick\"\n @keydown=\"onKeydown\"\n @pointerenter=\"onPointerEnter\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"start\">\n <VIcon v-if=\"iconStart\" class=\"v-menu-item-icon\" v-bind=\"iconProps(iconStart)\" />\n </slot>\n <span class=\"v-menu-item-content\">\n <span class=\"v-menu-item-label\"\n ><slot>{{ label }}</slot></span\n >\n <span v-if=\"sublabel !== undefined || $slots.sublabel\" class=\"v-menu-item-sublabel\">\n <slot name=\"sublabel\">{{ sublabel }}</slot>\n </span>\n </span>\n <!-- An item opening a submenu always shows the chevron announcing it, and never\n the end icon: the sideways opening is what the reader needs to be told -->\n <VIcon v-if=\"hasSubmenu()\" :name=\"chevronRightIcon\" class=\"v-menu-item-chevron\" mirrored />\n <slot v-else name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" />\n </slot>\n </component>\n <VMenuPanel\n v-if=\"hasSubmenu()\"\n :id=\"subId\"\n ref=\"subPanel\"\n placement=\"right-start\"\n submenu\n @toggle=\"subOpen = $event\"\n @pointerenter=\"hoverTimer.cancel()\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"submenu\" />\n </VMenuPanel>\n</template>\n\n<style>\n@layer vectis.components {\n .v-menu-item {\n /*\n * Every dimension comes from the `--control-*` variables the root panel sets and\n * this row inherits — one size table for the whole design system. The icons follow\n * with nothing written for them, their own variables belonging to that same block.\n *\n * The type is the one composite part: the SIZE comes from the scale, but the line\n * height stays that of body text — a unitless ratio, so it still follows the size\n * — and the weight stays regular. The full `control` type role would mean a medium\n * weight and lines set tight against each other, and a row may carry a second line\n * under its label.\n *\n * The label and that second line TRUNCATE rather than wrap, the VSideNavigationItem\n * recipe: the panel has a ceiling, and a command that grew a line would break the\n * rhythm of the list. `width` on VMenu is the way to give long labels more room.\n *\n * The corner is VSideNavigationItem's: the control radius capped at half a control\n * height, so a pill override paints every row of a menu alike, a row carrying a\n * sublabel included, instead of rounding each one to half of its own height.\n */\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n inline-size: 100%;\n min-block-size: var(--control-height);\n padding-block: var(--vectis-space-1);\n padding-inline: var(--control-padding-inline);\n border: none;\n background: transparent;\n color: var(--vectis-color-text);\n border-radius: min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));\n font-family: inherit;\n font-size: var(--control-font-size);\n line-height: var(--vectis-text-body-md-leading);\n text-align: start;\n text-decoration: none;\n cursor: pointer;\n }\n\n .v-menu-item-content {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n flex-direction: column;\n }\n\n .v-menu-item-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .v-menu-item-sublabel {\n overflow: hidden;\n font-size: var(--vectis-text-caption-size);\n color: var(--vectis-color-text-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* The start icon is muted, as on every row of the design system (VSideNavigationItem,\n VAccordionItem): the label carries the command, the icon only helps find it. */\n .v-menu-item-icon,\n .v-menu-item-chevron {\n flex: none;\n color: var(--vectis-color-text-muted);\n }\n\n /* In a menu the focus IS the highlight, so it is drawn on `:focus` and not on\n `:focus-visible`: the focus is moved from code — by the arrows, and by hovering —\n and the browser would not call that a keyboard focus worth showing. While a submenu is\n open its parent item keeps the highlight too, so the path followed through the levels\n stays visible. */\n .v-menu-item:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item:focus,\n .v-menu-item[aria-expanded='true'] {\n background: var(--vectis-color-surface-muted);\n outline: none;\n }\n\n .v-menu-item[data-selected] {\n background: var(--vectis-color-accent-surface);\n color: var(--vectis-color-accent-text);\n }\n\n .v-menu-item[data-selected]:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-selected]:focus,\n .v-menu-item[data-selected][aria-expanded='true'] {\n /* The selected row is already tinted, so its hover deepens that tint rather than\n replacing it with the neutral highlight. */\n background: color-mix(\n in oklab,\n var(--vectis-color-accent-surface),\n var(--vectis-color-accent-text) 8%\n );\n }\n\n .v-menu-item[data-tone='danger'] {\n color: var(--vectis-color-danger-text);\n }\n\n .v-menu-item[data-tone='danger']:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-tone='danger']:focus,\n .v-menu-item[data-tone='danger'][aria-expanded='true'] {\n background: var(--vectis-color-danger-surface);\n }\n\n /* Both forms have to be matched: `:disabled` only ever applies to a `<button>`, and\n an item rendered as a link is made inert through `aria-disabled` instead. */\n .v-menu-item:disabled,\n .v-menu-item[aria-disabled='true'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n cursor: not-allowed;\n }\n\n /* On a row painted in a colour of its own, the start icon and the second line take the\n label's colour rather than the muted grey: muted would clash with the selected and the\n danger tints, and on a disabled row it is DARKER than the subtle grey the label takes,\n so they would outshine it. Each `:is()` counts for its most specific member, which\n keeps the rule at (0,3,0), above the two (0,1,0) base rules. */\n .v-menu-item:is([data-selected], [data-tone='danger'], :disabled, [aria-disabled='true'])\n :is(.v-menu-item-icon, .v-menu-item-sublabel) {\n color: inherit;\n }\n\n /* Windows forced colors. The highlight is a background, which the mode forces to Canvas:\n the row under the focus would look exactly like the others, and a keyboard user would\n lose their place (WCAG 2.4.7). An outline inside the row takes its place. The selected\n row, also drawn by its tint alone, takes the system selection pair, the rule the\n selected controls of the actions family follow; the class is doubled to stay above\n its (0,3,0) hover and focus rules. */\n @media (forced-colors: active) {\n .v-menu-item:focus,\n .v-menu-item[aria-expanded='true'] {\n outline: var(--vectis-focus-ring-width) solid Highlight;\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-menu-item.v-menu-item[data-selected] {\n forced-color-adjust: none;\n background-color: Highlight;\n color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { iconProps as e } from "../VIcon/iconProps.js";
|
|
2
2
|
import t from "../VIcon/VIcon.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import a from "
|
|
7
|
-
import {
|
|
3
|
+
import { useInertLink as n } from "../../composables/useInertLink.js";
|
|
4
|
+
import { chevron_right as r } from "../VIcon/icons/chevron_right.js";
|
|
5
|
+
import { isRtl as i } from "../../utils/direction.js";
|
|
6
|
+
import { useTimer as a } from "../../composables/useTimer.js";
|
|
7
|
+
import { menuKey as o } from "./context.js";
|
|
8
|
+
import s from "./VMenuPanel.js";
|
|
9
|
+
import { Fragment as c, createBlock as l, createCommentVNode as u, createElementBlock as d, createElementVNode as f, createTextVNode as p, defineComponent as m, inject as h, mergeProps as g, normalizeProps as _, openBlock as v, ref as y, renderSlot as b, resolveDynamicComponent as x, toDisplayString as S, unref as C, useAttrs as w, useId as T, useSlots as E, withCtx as D } from "vue";
|
|
8
10
|
//#region src/components/VMenu/VMenuItem.vue?vue&type=script&setup=true&lang.ts
|
|
9
|
-
var
|
|
11
|
+
var O = { class: "v-menu-item-content" }, k = { class: "v-menu-item-label" }, A = {
|
|
10
12
|
key: 0,
|
|
11
13
|
class: "v-menu-item-sublabel"
|
|
12
|
-
},
|
|
14
|
+
}, j = /*@__PURE__*/ m({
|
|
13
15
|
inheritAttrs: !1,
|
|
14
16
|
__name: "VMenuItem",
|
|
15
17
|
props: {
|
|
@@ -29,101 +31,113 @@ var T = { class: "v-menu-item-content" }, E = { class: "v-menu-item-label" }, D
|
|
|
29
31
|
href: { default: void 0 }
|
|
30
32
|
},
|
|
31
33
|
emits: ["select"],
|
|
32
|
-
setup(
|
|
33
|
-
let
|
|
34
|
-
function
|
|
35
|
-
return !!
|
|
34
|
+
setup(m, { emit: j }) {
|
|
35
|
+
let M = m, N = j, P = E();
|
|
36
|
+
function F() {
|
|
37
|
+
return !!P.submenu;
|
|
36
38
|
}
|
|
37
|
-
function
|
|
38
|
-
return !
|
|
39
|
+
function I() {
|
|
40
|
+
return !F() && M.href !== void 0 ? "a" : "button";
|
|
39
41
|
}
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
let L = w(), R = n({
|
|
43
|
+
href: () => F() ? void 0 : M.href,
|
|
44
|
+
inert: () => M.disabled,
|
|
45
|
+
attrs: () => L
|
|
46
|
+
}), z = h(o, null);
|
|
47
|
+
function B() {
|
|
48
|
+
M.disabled || F() || (N("select"), z?.closeAll());
|
|
43
49
|
}
|
|
44
|
-
let
|
|
45
|
-
function
|
|
46
|
-
!
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
let V = T(), H = y(!1), U = y(null), W = y(null);
|
|
51
|
+
function G(e) {
|
|
52
|
+
if (!(e.altKey || e.ctrlKey || e.metaKey)) {
|
|
53
|
+
if (!F()) {
|
|
54
|
+
e.key === " " && I() === "a" && (e.preventDefault(), W.value?.click());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
M.disabled || [
|
|
58
|
+
i(W.value) ? "ArrowLeft" : "ArrowRight",
|
|
59
|
+
"Enter",
|
|
60
|
+
" "
|
|
61
|
+
].includes(e.key) && (e.preventDefault(), U.value?.show(W.value ?? void 0), U.value?.focusFirst());
|
|
62
|
+
}
|
|
51
63
|
}
|
|
52
|
-
let
|
|
53
|
-
function
|
|
54
|
-
|
|
64
|
+
let K = a();
|
|
65
|
+
function q(e) {
|
|
66
|
+
M.disabled || e.pointerType === "touch" || (W.value?.focus({ preventScroll: !0 }), F() && K.start(() => U.value?.show(W.value ?? void 0), 150));
|
|
55
67
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
function J(e) {
|
|
69
|
+
!F() || e.pointerType === "touch" || K.start(() => {
|
|
70
|
+
U.value?.el?.contains(document.activeElement) || U.value?.close();
|
|
59
71
|
}, 150);
|
|
60
72
|
}
|
|
61
|
-
return (
|
|
73
|
+
return (n, i) => (v(), d(c, null, [(v(), l(x(I()), g({
|
|
62
74
|
ref_key: "itemEl",
|
|
63
|
-
ref:
|
|
64
|
-
|
|
75
|
+
ref: W,
|
|
76
|
+
"aria-disabled": C(R).isInertLink.value ? "true" : void 0,
|
|
77
|
+
"aria-current": m.selected ? "true" : void 0,
|
|
78
|
+
"aria-haspopup": F() ? "menu" : void 0,
|
|
79
|
+
"aria-expanded": F() ? H.value : void 0,
|
|
80
|
+
"aria-controls": F() ? C(V) : void 0
|
|
81
|
+
}, C(R).attrs.value, {
|
|
65
82
|
role: "menuitem",
|
|
66
83
|
tabindex: "-1",
|
|
67
84
|
class: "v-menu-item",
|
|
68
|
-
type:
|
|
69
|
-
disabled:
|
|
70
|
-
href:
|
|
71
|
-
"
|
|
72
|
-
"data-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
onClick: F,
|
|
80
|
-
onKeydown: B,
|
|
81
|
-
onPointerenter: H,
|
|
82
|
-
onPointerleave: U
|
|
85
|
+
type: I() === "button" ? "button" : void 0,
|
|
86
|
+
disabled: I() === "button" ? m.disabled : void 0,
|
|
87
|
+
href: C(R).linkHref.value,
|
|
88
|
+
"data-tone": m.tone,
|
|
89
|
+
"data-selected": m.selected ? "" : void 0,
|
|
90
|
+
popovertarget: F() ? C(V) : void 0,
|
|
91
|
+
popovertargetaction: F() ? "show" : void 0,
|
|
92
|
+
onClick: B,
|
|
93
|
+
onKeydown: G,
|
|
94
|
+
onPointerenter: q,
|
|
95
|
+
onPointerleave: J
|
|
83
96
|
}), {
|
|
84
|
-
default:
|
|
85
|
-
|
|
97
|
+
default: D(() => [
|
|
98
|
+
b(n.$slots, "start", {}, () => [m.iconStart ? (v(), l(t, g({
|
|
86
99
|
key: 0,
|
|
87
100
|
class: "v-menu-item-icon"
|
|
88
|
-
},
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
}, C(e)(m.iconStart)), null, 16)) : u("", !0)]),
|
|
102
|
+
f("span", O, [f("span", k, [b(n.$slots, "default", {}, () => [p(S(m.label), 1)])]), m.sublabel !== void 0 || n.$slots.sublabel ? (v(), d("span", A, [b(n.$slots, "sublabel", {}, () => [p(S(m.sublabel), 1)])])) : u("", !0)]),
|
|
103
|
+
F() ? (v(), l(t, {
|
|
91
104
|
key: 0,
|
|
92
|
-
name:
|
|
105
|
+
name: C(r),
|
|
93
106
|
class: "v-menu-item-chevron",
|
|
94
107
|
mirrored: ""
|
|
95
|
-
}, null, 8, ["name"])) :
|
|
108
|
+
}, null, 8, ["name"])) : b(n.$slots, "end", {}, () => [m.iconEnd ? (v(), l(t, _(g({ key: 0 }, C(e)(m.iconEnd))), null, 16)) : u("", !0)], void 0, 1)
|
|
96
109
|
]),
|
|
97
110
|
_: 3
|
|
98
111
|
}, 16, [
|
|
99
|
-
"type",
|
|
100
|
-
"disabled",
|
|
101
|
-
"href",
|
|
102
112
|
"aria-disabled",
|
|
103
|
-
"data-tone",
|
|
104
|
-
"data-selected",
|
|
105
113
|
"aria-current",
|
|
106
114
|
"aria-haspopup",
|
|
107
115
|
"aria-expanded",
|
|
108
116
|
"aria-controls",
|
|
109
|
-
"
|
|
110
|
-
|
|
117
|
+
"type",
|
|
118
|
+
"disabled",
|
|
119
|
+
"href",
|
|
120
|
+
"data-tone",
|
|
121
|
+
"data-selected",
|
|
122
|
+
"popovertarget",
|
|
123
|
+
"popovertargetaction"
|
|
124
|
+
])), F() ? (v(), l(s, {
|
|
111
125
|
key: 0,
|
|
112
|
-
id:
|
|
126
|
+
id: C(V),
|
|
113
127
|
ref_key: "subPanel",
|
|
114
|
-
ref:
|
|
128
|
+
ref: U,
|
|
115
129
|
placement: "right-start",
|
|
116
130
|
submenu: "",
|
|
117
|
-
onToggle: i[0] ||= (e) =>
|
|
118
|
-
onPointerenter: i[1] ||= (e) =>
|
|
119
|
-
onPointerleave:
|
|
131
|
+
onToggle: i[0] ||= (e) => H.value = e,
|
|
132
|
+
onPointerenter: i[1] ||= (e) => C(K).cancel(),
|
|
133
|
+
onPointerleave: J
|
|
120
134
|
}, {
|
|
121
|
-
default:
|
|
135
|
+
default: D(() => [b(n.$slots, "submenu")]),
|
|
122
136
|
_: 3
|
|
123
|
-
}, 8, ["id"])) :
|
|
137
|
+
}, 8, ["id"])) : u("", !0)], 64));
|
|
124
138
|
}
|
|
125
139
|
});
|
|
126
140
|
//#endregion
|
|
127
|
-
export {
|
|
141
|
+
export { j as default };
|
|
128
142
|
|
|
129
143
|
//# sourceMappingURL=VMenuItem.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMenuItem.vue_vue_type_script_setup_true_lang.js","names":["$attrs","$slots"],"sources":["../../../src/components/VMenu/VMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One command in a menu. The panel moves the focus between items, and choosing one closes\n * the whole menu, submenus included.\n *\n * With an `href` it becomes a link, made inert by hand when disabled — a link has no\n * `disabled` attribute, so the address is removed and `aria-disabled` says why.\n *\n * With the `#submenu` slot it becomes the trigger of a nested panel rendered INSIDE the\n * parent one. That nesting is what buys the submenu behaviour from the browser: the panels\n * form a native stack, a click outside closes all of them, and opening one branch closes its\n * sibling.\n *\n * The JS covers only the two openings the browser does not: the keyboard, and a hover held\n * long enough to show it was meant. A click already opens it natively.\n */\n\nimport { inject, ref, useId } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport type { IconSource } from '../VIcon/types'\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuKey, SUBMENU_HOVER_DELAY } from './context'\n\nimport { useTimer } from '../../composables/useTimer'\n\n/**\n * What a command means, in colour. It is the subset of VButton's tones a menu row can\n * carry: an ordinary command, or a destructive one.\n */\nexport type MenuItemTone = 'neutral' | 'danger'\n\ninterface MenuItemProps {\n /** What the command says. The default slot replaces it. */\n label?: string\n /** A second line under the label, for a shortcut or a short explanation. */\n sublabel?: string\n /**\n * An icon before the label: an icon name, an image address, or an explicit render.\n * The `#start` slot replaces it.\n */\n iconStart?: IconSource\n /** An icon after the label, in the same forms. The `#end` slot replaces it. */\n iconEnd?: IconSource\n /**\n * Marks this item as the one currently in effect — the chosen sort order, the\n * active view. It is coloured and announced as such.\n */\n selected?: boolean\n /**\n * What the command means, in colour. `danger` marks it destructive — deleting\n * something belongs there — and `neutral`, the default, is every other command.\n *\n * A row is an action, so it takes the vocabulary of one: the same word on the same\n * prop as a VButton, rather than a boolean of its own. There is no `accent` here,\n * a menu having no primary command among its rows.\n */\n tone?: MenuItemTone\n /** Makes the item unusable: it no longer responds and the arrows skip over it. */\n disabled?: boolean\n /**\n * Turns the item into a link pointing at this address, for a menu that navigates\n * rather than acts. A disabled link is made inert by hand.\n */\n href?: string\n}\n\n// With a submenu this component renders two elements — the item and the nested panel\n// — so there is no single root for Vue to put the consumer's attributes on. They are\n// placed on the item explicitly.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<MenuItemProps>(), {\n label: undefined,\n sublabel: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n selected: false,\n tone: 'neutral',\n disabled: false,\n href: undefined,\n})\n\nconst emit = defineEmits<{\n /**\n * The command was chosen, by click or by keyboard. It is emitted before the menu\n * closes, so the handler runs while the menu is still open.\n */\n select: []\n}>()\n\nconst slots = defineSlots<{\n /** The label, replacing the `label` prop. */\n default?(): unknown\n /** The second line, replacing the `sublabel` prop. */\n sublabel?(): unknown\n /** Free content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Free content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n /**\n * The contents of a submenu — items, groups and separators, this component\n * included, so menus may nest as deep as needed.\n */\n submenu?(): unknown\n}>()\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasSubmenu() {\n return !!slots.submenu\n}\nfunction tag() {\n return !hasSubmenu() && props.href !== undefined ? ('a' as const) : ('button' as const)\n}\n\nconst menu = inject(menuKey, null)\n\nfunction onClick() {\n if (props.disabled || hasSubmenu()) return\n emit('select')\n menu?.closeAll()\n}\n\nconst subId = useId()\nconst subOpen = ref(false)\nconst subPanel = ref<InstanceType<typeof VMenuPanel> | null>(null)\n// TRAP — when a submenu is opened from code rather than by a click, the item must be\n// handed to the browser as the source of that opening. The implicit anchor is only\n// established natively, on click; without it the panel has nothing to position itself\n// against and lands at the corner of the viewport, with no error anywhere.\nconst itemEl = ref<HTMLElement | null>(null)\n\n// @keyboard\n// Opening a submenu from the keyboard, which the browser's own toggle does not cover\n// — it only reacts to a click.\nfunction onKeydown(event: KeyboardEvent) {\n if (!hasSubmenu() || props.disabled) return\n if (!['ArrowRight', 'Enter', ' '].includes(event.key)) return\n // The button's native activation has to be stopped: it would fire a click of its\n // own, and that click would toggle the panel shut again right behind the opening\n // below.\n event.preventDefault()\n subPanel.value?.show(itemEl.value ?? undefined)\n subPanel.value?.focusFirst()\n}\n\n// A submenu opens on hover, but only once the pointer has stayed long enough to show\n// it was meant — otherwise every panel crossed on the way to another one would flash\n// open. ONE timer serves both directions, since opening and closing are mutually\n// exclusive: arming either always cancels the other.\nconst hoverTimer = useTimer()\n\n// @a11y @core\nfunction onPointerEnter() {\n if (props.disabled) return\n // Hovering also moves the focus, so that the mouse and the keyboard never highlight\n // two different items at once — in a menu there is only ever one current item.\n itemEl.value?.focus({ preventScroll: true })\n if (!hasSubmenu()) return\n hoverTimer.start(() => subPanel.value?.show(itemEl.value ?? undefined), SUBMENU_HOVER_DELAY)\n}\n\n// @a11y @core — the test on where the focus currently is, is the accessibility half:\n// a pointer drifting off the item must not close a submenu a keyboard user is\n// standing inside.\nfunction onPointerLeave() {\n if (!hasSubmenu()) return\n hoverTimer.start(() => {\n if (subPanel.value?.el?.contains(document.activeElement)) return\n subPanel.value?.close()\n }, SUBMENU_HOVER_DELAY)\n}\n</script>\n\n<template>\n <component\n :is=\"tag()\"\n ref=\"itemEl\"\n v-bind=\"$attrs\"\n role=\"menuitem\"\n tabindex=\"-1\"\n class=\"v-menu-item\"\n :type=\"tag() === 'button' ? 'button' : undefined\"\n :disabled=\"tag() === 'button' ? disabled : undefined\"\n :href=\"tag() === 'a' && !disabled ? href : undefined\"\n :aria-disabled=\"tag() === 'a' && disabled ? 'true' : undefined\"\n :data-tone=\"tone\"\n :data-selected=\"selected ? '' : undefined\"\n :aria-current=\"selected ? 'true' : undefined\"\n :aria-haspopup=\"hasSubmenu() ? 'menu' : undefined\"\n :aria-expanded=\"hasSubmenu() ? subOpen : undefined\"\n :aria-controls=\"hasSubmenu() ? subId : undefined\"\n :popovertarget=\"hasSubmenu() ? subId : undefined\"\n @click=\"onClick\"\n @keydown=\"onKeydown\"\n @pointerenter=\"onPointerEnter\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"start\">\n <VIcon v-if=\"iconStart\" class=\"v-menu-item-icon\" v-bind=\"iconProps(iconStart)\" />\n </slot>\n <span class=\"v-menu-item-content\">\n <span class=\"v-menu-item-label\"\n ><slot>{{ label }}</slot></span\n >\n <span v-if=\"sublabel !== undefined || $slots.sublabel\" class=\"v-menu-item-sublabel\">\n <slot name=\"sublabel\">{{ sublabel }}</slot>\n </span>\n </span>\n <!-- An item opening a submenu always shows the chevron announcing it, and never\n the end icon: the sideways opening is what the reader needs to be told -->\n <VIcon v-if=\"hasSubmenu()\" :name=\"chevronRightIcon\" class=\"v-menu-item-chevron\" mirrored />\n <slot v-else name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" />\n </slot>\n </component>\n <VMenuPanel\n v-if=\"hasSubmenu()\"\n :id=\"subId\"\n ref=\"subPanel\"\n placement=\"right-start\"\n submenu\n @toggle=\"subOpen = $event\"\n @pointerenter=\"hoverTimer.cancel()\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"submenu\" />\n </VMenuPanel>\n</template>\n\n<style>\n@layer vectis.components {\n .v-menu-item {\n /*\n * Every dimension comes from the `--control-*` variables the root panel sets and\n * this row inherits — one size table for the whole design system. The icons follow\n * with nothing written for them, their own variables belonging to that same block.\n *\n * The type is the one composite part: the SIZE comes from the scale, but the line\n * height stays that of body text — a unitless ratio, so it still follows the size\n * — and the weight stays regular. The full `control` type role would mean a medium\n * weight and lines set tight against each other, and a row may carry a second line\n * under its label.\n *\n * The label and that second line TRUNCATE rather than wrap, the VSideNavigationItem\n * recipe: the panel has a ceiling, and a command that grew a line would break the\n * rhythm of the list. `width` on VMenu is the way to give long labels more room.\n *\n * The corner is VSideNavigationItem's: the control radius capped at half a control\n * height, so a pill override paints every row of a menu alike, a row carrying a\n * sublabel included, instead of rounding each one to half of its own height.\n */\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n inline-size: 100%;\n min-block-size: var(--control-height);\n padding-block: var(--vectis-space-1);\n padding-inline: var(--control-padding-inline);\n border: none;\n background: transparent;\n color: var(--vectis-color-text);\n border-radius: min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));\n font-family: inherit;\n font-size: var(--control-font-size);\n line-height: var(--vectis-text-body-md-leading);\n text-align: start;\n text-decoration: none;\n cursor: pointer;\n }\n\n .v-menu-item-content {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n flex-direction: column;\n }\n\n .v-menu-item-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .v-menu-item-sublabel {\n overflow: hidden;\n font-size: var(--vectis-text-caption-size);\n color: var(--vectis-color-text-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* The start icon is muted, as on every row of the design system (VSideNavigationItem,\n VAccordionItem): the label carries the command, the icon only helps find it. */\n .v-menu-item-icon,\n .v-menu-item-chevron {\n flex: none;\n color: var(--vectis-color-text-muted);\n }\n\n /* In a menu the focus IS the highlight, so it is drawn on `:focus` and not on\n `:focus-visible`: the focus is moved from code — by the arrows, and by hovering —\n and the browser would not call that a keyboard focus worth showing. While a submenu is\n open its parent item keeps the highlight too, so the path followed through the levels\n stays visible. */\n .v-menu-item:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item:focus,\n .v-menu-item[aria-expanded='true'] {\n background: var(--vectis-color-surface-muted);\n outline: none;\n }\n\n .v-menu-item[data-selected] {\n background: var(--vectis-color-accent-surface);\n color: var(--vectis-color-accent-text);\n }\n\n .v-menu-item[data-selected]:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-selected]:focus,\n .v-menu-item[data-selected][aria-expanded='true'] {\n /* The selected row is already tinted, so its hover deepens that tint rather than\n replacing it with the neutral highlight. */\n background: color-mix(\n in oklab,\n var(--vectis-color-accent-surface),\n var(--vectis-color-accent-text) 8%\n );\n }\n\n .v-menu-item[data-tone='danger'] {\n color: var(--vectis-color-danger-text);\n }\n\n .v-menu-item[data-tone='danger']:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-tone='danger']:focus,\n .v-menu-item[data-tone='danger'][aria-expanded='true'] {\n background: var(--vectis-color-danger-surface);\n }\n\n /* Both forms have to be matched: `:disabled` only ever applies to a `<button>`, and\n an item rendered as a link is made inert through `aria-disabled` instead. */\n .v-menu-item:disabled,\n .v-menu-item[aria-disabled='true'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n cursor: not-allowed;\n }\n\n /* On a row painted in a colour of its own, the start icon and the second line take the\n label's colour rather than the muted grey: muted would clash with the selected and the\n danger tints, and on a disabled row it is DARKER than the subtle grey the label takes,\n so they would outshine it. Each `:is()` counts for its most specific member, which\n keeps the rule at (0,3,0), above the two (0,1,0) base rules. */\n .v-menu-item:is([data-selected], [data-tone='danger'], :disabled, [aria-disabled='true'])\n :is(.v-menu-item-icon, .v-menu-item-sublabel) {\n color: inherit;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0EA,IAAM,IAAQ,GAWR,IAAO,GAQP,IAAQ,EAAA;EAkBd,SAAS,IAAa;GACpB,OAAO,CAAC,CAAC,EAAM;EACjB;EACA,SAAS,IAAM;GACb,OAAO,CAAC,EAAW,KAAK,EAAM,SAAS,KAAA,IAAa,MAAiB;EACvE;EAEA,IAAM,IAAO,EAAO,GAAS,IAAI;EAEjC,SAAS,IAAU;GACb,EAAM,YAAY,EAAW,MACjC,EAAK,QAAQ,GACb,GAAM,SAAS;EACjB;EAEA,IAAM,IAAQ,EAAM,GACd,IAAU,EAAI,EAAK,GACnB,IAAW,EAA4C,IAAI,GAK3D,IAAS,EAAwB,IAAI;EAK3C,SAAS,EAAU,GAAsB;GACnC,CAAC,EAAW,KAAK,EAAM,YACtB;IAAC;IAAc;IAAS;GAAG,CAAC,CAAC,SAAS,EAAM,GAAG,MAIpD,EAAM,eAAe,GACrB,EAAS,OAAO,KAAK,EAAO,SAAS,KAAA,CAAS,GAC9C,EAAS,OAAO,WAAW;EAC7B;EAMA,IAAM,IAAa,EAAS;EAG5B,SAAS,IAAiB;GACpB,EAAM,aAGV,EAAO,OAAO,MAAM,EAAE,eAAe,GAAK,CAAC,GACtC,EAAW,KAChB,EAAW,YAAY,EAAS,OAAO,KAAK,EAAO,SAAS,KAAA,CAAS,GAAA,GAAsB;EAC7F;EAKA,SAAS,IAAiB;GACnB,EAAW,KAChB,EAAW,YAAY;IACjB,EAAS,OAAO,IAAI,SAAS,SAAS,aAAa,KACvD,EAAS,OAAO,MAAM;GACxB,GAAA,GAAsB;EACxB;2CAIE,EAwCY,EAvCL,EAAG,CAAA,GADV,EAwCY;YAtCN;GAAJ,KAAI;KACIA,EAAAA,QAAM;GACd,MAAK;GACL,UAAS;GACT,OAAM;GACL,MAAM,EAAG,MAAA,WAAA,WAA6B,KAAA;GACtC,UAAU,EAAG,MAAA,WAAkB,EAAA,WAAW,KAAA;GAC1C,MAAM,EAAG,MAAA,OAAA,CAAe,EAAA,WAAW,EAAA,OAAO,KAAA;GAC1C,iBAAe,EAAG,MAAA,OAAc,EAAA,WAAQ,SAAY,KAAA;GACpD,aAAW,EAAA;GACX,iBAAe,EAAA,WAAQ,KAAQ,KAAA;GAC/B,gBAAc,EAAA,WAAQ,SAAY,KAAA;GAClC,iBAAe,EAAU,IAAA,SAAc,KAAA;GACvC,iBAAe,EAAU,IAAK,EAAA,QAAU,KAAA;GACxC,iBAAe,EAAU,IAAK,EAAA,CAAA,IAAQ,KAAA;GACtC,eAAe,EAAU,IAAK,EAAA,CAAA,IAAQ,KAAA;GAC/B;GACE;GACT,gBAAc;GACd,gBAAc;;oBAIR;IAFP,EAEO,EAAA,QAAA,SAAA,CAAA,SAAA,CADQ,EAAA,aAAA,EAAA,GAAb,EAAiF,GAAjF,EAAiF;;KAAzD,OAAM;OAA2B,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA;IAE9E,EAOO,QAPP,GAOO,CANL,EAEC,QAFD,GAEC,CADE,EAAwB,EAAA,QAAA,WAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAEL,EAAA,aAAa,KAAA,KAAaC,EAAAA,OAAO,YAAA,EAAA,GAA7C,EAEO,QAFP,GAEO,CADL,EAA2C,EAAA,QAAA,YAAA,CAAA,SAAA,CAAA,EAAA,EAAlB,EAAA,QAAQ,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA;IAKxB,EAAU,KAAA,EAAA,GAAvB,EAA2F,GAAA;;KAA/D,MAAM,EAAA,CAAA;KAAkB,OAAM;KAAsB,UAAA;6BAChF,EAEO,EAAA,QAAA,OAAA,CAAA,SAAA,CADQ,EAAA,WAAA,EAAA,GAAb,EAAoD,GAAA,EAAA,EAAA,EAAA,KAAA,EAAA,GAAtB,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,CAAA,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;;;;;;;;;;;;;;;OAI3C,EAAU,KAAA,EAAA,GADlB,EAWa,GAAA;;GATV,IAAI,EAAA,CAAA;YACD;GAAJ,KAAI;GACJ,WAAU;GACV,SAAA;GACC,UAAM,AAAA,EAAA,QAAA,MAAE,EAAA,QAAU;GAClB,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAA,CAAA,CAAU,CAAC,OAAM;GAC/B,gBAAc;;oBAEQ,CAAvB,EAAuB,EAAA,QAAA,SAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VMenuItem.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VMenu/VMenuItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One command in a menu. The panel moves the focus between items, and choosing one closes\n * the whole menu, submenus included.\n *\n * With an `href` it becomes a link, made inert through `useInertLink` when disabled: a\n * link has no `disabled` attribute, so the address is removed, `aria-disabled` says why\n * and the consumer's click listeners are dropped (an `<a>` without `href` still clicks).\n *\n * With the `#submenu` slot it becomes the trigger of a nested panel rendered INSIDE the\n * parent one. That nesting is what buys the submenu behaviour from the browser: the panels\n * form a native stack, a click outside closes all of them, and opening one branch closes its\n * sibling.\n *\n * The JS covers only the two openings the browser does not: the keyboard, and a hover held\n * long enough to show it was meant. A click already opens it natively, and ONLY opens it\n * (`popovertargetaction=\"show\"`): the hover has usually opened it a moment before the\n * click lands, and the default toggle would shut what the reader was reaching for.\n */\n\nimport { inject, ref, useAttrs, useId } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { useInertLink } from '../../composables/useInertLink'\nimport { isRtl } from '../../utils/direction'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport type { IconSource } from '../VIcon/types'\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuKey, SUBMENU_HOVER_DELAY } from './context'\n\nimport { useTimer } from '../../composables/useTimer'\n\n/**\n * What a command means, in colour. It is the subset of VButton's tones a menu row can\n * carry: an ordinary command, or a destructive one.\n */\nexport type MenuItemTone = 'neutral' | 'danger'\n\ninterface MenuItemProps {\n /** What the command says. The default slot replaces it. */\n label?: string\n /** A second line under the label, for a shortcut or a short explanation. */\n sublabel?: string\n /**\n * An icon before the label: an icon name, an image address, or an explicit render.\n * The `#start` slot replaces it.\n */\n iconStart?: IconSource\n /** An icon after the label, in the same forms. The `#end` slot replaces it. */\n iconEnd?: IconSource\n /**\n * Marks this item as the one currently in effect: the chosen sort order, the active\n * view. It is coloured and announced as such.\n */\n selected?: boolean\n /**\n * What the command means, in colour. `danger` marks it destructive (deleting something\n * belongs there), and `neutral`, the default, is every other command.\n *\n * A row is an action, so it takes the vocabulary of one: the same word on the same\n * prop as a VButton, rather than a boolean of its own. There is no `accent` here,\n * a menu having no primary command among its rows.\n */\n tone?: MenuItemTone\n /** Makes the item unusable: it no longer responds and the arrows skip over it. */\n disabled?: boolean\n /**\n * Turns the item into a link pointing at this address, for a menu that navigates\n * rather than acts. A disabled link is made inert by hand.\n */\n href?: string\n}\n\n// With a submenu this component renders two elements — the item and the nested panel\n// — so there is no single root for Vue to put the consumer's attributes on. They are\n// placed on the item explicitly.\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<MenuItemProps>(), {\n label: undefined,\n sublabel: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n selected: false,\n tone: 'neutral',\n disabled: false,\n href: undefined,\n})\n\nconst emit = defineEmits<{\n /**\n * The command was chosen, by click or by keyboard. It is emitted before the menu\n * closes, so the handler runs while the menu is still open.\n */\n select: []\n}>()\n\nconst slots = defineSlots<{\n /** The label, replacing the `label` prop. */\n default?(): unknown\n /** The second line, replacing the `sublabel` prop. */\n sublabel?(): unknown\n /** Free content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Free content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n /**\n * The contents of a submenu: items, groups and separators, this component included,\n * so menus may nest as deep as needed.\n */\n submenu?(): unknown\n}>()\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasSubmenu() {\n return !!slots.submenu\n}\nfunction tag() {\n return !hasSubmenu() && props.href !== undefined ? ('a' as const) : ('button' as const)\n}\n\nconst attrs = useAttrs()\nconst link = useInertLink({\n href: () => (hasSubmenu() ? undefined : props.href),\n inert: () => props.disabled,\n attrs: () => attrs,\n})\n\nconst menu = inject(menuKey, null)\n\nfunction onClick() {\n if (props.disabled || hasSubmenu()) return\n emit('select')\n menu?.closeAll()\n}\n\nconst subId = useId()\nconst subOpen = ref(false)\nconst subPanel = ref<InstanceType<typeof VMenuPanel> | null>(null)\n// TRAP — when a submenu is opened from code rather than by a click, the item must be\n// handed to the browser as the source of that opening. The implicit anchor is only\n// established natively, on click; without it the panel has nothing to position itself\n// against and lands at the corner of the viewport, with no error anywhere.\nconst itemEl = ref<HTMLElement | null>(null)\n\n// @keyboard\n// Opening a submenu from the keyboard, which the browser's own toggle does not cover\n// — it only reacts to a click.\nfunction onKeydown(event: KeyboardEvent) {\n // A key held with a modifier is the browser's: Alt+Right is Forward.\n if (event.altKey || event.ctrlKey || event.metaKey) return\n // @keyboard @a11y\n // A menu item is activated by Space as well as Enter, and a LINK answers Enter alone:\n // Space would scroll the page instead.\n if (!hasSubmenu()) {\n if (event.key === ' ' && tag() === 'a') {\n event.preventDefault()\n itemEl.value?.click()\n }\n return\n }\n if (props.disabled) return\n // The submenu opens towards the end of the line, so the arrow that points at it is the\n // one that opens it: the left one in a right-to-left page.\n const toward = isRtl(itemEl.value) ? 'ArrowLeft' : 'ArrowRight'\n if (![toward, 'Enter', ' '].includes(event.key)) return\n // The button's native activation has to be stopped: it would fire a click of its\n // own, and that click would toggle the panel shut again right behind the opening\n // below.\n event.preventDefault()\n subPanel.value?.show(itemEl.value ?? undefined)\n subPanel.value?.focusFirst()\n}\n\n// A submenu opens on hover, but only once the pointer has stayed long enough to show\n// it was meant — otherwise every panel crossed on the way to another one would flash\n// open. ONE timer serves both directions, since opening and closing are mutually\n// exclusive: arming either always cancels the other.\nconst hoverTimer = useTimer()\n\n// @a11y @core\n// TRAP — a TOUCH tap sends `pointerenter` and `pointerleave` before its `click`, so the\n// leave below armed the close and the submenu the tap opened shut 150 ms later. Hover\n// intent means nothing to a finger: only a pointer that can hover takes this route, the\n// tap opening the submenu natively through the click.\nfunction onPointerEnter(event: PointerEvent) {\n if (props.disabled || event.pointerType === 'touch') return\n // Hovering also moves the focus, so that the mouse and the keyboard never highlight\n // two different items at once — in a menu there is only ever one current item.\n itemEl.value?.focus({ preventScroll: true })\n if (!hasSubmenu()) return\n hoverTimer.start(() => subPanel.value?.show(itemEl.value ?? undefined), SUBMENU_HOVER_DELAY)\n}\n\n// @a11y @core\n// The test on where the focus currently is, is the accessibility half:\n// a pointer drifting off the item must not close a submenu a keyboard user is\n// standing inside.\nfunction onPointerLeave(event: PointerEvent) {\n if (!hasSubmenu() || event.pointerType === 'touch') return\n hoverTimer.start(() => {\n if (subPanel.value?.el?.contains(document.activeElement)) return\n subPanel.value?.close()\n }, SUBMENU_HOVER_DELAY)\n}\n</script>\n\n<template>\n <component\n :is=\"tag()\"\n ref=\"itemEl\"\n :aria-disabled=\"link.isInertLink.value ? 'true' : undefined\"\n :aria-current=\"selected ? 'true' : undefined\"\n :aria-haspopup=\"hasSubmenu() ? 'menu' : undefined\"\n :aria-expanded=\"hasSubmenu() ? subOpen : undefined\"\n :aria-controls=\"hasSubmenu() ? subId : undefined\"\n v-bind=\"link.attrs.value\"\n role=\"menuitem\"\n tabindex=\"-1\"\n class=\"v-menu-item\"\n :type=\"tag() === 'button' ? 'button' : undefined\"\n :disabled=\"tag() === 'button' ? disabled : undefined\"\n :href=\"link.linkHref.value\"\n :data-tone=\"tone\"\n :data-selected=\"selected ? '' : undefined\"\n :popovertarget=\"hasSubmenu() ? subId : undefined\"\n :popovertargetaction=\"hasSubmenu() ? 'show' : undefined\"\n @click=\"onClick\"\n @keydown=\"onKeydown\"\n @pointerenter=\"onPointerEnter\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"start\">\n <VIcon v-if=\"iconStart\" class=\"v-menu-item-icon\" v-bind=\"iconProps(iconStart)\" />\n </slot>\n <span class=\"v-menu-item-content\">\n <span class=\"v-menu-item-label\"\n ><slot>{{ label }}</slot></span\n >\n <span v-if=\"sublabel !== undefined || $slots.sublabel\" class=\"v-menu-item-sublabel\">\n <slot name=\"sublabel\">{{ sublabel }}</slot>\n </span>\n </span>\n <!-- An item opening a submenu always shows the chevron announcing it, and never\n the end icon: the sideways opening is what the reader needs to be told -->\n <VIcon v-if=\"hasSubmenu()\" :name=\"chevronRightIcon\" class=\"v-menu-item-chevron\" mirrored />\n <slot v-else name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" />\n </slot>\n </component>\n <VMenuPanel\n v-if=\"hasSubmenu()\"\n :id=\"subId\"\n ref=\"subPanel\"\n placement=\"right-start\"\n submenu\n @toggle=\"subOpen = $event\"\n @pointerenter=\"hoverTimer.cancel()\"\n @pointerleave=\"onPointerLeave\"\n >\n <slot name=\"submenu\" />\n </VMenuPanel>\n</template>\n\n<style>\n@layer vectis.components {\n .v-menu-item {\n /*\n * Every dimension comes from the `--control-*` variables the root panel sets and\n * this row inherits — one size table for the whole design system. The icons follow\n * with nothing written for them, their own variables belonging to that same block.\n *\n * The type is the one composite part: the SIZE comes from the scale, but the line\n * height stays that of body text — a unitless ratio, so it still follows the size\n * — and the weight stays regular. The full `control` type role would mean a medium\n * weight and lines set tight against each other, and a row may carry a second line\n * under its label.\n *\n * The label and that second line TRUNCATE rather than wrap, the VSideNavigationItem\n * recipe: the panel has a ceiling, and a command that grew a line would break the\n * rhythm of the list. `width` on VMenu is the way to give long labels more room.\n *\n * The corner is VSideNavigationItem's: the control radius capped at half a control\n * height, so a pill override paints every row of a menu alike, a row carrying a\n * sublabel included, instead of rounding each one to half of its own height.\n */\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n inline-size: 100%;\n min-block-size: var(--control-height);\n padding-block: var(--vectis-space-1);\n padding-inline: var(--control-padding-inline);\n border: none;\n background: transparent;\n color: var(--vectis-color-text);\n border-radius: min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));\n font-family: inherit;\n font-size: var(--control-font-size);\n line-height: var(--vectis-text-body-md-leading);\n text-align: start;\n text-decoration: none;\n cursor: pointer;\n }\n\n .v-menu-item-content {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n flex-direction: column;\n }\n\n .v-menu-item-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .v-menu-item-sublabel {\n overflow: hidden;\n font-size: var(--vectis-text-caption-size);\n color: var(--vectis-color-text-muted);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n /* The start icon is muted, as on every row of the design system (VSideNavigationItem,\n VAccordionItem): the label carries the command, the icon only helps find it. */\n .v-menu-item-icon,\n .v-menu-item-chevron {\n flex: none;\n color: var(--vectis-color-text-muted);\n }\n\n /* In a menu the focus IS the highlight, so it is drawn on `:focus` and not on\n `:focus-visible`: the focus is moved from code — by the arrows, and by hovering —\n and the browser would not call that a keyboard focus worth showing. While a submenu is\n open its parent item keeps the highlight too, so the path followed through the levels\n stays visible. */\n .v-menu-item:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item:focus,\n .v-menu-item[aria-expanded='true'] {\n background: var(--vectis-color-surface-muted);\n outline: none;\n }\n\n .v-menu-item[data-selected] {\n background: var(--vectis-color-accent-surface);\n color: var(--vectis-color-accent-text);\n }\n\n .v-menu-item[data-selected]:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-selected]:focus,\n .v-menu-item[data-selected][aria-expanded='true'] {\n /* The selected row is already tinted, so its hover deepens that tint rather than\n replacing it with the neutral highlight. */\n background: color-mix(\n in oklab,\n var(--vectis-color-accent-surface),\n var(--vectis-color-accent-text) 8%\n );\n }\n\n .v-menu-item[data-tone='danger'] {\n color: var(--vectis-color-danger-text);\n }\n\n .v-menu-item[data-tone='danger']:hover:not(:disabled, [aria-disabled='true']),\n .v-menu-item[data-tone='danger']:focus,\n .v-menu-item[data-tone='danger'][aria-expanded='true'] {\n background: var(--vectis-color-danger-surface);\n }\n\n /* Both forms have to be matched: `:disabled` only ever applies to a `<button>`, and\n an item rendered as a link is made inert through `aria-disabled` instead. */\n .v-menu-item:disabled,\n .v-menu-item[aria-disabled='true'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n cursor: not-allowed;\n }\n\n /* On a row painted in a colour of its own, the start icon and the second line take the\n label's colour rather than the muted grey: muted would clash with the selected and the\n danger tints, and on a disabled row it is DARKER than the subtle grey the label takes,\n so they would outshine it. Each `:is()` counts for its most specific member, which\n keeps the rule at (0,3,0), above the two (0,1,0) base rules. */\n .v-menu-item:is([data-selected], [data-tone='danger'], :disabled, [aria-disabled='true'])\n :is(.v-menu-item-icon, .v-menu-item-sublabel) {\n color: inherit;\n }\n\n /* Windows forced colors. The highlight is a background, which the mode forces to Canvas:\n the row under the focus would look exactly like the others, and a keyboard user would\n lose their place (WCAG 2.4.7). An outline inside the row takes its place. The selected\n row, also drawn by its tint alone, takes the system selection pair, the rule the\n selected controls of the actions family follow; the class is doubled to stay above\n its (0,3,0) hover and focus rules. */\n @media (forced-colors: active) {\n .v-menu-item:focus,\n .v-menu-item[aria-expanded='true'] {\n outline: var(--vectis-focus-ring-width) solid Highlight;\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-menu-item.v-menu-item[data-selected] {\n forced-color-adjust: none;\n background-color: Highlight;\n color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+EA,IAAM,IAAQ,GAWR,IAAO,GAQP,IAAQ,EAAA;EAkBd,SAAS,IAAa;GACpB,OAAO,CAAC,CAAC,EAAM;EACjB;EACA,SAAS,IAAM;GACb,OAAO,CAAC,EAAW,KAAK,EAAM,SAAS,KAAA,IAAa,MAAiB;EACvE;EAEA,IAAM,IAAQ,EAAS,GACjB,IAAO,EAAa;GACxB,YAAa,EAAW,IAAI,KAAA,IAAY,EAAM;GAC9C,aAAa,EAAM;GACnB,aAAa;EACf,CAAC,GAEK,IAAO,EAAO,GAAS,IAAI;EAEjC,SAAS,IAAU;GACb,EAAM,YAAY,EAAW,MACjC,EAAK,QAAQ,GACb,GAAM,SAAS;EACjB;EAEA,IAAM,IAAQ,EAAM,GACd,IAAU,EAAI,EAAK,GACnB,IAAW,EAA4C,IAAI,GAK3D,IAAS,EAAwB,IAAI;EAK3C,SAAS,EAAU,GAAsB;GAEnC,QAAM,UAAU,EAAM,WAAW,EAAM,UAI3C;QAAI,CAAC,EAAW,GAAG;KACjB,AAAI,EAAM,QAAQ,OAAO,EAAI,MAAM,QACjC,EAAM,eAAe,GACrB,EAAO,OAAO,MAAM;KAEtB;IACF;IACI,EAAM,YAIL;KADU,EAAM,EAAO,KAAK,IAAI,cAAc;KACrC;KAAS;IAAG,CAAC,CAAC,SAAS,EAAM,GAAG,MAI9C,EAAM,eAAe,GACrB,EAAS,OAAO,KAAK,EAAO,SAAS,KAAA,CAAS,GAC9C,EAAS,OAAO,WAAW;GAX3B;EAYF;EAMA,IAAM,IAAa,EAAS;EAO5B,SAAS,EAAe,GAAqB;GACvC,EAAM,YAAY,EAAM,gBAAgB,YAG5C,EAAO,OAAO,MAAM,EAAE,eAAe,GAAK,CAAC,GACtC,EAAW,KAChB,EAAW,YAAY,EAAS,OAAO,KAAK,EAAO,SAAS,KAAA,CAAS,GAAA,GAAsB;EAC7F;EAMA,SAAS,EAAe,GAAqB;GACvC,CAAC,EAAW,KAAK,EAAM,gBAAgB,WAC3C,EAAW,YAAY;IACjB,EAAS,OAAO,IAAI,SAAS,SAAS,aAAa,KACvD,EAAS,OAAO,MAAM;GACxB,GAAA,GAAsB;EACxB;2CAIE,EAyCY,EAxCL,EAAG,CAAA,GADV,EAyCY;YAvCN;GAAJ,KAAI;GACH,iBAAe,EAAA,CAAA,CAAI,CAAC,YAAY,QAAK,SAAY,KAAA;GACjD,gBAAc,EAAA,WAAQ,SAAY,KAAA;GAClC,iBAAe,EAAU,IAAA,SAAc,KAAA;GACvC,iBAAe,EAAU,IAAK,EAAA,QAAU,KAAA;GACxC,iBAAe,EAAU,IAAK,EAAA,CAAA,IAAQ,KAAA;KAC/B,EAAA,CAAA,CAAI,CAAC,MAAM,OAAK;GACxB,MAAK;GACL,UAAS;GACT,OAAM;GACL,MAAM,EAAG,MAAA,WAAA,WAA6B,KAAA;GACtC,UAAU,EAAG,MAAA,WAAkB,EAAA,WAAW,KAAA;GAC1C,MAAM,EAAA,CAAA,CAAI,CAAC,SAAS;GACpB,aAAW,EAAA;GACX,iBAAe,EAAA,WAAQ,KAAQ,KAAA;GAC/B,eAAe,EAAU,IAAK,EAAA,CAAA,IAAQ,KAAA;GACtC,qBAAqB,EAAU,IAAA,SAAc,KAAA;GACtC;GACE;GACT,gBAAc;GACd,gBAAc;;oBAIR;IAFP,EAEO,EAAA,QAAA,SAAA,CAAA,SAAA,CADQ,EAAA,aAAA,EAAA,GAAb,EAAiF,GAAjF,EAAiF;;KAAzD,OAAM;OAA2B,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA;IAE9E,EAOO,QAPP,GAOO,CANL,EAEC,QAFD,GAEC,CADE,EAAwB,EAAA,QAAA,WAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAEL,EAAA,aAAa,KAAA,KAAaA,EAAAA,OAAO,YAAA,EAAA,GAA7C,EAEO,QAFP,GAEO,CADL,EAA2C,EAAA,QAAA,YAAA,CAAA,SAAA,CAAA,EAAA,EAAlB,EAAA,QAAQ,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA;IAKxB,EAAU,KAAA,EAAA,GAAvB,EAA2F,GAAA;;KAA/D,MAAM,EAAA,CAAA;KAAkB,OAAM;KAAsB,UAAA;6BAChF,EAEO,EAAA,QAAA,OAAA,CAAA,SAAA,CADQ,EAAA,WAAA,EAAA,GAAb,EAAoD,GAAA,EAAA,EAAA,EAAA,KAAA,EAAA,GAAtB,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,CAAA,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;;;;;;;;;;;;;;;;OAI3C,EAAU,KAAA,EAAA,GADlB,EAWa,GAAA;;GATV,IAAI,EAAA,CAAA;YACD;GAAJ,KAAI;GACJ,WAAU;GACV,SAAA;GACC,UAAM,AAAA,EAAA,QAAA,MAAE,EAAA,QAAU;GAClB,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAA,CAAA,CAAU,CAAC,OAAM;GAC/B,gBAAc;;oBAEQ,CAAvB,EAAuB,EAAA,QAAA,SAAA,CAAA,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
@layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
|
|
2
|
-
@layer vectis.components{.v-menu{min-inline-size:var(--vectis-control-size-menu-min);max-inline-size:min(var(--vectis-control-size-menu-max), calc(
|
|
2
|
+
@layer vectis.components{.v-menu{min-inline-size:var(--vectis-control-size-menu-min);max-inline-size:min(var(--vectis-control-size-menu-max), calc(100dvi - var(--vectis-space-8)))}.v-menu:focus{outline:none}.v-menu .v-menu[data-placement=right-start]{margin-block-start:calc(-1 * (var(--vectis-space-1) + 1px))}.v-menu[data-width]{min-inline-size:0;inline-size:var(--menu-width)}.v-menu[data-match-trigger]{min-inline-size:anchor-size(width)}}
|
|
@@ -47,9 +47,9 @@ declare const __VLS_base: import('vue').DefineComponent<MenuPanelProps, {
|
|
|
47
47
|
close: () => void;
|
|
48
48
|
/** Whether it is open, as the browser last reported it. VMenu's model is checked against it. */
|
|
49
49
|
shown: import('vue').Ref<boolean, boolean>;
|
|
50
|
-
/** Puts the focus on the first reachable item
|
|
50
|
+
/** Puts the focus on the first reachable item: the keyboard's way in. */
|
|
51
51
|
focusFirst: typeof focusFirst;
|
|
52
|
-
/** Puts the focus on the panel itself
|
|
52
|
+
/** Puts the focus on the panel itself: the pointer's way in, singling out no command. */
|
|
53
53
|
focusPanel: typeof focusPanel;
|
|
54
54
|
/** The panel element, which is the popover. */
|
|
55
55
|
el: import('vue').Ref<HTMLElement | null, HTMLElement | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMenuPanel.js","names":[],"sources":["../../../src/components/VMenu/VMenuPanel.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @keyboard @core\n/**\n * The panel a menu is drawn in. Internal to VMenu, and the same component serves both\n * levels — the root menu and every submenu.\n *\n * It rests on `popover=\"auto\"`, which gives it light dismiss, positioning against its\n * invoker, and — a submenu being rendered inside its parent panel — a native stack the\n * browser closes from the outside in. Its look comes from the shared `.v-panel`.\n *\n * It carries `tabindex=\"-1\"` so it can hold the focus itself, which it does whenever the\n * menu was opened by POINTER: no command is singled out, but the keyboard still has\n * somewhere to arrive, everything below being listened for on this element.\n *\n * The JS is the ARIA menu keyboard, which the platform does not provide:\n *\n * - arrows and Home/End move focus item to item, CONFINED to the panel the focus is in — a\n * keystroke inside a submenu also bubbles through every panel containing it, hence the\n * guard on where the event came from;\n * - Escape closes the CURRENT LEVEL only, as does ArrowLeft in a submenu, handing focus\n * back to the item that opened it;\n * - Tab closes the whole menu, a menu not being something one tabs through.\n */\n\nimport { computed, inject, ref } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport { menuInvoker, menuKey } from './context'\nimport type { MenuPanelPlacement, MenuSize } from './context'\nimport { arrowNavigate } from '../../utils/arrowNav'\nimport { cssSize } from '../../utils/css'\n\ninterface MenuPanelProps {\n /** The panel's id, set by whoever owns it. It is what the trigger points at. */\n id: string\n /** Where the panel opens relative to whatever opened it. */\n placement?: MenuPanelPlacement\n /**\n * Marks this panel as a submenu, which enables the left arrow as a way back and keeps\n * the row-height settings off it, so that it inherits its parent's instead.\n */\n submenu?: boolean\n /**\n * The row height, set by the ROOT panel only. Submenus receive no value and inherit\n * it through CSS instead.\n */\n size?: MenuSize\n /** The reduced density, again set by the ROOT panel only and inherited through CSS. */\n compact?: boolean\n /**\n * An explicit width, set by the ROOT panel only. Submenus render no width attribute\n * and keep the default one, so an inherited value cannot reach them.\n */\n width?: number | string\n /**\n * Stops the panel from being narrower than its trigger, set by the ROOT panel only:\n * a submenu's trigger is a menu item, not a control worth matching.\n */\n matchTrigger?: boolean\n}\n\nconst props = withDefaults(defineProps<MenuPanelProps>(), {\n placement: 'bottom-start',\n submenu: false,\n size: undefined,\n compact: false,\n width: undefined,\n matchTrigger: false,\n})\n\nconst emit = defineEmits<{\n /**\n * The panel opened or closed. It is reported by the browser, so it also covers a\n * dismissal nobody asked for in code.\n */\n toggle: [open: boolean]\n}>()\n\ndefineSlots<{\n /** The contents of the panel: VMenuItem, VMenuGroup and VMenuSeparator. */\n default(): unknown\n}>()\n\nconst panelEl = ref<HTMLElement | null>(null)\n// The open state, the guards keeping the calls idempotent, and the option that gives\n// a programmatically opened submenu its anchor, all live in usePopover.\nconst { shown, syncShown, show, hide } = usePopover(panelEl)\n\n// Never absent: this panel is internal, rendered only by VMenu and by the VMenuItems inside\n// one, and VMenu provides the key before either exists.\nconst menu = inject(menuKey)!\n\nfunction onToggle(event: Event) {\n syncShown(event)\n emit('toggle', shown.value)\n}\n\n// @a11y @keyboard\n/**\n * The items the keyboard may move to. Two exclusions matter: `:disabled` only ever\n * matches a `<button>`, so an inert link is recognized by its `aria-disabled`\n * instead; and the final filter keeps only the items of THIS panel, since an open\n * submenu's items are DOM descendants of it and would otherwise join the list.\n */\nfunction items(): HTMLElement[] {\n const panel = panelEl.value\n if (!panel) return []\n return [\n ...panel.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"]:not(:disabled):not([aria-disabled=\"true\"])',\n ),\n ].filter((el) => el.closest('[role=\"menu\"]') === panel)\n}\n\n// @a11y\n/** Puts the focus on the first item the keyboard can reach, when the menu opens. */\nfunction focusFirst() {\n items()[0]?.focus()\n}\n\n// @a11y\n/**\n * Puts the focus on the panel itself, singling out no command. This is where a menu\n * opened with a pointer lands: the reader has not asked for a first choice, but the\n * keyboard still has to work from the very next keystroke — and every key below is\n * listened for on this element, so leaving the focus outside would silence all of them.\n */\nfunction focusPanel() {\n panelEl.value?.focus()\n}\n\n// @keyboard @a11y\nfunction onKeydown(event: KeyboardEvent) {\n const panel = panelEl.value\n // A keystroke inside a submenu passes through every panel containing it on its way\n // up, so only the panel the focused item DIRECTLY belongs to acts on it.\n if (!panel || (event.target as Element).closest('[role=\"menu\"]') !== panel) return\n\n if (event.key === 'Tab') {\n menu.closeAll()\n return\n }\n if (event.key === 'Escape' || (props.submenu && event.key === 'ArrowLeft')) {\n // Escape closes THIS level and no more. The `preventDefault` matters: left to\n // itself the browser would close the popover on its own, without handing the\n // focus back to the item that opened it — and our own hide() would already have\n // closed it anyway. At the top level, VMenu is what returns the focus to the\n // trigger.\n event.preventDefault()\n hide()\n if (props.submenu) menuInvoker(props.id)?.focus()\n return\n }\n // Coming from the panel itself — a menu opened with a pointer, where no command is\n // singled out — the up arrow goes to the LAST item, entering the list from its far\n // end the way a menu is expected to. `utils/arrowNav` starts any arrow at the\n // beginning when nothing in the list has the focus, which is the right answer for the\n // tabs, the pages and the toggle group it also serves: it is this menu that has a\n // convention of its own, so it is handled here rather than in the shared helper.\n // The case only exists BECAUSE of the pointer opening: reached from an item, the\n // arrow never lands here.\n if (event.key === 'ArrowUp' && document.activeElement === panel) {\n const list = items()\n if (list.length > 0) {\n event.preventDefault()\n list[list.length - 1]?.focus()\n return\n }\n }\n // The movement itself is the shared one from `utils/arrowNav`, but it is handed OUR\n // list rather than letting it discover the items: ours also drops the disabled\n // links and stays inside this panel, neither of which the generic selector does.\n arrowNavigate(event, panel, items, { vertical: true })\n}\n\n// The only thing set inline is an explicit width, when the prop asks for one.\nconst resolvedWidth = computed(() => cssSize(props.width) || undefined)\nconst panelStyle = computed(() =>\n resolvedWidth.value ? { '--menu-width': resolvedWidth.value } : undefined,\n)\n\n// Internal to VMenu, which drives the panel from outside: opening has to be synchronous,\n// and where the focus lands depends on whether a pointer or the keyboard asked.\ndefineExpose({\n /** Opens the panel. Pass the invoker, or the popover has no anchor to position against. */\n show,\n /** Closes it. */\n close: hide,\n /** Whether it is open, as the browser last reported it. VMenu's model is checked against it. */\n shown,\n /** Puts the focus on the first reachable item — the keyboard's way in. */\n focusFirst,\n /** Puts the focus on the panel itself — the pointer's way in, singling out no command. */\n focusPanel,\n /** The panel element, which is the popover. */\n el: panelEl,\n})\n</script>\n\n<template>\n <div\n :id=\"id\"\n ref=\"panelEl\"\n popover=\"auto\"\n role=\"menu\"\n tabindex=\"-1\"\n class=\"v-overlay v-panel v-menu v-floating\"\n :class=\"{\n /*\n * The size class goes on the ROOT panel only. It sets the whole `--control-*`\n * family — height, paddings, gap, type, icon context — and the submenus, being\n * DOM descendants, inherit every one of them.\n *\n * Adding it to the submenus as well would BREAK them: the class recomputes the\n * height from the base one without knowing about the compact attribute, which\n * submenus do not carry, so a compact menu's submenus would silently snap back\n * to full height.\n */\n 'v-control': !submenu,\n }\"\n :data-placement=\"placement\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-width=\"resolvedWidth ? '' : undefined\"\n :data-match-trigger=\"matchTrigger ? '' : undefined\"\n :style=\"panelStyle\"\n @beforetoggle=\"syncShown\"\n @toggle=\"onToggle\"\n @keydown=\"onKeydown\"\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The panel's surface, border, shadow and inner rhythm come from the shared\n `.v-panel` class, and its dimensions from the `v-control` class the template sets\n on the root panel — there is no size table here, the rows reading the inherited\n variables directly. Only what is specific to a dropdown menu stays below. */\n .v-menu {\n min-inline-size: var(--vectis-control-size-menu-min);\n max-inline-size: min(var(--vectis-control-size-menu-max), calc(100vw - var(--vectis-space-8)));\n }\n\n /* The panel is focusable, but only ever from code — its `tabindex` is -1, so the Tab\n key never brings anyone here. It holds the focus when the menu was opened with a\n pointer, and a ring drawn around the whole panel would then read as the reader\n having landed on something, which is the opposite of what that state means: no\n command is singled out. The rows keep their own highlight, `.v-menu-item:focus`. */\n .v-menu:focus {\n outline: none;\n }\n\n /* Pulls a submenu up so that its first item lines up with the item that opened it,\n by exactly the panel's own padding plus its border. */\n .v-menu .v-menu[data-placement='right-start'] {\n margin-block-start: calc(-1 * (var(--vectis-space-1) + 1px));\n }\n\n /* An explicit width, which only the ROOT panel ever renders: a submenu sets no such\n attribute, so the variable it inherits stays inert there. The ceiling that keeps\n a panel inside the viewport is untouched. */\n .v-menu[data-width] {\n min-inline-size: 0;\n inline-size: var(--menu-width);\n }\n\n /* The panel may not be narrower than its trigger. This works with no anchor name of\n any kind, because the button that opened the panel is already its implicit anchor\n — VCombobox and VTimeInput, anchored to a text input, have to name theirs. Like\n the width above, only the ROOT panel renders it.\n\n It is placed AFTER the width rule, at equal specificity, so that when both are\n given the floor wins. A trigger wider than the ceiling therefore widens the panel\n past it, a minimum width beating a maximum one — which is intended. */\n .v-menu[data-match-trigger] {\n min-inline-size: anchor-size(width);\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VMenuPanel.js","names":[],"sources":["../../../src/components/VMenu/VMenuPanel.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @keyboard @core\n/**\n * The panel a menu is drawn in. Internal to VMenu, and the same component serves both\n * levels — the root menu and every submenu.\n *\n * It rests on `popover=\"auto\"`, which gives it light dismiss, positioning against its\n * invoker, and — a submenu being rendered inside its parent panel — a native stack the\n * browser closes from the outside in. Its look comes from the shared `.v-panel`.\n *\n * It carries `tabindex=\"-1\"` so it can hold the focus itself, which it does whenever the\n * menu was opened by POINTER: no command is singled out, but the keyboard still has\n * somewhere to arrive, everything below being listened for on this element.\n *\n * The JS is the ARIA menu keyboard, which the platform does not provide:\n *\n * - arrows and Home/End move focus item to item, CONFINED to the panel the focus is in — a\n * keystroke inside a submenu also bubbles through every panel containing it, hence the\n * guard on where the event came from;\n * - Escape closes the CURRENT LEVEL only, as does ArrowLeft in a submenu, handing focus\n * back to the item that opened it;\n * - Tab closes the whole menu, a menu not being something one tabs through.\n */\n\nimport { computed, inject, ref } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport { menuInvoker, menuKey } from './context'\nimport type { MenuPanelPlacement, MenuSize } from './context'\nimport { arrowNavigate } from '../../utils/arrowNav'\nimport { isRtl } from '../../utils/direction'\nimport { cssSize } from '../../utils/css'\n\ninterface MenuPanelProps {\n /** The panel's id, set by whoever owns it. It is what the trigger points at. */\n id: string\n /** Where the panel opens relative to whatever opened it. */\n placement?: MenuPanelPlacement\n /**\n * Marks this panel as a submenu, which enables the left arrow as a way back and keeps\n * the row-height settings off it, so that it inherits its parent's instead.\n */\n submenu?: boolean\n /**\n * The row height, set by the ROOT panel only. Submenus receive no value and inherit\n * it through CSS instead.\n */\n size?: MenuSize\n /** The reduced density, again set by the ROOT panel only and inherited through CSS. */\n compact?: boolean\n /**\n * An explicit width, set by the ROOT panel only. Submenus render no width attribute\n * and keep the default one, so an inherited value cannot reach them.\n */\n width?: number | string\n /**\n * Stops the panel from being narrower than its trigger, set by the ROOT panel only:\n * a submenu's trigger is a menu item, not a control worth matching.\n */\n matchTrigger?: boolean\n}\n\nconst props = withDefaults(defineProps<MenuPanelProps>(), {\n placement: 'bottom-start',\n submenu: false,\n size: undefined,\n compact: false,\n width: undefined,\n matchTrigger: false,\n})\n\nconst emit = defineEmits<{\n /**\n * The panel opened or closed. It is reported by the browser, so it also covers a\n * dismissal nobody asked for in code.\n */\n toggle: [open: boolean]\n}>()\n\ndefineSlots<{\n /** The contents of the panel: VMenuItem, VMenuGroup and VMenuSeparator. */\n default(): unknown\n}>()\n\nconst panelEl = ref<HTMLElement | null>(null)\n// The open state, the guards keeping the calls idempotent, and the option that gives\n// a programmatically opened submenu its anchor, all live in usePopover.\nconst { shown, syncShown, show, hide } = usePopover(panelEl)\n\n// Never absent: this panel is internal, rendered only by VMenu and by the VMenuItems inside\n// one, and VMenu provides the key before either exists.\nconst menu = inject(menuKey)!\n\nfunction onToggle(event: Event) {\n syncShown(event)\n emit('toggle', shown.value)\n}\n\n// @a11y @keyboard\n/**\n * The items the keyboard may move to. Two exclusions matter: `:disabled` only ever\n * matches a `<button>`, so an inert link is recognized by its `aria-disabled`\n * instead; and the final filter keeps only the items of THIS panel, since an open\n * submenu's items are DOM descendants of it and would otherwise join the list.\n */\nfunction items(): HTMLElement[] {\n const panel = panelEl.value\n if (!panel) return []\n return [\n ...panel.querySelectorAll<HTMLElement>(\n '[role=\"menuitem\"]:not(:disabled):not([aria-disabled=\"true\"])',\n ),\n ].filter((el) => el.closest('[role=\"menu\"]') === panel)\n}\n\n// @a11y\n/** Puts the focus on the first item the keyboard can reach, when the menu opens. */\nfunction focusFirst() {\n items()[0]?.focus()\n}\n\n// @a11y\n/**\n * Puts the focus on the panel itself, singling out no command. This is where a menu\n * opened with a pointer lands: the reader has not asked for a first choice, but the\n * keyboard still has to work from the very next keystroke — and every key below is\n * listened for on this element, so leaving the focus outside would silence all of them.\n */\nfunction focusPanel() {\n panelEl.value?.focus()\n}\n\n// @keyboard @a11y\nfunction onKeydown(event: KeyboardEvent) {\n const panel = panelEl.value\n // A keystroke inside a submenu passes through every panel containing it on its way\n // up, so only the panel the focused item DIRECTLY belongs to acts on it.\n if (!panel || (event.target as Element).closest('[role=\"menu\"]') !== panel) return\n // A key held with a modifier is the browser's: Alt+Left is Back.\n if (event.altKey || event.ctrlKey || event.metaKey) return\n\n if (event.key === 'Tab') {\n menu.closeAll()\n return\n }\n // The arrow pointing BACK at the parent closes a submenu: the right one in a\n // right-to-left page, where the submenu opened on the left.\n const back = isRtl(panel) ? 'ArrowRight' : 'ArrowLeft'\n if (event.key === 'Escape' || (props.submenu && event.key === back)) {\n // Escape closes THIS level and no more. The `preventDefault` matters: left to\n // itself the browser would close the popover on its own, without handing the\n // focus back to the item that opened it — and our own hide() would already have\n // closed it anyway. At the top level, VMenu is what returns the focus to the\n // trigger.\n event.preventDefault()\n hide()\n if (props.submenu) menuInvoker(props.id)?.focus()\n return\n }\n // Coming from the panel itself — a menu opened with a pointer, where no command is\n // singled out — the up arrow goes to the LAST item, entering the list from its far\n // end the way a menu is expected to. `utils/arrowNav` starts any arrow at the\n // beginning when nothing in the list has the focus, which is the right answer for the\n // tabs, the pages and the toggle group it also serves: it is this menu that has a\n // convention of its own, so it is handled here rather than in the shared helper.\n // The case only exists BECAUSE of the pointer opening: reached from an item, the\n // arrow never lands here.\n if (event.key === 'ArrowUp' && document.activeElement === panel) {\n const list = items()\n if (list.length > 0) {\n event.preventDefault()\n list[list.length - 1]?.focus()\n return\n }\n }\n // The movement itself is the shared one from `utils/arrowNav`, but it is handed OUR\n // list rather than letting it discover the items: ours also drops the disabled\n // links and stays inside this panel, neither of which the generic selector does.\n arrowNavigate(event, panel, items, { vertical: true })\n}\n\n// The only thing set inline is an explicit width, when the prop asks for one.\nconst resolvedWidth = computed(() => cssSize(props.width) || undefined)\nconst panelStyle = computed(() =>\n resolvedWidth.value ? { '--menu-width': resolvedWidth.value } : undefined,\n)\n\n// Internal to VMenu, which drives the panel from outside: opening has to be synchronous,\n// and where the focus lands depends on whether a pointer or the keyboard asked.\ndefineExpose({\n /** Opens the panel. Pass the invoker, or the popover has no anchor to position against. */\n show,\n /** Closes it. */\n close: hide,\n /** Whether it is open, as the browser last reported it. VMenu's model is checked against it. */\n shown,\n /** Puts the focus on the first reachable item: the keyboard's way in. */\n focusFirst,\n /** Puts the focus on the panel itself: the pointer's way in, singling out no command. */\n focusPanel,\n /** The panel element, which is the popover. */\n el: panelEl,\n})\n</script>\n\n<template>\n <div\n :id=\"id\"\n ref=\"panelEl\"\n popover=\"auto\"\n role=\"menu\"\n tabindex=\"-1\"\n class=\"v-overlay v-panel v-menu v-floating\"\n :class=\"{\n /*\n * The size class goes on the ROOT panel only. It sets the whole `--control-*`\n * family — height, paddings, gap, type, icon context — and the submenus, being\n * DOM descendants, inherit every one of them.\n *\n * Adding it to the submenus as well would BREAK them: the class recomputes the\n * height from the base one without knowing about the compact attribute, which\n * submenus do not carry, so a compact menu's submenus would silently snap back\n * to full height.\n */\n 'v-control': !submenu,\n }\"\n :data-placement=\"placement\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-width=\"resolvedWidth ? '' : undefined\"\n :data-match-trigger=\"matchTrigger ? '' : undefined\"\n :style=\"panelStyle\"\n @beforetoggle=\"syncShown\"\n @toggle=\"onToggle\"\n @keydown=\"onKeydown\"\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The panel's surface, border, shadow and inner rhythm come from the shared\n `.v-panel` class, and its dimensions from the `v-control` class the template sets\n on the root panel — there is no size table here, the rows reading the inherited\n variables directly. Only what is specific to a dropdown menu stays below. */\n .v-menu {\n min-inline-size: var(--vectis-control-size-menu-min);\n max-inline-size: min(var(--vectis-control-size-menu-max), calc(100dvi - var(--vectis-space-8)));\n }\n\n /* The panel is focusable, but only ever from code — its `tabindex` is -1, so the Tab\n key never brings anyone here. It holds the focus when the menu was opened with a\n pointer, and a ring drawn around the whole panel would then read as the reader\n having landed on something, which is the opposite of what that state means: no\n command is singled out. The rows keep their own highlight, `.v-menu-item:focus`. */\n .v-menu:focus {\n outline: none;\n }\n\n /* Pulls a submenu up so that its first item lines up with the item that opened it,\n by exactly the panel's own padding plus its border. */\n .v-menu .v-menu[data-placement='right-start'] {\n margin-block-start: calc(-1 * (var(--vectis-space-1) + 1px));\n }\n\n /* An explicit width, which only the ROOT panel ever renders: a submenu sets no such\n attribute, so the variable it inherits stays inert there. The ceiling that keeps\n a panel inside the viewport is untouched. */\n .v-menu[data-width] {\n min-inline-size: 0;\n inline-size: var(--menu-width);\n }\n\n /* The panel may not be narrower than its trigger. This works with no anchor name of\n any kind, because the button that opened the panel is already its implicit anchor\n — VCombobox and VTimeInput, anchored to a text input, have to name theirs. Like\n the width above, only the ROOT panel renders it.\n\n It is placed AFTER the width rule, at equal specificity, so that when both are\n given the floor wins. A trigger wider than the ceiling therefore widens the panel\n past it, a minimum width beating a maximum one — which is intended. */\n .v-menu[data-match-trigger] {\n min-inline-size: anchor-size(width);\n }\n}\n</style>\n"],"mappings":""}
|