vectis-ui 0.10.1 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/VAccordion/VAccordion.js.map +1 -1
- package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.d.ts +6 -21
- package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/context.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.d.ts +2 -12
- package/dist/components/VAvatar/VAvatar.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.d.ts +2 -2
- package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/context.d.ts +1 -5
- package/dist/components/VAvatar/context.js.map +1 -1
- package/dist/components/VBadge/VBadge.d.ts +13 -27
- package/dist/components/VBadge/VBadge.js.map +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +3 -9
- package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButton.d.ts +10 -17
- package/dist/components/VButton/VButton.js.map +1 -1
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.d.ts +6 -15
- 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/VButton/context.d.ts +1 -9
- package/dist/components/VButton/context.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.d.ts +9 -24
- package/dist/components/VCalendar/VCalendar.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.d.ts +4 -13
- 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.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +1 -5
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/color.d.ts +1 -7
- package/dist/components/VCalendar/color.js.map +1 -1
- package/dist/components/VCalendar/edgeStep.d.ts +5 -15
- package/dist/components/VCalendar/edgeStep.js.map +1 -1
- package/dist/components/VCalendar/gesture.d.ts +12 -36
- package/dist/components/VCalendar/gesture.js.map +1 -1
- package/dist/components/VCalendar/keyboard.d.ts +5 -42
- package/dist/components/VCalendar/keyboard.js.map +1 -1
- package/dist/components/VCalendar/layout.d.ts +38 -199
- package/dist/components/VCalendar/layout.js.map +1 -1
- package/dist/components/VCalendar/timeGrid.d.ts +3 -25
- package/dist/components/VCalendar/timeGrid.js.map +1 -1
- package/dist/components/VCalendar/types.d.ts +6 -23
- package/dist/components/VCarousel/VCarousel.d.ts +25 -96
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.d.ts +1 -6
- 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.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.d.ts +1 -5
- package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VChip/VChip.d.ts +2 -8
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +21 -59
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +117 -119
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxOption.js.map +1 -1
- package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxSeparator.js.map +1 -1
- package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/infiniteScroll.d.ts +4 -10
- package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +11 -36
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +23 -20
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.d.ts +12 -21
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/mask.d.ts +14 -59
- package/dist/components/VDateInput/mask.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.d.ts +6 -12
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/keyboard.d.ts +10 -42
- package/dist/components/VDatePicker/keyboard.js.map +1 -1
- package/dist/components/VDatePicker/names.d.ts +8 -22
- package/dist/components/VDatePicker/names.js.map +1 -1
- package/dist/components/VDialog/VDialog.d.ts +5 -10
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +51 -53
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDialog/VDialogAlert.d.ts +2 -3
- package/dist/components/VDialog/VDialogAlert.js.map +1 -1
- package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +11 -37
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/truncate.d.ts +3 -23
- package/dist/components/VFileInput/truncate.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.d.ts +15 -65
- package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/fileKind.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.d.ts +6 -15
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.d.ts +10 -49
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VIcon/VIcon.d.ts +7 -19
- 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 +5 -19
- package/dist/components/VIcon/iconProps.js.map +1 -1
- package/dist/components/VIcon/resolver.d.ts +15 -43
- package/dist/components/VIcon/resolver.js.map +1 -1
- package/dist/components/VIcon/types.d.ts +7 -34
- package/dist/components/VIconButton/VIconButton.d.ts +2 -3
- 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 +14 -29
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.js.map +1 -1
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +34 -50
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/context.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +8 -16
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +55 -63
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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.d.ts +1 -6
- package/dist/components/VMenu/VMenuGroup.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.d.ts +0 -5
- package/dist/components/VMenu/VMenuItem.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.d.ts +1 -6
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuSeparator.js.map +1 -1
- package/dist/components/VMenu/VMenuSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/context.d.ts +3 -11
- package/dist/components/VMenu/context.js.map +1 -1
- package/dist/components/VPagination/VPagination.d.ts +9 -20
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VPopover/VPopover.d.ts +7 -18
- package/dist/components/VPopover/VPopover.js.map +1 -1
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js +23 -27
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.d.ts +2 -6
- 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.d.ts +4 -15
- 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.d.ts +1 -5
- package/dist/components/VRadio/VRadio.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.d.ts +2 -15
- 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 +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.d.ts +1 -5
- package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +1 -5
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +80 -75
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationSeparator.js.map +1 -1
- package/dist/components/VSideNavigation/context.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +6 -19
- package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSlider/VSlider.d.ts +5 -13
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/state.d.ts +4 -17
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.d.ts +3 -5
- 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.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTab.d.ts +1 -5
- package/dist/components/VTabs/VTab.js.map +1 -1
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabPanel.js.map +1 -1
- package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabs.d.ts +5 -17
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/context.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +13 -20
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.d.ts +8 -21
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/search.d.ts +3 -34
- package/dist/components/VTimeInput/search.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.d.ts +9 -31
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/limits.d.ts +18 -45
- package/dist/components/VTimePicker/limits.js.map +1 -1
- package/dist/components/VToast/VToast.js.map +1 -1
- 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.map +1 -1
- package/dist/components/VToast/state.d.ts +2 -7
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/components/VToggle/VToggle.d.ts +5 -9
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.d.ts +1 -9
- package/dist/components/VToggle/VToggleItem.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/context.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.d.ts +3 -16
- package/dist/components/VTooltip/VTooltip.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTypography/VTypography.d.ts +4 -8
- 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.map +1 -1
- package/dist/composables/useClearable.d.ts +2 -8
- package/dist/composables/useClearable.js.map +1 -1
- package/dist/composables/useControlShape.js.map +1 -1
- package/dist/composables/useDetailsOpen.d.ts +2 -10
- package/dist/composables/useDetailsOpen.js.map +1 -1
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.d.ts +4 -17
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileDrop.js.map +1 -1
- package/dist/composables/useFileField.d.ts +1 -5
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useFocusoutDismiss.js.map +1 -1
- package/dist/composables/useIconClickHandlers.d.ts +4 -19
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/composables/useInertLink.d.ts +1 -1
- package/dist/composables/useInertLink.js.map +1 -1
- package/dist/composables/useLiveAnnouncer.js.map +1 -1
- package/dist/composables/useMaskedField.d.ts +12 -27
- package/dist/composables/useMaskedField.js.map +1 -1
- package/dist/composables/usePopover.d.ts +3 -11
- package/dist/composables/usePopover.js.map +1 -1
- package/dist/composables/useProgressValue.js.map +1 -1
- package/dist/composables/useRootAttrs.d.ts +0 -9
- package/dist/composables/useRootAttrs.js +11 -4
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useSlotNodes.d.ts +3 -14
- package/dist/composables/useSlotNodes.js.map +1 -1
- package/dist/composables/useTextLimit.d.ts +4 -16
- package/dist/composables/useTextLimit.js.map +1 -1
- package/dist/composables/useTimer.d.ts +2 -15
- package/dist/composables/useTimer.js.map +1 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.d.ts +9 -38
- package/dist/i18n/state.js.map +1 -1
- package/dist/i18n/types.d.ts +19 -81
- package/dist/icons.d.ts +2 -12
- package/dist/tokens/css.d.ts +3 -16
- package/dist/tokens/css.js.map +1 -1
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/primitives.js.map +1 -1
- package/dist/tokens/semantic.d.ts +1 -23
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens/types.d.ts +2 -12
- package/dist/tokens/types.js.map +1 -1
- package/dist/types.d.ts +3 -21
- package/dist/utils/array.d.ts +2 -4
- package/dist/utils/array.js.map +1 -1
- package/dist/utils/arrowNav.d.ts +2 -26
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/chip.d.ts +1 -8
- package/dist/utils/chip.js.map +1 -1
- package/dist/utils/clock.d.ts +16 -39
- package/dist/utils/clock.js.map +1 -1
- package/dist/utils/css.d.ts +5 -16
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/date.d.ts +17 -60
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/direction.d.ts +2 -9
- package/dist/utils/direction.js.map +1 -1
- package/dist/utils/env.d.ts +2 -15
- package/dist/utils/env.js.map +1 -1
- package/dist/utils/file.d.ts +8 -46
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/focus.d.ts +1 -14
- package/dist/utils/focus.js +1 -6
- package/dist/utils/focus.js.map +1 -1
- package/dist/utils/hostWarns.d.ts +3 -8
- package/dist/utils/hostWarns.js.map +1 -1
- package/dist/utils/ids.d.ts +2 -7
- package/dist/utils/ids.js.map +1 -1
- package/dist/utils/matcher.d.ts +1 -8
- package/dist/utils/matcher.js.map +1 -1
- package/dist/utils/memo.d.ts +1 -11
- package/dist/utils/memo.js.map +1 -1
- package/dist/utils/number.d.ts +1 -7
- package/dist/utils/number.js.map +1 -1
- package/dist/utils/text.d.ts +4 -23
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/time.d.ts +4 -12
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/vnode.d.ts +0 -10
- package/dist/utils/vnode.js.map +1 -1
- package/package.json +1 -1
|
@@ -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-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
|
+
{"version":3,"file":"VButtonGroup.js","names":[],"sources":["../../../src/components/VButton/VButtonGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Group shape overrides individual buttons; tone is a fallback and disabled is cumulative. CSS\n * joins borders without changing native activation.\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 rather\n * than as segments.\n */\n seamless?: boolean\n /**\n * Stretches the row across the whole inline size of its parent, every segment taking an equal\n * share of that width.\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 /** The colour the segments take, among `accent`, `neutral` and `danger`. */\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 consumer `aria-label` or `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 `data-seamless` is withheld under `detached`, so the DOM never carries a claim the markup\n 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 * `.v-overlay` appears in both guards, and neither is optional. A panel must not pass for a\n * segment: VMenu renders its panel as a SIBLING of its trigger, so a group holding a menu has\n * a child that is no segment at all.\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 /*\n * Grid tracks equalize complete border boxes; flex distributes content boxes and leaves\n * wrapped segments narrower by their missing padding.\n */\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 /*\n * The two selectors are what keeps this out of a plain horizontal row, and that is not an\n * optimization. Nothing is stretched there: each segment is as wide as its own content, so a\n * percentage on the button would resolve against a wrapper whose width depends on that very\n * button, which is circular, and a wrapped segment would stop measuring its label.\n */\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 /*\n * The negative margin pulls each segment onto its neighbour so their two 1px borders collapse\n * into one line, over which the seam below is then laid: it draws the separation the filled\n * variants would otherwise lack (their border is transparent) and unifies the joint between\n * two outlined ones. Each block is scoped to one orientation on purpose.\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)):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 /*\n * The first segment needs none of that: a panel is rendered after the trigger it belongs to,\n * so it can never take `:first-child` from a segment.\n */\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 /*\n * The seam is a box of its own, and not the neighbour's own border, because CSS joins two\n * adjacent borders at a MITRE: where a 1px border meets the transparent top and bottom ones\n * of a solid segment, its last pixel is cut on the diagonal and the bar ends in a notch at\n * each end. A box carrying that border on ONE side has no second border to join, so its ends\n * stay square.\n */\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 /*\n * The class is doubled to reach (0,6,0), and that is not decoration. Written once these are\n * (0,5,0), which TIES with the compound a component overrides a segment's border through\n * (VToggle's outline frame,\n * `.v-toggle[data-item-variant='outline'] > .v-toggle-item[aria-pressed='true']:is(…)`), and\n * a tie between two sheets is settled by whichever the consumer's bundler put last.\n */\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 /*\n * Its argument is a list, so a wrapped segment answers for itself, and since a selector list\n * takes the specificity of its heaviest member, the two branches leave the weight where it\n * was. The raised BACKGROUND stays with each button.\n */\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 /*\n * VButton's own hover and active shadows are (0,4,0) on this very element, so at equal\n * specificity the winner would be whichever of the two sheets the consumer's bundler happened\n * to put last.\n */\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 /*\n * Raise keyboard focus above the next segment to preserve the shared-edge ring. Do not raise\n * hover/active states, which would cover the neighbour's border seam.\n */\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-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
|
+
{"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 * Group shape overrides individual buttons; tone is a fallback and disabled is cumulative. CSS\n * joins borders without changing native activation.\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 rather\n * than as segments.\n */\n seamless?: boolean\n /**\n * Stretches the row across the whole inline size of its parent, every segment taking an equal\n * share of that width.\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 /** The colour the segments take, among `accent`, `neutral` and `danger`. */\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 consumer `aria-label` or `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 `data-seamless` is withheld under `detached`, so the DOM never carries a claim the markup\n 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 * `.v-overlay` appears in both guards, and neither is optional. A panel must not pass for a\n * segment: VMenu renders its panel as a SIBLING of its trigger, so a group holding a menu has\n * a child that is no segment at all.\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 /*\n * Grid tracks equalize complete border boxes; flex distributes content boxes and leaves\n * wrapped segments narrower by their missing padding.\n */\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 /*\n * The two selectors are what keeps this out of a plain horizontal row, and that is not an\n * optimization. Nothing is stretched there: each segment is as wide as its own content, so a\n * percentage on the button would resolve against a wrapper whose width depends on that very\n * button, which is circular, and a wrapped segment would stop measuring its label.\n */\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 /*\n * The negative margin pulls each segment onto its neighbour so their two 1px borders collapse\n * into one line, over which the seam below is then laid: it draws the separation the filled\n * variants would otherwise lack (their border is transparent) and unifies the joint between\n * two outlined ones. Each block is scoped to one orientation on purpose.\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)):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 /*\n * The first segment needs none of that: a panel is rendered after the trigger it belongs to,\n * so it can never take `:first-child` from a segment.\n */\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 /*\n * The seam is a box of its own, and not the neighbour's own border, because CSS joins two\n * adjacent borders at a MITRE: where a 1px border meets the transparent top and bottom ones\n * of a solid segment, its last pixel is cut on the diagonal and the bar ends in a notch at\n * each end. A box carrying that border on ONE side has no second border to join, so its ends\n * stay square.\n */\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 /*\n * The class is doubled to reach (0,6,0), and that is not decoration. Written once these are\n * (0,5,0), which TIES with the compound a component overrides a segment's border through\n * (VToggle's outline frame,\n * `.v-toggle[data-item-variant='outline'] > .v-toggle-item[aria-pressed='true']:is(…)`), and\n * a tie between two sheets is settled by whichever the consumer's bundler put last.\n */\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 /*\n * Its argument is a list, so a wrapped segment answers for itself, and since a selector list\n * takes the specificity of its heaviest member, the two branches leave the weight where it\n * was. The raised BACKGROUND stays with each button.\n */\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 /*\n * VButton's own hover and active shadows are (0,4,0) on this very element, so at equal\n * specificity the winner would be whichever of the two sheets the consumer's bundler happened\n * to put last.\n */\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 /*\n * Raise keyboard focus above the next segment to preserve the shared-edge ring. Do not raise\n * hover/active states, which would cover the neighbour's border seam.\n */\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuEA,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,mBAQC,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"}
|
|
@@ -16,15 +16,7 @@ export interface ButtonGroupContext {
|
|
|
16
16
|
}
|
|
17
17
|
export declare const buttonGroupKey: InjectionKey<ButtonGroupContext>;
|
|
18
18
|
/**
|
|
19
|
-
* The
|
|
19
|
+
* The empty context a component provides to stop a row's at its own boundary. Every member
|
|
20
20
|
* being `undefined`, `??` and `||` hand each button below it back its own prop.
|
|
21
|
-
*
|
|
22
|
-
* It exists because `provide` reaches the whole subtree, floating panels included, and a
|
|
23
|
-
* panel holds buttons that are not segments of the row that opened it: VDatePicker writes
|
|
24
|
-
* `size="sm"` on its navigation, VTimePicker `size="lg"` on its hour and minute cells. Since
|
|
25
|
-
* a group WINS over a button's own prop, a VInputGroup in `lg` would otherwise blow the
|
|
26
|
-
* calendar arrows up to `lg`, and it only shows once the panel is open.
|
|
27
|
-
*
|
|
28
|
-
* This is the JS counterpart of the `.v-overlay` guard every VButtonGroup selector carries.
|
|
29
21
|
*/
|
|
30
22
|
export declare const NO_BUTTON_GROUP: ButtonGroupContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../../src/components/VButton/context.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../../src/components/VButton/context.ts"],"sourcesContent":["/**\n * Group shape wins; each button may override tone. Disabled is cumulative so no child can\n * reactivate a disabled row.\n */\n\nimport type { InjectionKey } from 'vue'\n\nimport type { ButtonSize, ButtonTone, ButtonVariant } from './VButton.vue'\n\nexport interface ButtonGroupContext {\n /** How much visual weight every segment carries. */\n readonly variant?: ButtonVariant\n /** The colour a segment takes unless it names one of its own. */\n readonly tone?: ButtonTone\n /** The height of the segments. */\n readonly size?: ButtonSize\n /** The reduced density. */\n readonly compact?: boolean\n /** Whether the segments are raised off the page. */\n readonly elevated?: boolean\n /** Whether the whole row is unusable, which no segment can refuse. */\n readonly disabled?: boolean\n}\n\nexport const buttonGroupKey: InjectionKey<ButtonGroupContext> = Symbol('v-button-group')\n\n/**\n * The empty context a component provides to stop a row's at its own boundary. Every member\n * being `undefined`, `??` and `||` hand each button below it back its own prop.\n */\nexport const NO_BUTTON_GROUP: ButtonGroupContext = Object.freeze({})\n"],"mappings":";AAwBA,IAAa,IAAmD,OAAO,gBAAgB,GAM1E,IAAsC,OAAO,OAAO,CAAC,CAAC"}
|
|
@@ -8,10 +8,8 @@ export interface CalendarProps {
|
|
|
8
8
|
/** How many days the custom view shows, and how far Previous and Next step in it. */
|
|
9
9
|
customDays?: number;
|
|
10
10
|
/**
|
|
11
|
-
* Which weekdays are on show, as numbers from 0 for Sunday.
|
|
12
|
-
*
|
|
13
|
-
* starts on, and it wins over `firstDayOfWeek`. Left out, the seven days starting on
|
|
14
|
-
* `firstDayOfWeek`.
|
|
11
|
+
* Which weekdays are on show, as numbers from 0 for Sunday. The ORDER matters as well: the
|
|
12
|
+
* first entry is the day a week starts on, and it wins over `firstDayOfWeek`.
|
|
15
13
|
*/
|
|
16
14
|
weekdays?: number[];
|
|
17
15
|
/**
|
|
@@ -50,25 +48,19 @@ export interface CalendarProps {
|
|
|
50
48
|
*/
|
|
51
49
|
readonly?: boolean;
|
|
52
50
|
/**
|
|
53
|
-
* Freezes the whole calendar: nothing can be moved, created or opened, no other period
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* separates this from `readonly`, where only the editing stops.
|
|
51
|
+
* Freezes the whole calendar: nothing can be moved, created or opened, no other period can be
|
|
52
|
+
* reached, and everything greys out through the colour tokens. That is what separates this
|
|
53
|
+
* from `readonly`, where only the editing stops.
|
|
57
54
|
*/
|
|
58
55
|
disabled?: boolean;
|
|
59
56
|
/**
|
|
60
57
|
* Lets an empty stretch of a time grid be drawn out with the pointer, up or down from the
|
|
61
|
-
* slot pressed.
|
|
62
|
-
* added to `events`: putting the event on the calendar is yours to do. A click, or Enter on a
|
|
63
|
-
* focused cell, reports `cell-activate` with or without this.
|
|
58
|
+
* slot pressed.
|
|
64
59
|
*/
|
|
65
60
|
creatable?: boolean;
|
|
66
61
|
/**
|
|
67
|
-
* How long a dragged event has to rest against the side of the calendar before the view
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* The wait is the point of it: paging the instant the pointer touched the edge would make
|
|
71
|
-
* the last day of a week impossible to aim at.
|
|
62
|
+
* How long a dragged event has to rest against the side of the calendar before the view turns
|
|
63
|
+
* to the previous or next period, in milliseconds.
|
|
72
64
|
*/
|
|
73
65
|
edgeStepDelay?: number;
|
|
74
66
|
/** Stops dragging near the top or bottom of a time grid from scrolling it. */
|
|
@@ -85,14 +77,7 @@ declare const __VLS_export: <E extends CalendarEvent>(__VLS_props: NonNullable<A
|
|
|
85
77
|
* year holding it. It opens on today.
|
|
86
78
|
*/
|
|
87
79
|
date?: string;
|
|
88
|
-
/**
|
|
89
|
-
* What is on the calendar, empty to begin with. It is a MODEL rather than a plain prop
|
|
90
|
-
* because dragging and resizing write back to it: the calendar rearranges what it is given
|
|
91
|
-
* and hands the new list back, never mutating the one it received.
|
|
92
|
-
*
|
|
93
|
-
* Opening an event for editing stays with the consumer: this component reads and
|
|
94
|
-
* rearranges, and never creates or deletes: a slot drawn out is reported, not added.
|
|
95
|
-
*/
|
|
80
|
+
/** What is on the calendar, empty to begin with. */
|
|
96
81
|
events?: E[];
|
|
97
82
|
}) & {
|
|
98
83
|
"onCell-activate"?: ((cell: CalendarCell) => any) | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VCalendar.js","names":[],"sources":["../../../src/components/VCalendar/VCalendar.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @a11y @ssr @core\n/**\n * An agenda: the days side by side, the hours running down them, the events drawn where they\n * fall.\n *\n * A place to READ and REARRANGE a schedule, never to write one. Opening an event for editing\n * stays with the consumer — this reports what was asked for and leaves the form to them.\n *\n * Everything is local ISO `YYYY-MM-DD` and 24-hour `HH:mm`, which is what keeps the server\n * and the browser in agreement. An event may name a `timezone`, and that name is SHOWN\n * beside its times but never applied: converting would move the card depending on where the\n * page was rendered, the one thing a calendar must not do.\n *\n * The JS is what the platform leaves no way round — there is no accessible time grid to\n * build on and no way to say \"these three meetings overlap, share the width\" in CSS, so the\n * ARIA grid pattern and the overlap layout are written by hand. Every geometric and\n * calendrical decision lives in `layout.ts`, pure, because jsdom lays nothing out and that is\n * the only place any of it can be tested.\n */\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { arrow_drop_down as arrowDropDownIcon } from '../VIcon/icons/arrow_drop_down'\nimport { chevron_left as chevronLeftIcon } from '../VIcon/icons/chevron_left'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTimer } from '../../composables/useTimer'\nimport { firstDayOfWeekFor, formatDateDisplay, formatDisplayRange } from '../../utils/date'\nimport { hourCycleFor, minutesOf } from '../../utils/time'\n\nimport { useMessages, useResolvedLocale } from '../../i18n/state'\n\nimport VCalendarMonth from './VCalendarMonth.vue'\nimport VCalendarTimeGrid, { type FocusedCell } from './VCalendarTimeGrid.vue'\nimport VCalendarYear from './VCalendarYear.vue'\nimport { EDGE_STEP_DELAY } from './edgeStep'\nimport {\n monthWeeks,\n monthsOfYear,\n normalizeWeekdays,\n stepAnchor,\n sameTimes,\n timeOf,\n timesOf,\n todayISO,\n visibleDays,\n visibleRange,\n windowOf,\n} from './layout'\nimport type {\n ActivatedCell,\n CalendarCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n CalendarView,\n} from './types'\n\nexport interface CalendarProps {\n /**\n * Which views the menu offers, in the order it lists them. Narrowing it is how a\n * calendar that only ever shows weeks stops offering anything else.\n */\n views?: CalendarView[]\n /** How many days the custom view shows, and how far Previous and Next step in it. */\n customDays?: number\n /**\n * Which weekdays are on show, as numbers from 0 for Sunday. `[1,2,3,4,5]` hides the\n * weekend everywhere. The ORDER matters as well: the first entry is the day a week\n * starts on, and it wins over `firstDayOfWeek`. Left out, the seven days starting on\n * `firstDayOfWeek`.\n */\n weekdays?: number[]\n /**\n * The day a week starts on, from 0 for Sunday, when `weekdays` is not given. Left out,\n * the locale decides.\n */\n firstDayOfWeek?: number\n /** The language the days, months and times are written in. Falls back to the global one. */\n locale?: string\n /**\n * Whether times are shown on a twelve or twenty-four hour clock. Left out, the reader's\n * language decides. It is the same prop, under the same name, as VTimePicker's.\n */\n format?: CalendarFormat\n /** The hour the grid starts at, from 0. */\n dayStart?: number\n /** The hour it ends at, up to 24. */\n dayEnd?: number\n /**\n * The step everything snaps to, in minutes: how far a nudge moves an event, and the unit a\n * slot is drawn out in.\n */\n slotDuration?: number\n /** Where the grid is scrolled to when it first appears, so the working day is in view. */\n scrollTime?: string\n /**\n * Leaves out the line drawn across today's column at the time it is now, and the dot on\n * its leading edge. Left in, it ticks once a minute while the calendar is on screen.\n */\n hideCurrentTime?: boolean\n /** How many events a day of the month view shows before it starts counting the rest. */\n monthEventLimit?: number\n /**\n * Stops events being moved and stretched, by dragging or with the keyboard. They\n * stay readable and clickable, and nothing else.\n */\n readonly?: boolean\n /**\n * Freezes the whole calendar: nothing can be moved, created or opened, no other period\n * can be reached, and everything greys out through the colour tokens. The cards leave the\n * tab order, the grid keeps its own so the agenda can still be read. That is what\n * separates this from `readonly`, where only the editing stops.\n */\n disabled?: boolean\n /**\n * Lets an empty stretch of a time grid be drawn out with the pointer, up or down from the\n * slot pressed. On release its times are reported through `event-create`, and nothing is\n * added to `events`: putting the event on the calendar is yours to do. A click, or Enter on a\n * focused cell, reports `cell-activate` with or without this.\n */\n creatable?: boolean\n /**\n * How long a dragged event has to rest against the side of the calendar before the view\n * turns to the previous or next period, in milliseconds. `0` turns that off.\n *\n * The wait is the point of it: paging the instant the pointer touched the edge would make\n * the last day of a week impossible to aim at.\n */\n edgeStepDelay?: number\n /** Stops dragging near the top or bottom of a time grid from scrolling it. */\n noEdgeScroll?: boolean\n /** What the calendar is called, for anyone who cannot see it. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<CalendarProps>(), {\n views: () => ['day', '4days', 'week'],\n customDays: 4,\n weekdays: undefined,\n firstDayOfWeek: undefined,\n locale: undefined,\n format: undefined,\n dayStart: 0,\n dayEnd: 24,\n slotDuration: 15,\n scrollTime: '08:00',\n hideCurrentTime: false,\n monthEventLimit: 3,\n readonly: false,\n disabled: false,\n creatable: false,\n edgeStepDelay: EDGE_STEP_DELAY,\n noEdgeScroll: false,\n label: undefined,\n})\n\n/*\n * The wrapper-root pattern: the consumer's class and style stay on the outer box, and\n * everything else — their `id`, their `aria-*` — goes on the region, which is the part\n * that carries the role and is therefore the part they mean.\n */\ndefineOptions({ inheritAttrs: false })\n\n/** Which span the calendar is showing. It opens on the week. */\nconst view = defineModel<CalendarView>('view', { default: 'week' })\n\n// @ssr\n/*\n * The anchor is read from the clock at setup, so the server and the client can in\n * principle disagree across a midnight boundary. That is the trade VDatePicker already\n * makes for the month it opens on, and it is a safer one here: what the anchor decides is\n * a whole WEEK or month, which is the same on both sides for all but a few seconds a day.\n * Today's date, which marks a single column, is a different matter and is read below in\n * `onMounted` where the server cannot see it at all.\n */\n/**\n * The day the calendar is anchored on, as `YYYY-MM-DD`: the view shows the week, month or\n * year holding it. It opens on today.\n */\nconst date = defineModel<string>('date', { default: () => todayISO() })\n/**\n * What is on the calendar, empty to begin with. It is a MODEL rather than a plain prop\n * because dragging and resizing write back to it: the calendar rearranges what it is given\n * and hands the new list back, never mutating the one it received.\n *\n * Opening an event for editing stays with the consumer: this component reads and\n * rearranges, and never creates or deletes: a slot drawn out is reported, not added.\n */\nconst events = defineModel<E[]>('events', { default: () => [] })\n\nconst emit = defineEmits<{\n /** A card was clicked or activated: the cue to open an editor of your own. */\n 'event-activate': [event: E]\n /**\n * An empty part of the grid was activated, at this day and this time. A day of the month\n * view has no hour of its own, and reports the one the time grids start at.\n */\n 'cell-activate': [cell: CalendarCell]\n /**\n * An event was dragged or nudged somewhere else. It carries the event as it now stands and\n * where it came from, so undoing it needs no copy of your own. A gesture that ends where it\n * began reports nothing.\n */\n 'event-move': [event: E, previous: CalendarEventTimes]\n /** An event's end was dragged or nudged, in the same two parts. */\n 'event-resize': [event: E, previous: CalendarEventTimes]\n /**\n * An empty stretch of a day was drawn out, and these are its times. Nothing has been added\n * to `events`: this is the cue to make the event, in your own model or through your own form.\n */\n 'event-create': [times: CalendarEventTimes]\n}>()\n\ndefineSlots<{\n /** Extra controls in the toolbar, between the range and the view menu. */\n actions?(): unknown\n /** The content of one event's card, replacing the title and times. */\n event?(props: CalendarEventSlotProps<E>): unknown\n /** The head of one day column, replacing the weekday and the number. */\n 'day-header'?(props: { iso: string; weekday: string; dayText: string; today: boolean }): unknown\n /** The label beside the band of all-day events. */\n 'all-day-label'?(): unknown\n}>()\n\nconst m = useMessages()\nconst { rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\n\nconst resolvedLocale = useResolvedLocale(() => props.locale)\nconst resolvedFormat = computed(() => props.format ?? hourCycleFor(resolvedLocale.value))\nconst resolvedWeekdays = computed(() =>\n normalizeWeekdays(\n props.weekdays,\n props.firstDayOfWeek ?? firstDayOfWeekFor(resolvedLocale.value),\n ),\n)\n\nconst timeWindow = computed(() => windowOf(props.dayStart, props.dayEnd))\n\nconst days = computed(() =>\n visibleDays(date.value, view.value, resolvedWeekdays.value, props.customDays),\n)\n\n/** Which view is a time grid, and which is a summary. Several things below turn on this. */\nconst isTimeGrid = computed(() => view.value !== 'month' && view.value !== 'year')\n\nconst weeks = computed(() =>\n view.value === 'month' ? monthWeeks(date.value, resolvedWeekdays.value) : [],\n)\n\nconst months = computed(() => (view.value === 'year' ? monthsOfYear(date.value) : []))\n\n// @ssr — today and the time it is now can only be known on the client: the server has no\n// way to tell either where the reader is, and rendering a guess would make the two markups\n// differ. Both stay null until then, and every rule that draws them is written to expect it.\nconst today = ref<string | null>(null)\nconst now = ref<number | null>(null)\n\nconst clock = useTimer()\n\n/**\n * Re-reads the clock, then arms itself for the top of the next minute — so the line moves\n * when the minute does rather than drifting by however long the interval was.\n *\n * TRAP — the floor on the delay is not a nicety. `useTimer` runs a delay of zero or less\n * SYNCHRONOUSLY, by design, so a tick that ever computed one would call itself straight back\n * and go on doing so until the stack gave out. A second is well below the resolution\n * anything here needs and comfortably above the point where that could happen.\n */\nfunction tick() {\n const at = new Date()\n now.value = at.getHours() * 60 + at.getMinutes()\n clock.start(tick, Math.max(1000, (60 - at.getSeconds()) * 1000))\n}\n\n/** Scrolls the time grid on show to `scrollTime`. */\nfunction scrollToStart() {\n const at = minutesOf(props.scrollTime)\n if (at !== null) gridRef.value?.scrollToMinutes(at)\n}\n\nonMounted(() => {\n today.value = todayISO()\n if (!props.hideCurrentTime) tick()\n scrollToStart()\n})\n\n// @core\n// The prop is a switch a consumer may flip after mount: the clock read at mount alone never\n// started when it was turned on later, and went on ticking when it was turned off.\nwatch(\n () => props.hideCurrentTime,\n (hidden) => {\n if (!hidden) return tick()\n clock.cancel()\n now.value = null\n },\n)\n\n/**\n * The grid, named by what it exposes rather than by `InstanceType<typeof …>`.\n *\n * TRAP — the usual form does not work here and the error it gives says nothing useful about\n * why. A generic `<script setup>` compiles to a FUNCTION, not to a class, so it has no\n * construct signature for `InstanceType` to read: the message complains that the component\n * \"provides no match for the signature `new (...args: any)`\", pointing at the template.\n * Writing the contract out is also the more honest of the two, since these two methods are\n * the whole of what this component asks of that one.\n */\nconst gridRef = ref<{\n focus(options?: FocusOptions): void\n scrollToMinutes(minutes: number): void\n} | null>(null)\n\n// @core\n// A time grid that appears after mount, a month switched to a week, opens at `scrollTime`\n// too. Post-flush, so the new grid is rendered and measurable when it is asked to scroll.\nwatch(gridRef, scrollToStart, { flush: 'post' })\n\n/** The region, the element a consumer's attributes land on. */\nconst regionEl = ref<HTMLElement | null>(null)\n\n/*\n * Which cell holds the tab stop, kept as two refs because the two kinds of view mean\n * different things by it: a time grid needs a day AND an hour, a month only a day. One ref\n * carrying an hour the month ignores would leave that hour to go stale, and the grid would\n * then reopen on whatever the month happened to leave behind.\n */\nconst focused = ref<FocusedCell>({ iso: date.value, minutes: timeWindow.value.start })\nconst focusedDay = ref(date.value)\n\nconst range = computed(() =>\n visibleRange(date.value, view.value, resolvedWeekdays.value, props.customDays),\n)\n\n/**\n * What the toolbar says the calendar is showing. Each view names itself at the coarsest\n * granularity that still identifies it: a single day says its weekday, a year says nothing\n * but the number.\n */\nconst rangeText = computed(() => {\n const locale = resolvedLocale.value\n if (view.value === 'year')\n return formatDateDisplay(range.value.start, locale, { year: 'numeric' })\n if (view.value === 'month') {\n return formatDateDisplay(range.value.start, locale, { month: 'long', year: 'numeric' })\n }\n if (view.value === 'day') {\n return formatDateDisplay(range.value.start, locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n })\n }\n return formatDisplayRange(range.value.start, range.value.end, locale, {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n })\n})\n\nconst viewLabel = (value: CalendarView) => {\n const words = m.value.calendar\n if (value === 'day') return words.viewDay\n if (value === '4days') return words.view4Days\n if (value === 'week') return words.viewWeek\n if (value === 'month') return words.viewMonth\n if (value === 'year') return words.viewYear\n return words.viewCustom(props.customDays)\n}\n\n/**\n * What the two navigation buttons are called. They name the step rather than saying\n * \"previous\" alone, because the step is what changes from one view to the next and a bare\n * \"previous\" leaves a reader to guess whether it is a day or a year they are about to lose.\n */\nconst stepLabels = computed(() => {\n const words = m.value.calendar\n if (view.value === 'day') return { previous: words.previousDay, next: words.nextDay }\n if (view.value === 'week') return { previous: words.previousWeek, next: words.nextWeek }\n if (view.value === 'month') return { previous: words.previousMonth, next: words.nextMonth }\n if (view.value === 'year') return { previous: words.previousYear, next: words.nextYear }\n return { previous: words.previousPeriod, next: words.nextPeriod }\n})\n\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.calendar.label)\n\n/*\n * The four ways of reaching another period all pass through here, and `disabled` is refused\n * in every one of them rather than on the controls alone: the edge step pages the view from a\n * drag, and the year grid opens a month from a cell, neither of which is a button this\n * component disabled. It is the VDatePicker arrangement, where the guard sits on `goTo`.\n * `onCellActivate` below takes the same guard, for the same reason.\n */\nfunction step(delta: -1 | 1) {\n if (props.disabled) return\n date.value = stepAnchor(date.value, view.value, delta, resolvedWeekdays.value, props.customDays)\n}\n\nfunction goToToday() {\n if (props.disabled) return\n date.value = todayISO()\n}\n\nfunction setView(value: CalendarView) {\n if (props.disabled) return\n view.value = value\n}\n\n/**\n * An empty cell taken up, from either grid. The one place the time a month day stands for is\n * decided: the hour the time grids start at.\n *\n * TRAP — `disabled` is refused HERE, and not by the stylesheet's `pointer-events: none`\n * alone. That rule stops a click, but the grid keeps its tab stop so the agenda stays\n * readable, and Enter on a focused cell reaches this with nothing in its way — a frozen\n * calendar would go on reporting cells to a consumer's form.\n */\nfunction onCellActivate(cell: ActivatedCell) {\n if (props.disabled) return\n emit('cell-activate', {\n date: cell.date,\n time: timeOf(cell.minutes ?? timeWindow.value.start),\n })\n}\n\n/**\n * Going from a summary to the thing it summarises: opening a day from the month view, or a\n * month from the year view.\n *\n * The view only changes if the calendar was offering that one in the first place. A consumer\n * who narrowed `views` to a week and a year has said what their calendar is for, and jumping\n * it into a day view they deliberately left out would be the component overruling them —\n * so the date moves and the view stays, which is still a step towards what was asked for.\n */\nfunction openIn(iso: string, target: CalendarView) {\n if (props.disabled) return\n date.value = iso\n if (props.views.includes(target)) view.value = target\n}\n\n/**\n * What a reader who cannot see the grid is told: the range after every step of a move, and\n * whether the move was taken or given up.\n *\n * The node is rendered from the FIRST paint and left empty, never inserted along with its\n * first message — a live region that appears at the same moment as its text is not announced\n * at all, which is the trap VDataTable's selection count already documents.\n */\nconst { polite: announcement, announce: say } = useLiveAnnouncer()\nconst uid = useId()\nconst hintId = `${uid}-hint`\n\n/*\n * Through the shared announcer, which empties the region and writes on the next tick: the\n * same words twice in a row are no change to a live region, and would not be read again.\n */\nfunction announce(message: string) {\n say(message, false)\n}\n\n/**\n * Writes one event's new times into the model and says what happened.\n *\n * The array is replaced rather than edited in place: a new reference is what wakes a\n * consumer's own watchers, and it is the design system's rule for every v-model holding a\n * list. This runs ONCE, when a gesture ends — never per frame of a drag, which would fill an\n * undo stack with sixty entries a second and send each one back as a render fighting the\n * finger.\n */\nfunction onEventDrop(id: CalendarEventId, times: CalendarEventTimes, kind: 'move' | 'resize') {\n const current = events.value.find((item) => item.id === id)\n if (!current) return\n\n const previous = timesOf(current)\n // A gesture that ends where it began moved nothing, and a consumer's undo stack must not\n // gain an entry for it, nor its watchers a new array.\n if (sameTimes(previous, times)) return\n const next = { ...current, ...times }\n events.value = events.value.map((item) => (item.id === id ? next : item))\n // Written out rather than picking the name with a conditional: the emit signatures are an\n // overload set, and TypeScript resolves one only against a literal.\n if (kind === 'resize') emit('event-resize', next, previous)\n else emit('event-move', next, previous)\n}\n\n/*\n * A drawn slot is REPORTED and never added. What an event is called, what else it carries and\n * whether it exists at all are the consumer's decisions, so the calendar hands over the times\n * and draws nothing once the pointer is let go. The grid only asks when it was told it could\n * create, so `creatable` is not checked a second time here.\n */\nfunction onSlotCreate(times: CalendarEventTimes) {\n emit('event-create', times)\n}\n\ndefineExpose({\n /**\n * Brings the focus into the view: the cell the grid is showing, or the first month heading\n * in the year view.\n */\n focus: (options?: FocusOptions) => gridRef.value?.focus(options),\n /** The region, which is where your `id` and `aria-*` attributes land. */\n el: regionEl,\n /** Goes back to the current day, exactly as the Today button does. */\n today: goToToday,\n /** Moves back one view (a week, a month or a year), exactly as the toolbar's arrow does. */\n previous: () => step(-1),\n /** Moves forward one view. */\n next: () => step(1),\n /** Scrolls the grid so a given `HH:mm` sits at the top of the visible area. */\n scrollTo: (time: string) => {\n const at = minutesOf(time)\n if (at !== null) gridRef.value?.scrollToMinutes(at)\n },\n})\n</script>\n\n<template>\n <div\n class=\"v-calendar\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-disabled=\"disabled ? '' : undefined\"\n >\n <section\n ref=\"regionEl\"\n :aria-roledescription=\"m.calendar.roleDescription\"\n v-bind=\"forwardedAttrs\"\n class=\"v-calendar-region\"\n :aria-label=\"ariaLabel\"\n >\n <div class=\"v-calendar-toolbar\">\n <div class=\"v-calendar-nav\">\n <VIconButton\n :label=\"stepLabels.previous\"\n size=\"sm\"\n :disabled=\"disabled\"\n @click=\"step(-1)\"\n >\n <VIcon :name=\"chevronLeftIcon\" mirrored />\n </VIconButton>\n <VIconButton :label=\"stepLabels.next\" size=\"sm\" :disabled=\"disabled\" @click=\"step(1)\">\n <VIcon :name=\"chevronRightIcon\" mirrored />\n </VIconButton>\n </div>\n\n <VButton variant=\"outline\" tone=\"neutral\" size=\"sm\" :disabled=\"disabled\" @click=\"goToToday\">\n {{ m.calendar.today }}\n </VButton>\n\n <VTypography variant=\"heading-4\" as=\"h2\" class=\"v-calendar-title\">\n {{ rangeText }}\n </VTypography>\n\n <div class=\"v-calendar-actions\">\n <slot name=\"actions\" />\n\n <VMenu v-if=\"views.length > 1\" size=\"sm\" placement=\"bottom-end\" match-trigger>\n <template #trigger=\"{ triggerProps }\">\n <VButton\n variant=\"outline\"\n tone=\"neutral\"\n size=\"sm\"\n :disabled=\"disabled\"\n v-bind=\"triggerProps\"\n :aria-label=\"`${m.calendar.view}: ${viewLabel(view)}`\"\n >\n {{ viewLabel(view) }}\n <VIcon :name=\"arrowDropDownIcon\" />\n </VButton>\n </template>\n <VMenuItem\n v-for=\"value in views\"\n :key=\"value\"\n :label=\"viewLabel(value)\"\n :selected=\"value === view\"\n @select=\"setView(value)\"\n />\n </VMenu>\n </div>\n </div>\n\n <!--\n The public props say what is REFUSED — `readonly`, `hideCurrentTime`,\n `noEdgeScroll` — so that none of them has to be turned off with a binding. The two\n internal grids keep the positive sense they read a dozen times each, and the whole\n of the inversion lives here, at the boundary: flip a sign on one of these lines and\n the grid quietly does the opposite of what was asked, since nothing downstream\n knows which way round the prop was written.\n -->\n <VCalendarTimeGrid\n v-if=\"isTimeGrid\"\n ref=\"gridRef\"\n v-model:focused=\"focused\"\n class=\"v-calendar-view\"\n :days=\"days\"\n :events=\"events\"\n :time-window=\"timeWindow\"\n :slot-duration=\"slotDuration\"\n :locale=\"resolvedLocale\"\n :format=\"resolvedFormat\"\n :today=\"today\"\n :now=\"hideCurrentTime ? null : now\"\n :editable=\"!readonly && !disabled\"\n :disabled=\"disabled\"\n :creatable=\"creatable && !disabled\"\n :hint-id=\"hintId\"\n :edge-step-delay=\"edgeStepDelay\"\n :auto-scroll=\"!noEdgeScroll\"\n :label=\"rangeText\"\n @cell-activate=\"onCellActivate\"\n @event-activate=\"emit('event-activate', $event)\"\n @event-drop=\"onEventDrop\"\n @slot-create=\"onSlotCreate\"\n @announce=\"announce\"\n @step=\"step\"\n >\n <template v-if=\"$slots.event\" #event=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['day-header']\" #day-header=\"slotProps\">\n <slot name=\"day-header\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['all-day-label']\" #all-day-label>\n <slot name=\"all-day-label\" />\n </template>\n </VCalendarTimeGrid>\n\n <VCalendarMonth\n v-else-if=\"view === 'month'\"\n ref=\"gridRef\"\n v-model:focused=\"focusedDay\"\n class=\"v-calendar-view\"\n :weeks=\"weeks\"\n :events=\"events\"\n :locale=\"resolvedLocale\"\n :format=\"resolvedFormat\"\n :today=\"today\"\n :month-event-limit=\"monthEventLimit\"\n :editable=\"!readonly && !disabled\"\n :disabled=\"disabled\"\n :hint-id=\"hintId\"\n :edge-step-delay=\"edgeStepDelay\"\n :label=\"rangeText\"\n @day-activate=\"openIn($event, 'day')\"\n @cell-activate=\"onCellActivate\"\n @event-activate=\"emit('event-activate', $event)\"\n @event-drop=\"onEventDrop\"\n @announce=\"announce\"\n @step=\"step\"\n >\n <template v-if=\"$slots.event\" #event=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarMonth>\n\n <VCalendarYear\n v-else\n ref=\"gridRef\"\n class=\"v-calendar-view\"\n :disabled=\"disabled\"\n :months=\"months\"\n :events=\"events\"\n :locale=\"resolvedLocale\"\n :weekdays=\"resolvedWeekdays\"\n :today=\"today\"\n :label=\"rangeText\"\n @month-activate=\"openIn($event, 'month')\"\n />\n\n <!--\n Both of these are rendered from the first paint and never conditionally.\n\n The live region has to EXIST before it has anything to say: one inserted along with\n its first message is not announced at all. And the hint is what every card points at\n with `aria-describedby`, so one node serves the whole calendar rather than repeating\n the same sentence on each of a hundred events.\n -->\n <div class=\"v-visually-hidden\" role=\"status\" aria-live=\"polite\">{{ announcement }}</div>\n <span :id=\"hintId\" class=\"v-visually-hidden\">{{ m.calendar.eventHint }}</span>\n </section>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar {\n display: flex;\n block-size: 100%;\n min-block-size: 0;\n font-family: var(--vectis-text-family);\n color: var(--vectis-color-text);\n }\n\n /* A frozen calendar greys out through the colour tokens and never through opacity, the\n design system rule: what a reader keeps here is the agenda itself, which has to stay\n readable. The cards are real disabled buttons, so they grey themselves. */\n .v-calendar[data-disabled] {\n color: var(--vectis-color-text-subtle);\n cursor: not-allowed;\n }\n\n .v-calendar[data-disabled] .v-calendar-view {\n pointer-events: none;\n }\n\n /*\n * The height is settled in CSS rather than measured, the VDataTable idiom: the region is\n * a flex column at full height, the toolbar refuses to grow and the grid takes what is\n * left. `min-block-size: 0` on the grid is what lets it shrink below its content and\n * therefore scroll — without it the automatic minimum of a flex item would hold it at its\n * full 24-hour height and the whole page would scroll instead.\n *\n * All of it is a no-op when the parent's height is auto, where the percentage falls back\n * to auto and the calendar is simply as tall as its content. That is what makes an opt-in\n * prop unnecessary.\n */\n .v-calendar-region {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-inline-size: 0;\n min-block-size: 0;\n }\n\n .v-calendar-toolbar {\n display: flex;\n flex: none;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--vectis-space-2);\n padding-block-end: var(--vectis-space-3);\n }\n\n .v-calendar-nav {\n display: flex;\n align-items: center;\n gap: var(--vectis-space-1);\n }\n\n .v-calendar-title {\n /* It may be long — a full weekday and month in the day view — so it is the part that\n gives way first, and the controls keep their size. */\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .v-calendar-actions {\n display: flex;\n align-items: center;\n gap: var(--vectis-space-2);\n /* The two siblings are pushed apart from here rather than by a `justify-content` on the\n toolbar, which would space every control equally instead of grouping them. */\n margin-inline-start: auto;\n }\n\n /*\n * The view on show, whichever it is: the one box that scrolls. The views inherit the family\n * and the colour from `.v-calendar` and declare neither again.\n */\n .v-calendar-view {\n flex: 1;\n overflow: auto;\n block-size: 100%;\n min-block-size: 0;\n border: 1px solid var(--vectis-color-border);\n border-radius: var(--vectis-radius-surface);\n }\n\n /*\n * What the three views share lives HERE rather than in each of their sheets. They are only\n * ever rendered by this component, so this sheet ships wherever they do — the\n * VCarousel/VCarouselItem arrangement. Every rule a view writes against one of these classes\n * is either disjoint from it or more specific, never tied: a tie between two sheets would be\n * settled by the consumer's bundler.\n */\n\n /*\n * A drag currently held off the calendar. `not-allowed` is the word the library already uses\n * for \"you cannot do this\", on every disabled control.\n *\n * BEST-EFFORT, and never the signal: while a pointer is captured the cursor is resolved\n * against the capture target rather than against whatever sits under the pointer, and engines\n * differ on it. What carries the message is the card's own paint.\n */\n .v-calendar-view[data-outside] {\n cursor: not-allowed;\n }\n\n .v-calendar-weekday {\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-overline-size);\n font-weight: var(--vectis-text-overline-weight);\n letter-spacing: var(--vectis-text-overline-tracking);\n text-transform: uppercase;\n }\n\n /*\n * Today's date, marked on the number the way a calendar marks a date rather than a column.\n * Semibold here marks a state, not a type role. Written at (0,2,0) so it wins over each view's\n * own (0,1,0) rule for the element it lands on.\n */\n .v-calendar-view .v-calendar-today {\n background: var(--vectis-color-accent);\n color: var(--vectis-color-text-on-accent);\n font-weight: var(--vectis-font-weight-semibold);\n }\n\n /*\n * A cell of either grid: ruled on two sides, the other two drawn by its neighbours. It keeps\n * the default cursor: the pointer hand belongs to what can be opened, the events, and a grid\n * of hands would say every square of the week is a link.\n */\n .v-calendar-cell {\n border-block-start: 1px solid var(--vectis-color-border);\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-cell:first-child {\n border-inline-start: none;\n }\n\n .v-calendar-cell:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n /* Drawn inwards: the view is a scrolling box, and an outward ring on a cell at the edge\n would be cropped by it. */\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n /* The plain buttons the month and year views draw: a day number, the \"+2 more\", a month. */\n .v-calendar-button {\n /* A button carries a border and a background from the browser, which around a round day\n number read as a stray ring. Neither is optional. */\n border: none;\n background: none;\n font-family: inherit;\n cursor: pointer;\n transition: background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-calendar-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 /*\n * The strip that lights up while a drag rests against an edge, counting down to turn the\n * page. It is what stops the paging being a surprise.\n *\n * Drawn INSIDE the box the cells occupy rather than on the scroller, because a scroller's\n * absolutely positioned child is placed against its content and scrolls away with it. That\n * box has to be positioned, which each view's own sheet sees to. An absolutely positioned\n * pseudo-element is not a grid item, so it disturbs no column.\n *\n * Its width is `--vectis-control-size-calendar-edge`, whose twin is `EDGE_BAND` in\n * `edgeStep.ts` — the JavaScript that decides where the countdown actually starts. Neither\n * may move without the other. The layer, 2, is the time grid's stacking table.\n */\n .v-calendar-edge-cue[data-edge]::after {\n content: '';\n position: absolute;\n inset-block: 0;\n inline-size: var(--vectis-control-size-calendar-edge);\n background: var(--vectis-color-accent-surface);\n z-index: 2;\n pointer-events: none;\n }\n\n .v-calendar-edge-cue[data-edge='start']::after {\n inset-inline-start: 0;\n }\n\n .v-calendar-edge-cue[data-edge='end']::after {\n inset-inline-end: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-calendar-button {\n transition: none;\n }\n }\n\n /*\n * Windows forced colors flattens every background to Canvas, and today and the paging strip\n * are drawn with nothing else. Today takes the system selection pair, the one every other\n * \"current\" mark in the library takes, and the strip takes Highlight.\n */\n @media (forced-colors: active) {\n .v-calendar-view .v-calendar-today {\n forced-color-adjust: none;\n background: Highlight;\n color: HighlightText;\n }\n\n .v-calendar-edge-cue[data-edge]::after {\n forced-color-adjust: none;\n background: Highlight;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VCalendar.js","names":[],"sources":["../../../src/components/VCalendar/VCalendar.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @a11y @ssr @core\n/**\n * Calendar layout is pure; JavaScript supplies grid navigation, editing gestures and\n * announcements because HTML has no native agenda widget. Editing forms remain consumer-owned.\n */\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { arrow_drop_down as arrowDropDownIcon } from '../VIcon/icons/arrow_drop_down'\nimport { chevron_left as chevronLeftIcon } from '../VIcon/icons/chevron_left'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTimer } from '../../composables/useTimer'\nimport { firstDayOfWeekFor, formatDateDisplay, formatDisplayRange } from '../../utils/date'\nimport { hourCycleFor, minutesOf } from '../../utils/time'\n\nimport { useMessages, useResolvedLocale } from '../../i18n/state'\n\nimport VCalendarMonth from './VCalendarMonth.vue'\nimport VCalendarTimeGrid, { type FocusedCell } from './VCalendarTimeGrid.vue'\nimport VCalendarYear from './VCalendarYear.vue'\nimport { EDGE_STEP_DELAY } from './edgeStep'\nimport {\n monthWeeks,\n monthsOfYear,\n normalizeWeekdays,\n stepAnchor,\n sameTimes,\n timeOf,\n timesOf,\n todayISO,\n visibleDays,\n visibleRange,\n windowOf,\n} from './layout'\nimport type {\n ActivatedCell,\n CalendarCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n CalendarView,\n} from './types'\n\nexport interface CalendarProps {\n /**\n * Which views the menu offers, in the order it lists them. Narrowing it is how a\n * calendar that only ever shows weeks stops offering anything else.\n */\n views?: CalendarView[]\n /** How many days the custom view shows, and how far Previous and Next step in it. */\n customDays?: number\n /**\n * Which weekdays are on show, as numbers from 0 for Sunday. The ORDER matters as well: the\n * first entry is the day a week starts on, and it wins over `firstDayOfWeek`.\n */\n weekdays?: number[]\n /**\n * The day a week starts on, from 0 for Sunday, when `weekdays` is not given. Left out,\n * the locale decides.\n */\n firstDayOfWeek?: number\n /** The language the days, months and times are written in. Falls back to the global one. */\n locale?: string\n /**\n * Whether times are shown on a twelve or twenty-four hour clock. Left out, the reader's\n * language decides. It is the same prop, under the same name, as VTimePicker's.\n */\n format?: CalendarFormat\n /** The hour the grid starts at, from 0. */\n dayStart?: number\n /** The hour it ends at, up to 24. */\n dayEnd?: number\n /**\n * The step everything snaps to, in minutes: how far a nudge moves an event, and the unit a\n * slot is drawn out in.\n */\n slotDuration?: number\n /** Where the grid is scrolled to when it first appears, so the working day is in view. */\n scrollTime?: string\n /**\n * Leaves out the line drawn across today's column at the time it is now, and the dot on\n * its leading edge. Left in, it ticks once a minute while the calendar is on screen.\n */\n hideCurrentTime?: boolean\n /** How many events a day of the month view shows before it starts counting the rest. */\n monthEventLimit?: number\n /**\n * Stops events being moved and stretched, by dragging or with the keyboard. They\n * stay readable and clickable, and nothing else.\n */\n readonly?: boolean\n /**\n * Freezes the whole calendar: nothing can be moved, created or opened, no other period can be\n * reached, and everything greys out through the colour tokens. That is what separates this\n * from `readonly`, where only the editing stops.\n */\n disabled?: boolean\n /**\n * Lets an empty stretch of a time grid be drawn out with the pointer, up or down from the\n * slot pressed.\n */\n creatable?: boolean\n /**\n * How long a dragged event has to rest against the side of the calendar before the view turns\n * to the previous or next period, in milliseconds.\n */\n edgeStepDelay?: number\n /** Stops dragging near the top or bottom of a time grid from scrolling it. */\n noEdgeScroll?: boolean\n /** What the calendar is called, for anyone who cannot see it. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<CalendarProps>(), {\n views: () => ['day', '4days', 'week'],\n customDays: 4,\n weekdays: undefined,\n firstDayOfWeek: undefined,\n locale: undefined,\n format: undefined,\n dayStart: 0,\n dayEnd: 24,\n slotDuration: 15,\n scrollTime: '08:00',\n hideCurrentTime: false,\n monthEventLimit: 3,\n readonly: false,\n disabled: false,\n creatable: false,\n edgeStepDelay: EDGE_STEP_DELAY,\n noEdgeScroll: false,\n label: undefined,\n})\n\n/*\n * The wrapper-root pattern: the consumer's class and style stay on the outer box, and\n * everything else; their `id`, their `aria-*`; goes on the region, which is the part that\n * carries the role and is therefore the part they mean.\n */\ndefineOptions({ inheritAttrs: false })\n\n/** Which span the calendar is showing. It opens on the week. */\nconst view = defineModel<CalendarView>('view', { default: 'week' })\n\n// @ssr\n/*\n * Today's date, which marks a single column, is a different matter and is read below in\n * `onMounted` where the server cannot see it at all.\n */\n/**\n * The day the calendar is anchored on, as `YYYY-MM-DD`: the view shows the week, month or\n * year holding it. It opens on today.\n */\nconst date = defineModel<string>('date', { default: () => todayISO() })\n/** What is on the calendar, empty to begin with. */\nconst events = defineModel<E[]>('events', { default: () => [] })\n\nconst emit = defineEmits<{\n /** A card was clicked or activated: the cue to open an editor of your own. */\n 'event-activate': [event: E]\n /**\n * An empty part of the grid was activated, at this day and this time. A day of the month\n * view has no hour of its own, and reports the one the time grids start at.\n */\n 'cell-activate': [cell: CalendarCell]\n /** An event was dragged or nudged somewhere else. */\n 'event-move': [event: E, previous: CalendarEventTimes]\n /** An event's end was dragged or nudged, in the same two parts. */\n 'event-resize': [event: E, previous: CalendarEventTimes]\n /**\n * An empty stretch of a day was drawn out, and these are its times. Nothing has been added\n * to `events`: this is the cue to make the event, in your own model or through your own form.\n */\n 'event-create': [times: CalendarEventTimes]\n}>()\n\ndefineSlots<{\n /** Extra controls in the toolbar, between the range and the view menu. */\n actions?(): unknown\n /** The content of one event's card, replacing the title and times. */\n event?(props: CalendarEventSlotProps<E>): unknown\n /** The head of one day column, replacing the weekday and the number. */\n 'day-header'?(props: { iso: string; weekday: string; dayText: string; today: boolean }): unknown\n /** The label beside the band of all-day events. */\n 'all-day-label'?(): unknown\n}>()\n\nconst m = useMessages()\nconst { rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\n\nconst resolvedLocale = useResolvedLocale(() => props.locale)\nconst resolvedFormat = computed(() => props.format ?? hourCycleFor(resolvedLocale.value))\nconst resolvedWeekdays = computed(() =>\n normalizeWeekdays(\n props.weekdays,\n props.firstDayOfWeek ?? firstDayOfWeekFor(resolvedLocale.value),\n ),\n)\n\nconst timeWindow = computed(() => windowOf(props.dayStart, props.dayEnd))\n\nconst days = computed(() =>\n visibleDays(date.value, view.value, resolvedWeekdays.value, props.customDays),\n)\n\n/** Which view is a time grid, and which is a summary. Several things below turn on this. */\nconst isTimeGrid = computed(() => view.value !== 'month' && view.value !== 'year')\n\nconst weeks = computed(() =>\n view.value === 'month' ? monthWeeks(date.value, resolvedWeekdays.value) : [],\n)\n\nconst months = computed(() => (view.value === 'year' ? monthsOfYear(date.value) : []))\n\n// @ssr\n// Way to tell either where the reader is, and rendering a guess would make the two markups\n// differ. Both stay null until then, and every rule that draws them is written to expect it.\nconst today = ref<string | null>(null)\nconst now = ref<number | null>(null)\n\nconst clock = useTimer()\n\n/**\n * The floor on the delay is not a nicety. `useTimer` runs a delay of zero or less\n * synchronously, by design, so a tick that ever computed one would call itself straight back\n * and go on doing so until the stack gave out.\n */\nfunction tick() {\n const at = new Date()\n now.value = at.getHours() * 60 + at.getMinutes()\n clock.start(tick, Math.max(1000, (60 - at.getSeconds()) * 1000))\n}\n\n/** Scrolls the time grid on show to `scrollTime`. */\nfunction scrollToStart() {\n const at = minutesOf(props.scrollTime)\n if (at !== null) gridRef.value?.scrollToMinutes(at)\n}\n\nonMounted(() => {\n today.value = todayISO()\n if (!props.hideCurrentTime) tick()\n scrollToStart()\n})\n\n// @core\n// Watch the visibility prop so toggling the current-time line starts or stops its timer after\n// mount.\nwatch(\n () => props.hideCurrentTime,\n (hidden) => {\n if (!hidden) return tick()\n clock.cancel()\n now.value = null\n },\n)\n\n/**\n * The usual form does not work here and the error it gives says nothing useful about why. A\n * generic `<script setup>` compiles to a FUNCTION, not to a class, so it has no construct\n * signature for `InstanceType` to read: the message complains that the component \"provides no\n * match for the signature `new (...args: any)`\", pointing at the template.\n */\nconst gridRef = ref<{\n focus(options?: FocusOptions): void\n scrollToMinutes(minutes: number): void\n} | null>(null)\n\n// @core\n// A time grid that appears after mount, a month switched to a week, opens at `scrollTime`\n// too. Post-flush, so the new grid is rendered and measurable when it is asked to scroll.\nwatch(gridRef, scrollToStart, { flush: 'post' })\n\n/** The region, the element a consumer's attributes land on. */\nconst regionEl = ref<HTMLElement | null>(null)\n\n/*\n * Which cell holds the tab stop, kept as two refs because the two kinds of view mean different\n * things by it: a time grid needs a day and an hour, a month only a day. One ref carrying an\n * hour the month ignores would leave that hour to go stale, and the grid would then reopen on\n * whatever the month happened to leave behind.\n */\nconst focused = ref<FocusedCell>({ iso: date.value, minutes: timeWindow.value.start })\nconst focusedDay = ref(date.value)\n\nconst range = computed(() =>\n visibleRange(date.value, view.value, resolvedWeekdays.value, props.customDays),\n)\n\n/**\n * What the toolbar says the calendar is showing. Each view names itself at the coarsest\n * granularity that still identifies it: a single day says its weekday, a year says nothing\n * but the number.\n */\nconst rangeText = computed(() => {\n const locale = resolvedLocale.value\n if (view.value === 'year')\n return formatDateDisplay(range.value.start, locale, { year: 'numeric' })\n if (view.value === 'month') {\n return formatDateDisplay(range.value.start, locale, { month: 'long', year: 'numeric' })\n }\n if (view.value === 'day') {\n return formatDateDisplay(range.value.start, locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n })\n }\n return formatDisplayRange(range.value.start, range.value.end, locale, {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n })\n})\n\nconst viewLabel = (value: CalendarView) => {\n const words = m.value.calendar\n if (value === 'day') return words.viewDay\n if (value === '4days') return words.view4Days\n if (value === 'week') return words.viewWeek\n if (value === 'month') return words.viewMonth\n if (value === 'year') return words.viewYear\n return words.viewCustom(props.customDays)\n}\n\n/** What the two navigation buttons are called. */\nconst stepLabels = computed(() => {\n const words = m.value.calendar\n if (view.value === 'day') return { previous: words.previousDay, next: words.nextDay }\n if (view.value === 'week') return { previous: words.previousWeek, next: words.nextWeek }\n if (view.value === 'month') return { previous: words.previousMonth, next: words.nextMonth }\n if (view.value === 'year') return { previous: words.previousYear, next: words.nextYear }\n return { previous: words.previousPeriod, next: words.nextPeriod }\n})\n\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.calendar.label)\n\n/*\n * The four ways of reaching another period all pass through here, and `disabled` is refused in\n * every one of them rather than on the controls alone: the edge step pages the view from a\n * drag, and the year grid opens a month from a cell, neither of which is a button this\n * component disabled. It is the VDatePicker arrangement, where the guard sits on `goTo`.\n */\nfunction step(delta: -1 | 1) {\n if (props.disabled) return\n date.value = stepAnchor(date.value, view.value, delta, resolvedWeekdays.value, props.customDays)\n}\n\nfunction goToToday() {\n if (props.disabled) return\n date.value = todayISO()\n}\n\nfunction setView(value: CalendarView) {\n if (props.disabled) return\n view.value = value\n}\n\n/**\n * `disabled` is refused here, and not by the stylesheet's `pointer-events: none` alone. That\n * rule stops a click, but the grid keeps its tab stop so the agenda stays readable, and Enter\n * on a focused cell reaches this with nothing in its way; a frozen calendar would go on\n * reporting cells to a consumer's form.\n */\nfunction onCellActivate(cell: ActivatedCell) {\n if (props.disabled) return\n emit('cell-activate', {\n date: cell.date,\n time: timeOf(cell.minutes ?? timeWindow.value.start),\n })\n}\n\n/**\n * Drill into an allowed detail view; if views excludes it, update the date while preserving the\n * current view.\n */\nfunction openIn(iso: string, target: CalendarView) {\n if (props.disabled) return\n date.value = iso\n if (props.views.includes(target)) view.value = target\n}\n\n/**\n * What a reader who cannot see the grid is told: the range after every step of a move, and\n * whether the move was taken or given up.\n */\nconst { polite: announcement, announce: say } = useLiveAnnouncer()\nconst uid = useId()\nconst hintId = `${uid}-hint`\n\n/*\n * Through the shared announcer, which empties the region and writes on the next tick: the\n * same words twice in a row are no change to a live region, and would not be read again.\n */\nfunction announce(message: string) {\n say(message, false)\n}\n\n/** Writes one event's new times into the model and says what happened. */\nfunction onEventDrop(id: CalendarEventId, times: CalendarEventTimes, kind: 'move' | 'resize') {\n const current = events.value.find((item) => item.id === id)\n if (!current) return\n\n const previous = timesOf(current)\n // A gesture that ends where it began moved nothing, and a consumer's undo stack must not\n // gain an entry for it, nor its watchers a new array.\n if (sameTimes(previous, times)) return\n const next = { ...current, ...times }\n events.value = events.value.map((item) => (item.id === id ? next : item))\n // Written out rather than picking the name with a conditional: the emit signatures are an\n // overload set, and TypeScript resolves one only against a literal.\n if (kind === 'resize') emit('event-resize', next, previous)\n else emit('event-move', next, previous)\n}\n\n/*\n * A drawn slot is REPORTED and never added. What an event is called, what else it carries and\n * whether it exists at all are the consumer's decisions, so the calendar hands over the times\n * and draws nothing once the pointer is let go.\n */\nfunction onSlotCreate(times: CalendarEventTimes) {\n emit('event-create', times)\n}\n\ndefineExpose({\n /**\n * Brings the focus into the view: the cell the grid is showing, or the first month heading\n * in the year view.\n */\n focus: (options?: FocusOptions) => gridRef.value?.focus(options),\n /** The region, which is where your `id` and `aria-*` attributes land. */\n el: regionEl,\n /** Goes back to the current day, exactly as the Today button does. */\n today: goToToday,\n /** Moves back one view (a week, a month or a year), exactly as the toolbar's arrow does. */\n previous: () => step(-1),\n /** Moves forward one view. */\n next: () => step(1),\n /** Scrolls the grid so a given `HH:mm` sits at the top of the visible area. */\n scrollTo: (time: string) => {\n const at = minutesOf(time)\n if (at !== null) gridRef.value?.scrollToMinutes(at)\n },\n})\n</script>\n\n<template>\n <div\n class=\"v-calendar\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-disabled=\"disabled ? '' : undefined\"\n >\n <section\n ref=\"regionEl\"\n :aria-roledescription=\"m.calendar.roleDescription\"\n v-bind=\"forwardedAttrs\"\n class=\"v-calendar-region\"\n :aria-label=\"ariaLabel\"\n >\n <div class=\"v-calendar-toolbar\">\n <div class=\"v-calendar-nav\">\n <VIconButton\n :label=\"stepLabels.previous\"\n size=\"sm\"\n :disabled=\"disabled\"\n @click=\"step(-1)\"\n >\n <VIcon :name=\"chevronLeftIcon\" mirrored />\n </VIconButton>\n <VIconButton :label=\"stepLabels.next\" size=\"sm\" :disabled=\"disabled\" @click=\"step(1)\">\n <VIcon :name=\"chevronRightIcon\" mirrored />\n </VIconButton>\n </div>\n\n <VButton variant=\"outline\" tone=\"neutral\" size=\"sm\" :disabled=\"disabled\" @click=\"goToToday\">\n {{ m.calendar.today }}\n </VButton>\n\n <VTypography variant=\"heading-4\" as=\"h2\" class=\"v-calendar-title\">\n {{ rangeText }}\n </VTypography>\n\n <div class=\"v-calendar-actions\">\n <slot name=\"actions\" />\n\n <VMenu v-if=\"views.length > 1\" size=\"sm\" placement=\"bottom-end\" match-trigger>\n <template #trigger=\"{ triggerProps }\">\n <VButton\n variant=\"outline\"\n tone=\"neutral\"\n size=\"sm\"\n :disabled=\"disabled\"\n v-bind=\"triggerProps\"\n :aria-label=\"`${m.calendar.view}: ${viewLabel(view)}`\"\n >\n {{ viewLabel(view) }}\n <VIcon :name=\"arrowDropDownIcon\" />\n </VButton>\n </template>\n <VMenuItem\n v-for=\"value in views\"\n :key=\"value\"\n :label=\"viewLabel(value)\"\n :selected=\"value === view\"\n @select=\"setView(value)\"\n />\n </VMenu>\n </div>\n </div>\n\n <!--\n Invert opt-out public props once at the internal-grid boundary; internal view contracts\n use positive booleans.\n -->\n <VCalendarTimeGrid\n v-if=\"isTimeGrid\"\n ref=\"gridRef\"\n v-model:focused=\"focused\"\n class=\"v-calendar-view\"\n :days=\"days\"\n :events=\"events\"\n :time-window=\"timeWindow\"\n :slot-duration=\"slotDuration\"\n :locale=\"resolvedLocale\"\n :format=\"resolvedFormat\"\n :today=\"today\"\n :now=\"hideCurrentTime ? null : now\"\n :editable=\"!readonly && !disabled\"\n :disabled=\"disabled\"\n :creatable=\"creatable && !disabled\"\n :hint-id=\"hintId\"\n :edge-step-delay=\"edgeStepDelay\"\n :auto-scroll=\"!noEdgeScroll\"\n :label=\"rangeText\"\n @cell-activate=\"onCellActivate\"\n @event-activate=\"emit('event-activate', $event)\"\n @event-drop=\"onEventDrop\"\n @slot-create=\"onSlotCreate\"\n @announce=\"announce\"\n @step=\"step\"\n >\n <template v-if=\"$slots.event\" #event=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['day-header']\" #day-header=\"slotProps\">\n <slot name=\"day-header\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots['all-day-label']\" #all-day-label>\n <slot name=\"all-day-label\" />\n </template>\n </VCalendarTimeGrid>\n\n <VCalendarMonth\n v-else-if=\"view === 'month'\"\n ref=\"gridRef\"\n v-model:focused=\"focusedDay\"\n class=\"v-calendar-view\"\n :weeks=\"weeks\"\n :events=\"events\"\n :locale=\"resolvedLocale\"\n :format=\"resolvedFormat\"\n :today=\"today\"\n :month-event-limit=\"monthEventLimit\"\n :editable=\"!readonly && !disabled\"\n :disabled=\"disabled\"\n :hint-id=\"hintId\"\n :edge-step-delay=\"edgeStepDelay\"\n :label=\"rangeText\"\n @day-activate=\"openIn($event, 'day')\"\n @cell-activate=\"onCellActivate\"\n @event-activate=\"emit('event-activate', $event)\"\n @event-drop=\"onEventDrop\"\n @announce=\"announce\"\n @step=\"step\"\n >\n <template v-if=\"$slots.event\" #event=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarMonth>\n\n <VCalendarYear\n v-else\n ref=\"gridRef\"\n class=\"v-calendar-view\"\n :disabled=\"disabled\"\n :months=\"months\"\n :events=\"events\"\n :locale=\"resolvedLocale\"\n :weekdays=\"resolvedWeekdays\"\n :today=\"today\"\n :label=\"rangeText\"\n @month-activate=\"openIn($event, 'month')\"\n />\n\n <!--\n Both of these are rendered from the first paint and never conditionally. The live region\n has to EXIST before it has anything to say: one inserted along with its first message is\n not announced at all.\n -->\n <div class=\"v-visually-hidden\" role=\"status\" aria-live=\"polite\">{{ announcement }}</div>\n <span :id=\"hintId\" class=\"v-visually-hidden\">{{ m.calendar.eventHint }}</span>\n </section>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar {\n display: flex;\n block-size: 100%;\n min-block-size: 0;\n font-family: var(--vectis-text-family);\n color: var(--vectis-color-text);\n }\n\n /* A frozen calendar greys out through the colour tokens and never through opacity, the\n design system rule: what a reader keeps here is the agenda itself, which has to stay\n readable. The cards are real disabled buttons, so they grey themselves. */\n .v-calendar[data-disabled] {\n color: var(--vectis-color-text-subtle);\n cursor: not-allowed;\n }\n\n .v-calendar[data-disabled] .v-calendar-view {\n pointer-events: none;\n }\n\n /*\n * `min-block-size: 0` on the grid is what lets it shrink below its content and therefore\n * scroll; without it the automatic minimum of a flex item would hold it at its full 24-hour\n * height and the whole page would scroll instead. All of it is a no-op when the parent's\n * height is auto, where the percentage falls back to auto and the calendar is simply as tall\n * as its content.\n */\n .v-calendar-region {\n display: flex;\n flex: 1;\n flex-direction: column;\n min-inline-size: 0;\n min-block-size: 0;\n }\n\n .v-calendar-toolbar {\n display: flex;\n flex: none;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--vectis-space-2);\n padding-block-end: var(--vectis-space-3);\n }\n\n .v-calendar-nav {\n display: flex;\n align-items: center;\n gap: var(--vectis-space-1);\n }\n\n .v-calendar-title {\n /*\n * It may be long; a full weekday and month in the day view; so it is the part that gives\n * way first, and the controls keep their size.\n */\n min-inline-size: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .v-calendar-actions {\n display: flex;\n align-items: center;\n gap: var(--vectis-space-2);\n /* The two siblings are pushed apart from here rather than by a `justify-content` on the\n toolbar, which would space every control equally instead of grouping them. */\n margin-inline-start: auto;\n }\n\n /*\n * The view on show, whichever it is: the one box that scrolls. The views inherit the family\n * and the colour from `.v-calendar` and declare neither again.\n */\n .v-calendar-view {\n flex: 1;\n overflow: auto;\n block-size: 100%;\n min-block-size: 0;\n border: 1px solid var(--vectis-color-border);\n border-radius: var(--vectis-radius-surface);\n }\n\n /*\n * Every rule a view writes against one of these classes is either disjoint from it or more\n * specific, never tied: a tie between two sheets would be settled by the consumer's bundler.\n */\n\n /*\n * `not-allowed` is the word the library already uses for \"you cannot do this\", on every\n * disabled control. BEST-EFFORT, and never the signal: while a pointer is captured the cursor\n * is resolved against the capture target rather than against whatever sits under the pointer,\n * and engines differ on it.\n */\n .v-calendar-view[data-outside] {\n cursor: not-allowed;\n }\n\n .v-calendar-weekday {\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-overline-size);\n font-weight: var(--vectis-text-overline-weight);\n letter-spacing: var(--vectis-text-overline-tracking);\n text-transform: uppercase;\n }\n\n /*\n * Today's date, marked on the number the way a calendar marks a date rather than a column.\n * Semibold here marks a state, not a type role.\n */\n .v-calendar-view .v-calendar-today {\n background: var(--vectis-color-accent);\n color: var(--vectis-color-text-on-accent);\n font-weight: var(--vectis-font-weight-semibold);\n }\n\n /*\n * A cell of either grid: ruled on two sides, the other two drawn by its neighbours. It keeps\n * the default cursor: the pointer hand belongs to what can be opened, the events, and a grid\n * of hands would say every square of the week is a link.\n */\n .v-calendar-cell {\n border-block-start: 1px solid var(--vectis-color-border);\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-cell:first-child {\n border-inline-start: none;\n }\n\n .v-calendar-cell:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n /* Drawn inwards: the view is a scrolling box, and an outward ring on a cell at the edge\n would be cropped by it. */\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-calendar-button {\n /* A button carries a border and a background from the browser, which around a round day\n number read as a stray ring. Neither is optional. */\n border: none;\n background: none;\n font-family: inherit;\n cursor: pointer;\n transition: background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-calendar-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 /*\n * Drawn INSIDE the box the cells occupy rather than on the scroller, because a scroller's\n * absolutely positioned child is placed against its content and scrolls away with it. That\n * box has to be positioned, which each view's own sheet sees to.\n */\n .v-calendar-edge-cue[data-edge]::after {\n content: '';\n position: absolute;\n inset-block: 0;\n inline-size: var(--vectis-control-size-calendar-edge);\n background: var(--vectis-color-accent-surface);\n z-index: 2;\n pointer-events: none;\n }\n\n .v-calendar-edge-cue[data-edge='start']::after {\n inset-inline-start: 0;\n }\n\n .v-calendar-edge-cue[data-edge='end']::after {\n inset-inline-end: 0;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-calendar-button {\n transition: none;\n }\n }\n\n /*\n * Windows forced colors flattens every background to Canvas, and today and the paging strip\n * are drawn with nothing else. Today takes the system selection pair, the one every other\n * \"current\" mark in the library takes, and the strip takes Highlight.\n */\n @media (forced-colors: active) {\n .v-calendar-view .v-calendar-today {\n forced-color-adjust: none;\n background: Highlight;\n color: HighlightText;\n }\n\n .v-calendar-edge-cue[data-edge]::after {\n forced-color-adjust: none;\n background: Highlight;\n }\n }\n}\n</style>\n"],"mappings":""}
|