vectis-ui 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/VAccordion/VAccordion.css +1 -1
- package/dist/components/VAccordion/VAccordion.d.ts +0 -6
- package/dist/components/VAccordion/VAccordion.js.map +1 -1
- package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js +3 -8
- package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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/VAlert/VAlert.js.map +1 -1
- package/dist/components/VAlert/VAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.d.ts +0 -3
- package/dist/components/VAvatar/VAvatar.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +21 -27
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.d.ts +0 -6
- package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +5 -19
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/context.d.ts +0 -1
- package/dist/components/VAvatar/context.js.map +1 -1
- package/dist/components/VBadge/VBadge.js.map +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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 +0 -6
- package/dist/components/VButton/VButton.js.map +1 -1
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +11 -17
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.d.ts +0 -6
- package/dist/components/VButton/VButtonGroup.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js +0 -7
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/context.d.ts +0 -2
- package/dist/components/VButton/context.js.map +1 -1
- 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.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 +5 -17
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +19 -31
- 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.js.map +1 -1
- package/dist/components/VCalendar/edgeStep.js +1 -5
- package/dist/components/VCalendar/edgeStep.js.map +1 -1
- package/dist/components/VCalendar/gesture.js +4 -12
- package/dist/components/VCalendar/gesture.js.map +1 -1
- package/dist/components/VCalendar/keyboard.js.map +1 -1
- package/dist/components/VCalendar/layout.js +8 -12
- package/dist/components/VCalendar/layout.js.map +1 -1
- package/dist/components/VCalendar/timeGrid.js +5 -5
- package/dist/components/VCalendar/timeGrid.js.map +1 -1
- package/dist/components/VCard/VCard.js.map +1 -1
- package/dist/components/VCard/VCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/context.js.map +1 -1
- package/dist/components/VCheckbox/VCheckMark.js.map +1 -1
- package/dist/components/VCheckbox/VCheckMark.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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 +0 -3
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VColorInput/VColorInput.d.ts +0 -3
- package/dist/components/VColorInput/VColorInput.js.map +1 -1
- package/dist/components/VColorInput/VColorInput.vue_vue_type_script_setup_true_lang.js +3 -9
- package/dist/components/VColorInput/VColorInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VColorPicker/VColorPicker.js.map +1 -1
- package/dist/components/VColorPicker/VColorPicker.vue_vue_type_script_setup_true_lang.js +1 -5
- package/dist/components/VColorPicker/VColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VColorPicker/color.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +0 -3
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +100 -119
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCommandPalette/VCommandPalette.js.map +1 -1
- package/dist/components/VCommandPalette/VCommandPalette.vue_vue_type_script_setup_true_lang.js +0 -1
- package/dist/components/VCommandPalette/VCommandPalette.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCommandPalette/VCommandPaletteItem.js.map +1 -1
- package/dist/components/VCommandPalette/VCommandPaletteItem.vue_vue_type_script_setup_true_lang.js +1 -5
- package/dist/components/VCommandPalette/VCommandPaletteItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VContextMenu/VContextMenu.d.ts +0 -3
- package/dist/components/VContextMenu/VContextMenu.js.map +1 -1
- package/dist/components/VContextMenu/VContextMenu.vue_vue_type_script_setup_true_lang.js +3 -11
- package/dist/components/VContextMenu/VContextMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.css +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +0 -2
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +69 -88
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.d.ts +0 -3
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +5 -10
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/mask.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/keyboard.js.map +1 -1
- package/dist/components/VDatePicker/names.js.map +1 -1
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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/VDrawer/VDrawer.js.map +1 -1
- package/dist/components/VDrawer/VDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VEmptyState/VEmptyState.js.map +1 -1
- package/dist/components/VEmptyState/VEmptyState.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VField/VField.js.map +1 -1
- package/dist/components/VField/VField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VField/VFieldAnnouncer.js.map +1 -1
- package/dist/components/VField/VFieldAnnouncer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VField/VFieldMessage.js.map +1 -1
- package/dist/components/VField/VFieldMessage.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFieldset/VFieldset.js.map +1 -1
- package/dist/components/VFieldset/VFieldset.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +0 -5
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +54 -65
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/truncate.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +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 +0 -3
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.js +1 -1
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VHoverCard/VHoverCard.js.map +1 -1
- package/dist/components/VHoverCard/VHoverCard.vue_vue_type_script_setup_true_lang.js +2 -12
- package/dist/components/VHoverCard/VHoverCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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.js.map +1 -1
- package/dist/components/VIcon/icons/add.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_downward.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_downward_alt.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_drop_down.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_drop_up.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_left_alt.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_right_alt.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_upward.js.map +1 -1
- package/dist/components/VIcon/icons/arrow_upward_alt.js.map +1 -1
- package/dist/components/VIcon/icons/attach_file.js.map +1 -1
- package/dist/components/VIcon/icons/audio_file.js.map +1 -1
- package/dist/components/VIcon/icons/calendar_today.js.map +1 -1
- package/dist/components/VIcon/icons/check.js.map +1 -1
- package/dist/components/VIcon/icons/check_circle.js.map +1 -1
- package/dist/components/VIcon/icons/chevron_left.js.map +1 -1
- package/dist/components/VIcon/icons/chevron_right.js.map +1 -1
- package/dist/components/VIcon/icons/close.js.map +1 -1
- package/dist/components/VIcon/icons/cloud_upload.js.map +1 -1
- package/dist/components/VIcon/icons/code.js.map +1 -1
- package/dist/components/VIcon/icons/colorize.js.map +1 -1
- package/dist/components/VIcon/icons/description.js.map +1 -1
- package/dist/components/VIcon/icons/error.js.map +1 -1
- package/dist/components/VIcon/icons/expand_less.js.map +1 -1
- package/dist/components/VIcon/icons/expand_more.js.map +1 -1
- package/dist/components/VIcon/icons/first_page.js.map +1 -1
- package/dist/components/VIcon/icons/folder_zip.js.map +1 -1
- package/dist/components/VIcon/icons/image.js.map +1 -1
- package/dist/components/VIcon/icons/inbox.js.map +1 -1
- package/dist/components/VIcon/icons/index.js.map +1 -1
- package/dist/components/VIcon/icons/info.js.map +1 -1
- package/dist/components/VIcon/icons/last_page.js.map +1 -1
- package/dist/components/VIcon/icons/more_horiz.js.map +1 -1
- package/dist/components/VIcon/icons/names.js.map +1 -1
- package/dist/components/VIcon/icons/notifications.js.map +1 -1
- package/dist/components/VIcon/icons/open_in_new.js.map +1 -1
- package/dist/components/VIcon/icons/picture_as_pdf.js.map +1 -1
- package/dist/components/VIcon/icons/priority_high.js.map +1 -1
- package/dist/components/VIcon/icons/remove.js.map +1 -1
- package/dist/components/VIcon/icons/schedule.js.map +1 -1
- package/dist/components/VIcon/icons/search.js.map +1 -1
- package/dist/components/VIcon/icons/search_off.js.map +1 -1
- package/dist/components/VIcon/icons/star.js.map +1 -1
- package/dist/components/VIcon/icons/swap_vert.js.map +1 -1
- package/dist/components/VIcon/icons/table_chart.js.map +1 -1
- package/dist/components/VIcon/icons/video_file.js.map +1 -1
- package/dist/components/VIcon/icons/viewBox.js.map +1 -1
- package/dist/components/VIcon/icons/warning.js.map +1 -1
- package/dist/components/VIcon/resolver.js.map +1 -1
- package/dist/components/VIconButton/VIconButton.d.ts +0 -3
- package/dist/components/VIconButton/VIconButton.js.map +1 -1
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInput.css +1 -1
- package/dist/components/VInput/VInput.d.ts +0 -3
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +2 -8
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.d.ts +0 -3
- package/dist/components/VInput/VInputGroup.js.map +1 -1
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +0 -7
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/context.d.ts +0 -2
- package/dist/components/VInput/context.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +0 -3
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +2 -14
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VLink/VLink.js.map +1 -1
- package/dist/components/VLink/VLink.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VListbox/VListboxChevron.js.map +1 -1
- package/dist/components/VListbox/VListboxChevron.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VListbox/VListboxGroup.js.map +1 -1
- package/dist/components/VListbox/VListboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VListbox/VListboxOption.js.map +1 -1
- package/dist/components/VListbox/VListboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VListbox/VListboxPanel.d.ts +0 -3
- package/dist/components/VListbox/VListboxPanel.js.map +1 -1
- package/dist/components/VListbox/VListboxPanel.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VListbox/VListboxPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VListbox/VListboxSeparator.js.map +1 -1
- package/dist/components/VListbox/VListboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VListbox/VListboxValues.js.map +1 -1
- package/dist/components/VListbox/VListboxValues.vue_vue_type_script_setup_true_lang.js +4 -14
- package/dist/components/VListbox/VListboxValues.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenu.d.ts +0 -3
- package/dist/components/VMenu/VMenu.js.map +1 -1
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +6 -12
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.d.ts +0 -3
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +9 -16
- 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.js.map +1 -1
- package/dist/components/VMeter/VMeter.js.map +1 -1
- package/dist/components/VMeter/VMeter.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VNumberInput/VNumberInput.d.ts +0 -3
- package/dist/components/VNumberInput/VNumberInput.js.map +1 -1
- package/dist/components/VNumberInput/VNumberInput.vue_vue_type_script_setup_true_lang.js +85 -90
- package/dist/components/VNumberInput/VNumberInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VNumberInput/number.js.map +1 -1
- package/dist/components/VPagination/VPagination.d.ts +0 -3
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VPopover/VPopover.js.map +1 -1
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.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.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.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VRating/VRating.js.map +1 -1
- package/dist/components/VRating/VRating.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VResizable/VResizable.js.map +1 -1
- package/dist/components/VResizable/VResizable.vue_vue_type_script_setup_true_lang.js +8 -6
- package/dist/components/VResizable/VResizable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VResizable/VResizablePanel.js.map +1 -1
- package/dist/components/VResizable/VResizablePanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VResizable/context.js.map +1 -1
- package/dist/components/VResizable/layout.js.map +1 -1
- package/dist/components/VSelect/VSelect.d.ts +0 -3
- package/dist/components/VSelect/VSelect.js.map +1 -1
- package/dist/components/VSelect/VSelect.vue_vue_type_script_setup_true_lang.js +80 -90
- package/dist/components/VSelect/VSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.d.ts +0 -6
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +2 -11
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +1 -5
- 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 +0 -3
- package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +107 -112
- 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.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSplitButton/VSplitButton.d.ts +0 -3
- package/dist/components/VSplitButton/VSplitButton.js.map +1 -1
- package/dist/components/VSplitButton/VSplitButton.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VSplitButton/VSplitButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VStepper/VStepper.js.map +1 -1
- package/dist/components/VStepper/VStepper.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.js.map +1 -1
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +0 -2
- 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.css +1 -1
- package/dist/components/VTabs/VTabs.d.ts +0 -3
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +2 -15
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/context.d.ts +0 -2
- package/dist/components/VTabs/context.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +0 -6
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +0 -6
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.d.ts +0 -3
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +197 -204
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/search.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/limits.js.map +1 -1
- package/dist/components/VTimeline/VTimeline.js.map +1 -1
- package/dist/components/VTimeline/VTimeline.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeline/VTimelineItem.js.map +1 -1
- package/dist/components/VTimeline/VTimelineItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeline/context.js.map +1 -1
- package/dist/components/VTimeline/datetime.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 +1 -1
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/components/VToggle/VToggle.d.ts +0 -3
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +1 -13
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTreeView/VTreeView.js.map +1 -1
- package/dist/components/VTreeView/VTreeView.vue_vue_type_script_setup_true_lang.js +1 -5
- package/dist/components/VTreeView/VTreeView.vue_vue_type_script_setup_true_lang.js.map +1 -1
- 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/components/VVirtualList/VVirtualList.js.map +1 -1
- package/dist/components/VVirtualList/VVirtualList.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/useAriaLabel.js.map +1 -1
- package/dist/composables/useChoice.js.map +1 -1
- package/dist/composables/useClearable.js.map +1 -1
- package/dist/composables/useControlShape.d.ts +1 -5
- package/dist/composables/useControlShape.js +0 -1
- package/dist/composables/useControlShape.js.map +1 -1
- package/dist/composables/useDebouncedSearch.js.map +1 -1
- package/dist/composables/useDetailsOpen.js.map +1 -1
- package/dist/composables/useEscapeDismiss.js.map +1 -1
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.js +3 -7
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileDrop.js.map +1 -1
- package/dist/composables/useFileField.js +2 -2
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useFocusoutDismiss.js.map +1 -1
- package/dist/composables/useHotkeyListener.js.map +1 -1
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/composables/useInertLink.js.map +1 -1
- package/dist/composables/useInfiniteScroll.js +2 -2
- package/dist/composables/useInfiniteScroll.js.map +1 -1
- package/dist/composables/useListbox.d.ts +0 -1
- package/dist/composables/useListbox.js +4 -10
- package/dist/composables/useListbox.js.map +1 -1
- package/dist/composables/useLiveAnnouncer.js.map +1 -1
- package/dist/composables/useMaskedField.js +28 -48
- package/dist/composables/useMaskedField.js.map +1 -1
- package/dist/composables/useModalDialog.js +1 -1
- package/dist/composables/useModalDialog.js.map +1 -1
- package/dist/composables/usePopover.js +1 -1
- package/dist/composables/usePopover.js.map +1 -1
- package/dist/composables/useProgressValue.js.map +1 -1
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useSlotNodes.js.map +1 -1
- package/dist/composables/useTextLimit.js.map +1 -1
- package/dist/composables/useTimer.js.map +1 -1
- package/dist/composables/useTypeahead.js.map +1 -1
- package/dist/composables/useVirtualList.js.map +1 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tokens/css.js.map +1 -1
- package/dist/tokens/index.d.ts +3 -0
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/primitives.js.map +1 -1
- package/dist/tokens/semantic.d.ts +3 -0
- package/dist/tokens/semantic.js +6 -5
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens/types.js.map +1 -1
- package/dist/tokens.json +12 -5
- package/dist/types.d.ts +0 -2
- package/dist/utils/array.js.map +1 -1
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/attrs.js.map +1 -1
- package/dist/utils/chip.d.ts +4 -6
- package/dist/utils/chip.js +8 -5
- package/dist/utils/chip.js.map +1 -1
- package/dist/utils/clock.js +3 -1
- package/dist/utils/clock.js.map +1 -1
- package/dist/utils/css.js +1 -1
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/direction.js.map +1 -1
- package/dist/utils/env.js.map +1 -1
- package/dist/utils/file.js +13 -31
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/focus.js.map +1 -1
- package/dist/utils/hostWarns.js.map +1 -1
- package/dist/utils/ids.js.map +1 -1
- package/dist/utils/matcher.js.map +1 -1
- package/dist/utils/memo.js.map +1 -1
- package/dist/utils/number.js.map +1 -1
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/vnode.js.map +1 -1
- package/package.json +19 -19
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VTabs.js","names":[],"sources":["../../../src/components/VTabs/VTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @a11y @core\n/**\n * Implement ARIA tab selection and roving focus over native buttons. Overflow measurements\n * control scrolling arrows because CSS cannot expose overflow state.\n */\n\nimport { computed, onMounted, provide, ref, useId, useSlots, watch } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_left as chevronLeftIcon } from '../VIcon/icons/chevron_left'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { expand_less as expandLessIcon } from '../VIcon/icons/expand_less'\nimport { expand_more as expandMoreIcon } from '../VIcon/icons/expand_more'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport { panelIdFor, tabIdFor, tabsKey } from './context'\n\nimport type { ItemValue } from '../../types'\nimport { arrowNavigate, navigableItems } from '../../utils/arrowNav'\nimport { isRtl } from '../../utils/direction'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/**\n * How the tabs are set off from the page: a rule under the tabs alone, the same inside a card\n * framed by a border, a shadow or a muted fill, or a sunken track.\n */\nexport type TabsVariant = 'flat' | 'outline' | 'elevated' | 'filled' | 'inset'\n/** The colour of the selected tab. */\nexport type TabsTone = 'accent' | 'neutral' | 'danger'\n/** The height of the tabs, from the scale every control shares. */\nexport type TabsSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n/** Whether the tabs run across the page or down its side. */\nexport type TabsOrientation = 'horizontal' | 'vertical'\n/** Where the tabs sit along the bar when they do not fill it. */\nexport type TabsAlign = 'start' | 'center' | 'end'\n/** Whether a tab is selected when it takes the focus, or only when it is activated. */\nexport type TabsActivation = 'manual' | 'automatic'\n\ninterface TabsProps {\n /**\n * How the tabs are set off from the page. `flat`, the default, draws a rule under the tabs\n * and nothing around them. `outline`, `elevated` and `filled` put the tabs and their panels in\n * a card with the same rule inside it: a border on the page surface, a shadow over a raised\n * surface, or a muted fill. `inset` draws the tabs as segments of a sunken track.\n */\n variant?: TabsVariant\n /** The colour the selected tab takes. The others stay neutral whatever this says. */\n tone?: TabsTone\n /** The height of the tabs, from the scale shared by every control. */\n size?: TabsSize\n /** Takes 4px off the height of every tab. */\n compact?: boolean\n /** Whether the tabs run across the page or down its side. */\n orientation?: TabsOrientation\n /** Where the tabs sit along the bar when they do not fill it. */\n align?: TabsAlign\n /**\n * Stretches the tabs across the whole bar, every tab taking an equal share of it, on the\n * terms of VButtonGroup's own `fullWidth`.\n */\n fullWidth?: boolean\n /**\n * Adds a button at each end of the bar to scroll it, each disabled once that end is\n * reached. It only makes sense when the tabs can overflow, so it excludes `fullWidth`.\n */\n scrollButtons?: boolean\n /** The icon of the button scrolling backwards. It follows the orientation by default. */\n prevIcon?: IconSource\n /** The icon of the button scrolling forwards. It follows the orientation by default. */\n nextIcon?: IconSource\n /** What the backward scroll button does, in words. It falls back to the dictionary. */\n prevLabel?: string\n /** What the forward scroll button does, in words. It falls back to the dictionary. */\n nextLabel?: string\n /** Whether moving to a tab also selects it. */\n activation?: TabsActivation\n /**\n * Makes every tab unusable, and the scroll buttons with them: the tabs leave the tab order\n * and grey out through the colour tokens.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row of tabs. It falls back to the design\n * system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<TabsProps>(), {\n variant: 'flat',\n tone: 'accent',\n size: 'md',\n compact: false,\n orientation: 'horizontal',\n align: 'start',\n fullWidth: false,\n scrollButtons: false,\n prevIcon: undefined,\n nextIcon: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n activation: 'manual',\n disabled: false,\n label: undefined,\n})\n\ndefineSlots<{\n /** The tabs themselves. */\n default(): unknown\n /**\n * The panels the tabs show. Leaving it out renders no panel area at all, which is\n * how the same component serves as a plain bar or a segmented control.\n */\n panels?(): unknown\n}>()\n\n/**\n * The `value` of the selected tab. There is deliberately no default: the component cannot know\n * which of the tabs a consumer wrote should open.\n */\nconst model = defineModel<ItemValue>()\n\n// The root element is only a container; the one that matters is the row of tabs. So `class` and\n// `style` stay outside, where a consumer expects to place the component, and everything else;\n// the id, the aria-* naming it, the data-*; goes down onto the row itself.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs: listAttrs } = useRootAttrs()\n// The prop wins over the dictionary, and above both, a consumer's own aria-label or\n// aria-labelledby still wins; that arbitration is what `useAriaLabel` is for.\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.tabs.label)\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? m.value.tabs.previous)\nconst resolvedNextLabel = computed(() => props.nextLabel ?? m.value.tabs.next)\n\nconst slots = useSlots()\nconst baseId = useId()\n\n// Everything is exposed through getters rather than as values: that is what keeps the\n// root's props reactive on the other side of the injection, so a tab re-renders when\n// the size or the tone changes.\nprovide(tabsKey, {\n get value() {\n return model.value\n },\n select(value: ItemValue) {\n model.value = value\n },\n tabId: (value: ItemValue) => tabIdFor(baseId, value),\n panelId: (value: ItemValue) => panelIdFor(baseId, value),\n // @ssr\n /*\n * A registry the panels filled in as they mounted would instead read as empty on the server\n * and full on the client, which is a hydration mismatch. The trade-off is that this is not\n * reactive: the panels slot must be there or not there, and cannot appear halfway through the\n * life of the component.\n */\n get hasPanels() {\n return slots.panels !== undefined\n },\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get activation() {\n return props.activation\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst resolvedPrevIcon = computed(\n () => props.prevIcon ?? (isVertical.value ? expandLessIcon : chevronLeftIcon),\n)\nconst resolvedNextIcon = computed(\n () => props.nextIcon ?? (isVertical.value ? expandMoreIcon : chevronRightIcon),\n)\n\nconst listEl = ref<HTMLElement | null>(null)\n\n// @keyboard @a11y\n/*\n * This handler only moves the focus. Selecting the tab that receives it; what `automatic`\n * activation means; is done by the tab itself, which knows its own typed value and therefore\n * never has to send it back through a DOM attribute.\n */\nfunction onKeydown(event: KeyboardEvent) {\n const list = listEl.value\n if (!list) return\n arrowNavigate(event, list, () => navigableItems(list, '[role=\"tab\"]:not(:disabled)'), {\n vertical: isVertical.value,\n })\n}\n\n/*\n * CSS can ask the question; VDialog uses that query for its scroll shadows; and still cannot\n * answer this one, for two independent reasons. It is Chrome/Edge 133+, with neither Safari nor\n * Firefox shipping it, and where VDialog's hairlines merely stay invisible without it these\n * buttons are functional; an `@supports` guard would mean keeping everything below anyway.\n */\nconst startSentinelEl = ref<HTMLElement | null>(null)\nconst endSentinelEl = ref<HTMLElement | null>(null)\nconst atStart = ref(true)\nconst atEnd = ref(true)\n\nwatch(\n [listEl, startSentinelEl, endSentinelEl],\n ([root, start, end], _previous, onCleanup) => {\n // @fallback\n // The observer exists neither on the server nor in the unit-test environment, so\n // its absence has to be tolerated rather than assumed away. The behaviour of the\n // ends is checked in a real browser instead, by the play functions.\n if (!root || !start || !end || typeof IntersectionObserver === 'undefined') return\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.target === start) atStart.value = entry.isIntersecting\n else atEnd.value = entry.isIntersecting\n }\n },\n { root },\n )\n observer.observe(start)\n observer.observe(end)\n onCleanup(() => observer.disconnect())\n },\n // `post` so the effect runs once the DOM is up to date: in the default timing the three\n // template refs are still null on the first pass, the guard above returns, and the buttons\n // stay disabled for good; the effect having tracked nothing to wake it again.\n { flush: 'post' },\n)\n\n// @a11y\n/*\n * The default `pre` timing is the point: the button still holds the focus when this runs, the\n * DOM not being patched with `disabled` yet. In `post` timing the focus would already be on\n * <body>, with nothing left to say which control lost it.\n */\nwatch([atStart, atEnd], ([start, end]) => {\n const list = listEl.value\n const prev = list?.previousElementSibling as HTMLElement | null\n const next = list?.nextElementSibling as HTMLElement | null\n const focused = document.activeElement\n const fromPrev = focused === prev\n if (!(fromPrev ? start : focused === next && end)) return\n if (fromPrev ? end : start) focusSelected()\n else (fromPrev ? next : prev)?.focus()\n})\n\n/** Scrolls the bar by most of its own length. */\nfunction scrollStep(direction: -1 | 1) {\n const list = listEl.value\n if (!list) return\n // Called optionally because the unit-test environment implements no scrolling at\n // all; this behaviour is covered in a real browser.\n if (isVertical.value) {\n list.scrollBy?.({ top: direction * list.clientHeight * 0.8 })\n return\n }\n // The horizontal offset is physical, not logical: in a right-to-left page, moving\n // forward means moving left, so the direction has to be flipped by hand.\n const rtl = isRtl(list)\n list.scrollBy?.({ left: direction * list.clientWidth * 0.8 * (rtl ? -1 : 1) })\n}\n\n// @a11y @core\n/*\n * The keyboard needs nothing here: it moves the focus, and the browser scrolls a focused\n * element into view by itself. The scrolling is confined to the row rather than asking the\n * element to reveal itself, which would scroll every scrollable ancestor up to the page.\n */\nfunction revealSelected(behavior?: ScrollBehavior) {\n const list = listEl.value\n const tab = list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\n if (!list || !tab) return\n const t = tab.getBoundingClientRect()\n const c = list.getBoundingClientRect()\n list.scrollBy?.({\n left: t.left < c.left ? t.left - c.left : t.right > c.right ? t.right - c.right : 0,\n top: t.top < c.top ? t.top - c.top : t.bottom > c.bottom ? t.bottom - c.bottom : 0,\n behavior,\n })\n}\n\n// `post`, so the tab that is now selected already carries `aria-selected` when it is looked\n// for: in the default timing the query would find the one that was selected before.\nwatch(model, () => revealSelected(), { flush: 'post' })\nonMounted(() => revealSelected('instant'))\n\n// `focus` goes where the Tab key would land: the selected tab, which holds the row's single tab\n// stop.\nfunction focusSelected(options?: FocusOptions) {\n const list = listEl.value\n const tab =\n list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]:not(:disabled)') ??\n list?.querySelector<HTMLElement>('[role=\"tab\"]:not(:disabled)')\n tab?.focus(options)\n}\n\ndefineExpose({\n /**\n * Moves the focus to the selected tab, or to the first tab that can take it when the\n * v-model names none.\n */\n focus: focusSelected,\n /** The `role=\"tablist\"` row, which is also where the consumer's attributes land. */\n el: listEl,\n})\n</script>\n\n<template>\n <div\n :class=\"['v-tabs', rootClass]\"\n :style=\"rootStyle\"\n :data-variant=\"variant\"\n :data-orientation=\"orientation\"\n :data-align=\"align\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-compact=\"compact ? '' : undefined\"\n >\n <div class=\"v-tabs-bar\">\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedPrevLabel\"\n :size=\"size\"\n :compact=\"compact\"\n :disabled=\"disabled || atStart\"\n @click=\"scrollStep(-1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedPrevIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n\n <div\n ref=\"listEl\"\n :aria-orientation=\"isVertical ? 'vertical' : undefined\"\n v-bind=\"listAttrs\"\n class=\"v-tabs-list\"\n role=\"tablist\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n >\n <!-- The two markers watched to know whether an end of the bar is reached.\n They are hidden from assistive technology: a row of tabs may contain\n nothing but tabs. -->\n <span\n v-if=\"scrollButtons\"\n ref=\"startSentinelEl\"\n class=\"v-tabs-sentinel\"\n aria-hidden=\"true\"\n />\n <slot />\n <span v-if=\"scrollButtons\" ref=\"endSentinelEl\" class=\"v-tabs-sentinel\" aria-hidden=\"true\" />\n </div>\n\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedNextLabel\"\n :size=\"size\"\n :compact=\"compact\"\n :disabled=\"disabled || atEnd\"\n @click=\"scrollStep(1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedNextIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n </div>\n\n <div v-if=\"$slots.panels\" class=\"v-tabs-panels\">\n <slot name=\"panels\" />\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-tabs {\n /*\n * The panels' padding, and the four pixels the compact density takes off it. The delta is\n * held apart from the value rather than the two being written out, so the density stays one\n * subtraction wherever the padding is read instead of a second table to keep in step; the\n * VAccordion idiom.\n */\n --tabs-pad-delta: 0px;\n --tabs-panels-pad: calc(var(--vectis-space-4) - var(--tabs-pad-delta));\n\n display: flex;\n flex-direction: column;\n font-family: var(--vectis-text-family);\n }\n\n .v-tabs[data-compact] {\n --tabs-pad-delta: var(--vectis-space-1);\n }\n\n .v-tabs[data-orientation='vertical'] {\n flex-direction: row;\n }\n\n /*\n * Keep card inner-radius calculations aligned with the tab corners; avoid clipping panel\n * focus rings and edge-to-edge consumer content. The border is transparent rather than absent\n * on `filled`: forced colours paint it. `elevated` has none, so that the rule under the tabs\n * reaches the edge its shadow draws; its end tabs then take the full radius.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']) {\n --tabs-corner-radius: calc(var(--vectis-radius-surface) - 1px);\n\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-variant='outline'] {\n background: var(--vectis-color-surface);\n border-color: var(--vectis-color-border);\n }\n\n .v-tabs[data-variant='elevated'] {\n --tabs-corner-radius: var(--vectis-radius-surface);\n\n border: none;\n background: var(--vectis-color-surface-raised);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-tabs[data-variant='filled'] {\n background: var(--vectis-color-surface-muted);\n }\n\n /* A neutral ghost hovers with the muted surface, which would vanish on this fill: the\n unselected tabs and the scroll buttons take a shade darker instead. */\n .v-tabs[data-variant='filled'] > .v-tabs-bar [data-tone='neutral'] {\n --tone-bg-soft: color-mix(\n in oklab,\n var(--vectis-color-surface-muted),\n var(--vectis-color-text) 4%\n );\n }\n\n .v-tabs-bar {\n display: flex;\n /* Stretched rather than centred: the row deliberately overlaps the track by one\n pixel through a negative margin, and centring would displace it by half of\n that. */\n align-items: stretch;\n /*\n * The row is allowed to shrink below its content as soon as the tabs overflow, so there\n * would be nothing left to distribute; and setting an alignment on a scrolling container\n * makes whatever overflows past its start edge permanently unreachable, since a scroll\n * position cannot go negative.\n */\n justify-content: flex-start;\n gap: var(--vectis-space-1);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar {\n flex-direction: column;\n }\n\n .v-tabs[data-align='center'] > .v-tabs-bar {\n justify-content: center;\n }\n\n .v-tabs[data-align='end'] > .v-tabs-bar {\n justify-content: flex-end;\n }\n\n /*\n * Inside a card it is what separates the tabs from the panels; which is why that edge is left\n * without a gutter, since otherwise the rule would separate nothing from nothing. The variants\n * with a track are named explicitly rather than excluding `inset`: a new variant will have to\n * opt in by hand instead of inheriting this silently.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar {\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n /*\n * This block has exactly the same specificity as the one above, so it is the ORDER that\n * decides. Moving it up would leave the vertical framed case with two rules on one side and\n * none between the two areas.\n */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-inline-start: none;\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs-list {\n display: flex;\n align-items: center;\n /* Held in a variable because the sentinels cancel it beside themselves. */\n --tabs-list-gap: var(--vectis-space-1);\n gap: var(--tabs-list-gap);\n overflow: auto;\n /* Without these the row refuses to shrink below the width of its tabs, and it\n would widen the whole bar instead of scrolling. */\n min-inline-size: 0;\n min-block-size: 0;\n scrollbar-width: none;\n scroll-behavior: smooth;\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar > .v-tabs-list {\n flex-direction: column;\n align-items: stretch;\n }\n\n /* Pulling the row one pixel into the track is what lets the selected tab's indicator\n COVER that line rather than sit on top of it, which would read as a thicker rule.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: -1px;\n }\n\n /* The row is now a pixel taller than the tabs it holds, so they are pushed against\n its end edge; the indicator each tab draws there then falls exactly on the\n track. */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list {\n align-items: flex-end;\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n margin-inline-start: -1px;\n }\n\n /* Same specificity as the rule above, so again it is the order that decides. */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-inline-start: 0;\n }\n\n /*\n * This is the one place the card may clip, and the one place it must. Nothing is lost to the\n * clip: there is no panel, hence no outer focus ring, and a tab draws its own ring inwards.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n ) {\n overflow: clip;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-end: none;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n }\n\n /* The hollow track of the segmented variant is drawn on the scrolling row itself,\n and not on the bar around it: its padding is what keeps the raised tab's shadow\n from being clipped as the row scrolls. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list {\n background: var(--vectis-color-surface-sunken);\n padding: var(--vectis-space-1);\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list {\n flex: 1;\n }\n\n /*\n * A pixel square each, at the very ends of the row. They are items of the row, so its gap\n * opens beside them as it does between two tabs: each pulls its neighbour back over that gap\n * and over its own pixel, so the first and last tabs sit exactly where the row's padding puts\n * them.\n */\n .v-tabs-sentinel {\n flex: 0 0 1px;\n align-self: flex-start;\n inline-size: 1px;\n block-size: 1px;\n }\n\n .v-tabs-sentinel:first-child {\n margin-inline-end: calc(-1px - var(--tabs-list-gap));\n margin-block-end: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-sentinel:last-child {\n margin-inline-start: calc(-1px - var(--tabs-list-gap));\n margin-block-start: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-scroll {\n flex: none;\n /*\n * The bar stretches its children for the track's sake, so the scroll buttons have to\n * re-centre themselves on the row; which in the segmented variant is taller than they are.\n */\n align-self: center;\n }\n\n .v-tabs-panels {\n min-inline-size: 0;\n }\n\n /*\n * The axis follows the ORIENTATION and not the writing mode, so it cannot be a single logical\n * declaration: the bar is above the panels when horizontal and beside them when vertical.\n * Both unframed variants are named explicitly rather than excluding the framed ones, as in\n * the track rules above: a new variant opts in by hand.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='horizontal']\n > .v-tabs-panels {\n padding-block-start: var(--tabs-panels-pad);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='vertical']\n > .v-tabs-panels {\n padding-inline-start: var(--tabs-panels-pad);\n }\n\n /* Inside a card the gutter is the panels' alone, on all four sides: the bar spends\n none, so the tabs and their track reach the frame and the content is the only thing\n set back from it. */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled'])\n > .v-tabs-panels {\n padding: var(--tabs-panels-pad);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-panels {\n flex: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs-list {\n scroll-behavior: auto;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VTabs.js","names":["_sfc_main","VTabs_vue_vue_type_script_setup_true_lang_default","VTabs_default"],"sources":["../../../src/components/VTabs/VTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @a11y @core\n/**\n * Implement ARIA tab selection and roving focus over native buttons. Overflow measurements\n * control scrolling arrows because CSS cannot expose overflow state.\n */\n\nimport { computed, onMounted, provide, ref, useId, useSlots, watch } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_left as chevronLeftIcon } from '../VIcon/icons/chevron_left'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { expand_less as expandLessIcon } from '../VIcon/icons/expand_less'\nimport { expand_more as expandMoreIcon } from '../VIcon/icons/expand_more'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport { panelIdFor, tabIdFor, tabsKey } from './context'\n\nimport type { ItemValue } from '../../types'\nimport { arrowNavigate, navigableItems } from '../../utils/arrowNav'\nimport { isRtl } from '../../utils/direction'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/**\n * How the tabs are set off from the page: a rule under the tabs alone, the same inside a card\n * framed by a border, a shadow or a muted fill, or a sunken track.\n */\nexport type TabsVariant = 'flat' | 'outline' | 'elevated' | 'filled' | 'inset'\n/** The colour of the selected tab. */\nexport type TabsTone = 'accent' | 'neutral' | 'danger'\n/** The height of the tabs, from the scale every control shares. */\nexport type TabsSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n/** Whether the tabs run across the page or down its side. */\nexport type TabsOrientation = 'horizontal' | 'vertical'\n/** Where the tabs sit along the bar when they do not fill it. */\nexport type TabsAlign = 'start' | 'center' | 'end'\n/** Whether a tab is selected when it takes the focus, or only when it is activated. */\nexport type TabsActivation = 'manual' | 'automatic'\n\ninterface TabsProps {\n /**\n * How the tabs are set off from the page. `flat`, the default, draws a rule under the tabs\n * and nothing around them. `outline`, `elevated` and `filled` put the tabs and their panels in\n * a card with the same rule inside it: a border on the page surface, a shadow over a raised\n * surface, or a muted fill. `inset` draws the tabs as segments of a sunken track.\n */\n variant?: TabsVariant\n /** The colour the selected tab takes. The others stay neutral whatever this says. */\n tone?: TabsTone\n /** The height of the tabs, from the scale shared by every control. */\n size?: TabsSize\n /** Whether the tabs run across the page or down its side. */\n orientation?: TabsOrientation\n /** Where the tabs sit along the bar when they do not fill it. */\n align?: TabsAlign\n /**\n * Stretches the tabs across the whole bar, every tab taking an equal share of it, on the\n * terms of VButtonGroup's own `fullWidth`.\n */\n fullWidth?: boolean\n /**\n * Adds a button at each end of the bar to scroll it, each disabled once that end is\n * reached. It only makes sense when the tabs can overflow, so it excludes `fullWidth`.\n */\n scrollButtons?: boolean\n /** The icon of the button scrolling backwards. It follows the orientation by default. */\n prevIcon?: IconSource\n /** The icon of the button scrolling forwards. It follows the orientation by default. */\n nextIcon?: IconSource\n /** What the backward scroll button does, in words. It falls back to the dictionary. */\n prevLabel?: string\n /** What the forward scroll button does, in words. It falls back to the dictionary. */\n nextLabel?: string\n /** Whether moving to a tab also selects it. */\n activation?: TabsActivation\n /**\n * Makes every tab unusable, and the scroll buttons with them: the tabs leave the tab order\n * and grey out through the colour tokens.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row of tabs. It falls back to the design\n * system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<TabsProps>(), {\n variant: 'flat',\n tone: 'accent',\n size: 'md',\n orientation: 'horizontal',\n align: 'start',\n fullWidth: false,\n scrollButtons: false,\n prevIcon: undefined,\n nextIcon: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n activation: 'manual',\n disabled: false,\n label: undefined,\n})\n\ndefineSlots<{\n /** The tabs themselves. */\n default(): unknown\n /**\n * The panels the tabs show. Leaving it out renders no panel area at all, which is\n * how the same component serves as a plain bar or a segmented control.\n */\n panels?(): unknown\n}>()\n\n/**\n * The `value` of the selected tab. There is deliberately no default: the component cannot know\n * which of the tabs a consumer wrote should open.\n */\nconst model = defineModel<ItemValue>()\n\n// The root element is only a container; the one that matters is the row of tabs. So `class` and\n// `style` stay outside, where a consumer expects to place the component, and everything else;\n// the id, the aria-* naming it, the data-*; goes down onto the row itself.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs: listAttrs } = useRootAttrs()\n// The prop wins over the dictionary, and above both, a consumer's own aria-label or\n// aria-labelledby still wins; that arbitration is what `useAriaLabel` is for.\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.tabs.label)\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? m.value.tabs.previous)\nconst resolvedNextLabel = computed(() => props.nextLabel ?? m.value.tabs.next)\n\nconst slots = useSlots()\nconst baseId = useId()\n\n// Everything is exposed through getters rather than as values: that is what keeps the\n// root's props reactive on the other side of the injection, so a tab re-renders when\n// the size or the tone changes.\nprovide(tabsKey, {\n get value() {\n return model.value\n },\n select(value: ItemValue) {\n model.value = value\n },\n tabId: (value: ItemValue) => tabIdFor(baseId, value),\n panelId: (value: ItemValue) => panelIdFor(baseId, value),\n // @ssr\n /*\n * A registry the panels filled in as they mounted would instead read as empty on the server\n * and full on the client, which is a hydration mismatch. The trade-off is that this is not\n * reactive: the panels slot must be there or not there, and cannot appear halfway through the\n * life of the component.\n */\n get hasPanels() {\n return slots.panels !== undefined\n },\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get activation() {\n return props.activation\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst resolvedPrevIcon = computed(\n () => props.prevIcon ?? (isVertical.value ? expandLessIcon : chevronLeftIcon),\n)\nconst resolvedNextIcon = computed(\n () => props.nextIcon ?? (isVertical.value ? expandMoreIcon : chevronRightIcon),\n)\n\nconst listEl = ref<HTMLElement | null>(null)\n\n// @keyboard @a11y\n/*\n * This handler only moves the focus. Selecting the tab that receives it; what `automatic`\n * activation means; is done by the tab itself, which knows its own typed value and therefore\n * never has to send it back through a DOM attribute.\n */\nfunction onKeydown(event: KeyboardEvent) {\n const list = listEl.value\n if (!list) return\n arrowNavigate(event, list, () => navigableItems(list, '[role=\"tab\"]:not(:disabled)'), {\n vertical: isVertical.value,\n })\n}\n\n/*\n * CSS can ask the question; VDialog uses that query for its scroll shadows; and still cannot\n * answer this one, for two independent reasons. It is Chrome/Edge 133+, with neither Safari nor\n * Firefox shipping it, and where VDialog's hairlines merely stay invisible without it these\n * buttons are functional; an `@supports` guard would mean keeping everything below anyway.\n */\nconst startSentinelEl = ref<HTMLElement | null>(null)\nconst endSentinelEl = ref<HTMLElement | null>(null)\nconst atStart = ref(true)\nconst atEnd = ref(true)\n\nwatch(\n [listEl, startSentinelEl, endSentinelEl],\n ([root, start, end], _previous, onCleanup) => {\n // @fallback\n // The observer exists neither on the server nor in the unit-test environment, so\n // its absence has to be tolerated rather than assumed away. The behaviour of the\n // ends is checked in a real browser instead, by the play functions.\n if (!root || !start || !end || typeof IntersectionObserver === 'undefined') return\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.target === start) atStart.value = entry.isIntersecting\n else atEnd.value = entry.isIntersecting\n }\n },\n { root },\n )\n observer.observe(start)\n observer.observe(end)\n onCleanup(() => observer.disconnect())\n },\n // `post` so the effect runs once the DOM is up to date: in the default timing the three\n // template refs are still null on the first pass, the guard above returns, and the buttons\n // stay disabled for good; the effect having tracked nothing to wake it again.\n { flush: 'post' },\n)\n\n// @a11y\n/*\n * The default `pre` timing is the point: the button still holds the focus when this runs, the\n * DOM not being patched with `disabled` yet. In `post` timing the focus would already be on\n * <body>, with nothing left to say which control lost it.\n */\nwatch([atStart, atEnd], ([start, end]) => {\n const list = listEl.value\n const prev = list?.previousElementSibling as HTMLElement | null\n const next = list?.nextElementSibling as HTMLElement | null\n const focused = document.activeElement\n const fromPrev = focused === prev\n if (!(fromPrev ? start : focused === next && end)) return\n if (fromPrev ? end : start) focusSelected()\n else (fromPrev ? next : prev)?.focus()\n})\n\n/** Scrolls the bar by most of its own length. */\nfunction scrollStep(direction: -1 | 1) {\n const list = listEl.value\n if (!list) return\n // Called optionally because the unit-test environment implements no scrolling at\n // all; this behaviour is covered in a real browser.\n if (isVertical.value) {\n list.scrollBy?.({ top: direction * list.clientHeight * 0.8 })\n return\n }\n // The horizontal offset is physical, not logical: in a right-to-left page, moving\n // forward means moving left, so the direction has to be flipped by hand.\n const rtl = isRtl(list)\n list.scrollBy?.({ left: direction * list.clientWidth * 0.8 * (rtl ? -1 : 1) })\n}\n\n// @a11y @core\n/*\n * The keyboard needs nothing here: it moves the focus, and the browser scrolls a focused\n * element into view by itself. The scrolling is confined to the row rather than asking the\n * element to reveal itself, which would scroll every scrollable ancestor up to the page.\n */\nfunction revealSelected(behavior?: ScrollBehavior) {\n const list = listEl.value\n const tab = list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\n if (!list || !tab) return\n const t = tab.getBoundingClientRect()\n const c = list.getBoundingClientRect()\n list.scrollBy?.({\n left: t.left < c.left ? t.left - c.left : t.right > c.right ? t.right - c.right : 0,\n top: t.top < c.top ? t.top - c.top : t.bottom > c.bottom ? t.bottom - c.bottom : 0,\n behavior,\n })\n}\n\n// `post`, so the tab that is now selected already carries `aria-selected` when it is looked\n// for: in the default timing the query would find the one that was selected before.\nwatch(model, () => revealSelected(), { flush: 'post' })\nonMounted(() => revealSelected('instant'))\n\n// `focus` goes where the Tab key would land: the selected tab, which holds the row's single tab\n// stop.\nfunction focusSelected(options?: FocusOptions) {\n const list = listEl.value\n const tab =\n list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]:not(:disabled)') ??\n list?.querySelector<HTMLElement>('[role=\"tab\"]:not(:disabled)')\n tab?.focus(options)\n}\n\ndefineExpose({\n /**\n * Moves the focus to the selected tab, or to the first tab that can take it when the\n * v-model names none.\n */\n focus: focusSelected,\n /** The `role=\"tablist\"` row, which is also where the consumer's attributes land. */\n el: listEl,\n})\n</script>\n\n<template>\n <div\n :class=\"['v-tabs', rootClass]\"\n :style=\"rootStyle\"\n :data-variant=\"variant\"\n :data-orientation=\"orientation\"\n :data-align=\"align\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n >\n <div class=\"v-tabs-bar\">\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedPrevLabel\"\n :size=\"size\"\n :disabled=\"disabled || atStart\"\n @click=\"scrollStep(-1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedPrevIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n\n <div\n ref=\"listEl\"\n :aria-orientation=\"isVertical ? 'vertical' : undefined\"\n v-bind=\"listAttrs\"\n class=\"v-tabs-list\"\n role=\"tablist\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n >\n <!-- The two markers watched to know whether an end of the bar is reached.\n They are hidden from assistive technology: a row of tabs may contain\n nothing but tabs. -->\n <span\n v-if=\"scrollButtons\"\n ref=\"startSentinelEl\"\n class=\"v-tabs-sentinel\"\n aria-hidden=\"true\"\n />\n <slot />\n <span v-if=\"scrollButtons\" ref=\"endSentinelEl\" class=\"v-tabs-sentinel\" aria-hidden=\"true\" />\n </div>\n\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedNextLabel\"\n :size=\"size\"\n :disabled=\"disabled || atEnd\"\n @click=\"scrollStep(1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedNextIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n </div>\n\n <div v-if=\"$slots.panels\" class=\"v-tabs-panels\">\n <slot name=\"panels\" />\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-tabs {\n /* The panels' padding, less what the density takes off, as on VAccordion. */\n --tabs-panels-pad: calc(var(--vectis-space-4) - var(--vectis-density-delta));\n\n display: flex;\n flex-direction: column;\n font-family: var(--vectis-text-family);\n }\n\n .v-tabs[data-orientation='vertical'] {\n flex-direction: row;\n }\n\n /*\n * Keep card inner-radius calculations aligned with the tab corners; avoid clipping panel\n * focus rings and edge-to-edge consumer content. The border is transparent rather than absent\n * on `filled`: forced colours paint it. `elevated` has none, so that the rule under the tabs\n * reaches the edge its shadow draws; its end tabs then take the full radius.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']) {\n --tabs-corner-radius: calc(var(--vectis-radius-surface) - 1px);\n\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-variant='outline'] {\n background: var(--vectis-color-surface);\n border-color: var(--vectis-color-border);\n }\n\n .v-tabs[data-variant='elevated'] {\n --tabs-corner-radius: var(--vectis-radius-surface);\n\n border: none;\n background: var(--vectis-color-surface-raised);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-tabs[data-variant='filled'] {\n background: var(--vectis-color-surface-muted);\n }\n\n /* A neutral ghost hovers with the muted surface, which would vanish on this fill: the\n unselected tabs and the scroll buttons take a shade darker instead. */\n .v-tabs[data-variant='filled'] > .v-tabs-bar [data-tone='neutral'] {\n --tone-bg-soft: color-mix(\n in oklab,\n var(--vectis-color-surface-muted),\n var(--vectis-color-text) 4%\n );\n }\n\n .v-tabs-bar {\n display: flex;\n /* Stretched rather than centred: the row deliberately overlaps the track by one\n pixel through a negative margin, and centring would displace it by half of\n that. */\n align-items: stretch;\n /*\n * The row is allowed to shrink below its content as soon as the tabs overflow, so there\n * would be nothing left to distribute; and setting an alignment on a scrolling container\n * makes whatever overflows past its start edge permanently unreachable, since a scroll\n * position cannot go negative.\n */\n justify-content: flex-start;\n gap: var(--vectis-space-1);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar {\n flex-direction: column;\n }\n\n .v-tabs[data-align='center'] > .v-tabs-bar {\n justify-content: center;\n }\n\n .v-tabs[data-align='end'] > .v-tabs-bar {\n justify-content: flex-end;\n }\n\n /*\n * Inside a card it is what separates the tabs from the panels; which is why that edge is left\n * without a gutter, since otherwise the rule would separate nothing from nothing. The variants\n * with a track are named explicitly rather than excluding `inset`: a new variant will have to\n * opt in by hand instead of inheriting this silently.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar {\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n /*\n * This block has exactly the same specificity as the one above, so it is the ORDER that\n * decides. Moving it up would leave the vertical framed case with two rules on one side and\n * none between the two areas.\n */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-inline-start: none;\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs-list {\n display: flex;\n align-items: center;\n /* Held in a variable because the sentinels cancel it beside themselves. */\n --tabs-list-gap: var(--vectis-space-1);\n gap: var(--tabs-list-gap);\n overflow: auto;\n /* Without these the row refuses to shrink below the width of its tabs, and it\n would widen the whole bar instead of scrolling. */\n min-inline-size: 0;\n min-block-size: 0;\n scrollbar-width: none;\n scroll-behavior: smooth;\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar > .v-tabs-list {\n flex-direction: column;\n align-items: stretch;\n }\n\n /* Pulling the row one pixel into the track is what lets the selected tab's indicator\n COVER that line rather than sit on top of it, which would read as a thicker rule.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: -1px;\n }\n\n /* The row is now a pixel taller than the tabs it holds, so they are pushed against\n its end edge; the indicator each tab draws there then falls exactly on the\n track. */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list {\n align-items: flex-end;\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n margin-inline-start: -1px;\n }\n\n /* Same specificity as the rule above, so again it is the order that decides. */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-inline-start: 0;\n }\n\n /*\n * This is the one place the card may clip, and the one place it must. Nothing is lost to the\n * clip: there is no panel, hence no outer focus ring, and a tab draws its own ring inwards.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n ) {\n overflow: clip;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-end: none;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n }\n\n /* The hollow track of the segmented variant is drawn on the scrolling row itself,\n and not on the bar around it: its padding is what keeps the raised tab's shadow\n from being clipped as the row scrolls. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list {\n background: var(--vectis-color-surface-sunken);\n padding: var(--vectis-space-1);\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list {\n flex: 1;\n }\n\n /*\n * A pixel square each, at the very ends of the row. They are items of the row, so its gap\n * opens beside them as it does between two tabs: each pulls its neighbour back over that gap\n * and over its own pixel, so the first and last tabs sit exactly where the row's padding puts\n * them.\n */\n .v-tabs-sentinel {\n flex: 0 0 1px;\n align-self: flex-start;\n inline-size: 1px;\n block-size: 1px;\n }\n\n .v-tabs-sentinel:first-child {\n margin-inline-end: calc(-1px - var(--tabs-list-gap));\n margin-block-end: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-sentinel:last-child {\n margin-inline-start: calc(-1px - var(--tabs-list-gap));\n margin-block-start: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-scroll {\n flex: none;\n /*\n * The bar stretches its children for the track's sake, so the scroll buttons have to\n * re-centre themselves on the row; which in the segmented variant is taller than they are.\n */\n align-self: center;\n }\n\n .v-tabs-panels {\n min-inline-size: 0;\n }\n\n /*\n * The axis follows the ORIENTATION and not the writing mode, so it cannot be a single logical\n * declaration: the bar is above the panels when horizontal and beside them when vertical.\n * Both unframed variants are named explicitly rather than excluding the framed ones, as in\n * the track rules above: a new variant opts in by hand.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='horizontal']\n > .v-tabs-panels {\n padding-block-start: var(--tabs-panels-pad);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='vertical']\n > .v-tabs-panels {\n padding-inline-start: var(--tabs-panels-pad);\n }\n\n /* Inside a card the gutter is the panels' alone, on all four sides: the bar spends\n none, so the tabs and their track reach the frame and the content is the only thing\n set back from it. */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled'])\n > .v-tabs-panels {\n padding: var(--tabs-panels-pad);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-panels {\n flex: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs-list {\n scroll-behavior: auto;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -17,8 +17,7 @@ var A = [
|
|
|
17
17
|
"data-variant",
|
|
18
18
|
"data-orientation",
|
|
19
19
|
"data-align",
|
|
20
|
-
"data-full-width"
|
|
21
|
-
"data-compact"
|
|
20
|
+
"data-full-width"
|
|
22
21
|
], j = { class: "v-tabs-bar" }, M = ["aria-orientation", "aria-label"], N = {
|
|
23
22
|
key: 0,
|
|
24
23
|
class: "v-tabs-panels"
|
|
@@ -29,10 +28,6 @@ var A = [
|
|
|
29
28
|
variant: { default: "flat" },
|
|
30
29
|
tone: { default: "accent" },
|
|
31
30
|
size: { default: "md" },
|
|
32
|
-
compact: {
|
|
33
|
-
type: Boolean,
|
|
34
|
-
default: !1
|
|
35
|
-
},
|
|
36
31
|
orientation: { default: "horizontal" },
|
|
37
32
|
align: { default: "start" },
|
|
38
33
|
fullWidth: {
|
|
@@ -81,9 +76,6 @@ var A = [
|
|
|
81
76
|
get size() {
|
|
82
77
|
return P.size;
|
|
83
78
|
},
|
|
84
|
-
get compact() {
|
|
85
|
-
return P.compact;
|
|
86
|
-
},
|
|
87
79
|
get activation() {
|
|
88
80
|
return P.activation;
|
|
89
81
|
},
|
|
@@ -145,15 +137,13 @@ var A = [
|
|
|
145
137
|
"data-variant": v.variant,
|
|
146
138
|
"data-orientation": v.orientation,
|
|
147
139
|
"data-align": v.align,
|
|
148
|
-
"data-full-width": v.fullWidth ? "" : void 0
|
|
149
|
-
"data-compact": v.compact ? "" : void 0
|
|
140
|
+
"data-full-width": v.fullWidth ? "" : void 0
|
|
150
141
|
}, [g("div", j, [
|
|
151
142
|
v.scrollButtons ? (C(), p(i, {
|
|
152
143
|
key: 0,
|
|
153
144
|
class: "v-tabs-scroll",
|
|
154
145
|
label: V.value,
|
|
155
146
|
size: v.size,
|
|
156
|
-
compact: v.compact,
|
|
157
147
|
disabled: v.disabled || Y.value,
|
|
158
148
|
onClick: r[0] ||= (e) => Z(-1)
|
|
159
149
|
}, {
|
|
@@ -162,7 +152,6 @@ var A = [
|
|
|
162
152
|
}, 8, [
|
|
163
153
|
"label",
|
|
164
154
|
"size",
|
|
165
|
-
"compact",
|
|
166
155
|
"disabled"
|
|
167
156
|
])) : m("", !0),
|
|
168
157
|
g("div", b({
|
|
@@ -196,7 +185,6 @@ var A = [
|
|
|
196
185
|
class: "v-tabs-scroll",
|
|
197
186
|
label: H.value,
|
|
198
187
|
size: v.size,
|
|
199
|
-
compact: v.compact,
|
|
200
188
|
disabled: v.disabled || X.value,
|
|
201
189
|
onClick: r[1] ||= (e) => Z(1)
|
|
202
190
|
}, {
|
|
@@ -205,7 +193,6 @@ var A = [
|
|
|
205
193
|
}, 8, [
|
|
206
194
|
"label",
|
|
207
195
|
"size",
|
|
208
|
-
"compact",
|
|
209
196
|
"disabled"
|
|
210
197
|
])) : m("", !0)
|
|
211
198
|
]), e.$slots.panels ? (C(), h("div", N, [E(e.$slots, "panels")])) : m("", !0)], 14, A));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VTabs.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VTabs/VTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @a11y @core\n/**\n * Implement ARIA tab selection and roving focus over native buttons. Overflow measurements\n * control scrolling arrows because CSS cannot expose overflow state.\n */\n\nimport { computed, onMounted, provide, ref, useId, useSlots, watch } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_left as chevronLeftIcon } from '../VIcon/icons/chevron_left'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { expand_less as expandLessIcon } from '../VIcon/icons/expand_less'\nimport { expand_more as expandMoreIcon } from '../VIcon/icons/expand_more'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport { panelIdFor, tabIdFor, tabsKey } from './context'\n\nimport type { ItemValue } from '../../types'\nimport { arrowNavigate, navigableItems } from '../../utils/arrowNav'\nimport { isRtl } from '../../utils/direction'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/**\n * How the tabs are set off from the page: a rule under the tabs alone, the same inside a card\n * framed by a border, a shadow or a muted fill, or a sunken track.\n */\nexport type TabsVariant = 'flat' | 'outline' | 'elevated' | 'filled' | 'inset'\n/** The colour of the selected tab. */\nexport type TabsTone = 'accent' | 'neutral' | 'danger'\n/** The height of the tabs, from the scale every control shares. */\nexport type TabsSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n/** Whether the tabs run across the page or down its side. */\nexport type TabsOrientation = 'horizontal' | 'vertical'\n/** Where the tabs sit along the bar when they do not fill it. */\nexport type TabsAlign = 'start' | 'center' | 'end'\n/** Whether a tab is selected when it takes the focus, or only when it is activated. */\nexport type TabsActivation = 'manual' | 'automatic'\n\ninterface TabsProps {\n /**\n * How the tabs are set off from the page. `flat`, the default, draws a rule under the tabs\n * and nothing around them. `outline`, `elevated` and `filled` put the tabs and their panels in\n * a card with the same rule inside it: a border on the page surface, a shadow over a raised\n * surface, or a muted fill. `inset` draws the tabs as segments of a sunken track.\n */\n variant?: TabsVariant\n /** The colour the selected tab takes. The others stay neutral whatever this says. */\n tone?: TabsTone\n /** The height of the tabs, from the scale shared by every control. */\n size?: TabsSize\n /** Takes 4px off the height of every tab. */\n compact?: boolean\n /** Whether the tabs run across the page or down its side. */\n orientation?: TabsOrientation\n /** Where the tabs sit along the bar when they do not fill it. */\n align?: TabsAlign\n /**\n * Stretches the tabs across the whole bar, every tab taking an equal share of it, on the\n * terms of VButtonGroup's own `fullWidth`.\n */\n fullWidth?: boolean\n /**\n * Adds a button at each end of the bar to scroll it, each disabled once that end is\n * reached. It only makes sense when the tabs can overflow, so it excludes `fullWidth`.\n */\n scrollButtons?: boolean\n /** The icon of the button scrolling backwards. It follows the orientation by default. */\n prevIcon?: IconSource\n /** The icon of the button scrolling forwards. It follows the orientation by default. */\n nextIcon?: IconSource\n /** What the backward scroll button does, in words. It falls back to the dictionary. */\n prevLabel?: string\n /** What the forward scroll button does, in words. It falls back to the dictionary. */\n nextLabel?: string\n /** Whether moving to a tab also selects it. */\n activation?: TabsActivation\n /**\n * Makes every tab unusable, and the scroll buttons with them: the tabs leave the tab order\n * and grey out through the colour tokens.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row of tabs. It falls back to the design\n * system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<TabsProps>(), {\n variant: 'flat',\n tone: 'accent',\n size: 'md',\n compact: false,\n orientation: 'horizontal',\n align: 'start',\n fullWidth: false,\n scrollButtons: false,\n prevIcon: undefined,\n nextIcon: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n activation: 'manual',\n disabled: false,\n label: undefined,\n})\n\ndefineSlots<{\n /** The tabs themselves. */\n default(): unknown\n /**\n * The panels the tabs show. Leaving it out renders no panel area at all, which is\n * how the same component serves as a plain bar or a segmented control.\n */\n panels?(): unknown\n}>()\n\n/**\n * The `value` of the selected tab. There is deliberately no default: the component cannot know\n * which of the tabs a consumer wrote should open.\n */\nconst model = defineModel<ItemValue>()\n\n// The root element is only a container; the one that matters is the row of tabs. So `class` and\n// `style` stay outside, where a consumer expects to place the component, and everything else;\n// the id, the aria-* naming it, the data-*; goes down onto the row itself.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs: listAttrs } = useRootAttrs()\n// The prop wins over the dictionary, and above both, a consumer's own aria-label or\n// aria-labelledby still wins; that arbitration is what `useAriaLabel` is for.\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.tabs.label)\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? m.value.tabs.previous)\nconst resolvedNextLabel = computed(() => props.nextLabel ?? m.value.tabs.next)\n\nconst slots = useSlots()\nconst baseId = useId()\n\n// Everything is exposed through getters rather than as values: that is what keeps the\n// root's props reactive on the other side of the injection, so a tab re-renders when\n// the size or the tone changes.\nprovide(tabsKey, {\n get value() {\n return model.value\n },\n select(value: ItemValue) {\n model.value = value\n },\n tabId: (value: ItemValue) => tabIdFor(baseId, value),\n panelId: (value: ItemValue) => panelIdFor(baseId, value),\n // @ssr\n /*\n * A registry the panels filled in as they mounted would instead read as empty on the server\n * and full on the client, which is a hydration mismatch. The trade-off is that this is not\n * reactive: the panels slot must be there or not there, and cannot appear halfway through the\n * life of the component.\n */\n get hasPanels() {\n return slots.panels !== undefined\n },\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get activation() {\n return props.activation\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst resolvedPrevIcon = computed(\n () => props.prevIcon ?? (isVertical.value ? expandLessIcon : chevronLeftIcon),\n)\nconst resolvedNextIcon = computed(\n () => props.nextIcon ?? (isVertical.value ? expandMoreIcon : chevronRightIcon),\n)\n\nconst listEl = ref<HTMLElement | null>(null)\n\n// @keyboard @a11y\n/*\n * This handler only moves the focus. Selecting the tab that receives it; what `automatic`\n * activation means; is done by the tab itself, which knows its own typed value and therefore\n * never has to send it back through a DOM attribute.\n */\nfunction onKeydown(event: KeyboardEvent) {\n const list = listEl.value\n if (!list) return\n arrowNavigate(event, list, () => navigableItems(list, '[role=\"tab\"]:not(:disabled)'), {\n vertical: isVertical.value,\n })\n}\n\n/*\n * CSS can ask the question; VDialog uses that query for its scroll shadows; and still cannot\n * answer this one, for two independent reasons. It is Chrome/Edge 133+, with neither Safari nor\n * Firefox shipping it, and where VDialog's hairlines merely stay invisible without it these\n * buttons are functional; an `@supports` guard would mean keeping everything below anyway.\n */\nconst startSentinelEl = ref<HTMLElement | null>(null)\nconst endSentinelEl = ref<HTMLElement | null>(null)\nconst atStart = ref(true)\nconst atEnd = ref(true)\n\nwatch(\n [listEl, startSentinelEl, endSentinelEl],\n ([root, start, end], _previous, onCleanup) => {\n // @fallback\n // The observer exists neither on the server nor in the unit-test environment, so\n // its absence has to be tolerated rather than assumed away. The behaviour of the\n // ends is checked in a real browser instead, by the play functions.\n if (!root || !start || !end || typeof IntersectionObserver === 'undefined') return\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.target === start) atStart.value = entry.isIntersecting\n else atEnd.value = entry.isIntersecting\n }\n },\n { root },\n )\n observer.observe(start)\n observer.observe(end)\n onCleanup(() => observer.disconnect())\n },\n // `post` so the effect runs once the DOM is up to date: in the default timing the three\n // template refs are still null on the first pass, the guard above returns, and the buttons\n // stay disabled for good; the effect having tracked nothing to wake it again.\n { flush: 'post' },\n)\n\n// @a11y\n/*\n * The default `pre` timing is the point: the button still holds the focus when this runs, the\n * DOM not being patched with `disabled` yet. In `post` timing the focus would already be on\n * <body>, with nothing left to say which control lost it.\n */\nwatch([atStart, atEnd], ([start, end]) => {\n const list = listEl.value\n const prev = list?.previousElementSibling as HTMLElement | null\n const next = list?.nextElementSibling as HTMLElement | null\n const focused = document.activeElement\n const fromPrev = focused === prev\n if (!(fromPrev ? start : focused === next && end)) return\n if (fromPrev ? end : start) focusSelected()\n else (fromPrev ? next : prev)?.focus()\n})\n\n/** Scrolls the bar by most of its own length. */\nfunction scrollStep(direction: -1 | 1) {\n const list = listEl.value\n if (!list) return\n // Called optionally because the unit-test environment implements no scrolling at\n // all; this behaviour is covered in a real browser.\n if (isVertical.value) {\n list.scrollBy?.({ top: direction * list.clientHeight * 0.8 })\n return\n }\n // The horizontal offset is physical, not logical: in a right-to-left page, moving\n // forward means moving left, so the direction has to be flipped by hand.\n const rtl = isRtl(list)\n list.scrollBy?.({ left: direction * list.clientWidth * 0.8 * (rtl ? -1 : 1) })\n}\n\n// @a11y @core\n/*\n * The keyboard needs nothing here: it moves the focus, and the browser scrolls a focused\n * element into view by itself. The scrolling is confined to the row rather than asking the\n * element to reveal itself, which would scroll every scrollable ancestor up to the page.\n */\nfunction revealSelected(behavior?: ScrollBehavior) {\n const list = listEl.value\n const tab = list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\n if (!list || !tab) return\n const t = tab.getBoundingClientRect()\n const c = list.getBoundingClientRect()\n list.scrollBy?.({\n left: t.left < c.left ? t.left - c.left : t.right > c.right ? t.right - c.right : 0,\n top: t.top < c.top ? t.top - c.top : t.bottom > c.bottom ? t.bottom - c.bottom : 0,\n behavior,\n })\n}\n\n// `post`, so the tab that is now selected already carries `aria-selected` when it is looked\n// for: in the default timing the query would find the one that was selected before.\nwatch(model, () => revealSelected(), { flush: 'post' })\nonMounted(() => revealSelected('instant'))\n\n// `focus` goes where the Tab key would land: the selected tab, which holds the row's single tab\n// stop.\nfunction focusSelected(options?: FocusOptions) {\n const list = listEl.value\n const tab =\n list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]:not(:disabled)') ??\n list?.querySelector<HTMLElement>('[role=\"tab\"]:not(:disabled)')\n tab?.focus(options)\n}\n\ndefineExpose({\n /**\n * Moves the focus to the selected tab, or to the first tab that can take it when the\n * v-model names none.\n */\n focus: focusSelected,\n /** The `role=\"tablist\"` row, which is also where the consumer's attributes land. */\n el: listEl,\n})\n</script>\n\n<template>\n <div\n :class=\"['v-tabs', rootClass]\"\n :style=\"rootStyle\"\n :data-variant=\"variant\"\n :data-orientation=\"orientation\"\n :data-align=\"align\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-compact=\"compact ? '' : undefined\"\n >\n <div class=\"v-tabs-bar\">\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedPrevLabel\"\n :size=\"size\"\n :compact=\"compact\"\n :disabled=\"disabled || atStart\"\n @click=\"scrollStep(-1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedPrevIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n\n <div\n ref=\"listEl\"\n :aria-orientation=\"isVertical ? 'vertical' : undefined\"\n v-bind=\"listAttrs\"\n class=\"v-tabs-list\"\n role=\"tablist\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n >\n <!-- The two markers watched to know whether an end of the bar is reached.\n They are hidden from assistive technology: a row of tabs may contain\n nothing but tabs. -->\n <span\n v-if=\"scrollButtons\"\n ref=\"startSentinelEl\"\n class=\"v-tabs-sentinel\"\n aria-hidden=\"true\"\n />\n <slot />\n <span v-if=\"scrollButtons\" ref=\"endSentinelEl\" class=\"v-tabs-sentinel\" aria-hidden=\"true\" />\n </div>\n\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedNextLabel\"\n :size=\"size\"\n :compact=\"compact\"\n :disabled=\"disabled || atEnd\"\n @click=\"scrollStep(1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedNextIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n </div>\n\n <div v-if=\"$slots.panels\" class=\"v-tabs-panels\">\n <slot name=\"panels\" />\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-tabs {\n /*\n * The panels' padding, and the four pixels the compact density takes off it. The delta is\n * held apart from the value rather than the two being written out, so the density stays one\n * subtraction wherever the padding is read instead of a second table to keep in step; the\n * VAccordion idiom.\n */\n --tabs-pad-delta: 0px;\n --tabs-panels-pad: calc(var(--vectis-space-4) - var(--tabs-pad-delta));\n\n display: flex;\n flex-direction: column;\n font-family: var(--vectis-text-family);\n }\n\n .v-tabs[data-compact] {\n --tabs-pad-delta: var(--vectis-space-1);\n }\n\n .v-tabs[data-orientation='vertical'] {\n flex-direction: row;\n }\n\n /*\n * Keep card inner-radius calculations aligned with the tab corners; avoid clipping panel\n * focus rings and edge-to-edge consumer content. The border is transparent rather than absent\n * on `filled`: forced colours paint it. `elevated` has none, so that the rule under the tabs\n * reaches the edge its shadow draws; its end tabs then take the full radius.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']) {\n --tabs-corner-radius: calc(var(--vectis-radius-surface) - 1px);\n\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-variant='outline'] {\n background: var(--vectis-color-surface);\n border-color: var(--vectis-color-border);\n }\n\n .v-tabs[data-variant='elevated'] {\n --tabs-corner-radius: var(--vectis-radius-surface);\n\n border: none;\n background: var(--vectis-color-surface-raised);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-tabs[data-variant='filled'] {\n background: var(--vectis-color-surface-muted);\n }\n\n /* A neutral ghost hovers with the muted surface, which would vanish on this fill: the\n unselected tabs and the scroll buttons take a shade darker instead. */\n .v-tabs[data-variant='filled'] > .v-tabs-bar [data-tone='neutral'] {\n --tone-bg-soft: color-mix(\n in oklab,\n var(--vectis-color-surface-muted),\n var(--vectis-color-text) 4%\n );\n }\n\n .v-tabs-bar {\n display: flex;\n /* Stretched rather than centred: the row deliberately overlaps the track by one\n pixel through a negative margin, and centring would displace it by half of\n that. */\n align-items: stretch;\n /*\n * The row is allowed to shrink below its content as soon as the tabs overflow, so there\n * would be nothing left to distribute; and setting an alignment on a scrolling container\n * makes whatever overflows past its start edge permanently unreachable, since a scroll\n * position cannot go negative.\n */\n justify-content: flex-start;\n gap: var(--vectis-space-1);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar {\n flex-direction: column;\n }\n\n .v-tabs[data-align='center'] > .v-tabs-bar {\n justify-content: center;\n }\n\n .v-tabs[data-align='end'] > .v-tabs-bar {\n justify-content: flex-end;\n }\n\n /*\n * Inside a card it is what separates the tabs from the panels; which is why that edge is left\n * without a gutter, since otherwise the rule would separate nothing from nothing. The variants\n * with a track are named explicitly rather than excluding `inset`: a new variant will have to\n * opt in by hand instead of inheriting this silently.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar {\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n /*\n * This block has exactly the same specificity as the one above, so it is the ORDER that\n * decides. Moving it up would leave the vertical framed case with two rules on one side and\n * none between the two areas.\n */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-inline-start: none;\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs-list {\n display: flex;\n align-items: center;\n /* Held in a variable because the sentinels cancel it beside themselves. */\n --tabs-list-gap: var(--vectis-space-1);\n gap: var(--tabs-list-gap);\n overflow: auto;\n /* Without these the row refuses to shrink below the width of its tabs, and it\n would widen the whole bar instead of scrolling. */\n min-inline-size: 0;\n min-block-size: 0;\n scrollbar-width: none;\n scroll-behavior: smooth;\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar > .v-tabs-list {\n flex-direction: column;\n align-items: stretch;\n }\n\n /* Pulling the row one pixel into the track is what lets the selected tab's indicator\n COVER that line rather than sit on top of it, which would read as a thicker rule.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: -1px;\n }\n\n /* The row is now a pixel taller than the tabs it holds, so they are pushed against\n its end edge; the indicator each tab draws there then falls exactly on the\n track. */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list {\n align-items: flex-end;\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n margin-inline-start: -1px;\n }\n\n /* Same specificity as the rule above, so again it is the order that decides. */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-inline-start: 0;\n }\n\n /*\n * This is the one place the card may clip, and the one place it must. Nothing is lost to the\n * clip: there is no panel, hence no outer focus ring, and a tab draws its own ring inwards.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n ) {\n overflow: clip;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-end: none;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n }\n\n /* The hollow track of the segmented variant is drawn on the scrolling row itself,\n and not on the bar around it: its padding is what keeps the raised tab's shadow\n from being clipped as the row scrolls. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list {\n background: var(--vectis-color-surface-sunken);\n padding: var(--vectis-space-1);\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list {\n flex: 1;\n }\n\n /*\n * A pixel square each, at the very ends of the row. They are items of the row, so its gap\n * opens beside them as it does between two tabs: each pulls its neighbour back over that gap\n * and over its own pixel, so the first and last tabs sit exactly where the row's padding puts\n * them.\n */\n .v-tabs-sentinel {\n flex: 0 0 1px;\n align-self: flex-start;\n inline-size: 1px;\n block-size: 1px;\n }\n\n .v-tabs-sentinel:first-child {\n margin-inline-end: calc(-1px - var(--tabs-list-gap));\n margin-block-end: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-sentinel:last-child {\n margin-inline-start: calc(-1px - var(--tabs-list-gap));\n margin-block-start: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-scroll {\n flex: none;\n /*\n * The bar stretches its children for the track's sake, so the scroll buttons have to\n * re-centre themselves on the row; which in the segmented variant is taller than they are.\n */\n align-self: center;\n }\n\n .v-tabs-panels {\n min-inline-size: 0;\n }\n\n /*\n * The axis follows the ORIENTATION and not the writing mode, so it cannot be a single logical\n * declaration: the bar is above the panels when horizontal and beside them when vertical.\n * Both unframed variants are named explicitly rather than excluding the framed ones, as in\n * the track rules above: a new variant opts in by hand.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='horizontal']\n > .v-tabs-panels {\n padding-block-start: var(--tabs-panels-pad);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='vertical']\n > .v-tabs-panels {\n padding-inline-start: var(--tabs-panels-pad);\n }\n\n /* Inside a card the gutter is the panels' alone, on all four sides: the bar spends\n none, so the tabs and their track reach the frame and the content is the only thing\n set back from it. */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled'])\n > .v-tabs-panels {\n padding: var(--tabs-panels-pad);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-panels {\n flex: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs-list {\n scroll-behavior: auto;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VTabs.vue_vue_type_script_setup_true_lang.js","names":["$slots","_useModel","expandLessIcon","chevronLeftIcon","expandMoreIcon","chevronRightIcon","_openBlock","_createElementBlock","_normalizeClass","_unref","_normalizeStyle","_createElementVNode","_createBlock","VIconButton","_withCtx","_createVNode","VIcon","_mergeProps","_createCommentVNode","_renderSlot","VIcon_default","VIconButton_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","VTabs_vue_vue_type_script_setup_true_lang_default","\"modelValue\"","\"modelModifiers\"","__expose","props","__props","model","useModel","listAttrs","useRootAttrs","m","useMessages","ariaLabel","useAriaLabel","resolvedPrevLabel","computed","resolvedNextLabel","slots","useSlots","baseId","useId","tabsKey","value","tabIdFor","panelIdFor","isVertical","resolvedPrevIcon","expand_less","chevron_left","resolvedNextIcon","expand_more","chevron_right","listEl","ref","onKeydown","list","arrowNavigate","event","navigableItems","startSentinelEl","endSentinelEl","atStart","atEnd","watch","root","start","end","observer","entry","entries","onCleanup","prev","next","focused","fromPrev","focusSelected","scrollStep","direction","rtl","isRtl","revealSelected","tab","t","c","onMounted","options","openBlock","createElementBlock","normalizeClass","unref","rootClass","normalizeStyle","rootStyle","createElementVNode","createBlock","_cache","withCtx","createVNode","mergeProps","iconProps","createCommentVNode","renderSlot","_ctx"],"sources":["../../../src/components/VTabs/VTabs.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @a11y @core\n/**\n * Implement ARIA tab selection and roving focus over native buttons. Overflow measurements\n * control scrolling arrows because CSS cannot expose overflow state.\n */\n\nimport { computed, onMounted, provide, ref, useId, useSlots, watch } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_left as chevronLeftIcon } from '../VIcon/icons/chevron_left'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { expand_less as expandLessIcon } from '../VIcon/icons/expand_less'\nimport { expand_more as expandMoreIcon } from '../VIcon/icons/expand_more'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport { panelIdFor, tabIdFor, tabsKey } from './context'\n\nimport type { ItemValue } from '../../types'\nimport { arrowNavigate, navigableItems } from '../../utils/arrowNav'\nimport { isRtl } from '../../utils/direction'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/**\n * How the tabs are set off from the page: a rule under the tabs alone, the same inside a card\n * framed by a border, a shadow or a muted fill, or a sunken track.\n */\nexport type TabsVariant = 'flat' | 'outline' | 'elevated' | 'filled' | 'inset'\n/** The colour of the selected tab. */\nexport type TabsTone = 'accent' | 'neutral' | 'danger'\n/** The height of the tabs, from the scale every control shares. */\nexport type TabsSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n/** Whether the tabs run across the page or down its side. */\nexport type TabsOrientation = 'horizontal' | 'vertical'\n/** Where the tabs sit along the bar when they do not fill it. */\nexport type TabsAlign = 'start' | 'center' | 'end'\n/** Whether a tab is selected when it takes the focus, or only when it is activated. */\nexport type TabsActivation = 'manual' | 'automatic'\n\ninterface TabsProps {\n /**\n * How the tabs are set off from the page. `flat`, the default, draws a rule under the tabs\n * and nothing around them. `outline`, `elevated` and `filled` put the tabs and their panels in\n * a card with the same rule inside it: a border on the page surface, a shadow over a raised\n * surface, or a muted fill. `inset` draws the tabs as segments of a sunken track.\n */\n variant?: TabsVariant\n /** The colour the selected tab takes. The others stay neutral whatever this says. */\n tone?: TabsTone\n /** The height of the tabs, from the scale shared by every control. */\n size?: TabsSize\n /** Whether the tabs run across the page or down its side. */\n orientation?: TabsOrientation\n /** Where the tabs sit along the bar when they do not fill it. */\n align?: TabsAlign\n /**\n * Stretches the tabs across the whole bar, every tab taking an equal share of it, on the\n * terms of VButtonGroup's own `fullWidth`.\n */\n fullWidth?: boolean\n /**\n * Adds a button at each end of the bar to scroll it, each disabled once that end is\n * reached. It only makes sense when the tabs can overflow, so it excludes `fullWidth`.\n */\n scrollButtons?: boolean\n /** The icon of the button scrolling backwards. It follows the orientation by default. */\n prevIcon?: IconSource\n /** The icon of the button scrolling forwards. It follows the orientation by default. */\n nextIcon?: IconSource\n /** What the backward scroll button does, in words. It falls back to the dictionary. */\n prevLabel?: string\n /** What the forward scroll button does, in words. It falls back to the dictionary. */\n nextLabel?: string\n /** Whether moving to a tab also selects it. */\n activation?: TabsActivation\n /**\n * Makes every tab unusable, and the scroll buttons with them: the tabs leave the tab order\n * and grey out through the colour tokens.\n */\n disabled?: boolean\n /**\n * What screen readers announce for the row of tabs. It falls back to the design\n * system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<TabsProps>(), {\n variant: 'flat',\n tone: 'accent',\n size: 'md',\n orientation: 'horizontal',\n align: 'start',\n fullWidth: false,\n scrollButtons: false,\n prevIcon: undefined,\n nextIcon: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n activation: 'manual',\n disabled: false,\n label: undefined,\n})\n\ndefineSlots<{\n /** The tabs themselves. */\n default(): unknown\n /**\n * The panels the tabs show. Leaving it out renders no panel area at all, which is\n * how the same component serves as a plain bar or a segmented control.\n */\n panels?(): unknown\n}>()\n\n/**\n * The `value` of the selected tab. There is deliberately no default: the component cannot know\n * which of the tabs a consumer wrote should open.\n */\nconst model = defineModel<ItemValue>()\n\n// The root element is only a container; the one that matters is the row of tabs. So `class` and\n// `style` stay outside, where a consumer expects to place the component, and everything else;\n// the id, the aria-* naming it, the data-*; goes down onto the row itself.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs: listAttrs } = useRootAttrs()\n// The prop wins over the dictionary, and above both, a consumer's own aria-label or\n// aria-labelledby still wins; that arbitration is what `useAriaLabel` is for.\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.tabs.label)\nconst resolvedPrevLabel = computed(() => props.prevLabel ?? m.value.tabs.previous)\nconst resolvedNextLabel = computed(() => props.nextLabel ?? m.value.tabs.next)\n\nconst slots = useSlots()\nconst baseId = useId()\n\n// Everything is exposed through getters rather than as values: that is what keeps the\n// root's props reactive on the other side of the injection, so a tab re-renders when\n// the size or the tone changes.\nprovide(tabsKey, {\n get value() {\n return model.value\n },\n select(value: ItemValue) {\n model.value = value\n },\n tabId: (value: ItemValue) => tabIdFor(baseId, value),\n panelId: (value: ItemValue) => panelIdFor(baseId, value),\n // @ssr\n /*\n * A registry the panels filled in as they mounted would instead read as empty on the server\n * and full on the client, which is a hydration mismatch. The trade-off is that this is not\n * reactive: the panels slot must be there or not there, and cannot appear halfway through the\n * life of the component.\n */\n get hasPanels() {\n return slots.panels !== undefined\n },\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get activation() {\n return props.activation\n },\n get disabled() {\n return props.disabled\n },\n})\n\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst resolvedPrevIcon = computed(\n () => props.prevIcon ?? (isVertical.value ? expandLessIcon : chevronLeftIcon),\n)\nconst resolvedNextIcon = computed(\n () => props.nextIcon ?? (isVertical.value ? expandMoreIcon : chevronRightIcon),\n)\n\nconst listEl = ref<HTMLElement | null>(null)\n\n// @keyboard @a11y\n/*\n * This handler only moves the focus. Selecting the tab that receives it; what `automatic`\n * activation means; is done by the tab itself, which knows its own typed value and therefore\n * never has to send it back through a DOM attribute.\n */\nfunction onKeydown(event: KeyboardEvent) {\n const list = listEl.value\n if (!list) return\n arrowNavigate(event, list, () => navigableItems(list, '[role=\"tab\"]:not(:disabled)'), {\n vertical: isVertical.value,\n })\n}\n\n/*\n * CSS can ask the question; VDialog uses that query for its scroll shadows; and still cannot\n * answer this one, for two independent reasons. It is Chrome/Edge 133+, with neither Safari nor\n * Firefox shipping it, and where VDialog's hairlines merely stay invisible without it these\n * buttons are functional; an `@supports` guard would mean keeping everything below anyway.\n */\nconst startSentinelEl = ref<HTMLElement | null>(null)\nconst endSentinelEl = ref<HTMLElement | null>(null)\nconst atStart = ref(true)\nconst atEnd = ref(true)\n\nwatch(\n [listEl, startSentinelEl, endSentinelEl],\n ([root, start, end], _previous, onCleanup) => {\n // @fallback\n // The observer exists neither on the server nor in the unit-test environment, so\n // its absence has to be tolerated rather than assumed away. The behaviour of the\n // ends is checked in a real browser instead, by the play functions.\n if (!root || !start || !end || typeof IntersectionObserver === 'undefined') return\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.target === start) atStart.value = entry.isIntersecting\n else atEnd.value = entry.isIntersecting\n }\n },\n { root },\n )\n observer.observe(start)\n observer.observe(end)\n onCleanup(() => observer.disconnect())\n },\n // `post` so the effect runs once the DOM is up to date: in the default timing the three\n // template refs are still null on the first pass, the guard above returns, and the buttons\n // stay disabled for good; the effect having tracked nothing to wake it again.\n { flush: 'post' },\n)\n\n// @a11y\n/*\n * The default `pre` timing is the point: the button still holds the focus when this runs, the\n * DOM not being patched with `disabled` yet. In `post` timing the focus would already be on\n * <body>, with nothing left to say which control lost it.\n */\nwatch([atStart, atEnd], ([start, end]) => {\n const list = listEl.value\n const prev = list?.previousElementSibling as HTMLElement | null\n const next = list?.nextElementSibling as HTMLElement | null\n const focused = document.activeElement\n const fromPrev = focused === prev\n if (!(fromPrev ? start : focused === next && end)) return\n if (fromPrev ? end : start) focusSelected()\n else (fromPrev ? next : prev)?.focus()\n})\n\n/** Scrolls the bar by most of its own length. */\nfunction scrollStep(direction: -1 | 1) {\n const list = listEl.value\n if (!list) return\n // Called optionally because the unit-test environment implements no scrolling at\n // all; this behaviour is covered in a real browser.\n if (isVertical.value) {\n list.scrollBy?.({ top: direction * list.clientHeight * 0.8 })\n return\n }\n // The horizontal offset is physical, not logical: in a right-to-left page, moving\n // forward means moving left, so the direction has to be flipped by hand.\n const rtl = isRtl(list)\n list.scrollBy?.({ left: direction * list.clientWidth * 0.8 * (rtl ? -1 : 1) })\n}\n\n// @a11y @core\n/*\n * The keyboard needs nothing here: it moves the focus, and the browser scrolls a focused\n * element into view by itself. The scrolling is confined to the row rather than asking the\n * element to reveal itself, which would scroll every scrollable ancestor up to the page.\n */\nfunction revealSelected(behavior?: ScrollBehavior) {\n const list = listEl.value\n const tab = list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]')\n if (!list || !tab) return\n const t = tab.getBoundingClientRect()\n const c = list.getBoundingClientRect()\n list.scrollBy?.({\n left: t.left < c.left ? t.left - c.left : t.right > c.right ? t.right - c.right : 0,\n top: t.top < c.top ? t.top - c.top : t.bottom > c.bottom ? t.bottom - c.bottom : 0,\n behavior,\n })\n}\n\n// `post`, so the tab that is now selected already carries `aria-selected` when it is looked\n// for: in the default timing the query would find the one that was selected before.\nwatch(model, () => revealSelected(), { flush: 'post' })\nonMounted(() => revealSelected('instant'))\n\n// `focus` goes where the Tab key would land: the selected tab, which holds the row's single tab\n// stop.\nfunction focusSelected(options?: FocusOptions) {\n const list = listEl.value\n const tab =\n list?.querySelector<HTMLElement>('[role=\"tab\"][aria-selected=\"true\"]:not(:disabled)') ??\n list?.querySelector<HTMLElement>('[role=\"tab\"]:not(:disabled)')\n tab?.focus(options)\n}\n\ndefineExpose({\n /**\n * Moves the focus to the selected tab, or to the first tab that can take it when the\n * v-model names none.\n */\n focus: focusSelected,\n /** The `role=\"tablist\"` row, which is also where the consumer's attributes land. */\n el: listEl,\n})\n</script>\n\n<template>\n <div\n :class=\"['v-tabs', rootClass]\"\n :style=\"rootStyle\"\n :data-variant=\"variant\"\n :data-orientation=\"orientation\"\n :data-align=\"align\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n >\n <div class=\"v-tabs-bar\">\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedPrevLabel\"\n :size=\"size\"\n :disabled=\"disabled || atStart\"\n @click=\"scrollStep(-1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedPrevIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n\n <div\n ref=\"listEl\"\n :aria-orientation=\"isVertical ? 'vertical' : undefined\"\n v-bind=\"listAttrs\"\n class=\"v-tabs-list\"\n role=\"tablist\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n >\n <!-- The two markers watched to know whether an end of the bar is reached.\n They are hidden from assistive technology: a row of tabs may contain\n nothing but tabs. -->\n <span\n v-if=\"scrollButtons\"\n ref=\"startSentinelEl\"\n class=\"v-tabs-sentinel\"\n aria-hidden=\"true\"\n />\n <slot />\n <span v-if=\"scrollButtons\" ref=\"endSentinelEl\" class=\"v-tabs-sentinel\" aria-hidden=\"true\" />\n </div>\n\n <VIconButton\n v-if=\"scrollButtons\"\n class=\"v-tabs-scroll\"\n :label=\"resolvedNextLabel\"\n :size=\"size\"\n :disabled=\"disabled || atEnd\"\n @click=\"scrollStep(1)\"\n >\n <VIcon v-bind=\"iconProps(resolvedNextIcon)\" :mirrored=\"!isVertical\" />\n </VIconButton>\n </div>\n\n <div v-if=\"$slots.panels\" class=\"v-tabs-panels\">\n <slot name=\"panels\" />\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-tabs {\n /* The panels' padding, less what the density takes off, as on VAccordion. */\n --tabs-panels-pad: calc(var(--vectis-space-4) - var(--vectis-density-delta));\n\n display: flex;\n flex-direction: column;\n font-family: var(--vectis-text-family);\n }\n\n .v-tabs[data-orientation='vertical'] {\n flex-direction: row;\n }\n\n /*\n * Keep card inner-radius calculations aligned with the tab corners; avoid clipping panel\n * focus rings and edge-to-edge consumer content. The border is transparent rather than absent\n * on `filled`: forced colours paint it. `elevated` has none, so that the rule under the tabs\n * reaches the edge its shadow draws; its end tabs then take the full radius.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']) {\n --tabs-corner-radius: calc(var(--vectis-radius-surface) - 1px);\n\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-variant='outline'] {\n background: var(--vectis-color-surface);\n border-color: var(--vectis-color-border);\n }\n\n .v-tabs[data-variant='elevated'] {\n --tabs-corner-radius: var(--vectis-radius-surface);\n\n border: none;\n background: var(--vectis-color-surface-raised);\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-tabs[data-variant='filled'] {\n background: var(--vectis-color-surface-muted);\n }\n\n /* A neutral ghost hovers with the muted surface, which would vanish on this fill: the\n unselected tabs and the scroll buttons take a shade darker instead. */\n .v-tabs[data-variant='filled'] > .v-tabs-bar [data-tone='neutral'] {\n --tone-bg-soft: color-mix(\n in oklab,\n var(--vectis-color-surface-muted),\n var(--vectis-color-text) 4%\n );\n }\n\n .v-tabs-bar {\n display: flex;\n /* Stretched rather than centred: the row deliberately overlaps the track by one\n pixel through a negative margin, and centring would displace it by half of\n that. */\n align-items: stretch;\n /*\n * The row is allowed to shrink below its content as soon as the tabs overflow, so there\n * would be nothing left to distribute; and setting an alignment on a scrolling container\n * makes whatever overflows past its start edge permanently unreachable, since a scroll\n * position cannot go negative.\n */\n justify-content: flex-start;\n gap: var(--vectis-space-1);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar {\n flex-direction: column;\n }\n\n .v-tabs[data-align='center'] > .v-tabs-bar {\n justify-content: center;\n }\n\n .v-tabs[data-align='end'] > .v-tabs-bar {\n justify-content: flex-end;\n }\n\n /*\n * Inside a card it is what separates the tabs from the panels; which is why that edge is left\n * without a gutter, since otherwise the rule would separate nothing from nothing. The variants\n * with a track are named explicitly rather than excluding `inset`: a new variant will have to\n * opt in by hand instead of inheriting this silently.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar {\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-start: 1px solid var(--vectis-color-border);\n }\n\n /*\n * This block has exactly the same specificity as the one above, so it is the ORDER that\n * decides. Moving it up would leave the vertical framed case with two rules on one side and\n * none between the two areas.\n */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar {\n border-inline-start: none;\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n .v-tabs-list {\n display: flex;\n align-items: center;\n /* Held in a variable because the sentinels cancel it beside themselves. */\n --tabs-list-gap: var(--vectis-space-1);\n gap: var(--tabs-list-gap);\n overflow: auto;\n /* Without these the row refuses to shrink below the width of its tabs, and it\n would widen the whole bar instead of scrolling. */\n min-inline-size: 0;\n min-block-size: 0;\n scrollbar-width: none;\n scroll-behavior: smooth;\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-bar > .v-tabs-list {\n flex-direction: column;\n align-items: stretch;\n }\n\n /* Pulling the row one pixel into the track is what lets the selected tab's indicator\n COVER that line rather than sit on top of it, which would read as a thicker rule.\n */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: -1px;\n }\n\n /* The row is now a pixel taller than the tabs it holds, so they are pushed against\n its end edge; the indicator each tab draws there then falls exactly on the\n track. */\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='horizontal']\n > .v-tabs-bar\n > .v-tabs-list {\n align-items: flex-end;\n }\n\n .v-tabs:is(\n [data-variant='flat'],\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n margin-inline-start: -1px;\n }\n\n /* Same specificity as the rule above, so again it is the order that decides. */\n .v-tabs:is(\n [data-variant='outline'],\n [data-variant='elevated'],\n [data-variant='filled']\n )[data-orientation='vertical']\n > .v-tabs-bar\n > .v-tabs-list {\n margin-inline-start: 0;\n }\n\n /*\n * This is the one place the card may clip, and the one place it must. Nothing is lost to the\n * clip: there is no panel, hence no outer focus ring, and a tab draws its own ring inwards.\n */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n ) {\n overflow: clip;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar {\n border-block-end: none;\n border-inline-end: none;\n }\n\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled']):not(\n :has(> .v-tabs-panels)\n )\n > .v-tabs-bar\n > .v-tabs-list {\n margin-block-end: 0;\n }\n\n /* The hollow track of the segmented variant is drawn on the scrolling row itself,\n and not on the bar around it: its padding is what keeps the raised tab's shadow\n from being clipped as the row scrolls. */\n .v-tabs[data-variant='inset'] > .v-tabs-bar > .v-tabs-list {\n background: var(--vectis-color-surface-sunken);\n padding: var(--vectis-space-1);\n border-radius: var(--vectis-radius-surface);\n }\n\n .v-tabs[data-full-width] > .v-tabs-bar > .v-tabs-list {\n flex: 1;\n }\n\n /*\n * A pixel square each, at the very ends of the row. They are items of the row, so its gap\n * opens beside them as it does between two tabs: each pulls its neighbour back over that gap\n * and over its own pixel, so the first and last tabs sit exactly where the row's padding puts\n * them.\n */\n .v-tabs-sentinel {\n flex: 0 0 1px;\n align-self: flex-start;\n inline-size: 1px;\n block-size: 1px;\n }\n\n .v-tabs-sentinel:first-child {\n margin-inline-end: calc(-1px - var(--tabs-list-gap));\n margin-block-end: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-sentinel:last-child {\n margin-inline-start: calc(-1px - var(--tabs-list-gap));\n margin-block-start: calc(-1px - var(--tabs-list-gap));\n }\n\n .v-tabs-scroll {\n flex: none;\n /*\n * The bar stretches its children for the track's sake, so the scroll buttons have to\n * re-centre themselves on the row; which in the segmented variant is taller than they are.\n */\n align-self: center;\n }\n\n .v-tabs-panels {\n min-inline-size: 0;\n }\n\n /*\n * The axis follows the ORIENTATION and not the writing mode, so it cannot be a single logical\n * declaration: the bar is above the panels when horizontal and beside them when vertical.\n * Both unframed variants are named explicitly rather than excluding the framed ones, as in\n * the track rules above: a new variant opts in by hand.\n */\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='horizontal']\n > .v-tabs-panels {\n padding-block-start: var(--tabs-panels-pad);\n }\n\n .v-tabs:is([data-variant='flat'], [data-variant='inset'])[data-orientation='vertical']\n > .v-tabs-panels {\n padding-inline-start: var(--tabs-panels-pad);\n }\n\n /* Inside a card the gutter is the panels' alone, on all four sides: the bar spends\n none, so the tabs and their track reach the frame and the content is the only thing\n set back from it. */\n .v-tabs:is([data-variant='outline'], [data-variant='elevated'], [data-variant='filled'])\n > .v-tabs-panels {\n padding: var(--tabs-panels-pad);\n }\n\n .v-tabs[data-orientation='vertical'] > .v-tabs-panels {\n flex: 1;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-tabs-list {\n scroll-behavior: auto;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2FA,IAAM8B,IAAQC,GA+BRC,IAAQ/B,GAAsB8B,GAAA,YAAC,GAM/B,EAAE,cAAW,cAAW,gBAAgBG,MAAcC,EAAa,GAGnEC,IAAIC,EAAY,GAChBC,IAAYC,QAAmBT,EAAM,SAASM,EAAE,MAAM,KAAK,KAAK,GAChEI,IAAoBC,QAAeX,EAAM,aAAaM,EAAE,MAAM,KAAK,QAAQ,GAC3EM,IAAoBD,QAAeX,EAAM,aAAaM,EAAE,MAAM,KAAK,IAAI,GAEvEO,IAAQC,GAAS,GACjBC,IAASC,GAAM;EAKrB,EAAQC,GAAS;GACf,IAAI,QAAQ;IACV,OAAOf,EAAM;GACf;GACA,OAAO,GAAkB;IACvB,EAAM,QAAQgB;GAChB;GACA,QAAQ,MAAqBC,EAASJ,GAAQG,CAAK;GACnD,UAAU,MAAqBE,GAAWL,GAAQG,CAAK;GAQvD,IAAI,YAAY;IACd,OAAOL,EAAM,WAAW,KAAA;GAC1B;GACA,IAAI,UAAU;IACZ,OAAOb,EAAM;GACf;GACA,IAAI,OAAO;IACT,OAAOA,EAAM;GACf;GACA,IAAI,OAAO;IACT,OAAOA,EAAM;GACf;GACA,IAAI,aAAa;IACf,OAAOA,EAAM;GACf;GACA,IAAI,WAAW;IACb,OAAOA,EAAM;GACf;EACF,CAAC;EAED,IAAMqB,IAAaV,QAAeX,EAAM,gBAAgB,UAAU,GAC5DsB,KAAmBX,QACjBX,EAAM,aAAaqB,EAAW,QAAQjD,KAAiBC,GAC/D,GACMoD,KAAmBd,QACjBX,EAAM,aAAaqB,EAAW,QAAQ/C,KAAiBC,EAC/D,GAEMqD,IAASC,EAAwB,IAAI;EAQ3C,SAASC,GAAU,GAAsB;GACvC,IAAMC,IAAOH,EAAO;GACpB,AAAKG,KACLC,EAAcC,GAAOF,SAAYG,EAAeH,GAAM,+BAA6B,GAAG,EACpF,UAAUV,EAAW,MACvB,CAAC;EACH;EAQA,IAAMc,IAAkBN,EAAwB,IAAI,GAC9CO,IAAgBP,EAAwB,IAAI,GAC5CQ,IAAUR,EAAI,EAAI,GAClBS,IAAQT,EAAI,EAAI;EAmCtB,AAjCAU,EACE;GAACX;GAAQO;GAAiBC;EAAa,IACtC,CAACI,GAAMC,GAAOC,IAAM,GAAW,MAAc;GAK5C,IAAI,CAACF,KAAQ,CAACC,KAAS,CAACC,KAAO,OAAO,uBAAyB,KAAa;GAC5E,IAAMC,IAAW,IAAI,sBAClB,MAAY;IACX,KAAK,IAAMC,KAASC,GAClB,AAAID,EAAM,WAAWH,IAAO,EAAQ,QAAQG,EAAM,iBAC7C,EAAM,QAAQA,EAAM;GAE7B,GACA,EAAE,QAAK,CACT;GAGA,AAFAD,EAAS,QAAQF,CAAK,GACtBE,EAAS,QAAQD,CAAG,GACpBI,QAAgBH,EAAS,WAAW,CAAC;EACvC,GAIA,EAAE,OAAO,OAAO,CAClB,GAQAJ,EAAM,CAACF,GAASC,CAAK,IAAI,CAACG,GAAOC,OAAS;GACxC,IAAMX,IAAOH,EAAO,OACdmB,IAAOhB,GAAM,wBACbiB,IAAOjB,GAAM,oBACbkB,IAAU,SAAS,eACnBC,IAAWD,MAAYF;GAC7B,CAAMG,IAAWT,IAAQQ,MAAYD,KAAQN,QACzCQ,IAAWR,IAAMD,KAAOU,EAAc,KACpCD,IAAWF,IAAOD,EAAA,EAAO,MAAM;EACvC,CAAC;EAGD,SAASK,EAAW,GAAmB;GACrC,IAAMrB,IAAOH,EAAO;GACpB,IAAI,CAACG,GAAM;GAGX,IAAIV,EAAW,OAAO;IACpB,EAAK,WAAW,EAAE,KAAKgC,IAAYtB,EAAK,eAAe,GAAI,CAAC;IAC5D;GACF;GAGA,IAAMuB,IAAMC,EAAMxB,CAAI;GACtB,EAAK,WAAW,EAAE,MAAMsB,IAAYtB,EAAK,cAAc,MAAOuB,IAAM,KAAK,GAAG,CAAC;EAC/E;EAQA,SAASE,EAAe,GAA2B;GACjD,IAAMzB,IAAOH,EAAO,OACd6B,IAAM1B,GAAM,cAA2B,wCAAoC;GACjF,IAAI,CAACA,KAAQ,CAAC0B,GAAK;GACnB,IAAMC,IAAID,EAAI,sBAAsB,GAC9BE,IAAI5B,EAAK,sBAAsB;GACrC,EAAK,WAAW;IACd,MAAM2B,EAAE,OAAOC,EAAE,OAAOD,EAAE,OAAOC,EAAE,OAAOD,EAAE,QAAQC,EAAE,QAAQD,EAAE,QAAQC,EAAE,QAAQ;IAClF,KAAKD,EAAE,MAAMC,EAAE,MAAMD,EAAE,MAAMC,EAAE,MAAMD,EAAE,SAASC,EAAE,SAASD,EAAE,SAASC,EAAE,SAAS;IACjF;GACF,CAAC;EACH;EAKA,AADApB,EAAMrC,SAAasD,EAAe,GAAG,EAAE,OAAO,OAAO,CAAC,GACtDI,QAAgBJ,EAAe,SAAS,CAAC;EAIzC,SAASL,EAAc,GAAwB;GAC7C,IAAMpB,IAAOH,EAAO;GAIpB,CAFEG,GAAM,cAA2B,uDAAmD,KACpFA,GAAM,cAA2B,+BAA6B,EAAA,EAC3D,MAAM8B,CAAO;EACpB;SAEA9D,EAAa;GAKX,OAAOoD;GAEP,IAAIvB;EACN,CAAC,cAICpD,EAAA,GAAAC,EAyDM,OAAA;GAxDH,OAAKC,GAAA,CAAA,UAAaC,EAAAuF,CAAA,CAAS,CAAA;GAC3B,OAAKtF,EAAED,EAAAyF,CAAA,CAAS;GAChB,gBAAcnE,EAAA;GACd,oBAAkBA,EAAA;GAClB,cAAYA,EAAA;GACZ,mBAAiBA,EAAA,YAAS,KAAQ,KAAA;EAEnCpB,GAAAA,CAAAA,EA4CM,OA5CNY,GA4CM;GA1CIQ,EAAA,iBADRzB,EAAA,GAAAM,EAScC,GAAA;;IAPZ,OAAM;IACL,OAAO2B,EAAA;IACP,MAAMT,EAAA;IACN,UAAUA,EAAA,YAAYoC,EAAA;IACtB,SAAK,AAAAkC,EAAA,QAAA,MAAEnB,EAAU,EAAA;;IAElB,SAAApE,QAAsE,CAAtEC,EAAsEC,GAAtEC,EAAeR,EAAuDgG,CAAA,CAA9C,CAACrD,GAAA,KAAgB,GAAA,EAAI,UAAQ,CAAGD,EAAA,MAAA,CAAA,GAAA,MAAA,IAAA,CAAA,UAAA,CAAA,CAAA,CAAA;;;;;;;GAG1DxC,EAoBM,OApBNM,EAoBM;IAnBA,SAAA;IAAJ,KAAIyC;IACH,oBAAkBP,EAAA,QAAU,aAAgB,KAAA;GACrC,GAAA1C,EAAAyB,CAAA,GAAS;IACjB,OAAM;IACN,MAAK;IACJ,cAAYzB,EAAA6B,CAAA;IACH;;IAMFP,EAAA,iBADRzB,EAAA,GAAAC,EAKE,QAAA;;KAHI,SAAA;KAAJ,KAAI0D;KACJ,OAAM;KACN,eAAY;;IAEd9C,EAAQyF,EAAA,QAAA,SAAA;IACI7E,EAAA,iBAAZzB,EAAA,GAAAC,EAA4F,QAAA;;KAA7D,SAAA;KAAJ,KAAI2D;KAAgB,OAAM;KAAkB,eAAY;;;GAI7EnC,EAAA,iBADRzB,EAAA,GAAAM,EAScC,GAAA;;IAPZ,OAAM;IACL,OAAO6B,EAAA;IACP,MAAMX,EAAA;IACN,UAAUA,EAAA,YAAYqC,EAAA;IACtB,SAAK,AAAAiC,EAAA,QAAA,MAAEnB,EAAU,CAAA;;IAElB,SAAApE,QAAsE,CAAtEC,EAAsEC,GAAtEC,EAAeR,EAAuDgG,CAAA,CAA9C,CAAClD,GAAA,KAAgB,GAAA,EAAI,UAAQ,CAAGJ,EAAA,MAAA,CAAA,GAAA,MAAA,IAAA,CAAA,UAAA,CAAA,CAAA,CAAA;;;;;;;EAIjDnD,CAAAA,GAAAA,EAAAA,OAAO,UAAlBM,EAAA,GAAAC,EAEM,OAFNkB,GAEM,CADJN,EAAsByF,EAAA,QAAA,QAAA,CAAA,CAAA,KAAAF,EAAA,IAAA,EAAA,CAAA,GAAA,IAAApF,CAAA"}
|
|
@@ -21,8 +21,6 @@ export interface TabsContext {
|
|
|
21
21
|
readonly tone: TabsTone;
|
|
22
22
|
/** The height of the tabs. */
|
|
23
23
|
readonly size: TabsSize;
|
|
24
|
-
/** The reduced density. */
|
|
25
|
-
readonly compact: boolean;
|
|
26
24
|
/** Whether every tab is unusable, which no tab can refuse. */
|
|
27
25
|
readonly disabled: boolean;
|
|
28
26
|
/** Whether moving to a tab selects it, or merely focuses it. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../../src/components/VTabs/context.ts"],"sourcesContent":["/**\n * Derive tab/panel IDs from a shared base and value for SSR consistency. Pass size through\n * context because child buttons declare their own size.\n */\n\nimport type { InjectionKey } from 'vue'\n\nimport type { TabsActivation, TabsSize, TabsTone, TabsVariant } from './VTabs.vue'\nimport type { ItemValue } from '../../types'\n\nexport interface TabsContext {\n /** The selected tab, or nothing when the v-model names no tab that exists. */\n readonly value: ItemValue | undefined\n /** Selects a tab. */\n select: (value: ItemValue) => void\n /** The identifier of a tab, so its panel can point at it. */\n tabId: (value: ItemValue) => string\n /** The identifier of a panel, so its tab can point at it. */\n panelId: (value: ItemValue) => string\n /**\n * Whether panels were given at all. Without them the tabs are a bar of buttons and\n * must not claim to control anything.\n */\n readonly hasPanels: boolean\n /** The frame the bar is drawn in. */\n readonly variant: TabsVariant\n /** The colour the selected tab takes. */\n readonly tone: TabsTone\n /** The height of the tabs. */\n readonly size: TabsSize\n /**
|
|
1
|
+
{"version":3,"file":"context.js","names":["tabsKey","slug","value","char","tabIdFor","base","panelIdFor"],"sources":["../../../src/components/VTabs/context.ts"],"sourcesContent":["/**\n * Derive tab/panel IDs from a shared base and value for SSR consistency. Pass size through\n * context because child buttons declare their own size.\n */\n\nimport type { InjectionKey } from 'vue'\n\nimport type { TabsActivation, TabsSize, TabsTone, TabsVariant } from './VTabs.vue'\nimport type { ItemValue } from '../../types'\n\nexport interface TabsContext {\n /** The selected tab, or nothing when the v-model names no tab that exists. */\n readonly value: ItemValue | undefined\n /** Selects a tab. */\n select: (value: ItemValue) => void\n /** The identifier of a tab, so its panel can point at it. */\n tabId: (value: ItemValue) => string\n /** The identifier of a panel, so its tab can point at it. */\n panelId: (value: ItemValue) => string\n /**\n * Whether panels were given at all. Without them the tabs are a bar of buttons and\n * must not claim to control anything.\n */\n readonly hasPanels: boolean\n /** The frame the bar is drawn in. */\n readonly variant: TabsVariant\n /** The colour the selected tab takes. */\n readonly tone: TabsTone\n /** The height of the tabs. */\n readonly size: TabsSize\n /** Whether every tab is unusable, which no tab can refuse. */\n readonly disabled: boolean\n /** Whether moving to a tab selects it, or merely focuses it. */\n readonly activation: TabsActivation\n}\n\nexport const tabsKey: InjectionKey<TabsContext> = Symbol('v-tabs')\n\n/**\n * Encode type and every non-safe character, including underscore, so different values cannot\n * collide in tab/panel IDs.\n */\nconst slug = (value: ItemValue) =>\n (typeof value === 'number' ? 'n-' : 's-') +\n String(value).replace(/[^A-Za-z0-9-]/gu, (char) => `_${char.codePointAt(0)!.toString(16)}_`)\n\nexport const tabIdFor = (base: string, value: ItemValue) => `${base}-tab-${slug(value)}`\nexport const panelIdFor = (base: string, value: ItemValue) => `${base}-panel-${slug(value)}`\n"],"mappings":";AAoCA,IAAaA,IAAqC,OAAO,QAAQ,GAM3DC,KAAQ,OACX,OAAOC,KAAU,WAAW,OAAO,QACpC,OAAOA,CAAK,CAAC,CAAC,QAAQ,oBAAoB,MAAS,IAAIC,EAAK,YAAY,CAAC,CAAC,CAAE,SAAS,EAAE,EAAE,EAAE,GAEhFC,KAAY,GAAc,MAAqB,GAAGC,EAAK,OAAOJ,EAAKC,CAAK,KACxEI,KAAc,GAAc,MAAqB,GAAGD,EAAK,SAASJ,EAAKC,CAAK"}
|
|
@@ -5,11 +5,6 @@ export type TextareaSize = 'sm' | 'md' | 'lg';
|
|
|
5
5
|
interface TextareaProps {
|
|
6
6
|
/** The size of the field, which sets its padding, its type scale and its icons. */
|
|
7
7
|
size?: TextareaSize;
|
|
8
|
-
/**
|
|
9
|
-
* Takes 4px off the field by tightening its padding, leaving the number of lines,
|
|
10
|
-
* the type and the icons alone.
|
|
11
|
-
*/
|
|
12
|
-
compact?: boolean;
|
|
13
8
|
/**
|
|
14
9
|
* How many lines of text the field shows: the native `rows` attribute, which gives the field
|
|
15
10
|
* its height.
|
|
@@ -149,7 +144,6 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {
|
|
|
149
144
|
label: string;
|
|
150
145
|
error: string;
|
|
151
146
|
size: TextareaSize;
|
|
152
|
-
compact: boolean;
|
|
153
147
|
iconStart: IconSource;
|
|
154
148
|
iconEnd: IconSource;
|
|
155
149
|
disabled: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VTextarea.js","names":[],"sources":["../../../src/components/VTextarea/VTextarea.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * Native textarea owns editing and validity. JavaScript bridges the model, text limits and\n * clear-button focus; CSS controls optional content-based height.\n */\nimport { computed, ref } from 'vue'\n\nimport VField from '../VField/VField.vue'\nimport type { FieldControlProps, FieldLabelPosition } from '../VField/VField.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconName, iconProps } from '../VIcon/iconProps'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\nimport { useClearable } from '../../composables/useClearable'\nimport { useIconClickHandlers } from '../../composables/useIconClickHandlers'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTextLimit } from '../../composables/useTextLimit'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of one line: 32, 40 or 48 pixels. */\nexport type TextareaSize = 'sm' | 'md' | 'lg'\n\ninterface TextareaProps {\n /** The size of the field, which sets its padding, its type scale and its icons. */\n size?: TextareaSize\n /**\n * Takes 4px off the field by tightening its padding, leaving the number of lines,\n * the type and the icons alone.\n */\n compact?: boolean\n /**\n * How many lines of text the field shows: the native `rows` attribute, which gives the field\n * its height.\n */\n rows?: number\n /**\n * Lets the field grow as the text is typed, instead of scrolling inside the height `rows`\n * gives it, which stays its starting height.\n */\n autoGrow?: boolean\n /**\n * Marks the field as invalid whatever the browser thinks: the route for a rule\n * only the server can check.\n */\n invalid?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the text without allowing it to be changed. The field can still be focused\n * and copied from, and the clear button is hidden.\n */\n readonly?: boolean\n /** The label above the field, tied to it so that clicking it focuses the field. */\n label?: string\n /**\n * A line of help under the field, tied to the textarea for assistive technology so\n * that it is read out along with the label.\n */\n hint?: string\n /**\n * A message saying what is wrong with the value, shown in place of the hint so that the field\n * does not grow. It marks the field invalid, is read along with it, and is announced when it\n * appears or changes.\n */\n error?: string\n /** Marks the field as required: an asterisk follows the label, and the textarea is `required`. */\n required?: boolean\n /** Hides the label visually. It still names the field for assistive technology. */\n hideLabel?: boolean\n /**\n * Where the label sits: above the field (`top`, the default), or at its start, in a column of\n * its own, moving back above when there is not room for both.\n */\n labelPosition?: FieldLabelPosition\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes a real\n * button as soon as a `@click:icon-start` listener is attached, in which case it needs\n * `iconStartLabel`.\n */\n iconStart?: IconSource\n /**\n * The same at the end of the field. The `#end` slot replaces it, and the loading\n * spinner takes its place while it turns.\n */\n iconEnd?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /** What the end icon does, in words, once it is clickable. */\n iconEndLabel?: string\n /** Shows a spinner at the end of the field, in place of the end icon or slot. */\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 /**\n * Offers a cross that empties the field. It appears when there is something to\n * clear and the field can be edited.\n */\n clearable?: boolean\n /**\n * Decides whether the cross is shown, instead of letting the field work it out from its own\n * content (a read-only field included).\n */\n clearVisible?: boolean\n /** What the clear button does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The maximum number of characters. By default this is the browser's own limit,\n * which simply refuses anything beyond it.\n */\n maxlength?: number\n /**\n * Turns that limit into a soft one: the reader may type past it, and the field goes into\n * error instead of silently refusing the keystrokes.\n */\n softLimit?: boolean\n /**\n * Shows how much has been typed, under the field: \"12/80\" against a limit, or just\n * \"12\" without one.\n */\n counter?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<TextareaProps>(), {\n size: 'md',\n compact: false,\n rows: 5,\n autoGrow: false,\n invalid: false,\n disabled: false,\n readonly: false,\n label: undefined,\n hint: undefined,\n error: undefined,\n required: false,\n hideLabel: false,\n labelPosition: 'top',\n iconStart: undefined,\n iconEnd: undefined,\n iconStartLabel: undefined,\n iconEndLabel: undefined,\n loading: false,\n loadingText: undefined,\n clearable: false,\n clearVisible: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n softLimit: false,\n counter: false,\n})\n\nconst emit = defineEmits<{\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n /** The end icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-end': [event: MouseEvent]\n /** The clear button was pressed. The field has already been emptied. */\n clear: []\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its\n * place, as in VInput.\n */\n start?(): unknown\n /**\n * Content at the end of the field, which replaces `iconEnd`. It is hidden while the\n * field is loading, the spinner taking that place.\n */\n end?(): unknown\n /**\n * Controls of your own inside the field, placed before the field's own: the clear cross and\n * the end icon.\n */\n 'value-end'?(): unknown\n}>()\n\n/** The text in the field, empty to begin with. */\nconst model = defineModel<string>({ default: '' })\n\n// HTML wants `rows` to be a positive integer, and it drops anything else on the floor:\n// a 0, a negative or a fractional value would leave the browser falling back to its own\n// default of two rows, which is neither what was asked for nor this component's default.\n// Rounding and flooring here is what keeps the prop's contract out of the parser's hands.\nconst resolvedRows = computed(() => Math.max(1, Math.round(props.rows)))\n\n// `class` and `style` stay on the wrapper, where a consumer expects to style the\n// field; every other attribute goes to the textarea, where it does something.\nconst { rootClass, rootStyle, forwardedAttrs: restAttrs } = useRootAttrs()\n\n// The prop keeps priority; what it falls back to is the dictionary, so the default\n// wording follows the language the design system is set to.\nconst m = useMessages()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.common.clear)\n\n// Every length measurement goes through this projection: a value straight from an API or\n// a database is often `null`, and reading `.length` on it would throw at mount.\nconst modelText = computed(() => model.value ?? '')\n\nconst { hasIconStartHandler, hasIconEndHandler } = useIconClickHandlers({\n name: 'VTextarea',\n iconStartLabel: props.iconStartLabel,\n iconEndLabel: props.iconEndLabel,\n})\n\nconst controlEl = ref<HTMLTextAreaElement | null>(null)\n\nconst { showClear, onClear } = useClearable({\n clearable: () => props.clearable,\n clearVisible: () => props.clearVisible,\n disabled: () => props.disabled,\n readonly: () => props.readonly,\n text: () => modelText.value,\n controlEl,\n onCleared: () => {\n model.value = ''\n emit('clear')\n },\n})\n\nconst { counterText, over } = useTextLimit({\n el: controlEl,\n text: () => modelText.value,\n maxlength: () => props.maxlength,\n softLimit: () => props.softLimit,\n})\n\n/** The attributes of the textarea, built on VField's: the consumer's win over `invalid`. */\nfunction textareaAttrs(fieldProps: FieldControlProps): FieldControlProps {\n return { 'aria-invalid': props.invalid ? 'true' : undefined, ...fieldProps }\n}\n\ndefineExpose({\n /** Moves the focus to the real textarea. */\n focus: (options?: FocusOptions) => controlEl.value?.focus(options),\n /** Selects everything in the field. */\n select: () => controlEl.value?.select(),\n /** The real `<textarea>`, for what neither of the two above covers. */\n el: controlEl,\n})\n</script>\n\n<template>\n <VField\n v-bind=\"restAttrs\"\n class=\"v-textarea\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :label=\"label\"\n :hint=\"hint\"\n :error=\"error\"\n :required=\"required\"\n :disabled=\"disabled\"\n :hide-label=\"hideLabel\"\n :label-position=\"labelPosition\"\n >\n <template #default=\"{ fieldProps }\">\n <div\n class=\"v-textarea-field v-control\"\n :style=\"{ '--textarea-rows': resolvedRows }\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n :data-auto-grow=\"autoGrow ? '' : undefined\"\n >\n <!--\n The start icon is rendered before the slot, where the end icon is the slot's own\n fallback. The asymmetry is VInput's, mirrored here so the two fields answer the same\n way: what a composed field puts in that zone is OTHER content beside the icon, not\n another way of drawing it.\n -->\n <button\n v-if=\"iconStart && hasIconStartHandler\"\n type=\"button\"\n class=\"v-textarea-action v-field-action v-textarea-icon-start\"\n :aria-label=\"iconStartLabel ?? iconName(iconStart)\"\n :disabled=\"disabled\"\n @click=\"emit('click:icon-start', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconStart)\" />\n </button>\n <VIcon v-else-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" class=\"v-textarea-icon-start\" />\n <slot name=\"start\" />\n\n <!-- The label's `for` matches the id inside fieldProps, which the rule cannot follow. -->\n <!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label -->\n <textarea\n ref=\"controlEl\"\n v-model=\"model\"\n v-bind=\"textareaAttrs(fieldProps)\"\n class=\"v-textarea-control\"\n :rows=\"resolvedRows\"\n :maxlength=\"softLimit ? undefined : maxlength\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n />\n\n <slot name=\"value-end\" />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n class=\"v-textarea-action v-field-action v-textarea-clear\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"onClear\"\n >\n <VIcon :name=\"closeIcon\" />\n </button>\n\n <VSpinner v-if=\"loading\" :label=\"loadingText\" />\n <slot v-else name=\"end\">\n <button\n v-if=\"iconEnd && hasIconEndHandler\"\n type=\"button\"\n class=\"v-textarea-action v-field-action v-textarea-icon-end\"\n :aria-label=\"iconEndLabel ?? iconName(iconEnd)\"\n :disabled=\"disabled\"\n @click=\"emit('click:icon-end', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconEnd)\" />\n </button>\n <VIcon v-else-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" class=\"v-textarea-icon-end\" />\n </slot>\n </div>\n </template>\n <template v-if=\"counter\" #meta=\"{ id }\">\n <span :id=\"id\" class=\"v-textarea-counter v-field-counter\" :data-over=\"over ? '' : undefined\">\n {{ counterText }}\n </span>\n </template>\n </VField>\n</template>\n\n<style>\n@layer vectis.components {\n /* The root is a VField, which lays out the label, the hint and the error. */\n .v-textarea {\n width: 100%;\n }\n\n /*\n * This is the box that carries the border, the background, the focus ring and the resize\n * handle; which is why it also hides its overflow, `resize` having no effect on a box whose\n * overflow is visible. `--field-border-color` is the single source of truth for its colour,\n * and the hover, error and disabled states do nothing but redefine it.\n */\n .v-textarea-field {\n --field-border-color: var(--vectis-color-border-strong);\n\n /*\n * Nothing here restates the size scale: the `--control-*` variables come from the v-control\n * class on this box (styles/control-size.css), the icon context included. The field carries no\n * height of its own: it is the `rows` attribute on the textarea that sets one, and the\n * field is simply as tall as the control it wraps.\n */\n\n /*\n * The leading is named once and feeds two things that have to agree by construction: the\n * field's own `line-height`, and the first line box below, against which everything sitting\n * beside the text is offset.\n */\n --textarea-leading: var(--vectis-text-body-md-leading);\n\n /*\n * Derived rather than written as `1lh`: a custom property resolves `lh` where it is used, so\n * the icons and buttons beside the text would each measure their own line height.\n */\n --textarea-line: calc(var(--control-font-size) * var(--textarea-leading));\n\n display: flex;\n align-items: flex-start;\n gap: var(--control-gap);\n padding-inline: var(--control-padding-inline-field);\n /*\n * Derive padding from control height minus borders and one line. Read the compact-adjusted\n * height so rows=1 matches VInput and each additional row adds one line.\n */\n padding-block: calc((var(--control-height) - 2px - var(--textarea-line)) / 2);\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--field-border-color);\n /*\n * The cap is what carries the VInput identity above into the OTHER row counts, and it has\n * to stay derived from --control-height. A browser scales down any radius it cannot fit, so\n * on a one-row field a pill override is already painted as half the control height; min()\n * applies that same reduction to a field that is taller.\n */\n border-radius: min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));\n font-size: var(--control-font-size);\n /* Text running over several lines takes the body line height. The `control` type\n role, whose lines are set tight against one another, only makes sense for a\n single-line label. */\n line-height: var(--textarea-leading);\n resize: vertical;\n overflow: hidden;\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-textarea-control {\n flex: 1;\n min-width: 0;\n align-self: stretch;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n outline: none; /* The focus ring is drawn by the field around it, not here */\n resize: none;\n }\n\n .v-textarea-control::placeholder {\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n * The margin is SYMMETRIC, the VToast rule. It looks redundant under\n * `align-items: flex-start`, where only the start side moves anything, but the end side is\n * what keeps the item's OUTER box down to one line box.\n */\n .v-textarea-field > .v-icon,\n .v-textarea-field > .v-spinner {\n margin-block: calc((var(--textarea-line) - var(--vectis-icon-size)) / 2);\n }\n\n .v-textarea-field > .v-textarea-action {\n margin-block: calc((var(--textarea-line) - var(--control-action-size)) / 2);\n }\n\n /* A decorative icon is drawn in a muted grey, so it stays quieter than the text\n being typed beside it. */\n .v-textarea-field > .v-icon {\n color: var(--vectis-color-text-muted);\n }\n\n .v-textarea-field > .v-spinner {\n font-size: var(--vectis-icon-size);\n }\n\n /*\n * This block must stay first in the sequence of states: read-only, then hover, focus, invalid\n * and disabled. The read-only, focus, invalid and disabled selectors all weigh (0,3,0),\n * `:has()` taking the specificity of what it contains, so nothing but the source order\n * arbitrates between them.\n */\n .v-textarea .v-textarea-field[data-readonly] {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-textarea-field:hover:not(:has(.v-textarea-control:focus)):not(\n :has(\n .v-textarea-control:disabled,\n .v-textarea-control:user-invalid,\n .v-textarea-control[aria-invalid='true']\n )\n ) {\n --field-border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /*\n * The selector watches the control's focus and not `:focus-within`, so that when one of the\n * field's own buttons takes keyboard focus, only that button's outline lights up: two\n * indicators at once would be unreadable. And it is `:focus` rather than `:focus-visible`,\n * because a text field shows its focus even when it was reached with the mouse.\n */\n .v-textarea-field:has(.v-textarea-control:focus) {\n --field-border-color: var(--vectis-color-accent);\n\n box-shadow: 0 0 0 1px var(--field-border-color);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /*\n * Only the colour variable is changed, which is why the border and the focus ring both turn\n * red without either being restated.\n */\n .v-textarea-field:has(.v-textarea-control:user-invalid),\n .v-textarea-field:has(.v-textarea-control[aria-invalid='true']) {\n --field-border-color: var(--vectis-color-danger);\n }\n\n /*\n * A disabled field greys out through the colour tokens, the same ones VCheckbox and VRadio\n * use, and never through opacity. It comes last in the sequence of states, which at equal\n * specificity is what makes it win over all of them, the error included: a disabled field is\n * not submitted, so it has nothing to report.\n */\n .v-textarea .v-textarea-field[data-disabled] {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n cursor: not-allowed;\n resize: none;\n }\n\n .v-textarea[data-disabled] .v-textarea-counter {\n color: var(--vectis-color-text-subtle);\n }\n\n .v-textarea-field[data-disabled] .v-textarea-action,\n .v-textarea-field[data-disabled] > .v-icon {\n color: inherit;\n cursor: not-allowed;\n }\n\n .v-textarea-control:disabled {\n cursor: not-allowed;\n }\n\n /*\n * A browser without support keeps the fixed height and its scrollbar, which is the intended\n * fallback.\n */\n .v-textarea-field[data-auto-grow] {\n resize: none;\n }\n\n /*\n * It is scoped to this branch and must not be lifted onto the control at large: a floor there\n * would stop the control shrinking with the resize handle, and the field, which hides its\n * overflow, would clip it instead of scrolling.\n */\n .v-textarea-field[data-auto-grow] .v-textarea-control {\n field-sizing: content;\n min-block-size: calc(var(--textarea-rows) * var(--textarea-line));\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-textarea-field {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VTextarea.js","names":["_sfc_main","VTextarea_vue_vue_type_script_setup_true_lang_default","VTextarea_default"],"sources":["../../../src/components/VTextarea/VTextarea.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * Native textarea owns editing and validity. JavaScript bridges the model, text limits and\n * clear-button focus; CSS controls optional content-based height.\n */\nimport { computed, ref } from 'vue'\n\nimport VField from '../VField/VField.vue'\nimport type { FieldControlProps, FieldLabelPosition } from '../VField/VField.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconName, iconProps } from '../VIcon/iconProps'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\nimport { useClearable } from '../../composables/useClearable'\nimport { useIconClickHandlers } from '../../composables/useIconClickHandlers'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTextLimit } from '../../composables/useTextLimit'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of one line: 32, 40 or 48 pixels. */\nexport type TextareaSize = 'sm' | 'md' | 'lg'\n\ninterface TextareaProps {\n /** The size of the field, which sets its padding, its type scale and its icons. */\n size?: TextareaSize\n /**\n * How many lines of text the field shows: the native `rows` attribute, which gives the field\n * its height.\n */\n rows?: number\n /**\n * Lets the field grow as the text is typed, instead of scrolling inside the height `rows`\n * gives it, which stays its starting height.\n */\n autoGrow?: boolean\n /**\n * Marks the field as invalid whatever the browser thinks: the route for a rule\n * only the server can check.\n */\n invalid?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the text without allowing it to be changed. The field can still be focused\n * and copied from, and the clear button is hidden.\n */\n readonly?: boolean\n /** The label above the field, tied to it so that clicking it focuses the field. */\n label?: string\n /**\n * A line of help under the field, tied to the textarea for assistive technology so\n * that it is read out along with the label.\n */\n hint?: string\n /**\n * A message saying what is wrong with the value, shown in place of the hint so that the field\n * does not grow. It marks the field invalid, is read along with it, and is announced when it\n * appears or changes.\n */\n error?: string\n /** Marks the field as required: an asterisk follows the label, and the textarea is `required`. */\n required?: boolean\n /** Hides the label visually. It still names the field for assistive technology. */\n hideLabel?: boolean\n /**\n * Where the label sits: above the field (`top`, the default), or at its start, in a column of\n * its own, moving back above when there is not room for both.\n */\n labelPosition?: FieldLabelPosition\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes a real\n * button as soon as a `@click:icon-start` listener is attached, in which case it needs\n * `iconStartLabel`.\n */\n iconStart?: IconSource\n /**\n * The same at the end of the field. The `#end` slot replaces it, and the loading\n * spinner takes its place while it turns.\n */\n iconEnd?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /** What the end icon does, in words, once it is clickable. */\n iconEndLabel?: string\n /** Shows a spinner at the end of the field, in place of the end icon or slot. */\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 /**\n * Offers a cross that empties the field. It appears when there is something to\n * clear and the field can be edited.\n */\n clearable?: boolean\n /**\n * Decides whether the cross is shown, instead of letting the field work it out from its own\n * content (a read-only field included).\n */\n clearVisible?: boolean\n /** What the clear button does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The maximum number of characters. By default this is the browser's own limit,\n * which simply refuses anything beyond it.\n */\n maxlength?: number\n /**\n * Turns that limit into a soft one: the reader may type past it, and the field goes into\n * error instead of silently refusing the keystrokes.\n */\n softLimit?: boolean\n /**\n * Shows how much has been typed, under the field: \"12/80\" against a limit, or just\n * \"12\" without one.\n */\n counter?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<TextareaProps>(), {\n size: 'md',\n rows: 5,\n autoGrow: false,\n invalid: false,\n disabled: false,\n readonly: false,\n label: undefined,\n hint: undefined,\n error: undefined,\n required: false,\n hideLabel: false,\n labelPosition: 'top',\n iconStart: undefined,\n iconEnd: undefined,\n iconStartLabel: undefined,\n iconEndLabel: undefined,\n loading: false,\n loadingText: undefined,\n clearable: false,\n clearVisible: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n softLimit: false,\n counter: false,\n})\n\nconst emit = defineEmits<{\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n /** The end icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-end': [event: MouseEvent]\n /** The clear button was pressed. The field has already been emptied. */\n clear: []\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its\n * place, as in VInput.\n */\n start?(): unknown\n /**\n * Content at the end of the field, which replaces `iconEnd`. It is hidden while the\n * field is loading, the spinner taking that place.\n */\n end?(): unknown\n /**\n * Controls of your own inside the field, placed before the field's own: the clear cross and\n * the end icon.\n */\n 'value-end'?(): unknown\n}>()\n\n/** The text in the field, empty to begin with. */\nconst model = defineModel<string>({ default: '' })\n\n// HTML wants `rows` to be a positive integer, and it drops anything else on the floor:\n// a 0, a negative or a fractional value would leave the browser falling back to its own\n// default of two rows, which is neither what was asked for nor this component's default.\n// Rounding and flooring here is what keeps the prop's contract out of the parser's hands.\nconst resolvedRows = computed(() => Math.max(1, Math.round(props.rows)))\n\n// `class` and `style` stay on the wrapper, where a consumer expects to style the\n// field; every other attribute goes to the textarea, where it does something.\nconst { rootClass, rootStyle, forwardedAttrs: restAttrs } = useRootAttrs()\n\n// The prop keeps priority; what it falls back to is the dictionary, so the default\n// wording follows the language the design system is set to.\nconst m = useMessages()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.common.clear)\n\n// Every length measurement goes through this projection: a value straight from an API or\n// a database is often `null`, and reading `.length` on it would throw at mount.\nconst modelText = computed(() => model.value ?? '')\n\nconst { hasIconStartHandler, hasIconEndHandler } = useIconClickHandlers({\n name: 'VTextarea',\n iconStartLabel: props.iconStartLabel,\n iconEndLabel: props.iconEndLabel,\n})\n\nconst controlEl = ref<HTMLTextAreaElement | null>(null)\n\nconst { showClear, onClear } = useClearable({\n clearable: () => props.clearable,\n clearVisible: () => props.clearVisible,\n disabled: () => props.disabled,\n readonly: () => props.readonly,\n text: () => modelText.value,\n controlEl,\n onCleared: () => {\n model.value = ''\n emit('clear')\n },\n})\n\nconst { counterText, over } = useTextLimit({\n el: controlEl,\n text: () => modelText.value,\n maxlength: () => props.maxlength,\n softLimit: () => props.softLimit,\n})\n\n/** The attributes of the textarea, built on VField's: the consumer's win over `invalid`. */\nfunction textareaAttrs(fieldProps: FieldControlProps): FieldControlProps {\n return { 'aria-invalid': props.invalid ? 'true' : undefined, ...fieldProps }\n}\n\ndefineExpose({\n /** Moves the focus to the real textarea. */\n focus: (options?: FocusOptions) => controlEl.value?.focus(options),\n /** Selects everything in the field. */\n select: () => controlEl.value?.select(),\n /** The real `<textarea>`, for what neither of the two above covers. */\n el: controlEl,\n})\n</script>\n\n<template>\n <VField\n v-bind=\"restAttrs\"\n class=\"v-textarea\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :label=\"label\"\n :hint=\"hint\"\n :error=\"error\"\n :required=\"required\"\n :disabled=\"disabled\"\n :hide-label=\"hideLabel\"\n :label-position=\"labelPosition\"\n >\n <template #default=\"{ fieldProps }\">\n <div\n class=\"v-textarea-field v-control\"\n :style=\"{ '--textarea-rows': resolvedRows }\"\n :data-size=\"size\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n :data-auto-grow=\"autoGrow ? '' : undefined\"\n >\n <!--\n The start icon is rendered before the slot, where the end icon is the slot's own\n fallback. The asymmetry is VInput's, mirrored here so the two fields answer the same\n way: what a composed field puts in that zone is OTHER content beside the icon, not\n another way of drawing it.\n -->\n <button\n v-if=\"iconStart && hasIconStartHandler\"\n type=\"button\"\n class=\"v-textarea-action v-field-action v-textarea-icon-start\"\n :aria-label=\"iconStartLabel ?? iconName(iconStart)\"\n :disabled=\"disabled\"\n @click=\"emit('click:icon-start', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconStart)\" />\n </button>\n <VIcon v-else-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" class=\"v-textarea-icon-start\" />\n <slot name=\"start\" />\n\n <!-- The label's `for` matches the id inside fieldProps, which the rule cannot follow. -->\n <!-- eslint-disable-next-line vuejs-accessibility/form-control-has-label -->\n <textarea\n ref=\"controlEl\"\n v-model=\"model\"\n v-bind=\"textareaAttrs(fieldProps)\"\n class=\"v-textarea-control\"\n :rows=\"resolvedRows\"\n :maxlength=\"softLimit ? undefined : maxlength\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n />\n\n <slot name=\"value-end\" />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n class=\"v-textarea-action v-field-action v-textarea-clear\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"onClear\"\n >\n <VIcon :name=\"closeIcon\" />\n </button>\n\n <VSpinner v-if=\"loading\" :label=\"loadingText\" />\n <slot v-else name=\"end\">\n <button\n v-if=\"iconEnd && hasIconEndHandler\"\n type=\"button\"\n class=\"v-textarea-action v-field-action v-textarea-icon-end\"\n :aria-label=\"iconEndLabel ?? iconName(iconEnd)\"\n :disabled=\"disabled\"\n @click=\"emit('click:icon-end', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconEnd)\" />\n </button>\n <VIcon v-else-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" class=\"v-textarea-icon-end\" />\n </slot>\n </div>\n </template>\n <template v-if=\"counter\" #meta=\"{ id }\">\n <span :id=\"id\" class=\"v-textarea-counter v-field-counter\" :data-over=\"over ? '' : undefined\">\n {{ counterText }}\n </span>\n </template>\n </VField>\n</template>\n\n<style>\n@layer vectis.components {\n /* The root is a VField, which lays out the label, the hint and the error. */\n .v-textarea {\n width: 100%;\n }\n\n /*\n * This is the box that carries the border, the background, the focus ring and the resize\n * handle; which is why it also hides its overflow, `resize` having no effect on a box whose\n * overflow is visible. `--field-border-color` is the single source of truth for its colour,\n * and the hover, error and disabled states do nothing but redefine it.\n */\n .v-textarea-field {\n --field-border-color: var(--vectis-color-border-strong);\n\n /*\n * Nothing here restates the size scale: the `--control-*` variables come from the v-control\n * class on this box (styles/control-size.css), the icon context included. The field carries no\n * height of its own: it is the `rows` attribute on the textarea that sets one, and the\n * field is simply as tall as the control it wraps.\n */\n\n /*\n * The leading is named once and feeds two things that have to agree by construction: the\n * field's own `line-height`, and the first line box below, against which everything sitting\n * beside the text is offset.\n */\n --textarea-leading: var(--vectis-text-body-md-leading);\n\n /*\n * Derived rather than written as `1lh`: a custom property resolves `lh` where it is used, so\n * the icons and buttons beside the text would each measure their own line height.\n */\n --textarea-line: calc(var(--control-font-size) * var(--textarea-leading));\n\n display: flex;\n align-items: flex-start;\n gap: var(--control-gap);\n padding-inline: var(--control-padding-inline-field);\n /*\n * Derive padding from control height minus borders and one line. Read the density-adjusted\n * height so rows=1 matches VInput and each additional row adds one line.\n */\n padding-block: calc((var(--control-height) - 2px - var(--textarea-line)) / 2);\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--field-border-color);\n /*\n * The cap is what carries the VInput identity above into the OTHER row counts, and it has\n * to stay derived from --control-height. A browser scales down any radius it cannot fit, so\n * on a one-row field a pill override is already painted as half the control height; min()\n * applies that same reduction to a field that is taller.\n */\n border-radius: min(var(--vectis-radius-interactive), calc(var(--control-height) / 2));\n font-size: var(--control-font-size);\n /* Text running over several lines takes the body line height. The `control` type\n role, whose lines are set tight against one another, only makes sense for a\n single-line label. */\n line-height: var(--textarea-leading);\n resize: vertical;\n overflow: hidden;\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-textarea-control {\n flex: 1;\n min-width: 0;\n align-self: stretch;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n outline: none; /* The focus ring is drawn by the field around it, not here */\n resize: none;\n }\n\n .v-textarea-control::placeholder {\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n * The margin is SYMMETRIC, the VToast rule. It looks redundant under\n * `align-items: flex-start`, where only the start side moves anything, but the end side is\n * what keeps the item's OUTER box down to one line box.\n */\n .v-textarea-field > .v-icon,\n .v-textarea-field > .v-spinner {\n margin-block: calc((var(--textarea-line) - var(--vectis-icon-size)) / 2);\n }\n\n .v-textarea-field > .v-textarea-action {\n margin-block: calc((var(--textarea-line) - var(--control-action-size)) / 2);\n }\n\n /* A decorative icon is drawn in a muted grey, so it stays quieter than the text\n being typed beside it. */\n .v-textarea-field > .v-icon {\n color: var(--vectis-color-text-muted);\n }\n\n .v-textarea-field > .v-spinner {\n font-size: var(--vectis-icon-size);\n }\n\n /*\n * This block must stay first in the sequence of states: read-only, then hover, focus, invalid\n * and disabled. The read-only, focus, invalid and disabled selectors all weigh (0,3,0),\n * `:has()` taking the specificity of what it contains, so nothing but the source order\n * arbitrates between them.\n */\n .v-textarea .v-textarea-field[data-readonly] {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-textarea-field:hover:not(:has(.v-textarea-control:focus)):not(\n :has(\n .v-textarea-control:disabled,\n .v-textarea-control:user-invalid,\n .v-textarea-control[aria-invalid='true']\n )\n ) {\n --field-border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /*\n * The selector watches the control's focus and not `:focus-within`, so that when one of the\n * field's own buttons takes keyboard focus, only that button's outline lights up: two\n * indicators at once would be unreadable. And it is `:focus` rather than `:focus-visible`,\n * because a text field shows its focus even when it was reached with the mouse.\n */\n .v-textarea-field:has(.v-textarea-control:focus) {\n --field-border-color: var(--vectis-color-accent);\n\n box-shadow: 0 0 0 1px var(--field-border-color);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /*\n * Only the colour variable is changed, which is why the border and the focus ring both turn\n * red without either being restated.\n */\n .v-textarea-field:has(.v-textarea-control:user-invalid),\n .v-textarea-field:has(.v-textarea-control[aria-invalid='true']) {\n --field-border-color: var(--vectis-color-danger);\n }\n\n /*\n * A disabled field greys out through the colour tokens, the same ones VCheckbox and VRadio\n * use, and never through opacity. It comes last in the sequence of states, which at equal\n * specificity is what makes it win over all of them, the error included: a disabled field is\n * not submitted, so it has nothing to report.\n */\n .v-textarea .v-textarea-field[data-disabled] {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n cursor: not-allowed;\n resize: none;\n }\n\n .v-textarea[data-disabled] .v-textarea-counter {\n color: var(--vectis-color-text-subtle);\n }\n\n .v-textarea-field[data-disabled] .v-textarea-action,\n .v-textarea-field[data-disabled] > .v-icon {\n color: inherit;\n cursor: not-allowed;\n }\n\n .v-textarea-control:disabled {\n cursor: not-allowed;\n }\n\n /*\n * A browser without support keeps the fixed height and its scrollbar, which is the intended\n * fallback.\n */\n .v-textarea-field[data-auto-grow] {\n resize: none;\n }\n\n /*\n * It is scoped to this branch and must not be lifted onto the control at large: a floor there\n * would stop the control shrinking with the resize handle, and the field, which hides its\n * overflow, would clip it instead of scrolling.\n */\n .v-textarea-field[data-auto-grow] .v-textarea-control {\n field-sizing: content;\n min-block-size: calc(var(--textarea-rows) * var(--textarea-line));\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-textarea-field {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -12,7 +12,6 @@ import { computed as d, createBlock as f, createCommentVNode as p, createElement
|
|
|
12
12
|
//#region src/components/VTextarea/VTextarea.vue?vue&type=script&setup=true&lang.ts
|
|
13
13
|
var M = [
|
|
14
14
|
"data-size",
|
|
15
|
-
"data-compact",
|
|
16
15
|
"data-disabled",
|
|
17
16
|
"data-readonly",
|
|
18
17
|
"data-auto-grow"
|
|
@@ -26,10 +25,6 @@ var M = [
|
|
|
26
25
|
__name: "VTextarea",
|
|
27
26
|
props: /*@__PURE__*/ b({
|
|
28
27
|
size: { default: "md" },
|
|
29
|
-
compact: {
|
|
30
|
-
type: Boolean,
|
|
31
|
-
default: !1
|
|
32
|
-
},
|
|
33
28
|
rows: { default: 5 },
|
|
34
29
|
autoGrow: {
|
|
35
30
|
type: Boolean,
|
|
@@ -141,7 +136,6 @@ var M = [
|
|
|
141
136
|
class: "v-textarea-field v-control",
|
|
142
137
|
style: C({ "--textarea-rows": H.value }),
|
|
143
138
|
"data-size": v.size,
|
|
144
|
-
"data-compact": v.compact ? "" : void 0,
|
|
145
139
|
"data-disabled": v.disabled ? "" : void 0,
|
|
146
140
|
"data-readonly": v.readonly ? "" : void 0,
|
|
147
141
|
"data-auto-grow": v.autoGrow ? "" : void 0
|