vectis-ui 0.10.1 → 0.10.2
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":"VSnackbar.js","names":[],"sources":["../../../src/components/VSnackbar/VSnackbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Where confirmations appear: mounted ONCE at the root, after which every `snackbar()` call\n * shows up here.\n *\n * A confirmation answers something the reader just did — \"Message deleted\" — usually with\n * one button to take it back. Everything that separates it from a notification follows from\n * ONE idea: only the LAST action is worth offering to undo. Hence a single bar at a time (a\n * new one replaces it), hence no close cross (a bar that tidies itself must not ask the\n * reader to), hence the bottom edge alone and a shorter delay.\n *\n * That \"one at a time\" is also why this is a single SFC where the notifications need two:\n * one container, one card, and no book-keeping to tell several countdowns apart.\n *\n * The JS covers three things the platform does not: keeping the state and the container in\n * step, the popover being imperative; the auto-dismiss countdown; and HOLDING that countdown\n * while the pointer rests on the bar OR the keyboard is inside it, so something that\n * disappears on a clock can be read and acted on (WCAG 2.2.1).\n */\n\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport { useTimer } from '../../composables/useTimer'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useMessages } from '../../i18n/state'\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { current, dismissSnackbar, type SnackbarPlacement } from './state'\n\ninterface SnackbarProps {\n /** Which end of the bottom edge confirmations appear at, unless one of them asks for another. */\n placement?: SnackbarPlacement\n /**\n * How long a confirmation stays, in milliseconds, unless it asks for something else. A\n * confirmation given 0 stays until it is replaced or taken away by hand.\n */\n duration?: number\n /**\n * The word drawn on the single action, when the confirmation does not give one. It is\n * also the button's accessible name, and falls back to the design system dictionary.\n */\n actionText?: string\n /**\n * What screen readers announce for the confirmation area itself, which is a landmark of\n * the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SnackbarProps>(), {\n placement: 'bottom-center',\n /* Shorter than a notification's five seconds. A confirmation is one short sentence\n about something the reader has just done, so they already know what it says. */\n duration: 4000,\n actionText: undefined,\n label: undefined,\n})\n\n// The component renders the host AND its two live regions, so the consumer's attributes are\n// put on the host explicitly rather than left to a root that no longer exists.\ndefineOptions({ inheritAttrs: false })\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.snackbar.label)\n\nconst placement = computed(() => current.value?.placement ?? props.placement)\nconst actionText = computed(\n () => current.value?.actionText ?? props.actionText ?? m.value.snackbar.action,\n)\nconst icon = computed(() => (current.value?.icon ? iconProps(current.value.icon) : undefined))\n\nconst hostEl = ref<HTMLElement | null>(null)\nconst { syncShown, show, hide } = usePopover(hostEl)\nconst { start, cancel } = useTimer()\n\n/* The reasons to hold the countdown, each set by its own pair of events. They are two\n separate flags rather than one counter because they can be true at the same time and\n end independently: a reader tabs to the action button, then moves the mouse over the\n bar, then moves it away — and the bar must not leave while the button still has focus. */\nlet hovered = false\nlet focused = false\n\nfunction arm() {\n const bar = current.value\n if (!bar || hovered || focused) return\n const duration = bar.duration ?? props.duration\n /* GUARD, not a default: `useTimer` runs a delay of 0 SYNCHRONOUSLY, which is the design\n system's convention for \"no deferral at all\". Without this test a confirmation asking\n to be permanent would be taken away in the same tick it was raised. */\n if (duration > 0) start(() => dismissSnackbar(bar.id), duration)\n}\n\n// @core\n/**\n * Brings the page into line with the state: it shows the container when there is a\n * confirmation and hides it when there is none, and restarts the countdown from the top.\n *\n * Restarting unconditionally is what makes a replacement correct — the outgoing bar's\n * countdown must not be allowed to take the incoming one away — and it is safe precisely\n * because this runs only when the bar itself changes.\n *\n * It runs once on mount, which is what makes a confirmation raised before this component\n * existed appear all the same, and after that on every change.\n */\nconst { polite, assertive, announce } = useLiveAnnouncer()\nlet lastAnnounced: number | undefined\n\nfunction sync() {\n cancel()\n // TRAP — with no bar left, nothing can still be hovered or focused, so both flags are\n // dropped here rather than trusted to their events. The action button leaves the page\n // WITH the bar while it holds the focus, and an engine that sends no `focusout` for a\n // removed element would leave `focused` standing: every later confirmation would then\n // stay on screen for good, far from the gesture that caused it.\n if (!current.value) hovered = focused = false\n // TRAP — a REPLACEMENT removes the focused action too (the card is keyed on the bar), and\n // the same engine leaves `focused` standing: the new bar then never left. The toaster's\n // test, where the focus actually is, is the one that holds in both cases.\n else if (!hostEl.value?.contains(document.activeElement)) focused = false\n // @a11y\n // Said through the live regions rendered once beside the host: the card is created WITH\n // its message, and a region inserted along with its text is not reliably announced. A\n // failure interrupts; a plain confirmation waits for a pause.\n if (current.value && current.value.id !== lastAnnounced) {\n lastAnnounced = current.value.id\n announce(current.value.message, current.value.tone === 'danger')\n }\n // Showing and hiding are safe to call on a container already in that state, the guards\n // living in the popover plumbing — so there is no need to remember which it is in.\n if (current.value) show()\n else hide()\n arm()\n}\n\n/*\n * Watching the resolved bar covers a change of confirmation AND its disappearance. The\n * `post` timing is load-bearing: the card must already be in the page before its container\n * is told to show itself.\n */\nwatch(current, sync, { flush: 'post' })\n// @ssr\n// A watcher does not run during the server render, so a confirmation raised before\n// this component mounted would never be picked up. Running the same synchronization on\n// mount is what brings it in.\nonMounted(sync)\n\n// @a11y\n// WCAG 2.2.1: something that disappears on a clock has to be holdable, or a slow\n// reader simply never finishes it — and here they would also never reach the button.\n/*\n * Resting the pointer on the bar suspends its countdown, and so does moving the keyboard\n * into it: the action is a real button, so a reader tabbing towards it would otherwise\n * watch it vanish from under the focus ring. VToaster holds its stacks on the same two\n * reasons, with the same verbs, for its close crosses.\n *\n * Any focus counts, not just a keyboard one: a pointer landing on the button is about to\n * run the action anyway, so there is nothing to lose by holding as well.\n *\n * Leaving restarts the countdown from the FULL duration rather than from what was left:\n * simpler, and more generous to the reader who has just interrupted themselves.\n */\nfunction hold(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = true\n else focused = true\n cancel()\n}\n\n// Where the focus came from when it entered the bar, to hand it back once the action has\n// taken the bar, and the focused button with it, away.\nlet cameFrom: HTMLElement | null = null\nfunction onFocusIn(event: FocusEvent) {\n const from = event.relatedTarget as HTMLElement | null\n if (from && !hostEl.value?.contains(from)) cameFrom = from\n hold('focus')\n}\n\nfunction release(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = false\n else focused = false\n arm()\n}\n\n/* The action always takes the bar away: it answers the confirmation, so leaving it on\n screen would invite the reader to answer it twice. */\nfunction runAction() {\n const bar = current.value\n if (!bar) return\n const hadFocus = !!hostEl.value?.contains(document.activeElement)\n try {\n bar.action?.()\n } finally {\n // Even when the action throws: the error reaches the application, and the bar it\n // answered does not stay on screen, a permanent one for good.\n dismissSnackbar(bar.id)\n // @a11y\n // The focused button leaves with the bar; the focus goes back where it came from\n // rather than falling to `<body>`.\n if (hadFocus)\n void nextTick(() => {\n if (cameFrom?.isConnected && !current.value) cameFrom.focus()\n })\n }\n}\n</script>\n\n<template>\n <div\n ref=\"hostEl\"\n v-bind=\"$attrs\"\n class=\"v-overlay v-snackbar-host\"\n popover=\"manual\"\n :data-placement=\"placement\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"hold('pointer')\"\n @pointerleave=\"release('pointer')\"\n @focusin=\"onFocusIn\"\n @focusout=\"release('focus')\"\n @beforetoggle=\"syncShown\"\n @toggle=\"syncShown\"\n >\n <div\n v-if=\"current\"\n :key=\"current.id\"\n class=\"v-banner v-snackbar v-tone\"\n :data-tone=\"current.tone\"\n >\n <VIcon v-if=\"icon\" class=\"v-snackbar-icon\" v-bind=\"icon\" />\n <p class=\"v-banner-text v-snackbar-message\">{{ current.message }}</p>\n <VButton\n v-if=\"current.action\"\n class=\"v-banner-control v-snackbar-action\"\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n @click=\"runAction\"\n >\n {{ actionText }}\n </VButton>\n </div>\n </div>\n <span class=\"v-visually-hidden\" role=\"status\">{{ polite }}</span>\n <span class=\"v-visually-hidden\" role=\"alert\">{{ assertive }}</span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The container. Its rules are an ALIGNED COPY of VToaster's stacks: the two containers are the\n * same object, a popover at a physical corner that fades when it empties, and the copy is\n * kept line for line, the one difference being the top edge, which only the notifications take.\n * They are not factored into styles/banner.css because that sheet is paid for by every\n * consumer, and these rules cost more than the size gate allows; change one, change the other.\n *\n * The fixed positioning and the guard hiding a closed container come from the shared\n * `.v-overlay` class, set on this same element. What is undone here is the browser's own\n * popover decoration: its border, its padding, its opaque background.\n */\n .v-snackbar-host {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * PHYSICAL coordinates rather than logical ones: a message appears at a place on the\n * screen, and that place does not flip with the reading direction — the operating\n * system's own notifications behave the same way.\n *\n * `--banner-enter-y` is the direction each card slides in from, read by `.v-banner` in\n * styles/banner.css: the container is the only thing that knows which edge of the screen\n * it sits on. That sheet reads it with a `, 0` fallback, so dropping the declaration costs\n * the slide and nothing else, and nothing reports it.\n */\n .v-snackbar-host[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-snackbar-host {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-snackbar-host:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-snackbar-host:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-snackbar-host {\n transition: none;\n }\n }\n\n /* The box, the decoration, the typography and the entry motion come from the shared\n `.v-banner` class set on this same element — the chassis this bar has in common with\n the notification card, in `styles/banner.css`, along with the message block and the\n trailing control. What stays here is what the two genuinely differ on: the alignment,\n the padding, the width, and the tone painting. */\n .v-snackbar {\n /* Centred, where the notification hooks to its first line. A confirmation is ONE\n short sentence and it carries a real button: on a message that wraps, aligning to\n the start would park that button in the top corner, away from the text it answers.\n Centring also puts the icon in the middle, which is what keeps the two ends of the\n bar reading as one row. */\n align-items: center;\n padding-block: var(--vectis-space-3);\n /* The action pulls back into this gutter with a negative margin (`.v-banner-control`),\n rather than the bar declaring a smaller padding at its end. */\n padding-inline: var(--vectis-space-4);\n min-inline-size: var(--vectis-control-size-snackbar-min);\n /* Wide enough for a sentence, never wider than the viewport with the container's own\n margins deducted. */\n max-inline-size: min(\n var(--vectis-control-size-snackbar-max),\n calc(100dvi - 2 * var(--vectis-space-4))\n );\n }\n\n /*\n * Both tones are painted SOLID, and there is deliberately no soft variant: a\n * confirmation is a short-lived object laid over arbitrary content, so it has to read at\n * a glance rather than tint into the page.\n *\n * The tone table itself lives in styles/tones.css, in a layer below the components, and\n * is shared with VButton, VChip and VToast. Neutral needs no override here — its solid\n * pair IS the design system's canonical text/surface inversion, dark on a light theme\n * and light on a dark one, which is exactly what a snackbar wants and is why it is\n * expressed there rather than restated here. The action button reads on both through\n * the rebind `.v-banner-control` carries.\n */\n .v-snackbar {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /* No alignment margin here, unlike the notification: `align-items: center` above already\n puts the icon on the row's centre line, and an equal margin on a centred item changes\n nothing at all — it would be a declaration that does no work. */\n .v-snackbar-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n }\n\n /* Windows forced colors flattens the bar's background to Canvas and drops its shadow,\n which were its only edge: it would float over the page with no boundary at all. An\n outline draws one without moving the layout by a pixel. */\n @media (forced-colors: active) {\n .v-snackbar {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VSnackbar.js","names":[],"sources":["../../../src/components/VSnackbar/VSnackbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A manual popover hosts the current confirmation. JavaScript bridges imperative visibility,\n * announcements and a dismissal timer paused during hover or focus.\n */\n\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport { useTimer } from '../../composables/useTimer'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useMessages } from '../../i18n/state'\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { current, dismissSnackbar, type SnackbarPlacement } from './state'\n\ninterface SnackbarProps {\n /** Which end of the bottom edge confirmations appear at, unless one of them asks for another. */\n placement?: SnackbarPlacement\n /**\n * How long a confirmation stays, in milliseconds, unless it asks for something else. A\n * confirmation given 0 stays until it is replaced or taken away by hand.\n */\n duration?: number\n /**\n * The word drawn on the single action, when the confirmation does not give one. It is\n * also the button's accessible name, and falls back to the design system dictionary.\n */\n actionText?: string\n /**\n * What screen readers announce for the confirmation area itself, which is a landmark of\n * the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SnackbarProps>(), {\n placement: 'bottom-center',\n /* Shorter than a notification's five seconds. A confirmation is one short sentence\n about something the reader has just done, so they already know what it says. */\n duration: 4000,\n actionText: undefined,\n label: undefined,\n})\n\ndefineOptions({ inheritAttrs: false })\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.snackbar.label)\n\nconst placement = computed(() => current.value?.placement ?? props.placement)\nconst actionText = computed(\n () => current.value?.actionText ?? props.actionText ?? m.value.snackbar.action,\n)\nconst icon = computed(() => (current.value?.icon ? iconProps(current.value.icon) : undefined))\n\nconst hostEl = ref<HTMLElement | null>(null)\nconst { syncShown, show, hide } = usePopover(hostEl)\nconst { start, cancel } = useTimer()\n\n/*\n * They are two separate flags rather than one counter because they can be true at the same time\n * and end independently: a reader tabs to the action button, then moves the mouse over the bar,\n * then moves it away; and the bar must not leave while the button still has focus.\n */\nlet hovered = false\nlet focused = false\n\nfunction arm() {\n const bar = current.value\n if (!bar || hovered || focused) return\n const duration = bar.duration ?? props.duration\n /*\n * GUARD, not a default: `useTimer` runs a delay of 0 synchronously, which is the design\n * system's convention for \"no deferral at all\". Without this test a confirmation asking to be\n * permanent would be taken away in the same tick it was raised.\n */\n if (duration > 0) start(() => dismissSnackbar(bar.id), duration)\n}\n\n// @core\n/**\n * Brings the page into line with the state: it shows the container when there is a confirmation\n * and hides it when there is none, and restarts the countdown from the top.\n */\nconst { polite, assertive, announce } = useLiveAnnouncer()\nlet lastAnnounced: number | undefined\n\nfunction sync() {\n cancel()\n // Clear hover/focus flags when removing the bar; a removed focused element may emit no\n // focusout and leave future timers permanently held.\n if (!current.value) hovered = focused = false\n // A REPLACEMENT removes the focused action too (the card is keyed on the bar), and the same\n // engine leaves `focused` standing: the new bar then never left. The toaster's test, where\n // the focus actually is, is the one that holds in both cases.\n else if (!hostEl.value?.contains(document.activeElement)) focused = false\n // @a11y\n // Said through the live regions rendered once beside the host: the card is created WITH\n // its message, and a region inserted along with its text is not reliably announced. A\n // failure interrupts; a plain confirmation waits for a pause.\n if (current.value && current.value.id !== lastAnnounced) {\n lastAnnounced = current.value.id\n announce(current.value.message, current.value.tone === 'danger')\n }\n // Showing and hiding are safe to call on a container already in that state, the guards living\n // in the popover plumbing; so there is no need to remember which it is in.\n if (current.value) show()\n else hide()\n arm()\n}\n\n/*\n * Watching the resolved bar covers a change of confirmation and its disappearance. The `post`\n * timing is load-bearing: the card must already be in the page before its container is told to\n * show itself.\n */\nwatch(current, sync, { flush: 'post' })\n// @ssr\n// A watcher does not run during the server render, so a confirmation raised before\n// this component mounted would never be picked up. Running the same synchronization on\n// mount is what brings it in.\nonMounted(sync)\n\n// @a11y\n// WCAG 2.2.1: something that disappears on a clock has to be holdable, or a slow reader simply\n// never finishes it; and here they would also never reach the button.\n/*\n * Resting the pointer on the bar suspends its countdown, and so does moving the keyboard into\n * it: the action is a real button, so a reader tabbing towards it would otherwise watch it\n * vanish from under the focus ring. VToaster holds its stacks on the same two reasons, with the\n * same verbs, for its close crosses.\n */\nfunction hold(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = true\n else focused = true\n cancel()\n}\n\n// Where the focus came from when it entered the bar, to hand it back once the action has\n// taken the bar, and the focused button with it, away.\nlet cameFrom: HTMLElement | null = null\nfunction onFocusIn(event: FocusEvent) {\n const from = event.relatedTarget as HTMLElement | null\n if (from && !hostEl.value?.contains(from)) cameFrom = from\n hold('focus')\n}\n\nfunction release(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = false\n else focused = false\n arm()\n}\n\n/* The action always takes the bar away: it answers the confirmation, so leaving it on\n screen would invite the reader to answer it twice. */\nfunction runAction() {\n const bar = current.value\n if (!bar) return\n const hadFocus = !!hostEl.value?.contains(document.activeElement)\n try {\n bar.action?.()\n } finally {\n // Even when the action throws: the error reaches the application, and the bar it\n // answered does not stay on screen, a permanent one for good.\n dismissSnackbar(bar.id)\n // @a11y\n // The focused button leaves with the bar; the focus goes back where it came from\n // rather than falling to `<body>`.\n if (hadFocus)\n void nextTick(() => {\n if (cameFrom?.isConnected && !current.value) cameFrom.focus()\n })\n }\n}\n</script>\n\n<template>\n <div\n ref=\"hostEl\"\n v-bind=\"$attrs\"\n class=\"v-overlay v-snackbar-host\"\n popover=\"manual\"\n :data-placement=\"placement\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"hold('pointer')\"\n @pointerleave=\"release('pointer')\"\n @focusin=\"onFocusIn\"\n @focusout=\"release('focus')\"\n @beforetoggle=\"syncShown\"\n @toggle=\"syncShown\"\n >\n <div\n v-if=\"current\"\n :key=\"current.id\"\n class=\"v-banner v-snackbar v-tone\"\n :data-tone=\"current.tone\"\n >\n <VIcon v-if=\"icon\" class=\"v-snackbar-icon\" v-bind=\"icon\" />\n <p class=\"v-banner-text v-snackbar-message\">{{ current.message }}</p>\n <VButton\n v-if=\"current.action\"\n class=\"v-banner-control v-snackbar-action\"\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n @click=\"runAction\"\n >\n {{ actionText }}\n </VButton>\n </div>\n </div>\n <span class=\"v-visually-hidden\" role=\"status\">{{ polite }}</span>\n <span class=\"v-visually-hidden\" role=\"alert\">{{ assertive }}</span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * They are not factored into styles/banner.css because that sheet is paid for by every\n * consumer, and these rules cost more than the size gate allows; change one, change the\n * other. The fixed positioning and the guard hiding a closed container come from the shared\n * `.v-overlay` class, set on this same element.\n */\n .v-snackbar-host {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * Keep screen placement physical in RTL. The host supplies the entry direction because it\n * knows its screen edge.\n */\n .v-snackbar-host[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-snackbar-host {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-snackbar-host:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-snackbar-host:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-snackbar-host {\n transition: none;\n }\n }\n\n /*\n * Shared banner chrome supplies decoration and motion; snackbar owns alignment, dimensions\n * and tone painting.\n */\n .v-snackbar {\n /*\n * Centred, where the notification hooks to its first line. A confirmation is ONE short\n * sentence and it carries a real button: on a message that wraps, aligning to the start\n * would park that button in the top corner, away from the text it answers.\n */\n align-items: center;\n padding-block: var(--vectis-space-3);\n /* The action pulls back into this gutter with a negative margin (`.v-banner-control`),\n rather than the bar declaring a smaller padding at its end. */\n padding-inline: var(--vectis-space-4);\n min-inline-size: var(--vectis-control-size-snackbar-min);\n /* Wide enough for a sentence, never wider than the viewport with the container's own\n margins deducted. */\n max-inline-size: min(\n var(--vectis-control-size-snackbar-max),\n calc(100dvi - 2 * var(--vectis-space-4))\n );\n }\n\n /*\n * Both tones are painted SOLID, and there is deliberately no soft variant: a confirmation is\n * a short-lived object laid over arbitrary content, so it has to read at a glance rather than\n * tint into the page. The tone table itself lives in styles/tones.css, in a layer below the\n * components, and is shared with VButton, VChip and VToast.\n */\n .v-snackbar {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /*\n * No alignment margin here, unlike the notification: `align-items: center` above already puts\n * the icon on the row's centre line, and an equal margin on a centred item changes nothing at\n * all; it would be a declaration that does no work.\n */\n .v-snackbar-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n }\n\n /* An outline draws one without moving the layout by a pixel. */\n @media (forced-colors: active) {\n .v-snackbar {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VSnackbar.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"sources":["../../../src/components/VSnackbar/VSnackbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Where confirmations appear: mounted ONCE at the root, after which every `snackbar()` call\n * shows up here.\n *\n * A confirmation answers something the reader just did — \"Message deleted\" — usually with\n * one button to take it back. Everything that separates it from a notification follows from\n * ONE idea: only the LAST action is worth offering to undo. Hence a single bar at a time (a\n * new one replaces it), hence no close cross (a bar that tidies itself must not ask the\n * reader to), hence the bottom edge alone and a shorter delay.\n *\n * That \"one at a time\" is also why this is a single SFC where the notifications need two:\n * one container, one card, and no book-keeping to tell several countdowns apart.\n *\n * The JS covers three things the platform does not: keeping the state and the container in\n * step, the popover being imperative; the auto-dismiss countdown; and HOLDING that countdown\n * while the pointer rests on the bar OR the keyboard is inside it, so something that\n * disappears on a clock can be read and acted on (WCAG 2.2.1).\n */\n\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport { useTimer } from '../../composables/useTimer'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useMessages } from '../../i18n/state'\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { current, dismissSnackbar, type SnackbarPlacement } from './state'\n\ninterface SnackbarProps {\n /** Which end of the bottom edge confirmations appear at, unless one of them asks for another. */\n placement?: SnackbarPlacement\n /**\n * How long a confirmation stays, in milliseconds, unless it asks for something else. A\n * confirmation given 0 stays until it is replaced or taken away by hand.\n */\n duration?: number\n /**\n * The word drawn on the single action, when the confirmation does not give one. It is\n * also the button's accessible name, and falls back to the design system dictionary.\n */\n actionText?: string\n /**\n * What screen readers announce for the confirmation area itself, which is a landmark of\n * the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SnackbarProps>(), {\n placement: 'bottom-center',\n /* Shorter than a notification's five seconds. A confirmation is one short sentence\n about something the reader has just done, so they already know what it says. */\n duration: 4000,\n actionText: undefined,\n label: undefined,\n})\n\n// The component renders the host AND its two live regions, so the consumer's attributes are\n// put on the host explicitly rather than left to a root that no longer exists.\ndefineOptions({ inheritAttrs: false })\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.snackbar.label)\n\nconst placement = computed(() => current.value?.placement ?? props.placement)\nconst actionText = computed(\n () => current.value?.actionText ?? props.actionText ?? m.value.snackbar.action,\n)\nconst icon = computed(() => (current.value?.icon ? iconProps(current.value.icon) : undefined))\n\nconst hostEl = ref<HTMLElement | null>(null)\nconst { syncShown, show, hide } = usePopover(hostEl)\nconst { start, cancel } = useTimer()\n\n/* The reasons to hold the countdown, each set by its own pair of events. They are two\n separate flags rather than one counter because they can be true at the same time and\n end independently: a reader tabs to the action button, then moves the mouse over the\n bar, then moves it away — and the bar must not leave while the button still has focus. */\nlet hovered = false\nlet focused = false\n\nfunction arm() {\n const bar = current.value\n if (!bar || hovered || focused) return\n const duration = bar.duration ?? props.duration\n /* GUARD, not a default: `useTimer` runs a delay of 0 SYNCHRONOUSLY, which is the design\n system's convention for \"no deferral at all\". Without this test a confirmation asking\n to be permanent would be taken away in the same tick it was raised. */\n if (duration > 0) start(() => dismissSnackbar(bar.id), duration)\n}\n\n// @core\n/**\n * Brings the page into line with the state: it shows the container when there is a\n * confirmation and hides it when there is none, and restarts the countdown from the top.\n *\n * Restarting unconditionally is what makes a replacement correct — the outgoing bar's\n * countdown must not be allowed to take the incoming one away — and it is safe precisely\n * because this runs only when the bar itself changes.\n *\n * It runs once on mount, which is what makes a confirmation raised before this component\n * existed appear all the same, and after that on every change.\n */\nconst { polite, assertive, announce } = useLiveAnnouncer()\nlet lastAnnounced: number | undefined\n\nfunction sync() {\n cancel()\n // TRAP — with no bar left, nothing can still be hovered or focused, so both flags are\n // dropped here rather than trusted to their events. The action button leaves the page\n // WITH the bar while it holds the focus, and an engine that sends no `focusout` for a\n // removed element would leave `focused` standing: every later confirmation would then\n // stay on screen for good, far from the gesture that caused it.\n if (!current.value) hovered = focused = false\n // TRAP — a REPLACEMENT removes the focused action too (the card is keyed on the bar), and\n // the same engine leaves `focused` standing: the new bar then never left. The toaster's\n // test, where the focus actually is, is the one that holds in both cases.\n else if (!hostEl.value?.contains(document.activeElement)) focused = false\n // @a11y\n // Said through the live regions rendered once beside the host: the card is created WITH\n // its message, and a region inserted along with its text is not reliably announced. A\n // failure interrupts; a plain confirmation waits for a pause.\n if (current.value && current.value.id !== lastAnnounced) {\n lastAnnounced = current.value.id\n announce(current.value.message, current.value.tone === 'danger')\n }\n // Showing and hiding are safe to call on a container already in that state, the guards\n // living in the popover plumbing — so there is no need to remember which it is in.\n if (current.value) show()\n else hide()\n arm()\n}\n\n/*\n * Watching the resolved bar covers a change of confirmation AND its disappearance. The\n * `post` timing is load-bearing: the card must already be in the page before its container\n * is told to show itself.\n */\nwatch(current, sync, { flush: 'post' })\n// @ssr\n// A watcher does not run during the server render, so a confirmation raised before\n// this component mounted would never be picked up. Running the same synchronization on\n// mount is what brings it in.\nonMounted(sync)\n\n// @a11y\n// WCAG 2.2.1: something that disappears on a clock has to be holdable, or a slow\n// reader simply never finishes it — and here they would also never reach the button.\n/*\n * Resting the pointer on the bar suspends its countdown, and so does moving the keyboard\n * into it: the action is a real button, so a reader tabbing towards it would otherwise\n * watch it vanish from under the focus ring. VToaster holds its stacks on the same two\n * reasons, with the same verbs, for its close crosses.\n *\n * Any focus counts, not just a keyboard one: a pointer landing on the button is about to\n * run the action anyway, so there is nothing to lose by holding as well.\n *\n * Leaving restarts the countdown from the FULL duration rather than from what was left:\n * simpler, and more generous to the reader who has just interrupted themselves.\n */\nfunction hold(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = true\n else focused = true\n cancel()\n}\n\n// Where the focus came from when it entered the bar, to hand it back once the action has\n// taken the bar, and the focused button with it, away.\nlet cameFrom: HTMLElement | null = null\nfunction onFocusIn(event: FocusEvent) {\n const from = event.relatedTarget as HTMLElement | null\n if (from && !hostEl.value?.contains(from)) cameFrom = from\n hold('focus')\n}\n\nfunction release(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = false\n else focused = false\n arm()\n}\n\n/* The action always takes the bar away: it answers the confirmation, so leaving it on\n screen would invite the reader to answer it twice. */\nfunction runAction() {\n const bar = current.value\n if (!bar) return\n const hadFocus = !!hostEl.value?.contains(document.activeElement)\n try {\n bar.action?.()\n } finally {\n // Even when the action throws: the error reaches the application, and the bar it\n // answered does not stay on screen, a permanent one for good.\n dismissSnackbar(bar.id)\n // @a11y\n // The focused button leaves with the bar; the focus goes back where it came from\n // rather than falling to `<body>`.\n if (hadFocus)\n void nextTick(() => {\n if (cameFrom?.isConnected && !current.value) cameFrom.focus()\n })\n }\n}\n</script>\n\n<template>\n <div\n ref=\"hostEl\"\n v-bind=\"$attrs\"\n class=\"v-overlay v-snackbar-host\"\n popover=\"manual\"\n :data-placement=\"placement\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"hold('pointer')\"\n @pointerleave=\"release('pointer')\"\n @focusin=\"onFocusIn\"\n @focusout=\"release('focus')\"\n @beforetoggle=\"syncShown\"\n @toggle=\"syncShown\"\n >\n <div\n v-if=\"current\"\n :key=\"current.id\"\n class=\"v-banner v-snackbar v-tone\"\n :data-tone=\"current.tone\"\n >\n <VIcon v-if=\"icon\" class=\"v-snackbar-icon\" v-bind=\"icon\" />\n <p class=\"v-banner-text v-snackbar-message\">{{ current.message }}</p>\n <VButton\n v-if=\"current.action\"\n class=\"v-banner-control v-snackbar-action\"\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n @click=\"runAction\"\n >\n {{ actionText }}\n </VButton>\n </div>\n </div>\n <span class=\"v-visually-hidden\" role=\"status\">{{ polite }}</span>\n <span class=\"v-visually-hidden\" role=\"alert\">{{ assertive }}</span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The container. Its rules are an ALIGNED COPY of VToaster's stacks: the two containers are the\n * same object, a popover at a physical corner that fades when it empties, and the copy is\n * kept line for line, the one difference being the top edge, which only the notifications take.\n * They are not factored into styles/banner.css because that sheet is paid for by every\n * consumer, and these rules cost more than the size gate allows; change one, change the other.\n *\n * The fixed positioning and the guard hiding a closed container come from the shared\n * `.v-overlay` class, set on this same element. What is undone here is the browser's own\n * popover decoration: its border, its padding, its opaque background.\n */\n .v-snackbar-host {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * PHYSICAL coordinates rather than logical ones: a message appears at a place on the\n * screen, and that place does not flip with the reading direction — the operating\n * system's own notifications behave the same way.\n *\n * `--banner-enter-y` is the direction each card slides in from, read by `.v-banner` in\n * styles/banner.css: the container is the only thing that knows which edge of the screen\n * it sits on. That sheet reads it with a `, 0` fallback, so dropping the declaration costs\n * the slide and nothing else, and nothing reports it.\n */\n .v-snackbar-host[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-snackbar-host {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-snackbar-host:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-snackbar-host:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-snackbar-host {\n transition: none;\n }\n }\n\n /* The box, the decoration, the typography and the entry motion come from the shared\n `.v-banner` class set on this same element — the chassis this bar has in common with\n the notification card, in `styles/banner.css`, along with the message block and the\n trailing control. What stays here is what the two genuinely differ on: the alignment,\n the padding, the width, and the tone painting. */\n .v-snackbar {\n /* Centred, where the notification hooks to its first line. A confirmation is ONE\n short sentence and it carries a real button: on a message that wraps, aligning to\n the start would park that button in the top corner, away from the text it answers.\n Centring also puts the icon in the middle, which is what keeps the two ends of the\n bar reading as one row. */\n align-items: center;\n padding-block: var(--vectis-space-3);\n /* The action pulls back into this gutter with a negative margin (`.v-banner-control`),\n rather than the bar declaring a smaller padding at its end. */\n padding-inline: var(--vectis-space-4);\n min-inline-size: var(--vectis-control-size-snackbar-min);\n /* Wide enough for a sentence, never wider than the viewport with the container's own\n margins deducted. */\n max-inline-size: min(\n var(--vectis-control-size-snackbar-max),\n calc(100dvi - 2 * var(--vectis-space-4))\n );\n }\n\n /*\n * Both tones are painted SOLID, and there is deliberately no soft variant: a\n * confirmation is a short-lived object laid over arbitrary content, so it has to read at\n * a glance rather than tint into the page.\n *\n * The tone table itself lives in styles/tones.css, in a layer below the components, and\n * is shared with VButton, VChip and VToast. Neutral needs no override here — its solid\n * pair IS the design system's canonical text/surface inversion, dark on a light theme\n * and light on a dark one, which is exactly what a snackbar wants and is why it is\n * expressed there rather than restated here. The action button reads on both through\n * the rebind `.v-banner-control` carries.\n */\n .v-snackbar {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /* No alignment margin here, unlike the notification: `align-items: center` above already\n puts the icon on the row's centre line, and an equal margin on a centred item changes\n nothing at all — it would be a declaration that does no work. */\n .v-snackbar-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n }\n\n /* Windows forced colors flattens the bar's background to Canvas and drops its shadow,\n which were its only edge: it would float over the page with no boundary at all. An\n outline draws one without moving the layout by a pixel. */\n @media (forced-colors: active) {\n .v-snackbar {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAqDA,IAAM,IAAQ,GAaR,IAAI,EAAY,GAChB,IAAY,QAAmB,EAAM,SAAS,EAAE,MAAM,SAAS,KAAK,GAEpE,IAAY,QAAe,EAAQ,OAAO,aAAa,EAAM,SAAS,GACtE,IAAa,QACX,EAAQ,OAAO,cAAc,EAAM,cAAc,EAAE,MAAM,SAAS,MAC1E,GACM,IAAO,QAAgB,EAAQ,OAAO,OAAO,EAAU,EAAQ,MAAM,IAAI,IAAI,KAAA,CAAU,GAEvF,IAAS,EAAwB,IAAI,GACrC,EAAE,cAAW,SAAM,YAAS,EAAW,CAAM,GAC7C,EAAE,UAAO,cAAW,EAAS,GAM/B,IAAU,IACV,IAAU;EAEd,SAAS,IAAM;GACb,IAAM,IAAM,EAAQ;GACpB,IAAI,CAAC,KAAO,KAAW,GAAS;GAChC,IAAM,IAAW,EAAI,YAAY,EAAM;GAIvC,AAAI,IAAW,KAAG,QAAY,EAAgB,EAAI,EAAE,GAAG,CAAQ;EACjE;EAcA,IAAM,EAAE,WAAQ,cAAW,gBAAa,EAAiB,GACrD;EAEJ,SAAS,IAAO;GAwBd,AAvBA,EAAO,GAMF,EAAQ,QAIH,EAAO,OAAO,SAAS,SAAS,aAAa,MAAG,IAAU,MAJhD,IAAU,IAAU,IASpC,EAAQ,SAAS,EAAQ,MAAM,OAAO,MACxC,IAAgB,EAAQ,MAAM,IAC9B,EAAS,EAAQ,MAAM,SAAS,EAAQ,MAAM,SAAS,QAAQ,IAI7D,EAAQ,QAAO,EAAK,IACnB,EAAK,GACV,EAAI;EACN;EAYA,AALA,EAAM,GAAS,GAAM,EAAE,OAAO,OAAO,CAAC,GAKtC,EAAU,CAAI;EAiBd,SAAS,EAAK,GAA4B;GAGxC,AAFI,MAAU,YAAW,IAAU,KAC9B,IAAU,IACf,EAAO;EACT;EAIA,IAAI,IAA+B;EACnC,SAAS,GAAU,GAAmB;GACpC,IAAM,IAAO,EAAM;GAEnB,AADI,KAAQ,CAAC,EAAO,OAAO,SAAS,CAAI,MAAG,IAAW,IACtD,EAAK,OAAO;EACd;EAEA,SAAS,EAAQ,GAA4B;GAG3C,AAFI,MAAU,YAAW,IAAU,KAC9B,IAAU,IACf,EAAI;EACN;EAIA,SAAS,KAAY;GACnB,IAAM,IAAM,EAAQ;GACpB,IAAI,CAAC,GAAK;GACV,IAAM,IAAW,CAAC,CAAC,EAAO,OAAO,SAAS,SAAS,aAAa;GAChE,IAAI;IACF,EAAI,SAAS;GACf,UAAU;IAOR,AAJA,EAAgB,EAAI,EAAE,GAIlB,KACF,QAAoB;KAClB,AAAI,GAAU,eAAe,CAAC,EAAQ,SAAO,EAAS,MAAM;IAC9D,CAAC;GACL;EACF;;GAIE,EAkCM,OAlCN,EAkCM;aAjCA;IAAJ,KAAI;MACIA,EAAAA,QAAM;IACd,OAAM;IACN,SAAQ;IACP,kBAAgB,EAAA;IACjB,MAAK;IACJ,cAAY,EAAA,CAAA;IACZ,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAI,SAAA;IAClB,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAO,SAAA;IACrB,WAAS;IACT,YAAQ,AAAA,EAAA,QAAA,MAAE,EAAO,OAAA;IACjB,gBAAY,AAAA,EAAA,QAAA,GAAA,MAAE,EAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA,GAAA,CAAA;IACd,UAAM,AAAA,EAAA,QAAA,GAAA,MAAE,EAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA,GAAA,CAAA;QAGD,EAAA,CAAA,KAAA,EAAA,GADR,EAkBM,OAAA;IAhBH,KAAK,EAAA,CAAA,CAAO,CAAC;IACd,OAAM;IACL,aAAW,EAAA,CAAA,CAAO,CAAC;;IAEP,EAAA,SAAA,EAAA,GAAb,EAA2D,GAA3D,EAA2D;;KAAxC,OAAM;OAA0B,EAAA,KAAI,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA;IACvD,EAAqE,KAArE,GAAqE,EAAtB,EAAA,CAAA,CAAO,CAAC,OAAO,GAAA,CAAA;IAEtD,EAAA,CAAA,CAAO,CAAC,UAAA,EAAA,GADhB,EASU,GAAA;;KAPR,OAAM;KACN,SAAQ;KACR,MAAK;KACL,MAAK;KACJ,SAAO;;sBAEQ,CAAA,EAAA,EAAb,EAAA,KAAU,GAAA,CAAA,CAAA,CAAA;;;;GAInB,EAAiE,QAAjE,GAAiE,EAAhB,EAAA,CAAA,CAAM,GAAA,CAAA;GACvD,EAAmE,QAAnE,GAAmE,EAAnB,EAAA,CAAA,CAAS,GAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VSnackbar.vue_vue_type_script_setup_true_lang.js","names":["$attrs"],"sources":["../../../src/components/VSnackbar/VSnackbar.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A manual popover hosts the current confirmation. JavaScript bridges imperative visibility,\n * announcements and a dismissal timer paused during hover or focus.\n */\n\nimport { computed, nextTick, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport { useTimer } from '../../composables/useTimer'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useMessages } from '../../i18n/state'\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { current, dismissSnackbar, type SnackbarPlacement } from './state'\n\ninterface SnackbarProps {\n /** Which end of the bottom edge confirmations appear at, unless one of them asks for another. */\n placement?: SnackbarPlacement\n /**\n * How long a confirmation stays, in milliseconds, unless it asks for something else. A\n * confirmation given 0 stays until it is replaced or taken away by hand.\n */\n duration?: number\n /**\n * The word drawn on the single action, when the confirmation does not give one. It is\n * also the button's accessible name, and falls back to the design system dictionary.\n */\n actionText?: string\n /**\n * What screen readers announce for the confirmation area itself, which is a landmark of\n * the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SnackbarProps>(), {\n placement: 'bottom-center',\n /* Shorter than a notification's five seconds. A confirmation is one short sentence\n about something the reader has just done, so they already know what it says. */\n duration: 4000,\n actionText: undefined,\n label: undefined,\n})\n\ndefineOptions({ inheritAttrs: false })\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.snackbar.label)\n\nconst placement = computed(() => current.value?.placement ?? props.placement)\nconst actionText = computed(\n () => current.value?.actionText ?? props.actionText ?? m.value.snackbar.action,\n)\nconst icon = computed(() => (current.value?.icon ? iconProps(current.value.icon) : undefined))\n\nconst hostEl = ref<HTMLElement | null>(null)\nconst { syncShown, show, hide } = usePopover(hostEl)\nconst { start, cancel } = useTimer()\n\n/*\n * They are two separate flags rather than one counter because they can be true at the same time\n * and end independently: a reader tabs to the action button, then moves the mouse over the bar,\n * then moves it away; and the bar must not leave while the button still has focus.\n */\nlet hovered = false\nlet focused = false\n\nfunction arm() {\n const bar = current.value\n if (!bar || hovered || focused) return\n const duration = bar.duration ?? props.duration\n /*\n * GUARD, not a default: `useTimer` runs a delay of 0 synchronously, which is the design\n * system's convention for \"no deferral at all\". Without this test a confirmation asking to be\n * permanent would be taken away in the same tick it was raised.\n */\n if (duration > 0) start(() => dismissSnackbar(bar.id), duration)\n}\n\n// @core\n/**\n * Brings the page into line with the state: it shows the container when there is a confirmation\n * and hides it when there is none, and restarts the countdown from the top.\n */\nconst { polite, assertive, announce } = useLiveAnnouncer()\nlet lastAnnounced: number | undefined\n\nfunction sync() {\n cancel()\n // Clear hover/focus flags when removing the bar; a removed focused element may emit no\n // focusout and leave future timers permanently held.\n if (!current.value) hovered = focused = false\n // A REPLACEMENT removes the focused action too (the card is keyed on the bar), and the same\n // engine leaves `focused` standing: the new bar then never left. The toaster's test, where\n // the focus actually is, is the one that holds in both cases.\n else if (!hostEl.value?.contains(document.activeElement)) focused = false\n // @a11y\n // Said through the live regions rendered once beside the host: the card is created WITH\n // its message, and a region inserted along with its text is not reliably announced. A\n // failure interrupts; a plain confirmation waits for a pause.\n if (current.value && current.value.id !== lastAnnounced) {\n lastAnnounced = current.value.id\n announce(current.value.message, current.value.tone === 'danger')\n }\n // Showing and hiding are safe to call on a container already in that state, the guards living\n // in the popover plumbing; so there is no need to remember which it is in.\n if (current.value) show()\n else hide()\n arm()\n}\n\n/*\n * Watching the resolved bar covers a change of confirmation and its disappearance. The `post`\n * timing is load-bearing: the card must already be in the page before its container is told to\n * show itself.\n */\nwatch(current, sync, { flush: 'post' })\n// @ssr\n// A watcher does not run during the server render, so a confirmation raised before\n// this component mounted would never be picked up. Running the same synchronization on\n// mount is what brings it in.\nonMounted(sync)\n\n// @a11y\n// WCAG 2.2.1: something that disappears on a clock has to be holdable, or a slow reader simply\n// never finishes it; and here they would also never reach the button.\n/*\n * Resting the pointer on the bar suspends its countdown, and so does moving the keyboard into\n * it: the action is a real button, so a reader tabbing towards it would otherwise watch it\n * vanish from under the focus ring. VToaster holds its stacks on the same two reasons, with the\n * same verbs, for its close crosses.\n */\nfunction hold(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = true\n else focused = true\n cancel()\n}\n\n// Where the focus came from when it entered the bar, to hand it back once the action has\n// taken the bar, and the focused button with it, away.\nlet cameFrom: HTMLElement | null = null\nfunction onFocusIn(event: FocusEvent) {\n const from = event.relatedTarget as HTMLElement | null\n if (from && !hostEl.value?.contains(from)) cameFrom = from\n hold('focus')\n}\n\nfunction release(which: 'pointer' | 'focus') {\n if (which === 'pointer') hovered = false\n else focused = false\n arm()\n}\n\n/* The action always takes the bar away: it answers the confirmation, so leaving it on\n screen would invite the reader to answer it twice. */\nfunction runAction() {\n const bar = current.value\n if (!bar) return\n const hadFocus = !!hostEl.value?.contains(document.activeElement)\n try {\n bar.action?.()\n } finally {\n // Even when the action throws: the error reaches the application, and the bar it\n // answered does not stay on screen, a permanent one for good.\n dismissSnackbar(bar.id)\n // @a11y\n // The focused button leaves with the bar; the focus goes back where it came from\n // rather than falling to `<body>`.\n if (hadFocus)\n void nextTick(() => {\n if (cameFrom?.isConnected && !current.value) cameFrom.focus()\n })\n }\n}\n</script>\n\n<template>\n <div\n ref=\"hostEl\"\n v-bind=\"$attrs\"\n class=\"v-overlay v-snackbar-host\"\n popover=\"manual\"\n :data-placement=\"placement\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"hold('pointer')\"\n @pointerleave=\"release('pointer')\"\n @focusin=\"onFocusIn\"\n @focusout=\"release('focus')\"\n @beforetoggle=\"syncShown\"\n @toggle=\"syncShown\"\n >\n <div\n v-if=\"current\"\n :key=\"current.id\"\n class=\"v-banner v-snackbar v-tone\"\n :data-tone=\"current.tone\"\n >\n <VIcon v-if=\"icon\" class=\"v-snackbar-icon\" v-bind=\"icon\" />\n <p class=\"v-banner-text v-snackbar-message\">{{ current.message }}</p>\n <VButton\n v-if=\"current.action\"\n class=\"v-banner-control v-snackbar-action\"\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n @click=\"runAction\"\n >\n {{ actionText }}\n </VButton>\n </div>\n </div>\n <span class=\"v-visually-hidden\" role=\"status\">{{ polite }}</span>\n <span class=\"v-visually-hidden\" role=\"alert\">{{ assertive }}</span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * They are not factored into styles/banner.css because that sheet is paid for by every\n * consumer, and these rules cost more than the size gate allows; change one, change the\n * other. The fixed positioning and the guard hiding a closed container come from the shared\n * `.v-overlay` class, set on this same element.\n */\n .v-snackbar-host {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * Keep screen placement physical in RTL. The host supplies the entry direction because it\n * knows its screen edge.\n */\n .v-snackbar-host[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-snackbar-host[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-snackbar-host {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-snackbar-host:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-snackbar-host:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-snackbar-host {\n transition: none;\n }\n }\n\n /*\n * Shared banner chrome supplies decoration and motion; snackbar owns alignment, dimensions\n * and tone painting.\n */\n .v-snackbar {\n /*\n * Centred, where the notification hooks to its first line. A confirmation is ONE short\n * sentence and it carries a real button: on a message that wraps, aligning to the start\n * would park that button in the top corner, away from the text it answers.\n */\n align-items: center;\n padding-block: var(--vectis-space-3);\n /* The action pulls back into this gutter with a negative margin (`.v-banner-control`),\n rather than the bar declaring a smaller padding at its end. */\n padding-inline: var(--vectis-space-4);\n min-inline-size: var(--vectis-control-size-snackbar-min);\n /* Wide enough for a sentence, never wider than the viewport with the container's own\n margins deducted. */\n max-inline-size: min(\n var(--vectis-control-size-snackbar-max),\n calc(100dvi - 2 * var(--vectis-space-4))\n );\n }\n\n /*\n * Both tones are painted SOLID, and there is deliberately no soft variant: a confirmation is\n * a short-lived object laid over arbitrary content, so it has to read at a glance rather than\n * tint into the page. The tone table itself lives in styles/tones.css, in a layer below the\n * components, and is shared with VButton, VChip and VToast.\n */\n .v-snackbar {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /*\n * No alignment margin here, unlike the notification: `align-items: center` above already puts\n * the icon on the row's centre line, and an equal margin on a centred item changes nothing at\n * all; it would be a declaration that does no work.\n */\n .v-snackbar-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n }\n\n /* An outline draws one without moving the layout by a pixel. */\n @media (forced-colors: active) {\n .v-snackbar {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAuCA,IAAM,IAAQ,GAWR,IAAI,EAAY,GAChB,IAAY,QAAmB,EAAM,SAAS,EAAE,MAAM,SAAS,KAAK,GAEpE,IAAY,QAAe,EAAQ,OAAO,aAAa,EAAM,SAAS,GACtE,IAAa,QACX,EAAQ,OAAO,cAAc,EAAM,cAAc,EAAE,MAAM,SAAS,MAC1E,GACM,IAAO,QAAgB,EAAQ,OAAO,OAAO,EAAU,EAAQ,MAAM,IAAI,IAAI,KAAA,CAAU,GAEvF,IAAS,EAAwB,IAAI,GACrC,EAAE,cAAW,SAAM,YAAS,EAAW,CAAM,GAC7C,EAAE,UAAO,cAAW,EAAS,GAO/B,IAAU,IACV,IAAU;EAEd,SAAS,IAAM;GACb,IAAM,IAAM,EAAQ;GACpB,IAAI,CAAC,KAAO,KAAW,GAAS;GAChC,IAAM,IAAW,EAAI,YAAY,EAAM;GAMvC,AAAI,IAAW,KAAG,QAAY,EAAgB,EAAI,EAAE,GAAG,CAAQ;EACjE;EAOA,IAAM,EAAE,WAAQ,cAAW,gBAAa,EAAiB,GACrD;EAEJ,SAAS,IAAO;GAqBd,AApBA,EAAO,GAGF,EAAQ,QAIH,EAAO,OAAO,SAAS,SAAS,aAAa,MAAG,IAAU,MAJhD,IAAU,IAAU,IASpC,EAAQ,SAAS,EAAQ,MAAM,OAAO,MACxC,IAAgB,EAAQ,MAAM,IAC9B,EAAS,EAAQ,MAAM,SAAS,EAAQ,MAAM,SAAS,QAAQ,IAI7D,EAAQ,QAAO,EAAK,IACnB,EAAK,GACV,EAAI;EACN;EAYA,AALA,EAAM,GAAS,GAAM,EAAE,OAAO,OAAO,CAAC,GAKtC,EAAU,CAAI;EAWd,SAAS,EAAK,GAA4B;GAGxC,AAFI,MAAU,YAAW,IAAU,KAC9B,IAAU,IACf,EAAO;EACT;EAIA,IAAI,IAA+B;EACnC,SAAS,GAAU,GAAmB;GACpC,IAAM,IAAO,EAAM;GAEnB,AADI,KAAQ,CAAC,EAAO,OAAO,SAAS,CAAI,MAAG,IAAW,IACtD,EAAK,OAAO;EACd;EAEA,SAAS,EAAQ,GAA4B;GAG3C,AAFI,MAAU,YAAW,IAAU,KAC9B,IAAU,IACf,EAAI;EACN;EAIA,SAAS,KAAY;GACnB,IAAM,IAAM,EAAQ;GACpB,IAAI,CAAC,GAAK;GACV,IAAM,IAAW,CAAC,CAAC,EAAO,OAAO,SAAS,SAAS,aAAa;GAChE,IAAI;IACF,EAAI,SAAS;GACf,UAAU;IAOR,AAJA,EAAgB,EAAI,EAAE,GAIlB,KACF,QAAoB;KAClB,AAAI,GAAU,eAAe,CAAC,EAAQ,SAAO,EAAS,MAAM;IAC9D,CAAC;GACL;EACF;;GAIE,EAkCM,OAlCN,EAkCM;aAjCA;IAAJ,KAAI;MACIA,EAAAA,QAAM;IACd,OAAM;IACN,SAAQ;IACP,kBAAgB,EAAA;IACjB,MAAK;IACJ,cAAY,EAAA,CAAA;IACZ,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAI,SAAA;IAClB,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAO,SAAA;IACrB,WAAS;IACT,YAAQ,AAAA,EAAA,QAAA,MAAE,EAAO,OAAA;IACjB,gBAAY,AAAA,EAAA,QAAA,GAAA,MAAE,EAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA,GAAA,CAAA;IACd,UAAM,AAAA,EAAA,QAAA,GAAA,MAAE,EAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA,GAAA,CAAA;QAGD,EAAA,CAAA,KAAA,EAAA,GADR,EAkBM,OAAA;IAhBH,KAAK,EAAA,CAAA,CAAO,CAAC;IACd,OAAM;IACL,aAAW,EAAA,CAAA,CAAO,CAAC;;IAEP,EAAA,SAAA,EAAA,GAAb,EAA2D,GAA3D,EAA2D;;KAAxC,OAAM;OAA0B,EAAA,KAAI,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA;IACvD,EAAqE,KAArE,GAAqE,EAAtB,EAAA,CAAA,CAAO,CAAC,OAAO,GAAA,CAAA;IAEtD,EAAA,CAAA,CAAO,CAAC,UAAA,EAAA,GADhB,EASU,GAAA;;KAPR,OAAM;KACN,SAAQ;KACR,MAAK;KACL,MAAK;KACJ,SAAO;;sBAEQ,CAAA,EAAA,EAAb,EAAA,KAAU,GAAA,CAAA,CAAA,CAAA;;;;GAInB,EAAiE,QAAjE,GAAiE,EAAhB,EAAA,CAAA,CAAM,GAAA,CAAA;GACvD,EAAmE,QAAnE,GAAmE,EAAnB,EAAA,CAAA,CAAS,GAAA,CAAA"}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { IconSource } from '../VIcon/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* What a confirmation can mean. There are two, and deliberately no more: a snackbar
|
|
4
|
-
* says either "done" or "that did not work". Success, warning and the accent are
|
|
5
|
-
* STATES, which is a notification's subject: reporting one here would blur the line
|
|
6
|
-
* the two components exist to draw.
|
|
7
|
-
*/
|
|
2
|
+
/** What a confirmation can mean. */
|
|
8
3
|
export type SnackbarTone = 'neutral' | 'danger';
|
|
9
4
|
/** Where the bar appears. Along the bottom edge only, where it is out of the content's way. */
|
|
10
5
|
export type SnackbarPlacement = 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
@@ -47,19 +42,11 @@ export declare const current: import('vue').ShallowRef<SnackbarItem | null, Snac
|
|
|
47
42
|
/**
|
|
48
43
|
* Raises a confirmation, replacing whatever was showing, and hands back an id that
|
|
49
44
|
* `dismissSnackbar` can use to take it away again before its time.
|
|
50
|
-
*
|
|
51
|
-
* The two defaults that belong to the VSnackbar, where the bar appears and how long it
|
|
52
|
-
* stays, are deliberately NOT resolved here: they are read when the bar is rendered, so
|
|
53
|
-
* the component remains the single source of truth for its own settings.
|
|
54
45
|
*/
|
|
55
46
|
export declare function snackbar(options: SnackbarOptions): number;
|
|
56
47
|
/**
|
|
57
|
-
* Takes a confirmation away, by its id or, called with no argument, whichever one is
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* Passing an id is not a formality: it is checked against the bar actually on screen, and
|
|
61
|
-
* a stale one is ignored. Without that test a countdown or a handler arriving late would
|
|
62
|
-
* close the bar that has just REPLACED the one it was about, and the reader would watch a
|
|
63
|
-
* fresh confirmation vanish in a fraction of its time.
|
|
48
|
+
* Takes a confirmation away, by its id or, called with no argument, whichever one is showing.
|
|
49
|
+
* Passing an id is not a formality: it is checked against the bar actually on screen, and a
|
|
50
|
+
* stale one is ignored.
|
|
64
51
|
*/
|
|
65
52
|
export declare function dismissSnackbar(id?: number): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","names":[],"sources":["../../../src/components/VSnackbar/state.ts"],"sourcesContent":["// @ssr @core\n/**\n *
|
|
1
|
+
{"version":3,"file":"state.js","names":[],"sources":["../../../src/components/VSnackbar/state.ts"],"sourcesContent":["// @ssr @core\n/**\n * That is what separates it from a notification, which reports a state: a notification stacks,\n * because two things can be true at once, whereas only the last action a reader took is worth\n * offering to undo. So there is at most one snackbar at a time, and raising a new one replaces\n * the one before it on the spot.\n */\n\nimport { shallowRef } from 'vue'\n\nimport type { IconSource } from '../VIcon/types'\nimport { isDev } from '../../utils/env'\n\n/** What a confirmation can mean. */\nexport type SnackbarTone = 'neutral' | 'danger'\n\n/** Where the bar appears. Along the bottom edge only, where it is out of the content's way. */\nexport type SnackbarPlacement = 'bottom-left' | 'bottom-center' | 'bottom-right'\n\n/** Everything one can say when raising a confirmation. */\nexport interface SnackbarOptions {\n /** What the confirmation says. One short sentence: there is no title to frame it. */\n message: string\n /** What it means, expressed as a colour: the plain inversion, or the failure colour. */\n tone?: SnackbarTone\n /**\n * An icon before the message. There is none by default, and none is deduced from the\n * tone: a confirmation is read, not scanned.\n */\n icon?: IconSource\n /**\n * How long it stays, in milliseconds. Setting it to 0 keeps it until it is replaced or\n * taken away by hand, and leaving it out takes the duration set on the VSnackbar.\n */\n duration?: number\n /** Which end of the bottom edge it appears at. Left out, it takes the one set on the VSnackbar. */\n placement?: SnackbarPlacement\n /**\n * What the single action does. Left out, the bar carries no button at all. Running it\n * always takes the bar away, so nothing has to be dismissed by hand afterwards.\n */\n action?: () => void\n /** The word drawn on that action. It falls back to the design system dictionary (\"Undo\"). */\n actionText?: string\n}\n\n/**\n * A confirmation once it is showing, with the choices the caller left out filled in and\n * an id of its own. Internal: it is what VSnackbar renders.\n */\nexport interface SnackbarItem extends SnackbarOptions {\n id: number\n tone: SnackbarTone\n}\n\n/** The bar currently showing, if any. Internal, read only by VSnackbar, and not public API. */\nexport const current = shallowRef<SnackbarItem | null>(null)\n\nlet nextId = 0\n\n/**\n * Raises a confirmation, replacing whatever was showing, and hands back an id that\n * `dismissSnackbar` can use to take it away again before its time.\n */\nexport function snackbar(options: SnackbarOptions): number {\n // @devwarn\n // The queue is module state, shared by every request a server renders: raised there, it\n // would reach another reader's page and differ from what the browser hydrates.\n if (isDev && typeof document === 'undefined')\n console.warn(\n '[snackbar] called during a server render: call it from a handler or a lifecycle hook that runs in the browser.',\n )\n const id = nextId++\n current.value = { tone: 'neutral', ...options, id }\n return id\n}\n\n/**\n * Takes a confirmation away, by its id or, called with no argument, whichever one is showing.\n * Passing an id is not a formality: it is checked against the bar actually on screen, and a\n * stale one is ignored.\n */\nexport function dismissSnackbar(id?: number): void {\n if (id === undefined || current.value?.id === id) current.value = null\n}\n"],"mappings":";;;AAwDA,IAAa,IAAU,EAAgC,IAAI,GAEvD,IAAS;AAMb,SAAgB,EAAS,GAAkC;CAIzD,AAAI,KAAS,OAAO,WAAa,OAC/B,QAAQ,KACN,gHACF;CACF,IAAM,IAAK;CAEX,OADA,EAAQ,QAAQ;EAAE,MAAM;EAAW,GAAG;EAAS;CAAG,GAC3C;AACT;AAOA,SAAgB,EAAgB,GAAmB;CACjD,CAAI,MAAO,KAAA,KAAa,EAAQ,OAAO,OAAO,OAAI,EAAQ,QAAQ;AACpE"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
interface SpinnerProps {
|
|
2
2
|
/**
|
|
3
|
-
* A size in pixels, as a number or a numeric string, understood exactly as VIcon's: it
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* text around it, which is how the spinner stays proportionate inside a button or a
|
|
7
|
-
* paragraph without being told anything.
|
|
3
|
+
* A size in pixels, as a number or a numeric string, understood exactly as VIcon's: it is the
|
|
4
|
+
* BOX the spinner occupies, not the diameter of the ring, which is drawn slightly smaller
|
|
5
|
+
* inside it.
|
|
8
6
|
*/
|
|
9
7
|
size?: number | string;
|
|
10
8
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VSpinner.js","names":[],"sources":["../../../src/components/VSpinner/VSpinner.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n *
|
|
1
|
+
{"version":3,"file":"VSpinner.js","names":[],"sources":["../../../src/components/VSpinner/VSpinner.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Animate the ring in CSS; JavaScript resolves its accessible status label and normalizes\n * icon-compatible dimensions.\n */\nimport { computed } from 'vue'\n\nimport { useMessages } from '../../i18n/state'\nimport { px } from '../../utils/css'\n\ninterface SpinnerProps {\n /**\n * A size in pixels, as a number or a numeric string, understood exactly as VIcon's: it is the\n * BOX the spinner occupies, not the diameter of the ring, which is drawn slightly smaller\n * inside it.\n */\n size?: number | string\n /**\n * What screen readers announce while it turns. It falls back to the wording of the\n * design system dictionary, in the current language.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SpinnerProps>(), {\n size: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst resolvedLabel = computed(() => props.label ?? m.value.common.loading)\n</script>\n\n<template>\n <span\n class=\"v-spinner\"\n :style=\"px(size) !== undefined ? { '--spinner-size': px(size) } : undefined\"\n role=\"status\"\n >\n <span class=\"v-spinner-circle\" aria-hidden=\"true\" />\n <span class=\"v-visually-hidden\">{{ resolvedLabel }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n .v-spinner {\n /* Draw at five sixths of the icon box to match the Material Symbols spinner's visible size. */\n --spinner-size: 1em;\n --spinner-ring: calc(var(--spinner-size) * 5 / 6);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--spinner-size);\n height: var(--spinner-size);\n }\n\n .v-spinner-circle {\n width: var(--spinner-ring);\n height: var(--spinner-ring);\n /*\n * The stroke is a tenth of the ring's diameter, the same source again: the glyph's outer\n * radius is 400 and its inner one 320, so its stroke is 80 of those 800. It stays in\n * proportion at every size, with a one-pixel floor below which the ring would disappear; a\n * floor that only bites under a 12px box, which no size of the design system reaches.\n */\n border: max(1px, calc(var(--spinner-ring) / 10)) solid\n color-mix(in oklab, currentcolor, transparent 75%);\n border-block-start-color: currentcolor;\n border-radius: var(--vectis-radius-pill);\n animation: v-spinner-spin var(--vectis-duration-1000) linear infinite;\n }\n\n /* Written here and not shared with VProgressCircular's identical turn: in the core sheet\n it cost every consumer the bytes, and the two copies only meet in an application that\n loads both components. The name carries the component, since keyframe names are global. */\n @keyframes v-spinner-spin {\n to {\n transform: rotate(1turn);\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-spinner-circle {\n animation-duration: var(--vectis-duration-3000);\n }\n }\n\n /*\n * The track is drawn in GrayText and the moving quarter keeps the forced text colour, which\n * `currentcolor` still reads here since only the circle opts out of the forcing.\n */\n @media (forced-colors: active) {\n .v-spinner-circle {\n forced-color-adjust: none;\n border-color: GrayText;\n border-block-start-color: currentcolor;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VSpinner.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VSpinner/VSpinner.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n *
|
|
1
|
+
{"version":3,"file":"VSpinner.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VSpinner/VSpinner.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Animate the ring in CSS; JavaScript resolves its accessible status label and normalizes\n * icon-compatible dimensions.\n */\nimport { computed } from 'vue'\n\nimport { useMessages } from '../../i18n/state'\nimport { px } from '../../utils/css'\n\ninterface SpinnerProps {\n /**\n * A size in pixels, as a number or a numeric string, understood exactly as VIcon's: it is the\n * BOX the spinner occupies, not the diameter of the ring, which is drawn slightly smaller\n * inside it.\n */\n size?: number | string\n /**\n * What screen readers announce while it turns. It falls back to the wording of the\n * design system dictionary, in the current language.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<SpinnerProps>(), {\n size: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst resolvedLabel = computed(() => props.label ?? m.value.common.loading)\n</script>\n\n<template>\n <span\n class=\"v-spinner\"\n :style=\"px(size) !== undefined ? { '--spinner-size': px(size) } : undefined\"\n role=\"status\"\n >\n <span class=\"v-spinner-circle\" aria-hidden=\"true\" />\n <span class=\"v-visually-hidden\">{{ resolvedLabel }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n .v-spinner {\n /* Draw at five sixths of the icon box to match the Material Symbols spinner's visible size. */\n --spinner-size: 1em;\n --spinner-ring: calc(var(--spinner-size) * 5 / 6);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n width: var(--spinner-size);\n height: var(--spinner-size);\n }\n\n .v-spinner-circle {\n width: var(--spinner-ring);\n height: var(--spinner-ring);\n /*\n * The stroke is a tenth of the ring's diameter, the same source again: the glyph's outer\n * radius is 400 and its inner one 320, so its stroke is 80 of those 800. It stays in\n * proportion at every size, with a one-pixel floor below which the ring would disappear; a\n * floor that only bites under a 12px box, which no size of the design system reaches.\n */\n border: max(1px, calc(var(--spinner-ring) / 10)) solid\n color-mix(in oklab, currentcolor, transparent 75%);\n border-block-start-color: currentcolor;\n border-radius: var(--vectis-radius-pill);\n animation: v-spinner-spin var(--vectis-duration-1000) linear infinite;\n }\n\n /* Written here and not shared with VProgressCircular's identical turn: in the core sheet\n it cost every consumer the bytes, and the two copies only meet in an application that\n loads both components. The name carries the component, since keyframe names are global. */\n @keyframes v-spinner-spin {\n to {\n transform: rotate(1turn);\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-spinner-circle {\n animation-duration: var(--vectis-duration-3000);\n }\n }\n\n /*\n * The track is drawn in GrayText and the moving quarter keeps the forced text colour, which\n * `currentcolor` still reads here since only the circle opts out of the forcing.\n */\n @media (forced-colors: active) {\n .v-spinner-circle {\n forced-color-adjust: none;\n border-color: GrayText;\n border-block-start-color: currentcolor;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;EAyBA,IAAM,IAAQ,GAKR,IAAI,EAAY,GAChB,IAAgB,QAAe,EAAM,SAAS,EAAE,MAAM,OAAO,OAAO;yBAIxE,EAOO,QAAA;GANL,OAAM;GACL,OAAK,EAAE,EAAA,CAAA,CAAE,CAAC,EAAA,IAAI,MAAM,KAAA,IAA6C,KAAA,IAApC,EAAA,kBAAuB,EAAA,CAAA,CAAE,CAAC,EAAA,IAAI,EAAA,CAAe;GAC3E,MAAK;eAEL,EAAoD,QAAA;GAA9C,OAAM;GAAmB,eAAY;gBAC3C,EAA0D,QAA1D,GAA0D,EAAvB,EAAA,KAAa,GAAA,CAAA,CAAA,GAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VSwitch.js","names":[],"sources":["../../../src/components/VSwitch/VSwitch.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A switch, for a setting that takes effect at once — where a checkbox states an\n * intention to be confirmed later.\n *\n * Underneath it is a native checkbox marked `role=\"switch\"`, so a screen reader\n * announces it as on or off rather than as ticked, and the keyboard and the form\n * behaviour come along for free. The track and the moving thumb are pure CSS,\n * following the input's checked state; the only JavaScript besides the v-model is the\n * refusal `readonly` asks for, which a checkbox has no native form of.\n */\n\nimport { ref } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the switch the label sits on. */\nexport type SwitchLabelPosition = 'start' | 'end'\n\ninterface SwitchProps {\n /** The text beside the switch, which names it. The default slot replaces it. */\n label?: string\n /**\n * A line of help under the label. It is tied to the switch for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /** Which side of the switch the label sits on. */\n labelPosition?: SwitchLabelPosition\n /**\n * Pushes the label and the switch to opposite ends of the line, the row taking the\n * full width available. This is the usual shape for a list of settings.\n */\n spread?: boolean\n /**\n * Marks the field as invalid, which rings the track and tells assistive technology\n * so. Use it for a rule the browser cannot check by itself; native validity is\n * already handled without it.\n */\n invalid?: boolean\n /** Makes the switch unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the setting without allowing it to be changed. The switch can still be\n * focused, is announced as read-only and is still submitted with its form; a click\n * or the Space key simply changes nothing.\n */\n readonly?: boolean\n}\n\nconst props = withDefaults(defineProps<SwitchProps>(), {\n label: undefined,\n hint: undefined,\n labelPosition: 'end',\n spread: false,\n invalid: false,\n disabled: false,\n readonly: false,\n})\n\n// @a11y\n// The root element is a layout box, so the attributes the consumer passes have to be\n// redirected onto the input: `name` for the form, and the aria-* for the element that\n// actually carries the switch role.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/** Whether the switch is on. It starts off. */\nconst model = defineModel<boolean>({ default: false })\n\ndefineSlots<{\n /** The label, when it needs more than the `label` prop's text. It is clickable. */\n default?(): unknown\n}>()\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser puts `checked` back and fires no `change`, so the v-model\n// never hears of it. It covers the Space key, which a checkbox turns into a click, and a\n// click on the label, which is forwarded to the input.\nfunction refuseWhenReadonly(event: MouseEvent) {\n if (props.readonly) event.preventDefault()\n}\n\n// The root is a wrapper, so a template ref on the component reaches the layout box and not\n// the control: these two are the way to the real switch.\ndefineExpose({\n /** Moves the focus to the real switch. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\" role=\"switch\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-switch v-choice\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-label-position=\"labelPosition\"\n :data-spread=\"spread ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <label class=\"v-choice-row\">\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n role=\"switch\"\n class=\"v-switch-input v-hidden-input\"\n :aria-invalid=\"invalid || undefined\"\n :aria-readonly=\"readonly || undefined\"\n v-bind=\"forwardedAttrs\"\n :disabled=\"disabled\"\n :aria-describedby=\"describedBy\"\n @click=\"refuseWhenReadonly\"\n />\n <span class=\"v-switch-track\" aria-hidden=\"true\">\n <span class=\"v-switch-thumb\" />\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-switch-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-switch-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /* The track's own measurements, which nothing outside this sheet reads. The row they\n sit in is `.v-choice`'s (styles/choice.css), and so is the focus ring.\n\n TRAP — the selector is COMPOUNDED with `.v-choice`, which is on this very element and\n comes from another sheet at the same (0,1,0). Nothing collides today, this rule setting\n only custom properties and that one only real ones, but the day either gains a\n `display`, a `font-family` or a `color` the winner would be whichever sheet the\n consumer's bundler happened to emit last. VCheckbox and VRadio declare no root rule at\n all, so this is the one place in the family exposed to it. */\n .v-choice.v-switch {\n --switch-track-w: var(--vectis-control-size-switch-w);\n --switch-track-h: var(--vectis-control-size-switch-h);\n --switch-pad: var(--vectis-control-size-switch-pad);\n }\n\n .v-switch-track {\n display: inline-flex;\n align-items: center;\n inline-size: var(--switch-track-w);\n block-size: var(--switch-track-h);\n padding: var(--switch-pad);\n background: var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-pill);\n transition: background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-switch-thumb {\n inline-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n block-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n background: var(--vectis-color-text-on-accent);\n border-radius: var(--vectis-radius-pill);\n box-shadow: var(--vectis-shadow-xs);\n /* The thumb travels through `margin-inline-start` rather than a translation,\n because a logical property follows the reading direction: in a right-to-left\n page the thumb then moves leftwards on its own, where a translateX would have\n to be mirrored by a second rule. The track's own box is stated logically for the\n same reason: it is the one box of the family that is not square, so a physical\n `width` would name the travel's axis in one page and the other axis in the next. */\n transition: margin-inline-start var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-accent);\n }\n\n .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n margin-inline-start: calc(var(--switch-track-w) - var(--switch-track-h));\n }\n\n /* The two hover steps VCheckbox and VRadio take, on the track's own colour: the 15 % mix\n when the switch is off, `accent-hover` when it is on. Both are needed, or a list of\n switches that are all ON answers the pointer nowhere and a live one cannot be told\n from a frozen one.\n\n Unlike its two siblings these exclude no invalid state, and they must not: an invalid\n switch is RINGED rather than tinted, so the hover repaints a property the invalid rule\n never touches and the two states simply add up. */\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled, :checked)\n + .v-switch-track {\n background: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled):checked\n + .v-switch-track {\n background: var(--vectis-color-accent-hover);\n }\n\n /* The same pair of selectors as VCheckbox and VRadio, so a switch reports a rule the\n browser checked and one only the consumer can. It is drawn as a RING rather than as\n a border colour: the track has no border to tint, and a shadow changes no geometry,\n where a border would eat into the padding the thumb travels in. Its width is the same\n token the other two give their border, so a consumer who thickens their controls\n thickens all three; the focus outline then starts where the ring ends, at its own\n offset, and the two never sit on the same pixels. */\n .v-switch-input:user-invalid + .v-switch-track,\n .v-switch-input[aria-invalid='true'] + .v-switch-track {\n box-shadow: 0 0 0 var(--vectis-control-border-width) var(--vectis-color-danger);\n }\n\n /* Read-only, and it has to say so whichever way the switch is pointing. An OFF switch\n sinks like a read-only checkbox — `surface-sunken` behind the 1px `border` ring the box\n and the dot draw as a real border, which a track has nowhere to put but inside itself —\n and its thumb takes the muted text colour, white being invisible on that pale track.\n An ON switch trades the accent for that same muted colour and gives the thumb the\n surface colour, which contrasts with it in both themes; the ring goes with the tint it\n was drawn against.\n\n `:where()` keeps all four rules at the weight of the disabled ones below, which\n therefore win by order on a switch that is both. */\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track {\n background: var(--vectis-color-surface-sunken);\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) var(--vectis-color-border);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-muted);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-text-muted);\n box-shadow: none;\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-surface);\n }\n\n /* The thumb takes text-subtle — the colour VCheckbox's disabled tick and VRadio's\n disabled dot take — which is what keeps it visible against the grey track in both\n themes. The greying of the row itself comes from `.v-choice`. */\n /* The `box-shadow: none` is not tidying: it is this rule's only way of cancelling the\n rings above it. A checkbox draws both its invalid verdict and its read-only sink with\n `border-color`, so its disabled rule overwrites them by naming the same property; here\n they are shadows, and left standing a disabled switch would go on wearing a danger ring\n around a grey track nobody can act on. */\n .v-switch-input:disabled + .v-switch-track {\n background: var(--vectis-color-surface-muted);\n box-shadow: none;\n }\n\n .v-switch-input:disabled + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-subtle);\n box-shadow: none;\n }\n\n /* Windows forced colors erase every author colour: a `background` becomes `Canvas` and a\n shadow is dropped altogether, so a switch loses BOTH its state and its outline, the\n track and the thumb being nothing but backgrounds. The track therefore draws its own\n edge as a real border here, inside the box so the thumb's travel is unchanged, and the\n system Highlight pair says \"on\" as it does on a pressed VToggleItem.\n\n The classes are repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n cannot repaint the track: `forced-color-adjust: none` takes the forcing off it, and a\n hover left winning would then paint its REAL colour over the system one. */\n @media (forced-colors: active) {\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input + .v-switch-track {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input\n + .v-switch-track\n .v-switch-thumb {\n forced-color-adjust: none;\n background: CanvasText;\n box-shadow: none;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track {\n background: Highlight;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) Highlight;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track\n .v-switch-thumb {\n background: HighlightText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track {\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) GrayText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track\n .v-switch-thumb {\n background: GrayText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-switch-track,\n .v-switch-thumb {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VSwitch.js","names":[],"sources":["../../../src/components/VSwitch/VSwitch.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Retain the native checkbox beneath switch styling for focus, forms and keyboard activation.\n * Keep the hint outside its accessible name.\n */\n\nimport { ref } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the switch the label sits on. */\nexport type SwitchLabelPosition = 'start' | 'end'\n\ninterface SwitchProps {\n /** The text beside the switch, which names it. The default slot replaces it. */\n label?: string\n /**\n * A line of help under the label. It is tied to the switch for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /** Which side of the switch the label sits on. */\n labelPosition?: SwitchLabelPosition\n /**\n * Pushes the label and the switch to opposite ends of the line, the row taking the\n * full width available. This is the usual shape for a list of settings.\n */\n spread?: boolean\n /**\n * Marks the field as invalid, which rings the track and tells assistive technology\n * so. Use it for a rule the browser cannot check by itself; native validity is\n * already handled without it.\n */\n invalid?: boolean\n /** Makes the switch unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the setting without allowing it to be changed. The switch can still be\n * focused, is announced as read-only and is still submitted with its form; a click\n * or the Space key simply changes nothing.\n */\n readonly?: boolean\n}\n\nconst props = withDefaults(defineProps<SwitchProps>(), {\n label: undefined,\n hint: undefined,\n labelPosition: 'end',\n spread: false,\n invalid: false,\n disabled: false,\n readonly: false,\n})\n\n// @a11y\n// The root element is a layout box, so the attributes the consumer passes have to be\n// redirected onto the input: `name` for the form, and the aria-* for the element that\n// actually carries the switch role.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/** Whether the switch is on. It starts off. */\nconst model = defineModel<boolean>({ default: false })\n\ndefineSlots<{\n /** The label, when it needs more than the `label` prop's text. It is clickable. */\n default?(): unknown\n}>()\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser puts `checked` back and fires no `change`, so the v-model\n// never hears of it.\nfunction refuseWhenReadonly(event: MouseEvent) {\n if (props.readonly) event.preventDefault()\n}\n\n// The root is a wrapper, so a template ref on the component reaches the layout box and not\n// the control: these two are the way to the real switch.\ndefineExpose({\n /** Moves the focus to the real switch. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\" role=\"switch\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-switch v-choice\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-label-position=\"labelPosition\"\n :data-spread=\"spread ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <label class=\"v-choice-row\">\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n role=\"switch\"\n class=\"v-switch-input v-hidden-input\"\n :aria-invalid=\"invalid || undefined\"\n :aria-readonly=\"readonly || undefined\"\n v-bind=\"forwardedAttrs\"\n :disabled=\"disabled\"\n :aria-describedby=\"describedBy\"\n @click=\"refuseWhenReadonly\"\n />\n <span class=\"v-switch-track\" aria-hidden=\"true\">\n <span class=\"v-switch-thumb\" />\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-switch-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-switch-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The selector is COMPOUNDED with `.v-choice`, which is on this very element and comes from\n * another sheet at the same (0,1,0). Nothing collides today, this rule setting only custom\n * properties and that one only real ones, but the day either gains a `display`, a\n * `font-family` or a `color` the winner would be whichever sheet the consumer's bundler\n * happened to emit last.\n */\n .v-choice.v-switch {\n --switch-track-w: var(--vectis-control-size-switch-w);\n --switch-track-h: var(--vectis-control-size-switch-h);\n --switch-pad: var(--vectis-control-size-switch-pad);\n }\n\n .v-switch-track {\n display: inline-flex;\n align-items: center;\n inline-size: var(--switch-track-w);\n block-size: var(--switch-track-h);\n padding: var(--switch-pad);\n background: var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-pill);\n transition: background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-switch-thumb {\n inline-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n block-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n background: var(--vectis-color-text-on-accent);\n border-radius: var(--vectis-radius-pill);\n box-shadow: var(--vectis-shadow-xs);\n /*\n * Move the thumb with logical margin so RTL reverses travel without a separate transform\n * rule.\n */\n transition: margin-inline-start var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-accent);\n }\n\n .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n margin-inline-start: calc(var(--switch-track-w) - var(--switch-track-h));\n }\n\n /*\n * Both are needed, or a list of switches that are all on answers the pointer nowhere and a\n * live one cannot be told from a frozen one. Unlike its two siblings these exclude no invalid\n * state, and they must not: an invalid switch is RINGED rather than tinted, so the hover\n * repaints a property the invalid rule never touches and the two states simply add up.\n */\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled, :checked)\n + .v-switch-track {\n background: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled):checked\n + .v-switch-track {\n background: var(--vectis-color-accent-hover);\n }\n\n /*\n * Its width is the same token the other two give their border, so a consumer who thickens\n * their controls thickens all three; the focus outline then starts where the ring ends, at\n * its own offset, and the two never sit on the same pixels.\n */\n .v-switch-input:user-invalid + .v-switch-track,\n .v-switch-input[aria-invalid='true'] + .v-switch-track {\n box-shadow: 0 0 0 var(--vectis-control-border-width) var(--vectis-color-danger);\n }\n\n /*\n * Read-only, and it has to say so whichever way the switch is pointing. An OFF switch sinks\n * like a read-only checkbox; `surface-sunken` behind the 1px `border` ring the box and the\n * dot draw as a real border, which a track has nowhere to put but inside itself; and its\n * thumb takes the muted text colour, white being invisible on that pale track.\n */\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track {\n background: var(--vectis-color-surface-sunken);\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) var(--vectis-color-border);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-muted);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-text-muted);\n box-shadow: none;\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-surface);\n }\n\n /*\n * The thumb takes text-subtle; the colour VCheckbox's disabled tick and VRadio's disabled dot\n * take; which keeps it visible against the grey track in both themes. The greying of the row\n * itself comes from `.v-choice`.\n */\n /*\n * A checkbox draws both its invalid verdict and its read-only sink with `border-color`, so\n * its disabled rule overwrites them by naming the same property; here they are shadows, and\n * left standing a disabled switch would go on wearing a danger ring around a grey track\n * nobody can act on.\n */\n .v-switch-input:disabled + .v-switch-track {\n background: var(--vectis-color-surface-muted);\n box-shadow: none;\n }\n\n .v-switch-input:disabled + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-subtle);\n box-shadow: none;\n }\n\n /*\n * The classes are repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n * cannot repaint the track: `forced-color-adjust: none` takes the forcing off it, and a hover\n * left winning would then paint its real colour over the system one.\n */\n @media (forced-colors: active) {\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input + .v-switch-track {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input\n + .v-switch-track\n .v-switch-thumb {\n forced-color-adjust: none;\n background: CanvasText;\n box-shadow: none;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track {\n background: Highlight;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) Highlight;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track\n .v-switch-thumb {\n background: HighlightText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track {\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) GrayText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track\n .v-switch-thumb {\n background: GrayText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-switch-track,\n .v-switch-thumb {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VSwitch.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VSwitch/VSwitch.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A switch, for a setting that takes effect at once — where a checkbox states an\n * intention to be confirmed later.\n *\n * Underneath it is a native checkbox marked `role=\"switch\"`, so a screen reader\n * announces it as on or off rather than as ticked, and the keyboard and the form\n * behaviour come along for free. The track and the moving thumb are pure CSS,\n * following the input's checked state; the only JavaScript besides the v-model is the\n * refusal `readonly` asks for, which a checkbox has no native form of.\n */\n\nimport { ref } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the switch the label sits on. */\nexport type SwitchLabelPosition = 'start' | 'end'\n\ninterface SwitchProps {\n /** The text beside the switch, which names it. The default slot replaces it. */\n label?: string\n /**\n * A line of help under the label. It is tied to the switch for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /** Which side of the switch the label sits on. */\n labelPosition?: SwitchLabelPosition\n /**\n * Pushes the label and the switch to opposite ends of the line, the row taking the\n * full width available. This is the usual shape for a list of settings.\n */\n spread?: boolean\n /**\n * Marks the field as invalid, which rings the track and tells assistive technology\n * so. Use it for a rule the browser cannot check by itself; native validity is\n * already handled without it.\n */\n invalid?: boolean\n /** Makes the switch unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the setting without allowing it to be changed. The switch can still be\n * focused, is announced as read-only and is still submitted with its form; a click\n * or the Space key simply changes nothing.\n */\n readonly?: boolean\n}\n\nconst props = withDefaults(defineProps<SwitchProps>(), {\n label: undefined,\n hint: undefined,\n labelPosition: 'end',\n spread: false,\n invalid: false,\n disabled: false,\n readonly: false,\n})\n\n// @a11y\n// The root element is a layout box, so the attributes the consumer passes have to be\n// redirected onto the input: `name` for the form, and the aria-* for the element that\n// actually carries the switch role.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/** Whether the switch is on. It starts off. */\nconst model = defineModel<boolean>({ default: false })\n\ndefineSlots<{\n /** The label, when it needs more than the `label` prop's text. It is clickable. */\n default?(): unknown\n}>()\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser puts `checked` back and fires no `change`, so the v-model\n// never hears of it. It covers the Space key, which a checkbox turns into a click, and a\n// click on the label, which is forwarded to the input.\nfunction refuseWhenReadonly(event: MouseEvent) {\n if (props.readonly) event.preventDefault()\n}\n\n// The root is a wrapper, so a template ref on the component reaches the layout box and not\n// the control: these two are the way to the real switch.\ndefineExpose({\n /** Moves the focus to the real switch. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\" role=\"switch\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-switch v-choice\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-label-position=\"labelPosition\"\n :data-spread=\"spread ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <label class=\"v-choice-row\">\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n role=\"switch\"\n class=\"v-switch-input v-hidden-input\"\n :aria-invalid=\"invalid || undefined\"\n :aria-readonly=\"readonly || undefined\"\n v-bind=\"forwardedAttrs\"\n :disabled=\"disabled\"\n :aria-describedby=\"describedBy\"\n @click=\"refuseWhenReadonly\"\n />\n <span class=\"v-switch-track\" aria-hidden=\"true\">\n <span class=\"v-switch-thumb\" />\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-switch-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-switch-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /* The track's own measurements, which nothing outside this sheet reads. The row they\n sit in is `.v-choice`'s (styles/choice.css), and so is the focus ring.\n\n TRAP — the selector is COMPOUNDED with `.v-choice`, which is on this very element and\n comes from another sheet at the same (0,1,0). Nothing collides today, this rule setting\n only custom properties and that one only real ones, but the day either gains a\n `display`, a `font-family` or a `color` the winner would be whichever sheet the\n consumer's bundler happened to emit last. VCheckbox and VRadio declare no root rule at\n all, so this is the one place in the family exposed to it. */\n .v-choice.v-switch {\n --switch-track-w: var(--vectis-control-size-switch-w);\n --switch-track-h: var(--vectis-control-size-switch-h);\n --switch-pad: var(--vectis-control-size-switch-pad);\n }\n\n .v-switch-track {\n display: inline-flex;\n align-items: center;\n inline-size: var(--switch-track-w);\n block-size: var(--switch-track-h);\n padding: var(--switch-pad);\n background: var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-pill);\n transition: background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-switch-thumb {\n inline-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n block-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n background: var(--vectis-color-text-on-accent);\n border-radius: var(--vectis-radius-pill);\n box-shadow: var(--vectis-shadow-xs);\n /* The thumb travels through `margin-inline-start` rather than a translation,\n because a logical property follows the reading direction: in a right-to-left\n page the thumb then moves leftwards on its own, where a translateX would have\n to be mirrored by a second rule. The track's own box is stated logically for the\n same reason: it is the one box of the family that is not square, so a physical\n `width` would name the travel's axis in one page and the other axis in the next. */\n transition: margin-inline-start var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-accent);\n }\n\n .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n margin-inline-start: calc(var(--switch-track-w) - var(--switch-track-h));\n }\n\n /* The two hover steps VCheckbox and VRadio take, on the track's own colour: the 15 % mix\n when the switch is off, `accent-hover` when it is on. Both are needed, or a list of\n switches that are all ON answers the pointer nowhere and a live one cannot be told\n from a frozen one.\n\n Unlike its two siblings these exclude no invalid state, and they must not: an invalid\n switch is RINGED rather than tinted, so the hover repaints a property the invalid rule\n never touches and the two states simply add up. */\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled, :checked)\n + .v-switch-track {\n background: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled):checked\n + .v-switch-track {\n background: var(--vectis-color-accent-hover);\n }\n\n /* The same pair of selectors as VCheckbox and VRadio, so a switch reports a rule the\n browser checked and one only the consumer can. It is drawn as a RING rather than as\n a border colour: the track has no border to tint, and a shadow changes no geometry,\n where a border would eat into the padding the thumb travels in. Its width is the same\n token the other two give their border, so a consumer who thickens their controls\n thickens all three; the focus outline then starts where the ring ends, at its own\n offset, and the two never sit on the same pixels. */\n .v-switch-input:user-invalid + .v-switch-track,\n .v-switch-input[aria-invalid='true'] + .v-switch-track {\n box-shadow: 0 0 0 var(--vectis-control-border-width) var(--vectis-color-danger);\n }\n\n /* Read-only, and it has to say so whichever way the switch is pointing. An OFF switch\n sinks like a read-only checkbox — `surface-sunken` behind the 1px `border` ring the box\n and the dot draw as a real border, which a track has nowhere to put but inside itself —\n and its thumb takes the muted text colour, white being invisible on that pale track.\n An ON switch trades the accent for that same muted colour and gives the thumb the\n surface colour, which contrasts with it in both themes; the ring goes with the tint it\n was drawn against.\n\n `:where()` keeps all four rules at the weight of the disabled ones below, which\n therefore win by order on a switch that is both. */\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track {\n background: var(--vectis-color-surface-sunken);\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) var(--vectis-color-border);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-muted);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-text-muted);\n box-shadow: none;\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-surface);\n }\n\n /* The thumb takes text-subtle — the colour VCheckbox's disabled tick and VRadio's\n disabled dot take — which is what keeps it visible against the grey track in both\n themes. The greying of the row itself comes from `.v-choice`. */\n /* The `box-shadow: none` is not tidying: it is this rule's only way of cancelling the\n rings above it. A checkbox draws both its invalid verdict and its read-only sink with\n `border-color`, so its disabled rule overwrites them by naming the same property; here\n they are shadows, and left standing a disabled switch would go on wearing a danger ring\n around a grey track nobody can act on. */\n .v-switch-input:disabled + .v-switch-track {\n background: var(--vectis-color-surface-muted);\n box-shadow: none;\n }\n\n .v-switch-input:disabled + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-subtle);\n box-shadow: none;\n }\n\n /* Windows forced colors erase every author colour: a `background` becomes `Canvas` and a\n shadow is dropped altogether, so a switch loses BOTH its state and its outline, the\n track and the thumb being nothing but backgrounds. The track therefore draws its own\n edge as a real border here, inside the box so the thumb's travel is unchanged, and the\n system Highlight pair says \"on\" as it does on a pressed VToggleItem.\n\n The classes are repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n cannot repaint the track: `forced-color-adjust: none` takes the forcing off it, and a\n hover left winning would then paint its REAL colour over the system one. */\n @media (forced-colors: active) {\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input + .v-switch-track {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input\n + .v-switch-track\n .v-switch-thumb {\n forced-color-adjust: none;\n background: CanvasText;\n box-shadow: none;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track {\n background: Highlight;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) Highlight;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track\n .v-switch-thumb {\n background: HighlightText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track {\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) GrayText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track\n .v-switch-thumb {\n background: GrayText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-switch-track,\n .v-switch-thumb {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmDA,IAAM,IAAQ,GAeR,EAAE,UAAO,cAAW,cAAW,sBAAmB,EAAa,GAC/D,EAAE,WAAQ,mBAAgB,EAAY,SAAa,CAAC,CAAC,EAAM,IAAI,GAG/D,IAAQ,EAAoB,GAAA,YAAmB,GAO/C,IAAU,EAA6B,IAAI;EAOjD,SAAS,EAAmB,GAAmB;GAC7C,AAAI,EAAM,YAAU,EAAM,eAAe;EAC3C;SAIA,EAAa;GAEX,QAAQ,MAA2B,EAAQ,OAAO,MAAM,CAAO;GAE/D,IAAI;EACN,CAAC,mBAIC,EA8BO,QAAA;GA7BL,OAAK,EAAA,CAAC,qBACE,EAAA,CAAA,CAAS,CAAA;GAChB,OAAK,EAAE,EAAA,CAAA,CAAS;GAChB,uBAAqB,EAAA;GACrB,eAAa,EAAA,SAAM,KAAQ,KAAA;GAC3B,iBAAe,EAAA,WAAQ,KAAQ,KAAA;MAEhC,EAoBQ,SApBR,GAoBQ;KAnBN,EAYE,SAZF,EAYE;aAXI;IAAJ,KAAI;6CACU,QAAA;IACd,MAAK;IACL,MAAK;IACL,OAAM;IACL,gBAAc,EAAA,WAAW,KAAA;IACzB,iBAAe,EAAA,YAAY,KAAA;MACpB,EAAA,CAAA,GAAc;IACrB,UAAU,EAAA;IACV,oBAAkB,EAAA,CAAA;IAClB,SAAO;0BATC,EAAA,KAAK,CAAA,CAAA;YAWhB,EAEO,QAAA;IAFD,OAAM;IAAiB,eAAY;OACvC,EAA+B,QAAA,EAAzB,OAAM,iBAAgB,CAAA,CAAA,GAAA,EAAA;GAElBA,EAAAA,OAAO,WAAW,EAAA,SAAA,EAAA,GAA9B,EAEO,QAFP,GAEO,CADL,EAAwB,EAAA,QAAA,WAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA;MAGN,EAAA,QAAA,EAAA,GAAZ,EAAoF,QAAA;;GAAjE,IAAI,EAAA,CAAA;GAAQ,OAAM;OAAiC,EAAA,IAAI,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,IAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VSwitch.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VSwitch/VSwitch.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Retain the native checkbox beneath switch styling for focus, forms and keyboard activation.\n * Keep the hint outside its accessible name.\n */\n\nimport { ref } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the switch the label sits on. */\nexport type SwitchLabelPosition = 'start' | 'end'\n\ninterface SwitchProps {\n /** The text beside the switch, which names it. The default slot replaces it. */\n label?: string\n /**\n * A line of help under the label. It is tied to the switch for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /** Which side of the switch the label sits on. */\n labelPosition?: SwitchLabelPosition\n /**\n * Pushes the label and the switch to opposite ends of the line, the row taking the\n * full width available. This is the usual shape for a list of settings.\n */\n spread?: boolean\n /**\n * Marks the field as invalid, which rings the track and tells assistive technology\n * so. Use it for a rule the browser cannot check by itself; native validity is\n * already handled without it.\n */\n invalid?: boolean\n /** Makes the switch unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the setting without allowing it to be changed. The switch can still be\n * focused, is announced as read-only and is still submitted with its form; a click\n * or the Space key simply changes nothing.\n */\n readonly?: boolean\n}\n\nconst props = withDefaults(defineProps<SwitchProps>(), {\n label: undefined,\n hint: undefined,\n labelPosition: 'end',\n spread: false,\n invalid: false,\n disabled: false,\n readonly: false,\n})\n\n// @a11y\n// The root element is a layout box, so the attributes the consumer passes have to be\n// redirected onto the input: `name` for the form, and the aria-* for the element that\n// actually carries the switch role.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/** Whether the switch is on. It starts off. */\nconst model = defineModel<boolean>({ default: false })\n\ndefineSlots<{\n /** The label, when it needs more than the `label` prop's text. It is clickable. */\n default?(): unknown\n}>()\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser puts `checked` back and fires no `change`, so the v-model\n// never hears of it.\nfunction refuseWhenReadonly(event: MouseEvent) {\n if (props.readonly) event.preventDefault()\n}\n\n// The root is a wrapper, so a template ref on the component reaches the layout box and not\n// the control: these two are the way to the real switch.\ndefineExpose({\n /** Moves the focus to the real switch. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\" role=\"switch\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-switch v-choice\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-label-position=\"labelPosition\"\n :data-spread=\"spread ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <label class=\"v-choice-row\">\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n role=\"switch\"\n class=\"v-switch-input v-hidden-input\"\n :aria-invalid=\"invalid || undefined\"\n :aria-readonly=\"readonly || undefined\"\n v-bind=\"forwardedAttrs\"\n :disabled=\"disabled\"\n :aria-describedby=\"describedBy\"\n @click=\"refuseWhenReadonly\"\n />\n <span class=\"v-switch-track\" aria-hidden=\"true\">\n <span class=\"v-switch-thumb\" />\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-switch-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-switch-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The selector is COMPOUNDED with `.v-choice`, which is on this very element and comes from\n * another sheet at the same (0,1,0). Nothing collides today, this rule setting only custom\n * properties and that one only real ones, but the day either gains a `display`, a\n * `font-family` or a `color` the winner would be whichever sheet the consumer's bundler\n * happened to emit last.\n */\n .v-choice.v-switch {\n --switch-track-w: var(--vectis-control-size-switch-w);\n --switch-track-h: var(--vectis-control-size-switch-h);\n --switch-pad: var(--vectis-control-size-switch-pad);\n }\n\n .v-switch-track {\n display: inline-flex;\n align-items: center;\n inline-size: var(--switch-track-w);\n block-size: var(--switch-track-h);\n padding: var(--switch-pad);\n background: var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-pill);\n transition: background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-switch-thumb {\n inline-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n block-size: calc(var(--switch-track-h) - var(--switch-pad) * 2);\n background: var(--vectis-color-text-on-accent);\n border-radius: var(--vectis-radius-pill);\n box-shadow: var(--vectis-shadow-xs);\n /*\n * Move the thumb with logical margin so RTL reverses travel without a separate transform\n * rule.\n */\n transition: margin-inline-start var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-accent);\n }\n\n .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n margin-inline-start: calc(var(--switch-track-w) - var(--switch-track-h));\n }\n\n /*\n * Both are needed, or a list of switches that are all on answers the pointer nowhere and a\n * live one cannot be told from a frozen one. Unlike its two siblings these exclude no invalid\n * state, and they must not: an invalid switch is RINGED rather than tinted, so the hover\n * repaints a property the invalid rule never touches and the two states simply add up.\n */\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled, :checked)\n + .v-switch-track {\n background: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-switch:not([data-readonly])\n .v-choice-row:hover\n .v-switch-input:not(:disabled):checked\n + .v-switch-track {\n background: var(--vectis-color-accent-hover);\n }\n\n /*\n * Its width is the same token the other two give their border, so a consumer who thickens\n * their controls thickens all three; the focus outline then starts where the ring ends, at\n * its own offset, and the two never sit on the same pixels.\n */\n .v-switch-input:user-invalid + .v-switch-track,\n .v-switch-input[aria-invalid='true'] + .v-switch-track {\n box-shadow: 0 0 0 var(--vectis-control-border-width) var(--vectis-color-danger);\n }\n\n /*\n * Read-only, and it has to say so whichever way the switch is pointing. An OFF switch sinks\n * like a read-only checkbox; `surface-sunken` behind the 1px `border` ring the box and the\n * dot draw as a real border, which a track has nowhere to put but inside itself; and its\n * thumb takes the muted text colour, white being invisible on that pale track.\n */\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track {\n background: var(--vectis-color-surface-sunken);\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) var(--vectis-color-border);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-muted);\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track {\n background: var(--vectis-color-text-muted);\n box-shadow: none;\n }\n\n :where(.v-switch[data-readonly]) .v-switch-input:checked + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-surface);\n }\n\n /*\n * The thumb takes text-subtle; the colour VCheckbox's disabled tick and VRadio's disabled dot\n * take; which keeps it visible against the grey track in both themes. The greying of the row\n * itself comes from `.v-choice`.\n */\n /*\n * A checkbox draws both its invalid verdict and its read-only sink with `border-color`, so\n * its disabled rule overwrites them by naming the same property; here they are shadows, and\n * left standing a disabled switch would go on wearing a danger ring around a grey track\n * nobody can act on.\n */\n .v-switch-input:disabled + .v-switch-track {\n background: var(--vectis-color-surface-muted);\n box-shadow: none;\n }\n\n .v-switch-input:disabled + .v-switch-track .v-switch-thumb {\n background: var(--vectis-color-text-subtle);\n box-shadow: none;\n }\n\n /*\n * The classes are repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n * cannot repaint the track: `forced-color-adjust: none` takes the forcing off it, and a hover\n * left winning would then paint its real colour over the system one.\n */\n @media (forced-colors: active) {\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input + .v-switch-track {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) CanvasText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input\n + .v-switch-track\n .v-switch-thumb {\n forced-color-adjust: none;\n background: CanvasText;\n box-shadow: none;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track {\n background: Highlight;\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) Highlight;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:checked:not(\n :disabled\n )\n + .v-switch-track\n .v-switch-thumb {\n background: HighlightText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track {\n box-shadow: inset 0 0 0 var(--vectis-control-border-width) GrayText;\n }\n\n .v-switch-input.v-switch-input.v-switch-input.v-switch-input.v-switch-input:disabled\n + .v-switch-track\n .v-switch-thumb {\n background: GrayText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-switch-track,\n .v-switch-thumb {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CA,IAAM,IAAQ,GAeR,EAAE,UAAO,cAAW,cAAW,sBAAmB,EAAa,GAC/D,EAAE,WAAQ,mBAAgB,EAAY,SAAa,CAAC,CAAC,EAAM,IAAI,GAG/D,IAAQ,EAAoB,GAAA,YAAmB,GAO/C,IAAU,EAA6B,IAAI;EAMjD,SAAS,EAAmB,GAAmB;GAC7C,AAAI,EAAM,YAAU,EAAM,eAAe;EAC3C;SAIA,EAAa;GAEX,QAAQ,MAA2B,EAAQ,OAAO,MAAM,CAAO;GAE/D,IAAI;EACN,CAAC,mBAIC,EA8BO,QAAA;GA7BL,OAAK,EAAA,CAAC,qBACE,EAAA,CAAA,CAAS,CAAA;GAChB,OAAK,EAAE,EAAA,CAAA,CAAS;GAChB,uBAAqB,EAAA;GACrB,eAAa,EAAA,SAAM,KAAQ,KAAA;GAC3B,iBAAe,EAAA,WAAQ,KAAQ,KAAA;MAEhC,EAoBQ,SApBR,GAoBQ;KAnBN,EAYE,SAZF,EAYE;aAXI;IAAJ,KAAI;6CACU,QAAA;IACd,MAAK;IACL,MAAK;IACL,OAAM;IACL,gBAAc,EAAA,WAAW,KAAA;IACzB,iBAAe,EAAA,YAAY,KAAA;MACpB,EAAA,CAAA,GAAc;IACrB,UAAU,EAAA;IACV,oBAAkB,EAAA,CAAA;IAClB,SAAO;0BATC,EAAA,KAAK,CAAA,CAAA;YAWhB,EAEO,QAAA;IAFD,OAAM;IAAiB,eAAY;OACvC,EAA+B,QAAA,EAAzB,OAAM,iBAAgB,CAAA,CAAA,GAAA,EAAA;GAElBA,EAAAA,OAAO,WAAW,EAAA,SAAA,EAAA,GAA9B,EAEO,QAFP,GAEO,CADL,EAAwB,EAAA,QAAA,WAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA;MAGN,EAAA,QAAA,EAAA,GAAZ,EAAoF,QAAA;;GAAjE,IAAI,EAAA,CAAA;GAAQ,OAAM;OAAiC,EAAA,IAAI,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,IAAA,CAAA"}
|
|
@@ -14,11 +14,7 @@ interface TabProps {
|
|
|
14
14
|
iconEnd?: IconSource;
|
|
15
15
|
/** Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis). */
|
|
16
16
|
iconFilled?: boolean;
|
|
17
|
-
/**
|
|
18
|
-
* Makes the tab unusable: it no longer responds, the arrow keys skip over it, and it
|
|
19
|
-
* greys out through the colour tokens. A VTabs set `disabled` disables every tab, this
|
|
20
|
-
* one included, whatever this says.
|
|
21
|
-
*/
|
|
17
|
+
/** A VTabs set `disabled` disables every tab, this one included, whatever this says. */
|
|
22
18
|
disabled?: boolean;
|
|
23
19
|
}
|
|
24
20
|
type __VLS_Slots = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VTab.js","names":[],"sources":["../../../src/components/VTabs/VTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One tab. It IS a VButton — the colour, the size, the focus ring and the disabled\n * state all come from there — and what this component adds is the handful of\n * attributes that make a button part of a tab row for assistive technology.\n *\n * Used outside a VTabs it renders as a quiet neutral button at VButton's own size, and as\n * nothing more: the row's decisions all come from the context, and a tab with no row takes\n * VButton's defaults rather than a copy of the row's. It carries no `tab` role either,\n * which would be an orphan without a tablist, nor the roving `tabindex` that would take\n * it out of the tab order.\n */\n\nimport { computed, inject } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport { tabsKey } from './context'\nimport type { ItemValue } from '../../types'\n\ninterface TabProps {\n /**\n * What this tab is called in code. The panel carrying the same value is the one it\n * shows, and it is also what the v-model holds when this tab is selected.\n */\n value: ItemValue\n /** The visible label. The default slot replaces it. */\n label?: string\n /** An icon before the label: an icon name, or an explicit render. */\n iconStart?: IconSource\n /** An icon after the label, for a count or a state the tab carries. */\n iconEnd?: IconSource\n /** Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis). */\n iconFilled?: boolean\n /**\n * Makes the tab unusable: it no longer responds, the arrow keys skip over it, and it\n * greys out through the colour tokens. A VTabs set `disabled` disables every tab, this\n * one included, whatever this says.\n */\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<TabProps>(), {\n label: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n disabled: false,\n})\n\nconst slots = defineSlots<{\n /** The content of the tab, replacing the `label` prop. */\n default?(): unknown\n /** Content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n}>()\n\nconst tabs = inject(tabsKey, null)\n\nconst selected = computed(() => tabs != null && tabs.value === props.value)\nconst tabId = computed(() => tabs?.tabId(props.value))\nconst panelId = computed(() => (tabs?.hasPanels ? tabs.panelId(props.value) : undefined))\n// Cumulative, the VButtonGroup rule: a row switched off has no tab that can opt back in.\nconst resolvedDisabled = computed(() => props.disabled || Boolean(tabs?.disabled))\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\n/**\n * An icon, on either side, and no label at all: the tab becomes a square, like a VIconButton.\n * The same definition as VChip's, and the square itself is VButton's `[data-icon-only]` rule.\n */\nfunction iconOnly() {\n return (\n !props.label &&\n !slots.default &&\n Boolean(slots.start || slots.end || props.iconStart || props.iconEnd)\n )\n}\n\n// @keyboard @a11y\n/*\n * Selecting a tab the moment it takes focus, which is what `automatic` activation\n * means. It lives here rather than in the row's keyboard handler, because this\n * component knows its own value: the handler would have to read it back from a DOM\n * attribute, and would lose the distinction between the number 1 and the string \"1\"\n * doing so.\n */\nfunction onFocus() {\n if (tabs?.activation === 'automatic' && !resolvedDisabled.value) tabs.select(props.value)\n}\n</script>\n\n<template>\n <VButton\n :id=\"tabId\"\n class=\"v-tab\"\n :role=\"tabs ? 'tab' : undefined\"\n :aria-selected=\"tabs ? String(selected) : undefined\"\n :aria-controls=\"panelId\"\n :tabindex=\"tabs ? (selected ? 0 : -1) : undefined\"\n variant=\"ghost\"\n :elevated=\"selected && tabs?.variant === 'inset'\"\n :tone=\"selected && tabs ? tabs.tone : 'neutral'\"\n :size=\"tabs?.size\"\n :compact=\"tabs?.compact\"\n :disabled=\"resolvedDisabled\"\n :data-icon-only=\"iconOnly() ? '' : undefined\"\n @click=\"tabs?.select(value)\"\n @focus=\"onFocus\"\n >\n <template v-if=\"iconStart || $slots.start\" #start>\n <slot name=\"start\"><VIcon v-bind=\"iconProps(iconStart!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <template v-if=\"iconEnd || $slots.end\" #end>\n <slot name=\"end\"><VIcon v-bind=\"iconProps(iconEnd!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <!-- The label is wrapped in an element of its own so that it can be truncated:\n an ellipsis cannot be applied to the bare text of a flex container, and tabs\n sharing the bar equally have to be able to cut their labels short -->\n <span v-if=\"!iconOnly()\" class=\"v-tab-label\"\n ><slot>{{ label }}</slot></span\n >\n </VButton>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * These rules override a VButton, so they are qualified by an attribute that button\n * always renders. That is what makes them win whatever order the two sheets end up\n * in — the same device VIconButton uses.\n */\n .v-tab[data-size] {\n /* The indicator below is positioned against this box, and a button declares no\n position of its own. */\n position: relative;\n /*\n * The tabs are never compressed, and that is precisely WHAT makes the row\n * overflow. Allowed to shrink, they would squeeze down to their smallest possible\n * width and scrolling would never come into play at all.\n */\n flex: none;\n white-space: nowrap;\n }\n\n /*\n * The focus ring is drawn INSIDE the tab. The row scrolls, and a ring sitting\n * outside the tab would be cropped by that scrolling box on the first and last\n * tabs.\n */\n .v-tab[data-size]:focus-visible {\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-tab-label {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n flex: 1 1 0;\n min-inline-size: 0;\n }\n\n /* A box nested inside a rounded one needs a smaller radius to look concentric: the\n track's own, less the padding between them. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n border-radius: calc(var(--vectis-radius-surface) - var(--vectis-space-1));\n }\n\n /*\n * On a track the tabs are contiguous SEGMENTS with square corners: what carves the\n * bar up is the rule and the indicator, not the silhouette of each button. Rounded\n * corners would leave pale notches above the rule, and a gap would break the row of\n * hover highlights into islands.\n *\n * The gap is removed on the row rather than here, the row being what declares it;\n * the bar's own gap stays, since that one separates the scroll buttons and not the\n * tabs.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size] {\n border-radius: 0;\n }\n\n /*\n * Inside a card the row takes its rounded corners back at the ENDS only, the seams\n * between tabs staying square — the VButtonGroup idiom, applied to the one edge the\n * track does not occupy.\n *\n * The radius is the CARD's inner one — its own, less the border it sits behind — and\n * not the button's. The bar spends no gutter, so an end tab lands exactly on a corner\n * of the frame and has to follow that curve: a button radius, smaller, would leave a\n * sliver of card showing outside the tab as soon as it painted a background.\n *\n * TRAP — the selectors ask for the first and last of their TYPE rather than the\n * first and last child, because the scroll markers are the row's real first and last\n * children. They are spans, the tabs being the only buttons, which is what makes the\n * distinction work.\n */\n .v-tabs[data-variant='outlined'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size]:first-of-type {\n border-start-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n .v-tabs[data-variant='outlined'][data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-start-end-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /* Turned vertical, the track has migrated to the end edge, so the free edge is the\n start one: that is where the ends of the column round their corners. */\n .v-tabs[data-variant='outlined'][data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-end-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /*\n * The bar marking the selected tab, in the two framed variants. It is painted in the\n * tab's own text colour rather than by reading one of VButton's private variables:\n * that way it follows the selected tone AND the grey of a disabled tab on its own,\n * without this file having to know anything about either.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n content: '';\n position: absolute;\n inset-inline: 0;\n inset-block-end: 0;\n block-size: var(--vectis-control-size-tab-indicator);\n background: currentColor;\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n inset-block: 0;\n inset-inline: auto;\n inset-inline-start: 0;\n block-size: auto;\n inline-size: var(--vectis-control-size-tab-indicator);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size][aria-selected='true']::after {\n opacity: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n transition: none;\n }\n }\n\n /*\n * Windows forced colors replace every background with the page's, so the selected tab would look\n * exactly like the others there: the fill is its only cue, the state being in an ARIA\n * attribute a sighted reader never sees. It takes the system's own selection pair\n * instead, and opts out of the forcing for that element alone so the pair is painted.\n *\n * TRAP — the class is repeated to reach (0,6,0). The variant, hover and active rules\n * reach (0,5,0), and with the forcing turned off any of them that still won would paint\n * its tone over the selection, with HighlightText on top of it.\n */\n @media (forced-colors: active) {\n .v-tab.v-tab.v-tab.v-tab[aria-selected='true']:not(:disabled) {\n forced-color-adjust: none;\n background-color: Highlight;\n color: HighlightText;\n border-color: Highlight;\n outline-color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VTab.js","names":[],"sources":["../../../src/components/VTabs/VTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Reuse VButton for activation; tab context supplies selection, IDs and roving tabindex without\n * a mounted-child registry.\n */\n\nimport { computed, inject } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport { tabsKey } from './context'\nimport type { ItemValue } from '../../types'\n\ninterface TabProps {\n /**\n * What this tab is called in code. The panel carrying the same value is the one it\n * shows, and it is also what the v-model holds when this tab is selected.\n */\n value: ItemValue\n /** The visible label. The default slot replaces it. */\n label?: string\n /** An icon before the label: an icon name, or an explicit render. */\n iconStart?: IconSource\n /** An icon after the label, for a count or a state the tab carries. */\n iconEnd?: IconSource\n /** Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis). */\n iconFilled?: boolean\n /** A VTabs set `disabled` disables every tab, this one included, whatever this says. */\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<TabProps>(), {\n label: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n disabled: false,\n})\n\nconst slots = defineSlots<{\n /** The content of the tab, replacing the `label` prop. */\n default?(): unknown\n /** Content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n}>()\n\nconst tabs = inject(tabsKey, null)\n\nconst selected = computed(() => tabs != null && tabs.value === props.value)\nconst tabId = computed(() => tabs?.tabId(props.value))\nconst panelId = computed(() => (tabs?.hasPanels ? tabs.panelId(props.value) : undefined))\nconst resolvedDisabled = computed(() => props.disabled || Boolean(tabs?.disabled))\n\n// A function read by the template, never a `computed`: `slots` is not reactive, so a computed\n// would keep its first answer while a slot behind a `v-if` comes and goes.\n/**\n * An icon, on either side, and no label at all: the tab becomes a square, like a VIconButton.\n * The same definition as VChip's, and the square itself is VButton's `[data-icon-only]` rule.\n */\nfunction iconOnly() {\n return (\n !props.label &&\n !slots.default &&\n Boolean(slots.start || slots.end || props.iconStart || props.iconEnd)\n )\n}\n\n// @keyboard @a11y\n/*\n * Selecting a tab the moment it takes focus, which `automatic` activation means. It lives here\n * rather than in the row's keyboard handler, because this component knows its own value: the\n * handler would have to read it back from a DOM attribute, and would lose the distinction\n * between the number 1 and the string \"1\" doing so.\n */\nfunction onFocus() {\n if (tabs?.activation === 'automatic' && !resolvedDisabled.value) tabs.select(props.value)\n}\n</script>\n\n<template>\n <VButton\n :id=\"tabId\"\n class=\"v-tab\"\n :role=\"tabs ? 'tab' : undefined\"\n :aria-selected=\"tabs ? String(selected) : undefined\"\n :aria-controls=\"panelId\"\n :tabindex=\"tabs ? (selected ? 0 : -1) : undefined\"\n variant=\"ghost\"\n :elevated=\"selected && tabs?.variant === 'inset'\"\n :tone=\"selected && tabs ? tabs.tone : 'neutral'\"\n :size=\"tabs?.size\"\n :compact=\"tabs?.compact\"\n :disabled=\"resolvedDisabled\"\n :data-icon-only=\"iconOnly() ? '' : undefined\"\n @click=\"tabs?.select(value)\"\n @focus=\"onFocus\"\n >\n <template v-if=\"iconStart || $slots.start\" #start>\n <slot name=\"start\"><VIcon v-bind=\"iconProps(iconStart!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <template v-if=\"iconEnd || $slots.end\" #end>\n <slot name=\"end\"><VIcon v-bind=\"iconProps(iconEnd!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <!-- The label is wrapped in an element of its own so that it can be truncated:\n an ellipsis cannot be applied to the bare text of a flex container, and tabs\n sharing the bar equally have to be able to cut their labels short -->\n <span v-if=\"!iconOnly()\" class=\"v-tab-label\"\n ><slot>{{ label }}</slot></span\n >\n </VButton>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * These rules override a VButton, so they are qualified by an attribute that button always\n * renders. That is what makes them win whatever order the two sheets end up in; the same\n * device VIconButton uses.\n */\n .v-tab[data-size] {\n position: relative;\n /*\n * The tabs are never compressed, and that is precisely what makes the row overflow. Allowed\n * to shrink, they would squeeze down to their smallest possible width and scrolling would\n * never come into play at all.\n */\n flex: none;\n white-space: nowrap;\n }\n\n /*\n * The focus ring is drawn INSIDE the tab. The row scrolls, and a ring sitting\n * outside the tab would be cropped by that scrolling box on the first and last\n * tabs.\n */\n .v-tab[data-size]:focus-visible {\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-tab-label {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n flex: 1 1 0;\n min-inline-size: 0;\n }\n\n /* A box nested inside a rounded one needs a smaller radius to look concentric: the\n track's own, less the padding between them. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n border-radius: calc(var(--vectis-radius-surface) - var(--vectis-space-1));\n }\n\n /*\n * On a track the tabs are contiguous SEGMENTS with square corners: what carves the bar up is\n * the rule and the indicator, not the silhouette of each button. Rounded corners would leave\n * pale notches above the rule, and a gap would break the row of hover highlights into\n * islands.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size] {\n border-radius: 0;\n }\n\n /*\n * The selectors ask for the first and last of their TYPE rather than the first and last\n * child, because the scroll markers are the row's real first and last children. They are\n * spans, the tabs being the only buttons, which makes the distinction work.\n */\n .v-tabs[data-variant='outlined'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size]:first-of-type {\n border-start-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n .v-tabs[data-variant='outlined'][data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-start-end-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /* Turned vertical, the track has migrated to the end edge, so the free edge is the\n start one: that is where the ends of the column round their corners. */\n .v-tabs[data-variant='outlined'][data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-end-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /*\n * It is painted in the tab's own text colour rather than by reading one of VButton's private\n * variables: that way it follows the selected tone and the grey of a disabled tab on its own,\n * without this file having to know anything about either.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n content: '';\n position: absolute;\n inset-inline: 0;\n inset-block-end: 0;\n block-size: var(--vectis-control-size-tab-indicator);\n background: currentColor;\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n inset-block: 0;\n inset-inline: auto;\n inset-inline-start: 0;\n block-size: auto;\n inline-size: var(--vectis-control-size-tab-indicator);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size][aria-selected='true']::after {\n opacity: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n transition: none;\n }\n }\n\n /*\n * The class is repeated to reach (0,6,0). The variant, hover and active rules reach (0,5,0),\n * and with the forcing turned off any of them that still won would paint its tone over the\n * selection, with HighlightText on top of it.\n */\n @media (forced-colors: active) {\n .v-tab.v-tab.v-tab.v-tab[aria-selected='true']:not(:disabled) {\n forced-color-adjust: none;\n background-color: Highlight;\n color: HighlightText;\n border-color: Highlight;\n outline-color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VTab.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VTabs/VTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * One tab. It IS a VButton — the colour, the size, the focus ring and the disabled\n * state all come from there — and what this component adds is the handful of\n * attributes that make a button part of a tab row for assistive technology.\n *\n * Used outside a VTabs it renders as a quiet neutral button at VButton's own size, and as\n * nothing more: the row's decisions all come from the context, and a tab with no row takes\n * VButton's defaults rather than a copy of the row's. It carries no `tab` role either,\n * which would be an orphan without a tablist, nor the roving `tabindex` that would take\n * it out of the tab order.\n */\n\nimport { computed, inject } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport { tabsKey } from './context'\nimport type { ItemValue } from '../../types'\n\ninterface TabProps {\n /**\n * What this tab is called in code. The panel carrying the same value is the one it\n * shows, and it is also what the v-model holds when this tab is selected.\n */\n value: ItemValue\n /** The visible label. The default slot replaces it. */\n label?: string\n /** An icon before the label: an icon name, or an explicit render. */\n iconStart?: IconSource\n /** An icon after the label, for a count or a state the tab carries. */\n iconEnd?: IconSource\n /** Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis). */\n iconFilled?: boolean\n /**\n * Makes the tab unusable: it no longer responds, the arrow keys skip over it, and it\n * greys out through the colour tokens. A VTabs set `disabled` disables every tab, this\n * one included, whatever this says.\n */\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<TabProps>(), {\n label: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n disabled: false,\n})\n\nconst slots = defineSlots<{\n /** The content of the tab, replacing the `label` prop. */\n default?(): unknown\n /** Content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n}>()\n\nconst tabs = inject(tabsKey, null)\n\nconst selected = computed(() => tabs != null && tabs.value === props.value)\nconst tabId = computed(() => tabs?.tabId(props.value))\nconst panelId = computed(() => (tabs?.hasPanels ? tabs.panelId(props.value) : undefined))\n// Cumulative, the VButtonGroup rule: a row switched off has no tab that can opt back in.\nconst resolvedDisabled = computed(() => props.disabled || Boolean(tabs?.disabled))\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\n/**\n * An icon, on either side, and no label at all: the tab becomes a square, like a VIconButton.\n * The same definition as VChip's, and the square itself is VButton's `[data-icon-only]` rule.\n */\nfunction iconOnly() {\n return (\n !props.label &&\n !slots.default &&\n Boolean(slots.start || slots.end || props.iconStart || props.iconEnd)\n )\n}\n\n// @keyboard @a11y\n/*\n * Selecting a tab the moment it takes focus, which is what `automatic` activation\n * means. It lives here rather than in the row's keyboard handler, because this\n * component knows its own value: the handler would have to read it back from a DOM\n * attribute, and would lose the distinction between the number 1 and the string \"1\"\n * doing so.\n */\nfunction onFocus() {\n if (tabs?.activation === 'automatic' && !resolvedDisabled.value) tabs.select(props.value)\n}\n</script>\n\n<template>\n <VButton\n :id=\"tabId\"\n class=\"v-tab\"\n :role=\"tabs ? 'tab' : undefined\"\n :aria-selected=\"tabs ? String(selected) : undefined\"\n :aria-controls=\"panelId\"\n :tabindex=\"tabs ? (selected ? 0 : -1) : undefined\"\n variant=\"ghost\"\n :elevated=\"selected && tabs?.variant === 'inset'\"\n :tone=\"selected && tabs ? tabs.tone : 'neutral'\"\n :size=\"tabs?.size\"\n :compact=\"tabs?.compact\"\n :disabled=\"resolvedDisabled\"\n :data-icon-only=\"iconOnly() ? '' : undefined\"\n @click=\"tabs?.select(value)\"\n @focus=\"onFocus\"\n >\n <template v-if=\"iconStart || $slots.start\" #start>\n <slot name=\"start\"><VIcon v-bind=\"iconProps(iconStart!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <template v-if=\"iconEnd || $slots.end\" #end>\n <slot name=\"end\"><VIcon v-bind=\"iconProps(iconEnd!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <!-- The label is wrapped in an element of its own so that it can be truncated:\n an ellipsis cannot be applied to the bare text of a flex container, and tabs\n sharing the bar equally have to be able to cut their labels short -->\n <span v-if=\"!iconOnly()\" class=\"v-tab-label\"\n ><slot>{{ label }}</slot></span\n >\n </VButton>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * These rules override a VButton, so they are qualified by an attribute that button\n * always renders. That is what makes them win whatever order the two sheets end up\n * in — the same device VIconButton uses.\n */\n .v-tab[data-size] {\n /* The indicator below is positioned against this box, and a button declares no\n position of its own. */\n position: relative;\n /*\n * The tabs are never compressed, and that is precisely WHAT makes the row\n * overflow. Allowed to shrink, they would squeeze down to their smallest possible\n * width and scrolling would never come into play at all.\n */\n flex: none;\n white-space: nowrap;\n }\n\n /*\n * The focus ring is drawn INSIDE the tab. The row scrolls, and a ring sitting\n * outside the tab would be cropped by that scrolling box on the first and last\n * tabs.\n */\n .v-tab[data-size]:focus-visible {\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-tab-label {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n flex: 1 1 0;\n min-inline-size: 0;\n }\n\n /* A box nested inside a rounded one needs a smaller radius to look concentric: the\n track's own, less the padding between them. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n border-radius: calc(var(--vectis-radius-surface) - var(--vectis-space-1));\n }\n\n /*\n * On a track the tabs are contiguous SEGMENTS with square corners: what carves the\n * bar up is the rule and the indicator, not the silhouette of each button. Rounded\n * corners would leave pale notches above the rule, and a gap would break the row of\n * hover highlights into islands.\n *\n * The gap is removed on the row rather than here, the row being what declares it;\n * the bar's own gap stays, since that one separates the scroll buttons and not the\n * tabs.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size] {\n border-radius: 0;\n }\n\n /*\n * Inside a card the row takes its rounded corners back at the ENDS only, the seams\n * between tabs staying square — the VButtonGroup idiom, applied to the one edge the\n * track does not occupy.\n *\n * The radius is the CARD's inner one — its own, less the border it sits behind — and\n * not the button's. The bar spends no gutter, so an end tab lands exactly on a corner\n * of the frame and has to follow that curve: a button radius, smaller, would leave a\n * sliver of card showing outside the tab as soon as it painted a background.\n *\n * TRAP — the selectors ask for the first and last of their TYPE rather than the\n * first and last child, because the scroll markers are the row's real first and last\n * children. They are spans, the tabs being the only buttons, which is what makes the\n * distinction work.\n */\n .v-tabs[data-variant='outlined'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size]:first-of-type {\n border-start-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n .v-tabs[data-variant='outlined'][data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-start-end-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /* Turned vertical, the track has migrated to the end edge, so the free edge is the\n start one: that is where the ends of the column round their corners. */\n .v-tabs[data-variant='outlined'][data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-end-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /*\n * The bar marking the selected tab, in the two framed variants. It is painted in the\n * tab's own text colour rather than by reading one of VButton's private variables:\n * that way it follows the selected tone AND the grey of a disabled tab on its own,\n * without this file having to know anything about either.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n content: '';\n position: absolute;\n inset-inline: 0;\n inset-block-end: 0;\n block-size: var(--vectis-control-size-tab-indicator);\n background: currentColor;\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n inset-block: 0;\n inset-inline: auto;\n inset-inline-start: 0;\n block-size: auto;\n inline-size: var(--vectis-control-size-tab-indicator);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size][aria-selected='true']::after {\n opacity: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n transition: none;\n }\n }\n\n /*\n * Windows forced colors replace every background with the page's, so the selected tab would look\n * exactly like the others there: the fill is its only cue, the state being in an ARIA\n * attribute a sighted reader never sees. It takes the system's own selection pair\n * instead, and opts out of the forcing for that element alone so the pair is painted.\n *\n * TRAP — the class is repeated to reach (0,6,0). The variant, hover and active rules\n * reach (0,5,0), and with the forcing turned off any of them that still won would paint\n * its tone over the selection, with HighlightText on top of it.\n */\n @media (forced-colors: active) {\n .v-tab.v-tab.v-tab.v-tab[aria-selected='true']:not(:disabled) {\n forced-color-adjust: none;\n background-color: Highlight;\n color: HighlightText;\n border-color: Highlight;\n outline-color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;EA4CA,IAAM,IAAQ,GAQR,IAAQ,EAAA,GASR,IAAO,EAAO,GAAS,IAAI,GAE3B,IAAW,QAAe,KAAQ,QAAQ,EAAK,UAAU,EAAM,KAAK,GACpE,IAAQ,QAAe,GAAM,MAAM,EAAM,KAAK,CAAC,GAC/C,IAAU,QAAgB,GAAM,YAAY,EAAK,QAAQ,EAAM,KAAK,IAAI,KAAA,CAAU,GAElF,IAAmB,QAAe,EAAM,YAAY,EAAQ,GAAM,QAAS;EAQjF,SAAS,IAAW;GAClB,OACE,CAAC,EAAM,SACP,CAAC,EAAM,WACP,GAAQ,EAAM,SAAS,EAAM,OAAO,EAAM,aAAa,EAAM;EAEjE;EAUA,SAAS,IAAU;GACjB,AAAI,GAAM,eAAe,eAAe,CAAC,EAAiB,SAAO,EAAK,OAAO,EAAM,KAAK;EAC1F;yBAIE,EA6BU,GAAA;GA5BP,IAAI,EAAA;GACL,OAAM;GACL,MAAM,EAAA,CAAA,IAAI,QAAW,KAAA;GACrB,iBAAe,EAAA,CAAA,IAAO,OAAO,EAAA,KAAQ,IAAI,KAAA;GACzC,iBAAe,EAAA;GACf,UAAU,EAAA,CAAA,IAAQ,EAAA,QAAQ,IAAA,KAAa,KAAA;GACxC,SAAQ;GACP,UAAU,EAAA,SAAY,EAAA,CAAA,CAAI,EAAE,YAAO;GACnC,MAAM,EAAA,SAAY,EAAA,CAAA,IAAO,EAAA,CAAA,CAAI,CAAC,OAAI;GAClC,MAAM,EAAA,CAAA,CAAI,EAAE;GACZ,SAAS,EAAA,CAAA,CAAI,EAAE;GACf,UAAU,EAAA;GACV,kBAAgB,EAAQ,IAAA,KAAU,KAAA;GAClC,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,CAAA,CAAI,EAAE,OAAO,EAAA,KAAK;GAClB;;oBAaP,CAFY,EAAQ,IACJ,EAAA,IAAA,EAAA,KADI,EAAA,GAArB,EAEC,QAFD,GAEC,CADE,EAAwB,EAAA,QAAA,WAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA;;MAVD,EAAA,aAAaA,EAAAA,OAAO,QAAA;SAAQ;eAC6C,CAAvF,EAAuF,EAAA,QAAA,SAAA,CAAA,SAAA,CAApE,EAA6D,GAA7D,EAAe,EAA8C,CAAA,CAArC,CAAC,EAAA,SAAS,GAAA,EAAK,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;cAE9D,EAAA,WAAWA,EAAAA,OAAO,MAAA;SAAM;eAC6C,CAAnF,EAAmF,EAAA,QAAA,OAAA,CAAA,SAAA,CAAlE,EAA2D,GAA3D,EAAe,EAA4C,CAAA,CAAnC,CAAC,EAAA,OAAO,GAAA,EAAK,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,CAAA,CAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VTab.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VTabs/VTab.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Reuse VButton for activation; tab context supplies selection, IDs and roving tabindex without\n * a mounted-child registry.\n */\n\nimport { computed, inject } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport { tabsKey } from './context'\nimport type { ItemValue } from '../../types'\n\ninterface TabProps {\n /**\n * What this tab is called in code. The panel carrying the same value is the one it\n * shows, and it is also what the v-model holds when this tab is selected.\n */\n value: ItemValue\n /** The visible label. The default slot replaces it. */\n label?: string\n /** An icon before the label: an icon name, or an explicit render. */\n iconStart?: IconSource\n /** An icon after the label, for a count or a state the tab carries. */\n iconEnd?: IconSource\n /** Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis). */\n iconFilled?: boolean\n /** A VTabs set `disabled` disables every tab, this one included, whatever this says. */\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<TabProps>(), {\n label: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n disabled: false,\n})\n\nconst slots = defineSlots<{\n /** The content of the tab, replacing the `label` prop. */\n default?(): unknown\n /** Content before the label, which takes the place of `iconStart`. */\n start?(): unknown\n /** Content after the label, which takes the place of `iconEnd`. */\n end?(): unknown\n}>()\n\nconst tabs = inject(tabsKey, null)\n\nconst selected = computed(() => tabs != null && tabs.value === props.value)\nconst tabId = computed(() => tabs?.tabId(props.value))\nconst panelId = computed(() => (tabs?.hasPanels ? tabs.panelId(props.value) : undefined))\nconst resolvedDisabled = computed(() => props.disabled || Boolean(tabs?.disabled))\n\n// A function read by the template, never a `computed`: `slots` is not reactive, so a computed\n// would keep its first answer while a slot behind a `v-if` comes and goes.\n/**\n * An icon, on either side, and no label at all: the tab becomes a square, like a VIconButton.\n * The same definition as VChip's, and the square itself is VButton's `[data-icon-only]` rule.\n */\nfunction iconOnly() {\n return (\n !props.label &&\n !slots.default &&\n Boolean(slots.start || slots.end || props.iconStart || props.iconEnd)\n )\n}\n\n// @keyboard @a11y\n/*\n * Selecting a tab the moment it takes focus, which `automatic` activation means. It lives here\n * rather than in the row's keyboard handler, because this component knows its own value: the\n * handler would have to read it back from a DOM attribute, and would lose the distinction\n * between the number 1 and the string \"1\" doing so.\n */\nfunction onFocus() {\n if (tabs?.activation === 'automatic' && !resolvedDisabled.value) tabs.select(props.value)\n}\n</script>\n\n<template>\n <VButton\n :id=\"tabId\"\n class=\"v-tab\"\n :role=\"tabs ? 'tab' : undefined\"\n :aria-selected=\"tabs ? String(selected) : undefined\"\n :aria-controls=\"panelId\"\n :tabindex=\"tabs ? (selected ? 0 : -1) : undefined\"\n variant=\"ghost\"\n :elevated=\"selected && tabs?.variant === 'inset'\"\n :tone=\"selected && tabs ? tabs.tone : 'neutral'\"\n :size=\"tabs?.size\"\n :compact=\"tabs?.compact\"\n :disabled=\"resolvedDisabled\"\n :data-icon-only=\"iconOnly() ? '' : undefined\"\n @click=\"tabs?.select(value)\"\n @focus=\"onFocus\"\n >\n <template v-if=\"iconStart || $slots.start\" #start>\n <slot name=\"start\"><VIcon v-bind=\"iconProps(iconStart!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <template v-if=\"iconEnd || $slots.end\" #end>\n <slot name=\"end\"><VIcon v-bind=\"iconProps(iconEnd!)\" :filled=\"iconFilled\" /></slot>\n </template>\n <!-- The label is wrapped in an element of its own so that it can be truncated:\n an ellipsis cannot be applied to the bare text of a flex container, and tabs\n sharing the bar equally have to be able to cut their labels short -->\n <span v-if=\"!iconOnly()\" class=\"v-tab-label\"\n ><slot>{{ label }}</slot></span\n >\n </VButton>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * These rules override a VButton, so they are qualified by an attribute that button always\n * renders. That is what makes them win whatever order the two sheets end up in; the same\n * device VIconButton uses.\n */\n .v-tab[data-size] {\n position: relative;\n /*\n * The tabs are never compressed, and that is precisely what makes the row overflow. Allowed\n * to shrink, they would squeeze down to their smallest possible width and scrolling would\n * never come into play at all.\n */\n flex: none;\n white-space: nowrap;\n }\n\n /*\n * The focus ring is drawn INSIDE the tab. The row scrolls, and a ring sitting\n * outside the tab would be cropped by that scrolling box on the first and last\n * tabs.\n */\n .v-tab[data-size]:focus-visible {\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-tab-label {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n flex: 1 1 0;\n min-inline-size: 0;\n }\n\n /* A box nested inside a rounded one needs a smaller radius to look concentric: the\n track's own, less the padding between them. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size] {\n border-radius: calc(var(--vectis-radius-surface) - var(--vectis-space-1));\n }\n\n /*\n * On a track the tabs are contiguous SEGMENTS with square corners: what carves the bar up is\n * the rule and the indicator, not the silhouette of each button. Rounded corners would leave\n * pale notches above the rule, and a gap would break the row of hover highlights into\n * islands.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size] {\n border-radius: 0;\n }\n\n /*\n * The selectors ask for the first and last of their TYPE rather than the first and last\n * child, because the scroll markers are the row's real first and last children. They are\n * spans, the tabs being the only buttons, which makes the distinction work.\n */\n .v-tabs[data-variant='outlined'] > .v-tabs-bar > .v-tabs-list .v-tab[data-size]:first-of-type {\n border-start-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n .v-tabs[data-variant='outlined'][data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-start-end-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /* Turned vertical, the track has migrated to the end edge, so the free edge is the\n start one: that is where the ends of the column round their corners. */\n .v-tabs[data-variant='outlined'][data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]:last-of-type {\n border-end-start-radius: calc(var(--vectis-radius-surface) - 1px);\n }\n\n /*\n * It is painted in the tab's own text colour rather than by reading one of VButton's private\n * variables: that way it follows the selected tone and the grey of a disabled tab on its own,\n * without this file having to know anything about either.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n content: '';\n position: absolute;\n inset-inline: 0;\n inset-block-end: 0;\n block-size: var(--vectis-control-size-tab-indicator);\n background: currentColor;\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n inset-block: 0;\n inset-inline: auto;\n inset-inline-start: 0;\n block-size: auto;\n inline-size: var(--vectis-control-size-tab-indicator);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size][aria-selected='true']::after {\n opacity: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs:is([data-variant='flat'], [data-variant='outlined'])\n > .v-tabs-bar\n > .v-tabs-list\n .v-tab[data-size]::after {\n transition: none;\n }\n }\n\n /*\n * The class is repeated to reach (0,6,0). The variant, hover and active rules reach (0,5,0),\n * and with the forcing turned off any of them that still won would paint its tone over the\n * selection, with HighlightText on top of it.\n */\n @media (forced-colors: active) {\n .v-tab.v-tab.v-tab.v-tab[aria-selected='true']:not(:disabled) {\n forced-color-adjust: none;\n background-color: Highlight;\n color: HighlightText;\n border-color: Highlight;\n outline-color: HighlightText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;EAiCA,IAAM,IAAQ,GAQR,IAAQ,EAAA,GASR,IAAO,EAAO,GAAS,IAAI,GAE3B,IAAW,QAAe,KAAQ,QAAQ,EAAK,UAAU,EAAM,KAAK,GACpE,IAAQ,QAAe,GAAM,MAAM,EAAM,KAAK,CAAC,GAC/C,IAAU,QAAgB,GAAM,YAAY,EAAK,QAAQ,EAAM,KAAK,IAAI,KAAA,CAAU,GAClF,IAAmB,QAAe,EAAM,YAAY,EAAQ,GAAM,QAAS;EAQjF,SAAS,IAAW;GAClB,OACE,CAAC,EAAM,SACP,CAAC,EAAM,WACP,GAAQ,EAAM,SAAS,EAAM,OAAO,EAAM,aAAa,EAAM;EAEjE;EASA,SAAS,IAAU;GACjB,AAAI,GAAM,eAAe,eAAe,CAAC,EAAiB,SAAO,EAAK,OAAO,EAAM,KAAK;EAC1F;yBAIE,EA6BU,GAAA;GA5BP,IAAI,EAAA;GACL,OAAM;GACL,MAAM,EAAA,CAAA,IAAI,QAAW,KAAA;GACrB,iBAAe,EAAA,CAAA,IAAO,OAAO,EAAA,KAAQ,IAAI,KAAA;GACzC,iBAAe,EAAA;GACf,UAAU,EAAA,CAAA,IAAQ,EAAA,QAAQ,IAAA,KAAa,KAAA;GACxC,SAAQ;GACP,UAAU,EAAA,SAAY,EAAA,CAAA,CAAI,EAAE,YAAO;GACnC,MAAM,EAAA,SAAY,EAAA,CAAA,IAAO,EAAA,CAAA,CAAI,CAAC,OAAI;GAClC,MAAM,EAAA,CAAA,CAAI,EAAE;GACZ,SAAS,EAAA,CAAA,CAAI,EAAE;GACf,UAAU,EAAA;GACV,kBAAgB,EAAQ,IAAA,KAAU,KAAA;GAClC,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,CAAA,CAAI,EAAE,OAAO,EAAA,KAAK;GAClB;;oBAaP,CAFY,EAAQ,IACJ,EAAA,IAAA,EAAA,KADI,EAAA,GAArB,EAEC,QAFD,GAEC,CADE,EAAwB,EAAA,QAAA,WAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,EAAA,CAAA;;MAVD,EAAA,aAAaA,EAAAA,OAAO,QAAA;SAAQ;eAC6C,CAAvF,EAAuF,EAAA,QAAA,SAAA,CAAA,SAAA,CAApE,EAA6D,GAA7D,EAAe,EAA8C,CAAA,CAArC,CAAC,EAAA,SAAS,GAAA,EAAK,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;cAE9D,EAAA,WAAWA,EAAAA,OAAO,MAAA;SAAM;eAC6C,CAAnF,EAAmF,EAAA,QAAA,OAAA,CAAA,SAAA,CAAlE,EAA2D,GAA3D,EAAe,EAA4C,CAAA,CAAnC,CAAC,EAAA,OAAO,GAAA,EAAK,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,CAAA,CAAA,CAAA,CAAA"}
|