vectis-ui 0.8.1 → 0.9.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/README.md +3 -3
- package/dist/components/VAccordion/VAccordionItem.d.ts +4 -6
- package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js +21 -19
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/context.d.ts +6 -1
- package/dist/components/VAccordion/context.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.css +1 -1
- package/dist/components/VAvatar/VAvatar.d.ts +5 -3
- package/dist/components/VAvatar/VAvatar.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +53 -39
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.d.ts +12 -3
- package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +27 -20
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/context.d.ts +2 -2
- package/dist/components/VAvatar/context.js.map +1 -1
- package/dist/components/VBadge/VBadge.css +1 -1
- package/dist/components/VBadge/VBadge.d.ts +8 -4
- package/dist/components/VBadge/VBadge.js.map +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +5 -4
- package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +8 -3
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButton.css +1 -1
- package/dist/components/VButton/VButton.d.ts +4 -1
- package/dist/components/VButton/VButton.js.map +1 -1
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +8 -7
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.css +1 -1
- package/dist/components/VCalendar/VCalendar.d.ts +15 -6
- package/dist/components/VCalendar/VCalendar.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +144 -126
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.css +1 -1
- package/dist/components/VCalendar/VCalendarEvent.d.ts +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.css +1 -1
- package/dist/components/VCalendar/VCalendarMonth.d.ts +1 -1
- package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +99 -90
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.css +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +3 -3
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +256 -246
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.css +1 -1
- package/dist/components/VCalendar/VCalendarYear.d.ts +8 -1
- package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js +39 -32
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/color.d.ts +1 -1
- package/dist/components/VCalendar/color.js.map +1 -1
- package/dist/components/VCalendar/edgeStep.d.ts +3 -3
- package/dist/components/VCalendar/edgeStep.js +5 -0
- package/dist/components/VCalendar/edgeStep.js.map +1 -1
- package/dist/components/VCalendar/gesture.d.ts +10 -9
- package/dist/components/VCalendar/gesture.js +60 -45
- package/dist/components/VCalendar/gesture.js.map +1 -1
- package/dist/components/VCalendar/keyboard.d.ts +22 -5
- package/dist/components/VCalendar/keyboard.js +17 -16
- package/dist/components/VCalendar/keyboard.js.map +1 -1
- package/dist/components/VCalendar/layout.d.ts +47 -37
- package/dist/components/VCalendar/layout.js +47 -37
- package/dist/components/VCalendar/layout.js.map +1 -1
- package/dist/components/VCalendar/timeGrid.d.ts +4 -4
- package/dist/components/VCalendar/timeGrid.js +6 -6
- package/dist/components/VCalendar/timeGrid.js.map +1 -1
- package/dist/components/VCalendar/types.d.ts +5 -5
- package/dist/components/VCarousel/VCarousel.css +1 -1
- package/dist/components/VCarousel/VCarousel.d.ts +59 -38
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +161 -132
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.d.ts +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.d.ts +1 -1
- package/dist/components/VCarousel/context.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.css +1 -1
- package/dist/components/VCheckbox/VCheckbox.d.ts +2 -2
- package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +10 -8
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VChip/VChip.css +1 -1
- package/dist/components/VChip/VChip.d.ts +7 -2
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +69 -55
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.css +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +50 -36
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +167 -161
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.css +1 -1
- package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/infiniteScroll.d.ts +12 -3
- package/dist/components/VCombobox/infiniteScroll.js +18 -12
- package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.css +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +55 -24
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +192 -155
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.css +1 -1
- package/dist/components/VDateInput/VDateInput.d.ts +15 -12
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +180 -160
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/mask.d.ts +95 -0
- package/dist/components/VDateInput/mask.js +118 -0
- package/dist/components/VDateInput/mask.js.map +1 -0
- package/dist/components/VDatePicker/VDatePicker.css +1 -1
- package/dist/components/VDatePicker/VDatePicker.d.ts +6 -5
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +204 -173
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/keyboard.d.ts +3 -3
- package/dist/components/VDatePicker/keyboard.js.map +1 -1
- package/dist/components/VDatePicker/names.d.ts +34 -0
- package/dist/components/VDatePicker/names.js +42 -0
- package/dist/components/VDatePicker/names.js.map +1 -0
- package/dist/components/VDialog/VDialog.css +1 -1
- package/dist/components/VDialog/VDialog.d.ts +2 -2
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +63 -41
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +38 -25
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +74 -71
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/truncate.d.ts +4 -4
- package/dist/components/VFileInput/truncate.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.css +1 -1
- package/dist/components/VFilePicker/VFilePicker.d.ts +54 -37
- package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +152 -135
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/fileKind.d.ts +2 -2
- package/dist/components/VFilePicker/fileKind.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.css +1 -1
- package/dist/components/VHotkeys/VHotkeys.d.ts +10 -8
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +22 -20
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.d.ts +9 -7
- package/dist/components/VHotkeys/platform.js +41 -30
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VIcon/VIcon.css +1 -1
- package/dist/components/VIcon/VIcon.d.ts +6 -6
- package/dist/components/VIcon/VIcon.js.map +1 -1
- package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VIcon/iconProps.d.ts +4 -4
- package/dist/components/VIcon/iconProps.js.map +1 -1
- package/dist/components/VIcon/icons/index.d.ts +1 -1
- package/dist/components/VIcon/icons/index.js.map +1 -1
- package/dist/components/VIcon/icons/names.d.ts +1 -1
- package/dist/components/VIcon/icons/names.js.map +1 -1
- package/dist/components/VIcon/resolver.d.ts +3 -3
- package/dist/components/VIcon/resolver.js +23 -20
- package/dist/components/VIcon/resolver.js.map +1 -1
- package/dist/components/VIcon/types.d.ts +4 -4
- package/dist/components/VIconButton/VIconButton.d.ts +4 -4
- package/dist/components/VIconButton/VIconButton.js.map +1 -1
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInput.d.ts +16 -8
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +22 -16
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.css +1 -1
- package/dist/components/VInput/VInputGroup.js.map +1 -1
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +43 -28
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.css +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +3 -3
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +152 -85
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenu.d.ts +4 -4
- package/dist/components/VMenu/VMenu.js.map +1 -1
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.css +1 -1
- package/dist/components/VMenu/VMenuGroup.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js +18 -11
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.css +1 -1
- package/dist/components/VMenu/VMenuItem.d.ts +6 -6
- package/dist/components/VMenu/VMenuItem.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +83 -69
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.css +1 -1
- package/dist/components/VMenu/VMenuPanel.d.ts +2 -2
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +56 -55
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/context.d.ts +2 -2
- package/dist/components/VMenu/context.js.map +1 -1
- package/dist/components/VPagination/VPagination.css +1 -1
- package/dist/components/VPagination/VPagination.d.ts +22 -9
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +67 -57
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VPopover/VPopover.css +1 -1
- package/dist/components/VPopover/VPopover.d.ts +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.css +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.d.ts +12 -7
- 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.css +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.d.ts +15 -10
- 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.css +1 -1
- package/dist/components/VRadio/VRadio.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.d.ts +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 +2 -2
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +12 -13
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +87 -68
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.css +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +10 -7
- package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSlider/VSlider.css +1 -1
- package/dist/components/VSlider/VSlider.d.ts +21 -7
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +159 -140
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.css +1 -1
- package/dist/components/VSnackbar/VSnackbar.d.ts +4 -4
- package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +85 -59
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/state.d.ts +7 -7
- package/dist/components/VSnackbar/state.js +13 -11
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.css +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/VSwitch/VSwitch.css +1 -1
- package/dist/components/VSwitch/VSwitch.d.ts +1 -1
- package/dist/components/VSwitch/VSwitch.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTab.css +1 -1
- package/dist/components/VTabs/VTab.d.ts +4 -0
- package/dist/components/VTabs/VTab.js.map +1 -1
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +9 -8
- package/dist/components/VTabs/VTab.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 +17 -7
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +84 -77
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +8 -8
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +71 -69
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.css +1 -1
- package/dist/components/VTimeInput/VTimeInput.d.ts +21 -12
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +230 -206
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/search.d.ts +17 -2
- package/dist/components/VTimeInput/search.js +19 -9
- package/dist/components/VTimeInput/search.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.css +1 -1
- package/dist/components/VTimePicker/VTimePicker.d.ts +8 -7
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +128 -106
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/limits.d.ts +11 -4
- package/dist/components/VTimePicker/limits.js +3 -2
- package/dist/components/VTimePicker/limits.js.map +1 -1
- package/dist/components/VToast/VToast.css +1 -1
- package/dist/components/VToast/VToast.js.map +1 -1
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +33 -37
- 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 +91 -61
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/state.d.ts +8 -6
- package/dist/components/VToast/state.js +15 -13
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/components/VToggle/VToggle.css +1 -1
- package/dist/components/VToggle/VToggle.d.ts +9 -4
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.d.ts +4 -0
- package/dist/components/VToggle/VToggleItem.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.css +1 -1
- package/dist/components/VTooltip/VTooltip.d.ts +17 -10
- package/dist/components/VTooltip/VTooltip.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +46 -26
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTypography/VTypography.css +1 -1
- package/dist/components/VTypography/VTypography.d.ts +6 -4
- package/dist/components/VTypography/VTypography.js.map +1 -1
- package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/useAriaLabel.js +1 -1
- package/dist/composables/useAriaLabel.js.map +1 -1
- package/dist/composables/useClearable.d.ts +2 -2
- package/dist/composables/useClearable.js.map +1 -1
- package/dist/composables/useDetailsOpen.d.ts +3 -3
- package/dist/composables/useDetailsOpen.js +14 -12
- package/dist/composables/useDetailsOpen.js.map +1 -1
- package/dist/composables/useFieldIds.d.ts +2 -1
- package/dist/composables/useFieldIds.js +6 -5
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.d.ts +2 -2
- package/dist/composables/useFieldPanel.js +47 -37
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileDrop.js +3 -3
- package/dist/composables/useFileDrop.js.map +1 -1
- package/dist/composables/useFileField.d.ts +1 -1
- package/dist/composables/useFileField.js +62 -41
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useIconClickHandlers.d.ts +3 -3
- package/dist/composables/useIconClickHandlers.js +14 -13
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/composables/useInertLink.js +12 -10
- package/dist/composables/useInertLink.js.map +1 -1
- package/dist/composables/useLiveAnnouncer.d.ts +5 -0
- package/dist/composables/useLiveAnnouncer.js +23 -0
- package/dist/composables/useLiveAnnouncer.js.map +1 -0
- package/dist/composables/useMaskedField.d.ts +5 -5
- package/dist/composables/useMaskedField.js.map +1 -1
- package/dist/composables/usePopover.d.ts +3 -3
- package/dist/composables/usePopover.js +6 -4
- package/dist/composables/usePopover.js.map +1 -1
- package/dist/composables/useProgressValue.js +11 -2
- package/dist/composables/useProgressValue.js.map +1 -1
- package/dist/composables/useRootAttrs.d.ts +1 -1
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useSlotNodes.d.ts +18 -0
- package/dist/composables/useSlotNodes.js +13 -0
- package/dist/composables/useSlotNodes.js.map +1 -0
- package/dist/composables/useTextLimit.d.ts +2 -1
- package/dist/composables/useTextLimit.js +2 -2
- package/dist/composables/useTextLimit.js.map +1 -1
- package/dist/composables/useTimer.d.ts +5 -0
- package/dist/composables/useTimer.js +22 -11
- package/dist/composables/useTimer.js.map +1 -1
- package/dist/i18n/en.js +1 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js +14 -14
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.d.ts +7 -7
- package/dist/i18n/state.js +37 -28
- package/dist/i18n/state.js.map +1 -1
- package/dist/i18n/types.d.ts +12 -12
- package/dist/icons.d.ts +2 -2
- package/dist/index.d.ts +12 -11
- package/dist/styles.css +1 -1
- package/dist/tokens/css.d.ts +1 -1
- package/dist/tokens/css.js.map +1 -1
- package/dist/tokens/index.d.ts +6 -0
- package/dist/tokens/semantic.d.ts +6 -0
- package/dist/tokens/semantic.js +66 -60
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.js +3 -3
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens.json +130 -66
- package/dist/types.d.ts +1 -1
- package/dist/utils/arrowNav.d.ts +4 -1
- package/dist/utils/arrowNav.js +7 -7
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/chip.d.ts +1 -1
- package/dist/utils/chip.js.map +1 -1
- package/dist/utils/clock.d.ts +113 -0
- package/dist/utils/clock.js +84 -0
- package/dist/utils/clock.js.map +1 -0
- package/dist/utils/css.d.ts +6 -4
- package/dist/utils/css.js +2 -2
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/date.d.ts +7 -102
- package/dist/utils/date.js +33 -160
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/direction.d.ts +1 -1
- package/dist/utils/direction.js.map +1 -1
- package/dist/utils/env.d.ts +12 -3
- package/dist/utils/env.js +6 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/file.d.ts +3 -3
- package/dist/utils/file.js +1 -1
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/focus.d.ts +1 -1
- package/dist/utils/focus.js.map +1 -1
- package/dist/utils/hostWarns.d.ts +1 -1
- package/dist/utils/hostWarns.js.map +1 -1
- package/dist/utils/ids.d.ts +1 -1
- package/dist/utils/ids.js.map +1 -1
- package/dist/utils/matcher.d.ts +2 -2
- package/dist/utils/matcher.js.map +1 -1
- package/dist/utils/memo.d.ts +1 -1
- package/dist/utils/memo.js.map +1 -1
- package/dist/utils/text.d.ts +3 -3
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/time.d.ts +2 -101
- package/dist/utils/time.js +22 -98
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/vnode.d.ts +5 -3
- package/dist/utils/vnode.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import n from "../VIcon/
|
|
4
|
-
import r from "../
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { isDev as e } from "../../utils/env.js";
|
|
2
|
+
import { useMessages as t } from "../../i18n/state.js";
|
|
3
|
+
import { iconProps as n } from "../VIcon/iconProps.js";
|
|
4
|
+
import r from "../VIcon/VIcon.js";
|
|
5
|
+
import i from "../VTypography/VTypography.js";
|
|
6
|
+
import { useAriaLabel as a } from "../../composables/useAriaLabel.js";
|
|
7
|
+
import { joinIds as o } from "../../utils/ids.js";
|
|
8
|
+
import { Fragment as s, computed as c, createBlock as l, createCommentVNode as u, createElementBlock as d, createElementVNode as f, createTextVNode as p, defineComponent as m, mergeModels as h, mergeProps as g, openBlock as _, ref as v, renderList as y, toDisplayString as b, unref as x, useAttrs as S, useId as C, useModel as w, watch as T, withCtx as ee } from "vue";
|
|
8
9
|
//#region src/components/VInputOTP/VInputOTP.vue?vue&type=script&setup=true&lang.ts
|
|
9
10
|
var E = [
|
|
10
11
|
"aria-label",
|
|
@@ -23,15 +24,21 @@ var E = [
|
|
|
23
24
|
"aria-label",
|
|
24
25
|
"aria-invalid",
|
|
25
26
|
"onInput",
|
|
26
|
-
"onKeydown"
|
|
27
|
+
"onKeydown",
|
|
28
|
+
"onFocus"
|
|
27
29
|
], k = {
|
|
28
30
|
key: 1,
|
|
29
31
|
class: "v-input-otp-literal",
|
|
30
32
|
"aria-hidden": "true"
|
|
31
|
-
},
|
|
33
|
+
}, te = [
|
|
34
|
+
"value",
|
|
35
|
+
"pattern",
|
|
36
|
+
"disabled",
|
|
37
|
+
"readonly"
|
|
38
|
+
], A = /*@__PURE__*/ m({
|
|
32
39
|
inheritAttrs: !1,
|
|
33
40
|
__name: "VInputOTP",
|
|
34
|
-
props: /*@__PURE__*/
|
|
41
|
+
props: /*@__PURE__*/ h({
|
|
35
42
|
length: { default: 6 },
|
|
36
43
|
format: { default: "numeric" },
|
|
37
44
|
pattern: { default: void 0 },
|
|
@@ -59,9 +66,13 @@ var E = [
|
|
|
59
66
|
modelValue: { default: "" },
|
|
60
67
|
modelModifiers: {}
|
|
61
68
|
}),
|
|
62
|
-
emits: /*@__PURE__*/
|
|
63
|
-
setup(
|
|
64
|
-
let j =
|
|
69
|
+
emits: /*@__PURE__*/ h(["complete"], ["update:modelValue"]),
|
|
70
|
+
setup(m, { expose: h, emit: A }) {
|
|
71
|
+
let j = m, M = t(), N = a(() => j.label ?? M.value.inputOTP.label), P = S(), F = [
|
|
72
|
+
"name",
|
|
73
|
+
"form",
|
|
74
|
+
"required"
|
|
75
|
+
], I = c(() => Object.fromEntries(Object.entries(P).filter(([e]) => !F.includes(e)))), L = c(() => Object.fromEntries(Object.entries(P).filter(([e]) => F.includes(e)))), R = C(), ne = c(() => o(P["aria-describedby"], !!j.hint && R)), z = w(m, "modelValue"), B = A, V = c(() => {
|
|
65
76
|
if (j.pattern?.includes("#")) {
|
|
66
77
|
let e = 0;
|
|
67
78
|
return [...j.pattern].map((t) => t === "#" ? {
|
|
@@ -76,31 +87,42 @@ var E = [
|
|
|
76
87
|
type: "slot",
|
|
77
88
|
slotIndex: t
|
|
78
89
|
}));
|
|
79
|
-
}),
|
|
90
|
+
}), H = c(() => V.value.filter((e) => e.type === "slot").length);
|
|
91
|
+
e && j.pattern && !j.pattern.includes("#") && console.warn("[VInputOTP] pattern without '#' — falling back to `length`.");
|
|
92
|
+
let U = {
|
|
80
93
|
numeric: /[^0-9]/g,
|
|
81
94
|
alpha: /[^A-Z]/g,
|
|
82
95
|
alphanumeric: /[^A-Z0-9]/g
|
|
83
96
|
};
|
|
84
|
-
function
|
|
85
|
-
return (j.format === "numeric" ? e : e.toUpperCase()).replace(
|
|
97
|
+
function W(e) {
|
|
98
|
+
return (j.format === "numeric" ? e : e.toUpperCase()).replace(U[j.format], "");
|
|
86
99
|
}
|
|
87
|
-
let
|
|
88
|
-
function
|
|
89
|
-
|
|
100
|
+
let G = v([]), K = v(null), q = v([]);
|
|
101
|
+
function J(e) {
|
|
102
|
+
let t = W(e);
|
|
103
|
+
q.value = Array.from({ length: H.value }, (e, n) => t[n] ?? "");
|
|
90
104
|
}
|
|
91
|
-
|
|
92
|
-
(e !==
|
|
93
|
-
}),
|
|
94
|
-
|
|
105
|
+
J(z.value), T([z, H], ([e, t]) => {
|
|
106
|
+
(e !== q.value.join("") || q.value.length !== t) && J(e);
|
|
107
|
+
}), T(H, (e) => {
|
|
108
|
+
G.value.length = e;
|
|
95
109
|
}, { flush: "post" });
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
let Y = () => q.value.findIndex((e) => !e);
|
|
111
|
+
function X(e) {
|
|
112
|
+
q.value.splice(e, 1), q.value.push("");
|
|
113
|
+
}
|
|
114
|
+
function Z(e) {
|
|
115
|
+
let t = G.value[e];
|
|
116
|
+
t?.focus(), t?.select();
|
|
117
|
+
}
|
|
118
|
+
function Q() {
|
|
119
|
+
let e = q.value.join(""), t = z.value;
|
|
120
|
+
z.value = e, e.length === H.value && e !== t && B("complete", e);
|
|
99
121
|
}
|
|
100
|
-
function
|
|
101
|
-
let n =
|
|
102
|
-
for (; r > 0 && n[r - 1]?.type === "literal";) r--;
|
|
103
|
-
let
|
|
122
|
+
function re(e, t) {
|
|
123
|
+
let n = V.value, r = n.findIndex((e) => e.type === "slot" && e.slotIndex === t), i = [...e];
|
|
124
|
+
if (i.length > 1) for (; r > 0 && n[r - 1]?.type === "literal";) r--;
|
|
125
|
+
let a = 0, o = null;
|
|
104
126
|
for (let e of n.slice(r)) {
|
|
105
127
|
if (a >= i.length) break;
|
|
106
128
|
if (e.type === "literal") {
|
|
@@ -108,76 +130,121 @@ var E = [
|
|
|
108
130
|
continue;
|
|
109
131
|
}
|
|
110
132
|
let t = "";
|
|
111
|
-
for (; a < i.length && !t;) t =
|
|
133
|
+
for (; a < i.length && !t;) t = W(i[a] ?? ""), a++;
|
|
112
134
|
if (!t) break;
|
|
113
|
-
|
|
135
|
+
q.value[e.slotIndex] = t, o = e.slotIndex;
|
|
114
136
|
}
|
|
115
137
|
return o;
|
|
116
138
|
}
|
|
117
|
-
function
|
|
118
|
-
let n = t.target, r =
|
|
119
|
-
|
|
120
|
-
|
|
139
|
+
function ie(e, t) {
|
|
140
|
+
let n = t.target, r = Y(), i = r === -1 || e < r, a = q.value[e] ?? "", o = n.value, s = o.length - a.length;
|
|
141
|
+
a && s > 0 && n.selectionStart !== null && (o = o.slice(Math.max(0, n.selectionStart - s), n.selectionStart));
|
|
142
|
+
let c = re(o, i ? e : r);
|
|
143
|
+
if (c === null) {
|
|
144
|
+
i && X(e), n.value = q.value[e] ?? "", Q();
|
|
121
145
|
return;
|
|
122
146
|
}
|
|
123
|
-
n.value =
|
|
147
|
+
n.value = q.value[e] ?? "", Z(Math.min(c + 1, H.value - 1)), Q();
|
|
124
148
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
149
|
+
function ae(e, t) {
|
|
150
|
+
let n = Y();
|
|
151
|
+
n !== -1 && e > n ? G.value[n]?.focus() : t.target.select();
|
|
127
152
|
}
|
|
128
|
-
|
|
153
|
+
function oe(e, t) {
|
|
154
|
+
let n = Y(), r = n === -1 ? H.value - 1 : n, i = null;
|
|
155
|
+
switch (t.key) {
|
|
156
|
+
case "Backspace":
|
|
157
|
+
if (j.readonly || j.disabled || q.value[e] || e === 0) return;
|
|
158
|
+
X(e - 1), Q(), i = e - 1;
|
|
159
|
+
break;
|
|
160
|
+
case "ArrowLeft":
|
|
161
|
+
e > 0 && (i = e - 1);
|
|
162
|
+
break;
|
|
163
|
+
case "ArrowRight":
|
|
164
|
+
e < r && (i = e + 1);
|
|
165
|
+
break;
|
|
166
|
+
case "Home":
|
|
167
|
+
i = 0;
|
|
168
|
+
break;
|
|
169
|
+
case "End":
|
|
170
|
+
i = r;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
i !== null && (t.preventDefault(), Z(i));
|
|
174
|
+
}
|
|
175
|
+
let $ = /* @__PURE__ */ new Map();
|
|
176
|
+
function se(e) {
|
|
177
|
+
let t = $.get(e);
|
|
178
|
+
return t || (t = (t) => {
|
|
179
|
+
G.value[e] = t;
|
|
180
|
+
}, $.set(e, t)), t;
|
|
181
|
+
}
|
|
182
|
+
return h({
|
|
129
183
|
focus: (e) => {
|
|
130
|
-
let t =
|
|
131
|
-
|
|
184
|
+
let t = q.value.findIndex((e) => !e), n = t === -1 ? H.value - 1 : t;
|
|
185
|
+
G.value[n]?.focus(e);
|
|
132
186
|
},
|
|
133
187
|
select: () => {
|
|
134
|
-
(
|
|
188
|
+
(G.value.find((e) => e === document.activeElement) ?? G.value[0])?.select();
|
|
135
189
|
},
|
|
136
|
-
el:
|
|
137
|
-
}), (e,
|
|
190
|
+
el: K
|
|
191
|
+
}), (e, t) => (_(), d("div", g({
|
|
138
192
|
ref_key: "rootEl",
|
|
139
|
-
ref:
|
|
140
|
-
},
|
|
193
|
+
ref: K
|
|
194
|
+
}, I.value, {
|
|
141
195
|
class: "v-input-otp v-control",
|
|
142
196
|
role: "group",
|
|
143
|
-
"aria-label":
|
|
144
|
-
"aria-describedby":
|
|
145
|
-
"data-invalid":
|
|
146
|
-
"data-size":
|
|
147
|
-
"data-compact":
|
|
148
|
-
"data-disabled":
|
|
149
|
-
"data-readonly":
|
|
150
|
-
}), [
|
|
151
|
-
key:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
197
|
+
"aria-label": x(N),
|
|
198
|
+
"aria-describedby": ne.value,
|
|
199
|
+
"data-invalid": m.invalid ? "" : void 0,
|
|
200
|
+
"data-size": m.size,
|
|
201
|
+
"data-compact": m.compact ? "" : void 0,
|
|
202
|
+
"data-disabled": m.disabled ? "" : void 0,
|
|
203
|
+
"data-readonly": m.readonly ? "" : void 0
|
|
204
|
+
}), [
|
|
205
|
+
f("div", D, [(_(!0), d(s, null, y(V.value, (e, t) => (_(), d(s, { key: t }, [e.type === "slot" ? (_(), d("input", {
|
|
206
|
+
key: 0,
|
|
207
|
+
ref_for: !0,
|
|
208
|
+
ref: se(e.slotIndex),
|
|
209
|
+
type: "text",
|
|
210
|
+
class: "v-input-otp-input",
|
|
211
|
+
inputmode: m.format === "numeric" ? "numeric" : "text",
|
|
212
|
+
autocomplete: e.slotIndex === 0 ? "one-time-code" : "off",
|
|
213
|
+
value: q.value[e.slotIndex],
|
|
214
|
+
disabled: m.disabled,
|
|
215
|
+
readonly: m.readonly || void 0,
|
|
216
|
+
"aria-label": x(M).inputOTP.slot(e.slotIndex + 1, H.value),
|
|
217
|
+
"aria-invalid": m.invalid || void 0,
|
|
218
|
+
onInput: (t) => ie(e.slotIndex, t),
|
|
219
|
+
onKeydown: (t) => oe(e.slotIndex, t),
|
|
220
|
+
onFocus: (t) => ae(e.slotIndex, t)
|
|
221
|
+
}, null, 40, O)) : (_(), d("span", k, [m.separatorIcon ? (_(), l(r, g({
|
|
222
|
+
key: 0,
|
|
223
|
+
ref_for: !0
|
|
224
|
+
}, x(n)(m.separatorIcon)), null, 16)) : (_(), d(s, { key: 1 }, [p(b(e.char), 1)], 64))]))], 64))), 128))]),
|
|
225
|
+
f("input", g(L.value, {
|
|
226
|
+
class: "v-input-otp-native v-visually-hidden",
|
|
227
|
+
type: "text",
|
|
228
|
+
tabindex: "-1",
|
|
229
|
+
"aria-hidden": "true",
|
|
230
|
+
autocomplete: "off",
|
|
231
|
+
value: z.value,
|
|
232
|
+
pattern: `.{${H.value}}`,
|
|
233
|
+
disabled: m.disabled,
|
|
234
|
+
readonly: m.readonly || void 0,
|
|
235
|
+
onFocus: t[0] ||= (e) => Z(Y() === -1 ? 0 : Y())
|
|
236
|
+
}), null, 16, te),
|
|
237
|
+
m.hint ? (_(), l(i, {
|
|
238
|
+
key: 0,
|
|
239
|
+
id: x(R),
|
|
240
|
+
variant: "caption",
|
|
241
|
+
tone: "muted",
|
|
242
|
+
class: "v-input-otp-hint"
|
|
243
|
+
}, {
|
|
244
|
+
default: ee(() => [p(b(m.hint), 1)]),
|
|
245
|
+
_: 1
|
|
246
|
+
}, 8, ["id"])) : u("", !0)
|
|
247
|
+
], 16, E));
|
|
181
248
|
}
|
|
182
249
|
});
|
|
183
250
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VInputOTP.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * The row of boxes a one-time code is typed into, one character per box.\n *\n * The platform has no such control, so each box is a real `<input>` and the JS is what makes\n * the row behave as one field: a character moves to the next box, Backspace on an empty one\n * goes back, the arrows walk along, and a code pasted anywhere is spread across them. The\n * first box carries `autocomplete=\"one-time-code\"`, so a code arriving from an SMS or a\n * password manager is spread the same way.\n *\n * `pattern` cuts the row up: every `#` is a box, every other character a decorative literal\n * shown between them and never part of the value. Pasting understands those literals — a\n * code copied formatted, `GT-123`, is consumed with them in place — and `format` filters the\n * rest, forcing capitals outside a numeric code so the value has one canonical form.\n */\n\nimport { computed, ref, useAttrs, useId, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a\n * separator shown between the boxes without ever being part of the value —\n * `'GT-###'`, `'###.###.###'`. It wins over `length`.\n */\n pattern?: string\n /**\n * An icon drawn in place of EVERY separator of the pattern. It suits a template\n * whose separators are purely decorative, `'###-###'`, and should not be used with\n * one carrying meaningful text such as `'GT-###'`, which the icon would erase.\n */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code\n * can still be selected and copied, which is what separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design\n * system dictionary.\n *\n * Unlike the `label` of VInput and the other text fields, NOTHING is rendered from it:\n * a row of boxes carries its own instructions above it, written by the page. Use `hint`\n * for a line the reader can see.\n */\n label?: string\n /**\n * A line of help under the boxes — where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\n// @a11y\n/*\n * The root IS the group, so the consumer's attributes belong on it — but they are bound\n * BEFORE the two the component decides itself. `aria-describedby` is a list, and the hint\n * is appended to whatever the consumer already pointed at instead of replacing it; left\n * to the fallthrough, their value would land last and take the hint out of the\n * announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n// No `useFieldIds` here: the row is named by `aria-label` and renders no `<label>`, so the\n// field id that composable generates would have nothing to point it at.\nconst hintId = useId()\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /** Emitted the moment every box is filled, carrying the complete code. */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst inputs = ref<(HTMLInputElement | null)[]>([])\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and the model is NOT\n // rewritten to match: writing back here would feed the watcher below and the two\n // would keep correcting each other.\n digits.value = Array.from({ length: slotCount.value }, (_, i) => value[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/*\n * TRAP — the list of boxes is trimmed AFTER the render, never before it. A box that\n * disappears calls its function ref with `null` while it is being unmounted, at its OLD\n * position: trimmed in a `pre` watcher, that late assignment would stretch the array back\n * out, and a pattern that shortened would keep its departed boxes as trailing entries.\n */\nwatch(\n slotCount,\n (count) => {\n inputs.value.length = count\n },\n { flush: 'post' },\n)\n\nfunction commit() {\n const code = digits.value.join('')\n model.value = code\n if (code.length === slotCount.value) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received\n * them. It walks the row: a separator swallows the incoming character when the two\n * match, which is what lets a code pasted in its formatted shape line up, and a box\n * takes the next character the format accepts.\n *\n * It returns the last box it filled, so the caller knows where to put the focus, and\n * `null` when nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n // Step back over the separators immediately before that box: pasting the whole\n // string onto the first box means pasting its prefix too, and \"GT-\" has to be\n // matched rather than treated as characters of the code.\n while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n const chars = [...raw]\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core — moving the focus forward on its own is what turns a row of\n// separate inputs into something that types like one field; spreading the characters\n// is the core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n // The same path serves a single keystroke and a pasted code: both are spread from\n // this box onwards.\n const lastFilled = distribute(el.value, slotIndex)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format.\n digits.value[slotIndex] = ''\n el.value = ''\n commit()\n return\n }\n el.value = digits.value[slotIndex] ?? ''\n inputs.value[Math.min(lastFilled + 1, slotCount.value - 1)]?.focus()\n commit()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n if (event.key === 'Backspace' && !digits.value[slotIndex] && slotIndex > 0) {\n event.preventDefault()\n digits.value[slotIndex - 1] = ''\n commit()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowLeft' && slotIndex > 0) {\n event.preventDefault()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowRight' && slotIndex < slotCount.value - 1) {\n event.preventDefault()\n inputs.value[slotIndex + 1]?.focus()\n }\n}\n/*\n * The same trio every other field of the size scale exposes. `focus` goes to the FIRST\n * EMPTY box rather than to the first box outright, which is where a reader resuming a\n * half-entered code expects to land.\n */\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => {\n const empty = digits.value.findIndex((d) => !d)\n const target = empty === -1 ? slotCount.value - 1 : empty\n inputs.value[target]?.focus(options)\n },\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = inputs.value.find((el) => el === document.activeElement)\n ;(active ?? inputs.value[0])?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-bind=\"attrs\"\n class=\"v-input-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n :ref=\"\n (el) => {\n inputs[cell.slotIndex] = el as HTMLInputElement | null\n }\n \"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"($event.target as HTMLInputElement).select()\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-otp-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input-otp {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n\n /* A column, so the hint sits under the boxes rather than beside them; the row itself\n is the box below. `inline-flex` keeps the component's own width, and the start\n alignment stops the hint from stretching the row to its own length. */\n display: inline-flex;\n flex-direction: column;\n align-items: start;\n gap: var(--vectis-space-1);\n }\n\n .v-input-otp-boxes {\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition: border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The focused box appears to have a two-pixel border, exactly as in VInput and\n VTextarea: its own 1px border plus a 1px shadow of the same colour just outside\n it. The transparent outline is the safety net for Windows forced colours, which\n drop box-shadows entirely. */\n .v-input-otp-input:focus-visible {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /* A read-only row takes the sunken background of a read-only VInput, and the state is\n read from [data-readonly] rather than from `:read-only`, which the browser also matches\n on a disabled box. It is declared BEFORE the invalid and disabled blocks, all three\n weighing (0,2,0), so those still win over it. */\n .v-input-otp[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input:focus-visible {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /* A disabled row greys out through the colour tokens and never through opacity, the\n same treatment as VInput. */\n .v-input-otp[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp[data-disabled] .v-input-otp-literal,\n .v-input-otp[data-disabled] .v-input-otp-hint {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VInputOTP.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * The row of boxes a one-time code is typed into, one character per box.\n *\n * The platform has no such control, so each box is a real `<input>` and the JS is what makes\n * the row behave as one field: a character moves to the next box, Backspace on an empty one\n * goes back, the arrows walk along, and a code pasted anywhere is spread across them. The\n * first box carries `autocomplete=\"one-time-code\"`, so a code arriving from an SMS or a\n * password manager is spread the same way.\n *\n * `pattern` cuts the row up: every `#` is a box, every other character a decorative literal\n * shown between them and never part of the value. Pasting understands those literals — a\n * code copied formatted, `GT-123`, is consumed with them in place — and `format` filters the\n * rest, forcing capitals outside a numeric code so the value has one canonical form.\n *\n * The value is the filled boxes read in order, so the filled boxes always form a PREFIX:\n * typing or focusing past the first empty box lands in that box, and emptying a box closes\n * the gap. Without that invariant `1 _ 3 _` would be the value `13`, which the next sync\n * would redraw as `1 3 _ _`.\n *\n * A row of boxes is not a form control, so a visually hidden native input carries the code\n * for the form: `name`, `form` and `required` land on it, and a `pattern` of the full\n * length makes a half-typed code invalid.\n */\n\nimport { computed, ref, useAttrs, useId, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a\n * separator shown between the boxes without ever being part of the value, as in\n * `'GT-###'` or `'###.###.###'`. It wins over `length`.\n */\n pattern?: string\n /**\n * An icon drawn in place of EVERY separator of the pattern. It suits a template\n * whose separators are purely decorative, `'###-###'`, and should not be used with\n * one carrying meaningful text such as `'GT-###'`, which the icon would erase.\n */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code\n * can still be selected and copied, which is what separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design\n * system dictionary.\n *\n * Unlike the `label` of VInput and the other text fields, NOTHING is rendered from it:\n * a row of boxes carries its own instructions above it, written by the page. Use `hint`\n * for a line the reader can see.\n */\n label?: string\n /**\n * A line of help under the boxes: where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\n// @a11y\n/*\n * The root IS the group, so the consumer's attributes belong on it — but they are bound\n * BEFORE the two the component decides itself. `aria-describedby` is a list, and the hint\n * is appended to whatever the consumer already pointed at instead of replacing it; left\n * to the fallthrough, their value would land last and take the hint out of the\n * announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n\n// What the FORM reads goes on the hidden native input, and everything else on the group:\n// on the group a `name` submits nothing and `required` validates nothing, silently.\nconst NATIVE_ONLY = ['name', 'form', 'required']\nconst groupAttrs = computed(() =>\n Object.fromEntries(Object.entries(attrs).filter(([key]) => !NATIVE_ONLY.includes(key))),\n)\nconst nativeAttrs = computed(() =>\n Object.fromEntries(Object.entries(attrs).filter(([key]) => NATIVE_ONLY.includes(key))),\n)\n// No `useFieldIds` here: the row is named by `aria-label` and renders no `<label>`, so the\n// field id that composable generates would have nothing to point it at.\nconst hintId = useId()\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /**\n * Emitted the moment the code BECOMES complete, carrying it. Retyping a character of a\n * complete code with the same one does not emit it again.\n */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst inputs = ref<(HTMLInputElement | null)[]>([])\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and one holding characters the\n // format refuses is shown filtered — but the model is NOT rewritten to match: writing\n // back here would feed the watcher below and the two would keep correcting each other.\n const clean = sanitize(value)\n digits.value = Array.from({ length: slotCount.value }, (_, i) => clean[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/*\n * TRAP — the list of boxes is trimmed AFTER the render, never before it. A box that\n * disappears calls its function ref with `null` while it is being unmounted, at its OLD\n * position: trimmed in a `pre` watcher, that late assignment would stretch the array back\n * out, and a pattern that shortened would keep its departed boxes as trailing entries.\n */\nwatch(\n slotCount,\n (count) => {\n inputs.value.length = count\n },\n { flush: 'post' },\n)\n\n/** The first empty box, or -1 once the code is complete. Every box before it is filled. */\nconst firstEmpty = () => digits.value.findIndex((d) => !d)\n\n/** Takes a character out and moves the following ones back, so no gap is left behind. */\nfunction removeAt(slot: number) {\n digits.value.splice(slot, 1)\n digits.value.push('')\n}\n\nfunction focusBox(slot: number) {\n const el = inputs.value[slot]\n el?.focus()\n // Selected, so the next keystroke REPLACES the character: a box that already has the\n // focus receives no focus event to select it.\n el?.select()\n}\n\nfunction commit() {\n const code = digits.value.join('')\n const previous = model.value\n model.value = code\n // Only when the code CHANGES into a full one: retyping a character of a complete code\n // must not submit it a second time for a consumer listening to `complete`.\n if (code.length === slotCount.value && code !== previous) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received\n * them. It walks the row: a separator swallows the incoming character when the two\n * match, which is what lets a code pasted in its formatted shape line up, and a box\n * takes the next character the format accepts.\n *\n * It returns the last box it filled, so the caller knows where to put the focus, and\n * `null` when nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n const chars = [...raw]\n // Step back over the separators immediately before that box: pasting the whole\n // string onto the first box means pasting its prefix too, and \"GT-\" has to be\n // matched rather than treated as characters of the code. Never for ONE character: a\n // `G` typed into the first box of `GT-###` is the code's first character, not the\n // separator, and swallowing it would make such a code impossible to type.\n if (chars.length > 1) while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core — moving the focus forward on its own is what turns a row of\n// separate inputs into something that types like one field; spreading the characters\n// is the core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n const empty = firstEmpty()\n const filled = empty === -1 || slotIndex < empty\n\n // A filled box whose character was not selected receives the new one NEXT to it. What\n // was inserted is what lies just before the caret, and it is what counts.\n const previous = digits.value[slotIndex] ?? ''\n let raw = el.value\n const inserted = raw.length - previous.length\n if (previous && inserted > 0 && el.selectionStart !== null) {\n raw = raw.slice(Math.max(0, el.selectionStart - inserted), el.selectionStart)\n }\n\n // The same path serves a single keystroke and a pasted code: both are spread from this\n // box onwards — or from the first empty box, when this one lies past it.\n const lastFilled = distribute(raw, filled ? slotIndex : empty)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format. A filled box\n // loses its character and the ones after it close the gap.\n if (filled) removeAt(slotIndex)\n el.value = digits.value[slotIndex] ?? ''\n commit()\n return\n }\n // Written by hand: when the characters went to another box, this one's value is still\n // '' in the render, and the patch would leave the typed character on screen.\n el.value = digits.value[slotIndex] ?? ''\n focusBox(Math.min(lastFilled + 1, slotCount.value - 1))\n commit()\n}\n\n// @keyboard — the filled boxes form a prefix, so focusing a box past the first empty one\n// sends the focus there instead: a pointer landing on box 5 of a two-character code types\n// into box 3.\nfunction onFocus(slotIndex: number, event: FocusEvent) {\n const empty = firstEmpty()\n if (empty !== -1 && slotIndex > empty) inputs.value[empty]?.focus()\n else (event.target as HTMLInputElement).select()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n const empty = firstEmpty()\n // The furthest box the focus may reach: the first empty one, or the last of a full code.\n const reachable = empty === -1 ? slotCount.value - 1 : empty\n let target: number | null = null\n switch (event.key) {\n case 'Backspace':\n // A read-only box refuses its own edits natively, but this erases ANOTHER box, which\n // nothing native guards.\n if (props.readonly || props.disabled || digits.value[slotIndex] || slotIndex === 0) return\n removeAt(slotIndex - 1)\n commit()\n target = slotIndex - 1\n break\n case 'ArrowLeft':\n if (slotIndex > 0) target = slotIndex - 1\n break\n case 'ArrowRight':\n if (slotIndex < reachable) target = slotIndex + 1\n break\n case 'Home':\n target = 0\n break\n case 'End':\n target = reachable\n break\n }\n if (target === null) return\n event.preventDefault()\n focusBox(target)\n}\n\n// Function refs are handed to the template ONCE per box: an inline one is a new function\n// on every render, which Vue answers with a null-then-element call on every keystroke.\nconst refSetters = new Map<number, (el: unknown) => void>()\nfunction inputRef(slot: number) {\n let set = refSetters.get(slot)\n if (!set) {\n set = (el) => {\n inputs.value[slot] = el as HTMLInputElement | null\n }\n refSetters.set(slot, set)\n }\n return set\n}\n/*\n * The same trio every other field of the size scale exposes. `focus` goes to the FIRST\n * EMPTY box rather than to the first box outright, which is where a reader resuming a\n * half-entered code expects to land.\n */\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => {\n const empty = digits.value.findIndex((d) => !d)\n const target = empty === -1 ? slotCount.value - 1 : empty\n inputs.value[target]?.focus(options)\n },\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = inputs.value.find((el) => el === document.activeElement)\n ;(active ?? inputs.value[0])?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-bind=\"groupAttrs\"\n class=\"v-input-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n :ref=\"inputRef(cell.slotIndex)\"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"onFocus(cell.slotIndex, $event)\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <!-- What a form reads: the code, under the consumer's name. Out of the tab order and\n hidden from assistive technology, the boxes being the control; a browser focusing\n it to report an invalid code is sent on to the box to fill. -->\n <input\n v-bind=\"nativeAttrs\"\n class=\"v-input-otp-native v-visually-hidden\"\n type=\"text\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n autocomplete=\"off\"\n :value=\"model\"\n :pattern=\"`.{${slotCount}}`\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n @focus=\"focusBox(firstEmpty() === -1 ? 0 : firstEmpty())\"\n />\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-otp-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input-otp {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n\n /* A column, so the hint sits under the boxes rather than beside them; the row itself\n is the box below. `inline-flex` keeps the component's own width, and the start\n alignment stops the hint from stretching the row to its own length. */\n display: inline-flex;\n flex-direction: column;\n align-items: start;\n gap: var(--vectis-space-1);\n }\n\n /* A code reads left to right in every language, like the HH:MM of VTimeInput: mirrored\n under `dir=\"rtl\"`, the arrows would walk against the boxes and a `GT-###` pattern would\n be drawn backwards. */\n .v-input-otp-boxes {\n direction: ltr;\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The states follow VInput's, in VInput's order: read-only, hover, focus, invalid,\n disabled. Read-only, focus, invalid and disabled all weigh (0,2,0), so the source order\n arbitrates between them and read-only has to come FIRST, or it would repaint the focus\n and error borders grey.\n\n A read-only row takes the sunken background and the lighter border of a read-only\n VInput, and the state is read from [data-readonly] rather than from `:read-only`, which\n the browser also matches on a disabled box. */\n .v-input-otp[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n /* The hover weighs (0,5,0) and keeps itself off a focused, invalid or disabled box\n through its own `:not()`, as VInput's does. */\n .v-input-otp:not([data-invalid]) .v-input-otp-input:hover:not(:focus, :disabled) {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /* The focused box appears to have a two-pixel border, exactly as in VInput and\n VTextarea: its own 1px border plus a 1px shadow of the same colour just outside\n it. `:focus` rather than `:focus-visible`, like any text field, which shows its focus\n when clicked into too. The transparent outline is the safety net for Windows forced\n colours, which drop box-shadows entirely. */\n .v-input-otp-input:focus {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input:focus {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /* A disabled row greys out through the colour tokens and never through opacity, the\n same treatment as VInput — `text-muted` inside the box, where `text-subtle` would fall\n under 4.5:1 against `surface-muted`, and `text-subtle` for what sits on the page. */\n .v-input-otp[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp[data-disabled] .v-input-otp-literal,\n .v-input-otp[data-disabled] .v-input-otp-hint {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -14,7 +14,7 @@ interface MenuProps {
|
|
|
14
14
|
compact?: boolean;
|
|
15
15
|
/**
|
|
16
16
|
* A width for the panel: a number is read as pixels, a string as any CSS length or
|
|
17
|
-
* keyword
|
|
17
|
+
* keyword (`16rem`, `max-content`). It applies to the menu itself; submenus keep the
|
|
18
18
|
* default width.
|
|
19
19
|
*/
|
|
20
20
|
width?: number | string;
|
|
@@ -38,8 +38,8 @@ export type MenuTriggerProps = {
|
|
|
38
38
|
};
|
|
39
39
|
type __VLS_Slots = {
|
|
40
40
|
/**
|
|
41
|
-
* The button that opens the menu. Bind the `triggerProps` it receives onto it
|
|
42
|
-
*
|
|
41
|
+
* The button that opens the menu. Bind the `triggerProps` it receives onto it: that
|
|
42
|
+
* is what wires the two together.
|
|
43
43
|
*/
|
|
44
44
|
trigger(props: {
|
|
45
45
|
triggerProps: MenuTriggerProps;
|
|
@@ -51,7 +51,7 @@ declare function openAtTrigger(): void;
|
|
|
51
51
|
type __VLS_ModelProps = {
|
|
52
52
|
/**
|
|
53
53
|
* Whether the menu is showing. It starts closed and is fed BY the panel, so the browser's
|
|
54
|
-
* own dismissal
|
|
54
|
+
* own dismissal (a click outside, Escape, choosing a command) writes back to it.
|
|
55
55
|
*/
|
|
56
56
|
'open'?: boolean;
|
|
57
57
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMenu.js","names":[],"sources":["../../../src/components/VMenu/VMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A menu of actions, opened by a button and closed as soon as one is chosen. It follows the\n * ARIA menu pattern, which is what tells a screen reader these are commands rather than\n * links or options.\n *\n * It rests on `popover=\"auto\"`: the panel light-dismisses without a line of code and\n * positions itself in CSS against its `popovertarget` invoker, which is its implicit anchor.\n *\n * The JS covers only what the platform does not — keeping `v-model:open` in step, moving\n * focus into the panel and handing it back to the trigger. The keyboard inside the panel\n * lives in VMenuPanel.\n *\n * HOW FAR the focus goes in depends on how the menu was opened: from the keyboard it settles\n * on the first command, ready to be chosen; by pointer it stops at the panel, so nothing is\n * singled out for someone who has not asked for a choice yet. The arrows still enter the\n * list from there.\n */\n\nimport { computed, provide, ref, useId } from 'vue'\n\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuInvoker, menuKey } from './context'\nimport type { MenuPlacement, MenuSize } from './context'\nimport { usePopoverModel } from '../../composables/usePopover'\nimport { isKeyboardFocus } from '../../utils/focus'\n\ninterface MenuProps {\n /**\n * Where the panel opens relative to its trigger. The browser moves it to another\n * side by itself when there is not enough room.\n */\n placement?: MenuPlacement\n /**\n * How tall the rows are: 32, 40 or 48 pixels. Submenus inherit it, so it is set\n * once on the menu as a whole.\n */\n size?: MenuSize\n /** Takes 4px off the height of every row, submenus included. */\n compact?: boolean\n /**\n * A width for the panel: a number is read as pixels, a string as any CSS length or\n * keyword
|
|
1
|
+
{"version":3,"file":"VMenu.js","names":[],"sources":["../../../src/components/VMenu/VMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A menu of actions, opened by a button and closed as soon as one is chosen. It follows the\n * ARIA menu pattern, which is what tells a screen reader these are commands rather than\n * links or options.\n *\n * It rests on `popover=\"auto\"`: the panel light-dismisses without a line of code and\n * positions itself in CSS against its `popovertarget` invoker, which is its implicit anchor.\n *\n * The JS covers only what the platform does not — keeping `v-model:open` in step, moving\n * focus into the panel and handing it back to the trigger. The keyboard inside the panel\n * lives in VMenuPanel.\n *\n * HOW FAR the focus goes in depends on how the menu was opened: from the keyboard it settles\n * on the first command, ready to be chosen; by pointer it stops at the panel, so nothing is\n * singled out for someone who has not asked for a choice yet. The arrows still enter the\n * list from there.\n */\n\nimport { computed, provide, ref, useId } from 'vue'\n\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuInvoker, menuKey } from './context'\nimport type { MenuPlacement, MenuSize } from './context'\nimport { usePopoverModel } from '../../composables/usePopover'\nimport { isKeyboardFocus } from '../../utils/focus'\n\ninterface MenuProps {\n /**\n * Where the panel opens relative to its trigger. The browser moves it to another\n * side by itself when there is not enough room.\n */\n placement?: MenuPlacement\n /**\n * How tall the rows are: 32, 40 or 48 pixels. Submenus inherit it, so it is set\n * once on the menu as a whole.\n */\n size?: MenuSize\n /** Takes 4px off the height of every row, submenus included. */\n compact?: boolean\n /**\n * A width for the panel: a number is read as pixels, a string as any CSS length or\n * keyword (`16rem`, `max-content`). It applies to the menu itself; submenus keep the\n * default width.\n */\n width?: number | string\n /**\n * Stops the panel from being narrower than the button that opened it, while leaving\n * it free to grow wider for its content. Submenus are unaffected.\n */\n matchTrigger?: boolean\n}\n\n// Deliberately not assigned to a variable: the template reads the props directly.\nwithDefaults(defineProps<MenuProps>(), {\n placement: 'bottom-start',\n size: 'sm',\n compact: false,\n width: undefined,\n matchTrigger: false,\n})\n\n/**\n * Whether the menu is showing. It starts closed and is fed BY the panel, so the browser's\n * own dismissal (a click outside, Escape, choosing a command) writes back to it.\n */\nconst open = defineModel<boolean>('open', { default: false })\n\n/**\n * What the trigger has to carry: the link to the panel it opens, and the two\n * attributes telling assistive technology that a menu is attached to this button and\n * whether it is currently open.\n */\nexport type MenuTriggerProps = {\n popovertarget: string\n 'aria-haspopup': 'menu'\n 'aria-expanded': boolean\n 'aria-controls': string\n}\n\ndefineSlots<{\n /**\n * The button that opens the menu. Bind the `triggerProps` it receives onto it: that\n * is what wires the two together.\n */\n trigger(props: { triggerProps: MenuTriggerProps }): unknown\n /** The contents of the menu: VMenuItem, VMenuGroup and VMenuSeparator. */\n default(): unknown\n}>()\n\nconst panelRef = ref<InstanceType<typeof VMenuPanel> | null>(null)\nconst menuId = useId()\n\nconst triggerProps = computed<MenuTriggerProps>(() => ({\n popovertarget: menuId,\n 'aria-haspopup': 'menu',\n 'aria-expanded': open.value,\n 'aria-controls': menuId,\n}))\n\n// Closing this panel closes every submenu with it: they are rendered inside it, and\n// the browser closes a stack of popovers from the outside in.\nprovide(menuKey, { closeAll: () => panelRef.value?.close() })\n\n// @a11y\n// The focus half of the bridge: into the panel when it opens, back to the\n// trigger when it closes. Keeping the state in step alone would leave a keyboard user\n// stranded at the top of the page every time a menu opened or closed.\n//\n// WHERE it lands in the panel is the whole question, and the browser is the one that\n// answers it. In a menu the focus IS the highlight (see the rule in VMenuItem), so\n// taking the first item singles out a command — which is what someone arriving on the\n// arrows wants, and what someone who has just clicked never asked for. `:focus-visible`\n// is exactly that distinction, read off whatever holds the focus at this instant: the\n// trigger, since opening a popover through `popovertarget` does not move the focus.\n//\n// The pointer branch focuses the PANEL rather than nothing at all, and that is\n// structural: every key the menu answers to is listened for on the panel element, so\n// focus left outside it — on a trigger that is a DOM sibling — would silence the arrows,\n// Home/End and the Tab that closes the menu. Both ways of being wrong are mild and\n// symmetric: a misread pointer costs one extra keystroke, a misread keyboard gives back\n// the older behaviour.\nfunction onToggle(value: boolean) {\n open.value = value\n if (value) {\n if (isKeyboardFocus(document.activeElement)) panelRef.value?.focusFirst()\n else panelRef.value?.focusPanel()\n } else {\n // The browser's own dismissal — a click outside, Escape — leaves the focus\n // nowhere, on the page body. Only then is it handed back to the trigger: if the\n // focus has already moved somewhere else deliberately, it must be left alone.\n const active = document.activeElement\n if (!active || active === document.body || panelRef.value?.el?.contains(active)) {\n menuInvoker(menuId)?.focus()\n }\n }\n}\n\n/*\n * TRAP — every opening that does not come from a click on the trigger has to NAME that\n * trigger. A popover opened through `popovertarget` takes its invoker as its implicit\n * anchor; opened from code with no `source`, it has no anchor at all and the browser\n * paints it at the corner of the viewport. Nothing errors, and the panel is otherwise\n * perfectly functional there.\n *\n * The invoker is searched for rather than held: it is rendered by the consumer, in a\n * slot this component cannot reach into.\n */\nfunction openAtTrigger() {\n panelRef.value?.show(menuInvoker(menuId) ?? undefined)\n}\n\n// The open state is the panel's, read back from it rather than copied here: the panel has\n// already recorded the browser's answer by the time it reports the toggle.\nusePopoverModel(\n open,\n () => panelRef.value?.shown ?? false,\n openAtTrigger,\n () => panelRef.value?.close(),\n)\n\n/*\n * The same trio VDialog, VDialogAlert and VPopover answer. It is a relay onto the panel,\n * which has held it all along: opening a menu from code was otherwise only possible through\n * the model, which inserts a tick where a focus move cannot afford one.\n */\ndefineExpose({\n /** Opens the menu at once, without waiting for the model to come round. */\n show: openAtTrigger,\n /** Closes it at once, submenus included. */\n close: () => panelRef.value?.close(),\n /** The panel element, for what neither of the two above covers. */\n el: computed(() => panelRef.value?.el ?? null),\n})\n</script>\n\n<template>\n <slot name=\"trigger\" :trigger-props=\"triggerProps\" />\n <VMenuPanel\n :id=\"menuId\"\n ref=\"panelRef\"\n :placement=\"placement\"\n :size=\"size\"\n :compact=\"compact\"\n :width=\"width\"\n :match-trigger=\"matchTrigger\"\n @toggle=\"onToggle\"\n >\n <slot />\n </VMenuPanel>\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VMenu/VMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A menu of actions, opened by a button and closed as soon as one is chosen. It follows the\n * ARIA menu pattern, which is what tells a screen reader these are commands rather than\n * links or options.\n *\n * It rests on `popover=\"auto\"`: the panel light-dismisses without a line of code and\n * positions itself in CSS against its `popovertarget` invoker, which is its implicit anchor.\n *\n * The JS covers only what the platform does not — keeping `v-model:open` in step, moving\n * focus into the panel and handing it back to the trigger. The keyboard inside the panel\n * lives in VMenuPanel.\n *\n * HOW FAR the focus goes in depends on how the menu was opened: from the keyboard it settles\n * on the first command, ready to be chosen; by pointer it stops at the panel, so nothing is\n * singled out for someone who has not asked for a choice yet. The arrows still enter the\n * list from there.\n */\n\nimport { computed, provide, ref, useId } from 'vue'\n\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuInvoker, menuKey } from './context'\nimport type { MenuPlacement, MenuSize } from './context'\nimport { usePopoverModel } from '../../composables/usePopover'\nimport { isKeyboardFocus } from '../../utils/focus'\n\ninterface MenuProps {\n /**\n * Where the panel opens relative to its trigger. The browser moves it to another\n * side by itself when there is not enough room.\n */\n placement?: MenuPlacement\n /**\n * How tall the rows are: 32, 40 or 48 pixels. Submenus inherit it, so it is set\n * once on the menu as a whole.\n */\n size?: MenuSize\n /** Takes 4px off the height of every row, submenus included. */\n compact?: boolean\n /**\n * A width for the panel: a number is read as pixels, a string as any CSS length or\n * keyword
|
|
1
|
+
{"version":3,"file":"VMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VMenu/VMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A menu of actions, opened by a button and closed as soon as one is chosen. It follows the\n * ARIA menu pattern, which is what tells a screen reader these are commands rather than\n * links or options.\n *\n * It rests on `popover=\"auto\"`: the panel light-dismisses without a line of code and\n * positions itself in CSS against its `popovertarget` invoker, which is its implicit anchor.\n *\n * The JS covers only what the platform does not — keeping `v-model:open` in step, moving\n * focus into the panel and handing it back to the trigger. The keyboard inside the panel\n * lives in VMenuPanel.\n *\n * HOW FAR the focus goes in depends on how the menu was opened: from the keyboard it settles\n * on the first command, ready to be chosen; by pointer it stops at the panel, so nothing is\n * singled out for someone who has not asked for a choice yet. The arrows still enter the\n * list from there.\n */\n\nimport { computed, provide, ref, useId } from 'vue'\n\nimport VMenuPanel from './VMenuPanel.vue'\nimport { menuInvoker, menuKey } from './context'\nimport type { MenuPlacement, MenuSize } from './context'\nimport { usePopoverModel } from '../../composables/usePopover'\nimport { isKeyboardFocus } from '../../utils/focus'\n\ninterface MenuProps {\n /**\n * Where the panel opens relative to its trigger. The browser moves it to another\n * side by itself when there is not enough room.\n */\n placement?: MenuPlacement\n /**\n * How tall the rows are: 32, 40 or 48 pixels. Submenus inherit it, so it is set\n * once on the menu as a whole.\n */\n size?: MenuSize\n /** Takes 4px off the height of every row, submenus included. */\n compact?: boolean\n /**\n * A width for the panel: a number is read as pixels, a string as any CSS length or\n * keyword (`16rem`, `max-content`). It applies to the menu itself; submenus keep the\n * default width.\n */\n width?: number | string\n /**\n * Stops the panel from being narrower than the button that opened it, while leaving\n * it free to grow wider for its content. Submenus are unaffected.\n */\n matchTrigger?: boolean\n}\n\n// Deliberately not assigned to a variable: the template reads the props directly.\nwithDefaults(defineProps<MenuProps>(), {\n placement: 'bottom-start',\n size: 'sm',\n compact: false,\n width: undefined,\n matchTrigger: false,\n})\n\n/**\n * Whether the menu is showing. It starts closed and is fed BY the panel, so the browser's\n * own dismissal (a click outside, Escape, choosing a command) writes back to it.\n */\nconst open = defineModel<boolean>('open', { default: false })\n\n/**\n * What the trigger has to carry: the link to the panel it opens, and the two\n * attributes telling assistive technology that a menu is attached to this button and\n * whether it is currently open.\n */\nexport type MenuTriggerProps = {\n popovertarget: string\n 'aria-haspopup': 'menu'\n 'aria-expanded': boolean\n 'aria-controls': string\n}\n\ndefineSlots<{\n /**\n * The button that opens the menu. Bind the `triggerProps` it receives onto it: that\n * is what wires the two together.\n */\n trigger(props: { triggerProps: MenuTriggerProps }): unknown\n /** The contents of the menu: VMenuItem, VMenuGroup and VMenuSeparator. */\n default(): unknown\n}>()\n\nconst panelRef = ref<InstanceType<typeof VMenuPanel> | null>(null)\nconst menuId = useId()\n\nconst triggerProps = computed<MenuTriggerProps>(() => ({\n popovertarget: menuId,\n 'aria-haspopup': 'menu',\n 'aria-expanded': open.value,\n 'aria-controls': menuId,\n}))\n\n// Closing this panel closes every submenu with it: they are rendered inside it, and\n// the browser closes a stack of popovers from the outside in.\nprovide(menuKey, { closeAll: () => panelRef.value?.close() })\n\n// @a11y\n// The focus half of the bridge: into the panel when it opens, back to the\n// trigger when it closes. Keeping the state in step alone would leave a keyboard user\n// stranded at the top of the page every time a menu opened or closed.\n//\n// WHERE it lands in the panel is the whole question, and the browser is the one that\n// answers it. In a menu the focus IS the highlight (see the rule in VMenuItem), so\n// taking the first item singles out a command — which is what someone arriving on the\n// arrows wants, and what someone who has just clicked never asked for. `:focus-visible`\n// is exactly that distinction, read off whatever holds the focus at this instant: the\n// trigger, since opening a popover through `popovertarget` does not move the focus.\n//\n// The pointer branch focuses the PANEL rather than nothing at all, and that is\n// structural: every key the menu answers to is listened for on the panel element, so\n// focus left outside it — on a trigger that is a DOM sibling — would silence the arrows,\n// Home/End and the Tab that closes the menu. Both ways of being wrong are mild and\n// symmetric: a misread pointer costs one extra keystroke, a misread keyboard gives back\n// the older behaviour.\nfunction onToggle(value: boolean) {\n open.value = value\n if (value) {\n if (isKeyboardFocus(document.activeElement)) panelRef.value?.focusFirst()\n else panelRef.value?.focusPanel()\n } else {\n // The browser's own dismissal — a click outside, Escape — leaves the focus\n // nowhere, on the page body. Only then is it handed back to the trigger: if the\n // focus has already moved somewhere else deliberately, it must be left alone.\n const active = document.activeElement\n if (!active || active === document.body || panelRef.value?.el?.contains(active)) {\n menuInvoker(menuId)?.focus()\n }\n }\n}\n\n/*\n * TRAP — every opening that does not come from a click on the trigger has to NAME that\n * trigger. A popover opened through `popovertarget` takes its invoker as its implicit\n * anchor; opened from code with no `source`, it has no anchor at all and the browser\n * paints it at the corner of the viewport. Nothing errors, and the panel is otherwise\n * perfectly functional there.\n *\n * The invoker is searched for rather than held: it is rendered by the consumer, in a\n * slot this component cannot reach into.\n */\nfunction openAtTrigger() {\n panelRef.value?.show(menuInvoker(menuId) ?? undefined)\n}\n\n// The open state is the panel's, read back from it rather than copied here: the panel has\n// already recorded the browser's answer by the time it reports the toggle.\nusePopoverModel(\n open,\n () => panelRef.value?.shown ?? false,\n openAtTrigger,\n () => panelRef.value?.close(),\n)\n\n/*\n * The same trio VDialog, VDialogAlert and VPopover answer. It is a relay onto the panel,\n * which has held it all along: opening a menu from code was otherwise only possible through\n * the model, which inserts a tick where a focus move cannot afford one.\n */\ndefineExpose({\n /** Opens the menu at once, without waiting for the model to come round. */\n show: openAtTrigger,\n /** Closes it at once, submenus included. */\n close: () => panelRef.value?.close(),\n /** The panel element, for what neither of the two above covers. */\n el: computed(() => panelRef.value?.el ?? null),\n})\n</script>\n\n<template>\n <slot name=\"trigger\" :trigger-props=\"triggerProps\" />\n <VMenuPanel\n :id=\"menuId\"\n ref=\"panelRef\"\n :placement=\"placement\"\n :size=\"size\"\n :compact=\"compact\"\n :width=\"width\"\n :match-trigger=\"matchTrigger\"\n @toggle=\"onToggle\"\n >\n <slot />\n </VMenuPanel>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmEA,IAAM,IAAO,EAAoB,GAAC,MAA0B,GAwBtD,IAAW,EAA4C,IAAI,GAC3D,IAAS,EAAM,GAEf,IAAe,SAAkC;GACrD,eAAe;GACf,iBAAiB;GACjB,iBAAiB,EAAK;GACtB,iBAAiB;EACnB,EAAE;EAIF,EAAQ,GAAS,EAAE,gBAAgB,EAAS,OAAO,MAAM,EAAE,CAAC;EAoB5D,SAAS,EAAS,GAAgB;GAEhC,IADA,EAAK,QAAQ,GACT,GACF,AAAI,EAAgB,SAAS,aAAa,IAAG,EAAS,OAAO,WAAW,IACnE,EAAS,OAAO,WAAW;QAC3B;IAIL,IAAM,IAAS,SAAS;IACxB,CAAI,CAAC,KAAU,MAAW,SAAS,QAAQ,EAAS,OAAO,IAAI,SAAS,CAAM,MAC5E,EAAY,CAAM,CAAC,EAAE,MAAM;GAE/B;EACF;EAYA,SAAS,IAAgB;GACvB,EAAS,OAAO,KAAK,EAAY,CAAM,KAAK,KAAA,CAAS;EACvD;SAIA,EACE,SACM,EAAS,OAAO,SAAS,IAC/B,SACM,EAAS,OAAO,MAAM,CAC9B,GAOA,EAAa;GAEX,MAAM;GAEN,aAAa,EAAS,OAAO,MAAM;GAEnC,IAAI,QAAe,EAAS,OAAO,MAAM,IAAI;EAC/C,CAAC,+BAIC,EAAqD,EAAA,QAAA,WAAA,EAA/B,cAAe,EAAA,MAAY,CAAA,GACjD,EAWa,GAAA;GAVV,IAAI,EAAA,CAAA;YACD;GAAJ,KAAI;GACH,WAAW,EAAA;GACX,MAAM,EAAA;GACN,SAAS,EAAA;GACT,OAAO,EAAA;GACP,iBAAe,EAAA;GACP;;oBAED,CAAR,EAAQ,EAAA,QAAA,SAAA,CAAA,CAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
@layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
|
|
2
|
-
@layer vectis.components{.v-menu-group{gap:var(--vectis-space-1);flex-direction:column;display:flex}}
|
|
2
|
+
@layer vectis.components{.v-menu-group{gap:var(--vectis-space-1);flex-direction:column;display:flex}.v-menu-group-label{min-block-size:var(--control-height);padding-block:var(--vectis-space-1);padding-inline:var(--control-padding-inline);font-size:var(--vectis-text-overline-size);font-weight:var(--vectis-text-overline-weight);letter-spacing:var(--vectis-text-overline-tracking);color:var(--vectis-color-text-muted);align-items:center;display:flex}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VMenuGroup.js","names":[],"sources":["../../../src/components/VMenu/VMenuGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n
|
|
1
|
+
{"version":3,"file":"VMenuGroup.js","names":[],"sources":["../../../src/components/VMenu/VMenuGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A named section inside a menu: \"Sort by\", \"Recent files\". The name is announced as\n * the group's label rather than being read as one more item, and it is plain text: it\n * cannot be chosen, and the arrow keys walk straight past it.\n */\n\nimport { useId, useSlots, watchEffect } from 'vue'\n\nimport { isDev } from '../../utils/env'\n\ninterface MenuGroupProps {\n /**\n * The name of the section, replaced by the `#label` slot. One of the two is REQUIRED:\n * it is what names the group, and a section named by neither points its\n * `aria-labelledby` at an empty element. It is a heading, not a command: nothing\n * happens on click.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<MenuGroupProps>(), {\n label: undefined,\n})\n\ndefineSlots<{\n /** The commands belonging to this section. */\n default(): unknown\n /** A name made of markup, replacing the `label` prop. */\n label?(): unknown\n}>()\n\n// @a11y @ssr\n// The generated id ties the group to its own label, and matches between server and\n// client by construction, Vue's `useId` guaranteeing exactly that.\nconst labelId = useId()\n\n// @devwarn\n// A group named by neither points its `aria-labelledby` at an empty element, and a\n// screen reader announces an unnamed group with nothing on screen to say so.\nif (isDev) {\n const slots = useSlots()\n watchEffect(() => {\n if (!props.label && !slots.label)\n console.warn('[VMenuGroup] needs a name: give it a `label` or a `#label` slot.')\n })\n}\n</script>\n\n<template>\n <div role=\"group\" class=\"v-menu-group\" :aria-labelledby=\"labelId\">\n <span :id=\"labelId\" class=\"v-menu-group-label\"\n ><slot name=\"label\">{{ label }}</slot></span\n >\n <slot />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-menu-group {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n }\n\n .v-menu-group-label {\n display: flex;\n align-items: center;\n min-block-size: var(--control-height);\n padding-block: var(--vectis-space-1);\n padding-inline: var(--control-padding-inline);\n font-size: var(--vectis-text-overline-size);\n font-weight: var(--vectis-text-overline-weight);\n letter-spacing: var(--vectis-text-overline-tracking);\n color: var(--vectis-color-text-muted);\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDev as e } from "../../utils/env.js";
|
|
2
|
+
import { createElementBlock as t, createElementVNode as n, createTextVNode as r, defineComponent as i, openBlock as a, renderSlot as o, toDisplayString as s, unref as c, useId as l, useSlots as u, watchEffect as d } from "vue";
|
|
2
3
|
//#region src/components/VMenu/VMenuGroup.vue?vue&type=script&setup=true&lang.ts
|
|
3
|
-
var
|
|
4
|
+
var f = ["aria-labelledby"], p = ["id"], m = /*@__PURE__*/ i({
|
|
4
5
|
__name: "VMenuGroup",
|
|
5
6
|
props: { label: { default: void 0 } },
|
|
6
|
-
setup(
|
|
7
|
-
let
|
|
8
|
-
|
|
7
|
+
setup(i) {
|
|
8
|
+
let m = i, h = l();
|
|
9
|
+
if (e) {
|
|
10
|
+
let e = u();
|
|
11
|
+
d(() => {
|
|
12
|
+
!m.label && !e.label && console.warn("[VMenuGroup] needs a name: give it a `label` or a `#label` slot.");
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return (e, l) => (a(), t("div", {
|
|
9
16
|
role: "group",
|
|
10
17
|
class: "v-menu-group",
|
|
11
|
-
"aria-labelledby":
|
|
12
|
-
}, [
|
|
13
|
-
id:
|
|
14
|
-
class: "v-menu-group-label
|
|
15
|
-
}, [
|
|
18
|
+
"aria-labelledby": c(h)
|
|
19
|
+
}, [n("span", {
|
|
20
|
+
id: c(h),
|
|
21
|
+
class: "v-menu-group-label"
|
|
22
|
+
}, [o(e.$slots, "label", {}, () => [r(s(i.label), 1)])], 8, p), o(e.$slots, "default")], 8, f));
|
|
16
23
|
}
|
|
17
24
|
});
|
|
18
25
|
//#endregion
|
|
19
|
-
export {
|
|
26
|
+
export { m as default };
|
|
20
27
|
|
|
21
28
|
//# sourceMappingURL=VMenuGroup.vue_vue_type_script_setup_true_lang.js.map
|