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":"VCarousel.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VCarousel/VCarousel.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @a11y @ssr @core\n// The DS's densest script; every block below carries its own tag.\n/**\n * Content the reader moves through one screenful at a time — images, cards, text — across\n * the page or down it.\n *\n * ONE mechanism: a native `scroll-snap` scroller. Touch, the trackpad, the scrollbar and the\n * snapping all come from the browser, so there is no `transform` track and no cloned slide\n * anywhere — `loop` included, where going past the last slide scrolls the real track back to\n * the first.\n *\n * Responsiveness is 100% CSS with no breakpoint and no observer: `itemsPerView` is a MAXIMUM,\n * `itemMinSize` a floor, and `max()` between them inside the slide's `flex-basis` IS the\n * query. The `peek` strip falls out of the same formula.\n *\n * The effects are scroll-driven animations, so they follow the finger and reverse with it.\n * They are progressive enhancement: without support the carousel simply slides.\n *\n * A move of more than one page is the exception, and it has to be: travelling five pages\n * would send four slides across the port at speed, each playing its own transition on the\n * way past. It scrolls instantly and plays the effect ONCE instead, on the slides that\n * arrive. Stepping one page keeps its scroll, and a drag never reaches that path at all.\n *\n * The JS is limited to what nothing else can do — scrolling to the Nth child, reading back\n * which page the reader landed on and measuring how many pages there are (subtler than it\n * sounds: not every slide can lead, and the last position is the END of the track), the\n * autoplay timer, and the arrow keys, which a focused scroller answers with a fixed pixel\n * step that snapping immediately undoes.\n */\n\nimport {\n cloneVNode,\n computed,\n onBeforeUnmount,\n onMounted,\n nextTick,\n ref,\n provide,\n watch,\n watchEffect,\n} from 'vue'\nimport type { StyleValue } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { arrow_downward_alt as arrowDownwardAltIcon } from '../VIcon/icons/arrow_downward_alt'\nimport { arrow_left_alt as arrowLeftAltIcon } from '../VIcon/icons/arrow_left_alt'\nimport { arrow_right_alt as arrowRightAltIcon } from '../VIcon/icons/arrow_right_alt'\nimport { arrow_upward_alt as arrowUpwardAltIcon } from '../VIcon/icons/arrow_upward_alt'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport { carouselKey } from './context'\n\nimport { cssSize } from '../../utils/css'\nimport { isRtl } from '../../utils/direction'\nimport { isDev } from '../../utils/env'\nimport { isKeyboardFocus } from '../../utils/focus'\nimport { clamp } from '../../utils/number'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useSlotNodes } from '../../composables/useSlotNodes'\nimport { useTimer } from '../../composables/useTimer'\nimport { useMessages } from '../../i18n/state'\n\n/** How one slide gives way to the next: sliding, fading in place, or scaling down. */\nexport type CarouselEffect = 'slide' | 'fade' | 'scale'\n/** Whether the carousel scrolls across the page or down it. */\nexport type CarouselOrientation = 'horizontal' | 'vertical'\n\n/** What the `#controls` slot receives. */\nexport interface CarouselControlsSlotProps {\n /** Moves one position back. */\n previous: () => void\n /** Moves one position forward. */\n next: () => void\n /** Whether there is no position before this one (always false when looping). */\n atStart: boolean\n /** Whether there is no position after this one (always false when looping). */\n atEnd: boolean\n /** The current position, from 0. */\n index: number\n /** How many slides there are. */\n count: number\n /** How many positions the carousel can actually rest on, which is usually fewer. */\n pageCount: number\n /** Which way it scrolls: a bar of your own cannot choose its icons without knowing. */\n orientation: CarouselOrientation\n}\n\n/** What the `#indicators` slot receives. */\nexport interface CarouselIndicatorsSlotProps {\n /** The current position, from 0. */\n index: number\n /** How many slides there are. */\n count: number\n /** How many positions there are: render one control per position, not per slide. */\n pageCount: number\n /** Moves to a position. */\n goTo: (index: number) => void\n /** Which way the carousel scrolls. */\n orientation: CarouselOrientation\n}\n\n/** What the `#indicator` slot receives. */\nexport interface CarouselIndicatorSlotProps {\n /** The position this dot stands for, from 0. */\n index: number\n /** Whether it is the current one. */\n active: boolean\n}\n/** Where the position dots go: nowhere, over the slides, or after them. */\nexport type CarouselIndicators = false | 'inside' | 'outside'\n/** Where the previous and next buttons go: nowhere, over the slides, or beside them. */\nexport type CarouselControls = false | 'inside' | 'outside'\n/**\n * When those buttons are visible. It is a named choice rather than a yes-or-no, because\n * \"on hover\" would misname what actually happens: they also appear on keyboard focus, and\n * they stay permanently visible where there is no pointer to hover with.\n */\nexport type CarouselControlsVisibility = 'always' | 'hover'\n\ninterface CarouselProps {\n /**\n * How many slides may be visible at once. It is a MAXIMUM and not a target: the floor\n * below decides how many actually fit, which is what makes the whole thing responsive\n * without a single breakpoint.\n */\n itemsPerView?: number\n /**\n * How small a slide is allowed to get. Once an equal share would fall below this, fewer\n * slides fit and the carousel simply scrolls further instead. A number is read as\n * pixels; anything else is used as given, so `'20vw'` works.\n */\n itemMinSize?: number | string\n /**\n * How much of the NEXT slide is left showing, as a hint that there is more. It includes\n * the gap before it, which is what keeps the sizing formula free of special cases.\n *\n * It cannot be combined with the fade transition: fading assumes a slide exactly fills\n * the view.\n */\n peek?: number | string\n /** The space between two slides. */\n gap?: number | string\n /** Whether the carousel scrolls across the page or down it. */\n orientation?: CarouselOrientation\n /**\n * How one slide gives way to the next, driven by the scroll itself. Sliding means no\n * animation at all.\n *\n * Fading requires ONE slide at a time and no peek: it works by holding each slide in\n * place while the scroll moves under it, which only lands correctly when a slide fills\n * the view exactly. Asked for otherwise, it falls back to sliding rather than degrading.\n */\n effect?: CarouselEffect\n /**\n * The height of the visible area.\n *\n * GIVE ONE when the carousel scrolls downwards: a slide sized as a share of the height\n * needs a height to take a share OF, and without it every slide collapses onto its own\n * content. Scrolling across the page, the height comes from the slides themselves.\n */\n height?: number | string\n /**\n * Whether the carousel comes back round: past the last position it returns to the first,\n * and before the first it goes to the last. Off by default, which stops it at both ends.\n *\n * Nothing is cloned to achieve it: the real track goes back to the beginning. Being a\n * move of more than one page, it goes there at once and plays the transition on\n * arrival, rather than rewinding past every slide in between, unless `noJump` puts\n * that rewind back.\n *\n * It has no effect where there is only one position to rest on, and the buttons stay\n * disabled there rather than becoming two controls that do nothing. The dots always go\n * straight to the position they name, and so do the Home and End keys.\n */\n loop?: boolean\n /**\n * Whether a move of more than one page keeps the whole scroll instead of going straight\n * there. Off by default: a dot five pages away lands at once and plays the transition\n * once, on arrival.\n *\n * Turn it on when the travel is the point: a short carousel where watching the track\n * run past says something about how far the reader has moved. Every route is covered,\n * the dots, the Home and End keys and a `loop` carousel coming back round, so the\n * component moves the way it did before there was a shortcut at all.\n *\n * It changes nothing for a reader who has asked for less motion: that preference makes\n * every scroll instant on its own.\n */\n noJump?: boolean\n /**\n * How long each slide is shown before the next one, in milliseconds; zero means it does\n * not advance by itself. It stops at the last page unless the carousel loops (in which\n * case it goes round for as long as the page is open), pauses while the pointer rests on\n * it or the KEYBOARD focus is inside it, and never runs at all for a reader who has asked\n * for less motion.\n *\n * The component deliberately renders NO pause button. This prop is reactive and setting\n * it to zero cancels the timer at once, so a stop control is a one-line binding on your\n * side, and it is worth adding: the guideline asks for a way to stop content that moves\n * on its own, and hover and focus leave a touch user with none. Looping makes that\n * binding MANDATORY rather than advisable, the movement no longer ending on its own.\n */\n autoplay?: number\n /**\n * Where the previous and next buttons go: over the slides, beside them, or nowhere.\n *\n * Placed beside, they sit at the ends of the scrolling axis and their room is reserved\n * as padding, so the component's footprint is unchanged and the slides narrow instead.\n * Either way they are centred on the SLIDES and never on the slides plus the dots.\n */\n controls?: CarouselControls\n /**\n * Where the position dots go: over the slides, after them, or nowhere. After them means\n * below when the carousel scrolls across the page, and beside it when it scrolls down.\n */\n indicators?: CarouselIndicators\n /**\n * Whether those buttons are always visible, or appear when the pointer is over the\n * carousel or the keyboard focus is inside it. Where there is no pointer to hover with,\n * they stay visible whatever this says. The dots are never hidden.\n */\n controlsVisibility?: CarouselControlsVisibility\n /** The icon of the previous button. It follows the orientation by default. */\n prevIcon?: IconSource\n /** The icon of the next button. It follows the orientation by default. */\n nextIcon?: IconSource\n /** What the previous button does, in words. It falls back to the dictionary. */\n prevLabel?: string\n /** What the next button does, in words. It falls back to the dictionary. */\n nextLabel?: string\n /**\n * What screen readers announce for the carousel as a whole.\n *\n * Give a DISTINCT one to every carousel on a page: this is a landmark of the page, and\n * two landmarks bearing the same name cannot be told apart by someone navigating between\n * them.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<CarouselProps>(), {\n itemsPerView: 1,\n itemMinSize: undefined,\n peek: undefined,\n gap: undefined,\n orientation: 'horizontal',\n effect: 'slide',\n height: undefined,\n loop: false,\n noJump: false,\n autoplay: 0,\n controls: 'inside',\n indicators: 'outside',\n controlsVisibility: 'always',\n prevIcon: undefined,\n nextIcon: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n label: undefined,\n})\n\ndefineSlots<{\n /**\n * The slides. How many there are is read from what this slot RENDERS, so a `v-for` is\n * perfectly fine, but the slot must not depend on something only true in a browser, or\n * the server and the client would count differently.\n */\n default(): unknown\n /**\n * Replaces the previous and next buttons entirely, their placement included: custom\n * content positions itself, and the visibility setting no longer applies to it.\n */\n controls?(props: CarouselControlsSlotProps): unknown\n /**\n * Replaces the whole bar of dots.\n *\n * Render one control per POSITION (`pageCount`) and not per slide. A position past the\n * last one cannot be reached, so a bar built on the number of slides offers dots that\n * scroll nowhere. The slide count is passed as well, for wording such as \"3 of 8\".\n */\n indicators?(props: CarouselIndicatorsSlotProps): unknown\n /**\n * Replaces what is drawn INSIDE one dot. The button itself, and everything that makes it\n * announce and behave correctly, stays the design system's.\n */\n indicator?(props: CarouselIndicatorSlotProps): unknown\n}>()\n\n/**\n * Which slide is current: the first one fully visible when several fit at once, which is\n * also the position the carousel has come to rest on. A value outside the positions the\n * carousel can rest on is brought back into range.\n */\nconst model = defineModel<number>({ default: 0 })\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.carousel.label)\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? m.value.carousel.previous)\nconst resolvedNextLabel = computed(() => props.nextLabel ?? m.value.carousel.next)\nconst isVertical = computed(() => props.orientation === 'vertical')\n/* Arrows with a shaft rather than the chevrons VTabs and VPagination use, and the\n difference is the surface they sit on: those two put their control in a dense bar,\n where a chevron is exactly the right weight, while this pair is a round button laid\n over whatever media the slides carry. A hairline chevron dissolves at the centre of\n a 40px disc; a shaft fills it and reads at a glance. */\nconst resolvedPrevIcon = computed(\n () => props.prevIcon ?? (isVertical.value ? arrowUpwardAltIcon : arrowLeftAltIcon),\n)\nconst resolvedNextIcon = computed(\n () => props.nextIcon ?? (isVertical.value ? arrowDownwardAltIcon : arrowRightAltIcon),\n)\n\n// @ssr\n/*\n * The count comes from the slot's VNODES, never from a registry the items feed at\n * mount: a registry renders 0 slides on the server and N on the client — a\n * guaranteed hydration mismatch, and the very reason VTabs derives `hasPanels`\n * from `slots` too. `cloneVNode` is then what carries each slide its position:\n * `setup()` order is deterministic on the first render but not on a later\n * insertion, so a self-incrementing counter would drift.\n */\nconst slides = useSlotNodes()\nconst count = computed(() => slides.value.length)\n\n// A functional component: it renders already-captured VNodes, which\n// <component :is> cannot do (it expects a definition). The VAvatarGroup idiom.\nconst Slides = () =>\n slides.value.map((node, index) => cloneVNode(node, { index, key: node.key ?? index }))\n\n/*\n * `fade` counter-translates every slide onto the same spot, so past one item per\n * view — or with a peek strip — the slides do not merely look degraded, they pile\n * up. The downgrade makes the combination unrepresentable; the dev warn explains.\n *\n * A peek of ZERO is no peek: the sizing formula collapses exactly to the peekless one, so\n * `peek: 0` (or `'0px'`) must not cost the effect. A length `parseFloat` cannot read — a\n * `calc()` — is taken as a real strip, the side that never piles slides up.\n */\nconst hasPeek = computed(\n () => props.peek !== undefined && Number.parseFloat(String(props.peek)) !== 0,\n)\nconst resolvedEffect = computed<CarouselEffect>(() =>\n props.effect === 'fade' && (props.itemsPerView > 1 || hasPeek.value) ? 'slide' : props.effect,\n)\n\nprovide(carouselKey, {\n get slideRoleDescription() {\n return m.value.carousel.slideRoleDescription\n },\n slideLabel: (index: number) => m.value.carousel.slide(index + 1, count.value),\n})\n\n/*\n * The jump one-shot, which replaces the travel whenever the model moves by more than\n * one page (see `scrollToIndex`). Two pieces of state, both read from the sheet:\n *\n * `jumpPhase` alternates between two IDENTICAL keyframe names, and that is the whole\n * reason there are two. Re-setting the same attribute leaves a running animation\n * running, so a second jump would play nothing at all; changing `animation-name` is\n * what a CSS animation restarts on.\n *\n * `jumpDirection` is the sign the `slide` character is written on, alongside the axis\n * vector and the RTL sign the effect keyframes already use. It is the LINEAR one, so a\n * `loop` wrap enters from the side the track actually rewinds towards.\n */\nconst jumpPhase = ref<'a' | 'b'>()\nconst jumpDirection = ref(1)\n\n/*\n * The one-shot is taken off once it has played. Left on the root, it would play again on\n * every slide mounted LATER (a lazy page, a re-keyed list): an animation starts when its\n * element is created with a name, and in `fade` the newcomers would flash in from zero.\n * The next jump restarts it all the same, a change from no name to one being a change.\n */\nfunction onAnimationEnd(event: AnimationEvent) {\n if (!event.animationName.startsWith('v-carousel-jump-')) return\n // Its own slides only: a nested carousel's animation bubbles through here as well.\n const owner = (event.target as Element | null)?.closest('.v-carousel')\n if (owner === event.currentTarget) jumpPhase.value = undefined\n}\n\nconst rootStyle = computed<StyleValue>(() => ({\n '--carousel-per-view': String(props.itemsPerView),\n '--carousel-item-min': cssSize(props.itemMinSize),\n '--carousel-peek': cssSize(props.peek),\n '--carousel-gap': cssSize(props.gap),\n '--carousel-viewport-block': cssSize(props.height),\n '--carousel-jump-dir': String(jumpDirection.value),\n}))\n\nconst viewportEl = ref<HTMLElement | null>(null)\n\n/*\n * Raised while a programmatic scroll is in flight, so the read-back does not fight the\n * request: without it, asking for slide 4 is overwritten by 1, 2 and 3 as they pass under\n * the port.\n *\n * A `ref` and NOT a plain `let`, because LOWERING it must be able to re-run the sync.\n * `scrollend` and the observer callback race, and a smooth scroll the user interrupts is the\n * losing order: the observer reports the final position while the flag is still up, the flag\n * drops a moment later, and with a `let` nothing ever comes back to write the model —\n * desynchronized for good. Caught by the `Default` play function.\n */\nconst settling = ref(false)\n\n/*\n * The arrival position, measured AGAIN — that second reading is the whole point. The\n * observer delivers only on a threshold crossing, so after a backward scroll under a `peek`\n * its last delivery is a MID-FLIGHT one naming the page being left (see `measure`). Lowering\n * the guard alone hands that stale index to the watcher, which writes it while `readBack`\n * suppresses the correcting scroll: the dot then sits one page ahead, for good.\n *\n * It is the CALL that removes the race, not its position — the sync watcher is a `pre` one,\n * so both writes land in the same flush and it runs once on the final values.\n */\nconst onScrollEnd = () => {\n const port = viewportEl.value\n if (port) measure(port)\n settling.value = false\n}\n\n/*\n * Raised around the model write the read-back makes, lowered on the next tick once\n * `watch(model)` has seen it. It is what stops the two directions CHAINING.\n *\n * The bug it fixes is a touch one: DOM → model runs on every frame of a drag, and each of\n * those writes comes back as a programmatic `scrollBy` FIGHTING the finger — the scroller\n * jumps to the slide just named, the release snaps it again, and one gesture plays two\n * animations. A wheel shows it rarely, ending between two snap positions far less often.\n *\n * `scrollToIndex`'s \"already there\" test does NOT cover it: that holds only at rest, and\n * mid-gesture the delta is real. Nothing needs writing anyway — the scroller is already\n * where the read-back read it.\n *\n * Lowered in a `nextTick` and NOT inside `watch(model)`: a controlled v-model that refuses\n * the value fires no watcher, and the flag would then swallow the consumer's next change.\n */\nlet readBack = false\n\n/**\n * How far slide `index`'s start edge sits from the port's, right now. `undefined` when\n * the slide is gone, which the deferred correction below has to ask again about: it runs\n * a frame later, and a consumer is free to have changed the slides in between.\n */\nfunction deltaTo(port: HTMLElement, index: number) {\n const slide = port.querySelector<HTMLElement>(`:scope > [data-carousel-index='${index}']`)\n if (!slide) return\n const target = slide.getBoundingClientRect()\n // The VIEWPORT's rect, not the stage's: every DOM read in this component is\n // viewport-scoped, and the `outside` gutter is padding on the root, so nothing here\n // has to know about it.\n const origin = port.getBoundingClientRect()\n return { left: target.left - origin.left, top: target.top - origin.top }\n}\n\n/** Under a pixel on both axes is the scroller already being where it was asked to go. */\nconst arrived = (delta: { left: number; top: number }) =>\n Math.abs(delta.left) < 1 && Math.abs(delta.top) < 1\n\n/**\n * Writes the DOM from the model. The deltas come from the RECTS, hence physical:\n * LTR, RTL and vertical all fall out with no direction test (the VTabs\n * `watch(model)` idiom); `scrollBy?.()` is optional-called because jsdom implements\n * no scrolling.\n *\n * A move of ONE page keeps the travel and omits `behavior`, so CSS `scroll-behavior`\n * governs and `prefers-reduced-motion` with it. A move of MORE than one is a JUMP: it\n * scrolls `instant` and plays a one-shot instead. The whole rule is that distance —\n * the dots, Home/End and a `loop` wrap are simply the only routes that produce one,\n * where a step keeps its scroll and a drag never reaches here at all (`readBack`).\n * That is what keeps `goTo` the single write path rather than splitting it in two,\n * and what makes `noJump` a single term in one condition rather than a branch per route.\n *\n * Cutting a travel of five pages is not a shortcut: what it removes is four slides\n * crossing the port at speed, and with a scroll-driven effect, four of them playing\n * their own transition on the way past. `instant` overrides no reduced-motion\n * preference either — that branch already makes every scroll instant.\n *\n * The LAST page is the exception it deliberately does not special-case: its index\n * names the slide that leads at the END of the track, whose start edge lies past\n * `scrollWidth - clientWidth`, so the request overshoots and the browser clamps it.\n * What makes the scroller REST there is the end-aligned last slide in the sheet.\n * Writing the clamped offset by hand would mean `scrollTo` and a signed `scrollLeft`,\n * the one thing the rect delta exists to avoid.\n */\nfunction scrollToIndex(index: number, from = index) {\n const port = viewportEl.value\n if (!port) return\n const delta = deltaTo(port, index)\n if (!delta) return\n const { left, top } = delta\n /*\n * Already there — which is what a model change coming FROM the read-back looks\n * like. Arming the guard here would be the other way to strand it: no movement\n * means no `scrollend` to lower it again.\n */\n if (arrived(delta)) return\n\n // `noJump` cancels the one-shot along with the cut, and has to: played over a travel\n // still in flight it would hold `fade`'s opacity at its own value for 200ms while the\n // scroller was several slides from where the animation says it has arrived.\n const jump = !props.noJump && Math.abs(index - from) > 1\n /*\n * Armed AFTER the guards, so a jump that scrolls nothing animates nothing — and\n * armed here rather than in the watcher because the flush that carries these two\n * writes to the DOM is a microtask, hence still ahead of the paint that shows the\n * new position. Set them a frame later and `fade` flashes its destination at full\n * opacity before dropping to zero to fade it back in.\n */\n if (jump) {\n jumpDirection.value = index > from ? 1 : -1\n jumpPhase.value = jumpPhase.value === 'a' ? 'b' : 'a'\n }\n settling.value = true\n port.scrollBy?.(jump ? { left, top, behavior: 'instant' } : { left, top })\n\n /*\n * TRAP — a jump measures ONCE more, a frame later, and corrects. A relative delta read\n * while another scroll is in flight is already stale: a smooth scroll is driven off the\n * main thread, so the rects above lag the position the delta lands on by a frame or two,\n * and `instant` then freezes the error in — the browser does not re-snap after an\n * explicit programmatic scroll. Interrupting a step with a jump (clicking `next` twice\n * on the second-to-last page of a looping carousel) left the first slide short of its\n * edge with a strip of the second showing, by up to a tenth of a slide and in proportion\n * to the speed the scroller had reached. One frame is enough BECAUSE the instant scroll\n * cancels the animation: by then nothing is moving, so the second reading is exact and\n * there is nothing to iterate on. It cannot be a `scrollTo` of the position read before\n * the jump either, that number being stale by the same frames.\n *\n * Only jumps, and only because they scroll `instant`. A step keeps its smooth scroll,\n * which the browser composes with the one already in flight and lands exactly on — two\n * quick clicks on `next` come to rest dead on the slide edge, measured at four points of\n * the interrupted animation. There is nothing to correct there, and nothing to correct\n * WITH either: a frame later that scroll is still running.\n */\n if (jump)\n requestAnimationFrame(() => {\n const again = deltaTo(port, index)\n if (again && !arrived(again)) port.scrollBy?.({ ...again, behavior: 'instant' })\n })\n}\n\nwatch(model, (index, previous) => {\n // The guard is what stops the two directions chaining: without it the model write the\n // read-back has just made comes straight back as a scroll, fighting the finger mid-drag.\n // See the flag's own declaration for why `scrollToIndex`'s \"already there\" test does not\n // cover it.\n if (readBack) return\n // The watcher's own previous value and NOT `observedIndex`: it is the last COMMITTED\n // page, defined before the first measurement and immune to a reading in flight.\n void nextTick(() => scrollToIndex(index, previous))\n})\n\nconst observedIndex = ref<number>()\nconst measuredPages = ref<number>()\n\n/**\n * Slack, in pixels, on every comparison below. `clientWidth` is an INTEGER where the\n * flex layout is fractional, so a position that is exactly reachable can measure a\n * hair short — and symmetrically a sub-pixel leftover must never mint a page, or the\n * dot count would change with the canvas width.\n */\nconst SLACK = 2\n\n/*\n * TRAP — the slides are the viewport's CHILDREN, never its descendants. A carousel placed\n * in a slide carries the same attribute on its own slides, and a descendant query then\n * reads the inner slide 0 as the outer slide 1: the step measures 0, the page count and the\n * read-back freeze, and \"next\" scrolls to the inner slide.\n */\nconst SLIDES = ':scope > [data-carousel-index]'\n\n/**\n * Reads the scroller ONCE and answers both questions from the same numbers: how many\n * positions it can rest on, and which one it rests on now. Computing them apart is exactly\n * what let them disagree.\n *\n * A position is a PAGE, not a slide. `scroll-snap-align: start` makes every slide's start\n * edge a position, but one past the end of the scroll range is UNREACHABLE — the scroller\n * clamps short of it — so a `peek` or an active `itemMinSize` costs the last. What replaces\n * it is the END of the track, declared by `.v-carousel-slide:last-child` in the sheet and\n * counted here whenever the leftover exceeds rounding noise. Without that page the last\n * slide is never fully revealed.\n *\n * Page indices ARE slide indices, which is why nothing downstream translates: slide `p`\n * leads at `p · step`, and at the end of the track the leading fully visible slide is\n * `ceil(scrollable / step)` — `pages - 1` in both branches.\n *\n * TRAP — the reading is POSITIONAL, and an `intersectionRatio` would bring back the bug it\n * fixes. A ratio must be DELIVERED to be read and an observer delivers only on a threshold\n * crossing: with a `peek` the outgoing slide keeps the strip's slack and stays fully visible\n * for the WHOLE of a backward scroll, destination included, so every mid-flight reading\n * names the page being LEFT while the incoming slide, parked at 0.997 by a fractional\n * layout, crosses nothing on arrival. Symptom: a dot stuck one page ahead of the content.\n *\n * RECTS and the port's own client size only, never `getComputedStyle` — this runs on every\n * frame of a smooth scroll. `step`, `offset` and `span` are rect DELTAS, hence unsigned, so\n * RTL and vertical fall out with no direction test where `scrollLeft` would be negative.\n *\n * TRAP — `span` and NOT `scrollWidth - clientWidth`, which is the same number at rest and\n * the reason this is easy to undo. A scroller's scrollable overflow includes the TRANSFORMED\n * boxes of its descendants, and only towards the END edge, so any animation that translates\n * something inside a slide inflates `scrollWidth` for as long as it runs. Measuring in that\n * window mints a page, and since nothing re-measures once the animation is over, the phantom\n * dot stays for good (reported against the jump one-shot, whose `slide` form translates the\n * inner box). Slide rects are never transformed — that is the whole point of VCarouselItem's\n * two boxes — so taking the span from the outer edges of the strip is immune to it, this\n * one-shot and any future effect alike.\n *\n * TRAP — `offset` and `span` both assume the viewport carries NO padding and NO border, so\n * slide 0's start edge coincides with the port's at rest and the strip ends where the scroll\n * range does. That is why the `outside` gutter is padding on the ROOT; give the viewport its\n * own and this reading, `scrollToIndex`'s delta and the page count all pick up a constant\n * bias with nothing to report it.\n */\nfunction measure(port: HTMLElement) {\n const boxes = port.querySelectorAll<HTMLElement>(SLIDES)\n const first = boxes[0]?.getBoundingClientRect()\n const second = boxes[1]?.getBoundingClientRect()\n const last = boxes[boxes.length - 1]?.getBoundingClientRect()\n if (!first || !second || !last) return\n const origin = port.getBoundingClientRect()\n const vertical = isVertical.value\n const step = vertical ? Math.abs(second.top - first.top) : Math.abs(second.left - first.left)\n // No layout at all (jsdom, a display:none ancestor). Writing a 0 here would drag\n // every clamp down with it; the prop-derived fallback stays the answer until there\n // IS a layout.\n if (step <= 0) return\n // How far the scroller has travelled: the first slide's start edge has left the\n // port's by exactly that much.\n const offset = vertical ? Math.abs(first.top - origin.top) : Math.abs(first.left - origin.left)\n // Outer edge to outer edge, picked by comparison rather than by index: in RTL the last\n // slide is the LEFTMOST box, so naming the sides would need a direction test.\n const span = vertical\n ? Math.max(first.bottom, last.bottom) - Math.min(first.top, last.top)\n : Math.max(first.right, last.right) - Math.min(first.left, last.left)\n const scrollable = span - (vertical ? port.clientHeight : port.clientWidth)\n\n const starts = Math.floor((scrollable + SLACK) / step)\n const residual = scrollable - starts * step\n const pages = starts + 1 + (residual > SLACK ? 1 : 0)\n // The end of the track is recognized by POSITION and not by rounding: it is the one\n // page that is not a multiple of `step`, so rounding would name the start position\n // it falls short of — the page the user has just left.\n const page = offset >= scrollable - SLACK ? pages - 1 : Math.round(offset / step)\n\n measuredPages.value = pages\n /*\n * Clamped against `pageCount` and not against the local `pages`, because that is the\n * one which also caps at `count`, and the write above is what makes it fresh. A\n * slide LARGER than the port is the case that needs it: `starts` already reaches the\n * last slide there and the leftover still mints a page, so the raw answer names an\n * index no slide carries — `scrollToIndex` would then silently do nothing and\n * `settling` would never come back down.\n */\n observedIndex.value = clamp(page, 0, pageCount.value - 1)\n}\n\n// @ssr @fallback\n/**\n * SSR, jsdom and the first paint. `itemsPerView` is not a guess there, and it is exact\n * for a `peek` too: the strip costs the last START-aligned position and gives back the\n * END of the track, so the two cancel and the reachable count is\n * `count - itemsPerView + 1` either way. What still makes this a fallback rather than\n * the answer is an ACTIVE `itemMinSize`: fewer slides then fit than the prop asks for,\n * the flooring form over-counts, and only a measurement can tell. A pure function of\n * the props is also what keeps the dot list identical on the server and on the client —\n * measuring here would be a hydration mismatch.\n *\n * The clamp lives here rather than in `measure` for two reasons: a stale measurement\n * must not outlive the removal of a slide (`count` is reactive, the raw measurement is\n * not), and a slide larger than the port mints one page more than there are slides —\n * see the clamp `measure` takes against this computed.\n */\nconst pageCount = computed(() =>\n count.value === 0\n ? 0\n : clamp(measuredPages.value ?? count.value - props.itemsPerView + 1, 1, count.value),\n)\n\n/*\n * TRAP — the observer watches ELEMENTS, so it is re-armed whenever the slides are, and the\n * count alone does not say so: the same number of slides under other keys mounts a new DOM,\n * and an observer left on the detached nodes never fires again, freezing the page count on\n * a resize with nothing to show for it. The keys are what Vue re-mounts on, so they are what\n * this watches — as one string, which only changes when a key does, rather than a fresh\n * array the watcher would take for a change on every render of the parent.\n */\nconst slideKeys = computed(() =>\n JSON.stringify(slides.value.map((node, index) => String(node.key ?? index))),\n)\n\n/*\n * DOM → model, with ONE observer, which also triggers the page measurement.\n *\n * TRAP — `1` must stay in `threshold`. Nothing READS a ratio, but the buckets still decide\n * WHEN this callback runs, and this observer is what makes the component cover a ROOT\n * RESIZE — which an IntersectionObserver does not do on its own, queueing an entry only on a\n * threshold crossing. `pageCount` changes exactly when the number of FULLY visible slides\n * changes, which crosses the 1.0 bucket, which queues the re-measure. Drop the 1 and the\n * page count silently freezes on a resize. Its premise: a slide's box never exceeds the port\n * on the cross axis, so the area ratio equals the scroll-axis ratio and a slide becoming\n * fully visible really does cross 1.0.\n */\n/*\n * TRAP — started in `onMounted`, and so are the two watches further down that read the\n * page count. A watch evaluates its source at once, and every one of these reaches the\n * slot through `slides`: at setup that is a slot call outside the render, which Vue warns\n * about for every slot passed as a function (a render function, JSX). By the time the\n * component is mounted the render has read the slot and the computed answers from cache.\n */\nonMounted(() =>\n watch(\n [viewportEl, slideKeys],\n ([port], _previous, onCleanup) => {\n // @fallback\n // IntersectionObserver exists neither in SSR nor in jsdom: `scrollend` still\n // measures there, and the whole loop is verified in the browser (play functions).\n if (!port || typeof IntersectionObserver === 'undefined') return\n const observer = new IntersectionObserver(\n () => {\n measure(port)\n // The request has arrived, so there is nothing left to protect. This is also\n // the ONLY exit when the browser clamps a programmatic scroll to no movement\n // at all — `scrollend` never fires there.\n if (observedIndex.value === model.value) settling.value = false\n },\n { root: port, threshold: [0, 0.25, 0.5, 0.75, 1] },\n )\n for (const slide of port.querySelectorAll(SLIDES)) observer.observe(slide)\n onCleanup(() => observer.disconnect())\n },\n { flush: 'post', immediate: true },\n ),\n)\n\n// Watching `settling` as well as the reading is the whole point of making it a ref:\n// whichever of the two lands last, the sync runs.\nwatch([observedIndex, settling], () => {\n const index = observedIndex.value\n if (settling.value || index === undefined || index === model.value) return\n readBack = true\n model.value = index\n /*\n * Lowered here and not inside `watch(model)`: a CONTROLLED v-model that refuses\n * the value fires no watcher at all, and the flag would then swallow the\n * consumer's next legitimate change. The pre-flush watcher runs inside the flush\n * this write schedules, a `nextTick` callback only after it — so it has read the\n * flag by the time this lands.\n */\n void nextTick(() => (readBack = false))\n})\n\n/*\n * The `loop` prop, resolved ONCE against what there is to loop through, because below\n * two positions it is not merely pointless but wrong twice over: the modulo would divide\n * by zero on an empty carousel, and on a single page it would hand the reader two enabled\n * buttons that move nothing — where the disabled pair says the truth. So the prop asks,\n * and this is the answer every consumer below reads.\n */\nconst looping = computed(() => props.loop && pageCount.value > 1)\n\n/*\n * Pure derivations, and they may be ONLY because `pageCount` never over-counts: index 0 is\n * always reachable and so is `pageCount - 1`, by construction. That is the load-bearing\n * premise — an over-count makes `atEnd` false at the real end, and autoplay then re-arms its\n * timer on every bounce, forever. The `Pages` play function is what keeps it honest.\n *\n * Once a `peek` or an active floor makes the fit fractional, `pageCount - 1` is the END of\n * the track rather than a slide's start edge — a page all the same: `goTo` and `End` reach\n * it and `measure` reads it back.\n *\n * A looping carousel HAS no ends, which is all the prop changes here — and, through `atEnd`,\n * all it changes for autoplay: `rotating` already reads this, so the timer keeps re-arming\n * with not a line of its own. Endless re-arming is only a bug where the movement has nowhere\n * to go; here each one advances a page.\n */\nconst atStart = computed(() => !looping.value && model.value <= 0)\nconst atEnd = computed(() => !looping.value && model.value >= pageCount.value - 1)\n\nfunction goTo(index: number) {\n const pages = pageCount.value\n /*\n * TRAP — the DOUBLE modulo. JS `%` keeps the sign of the dividend, so `previous()` at 0\n * gives `-1 % 5 === -1`; a single one hands the model an index no slide carries,\n * `scrollToIndex` finds nothing, and the carousel stops dead with no error anywhere.\n *\n * Home, End and every dot pass an index already in range, where the modulo is the\n * identity — which is why they stay absolute with no guard of their own. Only a STEP off\n * an edge ever has anything to wrap. `clamp` returns `min` on an empty interval, the\n * right answer with no slides at all.\n */\n model.value = looping.value ? ((index % pages) + pages) % pages : clamp(index, 0, pages - 1)\n}\n// @core\n/*\n * A model the PARENT writes is brought into range too, since `goTo` only guards the\n * component's own moves. Out of range, no dot is current, the live region announces a\n * slide that does not exist, and past the last page `settling` never comes back down: the\n * browser clamps the scroll to no movement, so no `scrollend` arrives to lower it.\n */\nonMounted(() =>\n watch(\n [model, pageCount],\n ([index, pages]) => {\n if (pages === 0) return\n const bounded = Number.isFinite(index) ? clamp(Math.round(index), 0, pages - 1) : 0\n if (bounded !== index) model.value = bounded\n },\n { immediate: true },\n ),\n)\n\nconst previous = () => goTo(model.value - 1)\nconst next = () => goTo(model.value + 1)\n\n// @keyboard\n/**\n * The one keyboard concession, and it is not the one it looks like. A focused\n * scroll container DOES move on the arrows — but Chromium scrolls it by a fixed\n * pixel step, and MANDATORY snapping then pulls it straight back to the slide it\n * started on, so the net movement is zero. Home/End behave the same way.\n * (Covered by the `Keyboard` play function, verified red without this handler.)\n *\n * Contract: it moves the MODEL, never the scroller — the scroll then goes through\n * `scrollToIndex` like every other route, so there is a single write path.\n * `arrowNav` is not the tool: its job is moving focus between sibling controls,\n * and slides are neither focusable nor a list.\n */\nfunction onKeydown(event: KeyboardEvent) {\n const port = viewportEl.value\n if (!port || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return\n // A key typed INSIDE a slide belongs to what holds the focus there: a caret in a field,\n // a nested VTabs or VSlider, another carousel. Only the viewport itself is ours.\n if (event.target !== event.currentTarget || event.defaultPrevented) return\n\n if (event.key === 'Home' || event.key === 'End') {\n event.preventDefault()\n goTo(event.key === 'Home' ? 0 : pageCount.value - 1)\n return\n }\n\n // Only the axis the carousel actually scrolls: the other one must stay with the\n // browser, which is what still scrolls a slide taller than the viewport.\n const steps: Record<string, number | undefined> = isVertical.value\n ? { ArrowUp: -1, ArrowDown: 1 }\n : { ArrowLeft: -1, ArrowRight: 1 }\n const step = steps[event.key]\n if (step === undefined) return\n event.preventDefault()\n // The INLINE arrows are physical, hence inverted in RTL (the `arrowNav` rule);\n // the block axis does not flip.\n const rtl = !isVertical.value && isRtl(port)\n goTo(model.value + (rtl ? -step : step))\n}\n\n// @a11y\n/*\n * A control that disables itself under the keyboard focus would drop it on `<body>`. It is\n * handed to the opposite control, or to the viewport when both ends are reached (a single\n * page). Started on mount, like the watches above. The DEFAULT `pre` timing is the point: the buttons are still enabled when this\n * runs, and a disabled one can take no focus (the VTabs scroll buttons).\n */\nonMounted(() =>\n watch([atStart, atEnd], ([start, end], [wasStart, wasEnd]) => {\n const port = viewportEl.value\n const stage = port?.parentElement\n if (!port || !stage) return\n const [back, forward] = stage.querySelectorAll<HTMLElement>(\n ':scope > .v-carousel-controls > .v-carousel-control',\n )\n const active = document.activeElement\n if (start && !wasStart && active === back) (end ? port : forward)?.focus()\n else if (end && !wasEnd && active === forward) (start ? port : back)?.focus()\n }),\n)\n\nconst { start, cancel } = useTimer()\nconst hovered = ref(false)\n// @a11y\n// KEYBOARD focus, not any focus, hence `isKeyboardFocus` rather than a bare `focusin` flag.\n// Clicking `next` leaves the focus on it, and a plain flag would then pause the rotation for\n// good — the user has to click outside the carousel to get it going again, which is not a\n// pause, it is a trap. What the pause is FOR is the keyboard user reading a slide.\nconst focused = ref(false)\n\nconst reducedMotion = ref(false)\n\n/*\n * Every reason to stop, gathered in ONE computed so a single watch re-arms the\n * timer (the single-instance idiom of VTooltip and VMenuItem).\n *\n * `autoplay > 0` is a GUARD, not a default: `useTimer` runs a delay ≤ 0\n * SYNCHRONOUSLY (the DS convention), and a synchronous callback bumping the model\n * would recurse until the stack blows. It is ALSO the consumer's stop control —\n * the prop is reactive, so binding it to `0` cancels the timer on the spot, which\n * is what a pause button of your own would drive.\n */\nconst rotating = computed(\n () =>\n props.autoplay > 0 && !hovered.value && !focused.value && !reducedMotion.value && !atEnd.value,\n)\n\n// `immediate`: without it the first delay would only be armed by a LATER change,\n// so an autoplay that nobody touches would never advance at all. Started on MOUNT: a\n// server render never unmounts, so a timer armed there would outlive the request and\n// fire `next` on a dead instance.\nonMounted(() =>\n watch(\n [rotating, model],\n () => {\n cancel()\n if (rotating.value) start(next, props.autoplay)\n },\n { immediate: true },\n ),\n)\n\n// @a11y @ssr\n// WCAG 2.2.2. Client-only: the server has no `matchMedia`.\n/*\n * The DS's second browser-preference read (after VHotkeys' `navigator`), and it\n * is not redundant with the CSS media query: that one stops a transition, never a\n * TIMER, and WCAG 2.2.2 is about the content moving at all. Client only, listener\n * released on unmount.\n */\nlet releaseMotionQuery: (() => void) | undefined\nonMounted(() => {\n const query = window.matchMedia?.('(prefers-reduced-motion: reduce)')\n if (!query) return\n const sync = () => (reducedMotion.value = query.matches)\n sync()\n query.addEventListener('change', sync)\n releaseMotionQuery = () => query.removeEventListener('change', sync)\n})\nonBeforeUnmount(() => releaseMotionQuery?.())\n\n// @a11y\n/*\n * Announced only at rest: narrating an auto-rotating carousel floods the screen reader, so\n * the region is `aria-live=\"off\"` while it rotates (APG). The TEXT always follows the model:\n * emptied while rotating instead, a hover that pauses the rotation would write it back and\n * be announced with nothing having moved. The region itself stays mounted, a live\n * container inserted at the same time as its text not being announced (the VDataTable\n * footer rule).\n */\nconst liveMessage = computed(() =>\n count.value === 0 ? '' : m.value.carousel.slide(model.value + 1, count.value),\n)\n\n// @devwarn\nif (isDev) {\n // Once per instance each: the loop warning names the very binding it recommends, and\n // that binding re-runs this effect on every pause and resume.\n const warned = new Set<string>()\n const warn = (id: string, message: string) => {\n if (warned.has(id)) return\n warned.add(id)\n console.warn(message)\n }\n watchEffect(() => {\n if (props.effect !== resolvedEffect.value)\n warn(\n 'fade',\n props.itemsPerView > 1\n ? '[VCarousel] `fade` needs one item per view: over a single view timeline every slide is counter-translated onto the same spot, so they would pile up. Downgraded to `slide`.'\n : '[VCarousel] `fade` and `peek` are mutually exclusive: the counter-translate parks every slide over the viewport, so the peeked strip is covered. Downgraded to `slide`.',\n )\n /*\n * Not a misuse — the combination is supported and does exactly what it says. What it\n * costs is the exemption the component leans on everywhere else: content that moves on\n * its own needs a way to stop it, and the five-second grace only ever applied because\n * the rotation ENDED. Looping, it does not, and hover and focus leave a touch user with\n * nothing. Hence a warning where the fix is a one-line binding rather than a button\n * this component would have to render for everyone.\n */\n if (props.loop && props.autoplay > 0)\n warn(\n 'loop',\n '[VCarousel] `loop` with `autoplay` rotates for as long as the page is open, so WCAG 2.2.2 asks for a way to stop it. Bind `autoplay` to 0 from a control of your own.',\n )\n if (isVertical.value && props.height === undefined)\n warn(\n 'height',\n '[VCarousel] a vertical carousel needs a `height`: a percentage flex-basis has no definite reference on the block axis. Falling back to --vectis-control-size-carousel-block.',\n )\n if (!Number.isInteger(props.itemsPerView) || props.itemsPerView < 1)\n warn(\n 'itemsPerView',\n `[VCarousel] \\`itemsPerView\\` must be an integer ≥ 1, got ${props.itemsPerView}.`,\n )\n })\n}\n</script>\n\n<template>\n <div\n class=\"v-carousel\"\n role=\"region\"\n :aria-roledescription=\"m.carousel.roleDescription\"\n :aria-label=\"ariaLabel\"\n :style=\"rootStyle\"\n :data-orientation=\"orientation\"\n :data-effect=\"resolvedEffect\"\n :data-jump=\"jumpPhase\"\n :data-controls=\"controls || undefined\"\n :data-indicators=\"indicators || undefined\"\n :data-controls-visibility=\"controlsVisibility\"\n @pointerenter=\"hovered = true\"\n @pointerleave=\"hovered = false\"\n @focusin=\"focused = isKeyboardFocus($event.target)\"\n @focusout=\"focused = false\"\n @animationend=\"onAnimationEnd\"\n >\n <!--\n The stage holds the viewport and the controls — and NOTHING else. The indicator bar is a sibling on purpose: inside this box it\n would join the height the controls are centred on, and the pair would sit\n visibly below the middle of the slides.\n -->\n <div class=\"v-carousel-stage\">\n <!--\n tabindex=\"0\" is MANDATORY, not defensive: a scroll container needs a\n TABBABLE descendant (axe `scrollable-region-focusable`) and slide content\n is arbitrary. It is also what makes the track the keyboard target of\n `onKeydown` — the browser's own arrow scrolling is real, but mandatory\n snapping undoes it, see there. A focusable box with an aria-label must\n carry a role, or axe reports `aria-prohibited-attr`.\n -->\n <div\n ref=\"viewportEl\"\n class=\"v-carousel-viewport\"\n role=\"group\"\n tabindex=\"0\"\n :aria-label=\"m.carousel.slides\"\n @keydown=\"onKeydown\"\n @scrollend=\"onScrollEnd\"\n >\n <component :is=\"Slides\" />\n </div>\n\n <slot\n name=\"controls\"\n :previous=\"previous\"\n :next=\"next\"\n :at-start=\"atStart\"\n :at-end=\"atEnd\"\n :index=\"model\"\n :count=\"count\"\n :page-count=\"pageCount\"\n :orientation=\"orientation\"\n >\n <div v-if=\"controls\" class=\"v-carousel-controls\">\n <VIconButton\n class=\"v-carousel-control\"\n :label=\"resolvedPrevLabel\"\n variant=\"ghost\"\n tone=\"neutral\"\n shape=\"circular\"\n elevated\n :disabled=\"atStart\"\n @click=\"previous\"\n >\n <VIcon v-bind=\"iconProps(resolvedPrevIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n <VIconButton\n class=\"v-carousel-control\"\n :label=\"resolvedNextLabel\"\n variant=\"ghost\"\n tone=\"neutral\"\n shape=\"circular\"\n elevated\n :disabled=\"atEnd\"\n @click=\"next\"\n >\n <VIcon v-bind=\"iconProps(resolvedNextIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n </div>\n </slot>\n </div>\n\n <slot\n name=\"indicators\"\n :index=\"model\"\n :count=\"count\"\n :page-count=\"pageCount\"\n :go-to=\"goTo\"\n :orientation=\"orientation\"\n >\n <div\n v-if=\"indicators\"\n class=\"v-carousel-indicators\"\n role=\"group\"\n :aria-label=\"m.carousel.indicators\"\n >\n <!--\n One control per REACHABLE position, not per slide: with 6 slides three at\n a time the scroller can only lead with 1 to 4, and dots 5 and 6 would\n scroll nowhere. With a `peek` the last of them parks the scroller at the\n END of the track rather than on a slide's start edge. Either way the label\n stays slide-based (\"4 of 6\"), naming the slide that LEADS there — which is\n what the control does — and it then agrees with the slide's own name and\n with the live region.\n -->\n <button\n v-for=\"index in pageCount\"\n :key=\"index\"\n type=\"button\"\n class=\"v-carousel-indicator\"\n :aria-label=\"m.carousel.slide(index, count)\"\n :aria-current=\"index - 1 === model ? 'true' : undefined\"\n @click=\"goTo(index - 1)\"\n >\n <slot name=\"indicator\" :index=\"index - 1\" :active=\"index - 1 === model\">\n <span class=\"v-carousel-dot\" />\n </slot>\n </button>\n </div>\n </slot>\n\n <span class=\"v-visually-hidden\" role=\"status\" :aria-live=\"rotating ? 'off' : 'polite'\">{{\n liveMessage\n }}</span>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-carousel {\n /*\n * Geometry vector and direction sign. The effect keyframes are written on\n * them ONCE instead of being duplicated per orientation and per direction:\n * `translate` is physical, and `calc(0 * -100%)` is a valid zero.\n */\n --carousel-axis-x: 1;\n --carousel-axis-y: 0;\n --carousel-dir: 1;\n\n /*\n * Effect constants: private, non-contractual geometry with no theming story,\n * hence no --vectis-* token (the qualification rule still applies).\n */\n --carousel-scale-min: 0.75;\n\n /*\n * The jump one-shot's starting point, NEUTRAL on all three axes here: each effect\n * below turns on the one dimension it owns, which is what lets a single pair of\n * keyframes serve `slide`, `fade` and `scale` instead of one block each. The\n * direction sign is written from the script, alongside the axis vector above.\n */\n --carousel-jump-opacity: 1;\n --carousel-jump-scale: 1;\n --carousel-jump-shift: 0%;\n --carousel-jump-dir: 1;\n\n /*\n * TRAP — both names are re-set on EVERY root, to `none`, because a custom property\n * inherits: a carousel placed inside another's slide would otherwise play its host's\n * effect and replay its host's jump. Each effect and each jump phase below sets them\n * again on the root that carries the attribute.\n */\n --carousel-effect-name: none;\n --carousel-jump-name: none;\n\n /* A true gutter, which is what --vectis-space-* is for. */\n --carousel-gap: var(--vectis-space-3);\n /* No floor by default: the itemsPerView share alone decides. */\n --carousel-item-min: 0px;\n --carousel-peek: 0px;\n --carousel-viewport-block: var(--vectis-control-size-carousel-block);\n /*\n * Room reserved on the SCROLL axis for the `outside` controls. `0px` and never\n * `0`: it feeds a `padding-*`, where a unitless zero is invalid — the\n * `--carousel-peek` precedent three lines up.\n */\n --carousel-outset: 0px;\n\n /* Positioning context of the `inside` indicator bar, which is deliberately NOT\n a child of the stage — see .v-carousel-stage. */\n position: relative;\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n font-family: var(--vectis-text-family);\n }\n\n /*\n * The gutter is PADDING on the root: the component's footprint is unchanged and\n * the viewport narrows instead, so the slides' percentage flex-basis follows with\n * no branch and no second measurement.\n *\n * INVARIANT — always on the scroll axis, always SYMMETRIC. That is what lets every\n * `inside` inset below be written against the stage's edges with no compensation:\n * an absolutely positioned box resolves against the PADDING box, the edges those\n * insets pin to are on the cross axis (which this never touches), and the axis they\n * centre on stays symmetric. Make this one-sided and the `inside` indicator bar\n * goes off-centre with nothing to report it.\n */\n .v-carousel[data-orientation='horizontal'] {\n padding-inline: var(--carousel-outset);\n }\n\n .v-carousel[data-orientation='vertical'] {\n --carousel-axis-x: 0;\n --carousel-axis-y: 1;\n\n /* The indicator bar sits BESIDE the stage here, so the root's own axis follows\n the scroll axis. */\n flex-direction: row;\n padding-block: var(--carousel-outset);\n }\n\n /*\n * TRAP — `control-height-md` is the size the two VIconButtons actually render at:\n * they pass no `size`, so they take VIconButton's own `md` default and their width\n * is `--control-height`. The gutter is that button plus one gap, so the pair lands\n * flush inside the root's border box and never covers a slide. Give the buttons a\n * `size` and this line has to follow, or the pair either overlaps the first slide\n * or floats away from it — with no error anywhere.\n *\n * The `:has()` is not decoration: a consumer `#controls` slot replaces the whole\n * bar, and reserving two buttons' worth of padding for a bar nobody rendered would\n * inset the slides for nothing.\n */\n .v-carousel[data-controls='outside']:has(> .v-carousel-stage > .v-carousel-controls) {\n --carousel-outset: calc(var(--vectis-control-height-md) + var(--vectis-space-2));\n }\n\n /*\n * The scroll axis is physical, so RTL mirrors the transforms — and ONLY the\n * transforms: flex-basis and the logical `scroll-snap-type` keyword handle\n * themselves. One sign flips the fade counter-translate. Scoped to\n * `horizontal`: the block axis does not flip. `:dir()` and not a `[dir]` ancestor, so\n * an LTR subtree inside an RTL page reads its own direction.\n */\n .v-carousel[data-orientation='horizontal']:dir(rtl) {\n --carousel-dir: -1;\n }\n\n /*\n * Positioning context of the controls — and of NOTHING else. The indicator bar is a SIBLING on purpose: inside this box it would join\n * the height the controls are centred on, and an `inside` pair would sit visibly\n * below the middle of the slides. Kept a flex column so a consumer `#controls` slot\n * still stacks under the viewport with a gutter.\n */\n .v-carousel-stage {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n min-inline-size: 0;\n min-block-size: 0;\n }\n\n /*\n * Vertical ONLY. The root is a `row` there, so the inline axis is the flex MAIN\n * axis and the stage would shrink-wrap the widest slide's content — an `inside`\n * bar's `inset-inline-end` would then land on the ROOT's edge instead of the\n * slides'. Deliberately NOT applied in horizontal: the main axis is the block one\n * there, and growing into a consumer-set height would push the stage past the\n * viewport and re-centre the controls on empty space — the very bug this split\n * fixes.\n *\n * `flex-grow` and not `flex: 1`: the shorthand's `0` basis, together with the\n * min-size floors above, lets an auto-sized flex container collapse it.\n */\n .v-carousel[data-orientation='vertical'] > .v-carousel-stage {\n flex-grow: 1;\n }\n\n .v-carousel-viewport {\n display: flex;\n gap: var(--carousel-gap);\n /*\n * `auto` on BOTH axes, never `auto hidden`: clipping the cross axis would crop\n * the focus ring of any focusable slide content.\n */\n overflow: auto;\n overscroll-behavior: contain;\n /* Logical keyword: RTL and vertical need no second declaration. */\n scroll-snap-type: inline mandatory;\n scroll-behavior: smooth;\n scrollbar-width: none;\n /* A flex item's automatic minimum would stop the viewport shrinking, hence scrolling. */\n min-inline-size: 0;\n min-block-size: 0;\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-carousel-viewport::-webkit-scrollbar {\n display: none;\n }\n\n .v-carousel-viewport:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n .v-carousel[data-orientation='vertical'] > .v-carousel-stage > .v-carousel-viewport {\n flex-direction: column;\n scroll-snap-type: block mandatory;\n /*\n * A percentage flex-basis resolves against the container's MAIN size, which on\n * the block axis has nothing intrinsic here: without a definite one the basis\n * falls back to `auto` and every slide collapses onto its content.\n */\n block-size: var(--carousel-viewport-block);\n }\n\n .v-carousel-slide {\n /*\n * `flex-basis` and NOT `inline-size`: it is the one dimension that resolves on\n * the flex MAIN axis, hence on the scroll axis, in both orientations and both\n * directions — a logical property would still need a rule per orientation,\n * since `inline` follows the writing mode and not `flex-direction`.\n *\n * Arithmetic: N slides fully visible cost N-1 INNER gaps. `peek` is the whole\n * strip reserved past the Nth slide, its leading gap included (its JSDoc says\n * so) — that is what keeps the formula branch-free, `peek: 0` collapsing it\n * exactly to (100% - (N-1)·gap) / N. `max()` makes itemMinSize a floor, never\n * a cap, and IS the responsiveness: no breakpoint, no @container.\n */\n /*\n * LONGHANDS, never the `flex` shorthand — the `animation` rule further down, for\n * the same reason. A shorthand whose value contains `var()` becomes a PENDING\n * SUBSTITUTION value: it cannot be expanded until computed-value time, and if any\n * one of the four custom properties then resolves to something invalid the WHOLE\n * declaration is dropped and `flex` reverts to its initial `0 1 auto` — shrink 1\n * and basis auto, which sizes every slide on its content and destroys the snap\n * grid. As longhands a bad value can only cost the basis. It also stops devtools\n * showing the declaration struck through, since a pending shorthand is what it\n * cannot expand.\n */\n flex-grow: 0;\n flex-shrink: 0;\n flex-basis: max(\n var(--carousel-item-min),\n calc(\n (100% - (var(--carousel-per-view) - 1) * var(--carousel-gap) - var(--carousel-peek)) /\n var(--carousel-per-view)\n )\n );\n /* `auto` is a CONTENT-based floor that can push a flex item past its basis:\n one unbreakable word would desynchronize the whole snap grid. */\n min-inline-size: 0;\n min-block-size: 0;\n scroll-snap-align: start;\n position: relative;\n }\n\n /*\n * TRAP — the LAST slide is aligned on its END edge, and that is what declares the end\n * of the track as a snap position in its own right. As soon as a `peek` or an active\n * `itemMinSize` makes the fit fractional, that slide's START-aligned position lies\n * past `scrollWidth - clientWidth`, so the only position left near the end is a strip\n * short of it and the last slide is never fully revealed. `measure()` counts the end\n * of the track as its final page: delete this rule and the JS keeps offering a page\n * the scroller is no longer told it may hold.\n *\n * It is INSURANCE rather than the mechanism — Chromium clamps an out-of-range snap\n * position into the scrollable range anyway — but a measurement resting on a clamping\n * artefact instead of a declared position is the kind of implicit contract that breaks\n * silently in another engine.\n *\n * It costs nothing in the other two regimes: with a slide exactly the size of the port the\n * start- and end-aligned offsets are the SAME number, and with a slide LARGER than the\n * port the alignment is ignored, an oversized snap area making every covering position\n * valid.\n *\n * `end` is logical, so RTL and vertical need no second declaration.\n */\n .v-carousel-slide:last-child {\n scroll-snap-align: end;\n }\n\n /*\n * Each effect names the keyframes it is written on, and the two mechanisms that\n * play them read the name from here rather than from a selector of their own —\n * which is what keeps the animation LISTS below written exactly once.\n */\n .v-carousel[data-effect='fade'] {\n --carousel-effect-name: v-carousel-fade;\n /* The dissolve, with no travel: the jump is the same gesture as the scroll. */\n --carousel-jump-opacity: 0;\n }\n\n .v-carousel[data-effect='scale'] {\n --carousel-effect-name: v-carousel-scale;\n /* Exactly where the scroll-driven effect holds a slide off the centre, so the\n one-shot ends on the value that takes over from it. */\n --carousel-jump-scale: var(--carousel-scale-min);\n }\n\n .v-carousel[data-effect='slide'] {\n /*\n * A FIFTH of the slide, not its width. Every inner box shifts at once, so a full\n * one would park each over its neighbour and the overlap would paint the wrong\n * slide for the length of the run — invisible at one item per view, plain at\n * three. A fifth reads as a direction and overlaps nothing at any count.\n */\n --carousel-jump-shift: 20%;\n }\n\n /*\n * The JUMP one-shot, and it is declared OUTSIDE the @supports below on purpose: it\n * is an ordinary time-based animation, and where scroll-driven ones are missing\n * (Firefox) `animation-duration: auto` is an invalid value that drops the whole\n * declaration — taking this one's duration to 0s with it. Here it stands alone; the\n * block below restates it as the second half of a pair.\n *\n * TRAP — the pair of names. `jumpPhase` alternates between two identical keyframe\n * blocks because re-setting an attribute does not restart a running animation, and\n * `animation-name` is the one thing that does.\n */\n .v-carousel-effect {\n block-size: 100%;\n animation-name: var(--carousel-jump-name, none);\n animation-duration: var(--vectis-duration-base);\n animation-timing-function: var(--vectis-ease-out);\n }\n\n .v-carousel[data-jump='a'] {\n --carousel-jump-name: v-carousel-jump-a;\n }\n\n .v-carousel[data-jump='b'] {\n --carousel-jump-name: v-carousel-jump-b;\n }\n\n /*\n * Effects — progressive enhancement. Without scroll-driven animations none of the\n * scroll-keyed half is declared and the carousel is a plain `slide`; Firefox lands\n * there, and the jump one-shot above keeps working for it.\n *\n * The TIMELINE is declared on the slide and the ANIMATION on its inner box, and\n * that split is load-bearing: a scroll-snap area is the element's TRANSFORMED\n * border box, so animating the slide itself would make its snap position depend\n * on the scroll position — a circular dependency whose symptom is jitter, with\n * no error anywhere. A named timeline is resolved by walking up the tree, so\n * each inner finds ITS OWN slide although they all share the ident.\n */\n @supports (view-timeline-name: --v) and (animation-range: cover) {\n .v-carousel:not([data-effect='slide'])\n > .v-carousel-stage\n > .v-carousel-viewport\n > .v-carousel-slide {\n view-timeline: --carousel-view inline;\n }\n\n .v-carousel[data-orientation='vertical']:not([data-effect='slide'])\n > .v-carousel-stage\n > .v-carousel-viewport\n > .v-carousel-slide {\n view-timeline: --carousel-view block;\n }\n\n .v-carousel-effect {\n /*\n * TRAP — longhands, never the `animation` shorthand: `animation-timeline`\n * and `animation-duration` are reset-only sub-properties, so a shorthand\n * written afterwards would silently put the timeline back to `auto` and the\n * duration to 0s, and every effect would vanish with no error.\n *\n * TRAP — SIX parallel lists, and their order is the arbitration. The\n * scroll-keyed effect comes first and the one-shot second, because two\n * animations touching one property are resolved last-wins: the jump has to\n * override `fade`'s opacity and `scale`'s scale while it runs. It hands back\n * cleanly all the same, its `to` being the value the effect holds at rest in\n * the centre, and `none` fill-mode leaving nothing behind after it. Reading\n * both names from custom properties is what stops the lists drifting: this\n * rule is the only place either animation is configured.\n *\n * `none` is a valid list item, which is what gives `slide` a first slot with\n * no effect in it and an unresolved `--carousel-view` nothing to break.\n *\n * `cover` is the only range that spans the full viewport+slide traversal and\n * stays defined for a slide narrower, equal to or wider than the scrollport:\n * `contain` has length |V - S|, hence exactly ZERO at one item per view.\n *\n * `linear` is load-bearing too: the default `ease` re-maps the progress and\n * the fade counter-translate would no longer cancel the scroll travel.\n */\n animation-name: var(--carousel-effect-name, none), var(--carousel-jump-name, none);\n animation-timeline: --carousel-view, auto;\n animation-range: cover, normal;\n animation-fill-mode: both, none;\n animation-timing-function: linear, var(--vectis-ease-out);\n /* `auto` is the whole timeline. A duration in seconds would be taken as a\n PROPORTION of it, and the effect would end a fraction of the way in. */\n animation-duration: auto, var(--vectis-duration-base);\n }\n }\n\n /*\n * Over the `cover` range a slide's origin travels the whole viewport+slide\n * distance, so a counter-translate of ±100% of the slide's OWN size pins it on\n * the scrollport when the two are equal — which is why `fade` is defined at one\n * item per view only. The result is a true dissolve in place, gap included.\n */\n @keyframes v-carousel-fade {\n 0% {\n opacity: 0;\n translate: calc(var(--carousel-axis-x) * var(--carousel-dir) * -100%)\n calc(var(--carousel-axis-y) * -100%);\n }\n 50% {\n opacity: 1;\n translate: 0 0;\n }\n 100% {\n opacity: 0;\n translate: calc(var(--carousel-axis-x) * var(--carousel-dir) * 100%)\n calc(var(--carousel-axis-y) * 100%);\n }\n }\n\n /*\n * Geometry ONLY, no opacity dimming — and that is an accessibility decision, not\n * a taste one: a slide off the centre still holds real text, and fading it is a\n * measurable contrast loss that axe rightly reports. The scale alone carries the\n * depth. `fade` keeps its opacity, which IS the effect.\n */\n @keyframes v-carousel-scale {\n 0%,\n 100% {\n scale: var(--carousel-scale-min);\n }\n 50% {\n scale: 1;\n }\n }\n\n /*\n * The jump one-shot, in ONE body serving all three effects: each turns on the one\n * dimension it owns and leaves the other two at their neutral value, so what plays\n * on a jump is recognizably the effect the carousel is set to.\n *\n * The `to` is spelled out rather than left implicit, and it is the same value the\n * scroll-keyed effect holds for a slide at rest in the centre — which is what makes\n * the handover at the end invisible.\n *\n * TWO IDENTICAL BLOCKS, and the duplication is the mechanism rather than an\n * oversight: `jumpPhase` alternates between these names, because a CSS animation\n * restarts on a change of `animation-name` and on nothing else. Edit one, edit both.\n */\n @keyframes v-carousel-jump-a {\n from {\n opacity: var(--carousel-jump-opacity);\n scale: var(--carousel-jump-scale);\n translate: calc(\n var(--carousel-axis-x) * var(--carousel-dir) * var(--carousel-jump-dir) *\n var(--carousel-jump-shift)\n )\n calc(var(--carousel-axis-y) * var(--carousel-jump-dir) * var(--carousel-jump-shift));\n }\n to {\n opacity: 1;\n scale: 1;\n translate: 0 0;\n }\n }\n\n @keyframes v-carousel-jump-b {\n from {\n opacity: var(--carousel-jump-opacity);\n scale: var(--carousel-jump-scale);\n translate: calc(\n var(--carousel-axis-x) * var(--carousel-dir) * var(--carousel-jump-dir) *\n var(--carousel-jump-shift)\n )\n calc(var(--carousel-axis-y) * var(--carousel-jump-dir) * var(--carousel-jump-shift));\n }\n to {\n opacity: 1;\n scale: 1;\n translate: 0 0;\n }\n }\n\n .v-carousel-controls {\n display: flex;\n /* A floor, not the layout — `space-between` places the pair. It only ever shows\n on a stage narrow enough to bring the two buttons together. */\n gap: var(--vectis-space-2);\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-carousel[data-orientation='vertical'] > .v-carousel-stage > .v-carousel-controls {\n flex-direction: column;\n }\n\n /*\n * BOTH placements are laid out of flow against the STAGE, and that is the whole\n * requirement: the pair is then centred on the slides alone, whatever the indicator\n * bar does. One `align-items: center` serves both orientations — it is the cross\n * axis in each. `pointer-events` is handed back to the buttons alone, so the strip\n * does not swallow a drag over the slides.\n *\n * Enumerated rather than a bare `[data-controls]`: a third placement has to opt in\n * by hand (the VTabs `:is()` rule). ORDER IS LOAD-BEARING: the per-placement blocks\n * below are (0,5,0) like this one.\n */\n .v-carousel:is([data-controls='inside'], [data-controls='outside'])\n > .v-carousel-stage\n > .v-carousel-controls {\n position: absolute;\n inset: 0;\n align-items: center;\n justify-content: space-between;\n pointer-events: none;\n }\n\n .v-carousel[data-controls='inside'] > .v-carousel-stage > .v-carousel-controls {\n inset: var(--vectis-space-2);\n }\n\n /*\n * `outside`: the pair is pulled out of the stage by exactly the gutter the root\n * reserved, so each button sits in that padding, one gap clear of the slides, still\n * centred on the stage's cross axis.\n *\n * RTL is FREE and needs no `--carousel-dir`: `inset-inline` is logical, the two\n * insets are equal so the box is direction-symmetric, and `space-between` puts\n * `previous` at the inline START — the right-hand side in RTL, where it belongs.\n */\n .v-carousel[data-controls='outside'] > .v-carousel-stage > .v-carousel-controls {\n inset-inline: calc(-1 * var(--carousel-outset));\n }\n\n .v-carousel[data-orientation='vertical'][data-controls='outside']\n > .v-carousel-stage\n > .v-carousel-controls {\n inset-inline: 0;\n inset-block: calc(-1 * var(--carousel-outset));\n }\n\n /*\n * `hover` REVEALS the pair, it never removes it: `display: none` and `visibility: hidden`\n * would drop the buttons from the tab order and the a11y tree, where `opacity: 0` keeps\n * both — the hidden-input rule. It costs no layout, so nothing shifts.\n *\n * The focus branch is read on the ROOT, so a keyboard user who has tabbed into the track\n * sees the navigation before deciding whether to use it. Same boundary autoplay pauses on.\n *\n * TRAP — `:has(:focus-visible)` and NEVER `:focus-within`. A pointer click LEAVES focus on\n * the control it hit, so reading any focus pins the pair revealed until the reader clicks\n * outside the carousel entirely, long after the pointer has gone. A click does not match\n * `:focus-visible`; a Tab does, which is the only case the focus branch is for.\n *\n * Behind `@media (hover: hover)`: a coarse pointer has no hover to give, so the\n * whole block is inert there and the pair stays permanently visible. NOT\n * `any-hover`, which is true as soon as any pointer capable of hovering is attached\n * and would strand exactly the users this protects.\n */\n @media (hover: hover) {\n .v-carousel[data-controls-visibility='hover'] > .v-carousel-stage > .v-carousel-controls {\n opacity: 0;\n }\n\n .v-carousel[data-controls-visibility='hover']:hover > .v-carousel-stage > .v-carousel-controls,\n .v-carousel[data-controls-visibility='hover']:has(:focus-visible)\n > .v-carousel-stage\n > .v-carousel-controls {\n opacity: 1;\n }\n }\n\n .v-carousel-control {\n pointer-events: auto;\n }\n\n .v-carousel-indicators {\n display: flex;\n gap: var(--vectis-space-1);\n align-items: center;\n justify-content: center;\n }\n\n .v-carousel[data-orientation='vertical'] > .v-carousel-indicators {\n flex-direction: column;\n }\n\n /*\n * `inside`: centred on the block-end edge, with NO surface of its own — a pill\n * behind the dots covers a slice of the slide the whole time, which is a lot of\n * media to spend on six 8px marks. The legibility moves into the dots instead\n * (see below), where it costs their own footprint and nothing more.\n */\n .v-carousel[data-indicators='inside'] > .v-carousel-indicators {\n position: absolute;\n inset-block-end: var(--vectis-space-3);\n inset-inline: 0;\n margin-inline: auto;\n inline-size: fit-content;\n }\n\n .v-carousel[data-orientation='vertical'][data-indicators='inside'] > .v-carousel-indicators {\n inset-block: 0;\n inset-inline: auto var(--vectis-space-3);\n block-size: fit-content;\n margin-block: auto;\n margin-inline: 0;\n }\n\n /*\n * The dot is 8px but the control is not: a 24px transparent hit area is what\n * satisfies `target-size` without inflating the visual.\n */\n .v-carousel-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n min-inline-size: var(--vectis-control-height-xs);\n min-block-size: var(--vectis-control-height-xs);\n padding: 0;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: var(--vectis-radius-pill);\n }\n\n .v-carousel-indicator:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-carousel-dot {\n display: block;\n inline-size: var(--vectis-control-size-carousel-indicator);\n block-size: var(--vectis-control-size-carousel-indicator);\n background: var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-pill);\n transition:\n inline-size var(--vectis-duration-base) var(--vectis-ease-default),\n block-size var(--vectis-duration-base) var(--vectis-ease-default),\n background var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n .v-carousel-indicator:hover .v-carousel-dot {\n background: var(--vectis-color-text-subtle);\n }\n\n /* The current dot stretches into a pill ALONG THE SCROLL AXIS. */\n .v-carousel-indicator[aria-current] .v-carousel-dot {\n inline-size: var(--vectis-control-size-carousel-indicator-active);\n background: var(--vectis-color-accent);\n }\n\n .v-carousel[data-orientation='vertical']\n > .v-carousel-indicators\n > .v-carousel-indicator[aria-current]\n .v-carousel-dot {\n inline-size: var(--vectis-control-size-carousel-indicator);\n block-size: var(--vectis-control-size-carousel-indicator-active);\n }\n\n /*\n * `inside` dots sit on the SLIDE rather than on the page, so they are deliberately\n * theme-independent: their backdrop is arbitrary media, and `text-on-accent` is the\n * library's \"drawn on a coloured surface\" colour, white in both themes.\n *\n * There is no bar behind them — a pill would cover a slice of the media permanently — so\n * the legibility lives in the dot itself, which carries both halves of the classic pair:\n * a light fill for dark media and a dark hairline for light media. `surface-inverse` is\n * the ring colour because it is dark in BOTH themes, which is the property needed here.\n * Drawn as a `box-shadow`, so it costs no layout and the dots stay 8px.\n *\n * Translucency is safe on a dot: it holds no text, so `color-contrast` never runs on it.\n * What the ring answers is WCAG 1.4.11, on any backdrop rather than on a judged one.\n */\n .v-carousel[data-indicators='inside'] > .v-carousel-indicators .v-carousel-dot {\n background: color-mix(in oklab, var(--vectis-color-text-on-accent) 55%, transparent);\n box-shadow: 0 0 0 1px color-mix(in oklab, var(--vectis-color-surface-inverse) 45%, transparent);\n }\n\n .v-carousel[data-indicators='inside']\n > .v-carousel-indicators\n > .v-carousel-indicator:hover\n .v-carousel-dot,\n .v-carousel[data-indicators='inside']\n > .v-carousel-indicators\n > .v-carousel-indicator[aria-current]\n .v-carousel-dot {\n background: var(--vectis-color-text-on-accent);\n }\n\n /*\n * Forced colours flatten a background to `Canvas` and drop every shadow, and a dot is\n * made of nothing else: left alone, every dot and the current page's pill vanish. Each\n * dot draws its own edge and the current one the system selection colour. The class is\n * repeated to (0,6,0) so these rules outrank the `inside` pair above whatever the state.\n */\n @media (forced-colors: active) {\n .v-carousel-indicator\n .v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 1px CanvasText;\n }\n\n .v-carousel-indicator:hover\n .v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot {\n box-shadow: inset 0 0 0 1px Highlight;\n }\n\n .v-carousel-indicator[aria-current]\n .v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot {\n background: Highlight;\n box-shadow: none;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n /*\n * `animation: none` and not `animation-name: none`: it also drops the timeline\n * binding, so the effect stops sampling the scroller altogether. Being the\n * shorthand is also what takes the jump one-shot with it, both animations living\n * in the same list — a jump then simply lands, which is what the preference asks\n * for and what `scroll-behavior: auto` below already does to the travel.\n */\n .v-carousel-effect {\n animation: none;\n }\n\n .v-carousel-viewport {\n scroll-behavior: auto;\n }\n\n .v-carousel-controls,\n .v-carousel-dot {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAqjBM,IAAQ,GAQR,IAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA1Uf,IAAM,IAAQ,GAqDR,IAAQ,GAAmB,GAAA,YAAe,GAE1C,IAAI,EAAY,GAChB,KAAY,QAAmB,EAAM,SAAS,EAAE,MAAM,SAAS,KAAK,GACpE,KAAoB,QAAe,EAAM,aAAa,EAAE,MAAM,SAAS,QAAQ,GAC/E,KAAoB,QAAe,EAAM,aAAa,EAAE,MAAM,SAAS,IAAI,GAC3E,IAAa,QAAe,EAAM,gBAAgB,UAAU,GAM5D,KAAmB,QACjB,EAAM,aAAa,EAAW,QAAQ,KAAqB,EACnE,GACM,KAAmB,QACjB,EAAM,aAAa,EAAW,QAAQ,IAAuB,EACrE,GAWM,IAAS,EAAa,GACtB,IAAQ,QAAe,EAAO,MAAM,MAAM,GAI1C,WACJ,EAAO,MAAM,KAAK,GAAM,MAAU,GAAW,GAAM;GAAE;GAAO,KAAK,EAAK,OAAO;EAAM,CAAC,CAAC,GAWjF,KAAU,QACR,EAAM,SAAS,KAAA,KAAa,OAAO,WAAW,OAAO,EAAM,IAAI,CAAC,MAAM,CAC9E,GACM,IAAiB,QACrB,EAAM,WAAW,WAAW,EAAM,eAAe,KAAK,GAAQ,SAAS,UAAU,EAAM,MACzF;EAEA,GAAQ,IAAa;GACnB,IAAI,uBAAuB;IACzB,OAAO,EAAE,MAAM,SAAS;GAC1B;GACA,aAAa,MAAkB,EAAE,MAAM,SAAS,MAAM,IAAQ,GAAG,EAAM,KAAK;EAC9E,CAAC;EAeD,IAAM,IAAY,EAAe,GAC3B,IAAgB,EAAI,CAAC;EAQ3B,SAAS,GAAe,GAAuB;GACxC,EAAM,cAAc,WAAW,kBAAkB,KAEvC,EAAM,QAA2B,QAAQ,aAAa,MACvD,EAAM,kBAAe,EAAU,QAAQ,KAAA;EACvD;EAEA,IAAM,KAAY,SAA4B;GAC5C,uBAAuB,OAAO,EAAM,YAAY;GAChD,uBAAuB,EAAQ,EAAM,WAAW;GAChD,mBAAmB,EAAQ,EAAM,IAAI;GACrC,kBAAkB,EAAQ,EAAM,GAAG;GACnC,6BAA6B,EAAQ,EAAM,MAAM;GACjD,uBAAuB,OAAO,EAAc,KAAK;EACnD,EAAE,GAEI,IAAa,EAAwB,IAAI,GAazC,IAAW,EAAI,EAAK,GAYpB,WAAoB;GACxB,IAAM,IAAO,EAAW;GAExB,AADI,KAAM,GAAQ,CAAI,GACtB,EAAS,QAAQ;EACnB,GAkBI,IAAW;EAOf,SAAS,GAAQ,GAAmB,GAAe;GACjD,IAAM,IAAQ,EAAK,cAA2B,kCAAkC,EAAM,GAAG;GACzF,IAAI,CAAC,GAAO;GACZ,IAAM,IAAS,EAAM,sBAAsB,GAIrC,IAAS,EAAK,sBAAsB;GAC1C,OAAO;IAAE,MAAM,EAAO,OAAO,EAAO;IAAM,KAAK,EAAO,MAAM,EAAO;GAAI;EACzE;EAGA,IAAM,MAAW,MACf,KAAK,IAAI,EAAM,IAAI,IAAI,KAAK,KAAK,IAAI,EAAM,GAAG,IAAI;EA4BpD,SAAS,GAAc,GAAe,IAAO,GAAO;GAClD,IAAM,IAAO,EAAW;GACxB,IAAI,CAAC,GAAM;GACX,IAAM,IAAQ,GAAQ,GAAM,CAAK;GACjC,IAAI,CAAC,GAAO;GACZ,IAAM,EAAE,SAAM,WAAQ;GAMtB,IAAI,GAAQ,CAAK,GAAG;GAKpB,IAAM,IAAO,CAAC,EAAM,UAAU,KAAK,IAAI,IAAQ,CAAI,IAAI;GAkCvD,AA1BI,MACF,EAAc,QAAQ,IAAQ,IAAO,IAAI,IACzC,EAAU,QAAQ,EAAU,UAAU,MAAM,MAAM,MAEpD,EAAS,QAAQ,IACjB,EAAK,WAAW,IAAO;IAAE;IAAM;IAAK,UAAU;GAAU,IAAI;IAAE;IAAM;GAAI,CAAC,GAqBrE,KACF,4BAA4B;IAC1B,IAAM,IAAQ,GAAQ,GAAM,CAAK;IACjC,AAAI,KAAS,CAAC,GAAQ,CAAK,KAAG,EAAK,WAAW;KAAE,GAAG;KAAO,UAAU;IAAU,CAAC;GACjF,CAAC;EACL;EAEA,EAAM,IAAQ,GAAO,MAAa;GAK5B,KAGJ,SAAoB,GAAc,GAAO,CAAQ,CAAC;EACpD,CAAC;EAED,IAAM,IAAgB,EAAY,GAC5B,KAAgB,EAAY;EA6DlC,SAAS,GAAQ,GAAmB;GAClC,IAAM,IAAQ,EAAK,iBAA8B,CAAM,GACjD,IAAQ,EAAM,EAAE,EAAE,sBAAsB,GACxC,IAAS,EAAM,EAAE,EAAE,sBAAsB,GACzC,IAAO,EAAM,EAAM,SAAS,EAAE,EAAE,sBAAsB;GAC5D,IAAI,CAAC,KAAS,CAAC,KAAU,CAAC,GAAM;GAChC,IAAM,IAAS,EAAK,sBAAsB,GACpC,IAAW,EAAW,OACtB,IAAkB,KAAK,IAAhB,IAAoB,EAAO,MAAM,EAAM,MAAgB,EAAO,OAAO,EAAM,IAAI;GAI5F,IAAI,KAAQ,GAAG;GAGf,IAAM,IAAoB,KAAK,IAAhB,IAAoB,EAAM,MAAM,EAAO,MAAgB,EAAM,OAAO,EAAO,IAAI,GAMxF,KAHO,IACT,KAAK,IAAI,EAAM,QAAQ,EAAK,MAAM,IAAI,KAAK,IAAI,EAAM,KAAK,EAAK,GAAG,IAClE,KAAK,IAAI,EAAM,OAAO,EAAK,KAAK,IAAI,KAAK,IAAI,EAAM,MAAM,EAAK,IAAI,MAC3C,IAAW,EAAK,eAAe,EAAK,cAEzD,IAAS,KAAK,OAAO,IAAa,KAAS,CAAI,GAC/C,IAAW,IAAa,IAAS,GACjC,IAAQ,IAAS,IAAK,MAAW,IAIjC,IAAO,KAAU,IAAa,IAAQ,IAAQ,IAAI,KAAK,MAAM,IAAS,CAAI;GAWhF,AATA,GAAc,QAAQ,GAStB,EAAc,QAAQ,EAAM,GAAM,GAAG,EAAU,QAAQ,CAAC;EAC1D;EAkBA,IAAM,IAAY,QAChB,EAAM,UAAU,IACZ,IACA,EAAM,GAAc,SAAS,EAAM,QAAQ,EAAM,eAAe,GAAG,GAAG,EAAM,KAAK,CACvF,GAUM,KAAY,QAChB,KAAK,UAAU,EAAO,MAAM,KAAK,GAAM,MAAU,OAAO,EAAK,OAAO,CAAK,CAAC,CAAC,CAC7E;EAgDA,AA3BA,QACE,EACE,CAAC,GAAY,EAAS,IACrB,CAAC,IAAO,GAAW,MAAc;GAIhC,IAAI,CAAC,KAAQ,OAAO,uBAAyB,KAAa;GAC1D,IAAM,IAAW,IAAI,2BACb;IAKJ,AAJA,GAAQ,CAAI,GAIR,EAAc,UAAU,EAAM,UAAO,EAAS,QAAQ;GAC5D,GACA;IAAE,MAAM;IAAM,WAAW;KAAC;KAAG;KAAM;KAAK;KAAM;IAAC;GAAE,CACnD;GACA,KAAK,IAAM,KAAS,EAAK,iBAAiB,CAAM,GAAG,EAAS,QAAQ,CAAK;GACzE,QAAgB,EAAS,WAAW,CAAC;EACvC,GACA;GAAE,OAAO;GAAQ,WAAW;EAAK,CACnC,CACF,GAIA,EAAM,CAAC,GAAe,CAAQ,SAAS;GACrC,IAAM,IAAQ,EAAc;GACxB,EAAS,SAAS,MAAU,KAAA,KAAa,MAAU,EAAM,UAC7D,IAAW,IACX,EAAM,QAAQ,GAQd,SAAqB,IAAW,EAAM;EACxC,CAAC;EASD,IAAM,IAAU,QAAe,EAAM,QAAQ,EAAU,QAAQ,CAAC,GAiB1D,IAAU,QAAe,CAAC,EAAQ,SAAS,EAAM,SAAS,CAAC,GAC3D,IAAQ,QAAe,CAAC,EAAQ,SAAS,EAAM,SAAS,EAAU,QAAQ,CAAC;EAEjF,SAAS,EAAK,GAAe;GAC3B,IAAM,IAAQ,EAAU;GAWxB,EAAM,QAAQ,EAAQ,SAAU,IAAQ,IAAS,KAAS,IAAQ,EAAM,GAAO,GAAG,IAAQ,CAAC;EAC7F;EAQA,QACE,EACE,CAAC,GAAO,CAAS,IAChB,CAAC,GAAO,OAAW;GAClB,IAAI,MAAU,GAAG;GACjB,IAAM,IAAU,OAAO,SAAS,CAAK,IAAI,EAAM,KAAK,MAAM,CAAK,GAAG,GAAG,IAAQ,CAAC,IAAI;GAClF,AAAI,MAAY,MAAO,EAAM,QAAQ;EACvC,GACA,EAAE,WAAW,GAAK,CACpB,CACF;EAEA,IAAM,WAAiB,EAAK,EAAM,QAAQ,CAAC,GACrC,UAAa,EAAK,EAAM,QAAQ,CAAC;EAevC,SAAS,GAAU,GAAsB;GACvC,IAAM,IAAO,EAAW;GAIxB,IAHI,CAAC,KAAQ,EAAM,UAAU,EAAM,WAAW,EAAM,WAAW,EAAM,YAGjE,EAAM,WAAW,EAAM,iBAAiB,EAAM,kBAAkB;GAEpE,IAAI,EAAM,QAAQ,UAAU,EAAM,QAAQ,OAAO;IAE/C,AADA,EAAM,eAAe,GACrB,EAAK,EAAM,QAAQ,SAAS,IAAI,EAAU,QAAQ,CAAC;IACnD;GACF;GAOA,IAAM,KAH4C,EAAW,QACzD;IAAE,SAAS;IAAI,WAAW;GAAE,IAC5B;IAAE,WAAW;IAAI,YAAY;GAAE,EAAA,CAChB,EAAM;GACzB,IAAI,MAAS,KAAA,GAAW;GACxB,EAAM,eAAe;GAGrB,IAAM,IAAM,CAAC,EAAW,SAAS,EAAM,CAAI;GAC3C,EAAK,EAAM,SAAS,IAAM,CAAC,IAAO,EAAK;EACzC;EASA,QACE,EAAM,CAAC,GAAS,CAAK,IAAI,CAAC,GAAO,IAAM,CAAC,GAAU,OAAY;GAC5D,IAAM,IAAO,EAAW,OAClB,IAAQ,GAAM;GACpB,IAAI,CAAC,KAAQ,CAAC,GAAO;GACrB,IAAM,CAAC,GAAM,KAAW,EAAM,iBAC5B,qDACF,GACM,IAAS,SAAS;GACxB,AAAI,KAAS,CAAC,KAAY,MAAW,KAAO,IAAM,IAAO,EAAA,EAAU,MAAM,IAChE,KAAO,CAAC,KAAU,MAAW,MAAU,IAAQ,IAAO,EAAA,EAAO,MAAM;EAC9E,CAAC,CACH;EAEA,IAAM,EAAE,WAAO,eAAW,EAAS,GAC7B,IAAU,EAAI,EAAK,GAMnB,IAAU,EAAI,EAAK,GAEnB,KAAgB,EAAI,EAAK,GAYzB,IAAW,QAEb,EAAM,WAAW,KAAK,CAAC,EAAQ,SAAS,CAAC,EAAQ,SAAS,CAAC,GAAc,SAAS,CAAC,EAAM,KAC7F;EAMA,QACE,EACE,CAAC,GAAU,CAAK,SACV;GAEJ,AADA,GAAO,GACH,EAAS,SAAO,GAAM,GAAM,EAAM,QAAQ;EAChD,GACA,EAAE,WAAW,GAAK,CACpB,CACF;EAUA,IAAI;EASJ,AARA,QAAgB;GACd,IAAM,IAAQ,OAAO,aAAa,kCAAkC;GACpE,IAAI,CAAC,GAAO;GACZ,IAAM,UAAc,GAAc,QAAQ,EAAM;GAGhD,AAFA,EAAK,GACL,EAAM,iBAAiB,UAAU,CAAI,GACrC,UAA2B,EAAM,oBAAoB,UAAU,CAAI;EACrE,CAAC,GACD,SAAsB,IAAqB,CAAC;EAW5C,IAAM,KAAc,QAClB,EAAM,UAAU,IAAI,KAAK,EAAE,MAAM,SAAS,MAAM,EAAM,QAAQ,GAAG,EAAM,KAAK,CAC9E;EAGA,IAAI,GAAO;GAGT,IAAM,oBAAS,IAAI,IAAY,GACzB,KAAQ,GAAY,MAAoB;IACxC,EAAO,IAAI,CAAE,MACjB,EAAO,IAAI,CAAE,GACb,QAAQ,KAAK,CAAO;GACtB;GACA,SAAkB;IA0BhB,AAzBI,EAAM,WAAW,EAAe,SAClC,EACE,QACA,EAAM,eAAe,IACjB,gLACA,yKACN,GASE,EAAM,QAAQ,EAAM,WAAW,KACjC,EACE,QACA,uKACF,GACE,EAAW,SAAS,EAAM,WAAW,KAAA,KACvC,EACE,UACA,8KACF,IACE,CAAC,OAAO,UAAU,EAAM,YAAY,KAAK,EAAM,eAAe,MAChE,EACE,gBACA,4DAA4D,EAAM,aAAa,EACjF;GACJ,CAAC;EACH;yBAIE,EA8HM,OAAA;GA7HJ,OAAM;GACN,MAAK;GACJ,wBAAsB,EAAA,CAAA,CAAC,CAAC,SAAS;GACjC,cAAY,EAAA,EAAA;GACZ,OAAK,GAAE,GAAA,KAAS;GAChB,oBAAkB,EAAA;GAClB,eAAa,EAAA;GACb,aAAW,EAAA;GACX,iBAAe,EAAA,YAAY,KAAA;GAC3B,mBAAiB,EAAA,cAAc,KAAA;GAC/B,4BAA0B,EAAA;GAC1B,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAA,QAAO;GACrB,gBAAY,AAAA,EAAA,QAAA,MAAE,EAAA,QAAO;GACrB,WAAO,AAAA,EAAA,QAAA,MAAE,EAAA,QAAU,EAAA,CAAA,CAAe,CAAC,EAAO,MAAM;GAChD,YAAQ,AAAA,EAAA,QAAA,MAAE,EAAA,QAAO;GACjB,gBAAc;;GAOf,EA2DM,OA3DN,IA2DM,CAlDJ,EAUM,OAAA;aATA;IAAJ,KAAI;IACJ,OAAM;IACN,MAAK;IACL,UAAS;IACR,cAAY,EAAA,CAAA,CAAC,CAAC,SAAS;IACd;IACT,aAAW;aAEZ,GAA0B,GAAV,EAAM,CAAA,EAAA,GAAA,IAAA,EAAA,GAGxB,EAqCO,EAAA,QAAA,YAAA;IAnCM;IACJ;IACN,SAAU,EAAA;IACV,OAAQ,EAAA;IACR,OAAO,EAAA;IACP,OAAO,EAAA;IACP,WAAY,EAAA;IACZ,aAAa,EAAA;YA4BT,CA1BM,EAAA,YAAA,EAAA,GAAX,EAyBM,OAzBN,IAyBM,CAxBJ,EAWc,GAAA;IAVZ,OAAM;IACL,OAAO,GAAA;IACR,SAAQ;IACR,MAAK;IACL,OAAM;IACN,UAAA;IACC,UAAU,EAAA;IACV,SAAO;;qBAE8D,CAAtE,EAAsE,GAAtE,EAAe,EAAuD,CAAA,CAA9C,CAAC,GAAA,KAAgB,GAAA,EAAI,UAAQ,CAAG,EAAA,MAAA,CAAA,GAAA,MAAA,IAAA,CAAA,UAAA,CAAA,CAAA,CAAA;;iCAE1D,EAWc,GAAA;IAVZ,OAAM;IACL,OAAO,GAAA;IACR,SAAQ;IACR,MAAK;IACL,OAAM;IACN,UAAA;IACC,UAAU,EAAA;IACV,SAAO;;qBAE8D,CAAtE,EAAsE,GAAtE,EAAe,EAAuD,CAAA,CAA9C,CAAC,GAAA,KAAgB,GAAA,EAAI,UAAQ,CAAG,EAAA,MAAA,CAAA,GAAA,MAAA,IAAA,CAAA,UAAA,CAAA,CAAA,CAAA;;;GAMhE,EAqCO,EAAA,QAAA,cAAA;IAnCJ,OAAO,EAAA;IACP,OAAO,EAAA;IACP,WAAY,EAAA;IACL;IACP,aAAa,EAAA;YA+BT,CA5BG,EAAA,cAAA,EAAA,GADR,EA4BM,OAAA;;IA1BJ,OAAM;IACN,MAAK;IACJ,cAAY,EAAA,CAAA,CAAC,CAAC,SAAS;eAWxB,EAYS,IAAA,MAAA,GAXS,EAAA,QAAT,YADT,EAYS,UAAA;IAVN,KAAK;IACN,MAAK;IACL,OAAM;IACL,cAAY,EAAA,CAAA,CAAC,CAAC,SAAS,MAAM,GAAO,EAAA,KAAK;IACzC,gBAAc,IAAK,MAAS,EAAA,QAAK,SAAY,KAAA;IAC7C,UAAK,MAAE,EAAK,IAAK,CAAA;OAElB,EAEO,EAAA,QAAA,aAAA;IAFiB,OAAO,IAAK;IAAO,QAAQ,IAAK,MAAS,EAAA;YAE1D,CAAA,AAAA,EAAA,OADL,EAA+B,QAAA,EAAzB,OAAM,iBAAgB,GAAA,MAAA,EAAA,CAAA,CAAA,CAAA,GAAA,GAAA,EAAA;GAMpC,EAES,QAAA;IAFH,OAAM;IAAoB,MAAK;IAAU,aAAW,EAAA,QAAQ,QAAA;SAChE,GAAA,KAAW,GAAA,GAAA,EAAA"}
|
|
1
|
+
{"version":3,"file":"VCarousel.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VCarousel/VCarousel.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @a11y @ssr @core\n/**\n * Native scroll snapping and CSS timelines drive slides. JavaScript supplies page measurements,\n * keyboard controls, autoplay and effects where browser support is absent.\n */\n\nimport {\n cloneVNode,\n computed,\n onBeforeUnmount,\n onMounted,\n nextTick,\n ref,\n provide,\n watch,\n watchEffect,\n} from 'vue'\nimport type { StyleValue } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { arrow_downward_alt as arrowDownwardAltIcon } from '../VIcon/icons/arrow_downward_alt'\nimport { arrow_left_alt as arrowLeftAltIcon } from '../VIcon/icons/arrow_left_alt'\nimport { arrow_right_alt as arrowRightAltIcon } from '../VIcon/icons/arrow_right_alt'\nimport { arrow_upward_alt as arrowUpwardAltIcon } from '../VIcon/icons/arrow_upward_alt'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport { carouselKey } from './context'\n\nimport { cssSize } from '../../utils/css'\nimport { isRtl } from '../../utils/direction'\nimport { isDev } from '../../utils/env'\nimport { isKeyboardFocus } from '../../utils/focus'\nimport { clamp } from '../../utils/number'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useSlotNodes } from '../../composables/useSlotNodes'\nimport { useTimer } from '../../composables/useTimer'\nimport { useMessages } from '../../i18n/state'\n\n/** How one slide gives way to the next: sliding, fading in place, or scaling down. */\nexport type CarouselEffect = 'slide' | 'fade' | 'scale'\n/** Whether the carousel scrolls across the page or down it. */\nexport type CarouselOrientation = 'horizontal' | 'vertical'\n\n/** What the `#controls` slot receives. */\nexport interface CarouselControlsSlotProps {\n /** Moves one position back. */\n previous: () => void\n /** Moves one position forward. */\n next: () => void\n /** Whether there is no position before this one (always false when looping). */\n atStart: boolean\n /** Whether there is no position after this one (always false when looping). */\n atEnd: boolean\n /** The current position, from 0. */\n index: number\n /** How many slides there are. */\n count: number\n /** How many positions the carousel can actually rest on, which is usually fewer. */\n pageCount: number\n /** Which way it scrolls: a bar of your own cannot choose its icons without knowing. */\n orientation: CarouselOrientation\n}\n\n/** What the `#indicators` slot receives. */\nexport interface CarouselIndicatorsSlotProps {\n /** The current position, from 0. */\n index: number\n /** How many slides there are. */\n count: number\n /** How many positions there are: render one control per position, not per slide. */\n pageCount: number\n /** Moves to a position. */\n goTo: (index: number) => void\n /** Which way the carousel scrolls. */\n orientation: CarouselOrientation\n}\n\n/** What the `#indicator` slot receives. */\nexport interface CarouselIndicatorSlotProps {\n /** The position this dot stands for, from 0. */\n index: number\n /** Whether it is the current one. */\n active: boolean\n}\n/** Where the position dots go: nowhere, over the slides, or after them. */\nexport type CarouselIndicators = false | 'inside' | 'outside'\n/** Where the previous and next buttons go: nowhere, over the slides, or beside them. */\nexport type CarouselControls = false | 'inside' | 'outside'\n/** When those buttons are visible. */\nexport type CarouselControlsVisibility = 'always' | 'hover'\n\ninterface CarouselProps {\n /**\n * How many slides may be visible at once. It is a MAXIMUM and not a target: the floor below\n * decides how many actually fit, which makes the whole thing responsive without a single\n * breakpoint.\n */\n itemsPerView?: number\n /**\n * How small a slide is allowed to get. Once an equal share would fall below this, fewer\n * slides fit and the carousel simply scrolls further instead.\n */\n itemMinSize?: number | string\n /** How much of the NEXT slide is left showing, as a hint that there is more. */\n peek?: number | string\n /** The space between two slides. */\n gap?: number | string\n /** Whether the carousel scrolls across the page or down it. */\n orientation?: CarouselOrientation\n /**\n * How one slide gives way to the next, driven by the scroll itself. Asked for otherwise, it\n * falls back to sliding rather than degrading.\n */\n effect?: CarouselEffect\n /** The height of the visible area. */\n height?: number | string\n /**\n * Whether the carousel comes back round: past the last position it returns to the first, and\n * before the first it goes to the last. Off by default, which stops it at both ends.\n */\n loop?: boolean\n /**\n * Whether a move of more than one page keeps the whole scroll instead of going straight\n * there. Off by default: a dot five pages away lands at once and plays the transition once,\n * on arrival.\n */\n noJump?: boolean\n /**\n * How long each slide is shown before the next one, in milliseconds; zero means it does not\n * advance by itself.\n */\n autoplay?: number\n /**\n * Where the previous and next buttons go: over the slides, beside them, or nowhere. Placed\n * beside, they sit at the ends of the scrolling axis and their room is reserved as padding,\n * so the component's footprint is unchanged and the slides narrow instead.\n */\n controls?: CarouselControls\n /**\n * Where the position dots go: over the slides, after them, or nowhere. After them means\n * below when the carousel scrolls across the page, and beside it when it scrolls down.\n */\n indicators?: CarouselIndicators\n /**\n * Whether those buttons are always visible, or appear when the pointer is over the carousel\n * or the keyboard focus is inside it.\n */\n controlsVisibility?: CarouselControlsVisibility\n /** The icon of the previous button. It follows the orientation by default. */\n prevIcon?: IconSource\n /** The icon of the next button. It follows the orientation by default. */\n nextIcon?: IconSource\n /** What the previous button does, in words. It falls back to the dictionary. */\n prevLabel?: string\n /** What the next button does, in words. It falls back to the dictionary. */\n nextLabel?: string\n /** What screen readers announce for the carousel as a whole. */\n label?: string\n}\n\nconst props = withDefaults(defineProps<CarouselProps>(), {\n itemsPerView: 1,\n itemMinSize: undefined,\n peek: undefined,\n gap: undefined,\n orientation: 'horizontal',\n effect: 'slide',\n height: undefined,\n loop: false,\n noJump: false,\n autoplay: 0,\n controls: 'inside',\n indicators: 'outside',\n controlsVisibility: 'always',\n prevIcon: undefined,\n nextIcon: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n label: undefined,\n})\n\ndefineSlots<{\n /** The slides. */\n default(): unknown\n controls?(props: CarouselControlsSlotProps): unknown\n /** Replaces the whole bar of dots. */\n indicators?(props: CarouselIndicatorsSlotProps): unknown\n /**\n * Replaces what is drawn INSIDE one dot. The button itself, and everything that makes it\n * announce and behave correctly, stays the design system's.\n */\n indicator?(props: CarouselIndicatorSlotProps): unknown\n}>()\n\n/**\n * Which slide is current: the first one fully visible when several fit at once, which is also\n * the position the carousel has come to rest on.\n */\nconst model = defineModel<number>({ default: 0 })\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.carousel.label)\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? m.value.carousel.previous)\nconst resolvedNextLabel = computed(() => props.nextLabel ?? m.value.carousel.next)\nconst isVertical = computed(() => props.orientation === 'vertical')\n/* Arrows with a shaft rather than the chevrons VTabs and VPagination use, and the\n difference is the surface they sit on: those two put their control in a dense bar,\n where a chevron is exactly the right weight, while this pair is a round button laid\n over whatever media the slides carry. A hairline chevron dissolves at the centre of\n a 40px disc; a shaft fills it and reads at a glance. */\nconst resolvedPrevIcon = computed(\n () => props.prevIcon ?? (isVertical.value ? arrowUpwardAltIcon : arrowLeftAltIcon),\n)\nconst resolvedNextIcon = computed(\n () => props.nextIcon ?? (isVertical.value ? arrowDownwardAltIcon : arrowRightAltIcon),\n)\n\n// @ssr\n/*\n * The count comes from the slot's VNODES, never from a registry the items feed at mount: a\n * registry renders 0 slides on the server and N on the client; a guaranteed hydration mismatch,\n * and the very reason VTabs derives `hasPanels` from `slots` too. `cloneVNode` is then what\n * carries each slide its position: `setup()` order is deterministic on the first render but not\n * on a later insertion, so a self-incrementing counter would drift.\n */\nconst slides = useSlotNodes()\nconst count = computed(() => slides.value.length)\n\n// A functional component: it renders already-captured VNodes, which\n// <component :is> cannot do (it expects a definition). The VAvatarGroup idiom.\nconst Slides = () =>\n slides.value.map((node, index) => cloneVNode(node, { index, key: node.key ?? index }))\n\n/*\n * A peek of ZERO is no peek: the sizing formula collapses exactly to the peekless one, so\n * `peek: 0` (or `'0px'`) must not cost the effect. A length `parseFloat` cannot read; a\n * `calc()`; is taken as a real strip, the side that never piles slides up.\n */\nconst hasPeek = computed(\n () => props.peek !== undefined && Number.parseFloat(String(props.peek)) !== 0,\n)\nconst resolvedEffect = computed<CarouselEffect>(() =>\n props.effect === 'fade' && (props.itemsPerView > 1 || hasPeek.value) ? 'slide' : props.effect,\n)\n\nprovide(carouselKey, {\n get slideRoleDescription() {\n return m.value.carousel.slideRoleDescription\n },\n slideLabel: (index: number) => m.value.carousel.slide(index + 1, count.value),\n})\n\n/*\n * The jump one-shot, which replaces the travel whenever the model moves by more than one page\n * (see `scrollToIndex`). Two pieces of state, both read from the sheet: `jumpPhase` alternates\n * between two IDENTICAL keyframe names, and that is the whole reason there are two.\n */\nconst jumpPhase = ref<'a' | 'b'>()\nconst jumpDirection = ref(1)\n\n/*\n * The one-shot is taken off once it has played. Left on the root, it would play again on every\n * slide mounted LATER (a lazy page, a re-keyed list): an animation starts when its element is\n * created with a name, and in `fade` the newcomers would flash in from zero.\n */\nfunction onAnimationEnd(event: AnimationEvent) {\n if (!event.animationName.startsWith('v-carousel-jump-')) return\n const owner = (event.target as Element | null)?.closest('.v-carousel')\n if (owner === event.currentTarget) jumpPhase.value = undefined\n}\n\nconst rootStyle = computed<StyleValue>(() => ({\n '--carousel-per-view': String(props.itemsPerView),\n '--carousel-item-min': cssSize(props.itemMinSize),\n '--carousel-peek': cssSize(props.peek),\n '--carousel-gap': cssSize(props.gap),\n '--carousel-viewport-block': cssSize(props.height),\n '--carousel-jump-dir': String(jumpDirection.value),\n}))\n\nconst viewportEl = ref<HTMLElement | null>(null)\n\n/*\n * Raised while a programmatic scroll is in flight, so the read-back does not fight the request:\n * without it, asking for slide 4 is overwritten by 1, 2 and 3 as they pass under the port. A\n * `ref` and not a plain `let`, because LOWERING it must be able to re-run the sync.\n */\nconst settling = ref(false)\n\n/*\n * Lowering the guard alone hands that stale index to the watcher, which writes it while\n * `readBack` suppresses the correcting scroll: the dot then sits one page ahead, for good. It\n * is the CALL that removes the race, not its position; the sync watcher is a `pre` one, so both\n * writes land in the same flush and it runs once on the final values.\n */\nconst onScrollEnd = () => {\n const port = viewportEl.value\n if (port) measure(port)\n settling.value = false\n}\n\n/*\n * `scrollToIndex`'s \"already there\" test does not cover it: that holds only at rest, and\n * mid-gesture the delta is real. Nothing needs writing anyway; the scroller is already where\n * the read-back read it.\n */\nlet readBack = false\n\n/**\n * How far slide `index`'s start edge sits from the port's, right now. `undefined` when\n * the slide is gone, which the deferred correction below has to ask again about: it runs\n * a frame later, and a consumer is free to have changed the slides in between.\n */\nfunction deltaTo(port: HTMLElement, index: number) {\n const slide = port.querySelector<HTMLElement>(`:scope > [data-carousel-index='${index}']`)\n if (!slide) return\n const target = slide.getBoundingClientRect()\n // The VIEWPORT's rect, not the stage's: every DOM read in this component is\n // viewport-scoped, and the `outside` gutter is padding on the root, so nothing here\n // has to know about it.\n const origin = port.getBoundingClientRect()\n return { left: target.left - origin.left, top: target.top - origin.top }\n}\n\n/** Under a pixel on both axes is the scroller already being where it was asked to go. */\nconst arrived = (delta: { left: number; top: number }) =>\n Math.abs(delta.left) < 1 && Math.abs(delta.top) < 1\n\n/**\n * Writes the DOM from the model. A move of MORE than one is a JUMP: it scrolls `instant` and\n * plays a one-shot instead.\n */\nfunction scrollToIndex(index: number, from = index) {\n const port = viewportEl.value\n if (!port) return\n const delta = deltaTo(port, index)\n if (!delta) return\n const { left, top } = delta\n /*\n * Already there; which a model change coming FROM the read-back looks like. Arming the guard\n * here would be the other way to strand it: no movement means no `scrollend` to lower it\n * again.\n */\n if (arrived(delta)) return\n\n // `noJump` cancels the one-shot along with the cut, and has to: played over a travel\n // still in flight it would hold `fade`'s opacity at its own value for 200ms while the\n // scroller was several slides from where the animation says it has arrived.\n const jump = !props.noJump && Math.abs(index - from) > 1\n /*\n * Armed after the guards, so a jump that scrolls nothing animates nothing; and armed here\n * rather than in the watcher because the flush that carries these two writes to the DOM is a\n * microtask, hence still ahead of the paint that shows the new position. Set them a frame\n * later and `fade` flashes its destination at full opacity before dropping to zero to fade it\n * back in.\n */\n if (jump) {\n jumpDirection.value = index > from ? 1 : -1\n jumpPhase.value = jumpPhase.value === 'a' ? 'b' : 'a'\n }\n settling.value = true\n port.scrollBy?.(jump ? { left, top, behavior: 'instant' } : { left, top })\n\n /*\n * A jump measures once more, a frame later, and corrects. A relative delta read while another\n * scroll is in flight is already stale: a smooth scroll is driven off the main thread, so the\n * rects above lag the position the delta lands on by a frame or two, and `instant` then\n * freezes the error in; the browser does not re-snap after an explicit programmatic scroll.\n */\n if (jump)\n requestAnimationFrame(() => {\n const again = deltaTo(port, index)\n if (again && !arrived(again)) port.scrollBy?.({ ...again, behavior: 'instant' })\n })\n}\n\nwatch(model, (index, previous) => {\n // The guard is what stops the two directions chaining: without it the model write the\n // read-back has just made comes straight back as a scroll, fighting the finger mid-drag.\n // See the flag's own declaration for why `scrollToIndex`'s \"already there\" test does not\n // cover it.\n if (readBack) return\n // The watcher's own previous value and not `observedIndex`: it is the last COMMITTED page,\n // defined before the first measurement and immune to a reading in flight.\n void nextTick(() => scrollToIndex(index, previous))\n})\n\nconst observedIndex = ref<number>()\nconst measuredPages = ref<number>()\n\n/** Slack, in pixels, on every comparison below. */\nconst SLACK = 2\n\n/*\n * The slides are the viewport's children, never its descendants. A carousel placed in a slide\n * carries the same attribute on its own slides, and a descendant query then reads the inner\n * slide 0 as the outer slide 1: the step measures 0, the page count and the read-back freeze,\n * and \"next\" scrolls to the inner slide.\n */\nconst SLIDES = ':scope > [data-carousel-index]'\n\n/**\n * Read slide positions directly: IntersectionObserver reports threshold crossings, so peek and\n * fractional layouts can leave ratios stale throughout a backward scroll.\n */\nfunction measure(port: HTMLElement) {\n const boxes = port.querySelectorAll<HTMLElement>(SLIDES)\n const first = boxes[0]?.getBoundingClientRect()\n const second = boxes[1]?.getBoundingClientRect()\n const last = boxes[boxes.length - 1]?.getBoundingClientRect()\n if (!first || !second || !last) return\n const origin = port.getBoundingClientRect()\n const vertical = isVertical.value\n const step = vertical ? Math.abs(second.top - first.top) : Math.abs(second.left - first.left)\n // No layout at all (jsdom, a display:none ancestor). Writing a 0 here would drag\n // every clamp down with it; the prop-derived fallback stays the answer until there\n // IS a layout.\n if (step <= 0) return\n // How far the scroller has travelled: the first slide's start edge has left the\n // port's by exactly that much.\n const offset = vertical ? Math.abs(first.top - origin.top) : Math.abs(first.left - origin.left)\n // Outer edge to outer edge, picked by comparison rather than by index: in RTL the last\n // slide is the LEFTMOST box, so naming the sides would need a direction test.\n const span = vertical\n ? Math.max(first.bottom, last.bottom) - Math.min(first.top, last.top)\n : Math.max(first.right, last.right) - Math.min(first.left, last.left)\n const scrollable = span - (vertical ? port.clientHeight : port.clientWidth)\n\n const starts = Math.floor((scrollable + SLACK) / step)\n const residual = scrollable - starts * step\n const pages = starts + 1 + (residual > SLACK ? 1 : 0)\n // The end of the track is recognized by POSITION and not by rounding: it is the one page that\n // is not a multiple of `step`, so rounding would name the start position it falls short of;\n // the page the user has just left.\n const page = offset >= scrollable - SLACK ? pages - 1 : Math.round(offset / step)\n\n measuredPages.value = pages\n /*\n * Clamped against `pageCount` and not against the local `pages`, because that is the one\n * which also caps at `count`, and the write above is what makes it fresh. A slide LARGER than\n * the port is the case that needs it: `starts` already reaches the last slide there and the\n * leftover still mints a page, so the raw answer names an index no slide carries;\n * `scrollToIndex` would then silently do nothing and `settling` would never come back down.\n */\n observedIndex.value = clamp(page, 0, pageCount.value - 1)\n}\n\n// @ssr @fallback\n/** SSR, jsdom and the first paint. */\nconst pageCount = computed(() =>\n count.value === 0\n ? 0\n : clamp(measuredPages.value ?? count.value - props.itemsPerView + 1, 1, count.value),\n)\n\n/*\n * Re-arm observers when slide keys change, even if the count stays equal: Vue replaces those\n * elements and observers on detached nodes stop reporting.\n */\nconst slideKeys = computed(() =>\n JSON.stringify(slides.value.map((node, index) => String(node.key ?? index))),\n)\n\n/*\n * `1` must stay in `threshold`. Nothing READS a ratio, but the buckets still decide WHEN this\n * callback runs, and this observer is what makes the component cover a ROOT RESIZE; which an\n * IntersectionObserver does not do on its own, queueing an entry only on a threshold crossing.\n */\n/*\n * Started in `onMounted`, and so are the two watches further down that read the page count. A\n * watch evaluates its source at once, and every one of these reaches the slot through `slides`:\n * at setup that is a slot call outside the render, which Vue warns about for every slot passed\n * as a function (a render function, JSX).\n */\nonMounted(() =>\n watch(\n [viewportEl, slideKeys],\n ([port], _previous, onCleanup) => {\n // @fallback\n // IntersectionObserver exists neither in SSR nor in jsdom: `scrollend` still\n // measures there, and the whole loop is verified in the browser (play functions).\n if (!port || typeof IntersectionObserver === 'undefined') return\n const observer = new IntersectionObserver(\n () => {\n measure(port)\n // The request has arrived, so there is nothing left to protect. This is also the only\n // exit when the browser clamps a programmatic scroll to no movement at all;\n // `scrollend` never fires there.\n if (observedIndex.value === model.value) settling.value = false\n },\n { root: port, threshold: [0, 0.25, 0.5, 0.75, 1] },\n )\n for (const slide of port.querySelectorAll(SLIDES)) observer.observe(slide)\n onCleanup(() => observer.disconnect())\n },\n { flush: 'post', immediate: true },\n ),\n)\n\n// Watching `settling` as well as the reading is the whole point of making it a ref:\n// whichever of the two lands last, the sync runs.\nwatch([observedIndex, settling], () => {\n const index = observedIndex.value\n if (settling.value || index === undefined || index === model.value) return\n readBack = true\n model.value = index\n /*\n * The pre-flush watcher runs inside the flush this write schedules, a `nextTick` callback\n * only after it; so it has read the flag by the time this lands.\n */\n void nextTick(() => (readBack = false))\n})\n\n/*\n * The `loop` prop, resolved once against what there is to loop through, because below two\n * positions it is not merely pointless but wrong twice over: the modulo would divide by zero on\n * an empty carousel, and on a single page it would hand the reader two enabled buttons that\n * move nothing; where the disabled pair says the truth. So the prop asks, and this is the\n * answer every consumer below reads.\n */\nconst looping = computed(() => props.loop && pageCount.value > 1)\n\n/*\n * Pure derivations, and they may be only because `pageCount` never over-counts: index 0 is\n * always reachable and so is `pageCount - 1`, by construction. That is the load-bearing\n * premise; an over-count makes `atEnd` false at the real end, and autoplay then re-arms its\n * timer on every bounce, forever.\n */\nconst atStart = computed(() => !looping.value && model.value <= 0)\nconst atEnd = computed(() => !looping.value && model.value >= pageCount.value - 1)\n\nfunction goTo(index: number) {\n const pages = pageCount.value\n /*\n * The DOUBLE modulo. JS `%` keeps the sign of the dividend, so `previous()` at 0 gives\n * `-1 % 5 === -1`; a single one hands the model an index no slide carries, `scrollToIndex`\n * finds nothing, and the carousel stops dead with no error anywhere.\n */\n model.value = looping.value ? ((index % pages) + pages) % pages : clamp(index, 0, pages - 1)\n}\n// @core\n/*\n * Out of range, no dot is current, the live region announces a slide that does not exist, and\n * past the last page `settling` never comes back down: the browser clamps the scroll to no\n * movement, so no `scrollend` arrives to lower it.\n */\nonMounted(() =>\n watch(\n [model, pageCount],\n ([index, pages]) => {\n if (pages === 0) return\n const bounded = Number.isFinite(index) ? clamp(Math.round(index), 0, pages - 1) : 0\n if (bounded !== index) model.value = bounded\n },\n { immediate: true },\n ),\n)\n\nconst previous = () => goTo(model.value - 1)\nconst next = () => goTo(model.value + 1)\n\n// @keyboard\n/** The one keyboard concession, and it is not the one it looks like. */\nfunction onKeydown(event: KeyboardEvent) {\n const port = viewportEl.value\n if (!port || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return\n // A key typed INSIDE a slide belongs to what holds the focus there: a caret in a field,\n // a nested VTabs or VSlider, another carousel. Only the viewport itself is ours.\n if (event.target !== event.currentTarget || event.defaultPrevented) return\n\n if (event.key === 'Home' || event.key === 'End') {\n event.preventDefault()\n goTo(event.key === 'Home' ? 0 : pageCount.value - 1)\n return\n }\n\n // Only the axis the carousel actually scrolls: the other one must stay with the browser,\n // which still scrolls a slide taller than the viewport.\n const steps: Record<string, number | undefined> = isVertical.value\n ? { ArrowUp: -1, ArrowDown: 1 }\n : { ArrowLeft: -1, ArrowRight: 1 }\n const step = steps[event.key]\n if (step === undefined) return\n event.preventDefault()\n // The inline arrows are physical, hence inverted in RTL (the `arrowNav` rule); the block axis\n // does not flip.\n const rtl = !isVertical.value && isRtl(port)\n goTo(model.value + (rtl ? -step : step))\n}\n\n// @a11y\n/*\n * A control that disables itself under the keyboard focus would drop it on `<body>`. It is\n * handed to the opposite control, or to the viewport when both ends are reached (a single\n * page).\n */\nonMounted(() =>\n watch([atStart, atEnd], ([start, end], [wasStart, wasEnd]) => {\n const port = viewportEl.value\n const stage = port?.parentElement\n if (!port || !stage) return\n const [back, forward] = stage.querySelectorAll<HTMLElement>(\n ':scope > .v-carousel-controls > .v-carousel-control',\n )\n const active = document.activeElement\n if (start && !wasStart && active === back) (end ? port : forward)?.focus()\n else if (end && !wasEnd && active === forward) (start ? port : back)?.focus()\n }),\n)\n\nconst { start, cancel } = useTimer()\nconst hovered = ref(false)\n// @a11y\n// KEYBOARD focus, not any focus, hence `isKeyboardFocus` rather than a bare `focusin` flag.\n// Clicking `next` leaves the focus on it, and a plain flag would then pause the rotation for\n// good; the user has to click outside the carousel to get it going again, which is not a pause,\n// it is a trap.\nconst focused = ref(false)\n\nconst reducedMotion = ref(false)\n\n/*\n * `autoplay > 0` is a GUARD, not a default: `useTimer` runs a delay ≤ 0 synchronously (the DS\n * convention), and a synchronous callback bumping the model would recurse until the stack\n * blows. It is ALSO the consumer's stop control; the prop is reactive, so binding it to `0`\n * cancels the timer on the spot, which a pause button of your own would drive.\n */\nconst rotating = computed(\n () =>\n props.autoplay > 0 && !hovered.value && !focused.value && !reducedMotion.value && !atEnd.value,\n)\n\n// `immediate`: without it the first delay would only be armed by a LATER change,\n// so an autoplay that nobody touches would never advance at all. Started on MOUNT: a\n// server render never unmounts, so a timer armed there would outlive the request and\n// fire `next` on a dead instance.\nonMounted(() =>\n watch(\n [rotating, model],\n () => {\n cancel()\n if (rotating.value) start(next, props.autoplay)\n },\n { immediate: true },\n ),\n)\n\n// @a11y @ssr\n/*\n * The DS's second browser-preference read (after VHotkeys' `navigator`), and it\n * is not redundant with the CSS media query: that one stops a transition, never a\n * TIMER, and WCAG 2.2.2 is about the content moving at all. Client only, listener\n * released on unmount.\n */\nlet releaseMotionQuery: (() => void) | undefined\nonMounted(() => {\n const query = window.matchMedia?.('(prefers-reduced-motion: reduce)')\n if (!query) return\n const sync = () => (reducedMotion.value = query.matches)\n sync()\n query.addEventListener('change', sync)\n releaseMotionQuery = () => query.removeEventListener('change', sync)\n})\nonBeforeUnmount(() => releaseMotionQuery?.())\n\n// @a11y\n/*\n * Announced only at rest: narrating an auto-rotating carousel floods the screen reader, so the\n * region is `aria-live=\"off\"` while it rotates (APG). The TEXT always follows the model:\n * emptied while rotating instead, a hover that pauses the rotation would write it back and be\n * announced with nothing having moved.\n */\nconst liveMessage = computed(() =>\n count.value === 0 ? '' : m.value.carousel.slide(model.value + 1, count.value),\n)\n\n// @devwarn\nif (isDev) {\n // Once per instance each: the loop warning names the very binding it recommends, and\n // that binding re-runs this effect on every pause and resume.\n const warned = new Set<string>()\n const warn = (id: string, message: string) => {\n if (warned.has(id)) return\n warned.add(id)\n console.warn(message)\n }\n watchEffect(() => {\n if (props.effect !== resolvedEffect.value)\n warn(\n 'fade',\n props.itemsPerView > 1\n ? '[VCarousel] `fade` needs one item per view: over a single view timeline every slide is counter-translated onto the same spot, so they would pile up. Downgraded to `slide`.'\n : '[VCarousel] `fade` and `peek` are mutually exclusive: the counter-translate parks every slide over the viewport, so the peeked strip is covered. Downgraded to `slide`.',\n )\n /*\n * What it costs is the exemption the component leans on everywhere else: content that moves\n * on its own needs a way to stop it, and the five-second grace only ever applied because\n * the rotation ENDED. Looping, it does not, and hover and focus leave a touch user with\n * nothing.\n */\n if (props.loop && props.autoplay > 0)\n warn(\n 'loop',\n '[VCarousel] `loop` with `autoplay` rotates for as long as the page is open, so WCAG 2.2.2 asks for a way to stop it. Bind `autoplay` to 0 from a control of your own.',\n )\n if (isVertical.value && props.height === undefined)\n warn(\n 'height',\n '[VCarousel] a vertical carousel needs a `height`: a percentage flex-basis has no definite reference on the block axis. Falling back to --vectis-control-size-carousel-block.',\n )\n if (!Number.isInteger(props.itemsPerView) || props.itemsPerView < 1)\n warn(\n 'itemsPerView',\n `[VCarousel] \\`itemsPerView\\` must be an integer ≥ 1, got ${props.itemsPerView}.`,\n )\n })\n}\n</script>\n\n<template>\n <div\n class=\"v-carousel\"\n role=\"region\"\n :aria-roledescription=\"m.carousel.roleDescription\"\n :aria-label=\"ariaLabel\"\n :style=\"rootStyle\"\n :data-orientation=\"orientation\"\n :data-effect=\"resolvedEffect\"\n :data-jump=\"jumpPhase\"\n :data-controls=\"controls || undefined\"\n :data-indicators=\"indicators || undefined\"\n :data-controls-visibility=\"controlsVisibility\"\n @pointerenter=\"hovered = true\"\n @pointerleave=\"hovered = false\"\n @focusin=\"focused = isKeyboardFocus($event.target)\"\n @focusout=\"focused = false\"\n @animationend=\"onAnimationEnd\"\n >\n <!--\n The stage holds the viewport and the controls; and NOTHING else. The indicator bar is a\n sibling on purpose: inside this box it would join the height the controls are centred on,\n and the pair would sit visibly below the middle of the slides.\n -->\n <div class=\"v-carousel-stage\">\n <!--\n It is also what makes the track the keyboard target of `onKeydown`; the browser's own\n arrow scrolling is real, but mandatory snapping undoes it, see there. A focusable box\n with an aria-label must carry a role, or axe reports `aria-prohibited-attr`.\n -->\n <div\n ref=\"viewportEl\"\n class=\"v-carousel-viewport\"\n role=\"group\"\n tabindex=\"0\"\n :aria-label=\"m.carousel.slides\"\n @keydown=\"onKeydown\"\n @scrollend=\"onScrollEnd\"\n >\n <component :is=\"Slides\" />\n </div>\n\n <slot\n name=\"controls\"\n :previous=\"previous\"\n :next=\"next\"\n :at-start=\"atStart\"\n :at-end=\"atEnd\"\n :index=\"model\"\n :count=\"count\"\n :page-count=\"pageCount\"\n :orientation=\"orientation\"\n >\n <div v-if=\"controls\" class=\"v-carousel-controls\">\n <VIconButton\n class=\"v-carousel-control\"\n :label=\"resolvedPrevLabel\"\n variant=\"ghost\"\n tone=\"neutral\"\n shape=\"circular\"\n elevated\n :disabled=\"atStart\"\n @click=\"previous\"\n >\n <VIcon v-bind=\"iconProps(resolvedPrevIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n <VIconButton\n class=\"v-carousel-control\"\n :label=\"resolvedNextLabel\"\n variant=\"ghost\"\n tone=\"neutral\"\n shape=\"circular\"\n elevated\n :disabled=\"atEnd\"\n @click=\"next\"\n >\n <VIcon v-bind=\"iconProps(resolvedNextIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n </div>\n </slot>\n </div>\n\n <slot\n name=\"indicators\"\n :index=\"model\"\n :count=\"count\"\n :page-count=\"pageCount\"\n :go-to=\"goTo\"\n :orientation=\"orientation\"\n >\n <div\n v-if=\"indicators\"\n class=\"v-carousel-indicators\"\n role=\"group\"\n :aria-label=\"m.carousel.indicators\"\n >\n <!--\n One control per REACHABLE position, not per slide: with 6 slides three at a time the\n scroller can only lead with 1 to 4, and dots 5 and 6 would scroll nowhere. With a\n `peek` the last of them parks the scroller at the END of the track rather than on a\n slide's start edge.\n -->\n <button\n v-for=\"index in pageCount\"\n :key=\"index\"\n type=\"button\"\n class=\"v-carousel-indicator\"\n :aria-label=\"m.carousel.slide(index, count)\"\n :aria-current=\"index - 1 === model ? 'true' : undefined\"\n @click=\"goTo(index - 1)\"\n >\n <slot name=\"indicator\" :index=\"index - 1\" :active=\"index - 1 === model\">\n <span class=\"v-carousel-dot\" />\n </slot>\n </button>\n </div>\n </slot>\n\n <span class=\"v-visually-hidden\" role=\"status\" :aria-live=\"rotating ? 'off' : 'polite'\">{{\n liveMessage\n }}</span>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-carousel {\n /*\n * Geometry vector and direction sign. The effect keyframes are written on them once instead\n * of being duplicated per orientation and per direction: `translate` is physical, and\n * `calc(0 * -100%)` is a valid zero.\n */\n --carousel-axis-x: 1;\n --carousel-axis-y: 0;\n --carousel-dir: 1;\n\n /*\n * Effect constants: private, non-contractual geometry with no theming story,\n * hence no --vectis-* token (the qualification rule still applies).\n */\n --carousel-scale-min: 0.75;\n\n /*\n * The jump one-shot's starting point, NEUTRAL on all three axes here: each effect below\n * turns on the one dimension it owns, which lets a single pair of keyframes serve `slide`,\n * `fade` and `scale` instead of one block each. The direction sign is written from the\n * script, alongside the axis vector above.\n */\n --carousel-jump-opacity: 1;\n --carousel-jump-scale: 1;\n --carousel-jump-shift: 0%;\n --carousel-jump-dir: 1;\n\n /*\n * Both names are re-set on every root, to `none`, because a custom property inherits: a\n * carousel placed inside another's slide would otherwise play its host's effect and replay\n * its host's jump. Each effect and each jump phase below sets them again on the root that\n * carries the attribute.\n */\n --carousel-effect-name: none;\n --carousel-jump-name: none;\n\n --carousel-gap: var(--vectis-space-3);\n --carousel-item-min: 0px;\n --carousel-peek: 0px;\n --carousel-viewport-block: var(--vectis-control-size-carousel-block);\n /*\n * Room reserved on the SCROLL axis for the `outside` controls. `0px` and never `0`: it\n * feeds a `padding-*`, where a unitless zero is invalid; the `--carousel-peek` precedent\n * three lines up.\n */\n --carousel-outset: 0px;\n\n /*\n * Positioning context of the `inside` indicator bar, which is deliberately not a child of\n * the stage; see .v-carousel-stage.\n */\n position: relative;\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n font-family: var(--vectis-text-family);\n }\n\n /*\n * That is what lets every `inside` inset below be written against the stage's edges with no\n * compensation: an absolutely positioned box resolves against the PADDING box, the edges\n * those insets pin to are on the cross axis (which this never touches), and the axis they\n * centre on stays symmetric. Make this one-sided and the `inside` indicator bar goes\n * off-centre with nothing to report it.\n */\n .v-carousel[data-orientation='horizontal'] {\n padding-inline: var(--carousel-outset);\n }\n\n .v-carousel[data-orientation='vertical'] {\n --carousel-axis-x: 0;\n --carousel-axis-y: 1;\n\n flex-direction: row;\n padding-block: var(--carousel-outset);\n }\n\n /*\n * `control-height-md` is the size the two VIconButtons actually render at: they pass no\n * `size`, so they take VIconButton's own `md` default and their width is `--control-height`.\n * The gutter is that button plus one gap, so the pair lands flush inside the root's border\n * box and never covers a slide.\n */\n .v-carousel[data-controls='outside']:has(> .v-carousel-stage > .v-carousel-controls) {\n --carousel-outset: calc(var(--vectis-control-height-md) + var(--vectis-space-2));\n }\n\n /*\n * Scoped to `horizontal`: the block axis does not flip. `:dir()` and not a `[dir]` ancestor,\n * so an LTR subtree inside an RTL page reads its own direction.\n */\n .v-carousel[data-orientation='horizontal']:dir(rtl) {\n --carousel-dir: -1;\n }\n\n /*\n * Positioning context of the controls; and of NOTHING else. The indicator bar is a SIBLING on\n * purpose: inside this box it would join the height the controls are centred on, and an\n * `inside` pair would sit visibly below the middle of the slides.\n */\n .v-carousel-stage {\n position: relative;\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n min-inline-size: 0;\n min-block-size: 0;\n }\n\n /*\n * Vertical only. The root is a `row` there, so the inline axis is the flex MAIN axis and the\n * stage would shrink-wrap the widest slide's content; an `inside` bar's `inset-inline-end`\n * would then land on the ROOT's edge instead of the slides'.\n */\n .v-carousel[data-orientation='vertical'] > .v-carousel-stage {\n flex-grow: 1;\n }\n\n .v-carousel-viewport {\n display: flex;\n gap: var(--carousel-gap);\n /*\n * `auto` on both axes, never `auto hidden`: clipping the cross axis would crop the focus\n * ring of any focusable slide content.\n */\n overflow: auto;\n overscroll-behavior: contain;\n /* Logical keyword: RTL and vertical need no second declaration. */\n scroll-snap-type: inline mandatory;\n scroll-behavior: smooth;\n scrollbar-width: none;\n min-inline-size: 0;\n min-block-size: 0;\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-carousel-viewport::-webkit-scrollbar {\n display: none;\n }\n\n .v-carousel-viewport:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n .v-carousel[data-orientation='vertical'] > .v-carousel-stage > .v-carousel-viewport {\n flex-direction: column;\n scroll-snap-type: block mandatory;\n /*\n * A percentage flex-basis resolves against the container's MAIN size, which on\n * the block axis has nothing intrinsic here: without a definite one the basis\n * falls back to `auto` and every slide collapses onto its content.\n */\n block-size: var(--carousel-viewport-block);\n }\n\n .v-carousel-slide {\n /*\n * `max()` makes itemMinSize a floor, never a cap, and IS the responsiveness: no breakpoint,\n * no @container.\n */\n /*\n * LONGHANDS, never the `flex` shorthand; the `animation` rule further down, for the same\n * reason. A shorthand whose value contains `var()` becomes a PENDING SUBSTITUTION value: it\n * cannot be expanded until computed-value time, and if any one of the four custom\n * properties then resolves to something invalid the whole declaration is dropped and `flex`\n * reverts to its initial `0 1 auto`; shrink 1 and basis auto, which sizes every slide on\n * its content and destroys the snap grid.\n */\n flex-grow: 0;\n flex-shrink: 0;\n flex-basis: max(\n var(--carousel-item-min),\n calc(\n (100% - (var(--carousel-per-view) - 1) * var(--carousel-gap) - var(--carousel-peek)) /\n var(--carousel-per-view)\n )\n );\n /* `auto` is a CONTENT-based floor that can push a flex item past its basis:\n one unbreakable word would desynchronize the whole snap grid. */\n min-inline-size: 0;\n min-block-size: 0;\n scroll-snap-align: start;\n position: relative;\n }\n\n /*\n * The last slide is aligned on its END edge, and that is what declares the end of the track\n * as a snap position in its own right. As soon as a `peek` or an active `itemMinSize` makes\n * the fit fractional, that slide's START-aligned position lies past\n * `scrollWidth - clientWidth`, so the only position left near the end is a strip short of it\n * and the last slide is never fully revealed.\n */\n .v-carousel-slide:last-child {\n scroll-snap-align: end;\n }\n\n /*\n * Each effect names the keyframes it is written on, and the two mechanisms that play them\n * read the name from here rather than from a selector of their own; which keeps the animation\n * LISTS below written exactly once.\n */\n .v-carousel[data-effect='fade'] {\n --carousel-effect-name: v-carousel-fade;\n --carousel-jump-opacity: 0;\n }\n\n .v-carousel[data-effect='scale'] {\n --carousel-effect-name: v-carousel-scale;\n /* Exactly where the scroll-driven effect holds a slide off the centre, so the\n one-shot ends on the value that takes over from it. */\n --carousel-jump-scale: var(--carousel-scale-min);\n }\n\n .v-carousel[data-effect='slide'] {\n /*\n * A FIFTH of the slide, not its width. Every inner box shifts at once, so a full one would\n * park each over its neighbour and the overlap would paint the wrong slide for the length\n * of the run; invisible at one item per view, plain at three.\n */\n --carousel-jump-shift: 20%;\n }\n\n /*\n * The pair of names. `jumpPhase` alternates between two identical keyframe blocks because\n * re-setting an attribute does not restart a running animation, and `animation-name` is the\n * one thing that does.\n */\n .v-carousel-effect {\n block-size: 100%;\n animation-name: var(--carousel-jump-name, none);\n animation-duration: var(--vectis-duration-base);\n animation-timing-function: var(--vectis-ease-out);\n }\n\n .v-carousel[data-jump='a'] {\n --carousel-jump-name: v-carousel-jump-a;\n }\n\n .v-carousel[data-jump='b'] {\n --carousel-jump-name: v-carousel-jump-b;\n }\n\n /*\n * Progressively enhance supported scroll timelines. Animate the inner box, never the snap\n * area: transformed snap geometry would create a scroll-position feedback loop.\n */\n @supports (view-timeline-name: --v) and (animation-range: cover) {\n .v-carousel:not([data-effect='slide'])\n > .v-carousel-stage\n > .v-carousel-viewport\n > .v-carousel-slide {\n view-timeline: --carousel-view inline;\n }\n\n .v-carousel[data-orientation='vertical']:not([data-effect='slide'])\n > .v-carousel-stage\n > .v-carousel-viewport\n > .v-carousel-slide {\n view-timeline: --carousel-view block;\n }\n\n .v-carousel-effect {\n /*\n * Longhands, never the `animation` shorthand: `animation-timeline` and\n * `animation-duration` are reset-only sub-properties, so a shorthand written afterwards\n * would silently put the timeline back to `auto` and the duration to 0s, and every effect\n * would vanish with no error. six parallel lists, and their order is the arbitration.\n */\n animation-name: var(--carousel-effect-name, none), var(--carousel-jump-name, none);\n animation-timeline: --carousel-view, auto;\n animation-range: cover, normal;\n animation-fill-mode: both, none;\n animation-timing-function: linear, var(--vectis-ease-out);\n /* `auto` is the whole timeline. A duration in seconds would be taken as a\n PROPORTION of it, and the effect would end a fraction of the way in. */\n animation-duration: auto, var(--vectis-duration-base);\n }\n }\n\n /*\n * Over the `cover` range a slide's origin travels the whole viewport+slide distance, so a\n * counter-translate of ±100% of the slide's own size pins it on the scrollport when the two\n * are equal; which is why `fade` is defined at one item per view only. The result is a true\n * dissolve in place, gap included.\n */\n @keyframes v-carousel-fade {\n 0% {\n opacity: 0;\n translate: calc(var(--carousel-axis-x) * var(--carousel-dir) * -100%)\n calc(var(--carousel-axis-y) * -100%);\n }\n 50% {\n opacity: 1;\n translate: 0 0;\n }\n 100% {\n opacity: 0;\n translate: calc(var(--carousel-axis-x) * var(--carousel-dir) * 100%)\n calc(var(--carousel-axis-y) * 100%);\n }\n }\n\n /*\n * Geometry only, no opacity dimming; and that is an accessibility decision, not a taste one:\n * a slide off the centre still holds real text, and fading it is a measurable contrast loss\n * that axe rightly reports. The scale alone carries the depth.\n */\n @keyframes v-carousel-scale {\n 0%,\n 100% {\n scale: var(--carousel-scale-min);\n }\n 50% {\n scale: 1;\n }\n }\n\n /*\n * Two IDENTICAL BLOCKS, and the duplication is the mechanism rather than an oversight:\n * `jumpPhase` alternates between these names, because a CSS animation restarts on a change of\n * `animation-name` and on nothing else. Edit one, edit both.\n */\n @keyframes v-carousel-jump-a {\n from {\n opacity: var(--carousel-jump-opacity);\n scale: var(--carousel-jump-scale);\n translate: calc(\n var(--carousel-axis-x) * var(--carousel-dir) * var(--carousel-jump-dir) *\n var(--carousel-jump-shift)\n )\n calc(var(--carousel-axis-y) * var(--carousel-jump-dir) * var(--carousel-jump-shift));\n }\n to {\n opacity: 1;\n scale: 1;\n translate: 0 0;\n }\n }\n\n @keyframes v-carousel-jump-b {\n from {\n opacity: var(--carousel-jump-opacity);\n scale: var(--carousel-jump-scale);\n translate: calc(\n var(--carousel-axis-x) * var(--carousel-dir) * var(--carousel-jump-dir) *\n var(--carousel-jump-shift)\n )\n calc(var(--carousel-axis-y) * var(--carousel-jump-dir) * var(--carousel-jump-shift));\n }\n to {\n opacity: 1;\n scale: 1;\n translate: 0 0;\n }\n }\n\n .v-carousel-controls {\n display: flex;\n /*\n * A floor, not the layout; `space-between` places the pair. It only ever shows on a stage\n * narrow enough to bring the two buttons together.\n */\n gap: var(--vectis-space-2);\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-carousel[data-orientation='vertical'] > .v-carousel-stage > .v-carousel-controls {\n flex-direction: column;\n }\n\n /*\n * `pointer-events` is handed back to the buttons alone, so the strip does not swallow a drag\n * over the slides. Enumerated rather than a bare `[data-controls]`: a third placement has to\n * opt in by hand (the VTabs `:is()` rule).\n */\n .v-carousel:is([data-controls='inside'], [data-controls='outside'])\n > .v-carousel-stage\n > .v-carousel-controls {\n position: absolute;\n inset: 0;\n align-items: center;\n justify-content: space-between;\n pointer-events: none;\n }\n\n .v-carousel[data-controls='inside'] > .v-carousel-stage > .v-carousel-controls {\n inset: var(--vectis-space-2);\n }\n\n /*\n * `outside`: the pair is pulled out of the stage by exactly the gutter the root reserved, so\n * each button sits in that padding, one gap clear of the slides, still centred on the stage's\n * cross axis. RTL is FREE and needs no `--carousel-dir`: `inset-inline` is logical, the two\n * insets are equal so the box is direction-symmetric, and `space-between` puts `previous` at\n * the inline START; the right-hand side in RTL, where it belongs.\n */\n .v-carousel[data-controls='outside'] > .v-carousel-stage > .v-carousel-controls {\n inset-inline: calc(-1 * var(--carousel-outset));\n }\n\n .v-carousel[data-orientation='vertical'][data-controls='outside']\n > .v-carousel-stage\n > .v-carousel-controls {\n inset-inline: 0;\n inset-block: calc(-1 * var(--carousel-outset));\n }\n\n /*\n * `:has(:focus-visible)` and never `:focus-within`. A pointer click LEAVES focus on the\n * control it hit, so reading any focus pins the pair revealed until the reader clicks outside\n * the carousel entirely, long after the pointer has gone.\n */\n @media (hover: hover) {\n .v-carousel[data-controls-visibility='hover'] > .v-carousel-stage > .v-carousel-controls {\n opacity: 0;\n }\n\n .v-carousel[data-controls-visibility='hover']:hover > .v-carousel-stage > .v-carousel-controls,\n .v-carousel[data-controls-visibility='hover']:has(:focus-visible)\n > .v-carousel-stage\n > .v-carousel-controls {\n opacity: 1;\n }\n }\n\n .v-carousel-control {\n pointer-events: auto;\n }\n\n .v-carousel-indicators {\n display: flex;\n gap: var(--vectis-space-1);\n align-items: center;\n justify-content: center;\n }\n\n .v-carousel[data-orientation='vertical'] > .v-carousel-indicators {\n flex-direction: column;\n }\n\n /*\n * `inside`: centred on the block-end edge, with no surface of its own; a pill behind the dots\n * covers a slice of the slide the whole time, which is a lot of media to spend on six 8px\n * marks. The legibility moves into the dots instead (see below), where it costs their own\n * footprint and nothing more.\n */\n .v-carousel[data-indicators='inside'] > .v-carousel-indicators {\n position: absolute;\n inset-block-end: var(--vectis-space-3);\n inset-inline: 0;\n margin-inline: auto;\n inline-size: fit-content;\n }\n\n .v-carousel[data-orientation='vertical'][data-indicators='inside'] > .v-carousel-indicators {\n inset-block: 0;\n inset-inline: auto var(--vectis-space-3);\n block-size: fit-content;\n margin-block: auto;\n margin-inline: 0;\n }\n\n /*\n * The dot is 8px but the control is not: a 24px transparent hit area is what\n * satisfies `target-size` without inflating the visual.\n */\n .v-carousel-indicator {\n display: flex;\n align-items: center;\n justify-content: center;\n min-inline-size: var(--vectis-control-height-xs);\n min-block-size: var(--vectis-control-height-xs);\n padding: 0;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: var(--vectis-radius-pill);\n }\n\n .v-carousel-indicator:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-carousel-dot {\n display: block;\n inline-size: var(--vectis-control-size-carousel-indicator);\n block-size: var(--vectis-control-size-carousel-indicator);\n background: var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-pill);\n transition:\n inline-size var(--vectis-duration-base) var(--vectis-ease-default),\n block-size var(--vectis-duration-base) var(--vectis-ease-default),\n background var(--vectis-duration-base) var(--vectis-ease-default);\n }\n\n .v-carousel-indicator:hover .v-carousel-dot {\n background: var(--vectis-color-text-subtle);\n }\n\n .v-carousel-indicator[aria-current] .v-carousel-dot {\n inline-size: var(--vectis-control-size-carousel-indicator-active);\n background: var(--vectis-color-accent);\n }\n\n .v-carousel[data-orientation='vertical']\n > .v-carousel-indicators\n > .v-carousel-indicator[aria-current]\n .v-carousel-dot {\n inline-size: var(--vectis-control-size-carousel-indicator);\n block-size: var(--vectis-control-size-carousel-indicator-active);\n }\n\n /*\n * `surface-inverse` is the ring colour because it is dark in both themes, which is the\n * property needed here. Drawn as a `box-shadow`, so it costs no layout and the dots stay 8px.\n */\n .v-carousel[data-indicators='inside'] > .v-carousel-indicators .v-carousel-dot {\n background: color-mix(in oklab, var(--vectis-color-text-on-accent) 55%, transparent);\n box-shadow: 0 0 0 1px color-mix(in oklab, var(--vectis-color-surface-inverse) 45%, transparent);\n }\n\n .v-carousel[data-indicators='inside']\n > .v-carousel-indicators\n > .v-carousel-indicator:hover\n .v-carousel-dot,\n .v-carousel[data-indicators='inside']\n > .v-carousel-indicators\n > .v-carousel-indicator[aria-current]\n .v-carousel-dot {\n background: var(--vectis-color-text-on-accent);\n }\n\n /*\n * Forced colours flatten a background to `Canvas` and drop every shadow, and a dot is made of\n * nothing else: left alone, every dot and the current page's pill vanish. Each dot draws its\n * own edge and the current one the system selection colour.\n */\n @media (forced-colors: active) {\n .v-carousel-indicator\n .v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot {\n forced-color-adjust: none;\n background: Canvas;\n box-shadow: inset 0 0 0 1px CanvasText;\n }\n\n .v-carousel-indicator:hover\n .v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot {\n box-shadow: inset 0 0 0 1px Highlight;\n }\n\n .v-carousel-indicator[aria-current]\n .v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot.v-carousel-dot {\n background: Highlight;\n box-shadow: none;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n /*\n * `animation: none` and not `animation-name: none`: it also drops the timeline binding, so\n * the effect stops sampling the scroller altogether. Being the shorthand is also what takes\n * the jump one-shot with it, both animations living in the same list; a jump then simply\n * lands, which the preference asks for and what `scroll-behavior: auto` below already does\n * to the travel.\n */\n .v-carousel-effect {\n animation: none;\n }\n\n .v-carousel-viewport {\n scroll-behavior: auto;\n }\n\n .v-carousel-controls,\n .v-carousel-dot {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
interface CarouselItemProps {
|
|
2
|
-
/**
|
|
3
|
-
* Which slide this is among its siblings. The carousel injects it as it renders them.
|
|
4
|
-
*
|
|
5
|
-
* NEVER pass it by hand: it is what makes the "3 of 8" a screen reader announces
|
|
6
|
-
* identical on the server and in the browser.
|
|
7
|
-
*/
|
|
2
|
+
/** Which slide this is among its siblings. */
|
|
8
3
|
index?: number;
|
|
9
4
|
}
|
|
10
5
|
type __VLS_Slots = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VCarouselItem.js","names":[],"sources":["../../../src/components/VCarousel/VCarouselItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n *
|
|
1
|
+
{"version":3,"file":"VCarouselItem.js","names":[],"sources":["../../../src/components/VCarousel/VCarouselItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Keep the outer snap area unanimated; transform only the inner box to avoid circular\n * dependencies between scroll position and snap geometry.\n */\n\nimport { inject } from 'vue'\n\nimport { carouselKey } from './context'\n\ninterface CarouselItemProps {\n /** Which slide this is among its siblings. */\n index?: number\n}\n\nconst props = withDefaults(defineProps<CarouselItemProps>(), { index: 0 })\n\ndefineSlots<{\n /** The slide's content: an image, a card, free text. */\n default(): unknown\n}>()\n\nconst carousel = inject(carouselKey, null)\n</script>\n\n<template>\n <div\n class=\"v-carousel-slide\"\n role=\"group\"\n :aria-roledescription=\"carousel?.slideRoleDescription\"\n :aria-label=\"carousel?.slideLabel(props.index)\"\n :data-carousel-index=\"props.index\"\n >\n <div class=\"v-carousel-effect\"><slot /></div>\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VCarouselItem.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VCarousel/VCarouselItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n *
|
|
1
|
+
{"version":3,"file":"VCarouselItem.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VCarousel/VCarouselItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Keep the outer snap area unanimated; transform only the inner box to avoid circular\n * dependencies between scroll position and snap geometry.\n */\n\nimport { inject } from 'vue'\n\nimport { carouselKey } from './context'\n\ninterface CarouselItemProps {\n /** Which slide this is among its siblings. */\n index?: number\n}\n\nconst props = withDefaults(defineProps<CarouselItemProps>(), { index: 0 })\n\ndefineSlots<{\n /** The slide's content: an image, a card, free text. */\n default(): unknown\n}>()\n\nconst carousel = inject(carouselKey, null)\n</script>\n\n<template>\n <div\n class=\"v-carousel-slide\"\n role=\"group\"\n :aria-roledescription=\"carousel?.slideRoleDescription\"\n :aria-label=\"carousel?.slideLabel(props.index)\"\n :data-carousel-index=\"props.index\"\n >\n <div class=\"v-carousel-effect\"><slot /></div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;EAeA,IAAM,IAAQ,GAOR,IAAW,EAAO,GAAa,IAAI;yBAIvC,EAQM,OAAA;GAPJ,OAAM;GACN,MAAK;GACJ,wBAAsB,EAAA,CAAA,CAAQ,EAAE;GAChC,cAAY,EAAA,CAAA,CAAQ,EAAE,WAAW,EAAM,KAAK;GAC5C,uBAAqB,EAAM;MAE5B,EAA6C,OAA7C,GAA6C,CAAd,EAAQ,EAAA,QAAA,SAAA,CAAA,CAAA,CAAA,GAAA,GAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../../src/components/VCarousel/context.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../../src/components/VCarousel/context.ts"],"sourcesContent":["/**\n * Context getters preserve reactive props and messages; slide positions are supplied during\n * rendering.\n */\n\nimport type { InjectionKey } from 'vue'\n\nexport interface CarouselContext {\n /** What a screen reader says a slide IS, in place of the bare word \"group\". */\n readonly slideRoleDescription: string\n /**\n * What a slide is called: \"3 of 8\". The position is given as code counts it, from zero;\n * the sentence counts as a human does.\n */\n slideLabel: (index: number) => string\n}\n\nexport const carouselKey: InjectionKey<CarouselContext> = Symbol('v-carousel')\n"],"mappings":";AAiBA,IAAa,IAA6C,OAAO,YAAY"}
|
|
@@ -8,11 +8,7 @@ interface CheckboxProps {
|
|
|
8
8
|
* so it is read out after the label rather than as part of it.
|
|
9
9
|
*/
|
|
10
10
|
hint?: string;
|
|
11
|
-
/**
|
|
12
|
-
* Shows the box as partially checked, a dash instead of a tick. This is what a
|
|
13
|
-
* parent checkbox looks like when some of its children are ticked and others are
|
|
14
|
-
* not; it is a state of its own, not a value the v-model can hold.
|
|
15
|
-
*/
|
|
11
|
+
/** Shows the box as partially checked, a dash instead of a tick. */
|
|
16
12
|
indeterminate?: boolean;
|
|
17
13
|
/** Which side of the box the label sits on. */
|
|
18
14
|
labelPosition?: CheckboxLabelPosition;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VCheckbox.js","names":[],"sources":["../../../src/components/VCheckbox/VCheckbox.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A checkbox whose square is drawn by the library rather than by the browser.\n *\n * The real `<input type=\"checkbox\">` is still there, hidden with `opacity: 0` and never\n * removed, so it keeps everything a checkbox is owed — focus, keyboard, form submission,\n * native validity — and the drawn square follows its state in CSS.\n *\n * The root is a container rather than the `<label>` itself, because the hint must sit\n * OUTSIDE the label: whatever a label holds becomes the control's accessible name.\n *\n * Two pieces of JS besides the v-model. `indeterminate`, the third \"partially checked\" look,\n * exists solely as a DOM property with no HTML attribute to set it from a template. And\n * `readonly` has no native meaning on a checkbox, so refusing the change is ours to do.\n */\n\nimport { ref, watchEffect } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the box the label sits on. */\nexport type CheckboxLabelPosition = 'start' | 'end'\n\ninterface CheckboxProps {\n /** The text beside the box, 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 checkbox for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /**\n * Shows the box as partially checked, a dash instead of a tick. This is what a\n * parent checkbox looks like when some of its children are ticked and others are\n * not; it is a state of its own, not a value the v-model can hold.\n */\n indeterminate?: boolean\n /** Which side of the box the label sits on. */\n labelPosition?: CheckboxLabelPosition\n /**\n * Pushes the label and the box 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 colours the box 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 checkbox unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the state without allowing it to be changed. The checkbox 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<CheckboxProps>(), {\n label: undefined,\n hint: undefined,\n indeterminate: false,\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 must be\n// redirected: `name`, `value`, `required` and the aria-* belong on the input, and left on\n// the wrapper they would take part in neither the form nor the accessibility tree.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/**\n * Whether the box is ticked. It starts unticked. `indeterminate` is a separate prop: the\n * dash is a third appearance, never a third value of this one.\n */\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// @ssr @core\n// The \"partially checked\" look exists only as a DOM property, so it is written by hand,\n// from two places.\n//\n// The effect covers the prop moving. On the server there is no element and it simply does\n// nothing; the `flush: 'post'` is load-bearing, since on the very first pass the template\n// ref is not filled in before the DOM has been updated — in the default timing the\n// property would be written to nothing at all.\n//\n// TRAP — the effect alone is not enough. Activating a checkbox CLEARS the property as part\n// of the gesture, and the prop it reads has not moved, so nothing re-runs it and the dash\n// never comes back. Re-asserting it from `change` is what covers that, and it covers it\n// even when the parent refuses the new value, where watching the model would not. That\n// handler is bound after the forwarded attributes, so `mergeProps` keeps a consumer's own\n// `@change` beside it rather than replacing it.\nfunction syncIndeterminate() {\n if (inputEl.value) inputEl.value.indeterminate = props.indeterminate\n}\n\nwatchEffect(syncIndeterminate, { flush: 'post' })\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser then puts `checked` AND `indeterminate` back as they were\n// and fires no `change`, so the v-model never hears of it. It covers the Space key, which a\n// checkbox turns into a click, and a 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 checkbox.\ndefineExpose({\n /** Moves the focus to the real checkbox. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-checkbox 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 <!-- `aria-describedby` comes AFTER the forwarded attributes, which is what lets the\n aggregated list, the consumer's references plus the hint, replace theirs. -->\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n class=\"v-checkbox-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 @change=\"syncIndeterminate\"\n />\n <span class=\"v-checkbox-box\" aria-hidden=\"true\">\n <svg class=\"v-checkbox-mark\" viewBox=\"0 0 12 12\">\n <path\n class=\"v-checkbox-mark-check\"\n d=\"M2.5 6.5l2.5 2.5 4.5-5.5\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <path\n class=\"v-checkbox-mark-dash\"\n d=\"M3 6h6\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n />\n </svg>\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-checkbox-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-checkbox-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /* The corner follows the control radius, capped at a QUARTER of the box rather than at\n the half a row takes. A pill override on --vectis-radius-interactive would otherwise\n turn the box into a disc, and a round checkbox reads as a radio. */\n .v-checkbox-box {\n display: inline-grid;\n place-items: center;\n width: var(--vectis-control-size-check);\n height: var(--vectis-control-size-check);\n background: transparent;\n border: var(--vectis-control-border-width) solid var(--vectis-color-border-strong);\n border-radius: min(\n var(--vectis-radius-interactive),\n calc(var(--vectis-control-size-check) / 4)\n );\n color: var(--vectis-color-text-on-accent);\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-checkbox-mark {\n width: var(--vectis-control-size-check-mark);\n height: var(--vectis-control-size-check-mark);\n }\n\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The state painting, rule for rule the same as VRadio's with its own classes: keep the\n two ALIGNED. The states below weigh (0,3,0), bar the read-only base at (0,2,0), so their\n ORDER arbitrates them: checked, read-only, invalid, then disabled last.\n\n The two hovers sit at (0,7,0) and therefore beat every one of them on specificity, order\n or no order — which is why they have to EXCLUDE by hand each state they must not repaint.\n Forgetting the invalid pair is what once rubbed the danger border out under the pointer,\n on a control whose whole point at that moment is to look wrong. */\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :checked, :indeterminate, :user-invalid, [aria-invalid='true'])\n + .v-checkbox-box {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-checkbox-input:is(:checked, :indeterminate) + .v-checkbox-box {\n background: var(--vectis-color-accent);\n border-color: var(--vectis-color-accent);\n }\n\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :user-invalid, [aria-invalid='true']):is(\n :checked,\n :indeterminate\n )\n + .v-checkbox-box {\n background: var(--vectis-color-accent-hover);\n border-color: var(--vectis-color-accent-hover);\n }\n\n /* Read-only sinks like a read-only field, and a selected control trades the accent for the\n muted text colour: still plainly on, no longer inviting a click. The mark takes the\n surface colour, which contrasts with that fill in both themes, where white would vanish\n against the light grey of the dark theme. `:where()` keeps both rules at the weight of\n the states they sit between. */\n :where(.v-checkbox[data-readonly]) .v-checkbox-input + .v-checkbox-box {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n :where(.v-checkbox[data-readonly])\n .v-checkbox-input:is(:checked, :indeterminate)\n + .v-checkbox-box {\n background: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-text-muted);\n color: var(--vectis-color-surface);\n }\n\n .v-checkbox-input:user-invalid + .v-checkbox-box,\n .v-checkbox-input[aria-invalid='true'] + .v-checkbox-box {\n border-color: var(--vectis-color-danger);\n }\n\n .v-checkbox-input:disabled + .v-checkbox-box {\n background: var(--vectis-color-surface-muted);\n border-color: var(--vectis-color-border);\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n TRAP — the two rules below are (0,4,0) and `checked` and `indeterminate` are\n INDEPENDENT DOM properties, so a box can be both at once. Nothing but the source\n order decides what such a box shows: the second rule hides the tick and the dash\n wins. Swap them, or slip a rule between them, and a checked-and-indeterminate box\n shows the tick with nothing in the console to say why — and this component is the\n one that sets `indeterminate` by hand, so it is the one that can reach that state. */\n .v-checkbox-input:checked + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 1;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 0;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-dash {\n opacity: 1;\n }\n\n @media (forced-colors: active) {\n /* Windows forced colors erase every author colour: a `background` becomes `Canvas` and a\n `border-color` becomes `CanvasText`, so a ticked box keeps the tick its `stroke` gives it and nothing else. The system\n Highlight pair is what says \"selected\" here, as it does on a pressed VToggleItem, a\n selected VChip and the current VPagination page.\n\n The class is repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n cannot repaint it: `forced-color-adjust: none` takes the forcing off this element, and\n a hover left winning would then paint its REAL grey over the system colour. */\n .v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input:is(\n :checked,\n :indeterminate\n ):not(:disabled)\n + .v-checkbox-box {\n forced-color-adjust: none;\n background: Highlight;\n border-color: Highlight;\n color: HighlightText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-checkbox-box,\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VCheckbox.js","names":[],"sources":["../../../src/components/VCheckbox/VCheckbox.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Retain the native checkbox for focus, forms and validity. Keep hints outside its label and\n * synchronize indeterminate after rendering.\n */\n\nimport { ref, watchEffect } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the box the label sits on. */\nexport type CheckboxLabelPosition = 'start' | 'end'\n\ninterface CheckboxProps {\n /** The text beside the box, 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 checkbox for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /** Shows the box as partially checked, a dash instead of a tick. */\n indeterminate?: boolean\n /** Which side of the box the label sits on. */\n labelPosition?: CheckboxLabelPosition\n /**\n * Pushes the label and the box 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 colours the box 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 checkbox unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the state without allowing it to be changed. The checkbox 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<CheckboxProps>(), {\n label: undefined,\n hint: undefined,\n indeterminate: false,\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 must be\n// redirected: `name`, `value`, `required` and the aria-* belong on the input, and left on\n// the wrapper they would take part in neither the form nor the accessibility tree.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/**\n * Whether the box is ticked. It starts unticked. `indeterminate` is a separate prop: the\n * dash is a third appearance, never a third value of this one.\n */\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// @ssr @core\n// The effect alone is not enough. Activating a checkbox CLEARS the property as part of the\n// gesture, and the prop it reads has not moved, so nothing re-runs it and the dash never comes\n// back.\nfunction syncIndeterminate() {\n if (inputEl.value) inputEl.value.indeterminate = props.indeterminate\n}\n\nwatchEffect(syncIndeterminate, { flush: 'post' })\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser then puts `checked` and `indeterminate` back as they were and\n// fires no `change`, so the v-model 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 checkbox.\ndefineExpose({\n /** Moves the focus to the real checkbox. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-checkbox 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 <!--\n `aria-describedby` comes after the forwarded attributes, which lets the aggregated list,\n the consumer's references plus the hint, replace theirs.\n -->\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n class=\"v-checkbox-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 @change=\"syncIndeterminate\"\n />\n <span class=\"v-checkbox-box\" aria-hidden=\"true\">\n <svg class=\"v-checkbox-mark\" viewBox=\"0 0 12 12\">\n <path\n class=\"v-checkbox-mark-check\"\n d=\"M2.5 6.5l2.5 2.5 4.5-5.5\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <path\n class=\"v-checkbox-mark-dash\"\n d=\"M3 6h6\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n />\n </svg>\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-checkbox-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-checkbox-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A pill override on --vectis-radius-interactive would otherwise turn the box into a disc,\n * and a round checkbox reads as a radio.\n */\n .v-checkbox-box {\n display: inline-grid;\n place-items: center;\n width: var(--vectis-control-size-check);\n height: var(--vectis-control-size-check);\n background: transparent;\n border: var(--vectis-control-border-width) solid var(--vectis-color-border-strong);\n border-radius: min(\n var(--vectis-radius-interactive),\n calc(var(--vectis-control-size-check) / 4)\n );\n color: var(--vectis-color-text-on-accent);\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-checkbox-mark {\n width: var(--vectis-control-size-check-mark);\n height: var(--vectis-control-size-check-mark);\n }\n\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * The states below weigh (0,3,0), bar the read-only base at (0,2,0), so their ORDER\n * arbitrates them: checked, read-only, invalid, then disabled last. The two hovers sit at\n * (0,7,0) and therefore beat every one of them on specificity, order or no order; which is\n * why they have to EXCLUDE by hand each state they must not repaint.\n */\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :checked, :indeterminate, :user-invalid, [aria-invalid='true'])\n + .v-checkbox-box {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-checkbox-input:is(:checked, :indeterminate) + .v-checkbox-box {\n background: var(--vectis-color-accent);\n border-color: var(--vectis-color-accent);\n }\n\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :user-invalid, [aria-invalid='true']):is(\n :checked,\n :indeterminate\n )\n + .v-checkbox-box {\n background: var(--vectis-color-accent-hover);\n border-color: var(--vectis-color-accent-hover);\n }\n\n /*\n * The mark takes the surface colour, which contrasts with that fill in both themes, where\n * white would vanish against the light grey of the dark theme. `:where()` keeps both rules at\n * the weight of the states they sit between.\n */\n :where(.v-checkbox[data-readonly]) .v-checkbox-input + .v-checkbox-box {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n :where(.v-checkbox[data-readonly])\n .v-checkbox-input:is(:checked, :indeterminate)\n + .v-checkbox-box {\n background: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-text-muted);\n color: var(--vectis-color-surface);\n }\n\n .v-checkbox-input:user-invalid + .v-checkbox-box,\n .v-checkbox-input[aria-invalid='true'] + .v-checkbox-box {\n border-color: var(--vectis-color-danger);\n }\n\n .v-checkbox-input:disabled + .v-checkbox-box {\n background: var(--vectis-color-surface-muted);\n border-color: var(--vectis-color-border);\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n * The two rules below are (0,4,0) and `checked` and `indeterminate` are INDEPENDENT DOM\n * properties, so a box can be both at once. Nothing but the source order decides what such a\n * box shows: the second rule hides the tick and the dash wins.\n */\n .v-checkbox-input:checked + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 1;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 0;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-dash {\n opacity: 1;\n }\n\n @media (forced-colors: active) {\n /*\n * The class is repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n * cannot repaint it: `forced-color-adjust: none` takes the forcing off this element, and a\n * hover left winning would then paint its real grey over the system colour.\n */\n .v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input:is(\n :checked,\n :indeterminate\n ):not(:disabled)\n + .v-checkbox-box {\n forced-color-adjust: none;\n background: Highlight;\n border-color: Highlight;\n color: HighlightText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-checkbox-box,\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VCheckbox.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VCheckbox/VCheckbox.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A checkbox whose square is drawn by the library rather than by the browser.\n *\n * The real `<input type=\"checkbox\">` is still there, hidden with `opacity: 0` and never\n * removed, so it keeps everything a checkbox is owed — focus, keyboard, form submission,\n * native validity — and the drawn square follows its state in CSS.\n *\n * The root is a container rather than the `<label>` itself, because the hint must sit\n * OUTSIDE the label: whatever a label holds becomes the control's accessible name.\n *\n * Two pieces of JS besides the v-model. `indeterminate`, the third \"partially checked\" look,\n * exists solely as a DOM property with no HTML attribute to set it from a template. And\n * `readonly` has no native meaning on a checkbox, so refusing the change is ours to do.\n */\n\nimport { ref, watchEffect } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the box the label sits on. */\nexport type CheckboxLabelPosition = 'start' | 'end'\n\ninterface CheckboxProps {\n /** The text beside the box, 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 checkbox for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /**\n * Shows the box as partially checked, a dash instead of a tick. This is what a\n * parent checkbox looks like when some of its children are ticked and others are\n * not; it is a state of its own, not a value the v-model can hold.\n */\n indeterminate?: boolean\n /** Which side of the box the label sits on. */\n labelPosition?: CheckboxLabelPosition\n /**\n * Pushes the label and the box 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 colours the box 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 checkbox unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the state without allowing it to be changed. The checkbox 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<CheckboxProps>(), {\n label: undefined,\n hint: undefined,\n indeterminate: false,\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 must be\n// redirected: `name`, `value`, `required` and the aria-* belong on the input, and left on\n// the wrapper they would take part in neither the form nor the accessibility tree.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/**\n * Whether the box is ticked. It starts unticked. `indeterminate` is a separate prop: the\n * dash is a third appearance, never a third value of this one.\n */\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// @ssr @core\n// The \"partially checked\" look exists only as a DOM property, so it is written by hand,\n// from two places.\n//\n// The effect covers the prop moving. On the server there is no element and it simply does\n// nothing; the `flush: 'post'` is load-bearing, since on the very first pass the template\n// ref is not filled in before the DOM has been updated — in the default timing the\n// property would be written to nothing at all.\n//\n// TRAP — the effect alone is not enough. Activating a checkbox CLEARS the property as part\n// of the gesture, and the prop it reads has not moved, so nothing re-runs it and the dash\n// never comes back. Re-asserting it from `change` is what covers that, and it covers it\n// even when the parent refuses the new value, where watching the model would not. That\n// handler is bound after the forwarded attributes, so `mergeProps` keeps a consumer's own\n// `@change` beside it rather than replacing it.\nfunction syncIndeterminate() {\n if (inputEl.value) inputEl.value.indeterminate = props.indeterminate\n}\n\nwatchEffect(syncIndeterminate, { flush: 'post' })\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser then puts `checked` AND `indeterminate` back as they were\n// and fires no `change`, so the v-model never hears of it. It covers the Space key, which a\n// checkbox turns into a click, and a 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 checkbox.\ndefineExpose({\n /** Moves the focus to the real checkbox. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-checkbox 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 <!-- `aria-describedby` comes AFTER the forwarded attributes, which is what lets the\n aggregated list, the consumer's references plus the hint, replace theirs. -->\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n class=\"v-checkbox-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 @change=\"syncIndeterminate\"\n />\n <span class=\"v-checkbox-box\" aria-hidden=\"true\">\n <svg class=\"v-checkbox-mark\" viewBox=\"0 0 12 12\">\n <path\n class=\"v-checkbox-mark-check\"\n d=\"M2.5 6.5l2.5 2.5 4.5-5.5\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <path\n class=\"v-checkbox-mark-dash\"\n d=\"M3 6h6\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n />\n </svg>\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-checkbox-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-checkbox-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /* The corner follows the control radius, capped at a QUARTER of the box rather than at\n the half a row takes. A pill override on --vectis-radius-interactive would otherwise\n turn the box into a disc, and a round checkbox reads as a radio. */\n .v-checkbox-box {\n display: inline-grid;\n place-items: center;\n width: var(--vectis-control-size-check);\n height: var(--vectis-control-size-check);\n background: transparent;\n border: var(--vectis-control-border-width) solid var(--vectis-color-border-strong);\n border-radius: min(\n var(--vectis-radius-interactive),\n calc(var(--vectis-control-size-check) / 4)\n );\n color: var(--vectis-color-text-on-accent);\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-checkbox-mark {\n width: var(--vectis-control-size-check-mark);\n height: var(--vectis-control-size-check-mark);\n }\n\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The state painting, rule for rule the same as VRadio's with its own classes: keep the\n two ALIGNED. The states below weigh (0,3,0), bar the read-only base at (0,2,0), so their\n ORDER arbitrates them: checked, read-only, invalid, then disabled last.\n\n The two hovers sit at (0,7,0) and therefore beat every one of them on specificity, order\n or no order — which is why they have to EXCLUDE by hand each state they must not repaint.\n Forgetting the invalid pair is what once rubbed the danger border out under the pointer,\n on a control whose whole point at that moment is to look wrong. */\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :checked, :indeterminate, :user-invalid, [aria-invalid='true'])\n + .v-checkbox-box {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-checkbox-input:is(:checked, :indeterminate) + .v-checkbox-box {\n background: var(--vectis-color-accent);\n border-color: var(--vectis-color-accent);\n }\n\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :user-invalid, [aria-invalid='true']):is(\n :checked,\n :indeterminate\n )\n + .v-checkbox-box {\n background: var(--vectis-color-accent-hover);\n border-color: var(--vectis-color-accent-hover);\n }\n\n /* Read-only sinks like a read-only field, and a selected control trades the accent for the\n muted text colour: still plainly on, no longer inviting a click. The mark takes the\n surface colour, which contrasts with that fill in both themes, where white would vanish\n against the light grey of the dark theme. `:where()` keeps both rules at the weight of\n the states they sit between. */\n :where(.v-checkbox[data-readonly]) .v-checkbox-input + .v-checkbox-box {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n :where(.v-checkbox[data-readonly])\n .v-checkbox-input:is(:checked, :indeterminate)\n + .v-checkbox-box {\n background: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-text-muted);\n color: var(--vectis-color-surface);\n }\n\n .v-checkbox-input:user-invalid + .v-checkbox-box,\n .v-checkbox-input[aria-invalid='true'] + .v-checkbox-box {\n border-color: var(--vectis-color-danger);\n }\n\n .v-checkbox-input:disabled + .v-checkbox-box {\n background: var(--vectis-color-surface-muted);\n border-color: var(--vectis-color-border);\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n TRAP — the two rules below are (0,4,0) and `checked` and `indeterminate` are\n INDEPENDENT DOM properties, so a box can be both at once. Nothing but the source\n order decides what such a box shows: the second rule hides the tick and the dash\n wins. Swap them, or slip a rule between them, and a checked-and-indeterminate box\n shows the tick with nothing in the console to say why — and this component is the\n one that sets `indeterminate` by hand, so it is the one that can reach that state. */\n .v-checkbox-input:checked + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 1;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 0;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-dash {\n opacity: 1;\n }\n\n @media (forced-colors: active) {\n /* Windows forced colors erase every author colour: a `background` becomes `Canvas` and a\n `border-color` becomes `CanvasText`, so a ticked box keeps the tick its `stroke` gives it and nothing else. The system\n Highlight pair is what says \"selected\" here, as it does on a pressed VToggleItem, a\n selected VChip and the current VPagination page.\n\n The class is repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n cannot repaint it: `forced-color-adjust: none` takes the forcing off this element, and\n a hover left winning would then paint its REAL grey over the system colour. */\n .v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input:is(\n :checked,\n :indeterminate\n ):not(:disabled)\n + .v-checkbox-box {\n forced-color-adjust: none;\n background: Highlight;\n border-color: Highlight;\n color: HighlightText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-checkbox-box,\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6DA,IAAM,IAAQ,GAgBR,EAAE,UAAO,cAAW,cAAW,sBAAmB,EAAa,GAC/D,EAAE,WAAQ,mBAAgB,EAAY,SAAa,CAAC,CAAC,EAAM,IAAI,GAM/D,IAAQ,EAAoB,GAAA,YAAmB,GAO/C,IAAU,EAA6B,IAAI;EAiBjD,SAAS,IAAoB;GAC3B,AAAI,EAAQ,UAAO,EAAQ,MAAM,gBAAgB,EAAM;EACzD;EAEA,EAAY,GAAmB,EAAE,OAAO,OAAO,CAAC;EAOhD,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,EAkDO,QAAA;GAjDL,OAAK,EAAA,CAAC,uBACE,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,EAwCQ,SAxCR,GAwCQ;KArCN,EAYE,SAZF,EAYE;aAXI;IAAJ,KAAI;6CACU,QAAA;IACd,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;IACP,UAAQ;0BATA,EAAA,KAAK,CAAA,CAAA;YAWhB,EAoBO,QAAA;IApBD,OAAM;IAAiB,eAAY;OACvC,EAkBM,OAAA;IAlBD,OAAM;IAAkB,SAAQ;OACnC,EAQE,QAAA;IAPA,OAAM;IACN,GAAE;IACF,MAAK;IACL,QAAO;IACP,gBAAa;IACb,kBAAe;IACf,mBAAgB;OAElB,EAOE,QAAA;IANA,OAAM;IACN,GAAE;IACF,MAAK;IACL,QAAO;IACP,gBAAa;IACb,kBAAe;;GAITA,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,EAAsF,QAAA;;GAAnE,IAAI,EAAA,CAAA;GAAQ,OAAM;OAAmC,EAAA,IAAI,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,IAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VCheckbox.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VCheckbox/VCheckbox.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Retain the native checkbox for focus, forms and validity. Keep hints outside its label and\n * synchronize indeterminate after rendering.\n */\n\nimport { ref, watchEffect } from 'vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\n/** Which side of the box the label sits on. */\nexport type CheckboxLabelPosition = 'start' | 'end'\n\ninterface CheckboxProps {\n /** The text beside the box, 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 checkbox for assistive technology,\n * so it is read out after the label rather than as part of it.\n */\n hint?: string\n /** Shows the box as partially checked, a dash instead of a tick. */\n indeterminate?: boolean\n /** Which side of the box the label sits on. */\n labelPosition?: CheckboxLabelPosition\n /**\n * Pushes the label and the box 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 colours the box 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 checkbox unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the state without allowing it to be changed. The checkbox 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<CheckboxProps>(), {\n label: undefined,\n hint: undefined,\n indeterminate: false,\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 must be\n// redirected: `name`, `value`, `required` and the aria-* belong on the input, and left on\n// the wrapper they would take part in neither the form nor the accessibility tree.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst { hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\n/**\n * Whether the box is ticked. It starts unticked. `indeterminate` is a separate prop: the\n * dash is a third appearance, never a third value of this one.\n */\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// @ssr @core\n// The effect alone is not enough. Activating a checkbox CLEARS the property as part of the\n// gesture, and the prop it reads has not moved, so nothing re-runs it and the dash never comes\n// back.\nfunction syncIndeterminate() {\n if (inputEl.value) inputEl.value.indeterminate = props.indeterminate\n}\n\nwatchEffect(syncIndeterminate, { flush: 'post' })\n\n// @core\n// The native `readonly` attribute does nothing on a checkbox. Cancelling the click is what\n// refuses the change: the browser then puts `checked` and `indeterminate` back as they were and\n// fires no `change`, so the v-model 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 checkbox.\ndefineExpose({\n /** Moves the focus to the real checkbox. */\n focus: (options?: FocusOptions) => inputEl.value?.focus(options),\n /** The real `<input type=\"checkbox\">`, for what `focus` does not cover. */\n el: inputEl,\n})\n</script>\n\n<template>\n <span\n class=\"v-checkbox 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 <!--\n `aria-describedby` comes after the forwarded attributes, which lets the aggregated list,\n the consumer's references plus the hint, replace theirs.\n -->\n <input\n ref=\"inputEl\"\n v-model=\"model\"\n type=\"checkbox\"\n class=\"v-checkbox-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 @change=\"syncIndeterminate\"\n />\n <span class=\"v-checkbox-box\" aria-hidden=\"true\">\n <svg class=\"v-checkbox-mark\" viewBox=\"0 0 12 12\">\n <path\n class=\"v-checkbox-mark-check\"\n d=\"M2.5 6.5l2.5 2.5 4.5-5.5\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <path\n class=\"v-checkbox-mark-dash\"\n d=\"M3 6h6\"\n fill=\"none\"\n stroke=\"currentcolor\"\n stroke-width=\"1.75\"\n stroke-linecap=\"round\"\n />\n </svg>\n </span>\n <span v-if=\"$slots.default || label\" class=\"v-checkbox-label v-choice-label\">\n <slot>{{ label }}</slot>\n </span>\n </label>\n <span v-if=\"hint\" :id=\"hintId\" class=\"v-checkbox-hint v-choice-hint\">{{ hint }}</span>\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A pill override on --vectis-radius-interactive would otherwise turn the box into a disc,\n * and a round checkbox reads as a radio.\n */\n .v-checkbox-box {\n display: inline-grid;\n place-items: center;\n width: var(--vectis-control-size-check);\n height: var(--vectis-control-size-check);\n background: transparent;\n border: var(--vectis-control-border-width) solid var(--vectis-color-border-strong);\n border-radius: min(\n var(--vectis-radius-interactive),\n calc(var(--vectis-control-size-check) / 4)\n );\n color: var(--vectis-color-text-on-accent);\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-checkbox-mark {\n width: var(--vectis-control-size-check-mark);\n height: var(--vectis-control-size-check-mark);\n }\n\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n opacity: 0;\n transition: opacity var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * The states below weigh (0,3,0), bar the read-only base at (0,2,0), so their ORDER\n * arbitrates them: checked, read-only, invalid, then disabled last. The two hovers sit at\n * (0,7,0) and therefore beat every one of them on specificity, order or no order; which is\n * why they have to EXCLUDE by hand each state they must not repaint.\n */\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :checked, :indeterminate, :user-invalid, [aria-invalid='true'])\n + .v-checkbox-box {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n .v-checkbox-input:is(:checked, :indeterminate) + .v-checkbox-box {\n background: var(--vectis-color-accent);\n border-color: var(--vectis-color-accent);\n }\n\n .v-checkbox:not([data-readonly])\n .v-choice-row:hover\n .v-checkbox-input:not(:disabled, :user-invalid, [aria-invalid='true']):is(\n :checked,\n :indeterminate\n )\n + .v-checkbox-box {\n background: var(--vectis-color-accent-hover);\n border-color: var(--vectis-color-accent-hover);\n }\n\n /*\n * The mark takes the surface colour, which contrasts with that fill in both themes, where\n * white would vanish against the light grey of the dark theme. `:where()` keeps both rules at\n * the weight of the states they sit between.\n */\n :where(.v-checkbox[data-readonly]) .v-checkbox-input + .v-checkbox-box {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n :where(.v-checkbox[data-readonly])\n .v-checkbox-input:is(:checked, :indeterminate)\n + .v-checkbox-box {\n background: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-text-muted);\n color: var(--vectis-color-surface);\n }\n\n .v-checkbox-input:user-invalid + .v-checkbox-box,\n .v-checkbox-input[aria-invalid='true'] + .v-checkbox-box {\n border-color: var(--vectis-color-danger);\n }\n\n .v-checkbox-input:disabled + .v-checkbox-box {\n background: var(--vectis-color-surface-muted);\n border-color: var(--vectis-color-border);\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n * The two rules below are (0,4,0) and `checked` and `indeterminate` are INDEPENDENT DOM\n * properties, so a box can be both at once. Nothing but the source order decides what such a\n * box shows: the second rule hides the tick and the dash wins.\n */\n .v-checkbox-input:checked + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 1;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-check {\n opacity: 0;\n }\n\n .v-checkbox-input:indeterminate + .v-checkbox-box .v-checkbox-mark-dash {\n opacity: 1;\n }\n\n @media (forced-colors: active) {\n /*\n * The class is repeated to (0,8,0) so the row's own hover rules, which reach (0,7,0),\n * cannot repaint it: `forced-color-adjust: none` takes the forcing off this element, and a\n * hover left winning would then paint its real grey over the system colour.\n */\n .v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input.v-checkbox-input:is(\n :checked,\n :indeterminate\n ):not(:disabled)\n + .v-checkbox-box {\n forced-color-adjust: none;\n background: Highlight;\n border-color: Highlight;\n color: HighlightText;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-checkbox-box,\n .v-checkbox-mark-check,\n .v-checkbox-mark-dash {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+CA,IAAM,IAAQ,GAgBR,EAAE,UAAO,cAAW,cAAW,sBAAmB,EAAa,GAC/D,EAAE,WAAQ,mBAAgB,EAAY,SAAa,CAAC,CAAC,EAAM,IAAI,GAM/D,IAAQ,EAAoB,GAAA,YAAmB,GAO/C,IAAU,EAA6B,IAAI;EAMjD,SAAS,IAAoB;GAC3B,AAAI,EAAQ,UAAO,EAAQ,MAAM,gBAAgB,EAAM;EACzD;EAEA,EAAY,GAAmB,EAAE,OAAO,OAAO,CAAC;EAMhD,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,EAoDO,QAAA;GAnDL,OAAK,EAAA,CAAC,uBACE,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,EA0CQ,SA1CR,GA0CQ;KArCN,EAYE,SAZF,EAYE;aAXI;IAAJ,KAAI;6CACU,QAAA;IACd,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;IACP,UAAQ;0BATA,EAAA,KAAK,CAAA,CAAA;YAWhB,EAoBO,QAAA;IApBD,OAAM;IAAiB,eAAY;OACvC,EAkBM,OAAA;IAlBD,OAAM;IAAkB,SAAQ;OACnC,EAQE,QAAA;IAPA,OAAM;IACN,GAAE;IACF,MAAK;IACL,QAAO;IACP,gBAAa;IACb,kBAAe;IACf,mBAAgB;OAElB,EAOE,QAAA;IANA,OAAM;IACN,GAAE;IACF,MAAK;IACL,QAAO;IACP,gBAAa;IACb,kBAAe;;GAITA,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,EAAsF,QAAA;;GAAnE,IAAI,EAAA,CAAA;GAAQ,OAAM;OAAmC,EAAA,IAAI,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,IAAA,CAAA"}
|