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":"VDataTable.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VDataTable/VDataTable.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"Row extends Record<string, unknown>\">\n// @core\n/**\n * A table of data with everything usually built around one: a title, a search field, sortable\n * columns, a selection and pagination.\n *\n * A real `<table>` underneath, with a caption, proper column headers and `aria-sort` — which\n * is what lets a screen reader say \"row 3 of 40, column Name\" rather than read a grid of\n * unrelated text. The furniture is composed from the library's own components.\n *\n * The JS is almost all derivation — filter, then sort, then slice — plus two effects\n * explained where they sit.\n *\n * Narrow-space behaviour and height-filling are both entirely CSS: `stack` turns each row\n * into a card once the COMPONENT is narrow, measured by a container query rather than by\n * code, and the root is a flex column in which only the scroller stretches.\n */\n\nimport { computed, ref, useId, useSlots, watch, watchEffect } from 'vue'\nimport type { StyleValue } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VCheckbox from '../VCheckbox/VCheckbox.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { arrow_downward as arrowDownwardIcon } from '../VIcon/icons/arrow_downward'\nimport { arrow_drop_down as arrowDropDownIcon } from '../VIcon/icons/arrow_drop_down'\nimport { arrow_upward as arrowUpwardIcon } from '../VIcon/icons/arrow_upward'\nimport { search as searchIcon } from '../VIcon/icons/search'\nimport { swap_vert as swapVertIcon } from '../VIcon/icons/swap_vert'\nimport type { IconSource } from '../VIcon/types'\nimport VInput from '../VInput/VInput.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\nimport VPagination from '../VPagination/VPagination.vue'\nimport VSpinner from '../VSpinner/VSpinner.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { toggleValue } from '../../utils/array'\nimport { cssSize } from '../../utils/css'\nimport { isDev } from '../../utils/env'\nimport { clamp } from '../../utils/number'\nimport { createNormalizedCache, normalizeText } from '../../utils/text'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\nimport { useTimer } from '../../composables/useTimer'\nimport { useLocale, useMessages } from '../../i18n/state'\n\n/** How a column's content is aligned. */\nexport type DataTableColumnAlign = 'start' | 'center' | 'end'\n\n/** One column of the table. */\nexport interface DataTableColumn {\n /** Which field of a row it shows, and the name its slots are addressed by. */\n key: string\n /** Its heading. */\n label: string\n /** Lets the reader sort by this column. */\n sortable?: boolean\n /** How its content is aligned. Numbers usually belong at the end. */\n align?: DataTableColumnAlign\n}\n\n/** Which way a column is sorted: A to Z, or Z to A. */\nexport type DataTableSortDirection = 'asc' | 'desc'\n\n/** Which column the table is sorted by, and in which direction. */\nexport interface DataTableSort {\n /** The `key` of the column the rows are ordered by. */\n key: string\n /** Which way they are ordered. */\n direction: DataTableSortDirection\n}\n\n/** How a row is identified: by the field named in `rowKey`, or failing that by its position. */\nexport type DataTableRowId = string | number\n\n/**\n * Everything the table is currently being asked for, reported whenever it changes so a\n * server can answer it. What is not set is `null` rather than left out, so the object keeps\n * the same keys once serialized into a request.\n */\nexport interface DataTableParams {\n /** The page asked for, from 1. */\n page: number\n /** How many rows a page holds, `null` when the table is not paginated. */\n perPage: number | null\n /** The `key` of the sorted column, `null` when the rows are not sorted. */\n sortKey: string | null\n /** Which way they are sorted, `null` when they are not. */\n sortDirection: DataTableSortDirection | null\n /** The committed search, empty when nothing is searched for. */\n search: string\n}\n\n/** What the `cell-<key>` slots receive. */\nexport interface DataTableCellSlotProps<Row extends Record<string, unknown>> {\n /** The row the cell belongs to. */\n row: Row\n /** The value of the column's field in that row. */\n value: unknown\n /** The column the cell belongs to. */\n column: DataTableColumn\n}\n\n/** What the `head-<key>` slots receive. */\nexport interface DataTableHeadSlotProps {\n /** The column the heading belongs to. */\n column: DataTableColumn\n}\n\n/** What the `#empty` slot receives. */\nexport interface DataTableEmptySlotProps {\n /** The search that produced the empty result, empty when nothing was searched for. */\n search: string\n}\n\n/** The rows on show, as `rangeText` receives them, counted from 1. */\nexport interface DataTableRange {\n /** The position of the first row on show. */\n start: number\n /** The position of the last row on show. */\n end: number\n /** How many rows there are in all. */\n total: number\n}\n\n/** How much decoration the table carries. */\nexport type DataTableVariant = 'flat' | 'outlined'\n\n/** What a narrow container does to the rows. */\nexport type DataTableResponsive = 'scroll' | 'stack'\n\n// TRAP — this interface is exported rather than kept local, and it is generic rather than\n// referring to the component's own type parameter.\n//\n// A component typed over its rows inlines the whole signature of its props into the\n// declarations it emits, so a name that is not exported cannot be written there and the\n// build of the type declarations fails. And being lifted out of the component's scope, it\n// has no access to that parameter and must take one of its own.\nexport interface DataTableProps<Row extends Record<string, unknown>> {\n /** The columns to show, in order. */\n columns: DataTableColumn[]\n /** The rows to show. */\n rows: Row[]\n /**\n * Which field identifies a row. Without it a row is identified by its position, which\n * is enough for display but not for a selection: it must be given as soon as rows can\n * be selected, or the selection follows the positions rather than the rows.\n */\n rowKey?: string\n /**\n * A sentence describing what the table holds. It is announced before the table itself,\n * and is what tells a screen reader user whether it is worth exploring.\n */\n caption?: string\n /**\n * How the table is framed: nothing at all, or a card with a raised background, a border\n * and rounded corners.\n */\n variant?: DataTableVariant\n /**\n * What happens when the component is too narrow: the table scrolls sideways, or each\n * row becomes a card with its column headings repeated inside it.\n */\n responsive?: DataTableResponsive\n /** Shows that the rows are being loaded. */\n loading?: boolean\n /**\n * What is written beside the spinner while the rows are loading. It falls back to the design\n * system dictionary.\n */\n loadingText?: string\n /** What is said when there is no row to show. It falls back to the design system dictionary. */\n emptyText?: string\n /**\n * A title above the table, on the left of its toolbar.\n *\n * With no `caption` it also names the table for screen readers.\n *\n * Note that this prop shadows the HTML attribute of the same name on the component\n * itself, an accepted trade-off: a tooltip over a whole table would be of little use.\n */\n title?: string\n /** Adds a search field to the toolbar. */\n searchable?: boolean\n /** What that field says while empty. It falls back to the design system dictionary. */\n searchPlaceholder?: string\n /**\n * What screen readers announce for the search field. It falls back to the design\n * system dictionary.\n */\n searchLabel?: string\n /**\n * When a server does the searching, how long to wait after a keystroke before asking\n * it, in milliseconds. Zero asks at once.\n */\n searchDebounce?: number\n /** Tints every other row, which helps the eye follow a long line across the table. */\n striped?: boolean\n /**\n * Keeps the column headings in place while the rows scroll under them. It needs a\n * bounded scrolling area to work: either the `height` prop, or a parent with a height\n * of its own.\n */\n stickyHeader?: boolean\n /** Tightens the cells by one step, and everything the table renders with them. */\n compact?: boolean\n /**\n * The height of the WHOLE component, toolbar and pagination included: a number is read\n * as pixels, anything else as a CSS length. Left out, the table takes its parent's\n * height whenever the parent has one.\n */\n height?: number | string\n /** The heading icon of a column that can be sorted but currently is not. */\n sortIcon?: IconSource\n /**\n * The icon of an ascending sort. It points DOWN by default, the spreadsheet\n * convention: sorting A to Z reads downwards.\n */\n sortAscIcon?: IconSource\n /** The icon of a descending sort. */\n sortDescIcon?: IconSource\n /** The choices offered for how many rows a page holds. */\n perPageOptions?: number[]\n /** What that choice is called. It falls back to the design system dictionary. */\n perPageText?: string\n /**\n * How many rows there are in all on the server. It is what lets the pagination and the\n * range be right when the table only ever holds one page.\n */\n total?: number\n /** Shows which rows are being looked at (\"1–10 of 42\") in the footer. */\n showRange?: boolean\n /** Rephrases that range. It falls back to the design system dictionary. */\n rangeText?: (range: DataTableRange) => string\n /** Adds a checkbox to every row, and one in the heading to take the whole page. */\n selectable?: boolean\n /**\n * What the heading checkbox is announced as. It falls back to the design system\n * dictionary.\n */\n selectAllLabel?: string\n /**\n * How the selection is summed up in the footer. It says nothing at all when nothing is\n * selected, and falls back to the design system dictionary.\n */\n selectionText?: (count: number) => string\n /**\n * What a row's checkbox is announced as. \"Select row\" tells a screen reader user\n * nothing about WHICH row, so this is worth supplying with something from the row\n * itself. `index` is the row's position in the whole table, from 0, not in the page.\n * It falls back to the design system dictionary, which numbers the rows from 1.\n */\n selectRowLabel?: (row: Row, index: number) => string\n /**\n * Hands the searching, the sorting and the paging over to a server: the rows are shown\n * exactly as they arrive, and every change of what is being asked for is reported so\n * the server can answer it.\n */\n serverSide?: boolean\n}\n\nconst props = withDefaults(defineProps<DataTableProps<Row>>(), {\n rowKey: undefined,\n caption: undefined,\n variant: 'flat',\n responsive: 'scroll',\n loading: false,\n loadingText: undefined,\n emptyText: undefined,\n title: undefined,\n searchable: false,\n searchPlaceholder: undefined,\n searchLabel: undefined,\n searchDebounce: 250,\n striped: false,\n stickyHeader: false,\n compact: false,\n height: undefined,\n sortIcon: () => swapVertIcon,\n sortAscIcon: () => arrowDownwardIcon,\n sortDescIcon: () => arrowUpwardIcon,\n perPageOptions: undefined,\n perPageText: undefined,\n total: undefined,\n showRange: false,\n rangeText: undefined,\n selectable: false,\n selectAllLabel: undefined,\n selectionText: undefined,\n selectRowLabel: undefined,\n serverSide: false,\n})\n\nconst m = useMessages()\nconst vectisLocale = useLocale()\nconst resolvedEmptyText = computed(() => props.emptyText ?? m.value.dataTable.empty)\nconst resolvedLoadingText = computed(() => props.loadingText ?? m.value.dataTable.loading)\nconst resolvedSearchPlaceholder = computed(\n () => props.searchPlaceholder ?? m.value.dataTable.searchPlaceholder,\n)\nconst resolvedSearchLabel = computed(() => props.searchLabel ?? m.value.dataTable.searchLabel)\nconst resolvedPerPageText = computed(() => props.perPageText ?? m.value.dataTable.perPage)\nconst resolvedSelectAllLabel = computed(() => props.selectAllLabel ?? m.value.dataTable.selectAll)\n\n/**\n * Which column the rows are sorted by, and in which direction. Nothing is sorted to begin\n * with. It may be driven from outside or simply left to the table, which sets it as headers\n * are clicked; changing it does not send the reader back to the first page.\n */\nconst sort = defineModel<DataTableSort | null>('sort', { default: null })\n/**\n * The page being shown, counted from 1. It starts on the first, and searching or changing\n * the page size sends it back there. It is clamped by derivation rather than written to, so\n * a page beyond the last simply displays the last.\n */\nconst page = defineModel<number>('page', { default: 1 })\n/**\n * How many rows a page holds. Any value above zero turns the pagination on, so passing\n * one down without binding it is enough to enable it.\n */\nconst perPage = defineModel<number | undefined>('perPage', { default: undefined })\n/*\n * \"No value\" is spelled two ways on purpose. `sort` is `null` because the TABLE writes that\n * state itself, a third click on a heading clearing the order, and a model the component\n * writes needs a value a consumer can store and compare. `perPage` is `undefined` because\n * nothing here ever clears it: it is simply a model that was not given. `DataTableParams`\n * reports both as `null`, being a request body rather than a model.\n */\n/**\n * The selected rows, as the identities `rowKey` gives them, never the row objects\n * themselves. Nothing is selected to begin with, and a selection SURVIVES a change of page:\n * the header checkbox covers the visible page alone, which is why it can be indeterminate.\n * The footer counts this list as it stands, identities of rows no longer shown included.\n */\nconst selected = defineModel<DataTableRowId[]>('selected', { default: () => [] })\n/**\n * What is typed in the search field, empty to begin with. Only the declared columns are\n * searched, accent- and case-insensitively; in `serverSide` mode nothing is filtered here\n * and the term is reported through `update:params` instead.\n */\nconst search = defineModel<string>('search', { default: '' })\n\nconst emit = defineEmits<{\n /**\n * What the table is now being asked for, when a server is answering: the page, the\n * page size, the sort or the search has changed, the last one after its delay. An equal\n * value handed down again asks for nothing.\n *\n * Nothing is emitted when the component appears: fetching the first page is the\n * consumer's own business, and emitting would make every table fetch twice.\n */\n 'update:params': [params: DataTableParams]\n}>()\n\ndefineSlots<{\n /** What a cell of a given column shows: a slot named after that column's key. */\n [name: `cell-${string}`]: (scope: DataTableCellSlotProps<Row>) => unknown\n /** What a column's heading shows: a slot named after that column's key. */\n [name: `head-${string}`]: (scope: DataTableHeadSlotProps) => unknown\n /** The left side of the toolbar, replacing the `title` prop. */\n title?(): unknown\n /** What the table shows while its rows are loading, replacing the spinner and its text. */\n loading?(): unknown\n /**\n * What the table shows when there is no row to show, replacing `emptyText`. It receives the\n * search that produced the empty result, empty when nothing was searched for.\n */\n empty?(scope: DataTableEmptySlotProps): unknown\n}>()\n\n// `class` and `style` stay on the wrapper, where a consumer expects to place the\n// component; everything else — an id, the aria-* — goes to the `<table>` itself, the only\n// element they validly describe.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst slots = useSlots()\n\n// A template ref on the component reaches the wrapper, a layout box; the `<table>` is where\n// the consumer's attributes land and what they describe.\nconst tableEl = ref<HTMLTableElement | null>(null)\ndefineExpose({\n /** The `<table>` element, where the consumer's attributes land. */\n el: tableEl,\n})\n\n// @a11y\n/*\n * The title names the table when nothing else does. A caption already does, and so does a\n * consumer's own `aria-label` or `aria-labelledby`, which a reference to the title would\n * override. Bound BEFORE the forwarded attributes, like every state the component owns.\n */\nconst titleId = useId()\nconst tableLabelledBy = computed(() =>\n props.caption ||\n !(props.title || slots.title) ||\n attrs['aria-label'] !== undefined ||\n attrs['aria-labelledby'] !== undefined\n ? undefined\n : titleId,\n)\n\n// @devwarn\nif (isDev) {\n if (props.selectable && !props.rowKey)\n console.warn(\n '[VDataTable] `selectable` without `rowKey` — index-based identities are corrupted by sorting, filtering and pagination.',\n )\n /*\n * A key that is missing, or not a string or a number, turns into the same text on every\n * row (\"undefined\"), and two rows sharing a key share an identity: ticking one ticks them\n * all, and the rows' own `:key`s collide. Once per instance and per kind.\n */\n const warned = new Set<string>()\n watchEffect(() => {\n const key = props.rowKey\n if (!key) return\n const seen = new Set<unknown>()\n for (const row of props.rows) {\n const value = row[key]\n if (typeof value !== 'string' && typeof value !== 'number') {\n if (!warned.has('type')) {\n warned.add('type')\n console.warn(\n `[VDataTable] \\`rowKey\\` \"${key}\" is missing or is not a string or a number on some rows: those rows share one identity.`,\n )\n }\n } else if (seen.has(value)) {\n if (!warned.has('duplicate')) {\n warned.add('duplicate')\n console.warn(\n `[VDataTable] \\`rowKey\\` \"${key}\" holds the value \"${value}\" twice: rows are told apart by it, so it must be unique.`,\n )\n }\n } else seen.add(value)\n }\n })\n}\n\n/*\n * TRAP — a key that is already a string or a number is returned AS IT IS. The selection\n * holds these identities and is looked up with a Set, which tells the number 7 from the\n * string \"7\": turned into text, a table keyed by numeric ids never found a selected row\n * again, so every checkbox stayed unticked.\n */\nfunction rowIdentity(row: Row, index: number): DataTableRowId {\n if (!props.rowKey) return index\n const key = row[props.rowKey]\n return typeof key === 'string' || typeof key === 'number' ? key : String(key)\n}\n\n// From here on: filter, then sort, then cut into pages — three derivations, each reading\n// the one before it.\n//\n// The search only ever looks at the columns that are DISPLAYED. Searching fields the\n// reader cannot see would return rows for reasons nothing on screen explains.\n/*\n * The cells in accent-insensitive form, memoized per row and column and re-checked against\n * the raw value, so a value edited in place is noticed. The filter below re-reads the whole\n * table on every keystroke: ten thousand rows across five columns is fifty thousand\n * normalizations per character typed without it.\n */\nconst normalizedOf = createNormalizedCache<Row>()\nconst normalizedCell = (row: Row, key: string) => normalizedOf(row, String(row[key] ?? ''), key)\n\nconst filteredRows = computed(() => {\n if (props.serverSide || !props.searchable) return props.rows\n const needle = normalizeText((search.value ?? '').trim())\n if (!needle) return props.rows\n return props.rows.filter((row) =>\n props.columns.some((column) => normalizedCell(row, column.key).includes(needle)),\n )\n})\n\n/*\n * The collator is built ONCE per locale rather than per comparison.\n *\n * `String.localeCompare` reuses an engine-cached collator only when the locale AND the\n * options are both `undefined`; passing either takes the slow path and constructs an\n * `Intl.Collator` per call — the cost class `utils/date.ts` fights, construction running one\n * to two orders of magnitude longer than use. Nor is it paid once: `search` feeds\n * `filteredRows` feeds this, so a sorted table re-sorts on every keystroke.\n *\n * The locale must nonetheless stay EXPLICIT: an `undefined` one resolves differently in Node\n * and in the browser, so the row order would diverge across hydration.\n */\nconst collator = computed(() => new Intl.Collator(vectisLocale.value, { numeric: true }))\n\n/*\n * What a cell is compared by. A `Date` by its instant: as text it collates on `toString()`,\n * which opens with the WEEKDAY, so Monday the 5th sorted before Saturday the 3rd. A number\n * written as text by its value: the collator's `numeric` option reads digit runs and\n * nothing else, so it put \"-5\" before \"-10\" and \"1.5\" before \"1.25\".\n */\nconst NUMERIC_TEXT = /^\\s*[+-]?(\\d+\\.?\\d*|\\.\\d+)(e[+-]?\\d+)?\\s*$/i\nfunction sortKey(value: unknown): unknown {\n if (value instanceof Date) return value.getTime()\n if (typeof value === 'string' && NUMERIC_TEXT.test(value)) return Number(value)\n return value\n}\n\nconst sortedRows = computed(() => {\n const current = sort.value\n if (!current || props.serverSide) return filteredRows.value\n const factor = current.direction === 'asc' ? 1 : -1\n const compare = collator.value.compare\n return [...filteredRows.value].sort((a, b) => {\n const av = sortKey(a[current.key])\n const bv = sortKey(b[current.key])\n if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * factor\n return compare(String(av ?? ''), String(bv ?? '')) * factor\n })\n})\n\nconst paginated = computed(() => (perPage.value ?? 0) > 0)\nconst totalCount = computed(() =>\n props.serverSide ? (props.total ?? props.rows.length) : filteredRows.value.length,\n)\nconst pageCount = computed(() =>\n paginated.value ? Math.max(1, Math.ceil(totalCount.value / (perPage.value ?? 1))) : 1,\n)\n// The page actually shown is CLAMPED by derivation and the model is never rewritten: a\n// search that leaves fewer pages simply falls back to the last one, and typing a\n// character that would have emptied the table does not silently move the consumer's own\n// page number.\nconst currentPage = computed(() => clamp(page.value, 1, pageCount.value))\n\nconst displayedRows = computed(() => {\n // With a server answering, the rows received ARE the current page: cutting them again\n // would show a tenth of a page.\n if (props.serverSide || !paginated.value) return sortedRows.value\n const start = (currentPage.value - 1) * (perPage.value ?? 0)\n return sortedRows.value.slice(start, start + (perPage.value ?? 0))\n})\n\nfunction toggleSort(key: string) {\n const current = sort.value\n if (!current || current.key !== key) sort.value = { key, direction: 'asc' }\n else if (current.direction === 'asc') sort.value = { key, direction: 'desc' }\n else sort.value = null\n}\n\n// @a11y\n// Only the column actually sorted carries the attribute. Setting it on every heading would have a screen reader announce \"not sorted\" on each one in turn, drowning\n// the single piece of information that matters: which column the order comes from.\nfunction ariaSort(column: DataTableColumn): 'ascending' | 'descending' | undefined {\n if (sort.value?.key !== column.key) return undefined\n return sort.value.direction === 'asc' ? 'ascending' : 'descending'\n}\n\n/**\n * Which icon a column's heading shows: a neutral one on any sortable column, and the\n * arrow of the current direction on the one being sorted by.\n *\n * It is deliberately named differently from the prop it falls back to: written the same,\n * the function would shadow the prop inside the template and the icons would never be\n * overridable.\n */\nfunction sortIconFor(column: DataTableColumn): IconSource {\n if (sort.value?.key !== column.key) return props.sortIcon\n return sort.value.direction === 'asc' ? props.sortAscIcon : props.sortDescIcon\n}\n\n// The two effects the search needs.\n//\n// A changed search invalidates where one was: page 4 of the old results has nothing to\n// do with page 4 of the new ones, so the reader goes back to the first. And when a server\n// answers, nothing native waits before asking it, so that has to be written.\n//\n// The waiting is delegated to `useTimer` — a delay of zero running at once, cancellation\n// when the component goes away — and the term that was actually committed is what the\n// parameters below report.\n// It starts from the search the table was GIVEN: starting empty, a table mounted with a\n// search already in place reported an empty one in every request until the reader typed.\nconst committedSearch = ref(search.value)\nconst searchTimer = useTimer()\n\nfunction commitSearch() {\n // A term typed and erased inside the debounce is no new search: going back to page 1\n // and asking the server again would be a request for what is already on screen.\n if (search.value === committedSearch.value) return\n committedSearch.value = search.value\n page.value = 1\n}\n\nwatch(search, () => {\n if (!props.serverSide) {\n page.value = 1\n return\n }\n searchTimer.start(commitSearch, props.searchDebounce)\n})\n\n// The search the rows on show answer to: the committed one when a server does the searching.\nconst emptySearch = computed(() => (props.serverSide ? committedSearch.value : search.value))\n\n// What the table is asking for, as one value.\n/*\n * TRAP — `page` is the MODEL, never the clamped `currentPage`. The clamp reads `total`,\n * which is the server's own answer: read here, a response that changed the total would move\n * the parameters and ask for the page again, and a consumer clearing `total` while it loads\n * would send the reader back to page 1.\n */\nconst params = computed<DataTableParams>(() => ({\n page: page.value,\n perPage: perPage.value ?? null,\n sortKey: sort.value?.key ?? null,\n sortDirection: sort.value?.direction ?? null,\n search: committedSearch.value,\n}))\n\n// Committing a search and going back to the first page happen one after the other, but\n// both land in the same flush, so this runs ONCE on the final values — never two requests\n// for what the reader experienced as a single action.\n// Compared by value: a parent handing down an equal `sort` object is no new request.\nwatch(params, (value, previous) => {\n if (!props.serverSide) return\n if (JSON.stringify(value) === JSON.stringify(previous)) return\n emit('update:params', value)\n})\n\n// The selection. The heading checkbox covers the rows currently VISIBLE and not the whole\n// table: a box that silently selected forty thousand rows would be a trap.\nconst selectedSet = computed(() => new Set<DataTableRowId>(selected.value))\nconst visibleIds = computed(() => displayedRows.value.map((row, index) => rowIdentity(row, index)))\nconst allVisibleSelected = computed(\n () => visibleIds.value.length > 0 && visibleIds.value.every((id) => selectedSet.value.has(id)),\n)\nconst masterIndeterminate = computed(\n () => !allVisibleSelected.value && visibleIds.value.some((id) => selectedSet.value.has(id)),\n)\n\n/*\n * A row is asked about by the identity `visibleIds` already holds for it: the template reads\n * `visibleIds[index]` for the key, the tint and the checkbox alike, rather than deriving it\n * again three or four times per row per render.\n */\nconst isSelected = (id: DataTableRowId) => selectedSet.value.has(id)\n\nfunction toggleRow(id: DataTableRowId) {\n selected.value = toggleValue(selected.value, id)\n}\n\n// It only ever touches the rows on screen, so what was selected on the other pages\n// survives moving between them.\n// Refused with no row on screen, or while the rows on screen are the PREVIOUS ones behind\n// the loading state: ticked there, it stayed ticked with nothing selected, or selected rows\n// the reader could not see.\nconst masterDisabled = computed(() => visibleIds.value.length === 0 || props.loading)\n\nfunction toggleMaster() {\n if (masterDisabled.value) return\n const ids = visibleIds.value\n if (allVisibleSelected.value) {\n const visible = new Set(ids)\n selected.value = selected.value.filter((id) => !visible.has(id))\n } else {\n const current = selectedSet.value\n selected.value = [...selected.value, ...ids.filter((id) => !current.has(id))]\n }\n}\n\n// @a11y\n// Without this every checkbox in the column would be announced identically, and a screen\n// reader user would have no way of knowing which row they were about to select.\nfunction rowSelectLabel(row: Row, index: number): string {\n // The position in the WHOLE table, or every page would have its own \"Select row 1\". The\n // dictionary counts from one as a human does, the prop from zero as code does.\n const position = (paginated.value ? (currentPage.value - 1) * (perPage.value ?? 0) : 0) + index\n return props.selectRowLabel?.(row, position) ?? m.value.dataTable.selectRow(position + 1)\n}\n\nfunction setPerPage(option: number) {\n perPage.value = option\n}\n\n// Whoever changes it, the menu or the parent: the page the reader was on no longer starts\n// on the same row, so it goes back to the first one.\nwatch(perPage, () => {\n page.value = 1\n})\n\nconst colCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))\n\n// @a11y\n/**\n * What the footer says about the selection.\n *\n * TRAP — it is rendered, EMPTY, as soon as rows can be selected at all. A region meant to\n * announce its own changes must already exist before the first change: inserted into the\n * page at the same moment as its text, it announces nothing, and the first row selected\n * would pass in silence.\n */\nconst selectionSummary = computed(() => {\n const count = selected.value.length\n if (count === 0) return ''\n if (props.selectionText) return props.selectionText(count)\n return m.value.dataTable.selection(count)\n})\n\nconst rangeSummary = computed(() => {\n const per = perPage.value ?? 0\n const total = totalCount.value\n const start = total === 0 ? 0 : (currentPage.value - 1) * per + 1\n const end = Math.min(currentPage.value * per, total)\n return props.rangeText?.({ start, end, total }) ?? m.value.dataTable.range({ start, end, total })\n})\n\n// The height is applied to the WHOLE component and not as a ceiling on the scrolling\n// area. The component then takes exactly the height asked for and the scrolling area\n// absorbs whatever is left, so a page with three rows on it no longer makes the table\n// shrink around them.\n//\n// A soft ceiling remains available to the consumer through a maximum height of their own:\n// the component is a column whose scrolling area can be compressed.\nconst heightStyle = computed<StyleValue | undefined>(() =>\n props.height !== undefined ? { blockSize: cssSize(props.height) } : undefined,\n)\n</script>\n\n<template>\n <div\n class=\"v-data-table\"\n :class=\"rootClass\"\n :style=\"[heightStyle, rootStyle]\"\n :data-variant=\"variant\"\n :data-responsive=\"responsive\"\n :data-striped=\"striped ? '' : undefined\"\n :data-compact=\"compact ? '' : undefined\"\n :data-sticky-header=\"stickyHeader ? '' : undefined\"\n :data-selectable=\"selectable ? '' : undefined\"\n >\n <div v-if=\"title || $slots.title || searchable\" class=\"v-data-table-toolbar\">\n <VTypography :id=\"titleId\" as=\"div\" variant=\"heading-4\" class=\"v-data-table-title\">\n <slot name=\"title\">{{ title }}</slot>\n </VTypography>\n <VInput\n v-if=\"searchable\"\n v-model=\"search\"\n class=\"v-data-table-search\"\n type=\"search\"\n size=\"sm\"\n :compact=\"compact\"\n :icon-start=\"searchIcon\"\n clearable\n :placeholder=\"resolvedSearchPlaceholder\"\n :aria-label=\"resolvedSearchLabel\"\n />\n </div>\n\n <!-- Only the table itself scrolls; the toolbar above and the footer below stay\n where they are. -->\n <div class=\"v-data-table-scroller\">\n <table\n ref=\"tableEl\"\n class=\"v-data-table-table\"\n :aria-labelledby=\"tableLabelledBy\"\n v-bind=\"forwardedAttrs\"\n >\n <caption v-if=\"caption\" class=\"v-data-table-caption\">\n {{\n caption\n }}\n </caption>\n <thead class=\"v-data-table-head\">\n <tr>\n <th v-if=\"selectable\" scope=\"col\" class=\"v-data-table-select\">\n <VCheckbox\n :model-value=\"allVisibleSelected\"\n :indeterminate=\"masterIndeterminate\"\n :disabled=\"masterDisabled\"\n :aria-label=\"resolvedSelectAllLabel\"\n @update:model-value=\"toggleMaster\"\n />\n <!-- Its hidden box would leave the heading EMPTY in the stacked layout (axe\n empty-table-header), so it keeps its name as text there. -->\n <span class=\"v-data-table-stack-label\">{{ resolvedSelectAllLabel }}</span>\n </th>\n <th\n v-for=\"column in columns\"\n :key=\"column.key\"\n scope=\"col\"\n :data-align=\"column.align\"\n :aria-sort=\"ariaSort(column)\"\n >\n <button\n v-if=\"column.sortable\"\n type=\"button\"\n class=\"v-data-table-sort\"\n :data-direction=\"sort?.key === column.key ? sort.direction : undefined\"\n @click=\"toggleSort(column.key)\"\n >\n <slot :name=\"`head-${column.key}`\" :column=\"column\">{{ column.label }}</slot>\n <!-- Decorative, and deliberately so: given no label, an icon hides itself\n from screen readers. What the sort state is, is already carried by\n the heading itself. -->\n <VIcon class=\"v-data-table-sort-icon\" v-bind=\"iconProps(sortIconFor(column))\" />\n </button>\n <!-- The heading as plain text, shown to screen readers in the STACKED layout\n only, where the button above is hidden: see the stack rules. -->\n <span v-if=\"column.sortable\" class=\"v-data-table-stack-label\">{{\n column.label\n }}</span>\n <template v-else>\n <slot :name=\"`head-${column.key}`\" :column=\"column\">{{ column.label }}</slot>\n </template>\n </th>\n </tr>\n </thead>\n <tbody>\n <!-- The order matters: loading is checked BEFORE emptiness, so a table waiting\n for its rows never claims there are none. -->\n <tr v-if=\"loading\">\n <td :colspan=\"colCount\" class=\"v-data-table-state\">\n <slot name=\"loading\">\n <!-- The spinner carries the text for a screen reader, so the visible copy is\n hidden from it: read twice, the cell would say it is loading twice. -->\n <span class=\"v-data-table-state-loading\">\n <VSpinner :label=\"resolvedLoadingText\" />\n <span aria-hidden=\"true\">{{ resolvedLoadingText }}</span>\n </span>\n </slot>\n </td>\n </tr>\n <tr v-else-if=\"displayedRows.length === 0\">\n <td :colspan=\"colCount\" class=\"v-data-table-state\">\n <slot name=\"empty\" :search=\"emptySearch\">{{ resolvedEmptyText }}</slot>\n </td>\n </tr>\n <template v-else>\n <tr\n v-for=\"(row, index) in displayedRows\"\n :key=\"visibleIds[index]\"\n :data-selected=\"selectable && isSelected(visibleIds[index]!) ? '' : undefined\"\n >\n <!-- TRAP — the selection is marked with a plain attribute and NOT with the\n ARIA selected state, which is invalid on the row of a table: it belongs\n to a grid. What tells assistive technology that a row is selected is\n its checkbox being checked. -->\n <td v-if=\"selectable\" class=\"v-data-table-select\">\n <VCheckbox\n :model-value=\"isSelected(visibleIds[index]!)\"\n :aria-label=\"rowSelectLabel(row, index)\"\n @update:model-value=\"toggleRow(visibleIds[index]!)\"\n />\n </td>\n <td\n v-for=\"column in columns\"\n :key=\"column.key\"\n :data-label=\"column.label\"\n :data-align=\"column.align\"\n >\n <slot\n :name=\"`cell-${column.key}`\"\n :row=\"row\"\n :value=\"row[column.key]\"\n :column=\"column\"\n >\n {{ row[column.key] }}\n </slot>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n </div>\n\n <!-- The footer has two zones: what is selected on the left, and on the right the page\n size, the range and the pagination, in that order. -->\n <div v-if=\"paginated || selectable\" class=\"v-data-table-footer\">\n <span v-if=\"selectable\" class=\"v-data-table-selection\" aria-live=\"polite\">{{\n selectionSummary\n }}</span>\n <div v-if=\"paginated\" class=\"v-data-table-footer-end\">\n <div v-if=\"perPageOptions?.length\" class=\"v-data-table-per-page\">\n <span class=\"v-data-table-per-page-label\" aria-hidden=\"true\">{{\n resolvedPerPageText\n }}</span>\n <!-- The panel is told to match its trigger, which here replaces the default\n minimum width with something sensible: a menu of \"10\", \"25\", \"50\" has no\n use for the width a menu of commands assumes, and it still cannot end up\n narrower than the button opening it. -->\n <VMenu size=\"sm\" :compact=\"compact\" placement=\"top-end\" match-trigger>\n <template #trigger=\"{ triggerProps }\">\n <VButton\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n :compact=\"compact\"\n v-bind=\"triggerProps\"\n :aria-label=\"m.dataTable.perPageValue(resolvedPerPageText, perPage ?? 0)\"\n >\n {{ perPage }}\n <VIcon :name=\"arrowDropDownIcon\" />\n </VButton>\n </template>\n <VMenuItem\n v-for=\"option in perPageOptions\"\n :key=\"option\"\n :label=\"String(option)\"\n :selected=\"option === perPage\"\n @select=\"setPerPage(option)\"\n />\n </VMenu>\n </div>\n <span v-if=\"showRange\" class=\"v-data-table-range\" aria-live=\"polite\">{{\n rangeSummary\n }}</span>\n <!-- Named after the table rather than with the generic pagination wording: a page\n holding this table AND a pagination of its own would otherwise expose two\n navigation landmarks with the same name, and a screen reader user could not\n tell them apart. -->\n <VPagination\n v-model=\"page\"\n :label=\"m.dataTable.pagination\"\n :length=\"pageCount\"\n size=\"sm\"\n :compact=\"compact\"\n align=\"end\"\n :total-visible=\"7\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-data-table {\n /* The density, expressed as the cells' padding and tightened by one step in the\n compact form. It does not go through the shared control scale — there is no single\n control height in a table — which is the same case VAccordion is in. */\n --data-table-pad-block: var(--vectis-space-3);\n --data-table-pad-inline: var(--vectis-space-3);\n --data-table-head-pad-block: var(--vectis-space-2);\n\n /* The gutter between the frame and what it holds: nothing when the table is\n unframed, so the caption, the toolbar and the footer sit flush with the edge, and\n the cells' own inline padding as soon as a frame appears. */\n --data-table-frame-pad: 0px;\n\n container-type: inline-size;\n font-family: var(--vectis-text-family);\n\n /*\n * A column of three: toolbar, scroller, footer. The root takes its parent's height and\n * the scroller absorbs what is left, which stops a last page of two rows shrinking the\n * whole table and keeps the footer at the bottom. With no height on the parent, a\n * percentage of `auto` resolves to `auto`, so this costs nothing and measures nothing.\n *\n * TRAP — deliberately NO `min-block-size: 0` here. Inside a flex column parent too short\n * for it, the automatic minimum is what keeps the table at its natural height instead of\n * letting it be crushed to nothing.\n */\n display: flex;\n flex-direction: column;\n block-size: 100%;\n }\n\n .v-data-table[data-compact] {\n --data-table-pad-block: var(--vectis-space-2);\n --data-table-pad-inline: var(--vectis-space-2);\n --data-table-head-pad-block: var(--vectis-space-1);\n }\n\n /* The card. The unframed default has nothing to undo, since it declares no decoration\n at all — whatever surrounds the table is what provides the surface then. */\n .v-data-table[data-variant='outlined'] {\n --data-table-frame-pad: var(--data-table-pad-inline);\n\n background: var(--vectis-color-surface-raised);\n border: 1px solid var(--vectis-color-border);\n border-radius: var(--vectis-radius-surface);\n /*\n * The price of the rounded corners: striped and selected row backgrounds, the sticky\n * heading's opaque background and the scroller's square corners would all spill past.\n *\n * TRAP — `clip` and not `hidden`. Hiding the overflow would make this element a scroll\n * container of its own, and the sticky heading would then anchor to IT rather than to\n * the area that actually scrolls — which is to say it would not stick at all.\n *\n * Confined to the framed form on purpose: unframed, the toolbar and footer sit flush\n * with the edge and the clip would crop their focus rings.\n */\n overflow: clip;\n }\n\n /*\n * The ONE part that scrolls, taking whatever the toolbar and footer leave. The `auto`\n * basis grows it from its CONTENT, so the whole arrangement is inert while there is no\n * free space: a table shorter than its container stays exactly as tall as it is.\n *\n * `min-block-size: 0` is load-bearing — a flex item refuses by default to shrink below its\n * content, so without it the area never compresses and the table overflows instead of\n * scrolling.\n *\n * Deliberately NOT confined to `data-responsive='scroll'`: a table taller than its host\n * must scroll in `stack` too, or `outlined`'s clip crops it and the rows below become\n * unreachable.\n */\n .v-data-table-scroller {\n flex: 1 1 auto;\n min-block-size: 0;\n overflow: auto;\n }\n\n .v-data-table-toolbar {\n flex: none; /* stays at the top, outside whatever scrolls */\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--vectis-space-3);\n padding-block-start: var(--data-table-frame-pad);\n padding-block-end: var(--vectis-space-3);\n padding-inline: var(--data-table-frame-pad);\n }\n\n /* The title is rendered by VTypography, and its colour is stated explicitly because the\n toolbar may well sit inside a context whose text is dimmed.\n\n TRAP — it is set through the colour VARIABLE that component reads, and not with a\n plain colour declaration. That declaration would collide with VTypography's own at\n equal specificity, and the winner would be decided by whichever sheet the consumer's\n bundler put last. */\n .v-data-table-title {\n --typography-color: var(--vectis-color-text);\n }\n\n /* The search field is given a width of its own, overriding the full width VInput takes\n by default. The selector is qualified by its context, which makes it one step more\n specific than VInput's own rule and therefore independent of the order the two sheets\n end up in. */\n .v-data-table-toolbar .v-input {\n inline-size: var(--vectis-control-size-table-search);\n max-inline-size: 100%;\n }\n\n .v-data-table-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text);\n }\n\n .v-data-table-caption {\n padding-block-end: var(--vectis-space-3);\n padding-inline: var(--data-table-frame-pad);\n text-align: start;\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text-muted);\n }\n\n .v-data-table-table th {\n padding: var(--data-table-head-pad-block) var(--data-table-pad-inline);\n text-align: start;\n font-size: var(--vectis-text-body-md-size);\n /* The heavier weight distinguishes a heading from the data under it, which is\n emphasis rather than a typographic role — hence a font token read directly. */\n font-weight: var(--vectis-font-weight-semibold);\n color: var(--vectis-color-text-muted);\n /* The heading row gets a tint of its own so it reads apart from the data. `muted` and\n not `sunken`: the striped rows already take `sunken`, and a heading painted the same\n would read as one more stripe. Opaque in both themes, which is also what a frozen\n heading needs, the rows scrolling underneath it. */\n background-color: var(--vectis-color-surface-muted);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-data-table-table td {\n padding: var(--data-table-pad-block) var(--data-table-pad-inline);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-data-table-table tbody tr:last-child td {\n border-block-end: none;\n }\n\n .v-data-table-table [data-align='end'] {\n text-align: end;\n }\n\n .v-data-table-table [data-align='center'] {\n text-align: center;\n }\n\n /* The checkbox column is reduced to the width of its content. A table lays its columns\n out automatically, so asking for no width at all is what makes it take the least\n possible. */\n .v-data-table-stack-label {\n display: none;\n }\n\n .v-data-table-table .v-data-table-select {\n inline-size: 0;\n }\n\n .v-data-table[data-striped] tbody tr:nth-child(even) {\n background-color: var(--vectis-color-surface-sunken);\n }\n\n /* Placed after the striping on purpose: the specificity is the same, so it is the order\n that makes a selected row keep its tint on both odd and even rows. */\n .v-data-table[data-selectable] tbody tr[data-selected] {\n background-color: var(--vectis-color-accent-surface);\n }\n\n /* Forced colours flatten both tints to `Canvas`: the stripes are only a reading aid, but\n a selected row would be told apart by its checkbox alone. It draws an inward outline in\n the system selection colour, which the mode keeps. */\n @media (forced-colors: active) {\n .v-data-table[data-selectable] tbody tr[data-selected] {\n outline: var(--vectis-focus-ring-width) solid Highlight;\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n }\n\n /* TRAP — a frozen heading relies on the opaque background every `th` already carries: the\n rows scroll underneath it, and a consumer restyling that background with a translucent\n colour will see them through. */\n .v-data-table[data-sticky-header] th {\n position: sticky;\n inset-block-start: 0;\n z-index: 1;\n }\n\n .v-data-table-sort {\n /* The icon context for the sort glyph. Without it the icon would fall back to one em\n — the heading's own text size — and come out visibly smaller than every other icon\n in the component. */\n --vectis-icon-size: var(--vectis-icon-size-md);\n --vectis-icon-opsz: 20;\n\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-1);\n border: none;\n background: transparent;\n padding: 0;\n color: inherit;\n font: inherit;\n cursor: pointer;\n /* Seen only on the focus ring: the control radius capped at half a line, VBreadcrumb's\n link recipe, since a heading that wraps would otherwise round to half its height. */\n border-radius: min(var(--vectis-radius-interactive), 0.5lh);\n }\n\n .v-data-table-sort:hover {\n color: var(--vectis-color-text);\n }\n\n .v-data-table-sort:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The glyph stays faint on every column that merely COULD be sorted: it announces that\n the heading can be clicked without competing for attention with the one column\n actually carrying the order. */\n .v-data-table-sort-icon {\n opacity: 0.35;\n }\n\n .v-data-table-sort[data-direction] .v-data-table-sort-icon {\n opacity: 1;\n }\n\n .v-data-table-state {\n padding: var(--vectis-space-6);\n text-align: center;\n color: var(--vectis-color-text-muted);\n }\n\n .v-data-table-state-loading {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n }\n\n .v-data-table-footer {\n flex: none; /* stays at the bottom, outside whatever scrolls */\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-4);\n padding-block-start: var(--vectis-space-3);\n padding-block-end: var(--data-table-frame-pad);\n padding-inline: var(--data-table-frame-pad);\n }\n\n /*\n * The right-hand zone is pushed to the edge by an automatic margin rather than by an\n * alignment on the footer. The two zones are siblings in the same row, and the count on\n * the left has to stay at the OPPOSITE edge: any alignment set on the footer would move\n * both of them together.\n *\n * The margin also does the right thing when there is no count at all, the zone simply\n * being pushed against the far edge on its own.\n */\n .v-data-table-footer-end {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-4);\n margin-inline-start: auto;\n }\n\n .v-data-table-selection,\n .v-data-table-range,\n .v-data-table-per-page-label {\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text-muted);\n }\n\n .v-data-table-per-page {\n display: flex;\n align-items: center;\n gap: var(--vectis-space-2);\n }\n\n /* The stacked form: once the COMPONENT is narrow, each row becomes a card carrying its\n own headings. It is entirely CSS, and the threshold is written as a literal length —\n these queries accept no variables.\n\n The column headings are not removed but hidden the way visually hidden text is: they\n still name each column for a screen reader, which reads the cells in the same order\n either way. */\n @container (max-width: 640px) {\n .v-data-table[data-responsive='stack'] .v-data-table-head {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n }\n\n /*\n * The heading row is out of sight, so its CONTROLS leave the tab order with it: a sort\n * button or a \"select all\" box the keyboard lands on and nobody can see. `visibility`\n * is what takes a control out of the tab order in CSS, and it takes it out of the\n * accessibility tree too, so a sortable heading hands its name to a plain text copy.\n */\n .v-data-table[data-responsive='stack'] .v-data-table-head :is(.v-data-table-sort, .v-checkbox) {\n visibility: hidden;\n }\n\n .v-data-table[data-responsive='stack'] .v-data-table-stack-label {\n display: inline;\n }\n\n .v-data-table[data-responsive='stack'] tbody tr {\n display: block;\n padding-block: var(--vectis-space-2);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-data-table[data-responsive='stack'] tbody tr:last-child {\n border-block-end: none;\n }\n\n .v-data-table[data-responsive='stack'] td {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--vectis-space-4);\n padding: var(--vectis-space-1) var(--vectis-space-2);\n border: none;\n text-align: end;\n }\n\n /* Each cell writes its own column's name before itself, taken from the attribute the\n template put there. It takes the overline type role, without forcing capitals. */\n .v-data-table[data-responsive='stack'] td::before {\n content: attr(data-label);\n font-size: var(--vectis-text-overline-size);\n font-weight: var(--vectis-text-overline-weight);\n letter-spacing: var(--vectis-text-overline-tracking);\n color: var(--vectis-color-text-muted);\n }\n\n /* The checkbox cell carries no column name — there is none — so it gets no heading\n and opens the card on its own line. */\n .v-data-table[data-responsive='stack'] td.v-data-table-select {\n justify-content: flex-start;\n }\n\n .v-data-table[data-responsive='stack'] td.v-data-table-select::before {\n content: none;\n }\n\n /* In a narrow component the search field drops under the title and takes the whole\n width. This applies to both responsive forms, the toolbar never being part of what\n scrolls. */\n .v-data-table-search {\n flex: 1 1 100%;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VDataTable.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VDataTable/VDataTable.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"Row extends Record<string, unknown>\">\n// @core\n/**\n * Native table semantics remain intact. JavaScript adds sorting, paging, selection and\n * debounced search; browser measurements stay in browser tests.\n */\n\nimport { computed, ref, useId, useSlots, watch, watchEffect } from 'vue'\nimport type { StyleValue } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VCheckbox from '../VCheckbox/VCheckbox.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { arrow_downward as arrowDownwardIcon } from '../VIcon/icons/arrow_downward'\nimport { arrow_drop_down as arrowDropDownIcon } from '../VIcon/icons/arrow_drop_down'\nimport { arrow_upward as arrowUpwardIcon } from '../VIcon/icons/arrow_upward'\nimport { search as searchIcon } from '../VIcon/icons/search'\nimport { swap_vert as swapVertIcon } from '../VIcon/icons/swap_vert'\nimport type { IconSource } from '../VIcon/types'\nimport VInput from '../VInput/VInput.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\nimport VPagination from '../VPagination/VPagination.vue'\nimport VSpinner from '../VSpinner/VSpinner.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { toggleValue } from '../../utils/array'\nimport { cssSize } from '../../utils/css'\nimport { isDev } from '../../utils/env'\nimport { clamp } from '../../utils/number'\nimport { createNormalizedCache, normalizeText } from '../../utils/text'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\nimport { useTimer } from '../../composables/useTimer'\nimport { useLocale, useMessages } from '../../i18n/state'\n\n/** How a column's content is aligned. */\nexport type DataTableColumnAlign = 'start' | 'center' | 'end'\n\n/** One column of the table. */\nexport interface DataTableColumn {\n /** Which field of a row it shows, and the name its slots are addressed by. */\n key: string\n /** Its heading. */\n label: string\n /** Lets the reader sort by this column. */\n sortable?: boolean\n /** How its content is aligned. Numbers usually belong at the end. */\n align?: DataTableColumnAlign\n}\n\n/** Which way a column is sorted: A to Z, or Z to A. */\nexport type DataTableSortDirection = 'asc' | 'desc'\n\n/** Which column the table is sorted by, and in which direction. */\nexport interface DataTableSort {\n /** The `key` of the column the rows are ordered by. */\n key: string\n /** Which way they are ordered. */\n direction: DataTableSortDirection\n}\n\n/** How a row is identified: by the field named in `rowKey`, or failing that by its position. */\nexport type DataTableRowId = string | number\n\n/**\n * Everything the table is currently being asked for, reported whenever it changes so a\n * server can answer it. What is not set is `null` rather than left out, so the object keeps\n * the same keys once serialized into a request.\n */\nexport interface DataTableParams {\n /** The page asked for, from 1. */\n page: number\n /** How many rows a page holds, `null` when the table is not paginated. */\n perPage: number | null\n /** The `key` of the sorted column, `null` when the rows are not sorted. */\n sortKey: string | null\n /** Which way they are sorted, `null` when they are not. */\n sortDirection: DataTableSortDirection | null\n /** The committed search, empty when nothing is searched for. */\n search: string\n}\n\n/** What the `cell-<key>` slots receive. */\nexport interface DataTableCellSlotProps<Row extends Record<string, unknown>> {\n /** The row the cell belongs to. */\n row: Row\n /** The value of the column's field in that row. */\n value: unknown\n /** The column the cell belongs to. */\n column: DataTableColumn\n}\n\n/** What the `head-<key>` slots receive. */\nexport interface DataTableHeadSlotProps {\n /** The column the heading belongs to. */\n column: DataTableColumn\n}\n\n/** What the `#empty` slot receives. */\nexport interface DataTableEmptySlotProps {\n /** The search that produced the empty result, empty when nothing was searched for. */\n search: string\n}\n\n/** The rows on show, as `rangeText` receives them, counted from 1. */\nexport interface DataTableRange {\n /** The position of the first row on show. */\n start: number\n /** The position of the last row on show. */\n end: number\n /** How many rows there are in all. */\n total: number\n}\n\n/** How much decoration the table carries. */\nexport type DataTableVariant = 'flat' | 'outlined'\n\n/** What a narrow container does to the rows. */\nexport type DataTableResponsive = 'scroll' | 'stack'\n\n// This interface is exported rather than kept local, and it is generic rather than referring to\n// the component's own type parameter. A component typed over its rows inlines the whole\n// signature of its props into the declarations it emits, so a name that is not exported cannot\n// be written there and the build of the type declarations fails.\nexport interface DataTableProps<Row extends Record<string, unknown>> {\n /** The columns to show, in order. */\n columns: DataTableColumn[]\n /** The rows to show. */\n rows: Row[]\n /** Which field identifies a row. */\n rowKey?: string\n /**\n * A sentence describing what the table holds. It is announced before the table itself,\n * and is what tells a screen reader user whether it is worth exploring.\n */\n caption?: string\n /**\n * How the table is framed: nothing at all, or a card with a raised background, a border\n * and rounded corners.\n */\n variant?: DataTableVariant\n /**\n * What happens when the component is too narrow: the table scrolls sideways, or each\n * row becomes a card with its column headings repeated inside it.\n */\n responsive?: DataTableResponsive\n /** Shows that the rows are being loaded. */\n loading?: boolean\n /**\n * What is written beside the spinner while the rows are loading. It falls back to the design\n * system dictionary.\n */\n loadingText?: string\n /** What is said when there is no row to show. It falls back to the design system dictionary. */\n emptyText?: string\n /** A title above the table, on the left of its toolbar. */\n title?: string\n /** Adds a search field to the toolbar. */\n searchable?: boolean\n /** What that field says while empty. It falls back to the design system dictionary. */\n searchPlaceholder?: string\n /**\n * What screen readers announce for the search field. It falls back to the design\n * system dictionary.\n */\n searchLabel?: string\n /**\n * When a server does the searching, how long to wait after a keystroke before asking\n * it, in milliseconds. Zero asks at once.\n */\n searchDebounce?: number\n /** Tints every other row, which helps the eye follow a long line across the table. */\n striped?: boolean\n /**\n * Keeps the column headings in place while the rows scroll under them. It needs a\n * bounded scrolling area to work: either the `height` prop, or a parent with a height\n * of its own.\n */\n stickyHeader?: boolean\n /** Tightens the cells by one step, and everything the table renders with them. */\n compact?: boolean\n /**\n * The height of the whole component, toolbar and pagination included: a number is read as\n * pixels, anything else as a CSS length. Left out, the table takes its parent's height\n * whenever the parent has one.\n */\n height?: number | string\n /** The heading icon of a column that can be sorted but currently is not. */\n sortIcon?: IconSource\n /**\n * The icon of an ascending sort. It points DOWN by default, the spreadsheet\n * convention: sorting A to Z reads downwards.\n */\n sortAscIcon?: IconSource\n /** The icon of a descending sort. */\n sortDescIcon?: IconSource\n /** The choices offered for how many rows a page holds. */\n perPageOptions?: number[]\n /** What that choice is called. It falls back to the design system dictionary. */\n perPageText?: string\n /**\n * How many rows there are in all on the server. It is what lets the pagination and the\n * range be right when the table only ever holds one page.\n */\n total?: number\n /** Shows which rows are being looked at (\"1–10 of 42\") in the footer. */\n showRange?: boolean\n /** Rephrases that range. It falls back to the design system dictionary. */\n rangeText?: (range: DataTableRange) => string\n /** Adds a checkbox to every row, and one in the heading to take the whole page. */\n selectable?: boolean\n /**\n * What the heading checkbox is announced as. It falls back to the design system\n * dictionary.\n */\n selectAllLabel?: string\n /**\n * How the selection is summed up in the footer. It says nothing at all when nothing is\n * selected, and falls back to the design system dictionary.\n */\n selectionText?: (count: number) => string\n /**\n * What a row's checkbox is announced as. \"Select row\" tells a screen reader user nothing\n * about WHICH row, so this is worth supplying with something from the row itself. It falls\n * back to the design system dictionary, which numbers the rows from 1.\n */\n selectRowLabel?: (row: Row, index: number) => string\n /**\n * Hands the searching, the sorting and the paging over to a server: the rows are shown\n * exactly as they arrive, and every change of what is being asked for is reported so\n * the server can answer it.\n */\n serverSide?: boolean\n}\n\nconst props = withDefaults(defineProps<DataTableProps<Row>>(), {\n rowKey: undefined,\n caption: undefined,\n variant: 'flat',\n responsive: 'scroll',\n loading: false,\n loadingText: undefined,\n emptyText: undefined,\n title: undefined,\n searchable: false,\n searchPlaceholder: undefined,\n searchLabel: undefined,\n searchDebounce: 250,\n striped: false,\n stickyHeader: false,\n compact: false,\n height: undefined,\n sortIcon: () => swapVertIcon,\n sortAscIcon: () => arrowDownwardIcon,\n sortDescIcon: () => arrowUpwardIcon,\n perPageOptions: undefined,\n perPageText: undefined,\n total: undefined,\n showRange: false,\n rangeText: undefined,\n selectable: false,\n selectAllLabel: undefined,\n selectionText: undefined,\n selectRowLabel: undefined,\n serverSide: false,\n})\n\nconst m = useMessages()\nconst vectisLocale = useLocale()\nconst resolvedEmptyText = computed(() => props.emptyText ?? m.value.dataTable.empty)\nconst resolvedLoadingText = computed(() => props.loadingText ?? m.value.dataTable.loading)\nconst resolvedSearchPlaceholder = computed(\n () => props.searchPlaceholder ?? m.value.dataTable.searchPlaceholder,\n)\nconst resolvedSearchLabel = computed(() => props.searchLabel ?? m.value.dataTable.searchLabel)\nconst resolvedPerPageText = computed(() => props.perPageText ?? m.value.dataTable.perPage)\nconst resolvedSelectAllLabel = computed(() => props.selectAllLabel ?? m.value.dataTable.selectAll)\n\n/** Which column the rows are sorted by, and in which direction. */\nconst sort = defineModel<DataTableSort | null>('sort', { default: null })\n/** The page being shown, counted from 1. */\nconst page = defineModel<number>('page', { default: 1 })\n/**\n * How many rows a page holds. Any value above zero turns the pagination on, so passing\n * one down without binding it is enough to enable it.\n */\nconst perPage = defineModel<number | undefined>('perPage', { default: undefined })\n/*\n * `sort` is `null` because the TABLE writes that state itself, a third click on a heading\n * clearing the order, and a model the component writes needs a value a consumer can store and\n * compare. `perPage` is `undefined` because nothing here ever clears it: it is simply a model\n * that was not given.\n */\n/** The selected rows, as the identities `rowKey` gives them, never the row objects themselves. */\nconst selected = defineModel<DataTableRowId[]>('selected', { default: () => [] })\n/**\n * What is typed in the search field, empty to begin with. Only the declared columns are\n * searched, accent- and case-insensitively; in `serverSide` mode nothing is filtered here\n * and the term is reported through `update:params` instead.\n */\nconst search = defineModel<string>('search', { default: '' })\n\nconst emit = defineEmits<{\n /**\n * What the table is now being asked for, when a server is answering: the page, the page size,\n * the sort or the search has changed, the last one after its delay.\n */\n 'update:params': [params: DataTableParams]\n}>()\n\ndefineSlots<{\n /** What a cell of a given column shows: a slot named after that column's key. */\n [name: `cell-${string}`]: (scope: DataTableCellSlotProps<Row>) => unknown\n /** What a column's heading shows: a slot named after that column's key. */\n [name: `head-${string}`]: (scope: DataTableHeadSlotProps) => unknown\n /** The left side of the toolbar, replacing the `title` prop. */\n title?(): unknown\n /** What the table shows while its rows are loading, replacing the spinner and its text. */\n loading?(): unknown\n /**\n * What the table shows when there is no row to show, replacing `emptyText`. It receives the\n * search that produced the empty result, empty when nothing was searched for.\n */\n empty?(scope: DataTableEmptySlotProps): unknown\n}>()\n\n// `class` and `style` stay on the wrapper, where a consumer expects to place the component;\n// everything else; an id, the aria-*; goes to the `<table>` itself, the only element they\n// validly describe.\ndefineOptions({ inheritAttrs: false })\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst slots = useSlots()\n\n// A template ref on the component reaches the wrapper, a layout box; the `<table>` is where\n// the consumer's attributes land and what they describe.\nconst tableEl = ref<HTMLTableElement | null>(null)\ndefineExpose({\n /** The `<table>` element, where the consumer's attributes land. */\n el: tableEl,\n})\n\n// @a11y\n/*\n * A caption already does, and so does a consumer's own `aria-label` or `aria-labelledby`, which\n * a reference to the title would override. Bound before the forwarded attributes, like every\n * state the component owns.\n */\nconst titleId = useId()\nconst tableLabelledBy = computed(() =>\n props.caption ||\n !(props.title || slots.title) ||\n attrs['aria-label'] !== undefined ||\n attrs['aria-labelledby'] !== undefined\n ? undefined\n : titleId,\n)\n\n// @devwarn\nif (isDev) {\n if (props.selectable && !props.rowKey)\n console.warn(\n '[VDataTable] `selectable` without `rowKey` — index-based identities are corrupted by sorting, filtering and pagination.',\n )\n /*\n * A key that is missing, or not a string or a number, turns into the same text on every\n * row (\"undefined\"), and two rows sharing a key share an identity: ticking one ticks them\n * all, and the rows' own `:key`s collide. Once per instance and per kind.\n */\n const warned = new Set<string>()\n watchEffect(() => {\n const key = props.rowKey\n if (!key) return\n const seen = new Set<unknown>()\n for (const row of props.rows) {\n const value = row[key]\n if (typeof value !== 'string' && typeof value !== 'number') {\n if (!warned.has('type')) {\n warned.add('type')\n console.warn(\n `[VDataTable] \\`rowKey\\` \"${key}\" is missing or is not a string or a number on some rows: those rows share one identity.`,\n )\n }\n } else if (seen.has(value)) {\n if (!warned.has('duplicate')) {\n warned.add('duplicate')\n console.warn(\n `[VDataTable] \\`rowKey\\` \"${key}\" holds the value \"${value}\" twice: rows are told apart by it, so it must be unique.`,\n )\n }\n } else seen.add(value)\n }\n })\n}\n\n/*\n * A key that is already a string or a number is returned AS IT IS. The selection holds these\n * identities and is looked up with a Set, which tells the number 7 from the string \"7\": turned\n * into text, a table keyed by numeric ids never found a selected row again, so every checkbox\n * stayed unticked.\n */\nfunction rowIdentity(row: Row, index: number): DataTableRowId {\n if (!props.rowKey) return index\n const key = row[props.rowKey]\n return typeof key === 'string' || typeof key === 'number' ? key : String(key)\n}\n\n// Searching fields the reader cannot see would return rows for reasons nothing on screen\n// explains.\n/*\n * The filter below re-reads the whole table on every keystroke: ten thousand rows across five\n * columns is fifty thousand normalizations per character typed without it.\n */\nconst normalizedOf = createNormalizedCache<Row>()\nconst normalizedCell = (row: Row, key: string) => normalizedOf(row, String(row[key] ?? ''), key)\n\nconst filteredRows = computed(() => {\n if (props.serverSide || !props.searchable) return props.rows\n const needle = normalizeText((search.value ?? '').trim())\n if (!needle) return props.rows\n return props.rows.filter((row) =>\n props.columns.some((column) => normalizedCell(row, column.key).includes(needle)),\n )\n})\n\n/*\n * The locale must nonetheless stay EXPLICIT: an `undefined` one resolves differently in Node\n * and in the browser, so the row order would diverge across hydration.\n */\nconst collator = computed(() => new Intl.Collator(vectisLocale.value, { numeric: true }))\n\n/*\n * What a cell is compared by. A `Date` by its instant: as text it collates on `toString()`,\n * which opens with the WEEKDAY, so Monday the 5th sorted before Saturday the 3rd.\n */\nconst NUMERIC_TEXT = /^\\s*[+-]?(\\d+\\.?\\d*|\\.\\d+)(e[+-]?\\d+)?\\s*$/i\nfunction sortKey(value: unknown): unknown {\n if (value instanceof Date) return value.getTime()\n if (typeof value === 'string' && NUMERIC_TEXT.test(value)) return Number(value)\n return value\n}\n\nconst sortedRows = computed(() => {\n const current = sort.value\n if (!current || props.serverSide) return filteredRows.value\n const factor = current.direction === 'asc' ? 1 : -1\n const compare = collator.value.compare\n return [...filteredRows.value].sort((a, b) => {\n const av = sortKey(a[current.key])\n const bv = sortKey(b[current.key])\n if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * factor\n return compare(String(av ?? ''), String(bv ?? '')) * factor\n })\n})\n\nconst paginated = computed(() => (perPage.value ?? 0) > 0)\nconst totalCount = computed(() =>\n props.serverSide ? (props.total ?? props.rows.length) : filteredRows.value.length,\n)\nconst pageCount = computed(() =>\n paginated.value ? Math.max(1, Math.ceil(totalCount.value / (perPage.value ?? 1))) : 1,\n)\n// The page actually shown is CLAMPED by derivation and the model is never rewritten: a\n// search that leaves fewer pages simply falls back to the last one, and typing a\n// character that would have emptied the table does not silently move the consumer's own\n// page number.\nconst currentPage = computed(() => clamp(page.value, 1, pageCount.value))\n\nconst displayedRows = computed(() => {\n // With a server answering, the rows received ARE the current page: cutting them again\n // would show a tenth of a page.\n if (props.serverSide || !paginated.value) return sortedRows.value\n const start = (currentPage.value - 1) * (perPage.value ?? 0)\n return sortedRows.value.slice(start, start + (perPage.value ?? 0))\n})\n\nfunction toggleSort(key: string) {\n const current = sort.value\n if (!current || current.key !== key) sort.value = { key, direction: 'asc' }\n else if (current.direction === 'asc') sort.value = { key, direction: 'desc' }\n else sort.value = null\n}\n\n// @a11y\n// Only the column actually sorted carries the attribute. Setting it on every heading would have a screen reader announce \"not sorted\" on each one in turn, drowning\n// the single piece of information that matters: which column the order comes from.\nfunction ariaSort(column: DataTableColumn): 'ascending' | 'descending' | undefined {\n if (sort.value?.key !== column.key) return undefined\n return sort.value.direction === 'asc' ? 'ascending' : 'descending'\n}\n\n/**\n * Which icon a column's heading shows: a neutral one on any sortable column, and the\n * arrow of the current direction on the one being sorted by.\n *\n * It is deliberately named differently from the prop it falls back to: written the same,\n * the function would shadow the prop inside the template and the icons would never be\n * overridable.\n */\nfunction sortIconFor(column: DataTableColumn): IconSource {\n if (sort.value?.key !== column.key) return props.sortIcon\n return sort.value.direction === 'asc' ? props.sortAscIcon : props.sortDescIcon\n}\n\n// And when a server answers, nothing native waits before asking it, so that has to be written.\n// The waiting is delegated to `useTimer`; a delay of zero running at once, cancellation when\n// the component goes away; and the term that was actually committed is what the parameters\n// below report.\nconst committedSearch = ref(search.value)\nconst searchTimer = useTimer()\n\nfunction commitSearch() {\n // A term typed and erased inside the debounce is no new search: going back to page 1\n // and asking the server again would be a request for what is already on screen.\n if (search.value === committedSearch.value) return\n committedSearch.value = search.value\n page.value = 1\n}\n\nwatch(search, () => {\n if (!props.serverSide) {\n page.value = 1\n return\n }\n searchTimer.start(commitSearch, props.searchDebounce)\n})\n\nconst emptySearch = computed(() => (props.serverSide ? committedSearch.value : search.value))\n\n/*\n * `page` is the model, never the clamped `currentPage`. The clamp reads `total`, which is the\n * server's own answer: read here, a response that changed the total would move the parameters\n * and ask for the page again, and a consumer clearing `total` while it loads would send the\n * reader back to page 1.\n */\n// Committing a search and going back to the first page happen one after the other, but both\n// land in the same flush, so this runs once on the final values; never two requests for what\n// the reader experienced as a single action. Compared by value: a parent handing down an equal\n// `sort` object is no new request.\nwatch(\n [\n page,\n () => perPage.value ?? null,\n () => sort.value?.key ?? null,\n () => sort.value?.direction ?? null,\n committedSearch,\n ],\n ([page, perPage, sortKey, sortDirection, search]) => {\n if (props.serverSide) emit('update:params', { page, perPage, sortKey, sortDirection, search })\n },\n)\n\n// The selection. The heading checkbox covers the rows currently VISIBLE and not the whole\n// table: a box that silently selected forty thousand rows would be a trap.\nconst selectedSet = computed(() => new Set<DataTableRowId>(selected.value))\nconst visibleIds = computed(() => displayedRows.value.map((row, index) => rowIdentity(row, index)))\nconst allVisibleSelected = computed(\n () => visibleIds.value.length > 0 && visibleIds.value.every((id) => selectedSet.value.has(id)),\n)\nconst masterIndeterminate = computed(\n () => !allVisibleSelected.value && visibleIds.value.some((id) => selectedSet.value.has(id)),\n)\n\n/*\n * A row is asked about by the identity `visibleIds` already holds for it: the template reads\n * `visibleIds[index]` for the key, the tint and the checkbox alike, rather than deriving it\n * again three or four times per row per render.\n */\nconst isSelected = (id: DataTableRowId) => selectedSet.value.has(id)\n\nfunction toggleRow(id: DataTableRowId) {\n selected.value = toggleValue(selected.value, id)\n}\n\n// It only ever touches the rows on screen, so what was selected on the other pages\n// survives moving between them.\n// Refused with no row on screen, or while the rows on screen are the PREVIOUS ones behind\n// the loading state: ticked there, it stayed ticked with nothing selected, or selected rows\n// the reader could not see.\nconst masterDisabled = computed(() => visibleIds.value.length === 0 || props.loading)\n\nfunction toggleMaster() {\n if (masterDisabled.value) return\n const ids = visibleIds.value\n if (allVisibleSelected.value) {\n const visible = new Set(ids)\n selected.value = selected.value.filter((id) => !visible.has(id))\n } else {\n const current = selectedSet.value\n selected.value = [...selected.value, ...ids.filter((id) => !current.has(id))]\n }\n}\n\n// @a11y\n// Without this every checkbox in the column would be announced identically, and a screen\n// reader user would have no way of knowing which row they were about to select.\nfunction rowSelectLabel(row: Row, index: number): string {\n // The position in the whole table, or every page would have its own \"Select row 1\". The\n // dictionary counts from one as a human does, the prop from zero as code does.\n const position = (paginated.value ? (currentPage.value - 1) * (perPage.value ?? 0) : 0) + index\n return props.selectRowLabel?.(row, position) ?? m.value.dataTable.selectRow(position + 1)\n}\n\nwatch(perPage, () => {\n page.value = 1\n})\n\nconst colCount = computed(() => props.columns.length + (props.selectable ? 1 : 0))\n\n// @a11y\n/**\n * It is rendered, empty, as soon as rows can be selected at all. A region meant to announce its\n * own changes must already exist before the first change: inserted into the page at the same\n * moment as its text, it announces nothing, and the first row selected would pass in silence.\n */\nconst selectionSummary = computed(() => {\n const count = selected.value.length\n if (count === 0) return ''\n if (props.selectionText) return props.selectionText(count)\n return m.value.dataTable.selection(count)\n})\n\nconst rangeSummary = computed(() => {\n const per = perPage.value ?? 0\n const total = totalCount.value\n const start = total === 0 ? 0 : (currentPage.value - 1) * per + 1\n const end = Math.min(currentPage.value * per, total)\n return props.rangeText?.({ start, end, total }) ?? m.value.dataTable.range({ start, end, total })\n})\n\n// The height is applied to the whole component and not as a ceiling on the scrolling area. A\n// soft ceiling remains available to the consumer through a maximum height of their own: the\n// component is a column whose scrolling area can be compressed.\nconst heightStyle = computed<StyleValue | undefined>(() =>\n props.height !== undefined ? { blockSize: cssSize(props.height) } : undefined,\n)\n</script>\n\n<template>\n <div\n class=\"v-data-table\"\n :class=\"rootClass\"\n :style=\"[heightStyle, rootStyle]\"\n :data-variant=\"variant\"\n :data-responsive=\"responsive\"\n :data-striped=\"striped ? '' : undefined\"\n :data-compact=\"compact ? '' : undefined\"\n :data-sticky-header=\"stickyHeader ? '' : undefined\"\n :data-selectable=\"selectable ? '' : undefined\"\n >\n <div v-if=\"title || $slots.title || searchable\" class=\"v-data-table-toolbar\">\n <VTypography :id=\"titleId\" as=\"div\" variant=\"heading-4\" class=\"v-data-table-title\">\n <slot name=\"title\">{{ title }}</slot>\n </VTypography>\n <VInput\n v-if=\"searchable\"\n v-model=\"search\"\n class=\"v-data-table-search\"\n type=\"search\"\n size=\"sm\"\n :compact=\"compact\"\n :icon-start=\"searchIcon\"\n clearable\n :placeholder=\"resolvedSearchPlaceholder\"\n :aria-label=\"resolvedSearchLabel\"\n />\n </div>\n\n <div class=\"v-data-table-scroller\">\n <table\n ref=\"tableEl\"\n class=\"v-data-table-table\"\n :aria-labelledby=\"tableLabelledBy\"\n v-bind=\"forwardedAttrs\"\n >\n <caption v-if=\"caption\" class=\"v-data-table-caption\">\n {{\n caption\n }}\n </caption>\n <thead class=\"v-data-table-head\">\n <tr>\n <th v-if=\"selectable\" scope=\"col\" class=\"v-data-table-select\">\n <VCheckbox\n :model-value=\"allVisibleSelected\"\n :indeterminate=\"masterIndeterminate\"\n :disabled=\"masterDisabled\"\n :aria-label=\"resolvedSelectAllLabel\"\n @update:model-value=\"toggleMaster\"\n />\n <!--\n Its hidden box would leave the heading empty in the stacked layout (axe\n empty-table-header), so it keeps its name as text there.\n -->\n <span class=\"v-data-table-stack-label\">{{ resolvedSelectAllLabel }}</span>\n </th>\n <th\n v-for=\"column in columns\"\n :key=\"column.key\"\n scope=\"col\"\n :data-align=\"column.align\"\n :aria-sort=\"ariaSort(column)\"\n >\n <button\n v-if=\"column.sortable\"\n type=\"button\"\n class=\"v-data-table-sort\"\n :data-direction=\"sort?.key === column.key ? sort.direction : undefined\"\n @click=\"toggleSort(column.key)\"\n >\n <slot :name=\"`head-${column.key}`\" :column=\"column\">{{ column.label }}</slot>\n <!-- Decorative, and deliberately so: given no label, an icon hides itself\n from screen readers. What the sort state is, is already carried by\n the heading itself. -->\n <VIcon class=\"v-data-table-sort-icon\" v-bind=\"iconProps(sortIconFor(column))\" />\n </button>\n <!-- The heading as plain text, shown to screen readers in the STACKED layout\n only, where the button above is hidden: see the stack rules. -->\n <span v-if=\"column.sortable\" class=\"v-data-table-stack-label\">{{\n column.label\n }}</span>\n <template v-else>\n <slot :name=\"`head-${column.key}`\" :column=\"column\">{{ column.label }}</slot>\n </template>\n </th>\n </tr>\n </thead>\n <tbody>\n <!--\n The order matters: loading is checked before emptiness, so a table waiting for its\n rows never claims there are none.\n -->\n <tr v-if=\"loading\">\n <td :colspan=\"colCount\" class=\"v-data-table-state\">\n <slot name=\"loading\">\n <!-- The spinner carries the text for a screen reader, so the visible copy is\n hidden from it: read twice, the cell would say it is loading twice. -->\n <span class=\"v-data-table-state-loading\">\n <VSpinner :label=\"resolvedLoadingText\" />\n <span aria-hidden=\"true\">{{ resolvedLoadingText }}</span>\n </span>\n </slot>\n </td>\n </tr>\n <tr v-else-if=\"displayedRows.length === 0\">\n <td :colspan=\"colCount\" class=\"v-data-table-state\">\n <slot name=\"empty\" :search=\"emptySearch\">{{ resolvedEmptyText }}</slot>\n </td>\n </tr>\n <template v-else>\n <tr\n v-for=\"(row, index) in displayedRows\"\n :key=\"visibleIds[index]\"\n :data-selected=\"selectable && isSelected(visibleIds[index]!) ? '' : undefined\"\n >\n <!--\n The selection is marked with a plain attribute and not with the ARIA selected\n state, which is invalid on the row of a table: it belongs to a grid. What tells\n assistive technology that a row is selected is its checkbox being checked.\n -->\n <td v-if=\"selectable\" class=\"v-data-table-select\">\n <VCheckbox\n :model-value=\"isSelected(visibleIds[index]!)\"\n :aria-label=\"rowSelectLabel(row, index)\"\n @update:model-value=\"toggleRow(visibleIds[index]!)\"\n />\n </td>\n <td\n v-for=\"column in columns\"\n :key=\"column.key\"\n :data-label=\"column.label\"\n :data-align=\"column.align\"\n >\n <slot\n :name=\"`cell-${column.key}`\"\n :row=\"row\"\n :value=\"row[column.key]\"\n :column=\"column\"\n >\n {{ row[column.key] }}\n </slot>\n </td>\n </tr>\n </template>\n </tbody>\n </table>\n </div>\n\n <!-- The footer has two zones: what is selected on the left, and on the right the page\n size, the range and the pagination, in that order. -->\n <div v-if=\"paginated || selectable\" class=\"v-data-table-footer\">\n <span v-if=\"selectable\" class=\"v-data-table-selection\" aria-live=\"polite\">{{\n selectionSummary\n }}</span>\n <div v-if=\"paginated\" class=\"v-data-table-footer-end\">\n <div v-if=\"perPageOptions?.length\" class=\"v-data-table-per-page\">\n <span class=\"v-data-table-per-page-label\" aria-hidden=\"true\">{{\n resolvedPerPageText\n }}</span>\n <!-- The panel is told to match its trigger, which here replaces the default\n minimum width with something sensible: a menu of \"10\", \"25\", \"50\" has no\n use for the width a menu of commands assumes, and it still cannot end up\n narrower than the button opening it. -->\n <VMenu size=\"sm\" :compact=\"compact\" placement=\"top-end\" match-trigger>\n <template #trigger=\"{ triggerProps }\">\n <VButton\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n :compact=\"compact\"\n v-bind=\"triggerProps\"\n :aria-label=\"m.dataTable.perPageValue(resolvedPerPageText, perPage ?? 0)\"\n >\n {{ perPage }}\n <VIcon :name=\"arrowDropDownIcon\" />\n </VButton>\n </template>\n <VMenuItem\n v-for=\"option in perPageOptions\"\n :key=\"option\"\n :label=\"String(option)\"\n :selected=\"option === perPage\"\n @select=\"perPage = option\"\n />\n </VMenu>\n </div>\n <span v-if=\"showRange\" class=\"v-data-table-range\" aria-live=\"polite\">{{\n rangeSummary\n }}</span>\n <!--\n Named after the table rather than with the generic pagination wording: a page holding\n this table and a pagination of its own would otherwise expose two navigation landmarks\n with the same name, and a screen reader user could not tell them apart.\n -->\n <VPagination\n v-model=\"page\"\n :label=\"m.dataTable.pagination\"\n :length=\"pageCount\"\n size=\"sm\"\n :compact=\"compact\"\n align=\"end\"\n :total-visible=\"7\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-data-table {\n /*\n * It does not go through the shared control scale; there is no single control height in a\n * table; which is the same case VAccordion is in.\n */\n --data-table-pad-block: var(--vectis-space-3);\n --data-table-pad-inline: var(--vectis-space-3);\n --data-table-head-pad-block: var(--vectis-space-2);\n\n /* The gutter between the frame and what it holds: nothing when the table is\n unframed, so the caption, the toolbar and the footer sit flush with the edge, and\n the cells' own inline padding as soon as a frame appears. */\n --data-table-frame-pad: 0px;\n\n container-type: inline-size;\n font-family: var(--vectis-text-family);\n\n /*\n * Deliberately no `min-block-size: 0` here. Inside a flex column parent too short for it,\n * the automatic minimum is what keeps the table at its natural height instead of letting it\n * be crushed to nothing.\n */\n display: flex;\n flex-direction: column;\n block-size: 100%;\n }\n\n .v-data-table[data-compact] {\n --data-table-pad-block: var(--vectis-space-2);\n --data-table-pad-inline: var(--vectis-space-2);\n --data-table-head-pad-block: var(--vectis-space-1);\n }\n\n /*\n * The card. The unframed default has nothing to undo, since it declares no decoration at all;\n * whatever surrounds the table is what provides the surface then.\n */\n .v-data-table[data-variant='outlined'] {\n --data-table-frame-pad: var(--data-table-pad-inline);\n\n background: var(--vectis-color-surface-raised);\n border: 1px solid var(--vectis-color-border);\n border-radius: var(--vectis-radius-surface);\n /*\n * `clip` and not `hidden`. Hiding the overflow would make this element a scroll container\n * of its own, and the sticky heading would then anchor to IT rather than to the area that\n * actually scrolls; which is to say it would not stick at all.\n */\n overflow: clip;\n }\n\n /*\n * `min-block-size: 0` is load-bearing; a flex item refuses by default to shrink below its\n * content, so without it the area never compresses and the table overflows instead of\n * scrolling. Deliberately not confined to `data-responsive='scroll'`: a table taller than its\n * host must scroll in `stack` too, or `outlined`'s clip crops it and the rows below become\n * unreachable.\n */\n .v-data-table-scroller {\n flex: 1 1 auto;\n min-block-size: 0;\n overflow: auto;\n }\n\n .v-data-table-toolbar {\n flex: none;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--vectis-space-3);\n padding-block-start: var(--data-table-frame-pad);\n padding-block-end: var(--vectis-space-3);\n padding-inline: var(--data-table-frame-pad);\n }\n\n /*\n * It is set through the colour VARIABLE that component reads, and not with a plain colour\n * declaration. That declaration would collide with VTypography's own at equal specificity,\n * and the winner would be decided by whichever sheet the consumer's bundler put last.\n */\n .v-data-table-title {\n --typography-color: var(--vectis-color-text);\n }\n\n /* The search field is given a width of its own, overriding the full width VInput takes\n by default. The selector is qualified by its context, which makes it one step more\n specific than VInput's own rule and therefore independent of the order the two sheets\n end up in. */\n .v-data-table-toolbar .v-input {\n inline-size: var(--vectis-control-size-table-search);\n max-inline-size: 100%;\n }\n\n .v-data-table-table {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text);\n }\n\n .v-data-table-caption {\n padding-block-end: var(--vectis-space-3);\n padding-inline: var(--data-table-frame-pad);\n text-align: start;\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text-muted);\n }\n\n .v-data-table-table th {\n padding: var(--data-table-head-pad-block) var(--data-table-pad-inline);\n text-align: start;\n font-size: var(--vectis-text-body-md-size);\n /*\n * The heavier weight distinguishes a heading from the data under it, which is emphasis\n * rather than a typographic role; hence a font token read directly.\n */\n font-weight: var(--vectis-font-weight-semibold);\n color: var(--vectis-color-text-muted);\n /*\n * The heading row gets a tint of its own so it reads apart from the data. `muted` and not\n * `sunken`: the striped rows already take `sunken`, and a heading painted the same would\n * read as one more stripe.\n */\n background-color: var(--vectis-color-surface-muted);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-data-table-table td {\n padding: var(--data-table-pad-block) var(--data-table-pad-inline);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-data-table-table tbody tr:last-child td {\n border-block-end: none;\n }\n\n .v-data-table-table [data-align='end'] {\n text-align: end;\n }\n\n .v-data-table-table [data-align='center'] {\n text-align: center;\n }\n\n /* The checkbox column is reduced to the width of its content. A table lays its columns\n out automatically, so asking for no width at all is what makes it take the least\n possible. */\n .v-data-table-stack-label {\n display: none;\n }\n\n .v-data-table-table .v-data-table-select {\n inline-size: 0;\n }\n\n .v-data-table[data-striped] tbody tr:nth-child(even) {\n background-color: var(--vectis-color-surface-sunken);\n }\n\n /* Placed after the striping on purpose: the specificity is the same, so it is the order\n that makes a selected row keep its tint on both odd and even rows. */\n .v-data-table[data-selectable] tbody tr[data-selected] {\n background-color: var(--vectis-color-accent-surface);\n }\n\n /* Forced colours flatten both tints to `Canvas`: the stripes are only a reading aid, but\n a selected row would be told apart by its checkbox alone. It draws an inward outline in\n the system selection colour, which the mode keeps. */\n @media (forced-colors: active) {\n .v-data-table[data-selectable] tbody tr[data-selected] {\n outline: var(--vectis-focus-ring-width) solid Highlight;\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n }\n\n /*\n * A frozen heading relies on the opaque background every `th` already carries: the rows\n * scroll underneath it, and a consumer restyling that background with a translucent colour\n * will see them through.\n */\n .v-data-table[data-sticky-header] th {\n position: sticky;\n inset-block-start: 0;\n z-index: 1;\n }\n\n .v-data-table-sort {\n /*\n * The icon context for the sort glyph. Without it the icon would fall back to one em; the\n * heading's own text size; and come out visibly smaller than every other icon in the\n * component.\n */\n --vectis-icon-size: var(--vectis-icon-size-md);\n --vectis-icon-opsz: 20;\n\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-1);\n border: none;\n background: transparent;\n padding: 0;\n color: inherit;\n font: inherit;\n cursor: pointer;\n /* Seen only on the focus ring: the control radius capped at half a line, VBreadcrumb's\n link recipe, since a heading that wraps would otherwise round to half its height. */\n border-radius: min(var(--vectis-radius-interactive), 0.5lh);\n }\n\n .v-data-table-sort:hover {\n color: var(--vectis-color-text);\n }\n\n .v-data-table-sort:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The glyph stays faint on every column that merely COULD be sorted: it announces that\n the heading can be clicked without competing for attention with the one column\n actually carrying the order. */\n .v-data-table-sort-icon {\n opacity: 0.35;\n }\n\n .v-data-table-sort[data-direction] .v-data-table-sort-icon {\n opacity: 1;\n }\n\n .v-data-table-state {\n padding: var(--vectis-space-6);\n text-align: center;\n color: var(--vectis-color-text-muted);\n }\n\n .v-data-table-state-loading {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n }\n\n .v-data-table-footer {\n flex: none;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-4);\n padding-block-start: var(--vectis-space-3);\n padding-block-end: var(--data-table-frame-pad);\n padding-inline: var(--data-table-frame-pad);\n }\n\n /*\n * The right-hand zone is pushed to the edge by an automatic margin rather than by an\n * alignment on the footer. The two zones are siblings in the same row, and the count on the\n * left has to stay at the OPPOSITE edge: any alignment set on the footer would move both of\n * them together.\n */\n .v-data-table-footer-end {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-4);\n margin-inline-start: auto;\n }\n\n .v-data-table-selection,\n .v-data-table-range,\n .v-data-table-per-page-label {\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text-muted);\n }\n\n .v-data-table-per-page {\n display: flex;\n align-items: center;\n gap: var(--vectis-space-2);\n }\n\n /*\n * The stacked form: once the COMPONENT is narrow, each row becomes a card carrying its own\n * headings. It is entirely CSS, and the threshold is written as a literal length; these\n * queries accept no variables.\n */\n @container (max-width: 640px) {\n .v-data-table[data-responsive='stack'] .v-data-table-head {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n }\n\n /*\n * The heading row is out of sight, so its CONTROLS leave the tab order with it: a sort\n * button or a \"select all\" box the keyboard lands on and nobody can see. `visibility`\n * is what takes a control out of the tab order in CSS, and it takes it out of the\n * accessibility tree too, so a sortable heading hands its name to a plain text copy.\n */\n .v-data-table[data-responsive='stack'] .v-data-table-head :is(.v-data-table-sort, .v-checkbox) {\n visibility: hidden;\n }\n\n .v-data-table[data-responsive='stack'] .v-data-table-stack-label {\n display: inline;\n }\n\n .v-data-table[data-responsive='stack'] tbody tr {\n display: block;\n padding-block: var(--vectis-space-2);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-data-table[data-responsive='stack'] tbody tr:last-child {\n border-block-end: none;\n }\n\n .v-data-table[data-responsive='stack'] td {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: var(--vectis-space-4);\n padding: var(--vectis-space-1) var(--vectis-space-2);\n border: none;\n text-align: end;\n }\n\n /* Each cell writes its own column's name before itself, taken from the attribute the\n template put there. It takes the overline type role, without forcing capitals. */\n .v-data-table[data-responsive='stack'] td::before {\n content: attr(data-label);\n font-size: var(--vectis-text-overline-size);\n font-weight: var(--vectis-text-overline-weight);\n letter-spacing: var(--vectis-text-overline-tracking);\n color: var(--vectis-color-text-muted);\n }\n\n /*\n * The checkbox cell carries no column name; there is none; so it gets no heading and opens\n * the card on its own line.\n */\n .v-data-table[data-responsive='stack'] td.v-data-table-select {\n justify-content: flex-start;\n }\n\n .v-data-table[data-responsive='stack'] td.v-data-table-select::before {\n content: none;\n }\n\n /* In a narrow component the search field drops under the title and takes the whole\n width. This applies to both responsive forms, the toolbar never being part of what\n scrolls. */\n .v-data-table-search {\n flex: 1 1 100%;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8OA,IAAM,IAAQ,GAgCR,IAAI,EAAY,GAChB,IAAe,EAAU,GACzB,KAAoB,QAAe,EAAM,aAAa,EAAE,MAAM,UAAU,KAAK,GAC7E,IAAsB,QAAe,EAAM,eAAe,EAAE,MAAM,UAAU,OAAO,GACnF,KAA4B,QAC1B,EAAM,qBAAqB,EAAE,MAAM,UAAU,iBACrD,GACM,KAAsB,QAAe,EAAM,eAAe,EAAE,MAAM,UAAU,WAAW,GACvF,IAAsB,QAAe,EAAM,eAAe,EAAE,MAAM,UAAU,OAAO,GACnF,IAAyB,QAAe,EAAM,kBAAkB,EAAE,MAAM,UAAU,SAAS,GAG3F,IAAO,EAAiC,GAAC,MAAyB,GAElE,IAAO,EAAmB,GAAC,MAAsB,GAKjD,IAAU,EAA+B,GAAC,SAAiC,GAQ3E,IAAW,EAA6B,GAAC,UAAiC,GAM1E,IAAS,EAAmB,GAAC,QAAyB,GAEtD,KAAO,IA4BP,EAAE,UAAO,eAAW,eAAW,uBAAmB,GAAa,GAC/D,KAAQ,GAAS,GAIjB,IAAU,EAA6B,IAAI;EACjD,EAAa,EAEX,IAAI,EACN,CAAC;EAQD,IAAM,IAAU,GAAM,GAChB,KAAkB,QACtB,EAAM,WACN,EAAE,EAAM,SAAS,GAAM,UACvB,EAAM,kBAAkB,KAAA,KACxB,EAAM,uBAAuB,KAAA,IACzB,KAAA,IACA,CACN;EAGA,IAAI,GAAO;GACT,AAAI,EAAM,cAAc,CAAC,EAAM,UAC7B,QAAQ,KACN,yHACF;GAMF,IAAM,oBAAS,IAAI,IAAY;GAC/B,SAAkB;IAChB,IAAM,IAAM,EAAM;IAClB,IAAI,CAAC,GAAK;IACV,IAAM,oBAAO,IAAI,IAAa;IAC9B,KAAK,IAAM,KAAO,EAAM,MAAM;KAC5B,IAAM,IAAQ,EAAI;KAClB,AAAI,OAAO,KAAU,YAAY,OAAO,KAAU,WAC3C,EAAO,IAAI,MAAM,MACpB,EAAO,IAAI,MAAM,GACjB,QAAQ,KACN,4BAA4B,EAAI,yFAClC,KAEO,EAAK,IAAI,CAAK,IAClB,EAAO,IAAI,WAAW,MACzB,EAAO,IAAI,WAAW,GACtB,QAAQ,KACN,4BAA4B,EAAI,qBAAqB,EAAM,0DAC7D,KAEG,EAAK,IAAI,CAAK;IACvB;GACF,CAAC;EACH;EAQA,SAAS,GAAY,GAAU,GAA+B;GAC5D,IAAI,CAAC,EAAM,QAAQ,OAAO;GAC1B,IAAM,IAAM,EAAI,EAAM;GACtB,OAAO,OAAO,KAAQ,YAAY,OAAO,KAAQ,WAAW,IAAM,OAAO,CAAG;EAC9E;EAQA,IAAM,KAAe,GAA2B,GAC1C,MAAkB,GAAU,MAAgB,GAAa,GAAK,OAAO,EAAI,MAAQ,EAAE,GAAG,CAAG,GAEzF,IAAe,QAAe;GAClC,IAAI,EAAM,cAAc,CAAC,EAAM,YAAY,OAAO,EAAM;GACxD,IAAM,IAAS,IAAe,EAAO,SAAS,GAAA,CAAI,KAAK,CAAC;GAExD,OADK,IACE,EAAM,KAAK,QAAQ,MACxB,EAAM,QAAQ,MAAM,MAAW,GAAe,GAAK,EAAO,GAAG,CAAC,CAAC,SAAS,CAAM,CAAC,CACjF,IAHoB,EAAM;EAI5B,CAAC,GAMK,KAAW,QAAe,IAAI,KAAK,SAAS,EAAa,OAAO,EAAE,SAAS,GAAK,CAAC,CAAC,GAMlF,KAAe;EACrB,SAAS,EAAQ,GAAyB;GAGxC,OAFI,aAAiB,OAAa,EAAM,QAAQ,IAC5C,OAAO,KAAU,YAAY,GAAa,KAAK,CAAK,IAAU,OAAO,CAAK,IACvE;EACT;EAEA,IAAM,IAAa,QAAe;GAChC,IAAM,IAAU,EAAK;GACrB,IAAI,CAAC,KAAW,EAAM,YAAY,OAAO,EAAa;GACtD,IAAM,IAAS,EAAQ,cAAc,QAAQ,IAAI,IAC3C,IAAU,GAAS,MAAM;GAC/B,OAAO,CAAC,GAAG,EAAa,KAAK,CAAC,CAAC,MAAM,GAAG,MAAM;IAC5C,IAAM,IAAK,EAAQ,EAAE,EAAQ,IAAI,GAC3B,IAAK,EAAQ,EAAE,EAAQ,IAAI;IAEjC,OADI,OAAO,KAAO,YAAY,OAAO,KAAO,YAAkB,IAAK,KAAM,IAClE,EAAQ,OAAO,KAAM,EAAE,GAAG,OAAO,KAAM,EAAE,CAAC,IAAI;GACvD,CAAC;EACH,CAAC,GAEK,IAAY,SAAgB,EAAQ,SAAS,KAAK,CAAC,GACnD,IAAa,QACjB,EAAM,aAAc,EAAM,SAAS,EAAM,KAAK,SAAU,EAAa,MAAM,MAC7E,GACM,IAAY,QAChB,EAAU,QAAQ,KAAK,IAAI,GAAG,KAAK,KAAK,EAAW,SAAS,EAAQ,SAAS,EAAE,CAAC,IAAI,CACtF,GAKM,IAAc,QAAe,GAAM,EAAK,OAAO,GAAG,EAAU,KAAK,CAAC,GAElE,IAAgB,QAAe;GAGnC,IAAI,EAAM,cAAc,CAAC,EAAU,OAAO,OAAO,EAAW;GAC5D,IAAM,KAAS,EAAY,QAAQ,MAAM,EAAQ,SAAS;GAC1D,OAAO,EAAW,MAAM,MAAM,GAAO,KAAS,EAAQ,SAAS,EAAE;EACnE,CAAC;EAED,SAAS,GAAW,GAAa;GAC/B,IAAM,IAAU,EAAK;GACrB,AAAI,CAAC,KAAW,EAAQ,QAAQ,IAAK,EAAK,QAAQ;IAAE;IAAK,WAAW;GAAM,IACjE,EAAQ,cAAc,QAAO,EAAK,QAAQ;IAAE;IAAK,WAAW;GAAO,IACvE,EAAK,QAAQ;EACpB;EAKA,SAAS,GAAS,GAAiE;GAC7E,MAAK,OAAO,QAAQ,EAAO,KAC/B,OAAO,EAAK,MAAM,cAAc,QAAQ,cAAc;EACxD;EAUA,SAAS,GAAY,GAAqC;GAExD,OADI,EAAK,OAAO,QAAQ,EAAO,MACxB,EAAK,MAAM,cAAc,QAAQ,EAAM,cAAc,EAAM,eADvB,EAAM;EAEnD;EAMA,IAAM,IAAkB,EAAI,EAAO,KAAK,GAClC,KAAc,GAAS;EAE7B,SAAS,KAAe;GAGlB,EAAO,UAAU,EAAgB,UACrC,EAAgB,QAAQ,EAAO,OAC/B,EAAK,QAAQ;EACf;EAEA,EAAM,SAAc;GAClB,IAAI,CAAC,EAAM,YAAY;IACrB,EAAK,QAAQ;IACb;GACF;GACA,GAAY,MAAM,IAAc,EAAM,cAAc;EACtD,CAAC;EAED,IAAM,KAAc,QAAgB,EAAM,aAAa,EAAgB,QAAQ,EAAO,KAAM;EAY5F,EACE;GACE;SACM,EAAQ,SAAS;SACjB,EAAK,OAAO,OAAO;SACnB,EAAK,OAAO,aAAa;GAC/B;EACF,IACC,CAAC,GAAM,GAAS,GAAS,GAAe,OAAY;GACnD,AAAI,EAAM,cAAY,GAAK,iBAAiB;IAAE;IAAM;IAAS;IAAS;IAAe;GAAO,CAAC;EAC/F,CACF;EAIA,IAAM,IAAc,QAAe,IAAI,IAAoB,EAAS,KAAK,CAAC,GACpE,IAAa,QAAe,EAAc,MAAM,KAAK,GAAK,MAAU,GAAY,GAAK,CAAK,CAAC,CAAC,GAC5F,IAAqB,QACnB,EAAW,MAAM,SAAS,KAAK,EAAW,MAAM,OAAO,MAAO,EAAY,MAAM,IAAI,CAAE,CAAC,CAC/F,GACM,KAAsB,QACpB,CAAC,EAAmB,SAAS,EAAW,MAAM,MAAM,MAAO,EAAY,MAAM,IAAI,CAAE,CAAC,CAC5F,GAOM,MAAc,MAAuB,EAAY,MAAM,IAAI,CAAE;EAEnE,SAAS,GAAU,GAAoB;GACrC,EAAS,QAAQ,GAAY,EAAS,OAAO,CAAE;EACjD;EAOA,IAAM,KAAiB,QAAe,EAAW,MAAM,WAAW,KAAK,EAAM,OAAO;EAEpF,SAAS,KAAe;GACtB,IAAI,GAAe,OAAO;GAC1B,IAAM,IAAM,EAAW;GACvB,IAAI,EAAmB,OAAO;IAC5B,IAAM,IAAU,IAAI,IAAI,CAAG;IAC3B,EAAS,QAAQ,EAAS,MAAM,QAAQ,MAAO,CAAC,EAAQ,IAAI,CAAE,CAAC;GACjE,OAAO;IACL,IAAM,IAAU,EAAY;IAC5B,EAAS,QAAQ,CAAC,GAAG,EAAS,OAAO,GAAG,EAAI,QAAQ,MAAO,CAAC,EAAQ,IAAI,CAAE,CAAC,CAAC;GAC9E;EACF;EAKA,SAAS,GAAe,GAAU,GAAuB;GAGvD,IAAM,KAAY,EAAU,SAAS,EAAY,QAAQ,MAAM,EAAQ,SAAS,KAAK,KAAK;GAC1F,OAAO,EAAM,iBAAiB,GAAK,CAAQ,KAAK,EAAE,MAAM,UAAU,UAAU,IAAW,CAAC;EAC1F;EAEA,EAAM,SAAe;GACnB,EAAK,QAAQ;EACf,CAAC;EAED,IAAM,KAAW,QAAe,EAAM,QAAQ,SAAU,KAAM,UAAmB,GAQ3E,KAAmB,QAAe;GACtC,IAAM,IAAQ,EAAS,MAAM;GAG7B,OAFI,MAAU,IAAU,KACpB,EAAM,gBAAsB,EAAM,cAAc,CAAK,IAClD,EAAE,MAAM,UAAU,UAAU,CAAK;EAC1C,CAAC,GAEK,KAAe,QAAe;GAClC,IAAM,IAAM,EAAQ,SAAS,GACvB,IAAQ,EAAW,OACnB,IAAQ,MAAU,IAAI,KAAK,EAAY,QAAQ,KAAK,IAAM,GAC1D,IAAM,KAAK,IAAI,EAAY,QAAQ,GAAK,CAAK;GACnD,OAAO,EAAM,YAAY;IAAE;IAAO;IAAK;GAAM,CAAC,KAAK,EAAE,MAAM,UAAU,MAAM;IAAE;IAAO;IAAK;GAAM,CAAC;EAClG,CAAC,GAKK,KAAc,QAClB,EAAM,WAAW,KAAA,IAAmD,KAAA,IAAvC,EAAE,WAAW,EAAQ,EAAM,MAAM,EAAE,CAClE;yBAIE,EA8MM,OAAA;GA7MJ,OAAK,GAAA,CAAC,gBACE,EAAA,EAAA,CAAS,CAAA;GAChB,OAAK,GAAA,CAAG,GAAA,OAAa,EAAA,EAAA,CAAS,CAAA;GAC9B,gBAAc,EAAA;GACd,mBAAiB,EAAA;GACjB,gBAAc,EAAA,UAAO,KAAQ,KAAA;GAC7B,gBAAc,EAAA,UAAO,KAAQ,KAAA;GAC7B,sBAAoB,EAAA,eAAY,KAAQ,KAAA;GACxC,mBAAiB,EAAA,aAAU,KAAQ,KAAA;;GAEzB,EAAA,SAASA,EAAAA,OAAO,SAAS,EAAA,cAAA,EAAA,GAApC,EAgBM,OAhBN,IAgBM,CAfJ,EAEc,GAAA;IAFA,IAAI,EAAA,CAAA;IAAS,IAAG;IAAM,SAAQ;IAAY,OAAM;;qBACvB,CAArC,EAAqC,EAAA,QAAA,SAAA,CAAA,SAAA,CAAA,EAAA,EAAf,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;kBAGrB,EAAA,cAAA,EAAA,GADR,EAWE,IAAA;;gBATS,EAAA;6CAAM,QAAA;IACf,OAAM;IACN,MAAK;IACL,MAAK;IACJ,SAAS,EAAA;IACT,cAAY,EAAA,EAAA;IACb,WAAA;IACC,aAAa,GAAA;IACb,cAAY,GAAA;;;;;;;;GAIjB,EAsHM,OAtHN,IAsHM,CArHJ,EAoHQ,SApHR,EAoHQ;aAnHF;IAAJ,KAAI;IACJ,OAAM;IACL,mBAAiB,GAAA;MACV,EAAA,EAAA,CAAc,GAAA;IAEP,EAAA,WAAA,EAAA,GAAf,EAIU,WAJV,IAIU,EAFN,EAAA,OAAO,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;IAGX,EA8CQ,SA9CR,IA8CQ,CA7CN,EA4CK,MAAA,MAAA,CA3CO,EAAA,cAAA,EAAA,GAAV,EAaK,MAbL,IAaK,CAZH,EAME,GAAA;KALC,eAAa,EAAA;KACb,eAAe,GAAA;KACf,UAAU,GAAA;KACV,cAAY,EAAA;KACZ,uBAAoB;;;;;;QAMvB,EAA0E,QAA1E,IAA0E,EAAhC,EAAA,KAAsB,GAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,IAAA,EAAA,EAAA,GAElE,EA4BK,GAAA,MAAA,EA3Bc,EAAA,UAAV,YADT,EA4BK,MAAA;KA1BF,KAAK,EAAO;KACb,OAAM;KACL,cAAY,EAAO;KACnB,aAAW,GAAS,CAAM;QAGnB,EAAO,YAAA,EAAA,GADf,EAYS,UAAA;;KAVP,MAAK;KACL,OAAM;KACL,kBAAgB,EAAA,OAAM,QAAQ,EAAO,MAAM,EAAA,MAAK,YAAY,KAAA;KAC5D,UAAK,MAAE,GAAW,EAAO,GAAG;QAE7B,EAA6E,EAAA,QAAA,QAAxD,EAAO,OAAG,EAAa,UAAM,SAA2B,CAAA,EAAA,EAAtB,EAAO,KAAK,GAAA,CAAA,CAAA,CAAA,GAInE,EAAgF,GAAhF,EAAgF,EAAzE,OAAM,yBAAwB,GAAA,EAAA,SAAA,GAAA,GAAS,EAAA,CAAA,CAAS,CAAC,GAAY,CAAM,CAAA,CAAA,GAAA,MAAA,EAAA,CAAA,GAAA,GAAA,EAAA,KAAA,EAAA,IAAA,EAAA,GAIhE,EAAO,YAAA,EAAA,GAAnB,EAES,QAFT,IAES,EADP,EAAO,KAAK,GAAA,CAAA,KAGZ,EAA6E,EAAA,QAAA,QAAxD,EAAO,OAAG,EAAa,UAAM,SAA2B,CAAA,EAAA,EAAtB,EAAO,KAAK,GAAA,CAAA,CAAA,GAAA,KAAA,GAAA,CAAA,CAAA,GAAA,GAAA,EAAA;IAK3E,EAyDQ,SAAA,MAAA,CApDI,EAAA,WAAA,EAAA,GAAV,EAWK,MAAA,IAAA,CAVH,EASK,MAAA;KATA,SAAS,GAAA;KAAU,OAAM;QAC5B,EAOO,EAAA,QAAA,WAAA,CAAA,SAAA,CAJL,EAGO,QAHP,IAGO,CAFL,EAAyC,IAAA,EAA9B,OAAO,EAAA,MAAmB,GAAA,MAAA,GAAA,CAAA,OAAA,CAAA,GACrC,EAAyD,QAAzD,IAAyD,EAA7B,EAAA,KAAmB,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAAA,GAAA,EAAA,CAAA,CAAA,KAKxC,EAAA,MAAc,WAAM,KAAA,EAAA,GAAnC,EAIK,MAAA,IAAA,CAHH,EAEK,MAAA;KAFA,SAAS,GAAA;KAAU,OAAM;QAC5B,EAAuE,EAAA,QAAA,SAAA,EAAnD,QAAQ,GAAA,MAAW,SAAgC,CAAA,EAAA,EAA3B,GAAA,KAAiB,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,GAAA,EAAA,CAAA,CAAA,MAAA,EAAA,EAAA,GAI/D,EAgCK,GAAA,EAAA,KAAA,EAAA,GAAA,EA/BoB,EAAA,QAAf,GAAK,YADf,EAgCK,MAAA;KA9BF,KAAK,EAAA,MAAW;KAChB,iBAAe,EAAA,cAAc,GAAW,EAAA,MAAW,EAAK,IAAA,KAAW,KAAA;QAO1D,EAAA,cAAA,EAAA,GAAV,EAMK,MANL,IAMK,CALH,EAIE,GAAA;KAHC,eAAa,GAAW,EAAA,MAAW,EAAK;KACxC,cAAY,GAAe,GAAK,CAAK;KACrC,wBAAkB,MAAE,GAAU,EAAA,MAAW,EAAK;;;;;+BAGnD,EAcK,GAAA,MAAA,EAbc,EAAA,UAAV,YADT,EAcK,MAAA;KAZF,KAAK,EAAO;KACZ,cAAY,EAAO;KACnB,cAAY,EAAO;QAEpB,EAOO,EAAA,QAAA,QANU,EAAO,OAAG;KACnB;KACL,OAAO,EAAI,EAAO;KACV;aAGJ,CAAA,EAAA,EADF,EAAI,EAAO,IAAG,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,GAAA,EAAA;;GAWpB,EAAA,SAAa,EAAA,cAAA,EAAA,GAAxB,EAsDM,OAtDN,IAsDM,CArDQ,EAAA,cAAA,EAAA,GAAZ,EAES,QAFT,IAES,EADP,GAAA,KAAgB,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAEP,EAAA,SAAA,EAAA,GAAX,EAiDM,OAjDN,IAiDM;IAhDO,EAAA,gBAAgB,UAAA,EAAA,GAA3B,EA8BM,OA9BN,IA8BM,CA7BJ,EAES,QAFT,IAES,EADP,EAAA,KAAmB,GAAA,CAAA,GAMrB,EAqBQ,IAAA;KArBD,MAAK;KAAM,SAAS,EAAA;KAAS,WAAU;KAAU,iBAAA;;KAC3C,SAAO,GAWN,EAXU,sBAAY,CAChC,EAUU,IAVV,EAUU;MATR,SAAQ;MACR,MAAK;MACL,MAAK;MACJ,SAAS,EAAA;QACF,GAAY,EACnB,cAAY,EAAA,CAAA,CAAC,CAAC,UAAU,aAAa,EAAA,OAAqB,EAAA,SAAO,CAAA,EAAA,CAAA,GAAA;uBAErD,CAAA,EAAA,EAAV,EAAA,KAAO,IAAG,KACb,CAAA,GAAA,EAAmC,GAAA,EAA3B,MAAM,EAAA,EAAA,EAAiB,GAAA,MAAA,GAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;;sBAID,EAAA,EAAA,EAAA,GADlC,EAME,GAAA,MAAA,EALiB,EAAA,iBAAV,YADT,EAME,IAAA;MAJC,KAAK;MACL,OAAO,OAAO,CAAM;MACpB,UAAU,MAAW,EAAA;MACrB,WAAM,MAAE,EAAA,QAAU;;;;;;;;IAIb,EAAA,aAAA,EAAA,GAAZ,EAES,QAFT,IAES,EADP,GAAA,KAAY,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;IAOd,EAQE,IAAA;iBAPS,EAAA;8CAAI,QAAA;KACZ,OAAO,EAAA,CAAA,CAAC,CAAC,UAAU;KACnB,QAAQ,EAAA;KACT,MAAK;KACJ,SAAS,EAAA;KACV,OAAM;KACL,iBAAe"}
|
|
@@ -44,9 +44,8 @@ interface DateInputProps {
|
|
|
44
44
|
*/
|
|
45
45
|
mode?: DateInputMode;
|
|
46
46
|
/**
|
|
47
|
-
* Offers the date picker alongside a field that can be typed into: an icon at the end of
|
|
48
|
-
*
|
|
49
|
-
* the calendar is already the only way to choose.
|
|
47
|
+
* Offers the date picker alongside a field that can be typed into: an icon at the end of the
|
|
48
|
+
* field, and a panel that opens on focus.
|
|
50
49
|
*/
|
|
51
50
|
showPicker?: boolean;
|
|
52
51
|
/** The label above the field. */
|
|
@@ -62,10 +61,8 @@ interface DateInputProps {
|
|
|
62
61
|
/** Makes the field unusable, greyed out through the colour tokens. */
|
|
63
62
|
disabled?: boolean;
|
|
64
63
|
/**
|
|
65
|
-
* Shows the date without letting it be changed: nothing can be typed, there is no
|
|
66
|
-
*
|
|
67
|
-
* which is what separates it from `disabled`, and it is a different question from
|
|
68
|
-
* `mode`, which says how a field that CAN be changed is filled in.
|
|
64
|
+
* Shows the date without letting it be changed: nothing can be typed, there is no calendar
|
|
65
|
+
* and no clear cross.
|
|
69
66
|
*/
|
|
70
67
|
readonly?: boolean;
|
|
71
68
|
/** Marks the field as invalid, for a rule of your own. */
|
|
@@ -105,9 +102,9 @@ interface DateInputProps {
|
|
|
105
102
|
*/
|
|
106
103
|
pickerIcon?: IconSource;
|
|
107
104
|
/**
|
|
108
|
-
* How the date is
|
|
109
|
-
*
|
|
110
|
-
*
|
|
105
|
+
* How the date is written OUT in the field. It has no effect on a field being typed into,
|
|
106
|
+
* which necessarily shows the numeric form one types, so it concerns the `picker` mode, and
|
|
107
|
+
* the period and list selections, which fall back to it.
|
|
111
108
|
*/
|
|
112
109
|
displayFormat?: Intl.DateTimeFormatOptions;
|
|
113
110
|
/** Where the panel opens relative to the field. */
|
|
@@ -122,27 +119,21 @@ type __VLS_Slots = {
|
|
|
122
119
|
start?(): unknown;
|
|
123
120
|
/**
|
|
124
121
|
* Controls of your own inside the field, placed before the ones the field owns: the clear
|
|
125
|
-
* cross and the icon that opens the panel.
|
|
126
|
-
* which is why there is no `#end` here: it would replace them.
|
|
122
|
+
* cross and the icon that opens the panel.
|
|
127
123
|
*/
|
|
128
124
|
'value-end'?(): unknown;
|
|
129
125
|
/** What a day cell shows, handed straight to the calendar. */
|
|
130
126
|
day?(props: DatePickerDaySlotProps): unknown;
|
|
131
127
|
/**
|
|
132
|
-
* The strip at the foot of the panel: actions, or preset dates such as "today". It
|
|
133
|
-
*
|
|
128
|
+
* The strip at the foot of the panel: actions, or preset dates such as "today". It receives
|
|
129
|
+
* `close`, which lets one of those buttons dismiss the panel.
|
|
134
130
|
*/
|
|
135
131
|
footer?(props: DateInputFooterSlotProps): unknown;
|
|
136
132
|
};
|
|
137
133
|
type __VLS_ModelProps = {
|
|
138
134
|
/**
|
|
139
|
-
* The date or dates chosen, in the shape `selection` calls for: an ISO `YYYY-MM-DD` string
|
|
140
|
-
*
|
|
141
|
-
* selected to begin with.
|
|
142
|
-
*
|
|
143
|
-
* While the reader types, it is only written once what they have entered is a complete and
|
|
144
|
-
* acceptable date; an unfinished or refused entry leaves it untouched and is reverted when
|
|
145
|
-
* they leave the field.
|
|
135
|
+
* The date or dates chosen, in the shape `selection` calls for: an ISO `YYYY-MM-DD` string for
|
|
136
|
+
* `single`, a `{ start, end }` pair for `range`, an array for `multiple`.
|
|
146
137
|
*/
|
|
147
138
|
modelValue?: DatePickerValue;
|
|
148
139
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VDateInput.js","names":[],"sources":["../../../src/components/VDateInput/VDateInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A field for choosing a date: a VInput, and a VDatePicker in a panel below it.\n *\n * The panel is `mode=\"manual\"` and driven imperatively, which is not a detail — `popovertarget`\n * is invalid on a text `<input>`, and opening it ourselves is also what lets focus be moved\n * INTO the grid so the keyboard lands on the dates. Closing is ours too: `focusout`, or Escape.\n *\n * Two FIELD modes, `mode`: `input` (the default) types digits only, the separators appearing\n * as they go in the order and punctuation of the reader's locale; `picker` makes the calendar\n * the only way in. What the field is FOR is a different question from the `readonly` prop,\n * which freezes whatever it holds by every route at once.\n *\n * That is a different question from WHAT is chosen — one date, a range, or several — which is\n * `selection`, passed straight through to the picker.\n */\n\nimport { computed, inject, provide, ref, watchEffect } from 'vue'\n\nimport { inputGroupKey } from '../VInput/context'\n\nimport VDatePicker from '../VDatePicker/VDatePicker.vue'\nimport type {\n DatePickerDaySlotProps,\n DatePickerEvent,\n DatePickerSelection,\n DatePickerValue,\n DatePickerMatcher,\n DatePickerRange,\n} from '../VDatePicker/VDatePicker.vue'\nimport {\n compareISO,\n formatDateDisplay,\n formatDisplayRange,\n isDateAllowed,\n isValidISO,\n} from '../../utils/date'\nimport {\n caretAfterDigits,\n dateMaskFor,\n formatDateMask,\n isoToMask,\n maskPlaceholder,\n parseDateMask,\n} from './mask'\nimport { digitsOf } from '../../utils/text'\nimport { resolveMatcher } from '../../utils/matcher'\nimport { isDev } from '../../utils/env'\nimport { hostWarnsKey } from '../../utils/hostWarns'\nimport { calendar_today as calendarTodayIcon } from '../VIcon/icons/calendar_today'\nimport type { IconSource } from '../VIcon/types'\nimport VInput from '../VInput/VInput.vue'\nimport VPopover from '../VPopover/VPopover.vue'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\nimport { useFieldPanel } from '../../composables/useFieldPanel'\nimport { canClear } from '../../composables/useClearable'\nimport { iconStartListener } from '../../composables/useIconClickHandlers'\nimport { useMaskedField } from '../../composables/useMaskedField'\nimport { useMessages, useResolvedLocale } from '../../i18n/state'\n\n/** Where the calendar opens relative to the field. */\nexport type DateInputPlacement =\n 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end'\n\n/** Whether the field can be typed into, or only filled from the calendar. */\nexport type DateInputMode = 'picker' | 'input'\n\n/** What the `#footer` slot receives. */\nexport interface DateInputFooterSlotProps {\n /** Closes the panel and hands the focus back to the field. */\n close: () => void\n}\n\nconst MODES: DateInputMode[] = ['picker', 'input']\n\n/**\n * The century a two-digit year is expanded into — \"10/06/26\" becomes 2026 — applied only\n * when the reader leaves the field.\n *\n * It is FIXED and never derived from the current year. A sliding rule would make the\n * component's behaviour depend on when it runs, which is the same objection that keeps\n * today's date out of VDatePicker's setup, and it would give the tests a shelf life.\n */\nconst YEAR_PIVOT = 2000\n\nconst DEFAULT_DISPLAY_FORMAT: Intl.DateTimeFormatOptions = {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n}\n\n/** The height of the field: 32, 40 or 48 pixels. */\nexport type DateInputSize = 'sm' | 'md' | 'lg'\n\ninterface DateInputProps {\n // Everything from here to `events` is handed straight to the calendar.\n /** What is being chosen: one date, a period between two, or several separate dates. */\n selection?: DatePickerSelection\n /**\n * A BCP 47 locale, which decides the month and day names, the first day of the week and\n * the order the field is typed in. It TAKES PRECEDENCE over the design system's global\n * locale and falls back to it, which is why it has no literal default: `undefined` has\n * to stay recognizable for the global locale to have its chance.\n */\n locale?: string\n /** Forces the day the weeks start on, from 0 for Sunday to 6 for Saturday. */\n firstDayOfWeek?: number\n /** The earliest date that can be chosen, as an ISO string. */\n min?: string\n /** The latest date that can be chosen, as an ISO string. */\n max?: string\n /** Dates that cannot be chosen, as a list or as a function. */\n disabledDates?: DatePickerMatcher\n /** Fills the corners of the grid with the greyed days of the neighbouring months. */\n showAdjacentDays?: boolean\n /** Lets those neighbouring days be clicked, which implies showing them. */\n selectAdjacentDays?: boolean\n /** Events to mark under the days they fall on. */\n events?: DatePickerEvent[]\n // From here on: the field.\n /**\n * Whether the field can be TYPED into (the default, using the numeric form of the\n * reader's language) or is filled from the calendar alone, which is `picker`.\n *\n * Typing is reserved for choosing a SINGLE date: a period or a list falls back to\n * `picker`, there being no sensible way to type either.\n */\n mode?: DateInputMode\n /**\n * Offers the date picker alongside a field that can be typed into: an icon at the end of\n * the field, and a panel that opens on focus. It means nothing in `picker` mode, where\n * the calendar is already the only way to choose.\n */\n showPicker?: boolean\n /** The label above the field. */\n label?: string\n /** A line of help under the field. */\n hint?: string\n /** What the field says while empty. */\n placeholder?: string\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: DateInputSize\n /** Takes 4px off the height. */\n compact?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the date without letting it be changed: nothing can be typed, there is no\n * calendar and no clear cross. The field stays focusable and can be copied from,\n * which is what separates it from `disabled`, and it is a different question from\n * `mode`, which says how a field that CAN be changed is filled in.\n */\n readonly?: boolean\n /** Marks the field as invalid, for a rule of your own. */\n invalid?: boolean\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes a\n * real button as soon as a `@click:icon-start` listener is attached, in which case it\n * needs `iconStartLabel`.\n */\n iconStart?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /**\n * What the calendar button does, in words. It names the button `pickerIcon` renders,\n * and falls back to the design system dictionary.\n */\n pickerIconLabel?: string\n /**\n * Shows a spinner at the end of the field, in place of the calendar icon. It says\n * that something is being loaded and changes nothing else: the field can still be\n * typed into and the panel still opens.\n */\n loading?: boolean\n /**\n * What screen readers announce while the spinner turns. It falls back to the design\n * system dictionary.\n */\n loadingText?: string\n /** Offers a cross that empties the value, shown before the end icon. */\n clearable?: boolean\n /** What that cross does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The icon that opens the date picker, at the end of the field. The clear cross appears\n * to its left rather than in its place, and no icon is rendered at all when there is no\n * panel to open.\n */\n pickerIcon?: IconSource\n /**\n * How the date is WRITTEN OUT in the field. It has no effect on a field being typed\n * into, which necessarily shows the numeric form one types, so it concerns the\n * `picker` mode, and the period and list selections, which fall back to it.\n */\n displayFormat?: Intl.DateTimeFormatOptions\n /** Where the panel opens relative to the field. */\n placement?: DateInputPlacement\n}\n\nconst props = withDefaults(defineProps<DateInputProps>(), {\n selection: 'single',\n locale: undefined,\n firstDayOfWeek: undefined,\n min: undefined,\n max: undefined,\n disabledDates: undefined,\n showAdjacentDays: false,\n selectAdjacentDays: false,\n events: undefined,\n // Deliberately left undefined rather than defaulted to the typed mode: that is what\n // distinguishes \"the prop was not given\" from \"the prop was given this value\", and\n // therefore what allows warning ONLY the consumer who explicitly asked for something\n // that cannot work.\n mode: undefined,\n showPicker: false,\n label: undefined,\n hint: undefined,\n placeholder: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n iconStart: undefined,\n iconStartLabel: undefined,\n pickerIconLabel: undefined,\n loading: false,\n loadingText: undefined,\n clearable: false,\n clearLabel: undefined,\n pickerIcon: () => calendarTodayIcon,\n // Left undefined rather than defaulted to the format object, for the same reason: it is\n // what lets the component notice that a consumer has asked for a display format in a\n // mode where it has no effect, and say so.\n displayFormat: undefined,\n placement: 'bottom-start',\n})\n\n/**\n * The date or dates chosen, in the shape `selection` calls for: an ISO `YYYY-MM-DD` string\n * for `single`, a `{ start, end }` pair for `range`, an array for `multiple`. Nothing is\n * selected to begin with.\n *\n * While the reader types, it is only written once what they have entered is a complete and\n * acceptable date; an unfinished or refused entry leaves it untouched and is reverted when\n * they leave the field.\n */\nconst model = defineModel<DatePickerValue>({ default: null })\n\nconst emit = defineEmits<{\n /** The clear cross emptied the field. The value has already been reset. */\n clear: []\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its place,\n * as in VInput.\n */\n start?(): unknown\n /**\n * Controls of your own inside the field, placed before the ones the field owns: the clear\n * cross and the icon that opens the panel. Those two are this component's own affordance,\n * which is why there is no `#end` here: it would replace them.\n */\n 'value-end'?(): unknown\n /** What a day cell shows, handed straight to the calendar. */\n day?(props: DatePickerDaySlotProps): unknown\n /**\n * The strip at the foot of the panel: actions, or preset dates such as \"today\". It\n * receives `close`, which is what lets one of those buttons dismiss the panel.\n */\n footer?(props: DateInputFooterSlotProps): unknown\n}>()\n\n// `class` and `style` stay on the wrapper; everything else goes down to the text field,\n// which is what a consumer's label points at and what assistive technology deals with.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\n\n// Declared as this component's own event, `click:icon-start` is out of `$attrs`, so the\n// listener is relayed to the field by hand — and only when the consumer wrote one.\nconst iconStartClick = iconStartListener((event) => emit('click:icon-start', event))\n\n/** What reaches the field: the consumer's own attributes, plus that listener. */\nconst fieldAttrs = computed(() => ({ ...forwardedAttrs.value, ...iconStartClick }))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelRef = ref<InstanceType<typeof VPopover> | null>(null)\nconst inputRef = ref<InstanceType<typeof VInput> | null>(null)\nconst pickerRef = ref<InstanceType<typeof VDatePicker> | null>(null)\n\n/** The real input inside the field, which the mask and the caret work on. */\nconst fieldEl = computed<HTMLInputElement | null>(() => inputRef.value?.el ?? null)\n\n/** What was asked for, falling back to the default when nothing or nonsense was given. */\nconst requestedMode = computed<DateInputMode>(() =>\n props.mode !== undefined && MODES.includes(props.mode) ? props.mode : 'input',\n)\n/**\n * What is actually used. Typing is only possible for a single date: a period or a list\n * has no form one could type into a single field.\n */\nconst resolvedMode = computed<DateInputMode>(() =>\n requestedMode.value === 'input' && props.selection === 'single' ? 'input' : 'picker',\n)\nconst typing = computed(() => resolvedMode.value === 'input')\n\n/**\n * Whether there is a panel at all. The calendar is only optional beside a field one can\n * type into: in `picker` mode it is the only way to choose anything, and a frozen field\n * has none at all.\n */\nconst hasPanel = computed(() => !props.readonly && (!typing.value || props.showPicker))\n\n// @devwarn\nif (isDev) {\n // The calendar receives `min` and `max` as they stand, so what it would say about them is\n // said here, under the name the consumer wrote (utils/hostWarns).\n provide(hostWarnsKey, true)\n watchEffect(() => {\n if (props.min && props.max && compareISO(props.min, props.max) > 0)\n console.warn(\n `[VDateInput] min \"${props.min}\" falls after max \"${props.max}\", so no date can be chosen.`,\n )\n // Without a panel there is no icon to draw and no button to name. A read-only field is\n // left out: it is a state that comes and goes, not a configuration to correct.\n if (typing.value && !props.showPicker) {\n const inert = ([] as string[]).concat(\n props.pickerIcon !== calendarTodayIcon ? 'pickerIcon' : [],\n props.pickerIconLabel ? 'pickerIconLabel' : [],\n )\n if (inert.length > 0)\n console.warn(\n `[VDateInput] ${inert.join(', ')} ${inert.length > 1 ? 'are' : 'is'} ignored without showPicker: a field one types into has no calendar icon unless it offers the calendar.`,\n )\n }\n if (props.mode !== undefined && !MODES.includes(props.mode))\n console.warn(\n `[VDateInput] unknown mode \"${props.mode}\": use \"input\" (the default) or \"picker\".`,\n )\n // Tested on the PROP and not on what was resolved from it: typing being the default,\n // a period or a list falls back on its own with nobody having asked for anything.\n // Only a consumer who explicitly asked to type deserves to be told it cannot work.\n if (props.mode === 'input' && props.selection !== 'single')\n console.warn(\n `[VDateInput] mode=\"input\" ignored in \"${props.selection}\" selection: a range or a list of dates cannot be typed on the keyboard.`,\n )\n // Here it is the RESOLVED mode that matters: the format really was supplied, and it\n // really will have no effect, whichever route led to a typed field. The warning stays\n // something the consumer can act on.\n if (props.displayFormat && typing.value)\n console.warn(\n '[VDateInput] displayFormat is ignored in \"input\" mode (the default): the field displays the locale\\'s numeric mask, the only typeable format. Pass mode=\"picker\" for a formatted display.',\n )\n })\n}\n\n// A VInputGroup joins several controls into one object, so the shape of this field is the\n// row's decision rather than its own (VInput/context.ts).\nconst group = inject(inputGroupKey, null)\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape(props, group)\n\n// The whole \"field plus panel\" shell, shared with VTimeInput: opening and closing, the\n// focus leaving the component and coming back to the field, a click on the field or its\n// icon, and the Escape, ArrowDown and Enter keys.\nconst {\n open,\n panelId,\n closeAndFocus,\n focusField,\n onFieldFocus,\n toggleFromIcon,\n onControlClick,\n onFocusout,\n onKeydown,\n onPanelMousedown,\n} = useFieldPanel({\n rootEl,\n panelRef,\n field: inputRef,\n // With no panel there is nothing to open. This is the composable's SINGLE cut-off\n // point, and every way in passes through it — clicking the field, focusing it, the\n // down arrow, Enter, the icon — so the condition never has to be repeated in a\n // handler.\n disabled: () => resolvedDisabled.value || !hasPanel.value,\n focusInPanel: () => pickerRef.value?.focus(),\n // The calendar stays mounted while the panel is closed, so it is put back on the days\n // view both ways: a field that opens on focus never goes through `focus()`.\n onOpen: () => pickerRef.value?.reset(),\n onClose: () => pickerRef.value?.reset(),\n // Beside a field one types into, the panel opens on focus WITHOUT taking it: typing\n // carries on in the field, and the down arrow remains the way into the grid.\n openOnFocus: () => typing.value,\n})\n\n// @a11y\n// TRAP — the popup wiring is spread OVER the forwarded attributes, never bound as four\n// attributes after them. A binding written after `v-bind` wins even when it is\n// `undefined`, so a field with no panel would erase the consumer's own `role` or\n// `aria-controls`; spread, the wiring carries no key at all when there is no panel, and\n// wins over the consumer's when there is one, which is what a combobox needs.\nconst inputAttrs = computed(() =>\n hasPanel.value\n ? {\n ...fieldAttrs.value,\n role: 'combobox',\n 'aria-haspopup': 'dialog',\n 'aria-expanded': open.value,\n 'aria-controls': panelId,\n }\n : fieldAttrs.value,\n)\n\nconst hasValue = computed(() => {\n if (props.selection === 'multiple') return Array.isArray(model.value) && model.value.length > 0\n if (props.selection === 'range') {\n const r = model.value as DatePickerRange | null\n return !!(r && (r.start || r.end))\n }\n return typeof model.value === 'string' && !!model.value\n})\n\n/* TRAP — this component is the SINGLE place the locale is resolved: what flows down to the\n calendar is the RESULT, never the prop, which would usually be undefined and would have\n the calendar resolve the language a second time — with every chance of the two\n disagreeing after a later change. */\nconst resolvedLocale = useResolvedLocale(() => props.locale)\n\nconst displayText = computed(() => {\n const locale = resolvedLocale.value\n const displayFormat = props.displayFormat ?? DEFAULT_DISPLAY_FORMAT\n if (props.selection === 'single') {\n return typeof model.value === 'string' && isValidISO(model.value)\n ? formatDateDisplay(model.value, locale, displayFormat)\n : ''\n }\n if (props.selection === 'range') {\n const r = model.value as DatePickerRange | null\n if (!r?.start) return ''\n if (!r.end) return formatDateDisplay(r.start, locale, displayFormat)\n return formatDisplayRange(r.start, r.end, locale, displayFormat)\n }\n const list = Array.isArray(model.value) ? model.value : []\n return list.map((iso) => formatDateDisplay(iso, locale, displayFormat)).join(', ')\n})\n\n/* From here on: everything the typed field needs. */\n\nconst mask = computed(() => dateMaskFor(resolvedLocale.value))\nconst maskHint = computed(() => maskPlaceholder(resolvedLocale.value, mask.value))\nconst isDisabledDate = computed(() => resolveMatcher(props.disabledDates))\n\n/**\n * Whether a date the reader has finished typing may actually be taken: it has to fall\n * within the allowed bounds and not be one of the excluded days.\n */\nconst acceptable = (iso: string) => isDateAllowed(iso, props.min, props.max, isDisabledDate.value)\n\n/*\n * The mask machinery — the text being typed, the bridge to the value, the reformatting\n * that preserves the caret, the commit as soon as the date is complete and the silent\n * revert when it is not — is shared with VTimeInput and lives in `useMaskedField`.\n *\n * What is injected here is the date VOCABULARY alone: the order of the fields, the\n * separator between them and their widths, all derived from the reader's language.\n */\nconst {\n draft,\n fieldModel,\n writeField,\n commitOrRevert,\n onFieldInput,\n onKeydown: onMaskKeydown,\n onPaste,\n} = useMaskedField({\n fieldEl,\n typing: () => typing.value,\n displayText: () => displayText.value,\n readValue: () =>\n typeof model.value === 'string' && isValidISO(model.value) ? model.value : null,\n writeValue: (iso) => {\n model.value = iso\n },\n maxDigits: () => mask.value.size,\n format: (digits) => formatDateMask(digits, mask.value),\n caret: (text, digitsBefore, inserting) =>\n caretAfterDigits(text, digitsBefore, inserting ? mask.value.separator : undefined),\n // A two-digit year is only expanded on the FINAL commit, when the reader leaves the\n // field — never while they are still typing, where \"26\" may well be on its way to\n // becoming \"2026\".\n parse: (text, final) =>\n parseDateMask(text, mask.value, final ? { yearPivot: YEAR_PIVOT } : undefined),\n toMask: (iso) => (iso ? isoToMask(iso, mask.value) : ''),\n acceptable,\n})\n\n/**\n * Typing anything that is not a digit — a slash, a dot, a space — completes the field\n * being typed with a leading zero and moves on to the next. It is what lets someone type\n * \"5/6/2026\" and get the same result as typing all eight digits.\n *\n * It applies to the day and the month only: a year cut short has no such convention, \"26\"\n * being neither 0026 nor 2026 until the reader has finished.\n */\nfunction padCurrentField(el: HTMLInputElement) {\n const start = el.selectionStart ?? el.value.length\n const before = digitsOf(el.value.slice(0, start)).length\n const digits = digitsOf(el.value)\n let offset = 0\n for (let k = 0; k < mask.value.order.length; k++) {\n const len = mask.value.lengths[k] as number\n if (before < offset + len) {\n const filled = before - offset\n if (mask.value.order[k] === 'year' || filled !== len - 1) return\n const next = digits.slice(0, offset) + '0'.repeat(len - filled) + digits.slice(offset)\n const text = formatDateMask(next.slice(0, mask.value.size), mask.value)\n writeField(text, caretAfterDigits(text, offset + len, mask.value.separator))\n return\n }\n offset += len\n }\n}\n\n// @keyboard\n// What the mask's keys mean for a date: a separator completes the field being\n// typed, and the down arrow is the one explicit way from the field into the calendar.\nfunction onFieldKeydown(event: KeyboardEvent) {\n onMaskKeydown(event, {\n onSeparator: padCurrentField,\n onArrowDown: () => {\n if (!open.value) return false\n pickerRef.value?.focus()\n return true\n },\n onEnter: closeAndFocus,\n })\n}\n\n/**\n * Pasting. A date recognizable as a whole — in the ISO form, or already written the way\n * this field writes them — is adopted as it stands; anything else contributes its digits\n * alone.\n */\nfunction onFieldPaste(event: ClipboardEvent) {\n onPaste(event, (pasted) =>\n isValidISO(pasted) ? pasted : parseDateMask(pasted, mask.value, { yearPivot: YEAR_PIVOT }),\n )\n}\n\n/*\n * The clear cross is the field's own, which renders it BEFORE the end icon rather than in\n * its place — the convention every field in the design system follows, so the two live\n * side by side.\n *\n * Whether it is shown has to be answered explicitly here: outside the typed mode the field\n * is read-only, and a read-only field hides its cross by default, rightly so — but here\n * the value comes from the panel, so there is something to clear all the same. The\n * `readonly` PROP is the one case where the default answer was right: frozen, the field\n * offers no route to a new value, so it offers no route to none either.\n */\nconst clearVisible = computed(() =>\n canClear(props, resolvedDisabled.value, hasValue.value || (typing.value && !!draft.value)),\n)\nconst endIcon = computed<IconSource | undefined>(() =>\n hasPanel.value ? props.pickerIcon : undefined,\n)\n// @a11y @devwarn\n/*\n * TRAP — the LABEL is defined at all times, even when no icon is rendered at all.\n *\n * The helper detecting a click handler on an icon warns AT SETUP if one is attached\n * without a label, and it has no way of knowing whether an icon exists. Since the\n * listener here is attached permanently and that detection is static, making the label\n * conditional would produce a false warning every time a field without a calendar is\n * mounted.\n *\n * The wording itself has no prop: the dictionary is where it is changed — see `src/i18n/`.\n */\nconst m = useMessages()\n\nconst endIconLabel = computed(() => props.pickerIconLabel ?? m.value.dateInput.openPicker)\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.dateInput.clear)\n\n/*\n * Emptying the value, called by the field as it emits its clear event. The focus is taken\n * through `focusField`, whose note says why that stops the panel reopening.\n */\nfunction clearValue() {\n model.value =\n props.selection === 'multiple'\n ? []\n : props.selection === 'range'\n ? { start: null, end: null }\n : null\n // The typed text is emptied EXPLICITLY: when the value was already empty, nothing\n // changes, and the guard that keeps the field and the value from chasing each other\n // would leave the text where it was.\n if (typing.value) writeField('')\n focusField()\n emit('clear')\n}\n\n/**\n * A date was chosen in the calendar. Choosing ONE date finishes the job, so the panel\n * closes; a period or a list is still being built and it stays open.\n */\nfunction onSelect() {\n if (props.selection === 'single') closeAndFocus()\n}\n\ndefineExpose({\n /** Moves the focus to the text field. */\n focus: (options?: FocusOptions) => inputRef.value?.focus(options),\n /** Selects what the field is showing. */\n select: () => inputRef.value?.select(),\n /** The real `<input>` behind the field, for what neither of the two above covers. */\n el: computed(() => inputRef.value?.el ?? null),\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-date-input\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-open=\"open ? '' : undefined\"\n :data-mode=\"resolvedMode\"\n :data-disabled=\"resolvedDisabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n @focusout=\"onFocusout\"\n @keydown=\"onKeydown\"\n >\n <div class=\"v-date-input-control\" @click=\"onControlClick\">\n <!-- The field is declared a combobox rather than left as the plain text box it\n implicitly is, because a text box may not carry the attribute saying whether\n something is expanded — and a text field with a panel attached IS the combobox\n pattern, the same role VCombobox gives its own field.\n\n The role and the three attributes are all conditioned on the SAME thing, so the\n role never appears without the expanded state it requires, and nothing ever\n points at a panel that was not rendered. -->\n <VInput\n ref=\"inputRef\"\n v-model=\"fieldModel\"\n :inputmode=\"typing ? 'numeric' : undefined\"\n :autocomplete=\"typing ? 'off' : undefined\"\n v-bind=\"inputAttrs\"\n :readonly=\"readonly\"\n :no-typing=\"!typing\"\n :label=\"label\"\n :hint=\"hint\"\n :placeholder=\"placeholder ?? (typing ? maskHint : undefined)\"\n :size=\"resolvedSize\"\n :compact=\"resolvedCompact\"\n :disabled=\"resolvedDisabled\"\n :invalid=\"invalid\"\n :clearable=\"clearable\"\n :clear-visible=\"clearVisible\"\n :clear-label=\"resolvedClearLabel\"\n :icon-start=\"iconStart\"\n :icon-start-label=\"iconStartLabel\"\n :loading=\"loading\"\n :loading-text=\"loadingText\"\n :icon-end=\"endIcon\"\n :icon-end-label=\"endIconLabel\"\n @click:icon-end=\"toggleFromIcon\"\n @clear=\"clearValue\"\n @focus=\"onFieldFocus\"\n @input=\"onFieldInput\"\n @change=\"commitOrRevert\"\n @blur=\"commitOrRevert\"\n @keydown=\"onFieldKeydown\"\n @paste=\"onFieldPaste\"\n >\n <template v-if=\"$slots.start\" #start><slot name=\"start\" /></template>\n <template v-if=\"$slots['value-end']\" #value-end><slot name=\"value-end\" /></template>\n </VInput>\n </div>\n\n <!-- With no panel rendered there is nothing to hold a reference to, and the open state\n — which is fed by the panel's own events — can no longer become true. The absence\n of a panel is therefore self-enforcing. -->\n <VPopover\n v-if=\"hasPanel\"\n :id=\"panelId\"\n ref=\"panelRef\"\n v-model:open=\"open\"\n mode=\"manual\"\n anchor=\"--date-input-anchor\"\n :placement=\"placement\"\n role=\"dialog\"\n :aria-label=\"label ?? m.dateInput.pickerLabel\"\n class=\"v-date-input-panel\"\n @mousedown=\"onPanelMousedown\"\n >\n <VDatePicker\n ref=\"pickerRef\"\n v-model=\"model\"\n :selection=\"selection\"\n :locale=\"resolvedLocale\"\n :first-day-of-week=\"firstDayOfWeek\"\n :min=\"min\"\n :max=\"max\"\n :disabled-dates=\"disabledDates\"\n :show-adjacent-days=\"showAdjacentDays\"\n :select-adjacent-days=\"selectAdjacentDays\"\n :events=\"events\"\n @select=\"onSelect\"\n >\n <template v-if=\"$slots.day\" #day=\"slotProps\">\n <slot name=\"day\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots.footer\" #footer>\n <slot name=\"footer\" :close=\"closeAndFocus\" />\n </template>\n </VDatePicker>\n </VPopover>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-date-input {\n /* Confines the anchor to this instance (the root = the common ancestor of the\n control and the panel) */\n anchor-scope: --date-input-anchor;\n display: block;\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The pointer says \"this opens a panel\", which a read-only or disabled field no longer\n does: the VFileInput gating. */\n .v-date-input:not([data-disabled]):not([data-readonly]) .v-date-input-control {\n cursor: pointer;\n }\n\n /* The anchor is the FIELD's box and not this wrapper's, which also holds the label and\n the hint: anchored to the wrapper, the calendar opens a hint's height below the field,\n and a label's height above it once there is no room below and `flip-block` turns it\n over. Against the field it covers whichever of the two it lands on, which is what a\n panel belonging to a control is supposed to do. */\n .v-date-input-control .v-input-field {\n anchor-name: --date-input-anchor;\n }\n\n /* Input mode: the field is editable, and the <input>'s text caret takes over from the\n control's `pointer` (which signals \"this opens a panel\"). The mask has a fixed width:\n tabular figures, otherwise the caret jitters from one digit to the next. */\n .v-date-input[data-mode='input']:not([data-disabled]):not([data-readonly]) .v-date-input-control {\n cursor: text;\n }\n\n .v-date-input[data-mode='input'] .v-input-control {\n font-variant-numeric: tabular-nums;\n }\n\n /* `position-anchor` and the chrome come from VPopover (the `anchor` prop, and the\n `.v-panel` it sets on any panel that is not `bare`): only the dimensions are left here,\n which `panel.css` deliberately does not carry. The panel's OWN padding is cancelled,\n VDatePicker padding itself as VTimePicker does, so both pickers keep the same room\n on a page and in a panel. Compounded with `.v-popover-panel` (VPopover puts both\n classes on the same element) because `.v-panel` declares `padding` too, and at\n equal specificity the winner would depend on the order in which the consumer's\n bundler concatenates the CSS. */\n .v-popover-panel.v-date-input-panel {\n width: max-content;\n padding: 0;\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VDateInput.js","names":[],"sources":["../../../src/components/VDateInput/VDateInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Compose VInput with VDatePicker. A text input cannot use popovertarget, so the manual panel\n * bridge handles opening, dismissal and focus transfer into the grid.\n */\n\nimport { computed, inject, provide, ref, watchEffect } from 'vue'\n\nimport { inputGroupKey } from '../VInput/context'\n\nimport VDatePicker from '../VDatePicker/VDatePicker.vue'\nimport type {\n DatePickerDaySlotProps,\n DatePickerEvent,\n DatePickerSelection,\n DatePickerValue,\n DatePickerMatcher,\n DatePickerRange,\n} from '../VDatePicker/VDatePicker.vue'\nimport {\n compareISO,\n formatDateDisplay,\n formatDisplayRange,\n isDateAllowed,\n isValidISO,\n} from '../../utils/date'\nimport {\n caretAfterDigits,\n dateMaskFor,\n formatDateMask,\n isoToMask,\n maskPlaceholder,\n parseDateMask,\n} from './mask'\nimport { digitsOf } from '../../utils/text'\nimport { resolveMatcher } from '../../utils/matcher'\nimport { isDev } from '../../utils/env'\nimport { hostWarnsKey } from '../../utils/hostWarns'\nimport { calendar_today as calendarTodayIcon } from '../VIcon/icons/calendar_today'\nimport type { IconSource } from '../VIcon/types'\nimport VInput from '../VInput/VInput.vue'\nimport VPopover from '../VPopover/VPopover.vue'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\nimport { useFieldPanel } from '../../composables/useFieldPanel'\nimport { canClear } from '../../composables/useClearable'\nimport { iconStartListener } from '../../composables/useIconClickHandlers'\nimport { useMaskedField } from '../../composables/useMaskedField'\nimport { useMessages, useResolvedLocale } from '../../i18n/state'\n\n/** Where the calendar opens relative to the field. */\nexport type DateInputPlacement =\n 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end'\n\n/** Whether the field can be typed into, or only filled from the calendar. */\nexport type DateInputMode = 'picker' | 'input'\n\n/** What the `#footer` slot receives. */\nexport interface DateInputFooterSlotProps {\n /** Closes the panel and hands the focus back to the field. */\n close: () => void\n}\n\nconst MODES: DateInputMode[] = ['picker', 'input']\n\n/**\n * The century a two-digit year is expanded into; \"10/06/26\" becomes 2026; applied only when the\n * reader leaves the field.\n */\nconst YEAR_PIVOT = 2000\n\nconst DEFAULT_DISPLAY_FORMAT: Intl.DateTimeFormatOptions = {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n}\n\n/** The height of the field: 32, 40 or 48 pixels. */\nexport type DateInputSize = 'sm' | 'md' | 'lg'\n\ninterface DateInputProps {\n /** What is being chosen: one date, a period between two, or several separate dates. */\n selection?: DatePickerSelection\n /**\n * A BCP 47 locale, which decides the month and day names, the first day of the week and\n * the order the field is typed in. It TAKES PRECEDENCE over the design system's global\n * locale and falls back to it, which is why it has no literal default: `undefined` has\n * to stay recognizable for the global locale to have its chance.\n */\n locale?: string\n /** Forces the day the weeks start on, from 0 for Sunday to 6 for Saturday. */\n firstDayOfWeek?: number\n /** The earliest date that can be chosen, as an ISO string. */\n min?: string\n /** The latest date that can be chosen, as an ISO string. */\n max?: string\n /** Dates that cannot be chosen, as a list or as a function. */\n disabledDates?: DatePickerMatcher\n /** Fills the corners of the grid with the greyed days of the neighbouring months. */\n showAdjacentDays?: boolean\n /** Lets those neighbouring days be clicked, which implies showing them. */\n selectAdjacentDays?: boolean\n /** Events to mark under the days they fall on. */\n events?: DatePickerEvent[]\n /**\n * Whether the field can be TYPED into (the default, using the numeric form of the\n * reader's language) or is filled from the calendar alone, which is `picker`.\n *\n * Typing is reserved for choosing a SINGLE date: a period or a list falls back to\n * `picker`, there being no sensible way to type either.\n */\n mode?: DateInputMode\n /**\n * Offers the date picker alongside a field that can be typed into: an icon at the end of the\n * field, and a panel that opens on focus.\n */\n showPicker?: boolean\n /** The label above the field. */\n label?: string\n /** A line of help under the field. */\n hint?: string\n /** What the field says while empty. */\n placeholder?: string\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: DateInputSize\n /** Takes 4px off the height. */\n compact?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the date without letting it be changed: nothing can be typed, there is no calendar\n * and no clear cross.\n */\n readonly?: boolean\n /** Marks the field as invalid, for a rule of your own. */\n invalid?: boolean\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes a\n * real button as soon as a `@click:icon-start` listener is attached, in which case it\n * needs `iconStartLabel`.\n */\n iconStart?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /**\n * What the calendar button does, in words. It names the button `pickerIcon` renders,\n * and falls back to the design system dictionary.\n */\n pickerIconLabel?: string\n /**\n * Shows a spinner at the end of the field, in place of the calendar icon. It says\n * that something is being loaded and changes nothing else: the field can still be\n * typed into and the panel still opens.\n */\n loading?: boolean\n /**\n * What screen readers announce while the spinner turns. It falls back to the design\n * system dictionary.\n */\n loadingText?: string\n /** Offers a cross that empties the value, shown before the end icon. */\n clearable?: boolean\n /** What that cross does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The icon that opens the date picker, at the end of the field. The clear cross appears\n * to its left rather than in its place, and no icon is rendered at all when there is no\n * panel to open.\n */\n pickerIcon?: IconSource\n /**\n * How the date is written OUT in the field. It has no effect on a field being typed into,\n * which necessarily shows the numeric form one types, so it concerns the `picker` mode, and\n * the period and list selections, which fall back to it.\n */\n displayFormat?: Intl.DateTimeFormatOptions\n /** Where the panel opens relative to the field. */\n placement?: DateInputPlacement\n}\n\nconst props = withDefaults(defineProps<DateInputProps>(), {\n selection: 'single',\n locale: undefined,\n firstDayOfWeek: undefined,\n min: undefined,\n max: undefined,\n disabledDates: undefined,\n showAdjacentDays: false,\n selectAdjacentDays: false,\n events: undefined,\n // Deliberately left undefined rather than defaulted to the typed mode: that is what\n // distinguishes \"the prop was not given\" from \"the prop was given this value\", and therefore\n // what allows warning only the consumer who explicitly asked for something that cannot work.\n mode: undefined,\n showPicker: false,\n label: undefined,\n hint: undefined,\n placeholder: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n iconStart: undefined,\n iconStartLabel: undefined,\n pickerIconLabel: undefined,\n loading: false,\n loadingText: undefined,\n clearable: false,\n clearLabel: undefined,\n pickerIcon: () => calendarTodayIcon,\n // Left undefined rather than defaulted to the format object, for the same reason: it is\n // what lets the component notice that a consumer has asked for a display format in a\n // mode where it has no effect, and say so.\n displayFormat: undefined,\n placement: 'bottom-start',\n})\n\n/**\n * The date or dates chosen, in the shape `selection` calls for: an ISO `YYYY-MM-DD` string for\n * `single`, a `{ start, end }` pair for `range`, an array for `multiple`.\n */\nconst model = defineModel<DatePickerValue>({ default: null })\n\nconst emit = defineEmits<{\n /** The clear cross emptied the field. The value has already been reset. */\n clear: []\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its place,\n * as in VInput.\n */\n start?(): unknown\n /**\n * Controls of your own inside the field, placed before the ones the field owns: the clear\n * cross and the icon that opens the panel.\n */\n 'value-end'?(): unknown\n /** What a day cell shows, handed straight to the calendar. */\n day?(props: DatePickerDaySlotProps): unknown\n /**\n * The strip at the foot of the panel: actions, or preset dates such as \"today\". It receives\n * `close`, which lets one of those buttons dismiss the panel.\n */\n footer?(props: DateInputFooterSlotProps): unknown\n}>()\n\n// `class` and `style` stay on the wrapper; everything else goes down to the text field, which a\n// consumer's label points at and what assistive technology deals with.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\n\n// Declared as this component's own event, `click:icon-start` is out of `$attrs`, so the\n// listener is relayed to the field by hand; and only when the consumer wrote one.\nconst iconStartClick = iconStartListener((event) => emit('click:icon-start', event))\n\n/** What reaches the field: the consumer's own attributes, plus that listener. */\nconst fieldAttrs = computed(() => ({ ...forwardedAttrs.value, ...iconStartClick }))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelRef = ref<InstanceType<typeof VPopover> | null>(null)\nconst inputRef = ref<InstanceType<typeof VInput> | null>(null)\nconst pickerRef = ref<InstanceType<typeof VDatePicker> | null>(null)\n\n/** The real input inside the field, which the mask and the caret work on. */\nconst fieldEl = computed<HTMLInputElement | null>(() => inputRef.value?.el ?? null)\n\n/** What was asked for, falling back to the default when nothing or nonsense was given. */\nconst requestedMode = computed<DateInputMode>(() =>\n props.mode !== undefined && MODES.includes(props.mode) ? props.mode : 'input',\n)\n/**\n * What is actually used. Typing is only possible for a single date: a period or a list\n * has no form one could type into a single field.\n */\nconst resolvedMode = computed<DateInputMode>(() =>\n requestedMode.value === 'input' && props.selection === 'single' ? 'input' : 'picker',\n)\nconst typing = computed(() => resolvedMode.value === 'input')\n\n/**\n * Whether there is a panel at all. The calendar is only optional beside a field one can\n * type into: in `picker` mode it is the only way to choose anything, and a frozen field\n * has none at all.\n */\nconst hasPanel = computed(() => !props.readonly && (!typing.value || props.showPicker))\n\n// @devwarn\nif (isDev) {\n // The calendar receives `min` and `max` as they stand, so what it would say about them is\n // said here, under the name the consumer wrote (utils/hostWarns).\n provide(hostWarnsKey, true)\n watchEffect(() => {\n if (props.min && props.max && compareISO(props.min, props.max) > 0)\n console.warn(\n `[VDateInput] min \"${props.min}\" falls after max \"${props.max}\", so no date can be chosen.`,\n )\n // Without a panel there is no icon to draw and no button to name. A read-only field is\n // left out: it is a state that comes and goes, not a configuration to correct.\n if (typing.value && !props.showPicker) {\n const inert = ([] as string[]).concat(\n props.pickerIcon !== calendarTodayIcon ? 'pickerIcon' : [],\n props.pickerIconLabel ? 'pickerIconLabel' : [],\n )\n if (inert.length > 0)\n console.warn(\n `[VDateInput] ${inert.join(', ')} ${inert.length > 1 ? 'are' : 'is'} ignored without showPicker: a field one types into has no calendar icon unless it offers the calendar.`,\n )\n }\n if (props.mode !== undefined && !MODES.includes(props.mode))\n console.warn(\n `[VDateInput] unknown mode \"${props.mode}\": use \"input\" (the default) or \"picker\".`,\n )\n // Only a consumer who explicitly asked to type deserves to be told it cannot work.\n if (props.mode === 'input' && props.selection !== 'single')\n console.warn(\n `[VDateInput] mode=\"input\" ignored in \"${props.selection}\" selection: a range or a list of dates cannot be typed on the keyboard.`,\n )\n // Here it is the RESOLVED mode that matters: the format really was supplied, and it\n // really will have no effect, whichever route led to a typed field. The warning stays\n // something the consumer can act on.\n if (props.displayFormat && typing.value)\n console.warn(\n '[VDateInput] displayFormat is ignored in \"input\" mode (the default): the field displays the locale\\'s numeric mask, the only typeable format. Pass mode=\"picker\" for a formatted display.',\n )\n })\n}\n\n// A VInputGroup joins several controls into one object, so the shape of this field is the\n// row's decision rather than its own (VInput/context.ts).\nconst group = inject(inputGroupKey, null)\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape(props, group)\n\n// The whole \"field plus panel\" shell, shared with VTimeInput: opening and closing, the\n// focus leaving the component and coming back to the field, a click on the field or its\n// icon, and the Escape, ArrowDown and Enter keys.\nconst {\n open,\n panelId,\n closeAndFocus,\n focusField,\n onFieldFocus,\n toggleFromIcon,\n onControlClick,\n onFocusout,\n onKeydown,\n onPanelMousedown,\n} = useFieldPanel({\n rootEl,\n panelRef,\n field: inputRef,\n // This is the composable's SINGLE cut-off point, and every way in passes through it; clicking\n // the field, focusing it, the down arrow, Enter, the icon; so the condition never has to be\n // repeated in a handler.\n disabled: () => resolvedDisabled.value || !hasPanel.value,\n focusInPanel: () => pickerRef.value?.focus(),\n // The calendar stays mounted while the panel is closed, so it is put back on the days\n // view both ways: a field that opens on focus never goes through `focus()`.\n onOpen: () => pickerRef.value?.reset(),\n onClose: () => pickerRef.value?.reset(),\n // Beside a field one types into, the panel opens on focus WITHOUT taking it: typing\n // carries on in the field, and the down arrow remains the way into the grid.\n openOnFocus: () => typing.value,\n})\n\n// @a11y\n// The popup wiring is spread OVER the forwarded attributes, never bound as four attributes\n// after them. A binding written after `v-bind` wins even when it is `undefined`, so a field\n// with no panel would erase the consumer's own `role` or `aria-controls`; spread, the wiring\n// carries no key at all when there is no panel, and wins over the consumer's when there is one,\n// which a combobox needs.\nconst inputAttrs = computed(() =>\n hasPanel.value\n ? {\n ...fieldAttrs.value,\n role: 'combobox',\n 'aria-haspopup': 'dialog',\n 'aria-expanded': open.value,\n 'aria-controls': panelId,\n }\n : fieldAttrs.value,\n)\n\nconst hasValue = computed(() => {\n if (props.selection === 'multiple') return Array.isArray(model.value) && model.value.length > 0\n if (props.selection === 'range') {\n const r = model.value as DatePickerRange | null\n return !!(r && (r.start || r.end))\n }\n return typeof model.value === 'string' && !!model.value\n})\n\n/*\n * This component is the SINGLE place the locale is resolved: what flows down to the calendar is\n * the RESULT, never the prop, which would usually be undefined and would have the calendar\n * resolve the language a second time; with every chance of the two disagreeing after a later\n * change.\n */\nconst resolvedLocale = useResolvedLocale(() => props.locale)\n\nconst displayText = computed(() => {\n const locale = resolvedLocale.value\n const displayFormat = props.displayFormat ?? DEFAULT_DISPLAY_FORMAT\n if (props.selection === 'single') {\n return typeof model.value === 'string' && isValidISO(model.value)\n ? formatDateDisplay(model.value, locale, displayFormat)\n : ''\n }\n if (props.selection === 'range') {\n const r = model.value as DatePickerRange | null\n if (!r?.start) return ''\n if (!r.end) return formatDateDisplay(r.start, locale, displayFormat)\n return formatDisplayRange(r.start, r.end, locale, displayFormat)\n }\n const list = Array.isArray(model.value) ? model.value : []\n return list.map((iso) => formatDateDisplay(iso, locale, displayFormat)).join(', ')\n})\n\nconst mask = computed(() => dateMaskFor(resolvedLocale.value))\nconst maskHint = computed(() => maskPlaceholder(resolvedLocale.value, mask.value))\nconst isDisabledDate = computed(() => resolveMatcher(props.disabledDates))\n\n/**\n * Whether a date the reader has finished typing may actually be taken: it has to fall\n * within the allowed bounds and not be one of the excluded days.\n */\nconst acceptable = (iso: string) => isDateAllowed(iso, props.min, props.max, isDisabledDate.value)\n\n/*\n * The mask machinery; the text being typed, the bridge to the value, the reformatting that\n * preserves the caret, the commit as soon as the date is complete and the silent revert when it\n * is not; is shared with VTimeInput and lives in `useMaskedField`. What is injected here is the\n * date VOCABULARY alone: the order of the fields, the separator between them and their widths,\n * all derived from the reader's language.\n */\nconst {\n draft,\n fieldModel,\n writeField,\n commitOrRevert,\n onFieldInput,\n onKeydown: onMaskKeydown,\n onPaste,\n} = useMaskedField({\n fieldEl,\n typing: () => typing.value,\n displayText: () => displayText.value,\n readValue: () =>\n typeof model.value === 'string' && isValidISO(model.value) ? model.value : null,\n writeValue: (iso) => {\n model.value = iso\n },\n maxDigits: () => mask.value.size,\n format: (digits) => formatDateMask(digits, mask.value),\n caret: (text, digitsBefore, inserting) =>\n caretAfterDigits(text, digitsBefore, inserting ? mask.value.separator : undefined),\n // A two-digit year is only expanded on the FINAL commit, when the reader leaves the field;\n // never while they are still typing, where \"26\" may well be on its way to becoming \"2026\".\n parse: (text, final) =>\n parseDateMask(text, mask.value, final ? { yearPivot: YEAR_PIVOT } : undefined),\n toMask: (iso) => (iso ? isoToMask(iso, mask.value) : ''),\n acceptable,\n})\n\n/**\n * Typing anything that is not a digit; a slash, a dot, a space; completes the field being typed\n * with a leading zero and moves on to the next.\n */\nfunction padCurrentField(el: HTMLInputElement) {\n const start = el.selectionStart ?? el.value.length\n const before = digitsOf(el.value.slice(0, start)).length\n const digits = digitsOf(el.value)\n let offset = 0\n for (let k = 0; k < mask.value.order.length; k++) {\n const len = mask.value.lengths[k] as number\n if (before < offset + len) {\n const filled = before - offset\n if (mask.value.order[k] === 'year' || filled !== len - 1) return\n const next = digits.slice(0, offset) + '0'.repeat(len - filled) + digits.slice(offset)\n const text = formatDateMask(next.slice(0, mask.value.size), mask.value)\n writeField(text, caretAfterDigits(text, offset + len, mask.value.separator))\n return\n }\n offset += len\n }\n}\n\n// @keyboard\n// What the mask's keys mean for a date: a separator completes the field being\n// typed, and the down arrow is the one explicit way from the field into the calendar.\nfunction onFieldKeydown(event: KeyboardEvent) {\n onMaskKeydown(event, {\n onSeparator: padCurrentField,\n onArrowDown: () => {\n if (!open.value) return false\n pickerRef.value?.focus()\n return true\n },\n onEnter: closeAndFocus,\n })\n}\n\n/**\n * Pasting. A date recognizable as a whole; in the ISO form, or already written the way this\n * field writes them; is adopted as it stands; anything else contributes its digits alone.\n */\nfunction onFieldPaste(event: ClipboardEvent) {\n onPaste(event, (pasted) =>\n isValidISO(pasted) ? pasted : parseDateMask(pasted, mask.value, { yearPivot: YEAR_PIVOT }),\n )\n}\n\n/*\n * Whether it is shown has to be answered explicitly here: outside the typed mode the field is\n * read-only, and a read-only field hides its cross by default, rightly so; but here the value\n * comes from the panel, so there is something to clear all the same. The `readonly` PROP is the\n * one case where the default answer was right: frozen, the field offers no route to a new\n * value, so it offers no route to none either.\n */\nconst clearVisible = computed(() =>\n canClear(props, resolvedDisabled.value, hasValue.value || (typing.value && !!draft.value)),\n)\nconst endIcon = computed<IconSource | undefined>(() =>\n hasPanel.value ? props.pickerIcon : undefined,\n)\n// @a11y @devwarn\n/*\n * The label is defined always, even when no icon is rendered at all. The helper detecting a\n * click handler on an icon warns AT setup if one is attached without a label, and it has no way\n * of knowing whether an icon exists.\n */\nconst m = useMessages()\n\nconst endIconLabel = computed(() => props.pickerIconLabel ?? m.value.dateInput.openPicker)\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.dateInput.clear)\n\n/*\n * Emptying the value, called by the field as it emits its clear event. The focus is taken\n * through `focusField`, whose note says why that stops the panel reopening.\n */\nfunction clearValue() {\n model.value =\n props.selection === 'multiple'\n ? []\n : props.selection === 'range'\n ? { start: null, end: null }\n : null\n // The typed text is emptied EXPLICITLY: when the value was already empty, nothing\n // changes, and the guard that keeps the field and the value from chasing each other\n // would leave the text where it was.\n if (typing.value) writeField('')\n focusField()\n emit('clear')\n}\n\n/**\n * A date was chosen in the calendar. Choosing ONE date finishes the job, so the panel\n * closes; a period or a list is still being built and it stays open.\n */\nfunction onSelect() {\n if (props.selection === 'single') closeAndFocus()\n}\n\ndefineExpose({\n /** Moves the focus to the text field. */\n focus: (options?: FocusOptions) => inputRef.value?.focus(options),\n /** Selects what the field is showing. */\n select: () => inputRef.value?.select(),\n /** The real `<input>` behind the field, for what neither of the two above covers. */\n el: computed(() => inputRef.value?.el ?? null),\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-date-input\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-open=\"open ? '' : undefined\"\n :data-mode=\"resolvedMode\"\n :data-disabled=\"resolvedDisabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n @focusout=\"onFocusout\"\n @keydown=\"onKeydown\"\n >\n <div class=\"v-date-input-control\" @click=\"onControlClick\">\n <!--\n The field is declared a combobox rather than left as the plain text box it implicitly\n is, because a text box may not carry the attribute saying whether something is expanded;\n and a text field with a panel attached IS the combobox pattern, the same role VCombobox\n gives its own field. The role and the three attributes are all conditioned on the same\n thing, so the role never appears without the expanded state it requires, and nothing\n ever points at a panel that was not rendered.\n -->\n <VInput\n ref=\"inputRef\"\n v-model=\"fieldModel\"\n :inputmode=\"typing ? 'numeric' : undefined\"\n :autocomplete=\"typing ? 'off' : undefined\"\n v-bind=\"inputAttrs\"\n :readonly=\"readonly\"\n :no-typing=\"!typing\"\n :label=\"label\"\n :hint=\"hint\"\n :placeholder=\"placeholder ?? (typing ? maskHint : undefined)\"\n :size=\"resolvedSize\"\n :compact=\"resolvedCompact\"\n :disabled=\"resolvedDisabled\"\n :invalid=\"invalid\"\n :clearable=\"clearable\"\n :clear-visible=\"clearVisible\"\n :clear-label=\"resolvedClearLabel\"\n :icon-start=\"iconStart\"\n :icon-start-label=\"iconStartLabel\"\n :loading=\"loading\"\n :loading-text=\"loadingText\"\n :icon-end=\"endIcon\"\n :icon-end-label=\"endIconLabel\"\n @click:icon-end=\"toggleFromIcon\"\n @clear=\"clearValue\"\n @focus=\"onFieldFocus\"\n @input=\"onFieldInput\"\n @change=\"commitOrRevert\"\n @blur=\"commitOrRevert\"\n @keydown=\"onFieldKeydown\"\n @paste=\"onFieldPaste\"\n >\n <template v-if=\"$slots.start\" #start><slot name=\"start\" /></template>\n <template v-if=\"$slots['value-end']\" #value-end><slot name=\"value-end\" /></template>\n </VInput>\n </div>\n\n <!-- The absence of a panel is therefore self-enforcing. -->\n <VPopover\n v-if=\"hasPanel\"\n :id=\"panelId\"\n ref=\"panelRef\"\n v-model:open=\"open\"\n mode=\"manual\"\n anchor=\"--date-input-anchor\"\n :placement=\"placement\"\n role=\"dialog\"\n :aria-label=\"label ?? m.dateInput.pickerLabel\"\n class=\"v-date-input-panel\"\n @mousedown=\"onPanelMousedown\"\n >\n <VDatePicker\n ref=\"pickerRef\"\n v-model=\"model\"\n :selection=\"selection\"\n :locale=\"resolvedLocale\"\n :first-day-of-week=\"firstDayOfWeek\"\n :min=\"min\"\n :max=\"max\"\n :disabled-dates=\"disabledDates\"\n :show-adjacent-days=\"showAdjacentDays\"\n :select-adjacent-days=\"selectAdjacentDays\"\n :events=\"events\"\n @select=\"onSelect\"\n >\n <template v-if=\"$slots.day\" #day=\"slotProps\">\n <slot name=\"day\" v-bind=\"slotProps\" />\n </template>\n <template v-if=\"$slots.footer\" #footer>\n <slot name=\"footer\" :close=\"closeAndFocus\" />\n </template>\n </VDatePicker>\n </VPopover>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-date-input {\n anchor-scope: --date-input-anchor;\n display: block;\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n .v-date-input:not([data-disabled]):not([data-readonly]) .v-date-input-control {\n cursor: pointer;\n }\n\n /*\n * The anchor is the FIELD's box and not this wrapper's, which also holds the label and the\n * hint: anchored to the wrapper, the calendar opens a hint's height below the field, and a\n * label's height above it once there is no room below and `flip-block` turns it over. Against\n * the field it covers whichever of the two it lands on, which a panel belonging to a control\n * is supposed to do.\n */\n .v-date-input-control .v-input-field {\n anchor-name: --date-input-anchor;\n }\n\n /*\n * The mask has a fixed width: tabular figures, otherwise the caret jitters from one digit to\n * the next.\n */\n .v-date-input[data-mode='input']:not([data-disabled]):not([data-readonly]) .v-date-input-control {\n cursor: text;\n }\n\n .v-date-input[data-mode='input'] .v-input-control {\n font-variant-numeric: tabular-nums;\n }\n\n /*\n * `position-anchor` and the chrome come from VPopover (the `anchor` prop, and the `.v-panel`\n * it sets on any panel that is not `bare`): only the dimensions are left here, which\n * `panel.css` deliberately does not carry. The panel's own padding is cancelled, VDatePicker\n * padding itself as VTimePicker does, so both pickers keep the same room on a page and in a\n * panel.\n */\n .v-popover-panel.v-date-input-panel {\n width: max-content;\n padding: 0;\n }\n}\n</style>\n"],"mappings":""}
|