vectis-ui 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +22 -9
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +67 -57
- 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,2 +1,2 @@
|
|
|
1
1
|
@layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
|
|
2
|
-
@layer vectis.components{.v-button{justify-content:center;align-items:center;gap:var(--control-gap);
|
|
2
|
+
@layer vectis.components{.v-button{justify-content:center;align-items:center;gap:var(--control-gap);block-size:var(--control-height);padding-inline:var(--control-padding-inline);border-radius:var(--vectis-radius-interactive);font-family:var(--vectis-text-family);font-size:var(--control-font-size);font-weight:var(--vectis-text-control-weight);line-height:var(--vectis-text-control-leading);cursor:pointer;transition:background-color var(--vectis-duration-fast) var(--vectis-ease-default), border-color var(--vectis-duration-fast) var(--vectis-ease-default), color var(--vectis-duration-fast) var(--vectis-ease-default), box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);border:1px solid #0000;text-decoration:none;display:inline-flex}.v-button[data-full-width]{inline-size:100%;display:flex}.v-button[data-icon-only]{min-inline-size:var(--control-height);padding-inline:0}.v-button:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:var(--vectis-focus-ring-offset)}.v-button[data-variant=solid]:hover:not(:disabled,[aria-disabled=true]){background:var(--tone-bg-solid-hover)}.v-button[data-variant=solid]:active:not(:disabled,[aria-disabled=true]){background:var(--tone-bg-solid-active)}.v-button:is([data-variant=outline],[data-variant=ghost]):not([data-elevated]):hover:not(:disabled,[aria-disabled=true]){background:var(--tone-bg-soft)}.v-button:is([data-variant=outline],[data-variant=ghost]):not([data-elevated]):active:not(:disabled,[aria-disabled=true]),.v-button[data-variant=soft]:hover:not(:disabled,[aria-disabled=true]){background:color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%)}.v-button[data-variant=soft]:active:not(:disabled,[aria-disabled=true]){background:color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%)}.v-button[data-elevated]{box-shadow:var(--vectis-shadow-sm)}.v-button[data-elevated]:is([data-variant=ghost],[data-variant=outline]){background:var(--vectis-color-surface-raised)}.v-button[data-elevated]:hover:not(:disabled,[aria-disabled=true]){box-shadow:var(--vectis-shadow-md)}.v-button[data-elevated]:is([data-variant=ghost],[data-variant=outline]):hover:not(:disabled,[aria-disabled=true]){background:color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%)}.v-button[data-elevated]:active:not(:disabled,[aria-disabled=true]){box-shadow:var(--vectis-shadow-sm)}.v-button[data-elevated]:is([data-variant=ghost],[data-variant=outline]):active:not(:disabled,[aria-disabled=true]){background:color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 12%)}.v-button:is(:disabled,[aria-disabled=true]){cursor:not-allowed}.v-button[data-loading]{opacity:.5}.v-button:is(:disabled,[aria-disabled=true]):not([data-loading]):is([data-variant=solid],[data-variant=soft]){background:var(--vectis-color-surface-muted);color:var(--vectis-color-text-subtle)}.v-button:is(:disabled,[aria-disabled=true]):not([data-loading])[data-variant=outline]{color:var(--vectis-color-text-subtle);border-color:var(--vectis-color-border);background:0 0}.v-button:is(:disabled,[aria-disabled=true]):not([data-loading])[data-variant=ghost]{color:var(--vectis-color-text-subtle);background:0 0}.v-button:is(:disabled,[aria-disabled=true]):not([data-loading])[data-elevated]{box-shadow:none}.v-button-spinner{font-size:var(--vectis-icon-size);flex:none;justify-content:center;align-items:center;block-size:1em;inline-size:1em;display:inline-flex}@media (prefers-reduced-motion:reduce){.v-button{transition:none}}}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { ButtonHTMLAttributes } from 'vue';
|
|
2
2
|
import { IconSource } from '../VIcon/types.js';
|
|
3
|
+
/** How much visual weight the button carries. */
|
|
3
4
|
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft';
|
|
5
|
+
/** What the action means, in colour. */
|
|
4
6
|
export type ButtonTone = 'accent' | 'neutral' | 'danger';
|
|
7
|
+
/** The height of the button, from the scale every control shares. */
|
|
5
8
|
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
6
9
|
interface ButtonProps {
|
|
7
10
|
/**
|
|
@@ -22,7 +25,7 @@ interface ButtonProps {
|
|
|
22
25
|
/**
|
|
23
26
|
* Raises the button off the page: a shadow that grows on hover and settles back
|
|
24
27
|
* when pressed, whatever the variant. On `ghost` and `outline`, which have no
|
|
25
|
-
* background of their own, it also paints the raised surface
|
|
28
|
+
* background of their own, it also paints the raised surface: in the dark theme
|
|
26
29
|
* a shadow lying straight on the page background has nothing casting it. Inside a
|
|
27
30
|
* VButtonGroup the group's own value wins over this one.
|
|
28
31
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VButton.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y — the component's ONLY behavioural JS, and it exists entirely for\n// accessibility: an <a> has no native `disabled`, so the inert-link bridge has to\n// reproduce it by hand.\n/**\n * The button that triggers an action, and the reference from which the tone and\n * variant tables of every other coloured component are taken.\n *\n * It renders a native <button>, or an <a> as soon as it is given an `href`, so\n * focus, keyboard activation and disabling all come from the browser. Its one piece\n * of behavioural code is the inert link: a link has no `disabled` attribute of its\n * own, so a disabled or loading <a> has its address removed — it can then be\n * neither focused nor followed — receives `aria-disabled` for assistive technology,\n * and has the click handlers it was given filtered out. Together these reproduce\n * the complete inertness that a <button disabled> gets for free.\n *\n * Inside a VButtonGroup it reads what the row decided: the group wins on the four\n * props that give the control its shape, the button keeps its own tone. The rule and\n * its reasons live in `context.ts`.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/*\n * The three unions a VButtonGroup has to name in order to hand them down, declared\n * BEFORE the interface and referenced from it. `build-api.ts` prints a type as the\n * source spells it, so this is what makes the documentation site name the type on the\n * Button page as it already does on the Chip, Tabs and Toggle ones — and what stops\n * the IconButton page from naming a type its own page leaves unnamed.\n */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone,\n * `soft` uses a tinted background, `outline` keeps only a border, and `ghost`\n * shows nothing until it is hovered. Inside a VButtonGroup the group's own variant\n * wins over this one.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary\n * one, `danger` for one that destroys something. On a button a tone is an\n * intention, which is why states such as success or warning are not offered here.\n * Left out inside a VButtonGroup it takes the group's tone, which is the point of\n * not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back\n * when pressed, whatever the variant. On `ghost` and `outline`, which have no\n * background of their own, it also paints the raised surface — in the dark theme\n * a shadow lying straight on the page background has nothing casting it. Inside a\n * VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving\n * it only as wide as its content. The label stays centred, and the button becomes\n * block-level, so it no longer sits on a line of text.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // TRAP — the only one of the five with no default. `tone` is the prop a button keeps\n // against its group, so `undefined` has to stay distinguishable from an explicit\n // `accent`, which would otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n :aria-busy=\"loading || undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n height: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* An icon and no label: the button becomes a square, as wide as it is tall. The attribute\n is set by whatever renders the button that way — VIconButton always, a VTab or a\n VToggleItem given an icon and no label — and the rule lives here because this sheet\n owns the padding it cancels.\n\n A FLOOR rather than a width, so a box that is stretched (a column of segments, a\n full-width row) still fills its track, and an icon wider than the box still fits.\n\n TRAP — at (0,2,0) it beats this sheet's own padding by weight, and it is the only rule\n that may touch that padding on an icon-only button. A consumer of VButton restyling the\n padding of its own buttons at (0,2,0) from another sheet would tie with it, and the\n bundler would decide: such a rule has to exclude `[data-icon-only]`. */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The tone table lives in styles/tones.css (`v-tone`, in the vectis.tokens layer) and the\n four ways of painting it in styles/variants.css (`v-variant`, in vectis.components).\n VButton overrides not a single one of their values: it is the reference they were\n written for, and what stays here is the STATES, which each component answers for\n itself. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /* The `:not([data-elevated])` is what makes these rules DISJOINT from the\n elevation rules below, rather than merely less specific than them. Nothing then\n arbitrates the background of a raised ghost by a one-step specificity margin,\n so a declaration added to either set later cannot silently leak into the other\n case. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /* Elevation is ORTHOGONAL to the variant: the shadow scale applies to all four of\n them, while the raised surface is only painted on the two that have no\n background of their own. The `:is()` here is not decoration — it is what gives\n these rules a specificity of (0,3,0), beating the (0,2,0) of the ghost and\n outline bases without depending on the order the rules end up in. */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /* The shadow needs a rule of its OWN. No variant declares a shadow, so none of the\n three rules above cancels one, and a disabled raised ghost would otherwise go on\n casting its shadow-sm. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /* A box exactly the size of an icon, since the spinner takes the place of\n iconStart: the button's width does not jump when it starts loading. The\n font-size is set to that same size so that VSpinner's own box, which measures\n 1em, coincides with this one — VSpinner then draws its ring inside it at the\n proportion Material Symbols gives `progress_activity`, which is what makes the\n spinner and the icon it replaces read at the same size. */\n width: var(--vectis-icon-size);\n height: var(--vectis-icon-size);\n font-size: var(--vectis-icon-size);\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VButton.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n// The component's ONLY behavioural JS, and it exists entirely for\n// accessibility: an <a> has no native `disabled`, so the inert-link bridge has to\n// reproduce it by hand.\n/**\n * The button that triggers an action, and the reference from which the tone and\n * variant tables of every other coloured component are taken.\n *\n * It renders a native <button>, or an <a> as soon as it is given an `href`, so\n * focus, keyboard activation and disabling all come from the browser. Its one piece\n * of behavioural code is the inert link: a link has no `disabled` attribute of its\n * own, so a disabled or loading <a> has its address removed — it can then be\n * neither focused nor followed — receives `aria-disabled` for assistive technology,\n * and has the click handlers it was given filtered out. Together these reproduce\n * the complete inertness that a <button disabled> gets for free.\n *\n * Inside a VButtonGroup it reads what the row decided: the group wins on the four\n * props that give the control its shape, the button keeps its own tone. The rule and\n * its reasons live in `context.ts`.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/*\n * The three unions a VButtonGroup has to name in order to hand them down, declared\n * BEFORE the interface and referenced from it. `build-api.ts` prints a type as the\n * source spells it, so this is what makes the documentation site name the type on the\n * Button page as it already does on the Chip, Tabs and Toggle ones — and what stops\n * the IconButton page from naming a type its own page leaves unnamed.\n */\n/** How much visual weight the button carries. */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\n/** What the action means, in colour. */\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\n/** The height of the button, from the scale every control shares. */\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone,\n * `soft` uses a tinted background, `outline` keeps only a border, and `ghost`\n * shows nothing until it is hovered. Inside a VButtonGroup the group's own variant\n * wins over this one.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary\n * one, `danger` for one that destroys something. On a button a tone is an\n * intention, which is why states such as success or warning are not offered here.\n * Left out inside a VButtonGroup it takes the group's tone, which is the point of\n * not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back\n * when pressed, whatever the variant. On `ghost` and `outline`, which have no\n * background of their own, it also paints the raised surface: in the dark theme\n * a shadow lying straight on the page background has nothing casting it. Inside a\n * VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving\n * it only as wide as its content. The label stays centred, and the button becomes\n * block-level, so it no longer sits on a line of text.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // TRAP — the only one of the five with no default. `tone` is the prop a button keeps\n // against its group, so `undefined` has to stay distinguishable from an explicit\n // `accent`, which would otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n block-size: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* An icon and no label: the button becomes a square, as wide as it is tall. The attribute\n is set by whatever renders the button that way — VIconButton always, a VTab or a\n VToggleItem given an icon and no label — and the rule lives here because this sheet\n owns the padding it cancels.\n\n A FLOOR rather than a width, so a box that is stretched (a column of segments, a\n full-width row) still fills its track, and an icon wider than the box still fits.\n\n TRAP — at (0,2,0) it beats this sheet's own padding by weight, and it is the only rule\n that may touch that padding on an icon-only button. A consumer of VButton restyling the\n padding of its own buttons at (0,2,0) from another sheet would tie with it, and the\n bundler would decide: such a rule has to exclude `[data-icon-only]`. */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The tone table lives in styles/tones.css (`v-tone`, in the vectis.tokens layer) and the\n four ways of painting it in styles/variants.css (`v-variant`, in vectis.components).\n VButton overrides not a single one of their values: it is the reference they were\n written for, and what stays here is the STATES, which each component answers for\n itself. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /* The `:not([data-elevated])` is what makes these rules DISJOINT from the\n elevation rules below, rather than merely less specific than them. Nothing then\n arbitrates the background of a raised ghost by a one-step specificity margin,\n so a declaration added to either set later cannot silently leak into the other\n case. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /* Elevation is ORTHOGONAL to the variant: the shadow scale applies to all four of\n them, while the raised surface is only painted on the two that have no\n background of their own. The `:is()` here is not decoration — it is what gives\n these rules a specificity of (0,3,0), beating the (0,2,0) of the ghost and\n outline bases without depending on the order the rules end up in. */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /* The shadow needs a rule of its OWN. No variant declares a shadow, so none of the\n three rules above cancels one, and a disabled raised ghost would otherwise go on\n casting its shadow-sm. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /* A box exactly the size of an icon, since the spinner takes the place of\n iconStart: a button with a start icon keeps its width when it starts loading\n (one with a label alone gains the spinner and the gap beside it). The\n font-size is set to that same size so that VSpinner's own box, which measures\n 1em, coincides with this one — VSpinner then draws its ring inside it at the\n proportion Material Symbols gives `progress_activity`, which is what makes the\n spinner and the icon it replaces read at the same size. */\n font-size: var(--vectis-icon-size);\n /* 1em of the line above rather than the variable again: a relative icon size would\n otherwise be resolved twice, once against the button's text and once against this\n box's own enlarged font-size. */\n inline-size: 1em;\n block-size: 1em;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -52,20 +52,21 @@ var b = {
|
|
|
52
52
|
inert: () => A.value,
|
|
53
53
|
attrs: () => S
|
|
54
54
|
});
|
|
55
|
-
return (n, r) => (m(), s(g(_(j) ? "a" : "button"), p(
|
|
55
|
+
return (n, r) => (m(), s(g(_(j) ? "a" : "button"), p({
|
|
56
|
+
"aria-disabled": _(M) ? "true" : void 0,
|
|
57
|
+
"aria-busy": d.loading || void 0
|
|
58
|
+
}, _(P), {
|
|
56
59
|
class: "v-button v-control v-tone v-variant",
|
|
57
60
|
href: _(N),
|
|
58
61
|
type: _(j) ? void 0 : d.type,
|
|
59
62
|
disabled: _(j) ? void 0 : A.value,
|
|
60
|
-
"aria-disabled": _(M) ? "true" : void 0,
|
|
61
63
|
"data-variant": D.value,
|
|
62
64
|
"data-tone": k.value,
|
|
63
65
|
"data-elevated": O.value ? "" : void 0,
|
|
64
66
|
"data-size": _(w),
|
|
65
67
|
"data-compact": _(T) ? "" : void 0,
|
|
66
68
|
"data-full-width": d.fullWidth ? "" : void 0,
|
|
67
|
-
"data-loading": d.loading ? "" : void 0
|
|
68
|
-
"aria-busy": d.loading || void 0
|
|
69
|
+
"data-loading": d.loading ? "" : void 0
|
|
69
70
|
}), {
|
|
70
71
|
default: y(() => [
|
|
71
72
|
d.loading ? (m(), l("span", b, [u(a)])) : h(n.$slots, "start", {}, () => [d.iconStart ? (m(), s(t, p({ key: 0 }, _(e)(d.iconStart), { filled: d.iconFilled }), null, 16, ["filled"])) : c("", !0)], void 0, 1),
|
|
@@ -74,18 +75,18 @@ var b = {
|
|
|
74
75
|
]),
|
|
75
76
|
_: 3
|
|
76
77
|
}, 16, [
|
|
78
|
+
"aria-disabled",
|
|
79
|
+
"aria-busy",
|
|
77
80
|
"href",
|
|
78
81
|
"type",
|
|
79
82
|
"disabled",
|
|
80
|
-
"aria-disabled",
|
|
81
83
|
"data-variant",
|
|
82
84
|
"data-tone",
|
|
83
85
|
"data-elevated",
|
|
84
86
|
"data-size",
|
|
85
87
|
"data-compact",
|
|
86
88
|
"data-full-width",
|
|
87
|
-
"data-loading"
|
|
88
|
-
"aria-busy"
|
|
89
|
+
"data-loading"
|
|
89
90
|
]));
|
|
90
91
|
}
|
|
91
92
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VButton.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y — the component's ONLY behavioural JS, and it exists entirely for\n// accessibility: an <a> has no native `disabled`, so the inert-link bridge has to\n// reproduce it by hand.\n/**\n * The button that triggers an action, and the reference from which the tone and\n * variant tables of every other coloured component are taken.\n *\n * It renders a native <button>, or an <a> as soon as it is given an `href`, so\n * focus, keyboard activation and disabling all come from the browser. Its one piece\n * of behavioural code is the inert link: a link has no `disabled` attribute of its\n * own, so a disabled or loading <a> has its address removed — it can then be\n * neither focused nor followed — receives `aria-disabled` for assistive technology,\n * and has the click handlers it was given filtered out. Together these reproduce\n * the complete inertness that a <button disabled> gets for free.\n *\n * Inside a VButtonGroup it reads what the row decided: the group wins on the four\n * props that give the control its shape, the button keeps its own tone. The rule and\n * its reasons live in `context.ts`.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/*\n * The three unions a VButtonGroup has to name in order to hand them down, declared\n * BEFORE the interface and referenced from it. `build-api.ts` prints a type as the\n * source spells it, so this is what makes the documentation site name the type on the\n * Button page as it already does on the Chip, Tabs and Toggle ones — and what stops\n * the IconButton page from naming a type its own page leaves unnamed.\n */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone,\n * `soft` uses a tinted background, `outline` keeps only a border, and `ghost`\n * shows nothing until it is hovered. Inside a VButtonGroup the group's own variant\n * wins over this one.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary\n * one, `danger` for one that destroys something. On a button a tone is an\n * intention, which is why states such as success or warning are not offered here.\n * Left out inside a VButtonGroup it takes the group's tone, which is the point of\n * not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back\n * when pressed, whatever the variant. On `ghost` and `outline`, which have no\n * background of their own, it also paints the raised surface — in the dark theme\n * a shadow lying straight on the page background has nothing casting it. Inside a\n * VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving\n * it only as wide as its content. The label stays centred, and the button becomes\n * block-level, so it no longer sits on a line of text.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // TRAP — the only one of the five with no default. `tone` is the prop a button keeps\n // against its group, so `undefined` has to stay distinguishable from an explicit\n // `accent`, which would otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n :aria-busy=\"loading || undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n height: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* An icon and no label: the button becomes a square, as wide as it is tall. The attribute\n is set by whatever renders the button that way — VIconButton always, a VTab or a\n VToggleItem given an icon and no label — and the rule lives here because this sheet\n owns the padding it cancels.\n\n A FLOOR rather than a width, so a box that is stretched (a column of segments, a\n full-width row) still fills its track, and an icon wider than the box still fits.\n\n TRAP — at (0,2,0) it beats this sheet's own padding by weight, and it is the only rule\n that may touch that padding on an icon-only button. A consumer of VButton restyling the\n padding of its own buttons at (0,2,0) from another sheet would tie with it, and the\n bundler would decide: such a rule has to exclude `[data-icon-only]`. */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The tone table lives in styles/tones.css (`v-tone`, in the vectis.tokens layer) and the\n four ways of painting it in styles/variants.css (`v-variant`, in vectis.components).\n VButton overrides not a single one of their values: it is the reference they were\n written for, and what stays here is the STATES, which each component answers for\n itself. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /* The `:not([data-elevated])` is what makes these rules DISJOINT from the\n elevation rules below, rather than merely less specific than them. Nothing then\n arbitrates the background of a raised ghost by a one-step specificity margin,\n so a declaration added to either set later cannot silently leak into the other\n case. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /* Elevation is ORTHOGONAL to the variant: the shadow scale applies to all four of\n them, while the raised surface is only painted on the two that have no\n background of their own. The `:is()` here is not decoration — it is what gives\n these rules a specificity of (0,3,0), beating the (0,2,0) of the ghost and\n outline bases without depending on the order the rules end up in. */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /* The shadow needs a rule of its OWN. No variant declares a shadow, so none of the\n three rules above cancels one, and a disabled raised ghost would otherwise go on\n casting its shadow-sm. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /* A box exactly the size of an icon, since the spinner takes the place of\n iconStart: the button's width does not jump when it starts loading. The\n font-size is set to that same size so that VSpinner's own box, which measures\n 1em, coincides with this one — VSpinner then draws its ring inside it at the\n proportion Material Symbols gives `progress_activity`, which is what makes the\n spinner and the icon it replaces read at the same size. */\n width: var(--vectis-icon-size);\n height: var(--vectis-icon-size);\n font-size: var(--vectis-icon-size);\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkHA,IAAM,IAAQ,GAiCR,IAAQ,EAAS,GACjB,IAAQ,EAAO,GAAgB,IAAI,GAKnC,EACJ,MAAM,GACN,SAAS,GACT,UAAU,MACR,EAA4B,GAAO,CAAK,GACtC,IAAkB,QAA8B,GAAO,WAAW,EAAM,OAAO,GAC/E,IAAmB,QAAe,GAAO,YAAY,EAAM,QAAQ,GAGnE,IAAe,QAA2B,EAAM,QAAQ,GAAO,QAAQ,QAAQ,GAE/E,IAAU,QAAe,EAAiB,SAAS,EAAM,OAAO,GAChE,EACJ,WACA,gBACA,aACA,OAAO,MACL,EAAa;GACf,YAAY,EAAM;GAClB,aAAa,EAAQ;GACrB,aAAa;EACf,CAAC;yBAIC,EA8BY,EA7BL,EAAA,CAAA,IAAM,MAAA,QAAA,GADb,EAEU,EA4BE,CAAA,GA5BS;GACnB,OAAM;GACL,MAAM,EAAA,CAAA;GACN,MAAM,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC3B,UAAU,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC/B,iBAAe,EAAA,CAAA,IAAW,SAAY,KAAA;GACtC,gBAAc,EAAA;GACd,aAAW,EAAA;GACX,iBAAe,EAAA,QAAgB,KAAQ,KAAA;GACvC,aAAW,EAAA,CAAA;GACX,gBAAc,EAAA,CAAA,IAAe,KAAQ,KAAA;GACrC,mBAAiB,EAAA,YAAS,KAAQ,KAAA;GAClC,gBAAc,EAAA,UAAO,KAAQ,KAAA;GAC7B,aAAW,EAAA,WAAW,KAAA;;oBAOhB;IAFK,EAAA,WAAA,EAAA,GAAZ,EAEO,QAFP,GAEO,CADL,EAAY,CAAA,CAAA,CAAA,KAEd,EAEO,EAAA,QAAA,SAAA,CAAA,SAAA,CADQ,EAAA,aAAA,EAAA,GAAb,EAA6E,GAA7E,EAA6E,EAAA,KAAA,EAAA,GAA7C,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;IAE3E,EAAQ,EAAA,QAAA,SAAA;IACR,EAEO,EAAA,QAAA,OAAA,CAAA,SAAA,CADQ,EAAA,WAAA,EAAA,GAAb,EAAyE,GAAzE,EAAyE,EAAA,KAAA,EAAA,GAA3C,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VButton.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n// The component's ONLY behavioural JS, and it exists entirely for\n// accessibility: an <a> has no native `disabled`, so the inert-link bridge has to\n// reproduce it by hand.\n/**\n * The button that triggers an action, and the reference from which the tone and\n * variant tables of every other coloured component are taken.\n *\n * It renders a native <button>, or an <a> as soon as it is given an `href`, so\n * focus, keyboard activation and disabling all come from the browser. Its one piece\n * of behavioural code is the inert link: a link has no `disabled` attribute of its\n * own, so a disabled or loading <a> has its address removed — it can then be\n * neither focused nor followed — receives `aria-disabled` for assistive technology,\n * and has the click handlers it was given filtered out. Together these reproduce\n * the complete inertness that a <button disabled> gets for free.\n *\n * Inside a VButtonGroup it reads what the row decided: the group wins on the four\n * props that give the control its shape, the button keeps its own tone. The rule and\n * its reasons live in `context.ts`.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/*\n * The three unions a VButtonGroup has to name in order to hand them down, declared\n * BEFORE the interface and referenced from it. `build-api.ts` prints a type as the\n * source spells it, so this is what makes the documentation site name the type on the\n * Button page as it already does on the Chip, Tabs and Toggle ones — and what stops\n * the IconButton page from naming a type its own page leaves unnamed.\n */\n/** How much visual weight the button carries. */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\n/** What the action means, in colour. */\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\n/** The height of the button, from the scale every control shares. */\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone,\n * `soft` uses a tinted background, `outline` keeps only a border, and `ghost`\n * shows nothing until it is hovered. Inside a VButtonGroup the group's own variant\n * wins over this one.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary\n * one, `danger` for one that destroys something. On a button a tone is an\n * intention, which is why states such as success or warning are not offered here.\n * Left out inside a VButtonGroup it takes the group's tone, which is the point of\n * not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back\n * when pressed, whatever the variant. On `ghost` and `outline`, which have no\n * background of their own, it also paints the raised surface: in the dark theme\n * a shadow lying straight on the page background has nothing casting it. Inside a\n * VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving\n * it only as wide as its content. The label stays centred, and the button becomes\n * block-level, so it no longer sits on a line of text.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // TRAP — the only one of the five with no default. `tone` is the prop a button keeps\n // against its group, so `undefined` has to stay distinguishable from an explicit\n // `accent`, which would otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n block-size: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* An icon and no label: the button becomes a square, as wide as it is tall. The attribute\n is set by whatever renders the button that way — VIconButton always, a VTab or a\n VToggleItem given an icon and no label — and the rule lives here because this sheet\n owns the padding it cancels.\n\n A FLOOR rather than a width, so a box that is stretched (a column of segments, a\n full-width row) still fills its track, and an icon wider than the box still fits.\n\n TRAP — at (0,2,0) it beats this sheet's own padding by weight, and it is the only rule\n that may touch that padding on an icon-only button. A consumer of VButton restyling the\n padding of its own buttons at (0,2,0) from another sheet would tie with it, and the\n bundler would decide: such a rule has to exclude `[data-icon-only]`. */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The tone table lives in styles/tones.css (`v-tone`, in the vectis.tokens layer) and the\n four ways of painting it in styles/variants.css (`v-variant`, in vectis.components).\n VButton overrides not a single one of their values: it is the reference they were\n written for, and what stays here is the STATES, which each component answers for\n itself. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /* The `:not([data-elevated])` is what makes these rules DISJOINT from the\n elevation rules below, rather than merely less specific than them. Nothing then\n arbitrates the background of a raised ghost by a one-step specificity margin,\n so a declaration added to either set later cannot silently leak into the other\n case. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /* Elevation is ORTHOGONAL to the variant: the shadow scale applies to all four of\n them, while the raised surface is only painted on the two that have no\n background of their own. The `:is()` here is not decoration — it is what gives\n these rules a specificity of (0,3,0), beating the (0,2,0) of the ghost and\n outline bases without depending on the order the rules end up in. */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /* The shadow needs a rule of its OWN. No variant declares a shadow, so none of the\n three rules above cancels one, and a disabled raised ghost would otherwise go on\n casting its shadow-sm. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /* A box exactly the size of an icon, since the spinner takes the place of\n iconStart: a button with a start icon keeps its width when it starts loading\n (one with a label alone gains the spinner and the gap beside it). The\n font-size is set to that same size so that VSpinner's own box, which measures\n 1em, coincides with this one — VSpinner then draws its ring inside it at the\n proportion Material Symbols gives `progress_activity`, which is what makes the\n spinner and the icon it replaces read at the same size. */\n font-size: var(--vectis-icon-size);\n /* 1em of the line above rather than the variable again: a relative icon size would\n otherwise be resolved twice, once against the button's text and once against this\n box's own enlarged font-size. */\n inline-size: 1em;\n block-size: 1em;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsHA,IAAM,IAAQ,GAiCR,IAAQ,EAAS,GACjB,IAAQ,EAAO,GAAgB,IAAI,GAKnC,EACJ,MAAM,GACN,SAAS,GACT,UAAU,MACR,EAA4B,GAAO,CAAK,GACtC,IAAkB,QAA8B,GAAO,WAAW,EAAM,OAAO,GAC/E,IAAmB,QAAe,GAAO,YAAY,EAAM,QAAQ,GAGnE,IAAe,QAA2B,EAAM,QAAQ,GAAO,QAAQ,QAAQ,GAE/E,IAAU,QAAe,EAAiB,SAAS,EAAM,OAAO,GAChE,EACJ,WACA,gBACA,aACA,OAAO,MACL,EAAa;GACf,YAAY,EAAM;GAClB,aAAa,EAAQ;GACrB,aAAa;EACf,CAAC;yBAIC,EA8BY,EA7BL,EAAA,CAAA,IAAM,MAAA,QAAA,GADb,EA8BY;GA5BT,iBAAe,EAAA,CAAA,IAAW,SAAY,KAAA;GACtC,aAAW,EAAA,WAAW,KAAA;KACf,EAAA,CAAA,GAAW;GACnB,OAAM;GACL,MAAM,EAAA,CAAA;GACN,MAAM,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC3B,UAAU,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC/B,gBAAc,EAAA;GACd,aAAW,EAAA;GACX,iBAAe,EAAA,QAAgB,KAAQ,KAAA;GACvC,aAAW,EAAA,CAAA;GACX,gBAAc,EAAA,CAAA,IAAe,KAAQ,KAAA;GACrC,mBAAiB,EAAA,YAAS,KAAQ,KAAA;GAClC,gBAAc,EAAA,UAAO,KAAQ,KAAA;;oBAOvB;IAFK,EAAA,WAAA,EAAA,GAAZ,EAEO,QAFP,GAEO,CADL,EAAY,CAAA,CAAA,CAAA,KAEd,EAEO,EAAA,QAAA,SAAA,CAAA,SAAA,CADQ,EAAA,aAAA,EAAA,GAAb,EAA6E,GAA7E,EAA6E,EAAA,KAAA,EAAA,GAA7C,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;IAE3E,EAAQ,EAAA,QAAA,SAAA;IACR,EAEO,EAAA,QAAA,OAAA,CAAA,SAAA,CADQ,EAAA,WAAA,EAAA,GAAb,EAAyE,GAAzE,EAAyE,EAAA,KAAA,EAAA,GAA3C,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VButtonGroup.js","names":[],"sources":["../../../src/components/VButton/VButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Joins several VButtons or VIconButtons into one segmented control: the borders\n * they share are merged into a single line, and only the two ends of the group keep\n * their rounded corners, so the row reads as one object rather than as buttons\n * placed side by side.\n *\n * How that object is drawn is settled here, in three decisions. `detached` leaves the\n * buttons apart, with a gap and their own corners, instead of joining them at all.\n * `seamless` keeps them joined but takes the lines out from between them, so the row\n * becomes a single frame. Everything else is the joined default.\n *\n * `fullWidth` is the row's width rather than its drawing: the group fills its parent, and\n * the segments divide that width into equal shares instead of each measuring its own\n * label, which is what a segmented control standing on a line of its own is asked for.\n *\n * Since the row is one object, how its segments are drawn is its decision too: the group\n * hands its variant, size, compact and elevated down to every button inside, and each of\n * them wins over what the button was given. The tone travels the other way, as a\n * fallback a button can refuse, so that one segment in the row can be the destructive\n * one, and `disabled` is cumulative rather than either. The rules and their reasons are\n * written out in `context.ts`.\n *\n * Naming none of them leaves every child exactly as it would have rendered on its\n * own: the context then holds `undefined` throughout, and `undefined` is what hands a\n * prop back to its owner.\n */\nimport { provide, ref } from 'vue'\n\nimport type { ButtonSize, ButtonTone, ButtonVariant } from './VButton.vue'\nimport { buttonGroupKey } from './context'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\n\n/** Which way the row runs. */\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical'\n\ninterface ButtonGroupProps {\n /**\n * The direction the buttons are joined in: a row by default, or a column under\n * `vertical`.\n */\n orientation?: ButtonGroupOrientation\n /**\n * Leaves the buttons as separate ones, with a gap between them and each keeping its\n * own corners, instead of joining them into a segmented control.\n */\n detached?: boolean\n /**\n * Takes the lines out from between the joined buttons, so the row reads as one frame\n * rather than as segments. It has no effect under `detached`, where the buttons are\n * apart already and there is no shared edge to draw on.\n */\n seamless?: boolean\n /**\n * Stretches the row across the whole inline size of its parent, every segment taking\n * an equal share of that width. A segment never shrinks below its own label, so a row\n * of labels too long for the parent overflows it rather than being crushed. Under\n * `vertical` it is the width alone: the segments already stretch across the column.\n */\n fullWidth?: boolean\n /**\n * How much visual weight every segment carries, on the values of VButton's own\n * `variant`: `solid`, `outline`, `ghost` or `soft`. It wins over the variant a\n * button inside was given.\n */\n variant?: ButtonVariant\n /**\n * The colour the segments take, among `accent`, `neutral` and `danger`. This one is\n * a fallback rather than an order: a button that names a tone of its own keeps it,\n * which is what lets a single destructive action stand out in the row.\n */\n tone?: ButtonTone\n /**\n * The height of the segments, from the size scale shared by every control:\n * `xs`, `sm`, `md`, `lg` or `xl`. It wins over the size a button inside was given.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height of every segment. It wins over the value a button inside\n * was given.\n */\n compact?: boolean\n /**\n * Raises every segment off the page, on the terms of VButton's own `elevated`. It\n * wins over the value a button inside was given.\n */\n elevated?: boolean\n /**\n * Makes every segment unusable. A button that disables itself stays disabled either\n * way: the two answers add up rather than one overruling the other.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row, which is a `role=\"group\"`: \"Text formatting\",\n * \"View\". A group with no name is announced as a bare group, so give one whenever the\n * buttons do not say on their own what they are for. A consumer `aria-label` or\n * `aria-labelledby` wins over it.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ButtonGroupProps>(), {\n // `detached`, `seamless` and `fullWidth` are the group's own layout, and take a real\n // default like the orientation does. The six that follow travel down to the buttons,\n // and are `undefined` by default, the booleans included: it is `undefined`, and not\n // `false`, that means the group has no opinion and lets the button keep its own.\n orientation: 'horizontal',\n detached: false,\n seamless: false,\n fullWidth: false,\n variant: undefined,\n tone: undefined,\n size: undefined,\n compact: undefined,\n elevated: undefined,\n disabled: undefined,\n label: undefined,\n})\n\nconst ariaLabel = useAriaLabel(() => props.label)\n\ndefineSlots<{\n /** The VButtons and VIconButtons to join together. */\n default(): unknown\n}>()\n\n// Getters, so the group's props stay reactive on the other side of the injection.\nprovide(buttonGroupKey, {\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get elevated() {\n return props.elevated\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst groupEl = ref<HTMLElement | null>(null)\n\n// A template ref on this component is not a safe way to its element: the comment above the\n// root makes the template a fragment in development, where `$el` is a text anchor. VToggle,\n// whose root this group is, reads the element from here.\ndefineExpose({\n /** The `role=\"group\"` element. */\n el: groupEl,\n})\n</script>\n\n<template>\n <!--\n The size travels through the injection alone, and the root deliberately carries\n neither `v-control` nor `data-size`, where VAvatarGroup carries both: nothing in\n this sheet reads a `--control-*` variable, and `v-control` set a second time above\n a subtree that already has it re-derives `--control-height` from the base height,\n dropping the compact its buttons had applied (styles/control-size.css).\n\n `data-seamless` is withheld under `detached`, so the DOM never carries a claim the\n markup cannot honour: apart, the buttons share no edge for a line to sit on.\n -->\n <div\n ref=\"groupEl\"\n class=\"v-button-group\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-orientation=\"orientation\"\n :data-detached=\"detached ? '' : undefined\"\n :data-seamless=\"seamless && !detached ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A SEGMENT is a direct child of the group, and it is not always the button. A\n * companion puts a wrapper between the two: VTooltip renders `.v-tooltip` around its\n * trigger, VPopover renders `.v-popover` around the `#trigger` slot, VBadge renders\n * `.v-badge-host` around the element it pins its pill to, and a consumer may add one of\n * their own. Written against the button alone, every rule in this sheet would step\n * aside for a segment carrying any of the three, and the row would quietly come apart\n * into buttons standing side by side.\n *\n * So each of them is written for BOTH shapes:\n *\n * > .v-button<tests> the button IS the segment\n * > WRAPPER<tests> BUTTON the button is somewhere inside it\n *\n * with WRAPPER = `:not(:where(.v-overlay, .v-button-group))` and BUTTON =\n * `.v-button:not(:where(.v-overlay *))`.\n *\n * Where the declarations land is decided by what they do. The PULL, the negative margin\n * that collapses two borders into one line, belongs to the segment: that is the box the\n * group lays out. The PAINT, the corners and the seam and the borders and the shadow,\n * belongs to the button, which carries them. Swapped, a wrapped segment would either be\n * pulled twice or paint nothing. A full-width row needs neither: its shares are tracks,\n * declared on the group itself and blind to what stands in them.\n *\n * The tests weigh the same on both branches (`:not(:first-child)` and `:has(~ …)` are\n * (0,1,0) each, and `:where()` weighs nothing), so a pair is always ONE specificity:\n * every figure quoted below holds for its wrapped half as well.\n *\n * The depth is free, which is what lets companions STACK: a badge inside a tooltip\n * inside a menu is three of them on one button, and each of the two guards in those\n * definitions is what makes a descendant combinator safe there.\n *\n * TRAP — `.v-overlay` appears in both guards, and neither is optional. A panel must not\n * pass for a segment: VMenu renders its panel as a SIBLING of its trigger, so a group\n * holding a menu has a child that is no segment at all. And a panel's CONTENT must not\n * be painted: VTooltip and VPopover render theirs INSIDE the wrapper, so a button\n * standing in one would otherwise take a segment's corners and seam. Every panel in the\n * design system carries that class, which is what makes one test cover both.\n *\n * TRAP — `.v-button-group` in the wrapper guard keeps a NESTED group out. A VToggle\n * inside a VButtonGroup is a row of its own, drawn by its own sheet; reaching through\n * it would flatten the corners of buttons the outer row never laid out and lay a second\n * seam over its own.\n */\n .v-button-group {\n display: inline-flex;\n /* Gives every segment the same height in a row, and the same width in a column,\n whatever each button's own content measures. */\n align-items: stretch;\n /* Declared on every group although only a raised one ever moves its shadow: that is\n what lets the reduced-motion block cancel it with a bare selector rather than\n repeating the elevation `:has()` below. */\n transition: box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-button-group[data-orientation='vertical'] {\n flex-direction: column;\n }\n\n /* An explicit display alongside the inline size, VButton's own full-width argument: an\n inline-level box sits on a line box, and the strut's descender would show as a few\n pixels of dead space under a row asked to fill its parent. */\n .v-button-group[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* Equal shares, and they are TRACKS rather than flex bases. A row that reads as one\n object cannot be cut into unequal pieces by whichever words happen to be in it, which\n rules out distributing the free space alone; and `flex: 1 1 0` cannot do it either,\n because flex shares out CONTENT boxes: a segment ends up as wide as its share PLUS\n the padding and border it carries, the same for three buttons but one button's worth\n of padding narrower for a segment wrapped in a tooltip, a popover or a badge, none of\n which pad anything. Measured at 34px on an `md` row. A `1fr` track is the share\n itself, whatever happens to stand in it.\n\n `1fr` is `minmax(auto, 1fr)`, so the automatic minimum is kept: a segment never\n shrinks below its label, and a row of labels too long for its parent overflows rather\n than crushing text nothing here could then truncate, a VButton having no label box to\n put an ellipsis on. A menu's panel takes no track either way — closed it is\n `display: none`, open it is fixed, and neither is a grid item.\n\n Horizontal only. In a column the width is the whole of it, the base `align-items:\n stretch` already giving every segment the full inline size. */\n .v-button-group[data-full-width][data-orientation='horizontal'] {\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: 1fr;\n }\n\n /* A wrapped button fills the segment it sits in, wherever the group STRETCHES that\n segment: down a column, where `align-items: stretch` gives every segment the full\n inline size, and across a full-width row, where each is given an equal track. The\n button would otherwise stay at the width of its own label and leave the rest of the\n segment blank.\n\n The button AND everything between it and the segment, since companions stack: a badge\n inside a tooltip has to be stretched itself, or the button fills a box that is still\n the width of its own label. Panels are left out of the chain, one of them holding a\n whole floating box that has no business being stretched to a segment.\n\n TRAP — the two selectors are what keeps this out of a plain horizontal row, and that\n is not an optimization. Nothing is stretched there: each segment is as wide as its own\n content, so a percentage on the button would resolve against a wrapper whose width\n depends on that very button, which is circular, and a wrapped segment would stop\n measuring its label. */\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)),\n .v-button-group[data-full-width][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)) {\n inline-size: 100%;\n }\n\n /* Detached, the buttons are simply spaced: the base rule already gives the flex box\n and the vertical direction, so a gap and the cross-axis alignment are the whole\n difference. The vertical rule restores the stretching the base declares, which the\n centring above beats by one attribute. */\n .v-button-group[data-detached] {\n align-items: center;\n gap: var(--vectis-space-1);\n }\n\n .v-button-group[data-detached][data-orientation='vertical'] {\n align-items: stretch;\n }\n\n /* The negative margin pulls each segment onto its neighbour so their two 1px borders\n collapse into one line, over which the seam below is then laid: it draws the\n separation the filled variants would otherwise lack (their border is transparent)\n and unifies the joint between two outlined ones.\n\n Each block is scoped to one orientation on purpose. Left unscoped, the\n horizontal rules would also flatten the side corners and borders in a vertical\n group, where those are precisely the edges that must stay round. */\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-inline-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n\n /* TRAP — the last segment is read as `:has(~ :not(.v-overlay))` and no longer as\n `:not(:last-child)`, and that is VMenu's doing: it renders its panel as a SIBLING of\n its trigger, so a menu closing a row would leave its trigger short of `:last-child`,\n and the row would lose its outer corner there with nothing in the DOM to say why.\n Asking whether a real SEGMENT follows, rather than whether anything does, makes the\n panel invisible to the count and weighs exactly what the test it replaces did.\n\n The first segment needs none of that: a panel is rendered after the trigger it\n belongs to, so it can never take `:first-child` from a segment. */\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-block-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-start-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n\n /* The seam is a box of its own, and not the neighbour's own border, because CSS joins\n two adjacent borders at a MITRE: where a 1px border meets the transparent top and\n bottom ones of a solid segment, its last pixel is cut on the diagonal and the bar\n ends in a notch at each end. A box carrying that border on ONE side has no second\n border to join, so its ends stay square.\n\n A BORDER and not a background, the VSeparator argument: Windows forced-colors\n forces a background to Canvas, where the separation would vanish, and a border\n colour to CanvasText, where it survives.\n\n Absolute, so the insets resolve against the PADDING box: -1px is exactly the width\n of the border, which lays the bar over the border area and gives it the full length\n of the border box. Every segment is positioned so that they all paint in the same\n phase, in document order, the way they did as plain flex items. */\n .v-button-group > .v-button,\n .v-button-group > :not(:where(.v-overlay, .v-button-group)) .v-button:not(:where(.v-overlay *)) {\n position: relative;\n }\n\n /* Only the generating rule is guarded. The two orientation rules below place the insets\n of a pseudo-element that, apart or seamless, is never generated at all. */\n .v-button-group:not([data-detached]):not([data-seamless]) > .v-button:not(:first-child)::before,\n .v-button-group:not([data-detached]):not([data-seamless])\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n content: '';\n position: absolute;\n }\n\n .v-button-group[data-orientation='horizontal'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-block: -1px;\n inset-inline-start: -1px;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n .v-button-group[data-orientation='vertical'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-inline: -1px;\n inset-block-start: -1px;\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n /* Seamless, the row is ONE object: an outer frame with nothing drawn inside it. The\n seam above is not generated, and what is left to clear is the segments' own borders\n on the edges they share.\n\n BOTH sides of every shared edge, never just one: the segments overlap by 1px, so a\n neighbour's border sits in the very same pixel column. Clearing one side alone leaves\n the other showing through wherever the segment painted on top has no background of\n its own, which is precisely the `ghost` and `outline` case.\n\n The COLOUR goes transparent rather than the border going away: every box keeps its\n 1px on all four sides, so nothing in the row changes width and the corners stay where\n they were rounded.\n\n TRAP — the class is doubled to reach (0,6,0), and that is not decoration. Written\n once these are (0,5,0), which TIES with the compound a component overrides a\n segment's border through (VToggle's outline frame, `.v-toggle[data-variant='outline']\n > .v-toggle-item[aria-pressed='true']:is(…)`), and a tie between two sheets is\n settled by whichever the consumer's bundler put last. An internal edge is never part\n of a frame, so the group has to win by construction. VToggle's rules are written for\n the wrapped shape too, and take the same class there, so the gap between the two\n holds whether an item is wrapped or not. */\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-inline-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-inline-end-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-block-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-block-end-color: transparent;\n }\n\n /* The elevation belongs to the ROW, not to each segment. Left to the buttons, every\n one of them casts a shadow onto the neighbour it overlaps, and the joints fill with\n a dark band instead of the whole reading as one object lifted off the page. So the\n group takes the shadow and the segments give theirs up, which also means the row\n rises as a whole when any of it is hovered.\n\n `:has()` rather than a data attribute on the root: the elevation reaches the\n buttons either from the group or one by one, and only the buttons know in the end.\n Its argument is a list, so a wrapped segment answers for itself, and since a\n selector list takes the specificity of its heaviest member, the two branches leave\n the weight where it was.\n\n The raised BACKGROUND stays with each button. It is what a ghost or an outline\n segment is painted on, and in the dark theme it is what the shadow needs in order\n to have something casting it.\n\n Detached, none of this applies and every rule steps aside: the buttons are apart, so\n there is no joint to fill and each of them casting its own shadow is right. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated],\n > :not(:where(.v-overlay, .v-button-group)) .v-button[data-elevated]:not(:where(.v-overlay *))\n ) {\n border-radius: var(--vectis-radius-interactive);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n /* After the hover rule and at equal specificity, the VButton order: pressing a\n raised segment settles the row back down. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n /* The class is doubled to reach (0,6,0). VButton's own hover and active shadows are\n (0,4,0) on this very element, so at equal specificity the winner would be whichever\n of the two sheets the consumer's bundler happened to put last. */\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated],\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:hover,\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:active,\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:where(.v-overlay *)) {\n box-shadow: none;\n }\n\n /* A FOCUSED segment rises above its neighbours, and it is the ONLY state that does.\n The ring is drawn outside the border box, so the next segment, painted later, would\n cut through it along the seam: removing this rule takes the keyboard focus ring away\n on one side of every segment but the last.\n\n Hover and active are deliberately not here, and putting them back is a visible bug:\n segments overlap by one pixel, and that pixel is the neighbour's own border, which\n is to say the seam. A raised segment paints its background over it, so the\n separation disappears on the far side of whichever segment the pointer is on, for\n the whole time it is there. A focused one covers it too, but under a ring that is\n drawing that edge itself.\n\n Wrapped, the `z-index` stays on the BUTTON and still counts among the segments: none\n of the three wrappers opens a stacking context, two being unpositioned and\n `.v-badge-host`, which is `position: relative`, keeping `z-index: auto`. Were one of\n them to take a `z-index`, this rule would raise the button inside that wrapper's own\n context and nothing on the page. */\n .v-button-group > .v-button:focus-visible,\n .v-button-group\n > :not(:where(.v-overlay, .v-button-group))\n .v-button:focus-visible:not(:where(.v-overlay *)) {\n z-index: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button-group {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VButtonGroup.js","names":[],"sources":["../../../src/components/VButton/VButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Joins several VButtons or VIconButtons into one segmented control: the borders\n * they share are merged into a single line, and only the two ends of the group keep\n * their rounded corners, so the row reads as one object rather than as buttons\n * placed side by side.\n *\n * How that object is drawn is settled here, in three decisions. `detached` leaves the\n * buttons apart, with a gap and their own corners, instead of joining them at all.\n * `seamless` keeps them joined but takes the lines out from between them, so the row\n * becomes a single frame. Everything else is the joined default.\n *\n * `fullWidth` is the row's width rather than its drawing: the group fills its parent, and\n * the segments divide that width into equal shares instead of each measuring its own\n * label, which is what a segmented control standing on a line of its own is asked for.\n *\n * Since the row is one object, how its segments are drawn is its decision too: the group\n * hands its variant, size, compact and elevated down to every button inside, and each of\n * them wins over what the button was given. The tone travels the other way, as a\n * fallback a button can refuse, so that one segment in the row can be the destructive\n * one, and `disabled` is cumulative rather than either. The rules and their reasons are\n * written out in `context.ts`.\n *\n * Naming none of them leaves every child exactly as it would have rendered on its\n * own: the context then holds `undefined` throughout, and `undefined` is what hands a\n * prop back to its owner.\n */\nimport { provide, ref } from 'vue'\n\nimport type { ButtonSize, ButtonTone, ButtonVariant } from './VButton.vue'\nimport { buttonGroupKey } from './context'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\n\n/** Which way the row runs. */\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical'\n\ninterface ButtonGroupProps {\n /**\n * The direction the buttons are joined in: a row by default, or a column under\n * `vertical`.\n */\n orientation?: ButtonGroupOrientation\n /**\n * Leaves the buttons as separate ones, with a gap between them and each keeping its\n * own corners, instead of joining them into a segmented control.\n */\n detached?: boolean\n /**\n * Takes the lines out from between the joined buttons, so the row reads as one frame\n * rather than as segments. It has no effect under `detached`, where the buttons are\n * apart already and there is no shared edge to draw on.\n */\n seamless?: boolean\n /**\n * Stretches the row across the whole inline size of its parent, every segment taking\n * an equal share of that width. A segment never shrinks below its own label, so a row\n * of labels too long for the parent overflows it rather than being crushed. Under\n * `vertical` it is the width alone: the segments already stretch across the column.\n */\n fullWidth?: boolean\n /**\n * How much visual weight every segment carries, on the values of VButton's own\n * `variant`: `solid`, `outline`, `ghost` or `soft`. It wins over the variant a\n * button inside was given.\n */\n variant?: ButtonVariant\n /**\n * The colour the segments take, among `accent`, `neutral` and `danger`. This one is\n * a fallback rather than an order: a button that names a tone of its own keeps it,\n * which is what lets a single destructive action stand out in the row.\n */\n tone?: ButtonTone\n /**\n * The height of the segments, from the size scale shared by every control:\n * `xs`, `sm`, `md`, `lg` or `xl`. It wins over the size a button inside was given.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height of every segment. It wins over the value a button inside\n * was given.\n */\n compact?: boolean\n /**\n * Raises every segment off the page, on the terms of VButton's own `elevated`. It\n * wins over the value a button inside was given.\n */\n elevated?: boolean\n /**\n * Makes every segment unusable. A button that disables itself stays disabled either\n * way: the two answers add up rather than one overruling the other.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row, which is a `role=\"group\"`: \"Text formatting\",\n * \"View\". A group with no name is announced as a bare group, so give one whenever the\n * buttons do not say on their own what they are for. A consumer `aria-label` or\n * `aria-labelledby` wins over it.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ButtonGroupProps>(), {\n // `detached`, `seamless` and `fullWidth` are the group's own layout, and take a real\n // default like the orientation does. The six that follow travel down to the buttons,\n // and are `undefined` by default, the booleans included: it is `undefined`, and not\n // `false`, that means the group has no opinion and lets the button keep its own.\n orientation: 'horizontal',\n detached: false,\n seamless: false,\n fullWidth: false,\n variant: undefined,\n tone: undefined,\n size: undefined,\n compact: undefined,\n elevated: undefined,\n disabled: undefined,\n label: undefined,\n})\n\nconst ariaLabel = useAriaLabel(() => props.label)\n\ndefineSlots<{\n /** The VButtons and VIconButtons to join together. */\n default(): unknown\n}>()\n\n// Getters, so the group's props stay reactive on the other side of the injection.\nprovide(buttonGroupKey, {\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get elevated() {\n return props.elevated\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst groupEl = ref<HTMLElement | null>(null)\n\n// A template ref on this component is not a safe way to its element: the comment above the\n// root makes the template a fragment in development, where `$el` is a text anchor. VToggle,\n// whose root this group is, reads the element from here.\ndefineExpose({\n /** The `role=\"group\"` element. */\n el: groupEl,\n})\n</script>\n\n<template>\n <!--\n The size travels through the injection alone, and the root deliberately carries\n neither `v-control` nor `data-size`, where VAvatarGroup carries both: nothing in\n this sheet reads a `--control-*` variable, and `v-control` set a second time above\n a subtree that already has it re-derives `--control-height` from the base height,\n dropping the compact its buttons had applied (styles/control-size.css).\n\n `data-seamless` is withheld under `detached`, so the DOM never carries a claim the\n markup cannot honour: apart, the buttons share no edge for a line to sit on.\n -->\n <div\n ref=\"groupEl\"\n class=\"v-button-group\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-orientation=\"orientation\"\n :data-detached=\"detached ? '' : undefined\"\n :data-seamless=\"seamless && !detached ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A SEGMENT is a direct child of the group, and it is not always the button. A\n * companion puts a wrapper between the two: VTooltip renders `.v-tooltip` around its\n * trigger, VPopover renders `.v-popover` around the `#trigger` slot, VBadge renders\n * `.v-badge-host` around the element it pins its pill to, and a consumer may add one of\n * their own. Written against the button alone, every rule in this sheet would step\n * aside for a segment carrying any of the three, and the row would quietly come apart\n * into buttons standing side by side.\n *\n * So each of them is written for BOTH shapes:\n *\n * > .v-button<tests> the button IS the segment\n * > WRAPPER<tests> BUTTON the button is somewhere inside it\n *\n * with WRAPPER = `:not(:where(.v-overlay, .v-button-group))` and BUTTON =\n * `.v-button:not(:where(.v-overlay *))`.\n *\n * Where the declarations land is decided by what they do. The PULL, the negative margin\n * that collapses two borders into one line, belongs to the segment: that is the box the\n * group lays out. The PAINT, the corners and the seam and the borders and the shadow,\n * belongs to the button, which carries them. Swapped, a wrapped segment would either be\n * pulled twice or paint nothing. A full-width row needs neither: its shares are tracks,\n * declared on the group itself and blind to what stands in them.\n *\n * The tests weigh the same on both branches (`:not(:first-child)` and `:has(~ …)` are\n * (0,1,0) each, and `:where()` weighs nothing), so a pair is always ONE specificity:\n * every figure quoted below holds for its wrapped half as well.\n *\n * The depth is free, which is what lets companions STACK: a badge inside a tooltip\n * inside a menu is three of them on one button, and each of the two guards in those\n * definitions is what makes a descendant combinator safe there.\n *\n * TRAP — `.v-overlay` appears in both guards, and neither is optional. A panel must not\n * pass for a segment: VMenu renders its panel as a SIBLING of its trigger, so a group\n * holding a menu has a child that is no segment at all. And a panel's CONTENT must not\n * be painted: VTooltip and VPopover render theirs INSIDE the wrapper, so a button\n * standing in one would otherwise take a segment's corners and seam. Every panel in the\n * design system carries that class, which is what makes one test cover both.\n *\n * TRAP — `.v-button-group` in the wrapper guard keeps a NESTED group out. A VToggle\n * inside a VButtonGroup is a row of its own, drawn by its own sheet; reaching through\n * it would flatten the corners of buttons the outer row never laid out and lay a second\n * seam over its own.\n */\n .v-button-group {\n display: inline-flex;\n /* Gives every segment the same height in a row, and the same width in a column,\n whatever each button's own content measures. */\n align-items: stretch;\n /* Declared on every group although only a raised one ever moves its shadow: that is\n what lets the reduced-motion block cancel it with a bare selector rather than\n repeating the elevation `:has()` below. */\n transition: box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-button-group[data-orientation='vertical'] {\n flex-direction: column;\n }\n\n /* An explicit display alongside the inline size, VButton's own full-width argument: an\n inline-level box sits on a line box, and the strut's descender would show as a few\n pixels of dead space under a row asked to fill its parent. */\n .v-button-group[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* Equal shares, and they are TRACKS rather than flex bases. A row that reads as one\n object cannot be cut into unequal pieces by whichever words happen to be in it, which\n rules out distributing the free space alone; and `flex: 1 1 0` cannot do it either,\n because flex shares out CONTENT boxes: a segment ends up as wide as its share PLUS\n the padding and border it carries, the same for three buttons but one button's worth\n of padding narrower for a segment wrapped in a tooltip, a popover or a badge, none of\n which pad anything. Measured at 34px on an `md` row. A `1fr` track is the share\n itself, whatever happens to stand in it.\n\n `1fr` is `minmax(auto, 1fr)`, so the automatic minimum is kept: a segment never\n shrinks below its label, and a row of labels too long for its parent overflows rather\n than crushing text nothing here could then truncate, a VButton having no label box to\n put an ellipsis on. A menu's panel takes no track either way — closed it is\n `display: none`, open it is fixed, and neither is a grid item.\n\n Horizontal only. In a column the width is the whole of it, the base `align-items:\n stretch` already giving every segment the full inline size. */\n .v-button-group[data-full-width][data-orientation='horizontal'] {\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: 1fr;\n }\n\n /* A wrapped button fills the segment it sits in, wherever the group STRETCHES that\n segment: down a column, where `align-items: stretch` gives every segment the full\n inline size, and across a full-width row, where each is given an equal track. The\n button would otherwise stay at the width of its own label and leave the rest of the\n segment blank.\n\n The button AND everything between it and the segment, since companions stack: a badge\n inside a tooltip has to be stretched itself, or the button fills a box that is still\n the width of its own label. Panels are left out of the chain, one of them holding a\n whole floating box that has no business being stretched to a segment.\n\n TRAP — the two selectors are what keeps this out of a plain horizontal row, and that\n is not an optimization. Nothing is stretched there: each segment is as wide as its own\n content, so a percentage on the button would resolve against a wrapper whose width\n depends on that very button, which is circular, and a wrapped segment would stop\n measuring its label. */\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)),\n .v-button-group[data-full-width][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)) {\n inline-size: 100%;\n }\n\n /* Detached, the buttons are simply spaced: the base rule already gives the flex box\n and the vertical direction, so a gap and the cross-axis alignment are the whole\n difference. The vertical rule restores the stretching the base declares, which the\n centring above beats by one attribute. */\n .v-button-group[data-detached] {\n align-items: center;\n gap: var(--vectis-space-1);\n }\n\n .v-button-group[data-detached][data-orientation='vertical'] {\n align-items: stretch;\n }\n\n /* The negative margin pulls each segment onto its neighbour so their two 1px borders\n collapse into one line, over which the seam below is then laid: it draws the\n separation the filled variants would otherwise lack (their border is transparent)\n and unifies the joint between two outlined ones.\n\n Each block is scoped to one orientation on purpose. Left unscoped, the\n horizontal rules would also flatten the side corners and borders in a vertical\n group, where those are precisely the edges that must stay round. */\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-inline-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n\n /* TRAP — the last segment is read as `:has(~ :not(.v-overlay))` and no longer as\n `:not(:last-child)`, and that is VMenu's doing: it renders its panel as a SIBLING of\n its trigger, so a menu closing a row would leave its trigger short of `:last-child`,\n and the row would lose its outer corner there with nothing in the DOM to say why.\n Asking whether a real SEGMENT follows, rather than whether anything does, makes the\n panel invisible to the count and weighs exactly what the test it replaces did.\n\n The first segment needs none of that: a panel is rendered after the trigger it\n belongs to, so it can never take `:first-child` from a segment. */\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-block-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-start-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n\n /* The seam is a box of its own, and not the neighbour's own border, because CSS joins\n two adjacent borders at a MITRE: where a 1px border meets the transparent top and\n bottom ones of a solid segment, its last pixel is cut on the diagonal and the bar\n ends in a notch at each end. A box carrying that border on ONE side has no second\n border to join, so its ends stay square.\n\n A BORDER and not a background, the VSeparator argument: Windows forced-colors\n forces a background to Canvas, where the separation would vanish, and a border\n colour to CanvasText, where it survives.\n\n Absolute, so the insets resolve against the PADDING box: -1px is exactly the width\n of the border, which lays the bar over the border area and gives it the full length\n of the border box. Every segment is positioned so that they all paint in the same\n phase, in document order, the way they did as plain flex items. */\n .v-button-group > .v-button,\n .v-button-group > :not(:where(.v-overlay, .v-button-group)) .v-button:not(:where(.v-overlay *)) {\n position: relative;\n }\n\n /* Only the generating rule is guarded. The two orientation rules below place the insets\n of a pseudo-element that, apart or seamless, is never generated at all. */\n .v-button-group:not([data-detached]):not([data-seamless]) > .v-button:not(:first-child)::before,\n .v-button-group:not([data-detached]):not([data-seamless])\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n content: '';\n position: absolute;\n }\n\n .v-button-group[data-orientation='horizontal'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-block: -1px;\n inset-inline-start: -1px;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n .v-button-group[data-orientation='vertical'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-inline: -1px;\n inset-block-start: -1px;\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n /* Seamless, the row is ONE object: an outer frame with nothing drawn inside it. The\n seam above is not generated, and what is left to clear is the segments' own borders\n on the edges they share.\n\n BOTH sides of every shared edge, never just one: the segments overlap by 1px, so a\n neighbour's border sits in the very same pixel column. Clearing one side alone leaves\n the other showing through wherever the segment painted on top has no background of\n its own, which is precisely the `ghost` and `outline` case.\n\n The COLOUR goes transparent rather than the border going away: every box keeps its\n 1px on all four sides, so nothing in the row changes width and the corners stay where\n they were rounded.\n\n TRAP — the class is doubled to reach (0,6,0), and that is not decoration. Written\n once these are (0,5,0), which TIES with the compound a component overrides a\n segment's border through (VToggle's outline frame, `.v-toggle[data-item-variant='outline']\n > .v-toggle-item[aria-pressed='true']:is(…)`), and a tie between two sheets is\n settled by whichever the consumer's bundler put last. An internal edge is never part\n of a frame, so the group has to win by construction. VToggle's rules are written for\n the wrapped shape too, and take the same class there, so the gap between the two\n holds whether an item is wrapped or not. */\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-inline-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-inline-end-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-block-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-block-end-color: transparent;\n }\n\n /* The elevation belongs to the ROW, not to each segment. Left to the buttons, every\n one of them casts a shadow onto the neighbour it overlaps, and the joints fill with\n a dark band instead of the whole reading as one object lifted off the page. So the\n group takes the shadow and the segments give theirs up, which also means the row\n rises as a whole when any of it is hovered.\n\n `:has()` rather than a data attribute on the root: the elevation reaches the\n buttons either from the group or one by one, and only the buttons know in the end.\n Its argument is a list, so a wrapped segment answers for itself, and since a\n selector list takes the specificity of its heaviest member, the two branches leave\n the weight where it was.\n\n The raised BACKGROUND stays with each button. It is what a ghost or an outline\n segment is painted on, and in the dark theme it is what the shadow needs in order\n to have something casting it.\n\n Detached, none of this applies and every rule steps aside: the buttons are apart, so\n there is no joint to fill and each of them casting its own shadow is right. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated],\n > :not(:where(.v-overlay, .v-button-group)) .v-button[data-elevated]:not(:where(.v-overlay *))\n ) {\n border-radius: var(--vectis-radius-interactive);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n /* After the hover rule and at equal specificity, the VButton order: pressing a\n raised segment settles the row back down. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n /* The class is doubled to reach (0,6,0). VButton's own hover and active shadows are\n (0,4,0) on this very element, so at equal specificity the winner would be whichever\n of the two sheets the consumer's bundler happened to put last. */\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated],\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:hover,\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:active,\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:where(.v-overlay *)) {\n box-shadow: none;\n }\n\n /* A FOCUSED segment rises above its neighbours, and it is the ONLY state that does.\n The ring is drawn outside the border box, so the next segment, painted later, would\n cut through it along the seam: removing this rule takes the keyboard focus ring away\n on one side of every segment but the last.\n\n Hover and active are deliberately not here, and putting them back is a visible bug:\n segments overlap by one pixel, and that pixel is the neighbour's own border, which\n is to say the seam. A raised segment paints its background over it, so the\n separation disappears on the far side of whichever segment the pointer is on, for\n the whole time it is there. A focused one covers it too, but under a ring that is\n drawing that edge itself.\n\n Wrapped, the `z-index` stays on the BUTTON and still counts among the segments: none\n of the three wrappers opens a stacking context, two being unpositioned and\n `.v-badge-host`, which is `position: relative`, keeping `z-index: auto`. Were one of\n them to take a `z-index`, this rule would raise the button inside that wrapper's own\n context and nothing on the page. */\n .v-button-group > .v-button:focus-visible,\n .v-button-group\n > :not(:where(.v-overlay, .v-button-group))\n .v-button:focus-visible:not(:where(.v-overlay *)) {\n z-index: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button-group {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VButtonGroup.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VButton/VButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Joins several VButtons or VIconButtons into one segmented control: the borders\n * they share are merged into a single line, and only the two ends of the group keep\n * their rounded corners, so the row reads as one object rather than as buttons\n * placed side by side.\n *\n * How that object is drawn is settled here, in three decisions. `detached` leaves the\n * buttons apart, with a gap and their own corners, instead of joining them at all.\n * `seamless` keeps them joined but takes the lines out from between them, so the row\n * becomes a single frame. Everything else is the joined default.\n *\n * `fullWidth` is the row's width rather than its drawing: the group fills its parent, and\n * the segments divide that width into equal shares instead of each measuring its own\n * label, which is what a segmented control standing on a line of its own is asked for.\n *\n * Since the row is one object, how its segments are drawn is its decision too: the group\n * hands its variant, size, compact and elevated down to every button inside, and each of\n * them wins over what the button was given. The tone travels the other way, as a\n * fallback a button can refuse, so that one segment in the row can be the destructive\n * one, and `disabled` is cumulative rather than either. The rules and their reasons are\n * written out in `context.ts`.\n *\n * Naming none of them leaves every child exactly as it would have rendered on its\n * own: the context then holds `undefined` throughout, and `undefined` is what hands a\n * prop back to its owner.\n */\nimport { provide, ref } from 'vue'\n\nimport type { ButtonSize, ButtonTone, ButtonVariant } from './VButton.vue'\nimport { buttonGroupKey } from './context'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\n\n/** Which way the row runs. */\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical'\n\ninterface ButtonGroupProps {\n /**\n * The direction the buttons are joined in: a row by default, or a column under\n * `vertical`.\n */\n orientation?: ButtonGroupOrientation\n /**\n * Leaves the buttons as separate ones, with a gap between them and each keeping its\n * own corners, instead of joining them into a segmented control.\n */\n detached?: boolean\n /**\n * Takes the lines out from between the joined buttons, so the row reads as one frame\n * rather than as segments. It has no effect under `detached`, where the buttons are\n * apart already and there is no shared edge to draw on.\n */\n seamless?: boolean\n /**\n * Stretches the row across the whole inline size of its parent, every segment taking\n * an equal share of that width. A segment never shrinks below its own label, so a row\n * of labels too long for the parent overflows it rather than being crushed. Under\n * `vertical` it is the width alone: the segments already stretch across the column.\n */\n fullWidth?: boolean\n /**\n * How much visual weight every segment carries, on the values of VButton's own\n * `variant`: `solid`, `outline`, `ghost` or `soft`. It wins over the variant a\n * button inside was given.\n */\n variant?: ButtonVariant\n /**\n * The colour the segments take, among `accent`, `neutral` and `danger`. This one is\n * a fallback rather than an order: a button that names a tone of its own keeps it,\n * which is what lets a single destructive action stand out in the row.\n */\n tone?: ButtonTone\n /**\n * The height of the segments, from the size scale shared by every control:\n * `xs`, `sm`, `md`, `lg` or `xl`. It wins over the size a button inside was given.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height of every segment. It wins over the value a button inside\n * was given.\n */\n compact?: boolean\n /**\n * Raises every segment off the page, on the terms of VButton's own `elevated`. It\n * wins over the value a button inside was given.\n */\n elevated?: boolean\n /**\n * Makes every segment unusable. A button that disables itself stays disabled either\n * way: the two answers add up rather than one overruling the other.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row, which is a `role=\"group\"`: \"Text formatting\",\n * \"View\". A group with no name is announced as a bare group, so give one whenever the\n * buttons do not say on their own what they are for. A consumer `aria-label` or\n * `aria-labelledby` wins over it.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ButtonGroupProps>(), {\n // `detached`, `seamless` and `fullWidth` are the group's own layout, and take a real\n // default like the orientation does. The six that follow travel down to the buttons,\n // and are `undefined` by default, the booleans included: it is `undefined`, and not\n // `false`, that means the group has no opinion and lets the button keep its own.\n orientation: 'horizontal',\n detached: false,\n seamless: false,\n fullWidth: false,\n variant: undefined,\n tone: undefined,\n size: undefined,\n compact: undefined,\n elevated: undefined,\n disabled: undefined,\n label: undefined,\n})\n\nconst ariaLabel = useAriaLabel(() => props.label)\n\ndefineSlots<{\n /** The VButtons and VIconButtons to join together. */\n default(): unknown\n}>()\n\n// Getters, so the group's props stay reactive on the other side of the injection.\nprovide(buttonGroupKey, {\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get elevated() {\n return props.elevated\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst groupEl = ref<HTMLElement | null>(null)\n\n// A template ref on this component is not a safe way to its element: the comment above the\n// root makes the template a fragment in development, where `$el` is a text anchor. VToggle,\n// whose root this group is, reads the element from here.\ndefineExpose({\n /** The `role=\"group\"` element. */\n el: groupEl,\n})\n</script>\n\n<template>\n <!--\n The size travels through the injection alone, and the root deliberately carries\n neither `v-control` nor `data-size`, where VAvatarGroup carries both: nothing in\n this sheet reads a `--control-*` variable, and `v-control` set a second time above\n a subtree that already has it re-derives `--control-height` from the base height,\n dropping the compact its buttons had applied (styles/control-size.css).\n\n `data-seamless` is withheld under `detached`, so the DOM never carries a claim the\n markup cannot honour: apart, the buttons share no edge for a line to sit on.\n -->\n <div\n ref=\"groupEl\"\n class=\"v-button-group\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-orientation=\"orientation\"\n :data-detached=\"detached ? '' : undefined\"\n :data-seamless=\"seamless && !detached ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A SEGMENT is a direct child of the group, and it is not always the button. A\n * companion puts a wrapper between the two: VTooltip renders `.v-tooltip` around its\n * trigger, VPopover renders `.v-popover` around the `#trigger` slot, VBadge renders\n * `.v-badge-host` around the element it pins its pill to, and a consumer may add one of\n * their own. Written against the button alone, every rule in this sheet would step\n * aside for a segment carrying any of the three, and the row would quietly come apart\n * into buttons standing side by side.\n *\n * So each of them is written for BOTH shapes:\n *\n * > .v-button<tests> the button IS the segment\n * > WRAPPER<tests> BUTTON the button is somewhere inside it\n *\n * with WRAPPER = `:not(:where(.v-overlay, .v-button-group))` and BUTTON =\n * `.v-button:not(:where(.v-overlay *))`.\n *\n * Where the declarations land is decided by what they do. The PULL, the negative margin\n * that collapses two borders into one line, belongs to the segment: that is the box the\n * group lays out. The PAINT, the corners and the seam and the borders and the shadow,\n * belongs to the button, which carries them. Swapped, a wrapped segment would either be\n * pulled twice or paint nothing. A full-width row needs neither: its shares are tracks,\n * declared on the group itself and blind to what stands in them.\n *\n * The tests weigh the same on both branches (`:not(:first-child)` and `:has(~ …)` are\n * (0,1,0) each, and `:where()` weighs nothing), so a pair is always ONE specificity:\n * every figure quoted below holds for its wrapped half as well.\n *\n * The depth is free, which is what lets companions STACK: a badge inside a tooltip\n * inside a menu is three of them on one button, and each of the two guards in those\n * definitions is what makes a descendant combinator safe there.\n *\n * TRAP — `.v-overlay` appears in both guards, and neither is optional. A panel must not\n * pass for a segment: VMenu renders its panel as a SIBLING of its trigger, so a group\n * holding a menu has a child that is no segment at all. And a panel's CONTENT must not\n * be painted: VTooltip and VPopover render theirs INSIDE the wrapper, so a button\n * standing in one would otherwise take a segment's corners and seam. Every panel in the\n * design system carries that class, which is what makes one test cover both.\n *\n * TRAP — `.v-button-group` in the wrapper guard keeps a NESTED group out. A VToggle\n * inside a VButtonGroup is a row of its own, drawn by its own sheet; reaching through\n * it would flatten the corners of buttons the outer row never laid out and lay a second\n * seam over its own.\n */\n .v-button-group {\n display: inline-flex;\n /* Gives every segment the same height in a row, and the same width in a column,\n whatever each button's own content measures. */\n align-items: stretch;\n /* Declared on every group although only a raised one ever moves its shadow: that is\n what lets the reduced-motion block cancel it with a bare selector rather than\n repeating the elevation `:has()` below. */\n transition: box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-button-group[data-orientation='vertical'] {\n flex-direction: column;\n }\n\n /* An explicit display alongside the inline size, VButton's own full-width argument: an\n inline-level box sits on a line box, and the strut's descender would show as a few\n pixels of dead space under a row asked to fill its parent. */\n .v-button-group[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* Equal shares, and they are TRACKS rather than flex bases. A row that reads as one\n object cannot be cut into unequal pieces by whichever words happen to be in it, which\n rules out distributing the free space alone; and `flex: 1 1 0` cannot do it either,\n because flex shares out CONTENT boxes: a segment ends up as wide as its share PLUS\n the padding and border it carries, the same for three buttons but one button's worth\n of padding narrower for a segment wrapped in a tooltip, a popover or a badge, none of\n which pad anything. Measured at 34px on an `md` row. A `1fr` track is the share\n itself, whatever happens to stand in it.\n\n `1fr` is `minmax(auto, 1fr)`, so the automatic minimum is kept: a segment never\n shrinks below its label, and a row of labels too long for its parent overflows rather\n than crushing text nothing here could then truncate, a VButton having no label box to\n put an ellipsis on. A menu's panel takes no track either way — closed it is\n `display: none`, open it is fixed, and neither is a grid item.\n\n Horizontal only. In a column the width is the whole of it, the base `align-items:\n stretch` already giving every segment the full inline size. */\n .v-button-group[data-full-width][data-orientation='horizontal'] {\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: 1fr;\n }\n\n /* A wrapped button fills the segment it sits in, wherever the group STRETCHES that\n segment: down a column, where `align-items: stretch` gives every segment the full\n inline size, and across a full-width row, where each is given an equal track. The\n button would otherwise stay at the width of its own label and leave the rest of the\n segment blank.\n\n The button AND everything between it and the segment, since companions stack: a badge\n inside a tooltip has to be stretched itself, or the button fills a box that is still\n the width of its own label. Panels are left out of the chain, one of them holding a\n whole floating box that has no business being stretched to a segment.\n\n TRAP — the two selectors are what keeps this out of a plain horizontal row, and that\n is not an optimization. Nothing is stretched there: each segment is as wide as its own\n content, so a percentage on the button would resolve against a wrapper whose width\n depends on that very button, which is circular, and a wrapped segment would stop\n measuring its label. */\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)),\n .v-button-group[data-full-width][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)) {\n inline-size: 100%;\n }\n\n /* Detached, the buttons are simply spaced: the base rule already gives the flex box\n and the vertical direction, so a gap and the cross-axis alignment are the whole\n difference. The vertical rule restores the stretching the base declares, which the\n centring above beats by one attribute. */\n .v-button-group[data-detached] {\n align-items: center;\n gap: var(--vectis-space-1);\n }\n\n .v-button-group[data-detached][data-orientation='vertical'] {\n align-items: stretch;\n }\n\n /* The negative margin pulls each segment onto its neighbour so their two 1px borders\n collapse into one line, over which the seam below is then laid: it draws the\n separation the filled variants would otherwise lack (their border is transparent)\n and unifies the joint between two outlined ones.\n\n Each block is scoped to one orientation on purpose. Left unscoped, the\n horizontal rules would also flatten the side corners and borders in a vertical\n group, where those are precisely the edges that must stay round. */\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-inline-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n\n /* TRAP — the last segment is read as `:has(~ :not(.v-overlay))` and no longer as\n `:not(:last-child)`, and that is VMenu's doing: it renders its panel as a SIBLING of\n its trigger, so a menu closing a row would leave its trigger short of `:last-child`,\n and the row would lose its outer corner there with nothing in the DOM to say why.\n Asking whether a real SEGMENT follows, rather than whether anything does, makes the\n panel invisible to the count and weighs exactly what the test it replaces did.\n\n The first segment needs none of that: a panel is rendered after the trigger it\n belongs to, so it can never take `:first-child` from a segment. */\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-block-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-start-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n\n /* The seam is a box of its own, and not the neighbour's own border, because CSS joins\n two adjacent borders at a MITRE: where a 1px border meets the transparent top and\n bottom ones of a solid segment, its last pixel is cut on the diagonal and the bar\n ends in a notch at each end. A box carrying that border on ONE side has no second\n border to join, so its ends stay square.\n\n A BORDER and not a background, the VSeparator argument: Windows forced-colors\n forces a background to Canvas, where the separation would vanish, and a border\n colour to CanvasText, where it survives.\n\n Absolute, so the insets resolve against the PADDING box: -1px is exactly the width\n of the border, which lays the bar over the border area and gives it the full length\n of the border box. Every segment is positioned so that they all paint in the same\n phase, in document order, the way they did as plain flex items. */\n .v-button-group > .v-button,\n .v-button-group > :not(:where(.v-overlay, .v-button-group)) .v-button:not(:where(.v-overlay *)) {\n position: relative;\n }\n\n /* Only the generating rule is guarded. The two orientation rules below place the insets\n of a pseudo-element that, apart or seamless, is never generated at all. */\n .v-button-group:not([data-detached]):not([data-seamless]) > .v-button:not(:first-child)::before,\n .v-button-group:not([data-detached]):not([data-seamless])\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n content: '';\n position: absolute;\n }\n\n .v-button-group[data-orientation='horizontal'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-block: -1px;\n inset-inline-start: -1px;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n .v-button-group[data-orientation='vertical'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-inline: -1px;\n inset-block-start: -1px;\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n /* Seamless, the row is ONE object: an outer frame with nothing drawn inside it. The\n seam above is not generated, and what is left to clear is the segments' own borders\n on the edges they share.\n\n BOTH sides of every shared edge, never just one: the segments overlap by 1px, so a\n neighbour's border sits in the very same pixel column. Clearing one side alone leaves\n the other showing through wherever the segment painted on top has no background of\n its own, which is precisely the `ghost` and `outline` case.\n\n The COLOUR goes transparent rather than the border going away: every box keeps its\n 1px on all four sides, so nothing in the row changes width and the corners stay where\n they were rounded.\n\n TRAP — the class is doubled to reach (0,6,0), and that is not decoration. Written\n once these are (0,5,0), which TIES with the compound a component overrides a\n segment's border through (VToggle's outline frame, `.v-toggle[data-variant='outline']\n > .v-toggle-item[aria-pressed='true']:is(…)`), and a tie between two sheets is\n settled by whichever the consumer's bundler put last. An internal edge is never part\n of a frame, so the group has to win by construction. VToggle's rules are written for\n the wrapped shape too, and take the same class there, so the gap between the two\n holds whether an item is wrapped or not. */\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-inline-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-inline-end-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-block-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-block-end-color: transparent;\n }\n\n /* The elevation belongs to the ROW, not to each segment. Left to the buttons, every\n one of them casts a shadow onto the neighbour it overlaps, and the joints fill with\n a dark band instead of the whole reading as one object lifted off the page. So the\n group takes the shadow and the segments give theirs up, which also means the row\n rises as a whole when any of it is hovered.\n\n `:has()` rather than a data attribute on the root: the elevation reaches the\n buttons either from the group or one by one, and only the buttons know in the end.\n Its argument is a list, so a wrapped segment answers for itself, and since a\n selector list takes the specificity of its heaviest member, the two branches leave\n the weight where it was.\n\n The raised BACKGROUND stays with each button. It is what a ghost or an outline\n segment is painted on, and in the dark theme it is what the shadow needs in order\n to have something casting it.\n\n Detached, none of this applies and every rule steps aside: the buttons are apart, so\n there is no joint to fill and each of them casting its own shadow is right. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated],\n > :not(:where(.v-overlay, .v-button-group)) .v-button[data-elevated]:not(:where(.v-overlay *))\n ) {\n border-radius: var(--vectis-radius-interactive);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n /* After the hover rule and at equal specificity, the VButton order: pressing a\n raised segment settles the row back down. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n /* The class is doubled to reach (0,6,0). VButton's own hover and active shadows are\n (0,4,0) on this very element, so at equal specificity the winner would be whichever\n of the two sheets the consumer's bundler happened to put last. */\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated],\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:hover,\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:active,\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:where(.v-overlay *)) {\n box-shadow: none;\n }\n\n /* A FOCUSED segment rises above its neighbours, and it is the ONLY state that does.\n The ring is drawn outside the border box, so the next segment, painted later, would\n cut through it along the seam: removing this rule takes the keyboard focus ring away\n on one side of every segment but the last.\n\n Hover and active are deliberately not here, and putting them back is a visible bug:\n segments overlap by one pixel, and that pixel is the neighbour's own border, which\n is to say the seam. A raised segment paints its background over it, so the\n separation disappears on the far side of whichever segment the pointer is on, for\n the whole time it is there. A focused one covers it too, but under a ring that is\n drawing that edge itself.\n\n Wrapped, the `z-index` stays on the BUTTON and still counts among the segments: none\n of the three wrappers opens a stacking context, two being unpositioned and\n `.v-badge-host`, which is `position: relative`, keeping `z-index: auto`. Were one of\n them to take a `z-index`, this rule would raise the button inside that wrapper's own\n context and nothing on the page. */\n .v-button-group > .v-button:focus-visible,\n .v-button-group\n > :not(:where(.v-overlay, .v-button-group))\n .v-button:focus-visible:not(:where(.v-overlay *)) {\n z-index: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button-group {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsGA,IAAM,IAAQ,GAkBR,IAAY,QAAmB,EAAM,KAAK;EAQhD,EAAQ,GAAgB;GACtB,IAAI,UAAU;IACZ,OAAO,EAAM;GACf;GACA,IAAI,OAAO;IACT,OAAO,EAAM;GACf;GACA,IAAI,OAAO;IACT,OAAO,EAAM;GACf;GACA,IAAI,UAAU;IACZ,OAAO,EAAM;GACf;GACA,IAAI,WAAW;IACb,OAAO,EAAM;GACf;GACA,IAAI,WAAW;IACb,OAAO,EAAM;GACf;EACF,CAAC;EAED,IAAM,IAAU,EAAwB,IAAI;SAK5C,EAAa,EAEX,IAAI,EACN,CAAC,mBAcC,EAWM,OAAA;YAVA;GAAJ,KAAI;GACJ,OAAM;GACN,MAAK;GACJ,cAAY,EAAA,CAAA;GACZ,oBAAkB,EAAA;GAClB,iBAAe,EAAA,WAAQ,KAAQ,KAAA;GAC/B,iBAAe,EAAA,YAAQ,CAAK,EAAA,WAAQ,KAAQ,KAAA;GAC5C,mBAAiB,EAAA,YAAS,KAAQ,KAAA;MAEnC,EAAQ,EAAA,QAAA,SAAA,CAAA,GAAA,GAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VButtonGroup.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VButton/VButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Joins several VButtons or VIconButtons into one segmented control: the borders\n * they share are merged into a single line, and only the two ends of the group keep\n * their rounded corners, so the row reads as one object rather than as buttons\n * placed side by side.\n *\n * How that object is drawn is settled here, in three decisions. `detached` leaves the\n * buttons apart, with a gap and their own corners, instead of joining them at all.\n * `seamless` keeps them joined but takes the lines out from between them, so the row\n * becomes a single frame. Everything else is the joined default.\n *\n * `fullWidth` is the row's width rather than its drawing: the group fills its parent, and\n * the segments divide that width into equal shares instead of each measuring its own\n * label, which is what a segmented control standing on a line of its own is asked for.\n *\n * Since the row is one object, how its segments are drawn is its decision too: the group\n * hands its variant, size, compact and elevated down to every button inside, and each of\n * them wins over what the button was given. The tone travels the other way, as a\n * fallback a button can refuse, so that one segment in the row can be the destructive\n * one, and `disabled` is cumulative rather than either. The rules and their reasons are\n * written out in `context.ts`.\n *\n * Naming none of them leaves every child exactly as it would have rendered on its\n * own: the context then holds `undefined` throughout, and `undefined` is what hands a\n * prop back to its owner.\n */\nimport { provide, ref } from 'vue'\n\nimport type { ButtonSize, ButtonTone, ButtonVariant } from './VButton.vue'\nimport { buttonGroupKey } from './context'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\n\n/** Which way the row runs. */\nexport type ButtonGroupOrientation = 'horizontal' | 'vertical'\n\ninterface ButtonGroupProps {\n /**\n * The direction the buttons are joined in: a row by default, or a column under\n * `vertical`.\n */\n orientation?: ButtonGroupOrientation\n /**\n * Leaves the buttons as separate ones, with a gap between them and each keeping its\n * own corners, instead of joining them into a segmented control.\n */\n detached?: boolean\n /**\n * Takes the lines out from between the joined buttons, so the row reads as one frame\n * rather than as segments. It has no effect under `detached`, where the buttons are\n * apart already and there is no shared edge to draw on.\n */\n seamless?: boolean\n /**\n * Stretches the row across the whole inline size of its parent, every segment taking\n * an equal share of that width. A segment never shrinks below its own label, so a row\n * of labels too long for the parent overflows it rather than being crushed. Under\n * `vertical` it is the width alone: the segments already stretch across the column.\n */\n fullWidth?: boolean\n /**\n * How much visual weight every segment carries, on the values of VButton's own\n * `variant`: `solid`, `outline`, `ghost` or `soft`. It wins over the variant a\n * button inside was given.\n */\n variant?: ButtonVariant\n /**\n * The colour the segments take, among `accent`, `neutral` and `danger`. This one is\n * a fallback rather than an order: a button that names a tone of its own keeps it,\n * which is what lets a single destructive action stand out in the row.\n */\n tone?: ButtonTone\n /**\n * The height of the segments, from the size scale shared by every control:\n * `xs`, `sm`, `md`, `lg` or `xl`. It wins over the size a button inside was given.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height of every segment. It wins over the value a button inside\n * was given.\n */\n compact?: boolean\n /**\n * Raises every segment off the page, on the terms of VButton's own `elevated`. It\n * wins over the value a button inside was given.\n */\n elevated?: boolean\n /**\n * Makes every segment unusable. A button that disables itself stays disabled either\n * way: the two answers add up rather than one overruling the other.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row, which is a `role=\"group\"`: \"Text formatting\",\n * \"View\". A group with no name is announced as a bare group, so give one whenever the\n * buttons do not say on their own what they are for. A consumer `aria-label` or\n * `aria-labelledby` wins over it.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ButtonGroupProps>(), {\n // `detached`, `seamless` and `fullWidth` are the group's own layout, and take a real\n // default like the orientation does. The six that follow travel down to the buttons,\n // and are `undefined` by default, the booleans included: it is `undefined`, and not\n // `false`, that means the group has no opinion and lets the button keep its own.\n orientation: 'horizontal',\n detached: false,\n seamless: false,\n fullWidth: false,\n variant: undefined,\n tone: undefined,\n size: undefined,\n compact: undefined,\n elevated: undefined,\n disabled: undefined,\n label: undefined,\n})\n\nconst ariaLabel = useAriaLabel(() => props.label)\n\ndefineSlots<{\n /** The VButtons and VIconButtons to join together. */\n default(): unknown\n}>()\n\n// Getters, so the group's props stay reactive on the other side of the injection.\nprovide(buttonGroupKey, {\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get elevated() {\n return props.elevated\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst groupEl = ref<HTMLElement | null>(null)\n\n// A template ref on this component is not a safe way to its element: the comment above the\n// root makes the template a fragment in development, where `$el` is a text anchor. VToggle,\n// whose root this group is, reads the element from here.\ndefineExpose({\n /** The `role=\"group\"` element. */\n el: groupEl,\n})\n</script>\n\n<template>\n <!--\n The size travels through the injection alone, and the root deliberately carries\n neither `v-control` nor `data-size`, where VAvatarGroup carries both: nothing in\n this sheet reads a `--control-*` variable, and `v-control` set a second time above\n a subtree that already has it re-derives `--control-height` from the base height,\n dropping the compact its buttons had applied (styles/control-size.css).\n\n `data-seamless` is withheld under `detached`, so the DOM never carries a claim the\n markup cannot honour: apart, the buttons share no edge for a line to sit on.\n -->\n <div\n ref=\"groupEl\"\n class=\"v-button-group\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-orientation=\"orientation\"\n :data-detached=\"detached ? '' : undefined\"\n :data-seamless=\"seamless && !detached ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A SEGMENT is a direct child of the group, and it is not always the button. A\n * companion puts a wrapper between the two: VTooltip renders `.v-tooltip` around its\n * trigger, VPopover renders `.v-popover` around the `#trigger` slot, VBadge renders\n * `.v-badge-host` around the element it pins its pill to, and a consumer may add one of\n * their own. Written against the button alone, every rule in this sheet would step\n * aside for a segment carrying any of the three, and the row would quietly come apart\n * into buttons standing side by side.\n *\n * So each of them is written for BOTH shapes:\n *\n * > .v-button<tests> the button IS the segment\n * > WRAPPER<tests> BUTTON the button is somewhere inside it\n *\n * with WRAPPER = `:not(:where(.v-overlay, .v-button-group))` and BUTTON =\n * `.v-button:not(:where(.v-overlay *))`.\n *\n * Where the declarations land is decided by what they do. The PULL, the negative margin\n * that collapses two borders into one line, belongs to the segment: that is the box the\n * group lays out. The PAINT, the corners and the seam and the borders and the shadow,\n * belongs to the button, which carries them. Swapped, a wrapped segment would either be\n * pulled twice or paint nothing. A full-width row needs neither: its shares are tracks,\n * declared on the group itself and blind to what stands in them.\n *\n * The tests weigh the same on both branches (`:not(:first-child)` and `:has(~ …)` are\n * (0,1,0) each, and `:where()` weighs nothing), so a pair is always ONE specificity:\n * every figure quoted below holds for its wrapped half as well.\n *\n * The depth is free, which is what lets companions STACK: a badge inside a tooltip\n * inside a menu is three of them on one button, and each of the two guards in those\n * definitions is what makes a descendant combinator safe there.\n *\n * TRAP — `.v-overlay` appears in both guards, and neither is optional. A panel must not\n * pass for a segment: VMenu renders its panel as a SIBLING of its trigger, so a group\n * holding a menu has a child that is no segment at all. And a panel's CONTENT must not\n * be painted: VTooltip and VPopover render theirs INSIDE the wrapper, so a button\n * standing in one would otherwise take a segment's corners and seam. Every panel in the\n * design system carries that class, which is what makes one test cover both.\n *\n * TRAP — `.v-button-group` in the wrapper guard keeps a NESTED group out. A VToggle\n * inside a VButtonGroup is a row of its own, drawn by its own sheet; reaching through\n * it would flatten the corners of buttons the outer row never laid out and lay a second\n * seam over its own.\n */\n .v-button-group {\n display: inline-flex;\n /* Gives every segment the same height in a row, and the same width in a column,\n whatever each button's own content measures. */\n align-items: stretch;\n /* Declared on every group although only a raised one ever moves its shadow: that is\n what lets the reduced-motion block cancel it with a bare selector rather than\n repeating the elevation `:has()` below. */\n transition: box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-button-group[data-orientation='vertical'] {\n flex-direction: column;\n }\n\n /* An explicit display alongside the inline size, VButton's own full-width argument: an\n inline-level box sits on a line box, and the strut's descender would show as a few\n pixels of dead space under a row asked to fill its parent. */\n .v-button-group[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* Equal shares, and they are TRACKS rather than flex bases. A row that reads as one\n object cannot be cut into unequal pieces by whichever words happen to be in it, which\n rules out distributing the free space alone; and `flex: 1 1 0` cannot do it either,\n because flex shares out CONTENT boxes: a segment ends up as wide as its share PLUS\n the padding and border it carries, the same for three buttons but one button's worth\n of padding narrower for a segment wrapped in a tooltip, a popover or a badge, none of\n which pad anything. Measured at 34px on an `md` row. A `1fr` track is the share\n itself, whatever happens to stand in it.\n\n `1fr` is `minmax(auto, 1fr)`, so the automatic minimum is kept: a segment never\n shrinks below its label, and a row of labels too long for its parent overflows rather\n than crushing text nothing here could then truncate, a VButton having no label box to\n put an ellipsis on. A menu's panel takes no track either way — closed it is\n `display: none`, open it is fixed, and neither is a grid item.\n\n Horizontal only. In a column the width is the whole of it, the base `align-items:\n stretch` already giving every segment the full inline size. */\n .v-button-group[data-full-width][data-orientation='horizontal'] {\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: 1fr;\n }\n\n /* A wrapped button fills the segment it sits in, wherever the group STRETCHES that\n segment: down a column, where `align-items: stretch` gives every segment the full\n inline size, and across a full-width row, where each is given an equal track. The\n button would otherwise stay at the width of its own label and leave the rest of the\n segment blank.\n\n The button AND everything between it and the segment, since companions stack: a badge\n inside a tooltip has to be stretched itself, or the button fills a box that is still\n the width of its own label. Panels are left out of the chain, one of them holding a\n whole floating box that has no business being stretched to a segment.\n\n TRAP — the two selectors are what keeps this out of a plain horizontal row, and that\n is not an optimization. Nothing is stretched there: each segment is as wide as its own\n content, so a percentage on the button would resolve against a wrapper whose width\n depends on that very button, which is circular, and a wrapped segment would stop\n measuring its label. */\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)),\n .v-button-group[data-full-width][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *)))\n :is(.v-button, :has(.v-button)):not(:where(.v-overlay, .v-overlay *)) {\n inline-size: 100%;\n }\n\n /* Detached, the buttons are simply spaced: the base rule already gives the flex box\n and the vertical direction, so a gap and the cross-axis alignment are the whole\n difference. The vertical rule restores the stretching the base declares, which the\n centring above beats by one attribute. */\n .v-button-group[data-detached] {\n align-items: center;\n gap: var(--vectis-space-1);\n }\n\n .v-button-group[data-detached][data-orientation='vertical'] {\n align-items: stretch;\n }\n\n /* The negative margin pulls each segment onto its neighbour so their two 1px borders\n collapse into one line, over which the seam below is then laid: it draws the\n separation the filled variants would otherwise lack (their border is transparent)\n and unifies the joint between two outlined ones.\n\n Each block is scoped to one orientation on purpose. Left unscoped, the\n horizontal rules would also flatten the side corners and borders in a vertical\n group, where those are precisely the edges that must stay round. */\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-inline-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='horizontal'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n\n /* TRAP — the last segment is read as `:has(~ :not(.v-overlay))` and no longer as\n `:not(:last-child)`, and that is VMenu's doing: it renders its panel as a SIBLING of\n its trigger, so a menu closing a row would leave its trigger short of `:last-child`,\n and the row would lose its outer corner there with nothing in the DOM to say why.\n Asking whether a real SEGMENT follows, rather than whether anything does, makes the\n panel invisible to the count and weighs exactly what the test it replaces did.\n\n The first segment needs none of that: a panel is rendered after the trigger it\n belongs to, so it can never take `:first-child` from a segment. */\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(.v-button:not(:where(.v-overlay *))):not(\n :first-child\n ) {\n margin-block-start: -1px;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical'] > .v-button:not(:first-child),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-start-start-radius: 0;\n border-start-end-radius: 0;\n }\n\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group:not([data-detached])[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-end-start-radius: 0;\n border-end-end-radius: 0;\n }\n\n /* The seam is a box of its own, and not the neighbour's own border, because CSS joins\n two adjacent borders at a MITRE: where a 1px border meets the transparent top and\n bottom ones of a solid segment, its last pixel is cut on the diagonal and the bar\n ends in a notch at each end. A box carrying that border on ONE side has no second\n border to join, so its ends stay square.\n\n A BORDER and not a background, the VSeparator argument: Windows forced-colors\n forces a background to Canvas, where the separation would vanish, and a border\n colour to CanvasText, where it survives.\n\n Absolute, so the insets resolve against the PADDING box: -1px is exactly the width\n of the border, which lays the bar over the border area and gives it the full length\n of the border box. Every segment is positioned so that they all paint in the same\n phase, in document order, the way they did as plain flex items. */\n .v-button-group > .v-button,\n .v-button-group > :not(:where(.v-overlay, .v-button-group)) .v-button:not(:where(.v-overlay *)) {\n position: relative;\n }\n\n /* Only the generating rule is guarded. The two orientation rules below place the insets\n of a pseudo-element that, apart or seamless, is never generated at all. */\n .v-button-group:not([data-detached]):not([data-seamless]) > .v-button:not(:first-child)::before,\n .v-button-group:not([data-detached]):not([data-seamless])\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n content: '';\n position: absolute;\n }\n\n .v-button-group[data-orientation='horizontal'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-block: -1px;\n inset-inline-start: -1px;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n .v-button-group[data-orientation='vertical'] > .v-button:not(:first-child)::before,\n .v-button-group[data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *))::before {\n inset-inline: -1px;\n inset-block-start: -1px;\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n /* Seamless, the row is ONE object: an outer frame with nothing drawn inside it. The\n seam above is not generated, and what is left to clear is the segments' own borders\n on the edges they share.\n\n BOTH sides of every shared edge, never just one: the segments overlap by 1px, so a\n neighbour's border sits in the very same pixel column. Clearing one side alone leaves\n the other showing through wherever the segment painted on top has no background of\n its own, which is precisely the `ghost` and `outline` case.\n\n The COLOUR goes transparent rather than the border going away: every box keeps its\n 1px on all four sides, so nothing in the row changes width and the corners stay where\n they were rounded.\n\n TRAP — the class is doubled to reach (0,6,0), and that is not decoration. Written\n once these are (0,5,0), which TIES with the compound a component overrides a\n segment's border through (VToggle's outline frame, `.v-toggle[data-item-variant='outline']\n > .v-toggle-item[aria-pressed='true']:is(…)`), and a tie between two sheets is\n settled by whichever the consumer's bundler put last. An internal edge is never part\n of a frame, so the group has to win by construction. VToggle's rules are written for\n the wrapped shape too, and take the same class there, so the gap between the two\n holds whether an item is wrapped or not. */\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-inline-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='horizontal']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-inline-end-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:not(:first-child),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n .v-button:not(:where(.v-overlay *)) {\n border-block-start-color: transparent;\n }\n\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > .v-button:has(~ :not(.v-overlay)),\n .v-button-group.v-button-group[data-seamless][data-orientation='vertical']\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n .v-button:not(:where(.v-overlay *)) {\n border-block-end-color: transparent;\n }\n\n /* The elevation belongs to the ROW, not to each segment. Left to the buttons, every\n one of them casts a shadow onto the neighbour it overlaps, and the joints fill with\n a dark band instead of the whole reading as one object lifted off the page. So the\n group takes the shadow and the segments give theirs up, which also means the row\n rises as a whole when any of it is hovered.\n\n `:has()` rather than a data attribute on the root: the elevation reaches the\n buttons either from the group or one by one, and only the buttons know in the end.\n Its argument is a list, so a wrapped segment answers for itself, and since a\n selector list takes the specificity of its heaviest member, the two branches leave\n the weight where it was.\n\n The raised BACKGROUND stays with each button. It is what a ghost or an outline\n segment is painted on, and in the dark theme it is what the shadow needs in order\n to have something casting it.\n\n Detached, none of this applies and every rule steps aside: the buttons are apart, so\n there is no joint to fill and each of them casting its own shadow is right. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated],\n > :not(:where(.v-overlay, .v-button-group)) .v-button[data-elevated]:not(:where(.v-overlay *))\n ) {\n border-radius: var(--vectis-radius-interactive);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n /* After the hover rule and at equal specificity, the VButton order: pressing a\n raised segment settles the row back down. */\n .v-button-group:not([data-detached]):has(\n > .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']),\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']):not(\n :where(.v-overlay *)\n )\n ) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n /* The class is doubled to reach (0,6,0). VButton's own hover and active shadows are\n (0,4,0) on this very element, so at equal specificity the winner would be whichever\n of the two sheets the consumer's bundler happened to put last. */\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated],\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:hover,\n .v-button-group.v-button-group:not([data-detached]) > .v-button[data-elevated]:active,\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:hover:not(:where(.v-overlay *)),\n .v-button-group.v-button-group:not([data-detached])\n > :not(:where(.v-overlay, .v-button-group))\n .v-button[data-elevated]:active:not(:where(.v-overlay *)) {\n box-shadow: none;\n }\n\n /* A FOCUSED segment rises above its neighbours, and it is the ONLY state that does.\n The ring is drawn outside the border box, so the next segment, painted later, would\n cut through it along the seam: removing this rule takes the keyboard focus ring away\n on one side of every segment but the last.\n\n Hover and active are deliberately not here, and putting them back is a visible bug:\n segments overlap by one pixel, and that pixel is the neighbour's own border, which\n is to say the seam. A raised segment paints its background over it, so the\n separation disappears on the far side of whichever segment the pointer is on, for\n the whole time it is there. A focused one covers it too, but under a ring that is\n drawing that edge itself.\n\n Wrapped, the `z-index` stays on the BUTTON and still counts among the segments: none\n of the three wrappers opens a stacking context, two being unpositioned and\n `.v-badge-host`, which is `position: relative`, keeping `z-index: auto`. Were one of\n them to take a `z-index`, this rule would raise the button inside that wrapper's own\n context and nothing on the page. */\n .v-button-group > .v-button:focus-visible,\n .v-button-group\n > :not(:where(.v-overlay, .v-button-group))\n .v-button:focus-visible:not(:where(.v-overlay *)) {\n z-index: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button-group {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsGA,IAAM,IAAQ,GAkBR,IAAY,QAAmB,EAAM,KAAK;EAQhD,EAAQ,GAAgB;GACtB,IAAI,UAAU;IACZ,OAAO,EAAM;GACf;GACA,IAAI,OAAO;IACT,OAAO,EAAM;GACf;GACA,IAAI,OAAO;IACT,OAAO,EAAM;GACf;GACA,IAAI,UAAU;IACZ,OAAO,EAAM;GACf;GACA,IAAI,WAAW;IACb,OAAO,EAAM;GACf;GACA,IAAI,WAAW;IACb,OAAO,EAAM;GACf;EACF,CAAC;EAED,IAAM,IAAU,EAAwB,IAAI;SAK5C,EAAa,EAEX,IAAI,EACN,CAAC,mBAcC,EAWM,OAAA;YAVA;GAAJ,KAAI;GACJ,OAAM;GACN,MAAK;GACJ,cAAY,EAAA,CAAA;GACZ,oBAAkB,EAAA;GAClB,iBAAe,EAAA,WAAQ,KAAQ,KAAA;GAC/B,iBAAe,EAAA,YAAQ,CAAK,EAAA,WAAQ,KAAQ,KAAA;GAC5C,mBAAiB,EAAA,YAAS,KAAQ,KAAA;MAEnC,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-calendar{block-size:100%;min-block-size:0;font-family:var(--vectis-text-family);color:var(--vectis-color-text);display:flex}.v-calendar[data-disabled]{color:var(--vectis-color-text-subtle);cursor:not-allowed}.v-calendar[data-disabled] .v-calendar-view{pointer-events:none}.v-calendar-region{flex-direction:column;flex:1;min-block-size:0;min-inline-size:0;display:flex}.v-calendar-toolbar{align-items:center;gap:var(--vectis-space-2);flex-wrap:wrap;flex:none;padding-block-end:var(--vectis-space-3);display:flex}.v-calendar-nav{align-items:center;gap:var(--vectis-space-1);display:flex}.v-calendar-title{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}.v-calendar-actions{align-items:center;gap:var(--vectis-space-2);margin-inline-start:auto;display:flex}.v-calendar-view{border:1px solid var(--vectis-color-border);border-radius:var(--vectis-radius-surface);flex:1;block-size:100%;min-block-size:0;overflow:auto}.v-calendar-view[data-outside]{cursor:not-allowed}.v-calendar-weekday{color:var(--vectis-color-text-muted);font-size:var(--vectis-text-overline-size);font-weight:var(--vectis-text-overline-weight);letter-spacing:var(--vectis-text-overline-tracking);text-transform:uppercase}.v-calendar-view .v-calendar-today{background:var(--vectis-color-accent);color:var(--vectis-color-text-on-accent);font-weight:var(--vectis-font-weight-semibold)}.v-calendar-cell{border-block-start:1px solid var(--vectis-color-border);border-inline-start:1px solid var(--vectis-color-border)}.v-calendar-cell:first-child{border-inline-start:none}.v-calendar-cell:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:calc(-1 * var(--vectis-focus-ring-width))}.v-calendar-button{cursor:pointer;transition:background-color var(--vectis-duration-fast) var(--vectis-ease-default);background:0 0;border:none;font-family:inherit}.v-calendar-button:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:var(--vectis-focus-ring-offset)}.v-calendar-edge-cue[data-edge]:after{content:"";inline-size:var(--vectis-control-size-calendar-edge);background:var(--vectis-color-accent-surface);z-index:2;pointer-events:none;position:absolute;inset-block:0}.v-calendar-edge-cue[data-edge=start]:after{inset-inline-start:0}.v-calendar-edge-cue[data-edge=end]:after{inset-inline-end:0}@media (prefers-reduced-motion:reduce){.v-calendar-button{transition:none}}}
|
|
2
|
+
@layer vectis.components{.v-calendar{block-size:100%;min-block-size:0;font-family:var(--vectis-text-family);color:var(--vectis-color-text);display:flex}.v-calendar[data-disabled]{color:var(--vectis-color-text-subtle);cursor:not-allowed}.v-calendar[data-disabled] .v-calendar-view{pointer-events:none}.v-calendar-region{flex-direction:column;flex:1;min-block-size:0;min-inline-size:0;display:flex}.v-calendar-toolbar{align-items:center;gap:var(--vectis-space-2);flex-wrap:wrap;flex:none;padding-block-end:var(--vectis-space-3);display:flex}.v-calendar-nav{align-items:center;gap:var(--vectis-space-1);display:flex}.v-calendar-title{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}.v-calendar-actions{align-items:center;gap:var(--vectis-space-2);margin-inline-start:auto;display:flex}.v-calendar-view{border:1px solid var(--vectis-color-border);border-radius:var(--vectis-radius-surface);flex:1;block-size:100%;min-block-size:0;overflow:auto}.v-calendar-view[data-outside]{cursor:not-allowed}.v-calendar-weekday{color:var(--vectis-color-text-muted);font-size:var(--vectis-text-overline-size);font-weight:var(--vectis-text-overline-weight);letter-spacing:var(--vectis-text-overline-tracking);text-transform:uppercase}.v-calendar-view .v-calendar-today{background:var(--vectis-color-accent);color:var(--vectis-color-text-on-accent);font-weight:var(--vectis-font-weight-semibold)}.v-calendar-cell{border-block-start:1px solid var(--vectis-color-border);border-inline-start:1px solid var(--vectis-color-border)}.v-calendar-cell:first-child{border-inline-start:none}.v-calendar-cell:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:calc(-1 * var(--vectis-focus-ring-width))}.v-calendar-button{cursor:pointer;transition:background-color var(--vectis-duration-fast) var(--vectis-ease-default);background:0 0;border:none;font-family:inherit}.v-calendar-button:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:var(--vectis-focus-ring-offset)}.v-calendar-edge-cue[data-edge]:after{content:"";inline-size:var(--vectis-control-size-calendar-edge);background:var(--vectis-color-accent-surface);z-index:2;pointer-events:none;position:absolute;inset-block:0}.v-calendar-edge-cue[data-edge=start]:after{inset-inline-start:0}.v-calendar-edge-cue[data-edge=end]:after{inset-inline-end:0}@media (prefers-reduced-motion:reduce){.v-calendar-button{transition:none}}@media (forced-colors:active){.v-calendar-view .v-calendar-today{forced-color-adjust:none;color:highlighttext;background:highlight}.v-calendar-edge-cue[data-edge]:after{forced-color-adjust:none;background:highlight}}}
|