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,65 +1,86 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { isDev as e } from "../utils/env.js";
|
|
2
|
+
import { screenFiles as t } from "../utils/file.js";
|
|
3
|
+
import { computed as n, getCurrentInstance as r, ref as i, watch as a, watchEffect as o } from "vue";
|
|
3
4
|
//#region src/composables/useFileField.ts
|
|
4
|
-
var
|
|
5
|
+
var s = [
|
|
5
6
|
"name",
|
|
6
7
|
"required",
|
|
7
8
|
"form",
|
|
8
9
|
"capture"
|
|
9
10
|
];
|
|
10
|
-
function
|
|
11
|
-
let { model:
|
|
12
|
-
let e =
|
|
13
|
-
return Object.fromEntries(Object.entries(
|
|
11
|
+
function c(c) {
|
|
12
|
+
let { model: l, props: u, emit: d } = c, f = i(null), p = () => c.disabled?.value ?? u.disabled, m = n(() => !p() && !u.readonly), h = n(() => {
|
|
13
|
+
let e = c.excludeFromControl ?? [];
|
|
14
|
+
return Object.fromEntries(Object.entries(c.forwardedAttrs.value).filter(([t]) => !s.includes(t) && !e.includes(t)));
|
|
14
15
|
});
|
|
15
|
-
function
|
|
16
|
-
|
|
16
|
+
function g() {
|
|
17
|
+
let e = f.value;
|
|
18
|
+
if (!e) return;
|
|
19
|
+
if (typeof DataTransfer > "u") {
|
|
20
|
+
e.value = "";
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let t = new DataTransfer();
|
|
24
|
+
for (let e of l.value) t.items.add(e);
|
|
25
|
+
e.files = t.files;
|
|
17
26
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
a(l, g, { flush: "post" });
|
|
28
|
+
function _(e) {
|
|
29
|
+
let n = u.multiple ? l.value : [], { accepted: r, rejected: i } = t(e, n, {
|
|
30
|
+
accept: u.accept,
|
|
31
|
+
maxSize: u.maxSize,
|
|
32
|
+
maxFiles: u.multiple ? u.maxFiles : 1,
|
|
33
|
+
maxTotalSize: u.maxTotalSize
|
|
24
34
|
});
|
|
25
|
-
for (let e of i)
|
|
26
|
-
|
|
35
|
+
for (let e of i) d("reject", e);
|
|
36
|
+
if (g(), r.length === 0) return;
|
|
37
|
+
let a = u.multiple ? void 0 : l.value[0];
|
|
38
|
+
l.value = [...n, ...r], a && a !== l.value[0] && d("remove", a, 0), d("change", l.value);
|
|
27
39
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
40
|
+
function v(e) {
|
|
41
|
+
_([...e.target.files ?? []]);
|
|
30
42
|
}
|
|
31
|
-
let
|
|
32
|
-
...Object.fromEntries(Object.entries(
|
|
43
|
+
let y = n(() => ({
|
|
44
|
+
...Object.fromEntries(Object.entries(c.forwardedAttrs.value).filter(([e]) => s.includes(e))),
|
|
33
45
|
type: "file",
|
|
34
46
|
tabindex: -1,
|
|
35
47
|
"aria-hidden": "true",
|
|
36
|
-
accept:
|
|
37
|
-
multiple:
|
|
38
|
-
disabled:
|
|
39
|
-
onChange:
|
|
48
|
+
accept: u.accept,
|
|
49
|
+
multiple: u.multiple || void 0,
|
|
50
|
+
disabled: p() || void 0,
|
|
51
|
+
onChange: v
|
|
40
52
|
}));
|
|
41
|
-
function
|
|
42
|
-
|
|
53
|
+
function b() {
|
|
54
|
+
m.value && f.value?.click();
|
|
43
55
|
}
|
|
44
|
-
function
|
|
45
|
-
let t =
|
|
46
|
-
if (!(!t || !
|
|
56
|
+
function x(e) {
|
|
57
|
+
let t = l.value[e];
|
|
58
|
+
if (!(!t || !m.value)) return l.value = l.value.filter((t, n) => n !== e), g(), d("remove", t, e), d("change", l.value), t;
|
|
59
|
+
}
|
|
60
|
+
function S() {
|
|
61
|
+
!m.value || l.value.length === 0 || (l.value = [], g(), c.onClear?.(), d("change", l.value));
|
|
47
62
|
}
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
if (e) {
|
|
64
|
+
let e = r()?.type.__name, t = /* @__PURE__ */ new Set(), n = (n) => {
|
|
65
|
+
t.has(n) || (t.add(n), console.warn(`[${e}] ${n}`));
|
|
66
|
+
};
|
|
67
|
+
o(() => {
|
|
68
|
+
u.maxFiles !== void 0 && !u.multiple && n("`maxFiles` ignored without `multiple`: single mode already caps at one file."), c.forwardedAttrs.value.required !== void 0 && n("`required` lands on the hidden file input: the browser does block an empty submission, but its message points at an input nobody can see. Validate the v-model yourself and use the `invalid` prop.");
|
|
69
|
+
for (let e of c.warnings?.() ?? []) e && n(e);
|
|
70
|
+
});
|
|
50
71
|
}
|
|
51
72
|
return {
|
|
52
|
-
fileEl:
|
|
53
|
-
enabled:
|
|
54
|
-
nativeInputAttrs:
|
|
55
|
-
controlAttrs:
|
|
56
|
-
acceptFiles:
|
|
57
|
-
openPicker:
|
|
58
|
-
removeAt:
|
|
59
|
-
clear:
|
|
73
|
+
fileEl: f,
|
|
74
|
+
enabled: m,
|
|
75
|
+
nativeInputAttrs: y,
|
|
76
|
+
controlAttrs: h,
|
|
77
|
+
acceptFiles: _,
|
|
78
|
+
openPicker: b,
|
|
79
|
+
removeAt: x,
|
|
80
|
+
clear: S
|
|
60
81
|
};
|
|
61
82
|
}
|
|
62
83
|
//#endregion
|
|
63
|
-
export {
|
|
84
|
+
export { c as useFileField };
|
|
64
85
|
|
|
65
86
|
//# sourceMappingURL=useFileField.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFileField.js","names":[],"sources":["../../src/composables/useFileField.ts"],"sourcesContent":["// @core\n/**\n * The hidden `<input type=\"file\">` gate of VFileInput and VFilePicker.\n *\n * The JS here is imposed by the platform, not chosen: a dialog opens ONLY from a real click\n * on a file input, and a `FileList` cannot be written from a template. So what the reader\n * clicks can never BE the file input, and the file input can only ever be a SOURCE of files.\n *\n * Hence `.click()` and not `showPicker()` — both need a transient user activation, but\n * `showPicker()` THROWS without one where `.click()` is merely inert — and hence\n * `resetNative()` on every path: without it, re-picking the SAME file after a clear fires no\n * `change` at all and that file becomes unreachable.\n *\n * Everything the two components did identically lives here, so the two cannot drift: the\n * attributes of the hidden input, the screening of what comes in, the removal of one file and\n * the emptying of all of them, and the development warnings both of them owe the integrator.\n */\nimport { computed, getCurrentInstance, ref, watchEffect, type ComputedRef, type Ref } from 'vue'\n\nimport { isDev } from '../utils/env'\nimport { screenFiles, type FileRejection } from '../utils/file'\n\n// @a11y\n/**\n * The attributes belonging on the hidden file input rather than on the visible control.\n *\n * The wrapper-root pattern assumes ONE functional element and there are two here, so the\n * attrs split in three: `class`/`style` on the root, these four on the input because they\n * are what the FORM reads, everything else on the element the READER focuses. The buckets\n * must not overlap — a duplicated `id` breaks a consumer's `<label for>`, a duplicated\n * `name` gets the field announced twice.\n */\nconst NATIVE_ONLY = ['name', 'required', 'form', 'capture']\n\n/** The props both components declare under the same names, read at the moment they matter. */\nexport interface FileFieldProps {\n multiple: boolean\n accept?: string\n maxSize?: number\n maxFiles?: number\n maxTotalSize?: number\n disabled: boolean\n readonly: boolean\n}\n\n/** The events both components declare under the same names and with the same payloads. */\nexport interface FileFieldEmit {\n (event: 'change', files: File[]): void\n (event: 'reject', rejection: FileRejection): void\n (event: 'remove', file: File, index: number): void\n}\n\nexport interface FileFieldOptions {\n /** The component's value — always a list of files, whether one or several are allowed. */\n model: Ref<File[]>\n props: FileFieldProps\n emit: FileFieldEmit\n /** The attributes the consumer wrote, minus styling, still to be split in two. */\n forwardedAttrs: ComputedRef<Record<string, unknown>>\n /**\n * Whether the component is disabled, the row it sits in taken into account. Left out, the\n * `disabled` prop answers alone.\n */\n disabled?: Ref<boolean>\n /**\n * Attributes to withhold from the visible control too. VFileInput withholds\n * `aria-describedby`, which it re-aggregates itself: a binding written after the spread\n * would replace the consumer's rather than merge with it.\n */\n excludeFromControl?: readonly string[]\n /** Called once the selection has been emptied by `clear`, before `change` is emitted. */\n onClear?: () => void\n /**\n * The component's own development warnings, one message per condition that holds. They\n * follow the same rule as the shared ones: each is given once per instance.\n */\n warnings?: () => (string | false)[]\n}\n\nexport function useFileField(options: FileFieldOptions) {\n const { model, props, emit } = options\n\n const fileEl = ref<HTMLInputElement | null>(null)\n\n const disabled = () => options.disabled?.value ?? props.disabled\n\n /** Whether files may come in or go out — no dialog, no drop and no removal otherwise. */\n const enabled = computed(() => !disabled() && !props.readonly)\n\n const controlAttrs = computed(() => {\n const excluded = options.excludeFromControl ?? []\n return Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(\n ([key]) => !NATIVE_ONLY.includes(key) && !excluded.includes(key),\n ),\n )\n })\n\n /**\n * TRAP — picking the SAME file again after a clear or a removal fires no `change` at all:\n * as far as the input is concerned its value never changed, and that file becomes\n * unreachable with nothing to show for it. Emptying it on every path is what keeps it\n * selectable.\n */\n function resetNative() {\n if (fileEl.value) fileEl.value.value = ''\n }\n\n /**\n * The single entry into the model: the dialog and a drop both arrive here.\n *\n * A refused file never enters the model at all, and every refusal is reported so the\n * component can say why. The screening is a pure function in `utils/file`, which is what\n * makes the ORDER of the reasons testable without a mount.\n */\n function acceptFiles(incoming: File[]) {\n const current = props.multiple ? model.value : []\n const { accepted, rejected } = screenFiles(incoming, current, {\n accept: props.accept,\n maxSize: props.maxSize,\n // A single-file field is a list capped at one, so extra files are refused for the\n // same reason as anywhere else: too many.\n maxFiles: props.multiple ? props.maxFiles : 1,\n maxTotalSize: props.maxTotalSize,\n })\n\n for (const rejection of rejected) emit('reject', rejection)\n\n resetNative()\n if (accepted.length === 0) return\n\n model.value = [...current, ...accepted]\n emit('change', model.value)\n }\n\n function onNativeChange(event: Event) {\n acceptFiles([...((event.target as HTMLInputElement).files ?? [])])\n }\n\n /**\n * Everything the hidden input is bound with, the consumer's form attributes included. It\n * is taken out of the tab order and hidden from screen readers, which leaves the visible\n * control as the single stop and the single announcement; it stays disabled only when the\n * component is — a read-only one still submits what it holds.\n */\n const nativeInputAttrs = computed(() => ({\n ...Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(([key]) => NATIVE_ONLY.includes(key)),\n ),\n type: 'file',\n tabindex: -1,\n 'aria-hidden': 'true' as const,\n accept: props.accept,\n multiple: props.multiple || undefined,\n disabled: disabled() || undefined,\n onChange: onNativeChange,\n }))\n\n // @fallback\n /**\n * `.click()` and not `showPicker()`. Both need a transient user activation, but\n * `showPicker()` THROWS without one — and inside a cross-origin iframe — where a click is\n * simply inert. On a file input there is nothing to gain in exchange.\n */\n function openPicker() {\n if (!enabled.value) return\n fileEl.value?.click()\n }\n\n /**\n * Takes ONE file out of the selection, and says which: `remove` with the file and the\n * position it held, then `change` with what is left. It returns the file removed, or\n * `undefined` when nothing was — so the component knows whether it has a focus to catch.\n */\n function removeAt(index: number): File | undefined {\n const file = model.value[index]\n if (!file || !enabled.value) return undefined\n\n model.value = model.value.filter((_, i) => i !== index)\n resetNative()\n emit('remove', file, index)\n emit('change', model.value)\n return file\n }\n\n /** Empties the selection, and the hidden input with it. */\n function clear() {\n model.value = []\n resetNative()\n options.onClear?.()\n emit('change', model.value)\n }\n\n // @devwarn\n /*\n * Both shared guards describe something that fails SILENTLY at runtime. Each message is given\n * once per instance: they sit in an effect, which re-runs on every change of what it reads,\n * and the same sentence repeated at every keystroke is how a warning stops being read.\n */\n if (isDev) {\n // The prefix is the component's own name, read off the instance rather than passed in:\n // this whole block is dropped from a production build, and the name with it.\n const name = getCurrentInstance()?.type.__name\n const warned = new Set<string>()\n const warn = (message: string) => {\n if (warned.has(message)) return\n warned.add(message)\n console.warn(`[${name}] ${message}`)\n }\n watchEffect(() => {\n if (props.maxFiles !== undefined && !props.multiple)\n warn('`maxFiles` ignored without `multiple`: single mode already caps at one file.')\n if (options.forwardedAttrs.value.required !== undefined)\n warn(\n '`required` lands on the hidden file input, which is not focusable: the browser blocks submission with no visible message. Validate the v-model yourself and use the `invalid` prop.',\n )\n for (const message of options.warnings?.() ?? []) if (message) warn(message)\n })\n }\n\n return {\n fileEl,\n enabled,\n nativeInputAttrs,\n controlAttrs,\n acceptFiles,\n openPicker,\n removeAt,\n clear,\n }\n}\n"],"mappings":";;;AAgCA,IAAM,IAAc;CAAC;CAAQ;CAAY;CAAQ;AAAS;AA+C1D,SAAgB,EAAa,GAA2B;CACtD,IAAM,EAAE,UAAO,UAAO,YAAS,GAEzB,IAAS,EAA6B,IAAI,GAE1C,UAAiB,EAAQ,UAAU,SAAS,EAAM,UAGlD,IAAU,QAAe,CAAC,EAAS,KAAK,CAAC,EAAM,QAAQ,GAEvD,IAAe,QAAe;EAClC,IAAM,IAAW,EAAQ,sBAAsB,CAAC;EAChD,OAAO,OAAO,YACZ,OAAO,QAAQ,EAAQ,eAAe,KAAK,CAAC,CAAC,QAC1C,CAAC,OAAS,CAAC,EAAY,SAAS,CAAG,KAAK,CAAC,EAAS,SAAS,CAAG,CACjE,CACF;CACF,CAAC;CAQD,SAAS,IAAc;EACrB,AAAI,EAAO,UAAO,EAAO,MAAM,QAAQ;CACzC;CASA,SAAS,EAAY,GAAkB;EACrC,IAAM,IAAU,EAAM,WAAW,EAAM,QAAQ,CAAC,GAC1C,EAAE,aAAU,gBAAa,EAAY,GAAU,GAAS;GAC5D,QAAQ,EAAM;GACd,SAAS,EAAM;GAGf,UAAU,EAAM,WAAW,EAAM,WAAW;GAC5C,cAAc,EAAM;EACtB,CAAC;EAED,KAAK,IAAM,KAAa,GAAU,EAAK,UAAU,CAAS;EAE1D,EAAY,GACR,EAAS,WAAW,MAExB,EAAM,QAAQ,CAAC,GAAG,GAAS,GAAG,CAAQ,GACtC,EAAK,UAAU,EAAM,KAAK;CAC5B;CAEA,SAAS,EAAe,GAAc;EACpC,EAAY,CAAC,GAAK,EAAM,OAA4B,SAAS,CAAC,CAAE,CAAC;CACnE;CAQA,IAAM,IAAmB,SAAgB;EACvC,GAAG,OAAO,YACR,OAAO,QAAQ,EAAQ,eAAe,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAS,EAAY,SAAS,CAAG,CAAC,CAC1F;EACA,MAAM;EACN,UAAU;EACV,eAAe;EACf,QAAQ,EAAM;EACd,UAAU,EAAM,YAAY,KAAA;EAC5B,UAAU,EAAS,KAAK,KAAA;EACxB,UAAU;CACZ,EAAE;CAQF,SAAS,IAAa;EACf,EAAQ,SACb,EAAO,OAAO,MAAM;CACtB;CAOA,SAAS,EAAS,GAAiC;EACjD,IAAM,IAAO,EAAM,MAAM;EACrB,OAAC,KAAQ,CAAC,EAAQ,QAMtB,OAJA,EAAM,QAAQ,EAAM,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAK,GACtD,EAAY,GACZ,EAAK,UAAU,GAAM,CAAK,GAC1B,EAAK,UAAU,EAAM,KAAK,GACnB;CACT;CAGA,SAAS,IAAQ;EAIf,AAHA,EAAM,QAAQ,CAAC,GACf,EAAY,GACZ,EAAQ,UAAU,GAClB,EAAK,UAAU,EAAM,KAAK;CAC5B;CA6BA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"useFileField.js","names":[],"sources":["../../src/composables/useFileField.ts"],"sourcesContent":["// @core\n/**\n * The hidden `<input type=\"file\">` gate of VFileInput and VFilePicker.\n *\n * The JS here is imposed by the platform, not chosen: a dialog opens ONLY from a real click\n * on a file input, and a `FileList` cannot be written from a template. So what the reader\n * clicks can never BE the file input, and the file input can only ever be a SOURCE of files.\n *\n * Hence `.click()` and not `showPicker()` — both need a transient user activation, but\n * `showPicker()` THROWS without one where `.click()` is merely inert — and hence\n * `syncNative()` on every path, which writes the selection back into the input so the form\n * submits it, and so re-picking a file that left the selection is a change again.\n *\n * Everything the two components did identically lives here, so the two cannot drift: the\n * attributes of the hidden input, the screening of what comes in, the removal of one file and\n * the emptying of all of them, and the development warnings both of them owe the integrator.\n */\nimport {\n computed,\n getCurrentInstance,\n ref,\n watch,\n watchEffect,\n type ComputedRef,\n type Ref,\n} from 'vue'\n\nimport { isDev } from '../utils/env'\nimport { screenFiles, type FileRejection } from '../utils/file'\n\n// @a11y\n/**\n * The attributes belonging on the hidden file input rather than on the visible control.\n *\n * The wrapper-root pattern assumes ONE functional element and there are two here, so the\n * attrs split in three: `class`/`style` on the root, these four on the input because they\n * are what the FORM reads, everything else on the element the READER focuses. The buckets\n * must not overlap — a duplicated `id` breaks a consumer's `<label for>`, a duplicated\n * `name` gets the field announced twice.\n */\nconst NATIVE_ONLY = ['name', 'required', 'form', 'capture']\n\n/** The props both components declare under the same names, read at the moment they matter. */\nexport interface FileFieldProps {\n multiple: boolean\n accept?: string\n maxSize?: number\n maxFiles?: number\n maxTotalSize?: number\n disabled: boolean\n readonly: boolean\n}\n\n/** The events both components declare under the same names and with the same payloads. */\nexport interface FileFieldEmit {\n (event: 'change', files: File[]): void\n (event: 'reject', rejection: FileRejection): void\n (event: 'remove', file: File, index: number): void\n}\n\nexport interface FileFieldOptions {\n /** The component's value: always a list of files, whether one or several are allowed. */\n model: Ref<File[]>\n props: FileFieldProps\n emit: FileFieldEmit\n /** The attributes the consumer wrote, minus styling, still to be split in two. */\n forwardedAttrs: ComputedRef<Record<string, unknown>>\n /**\n * Whether the component is disabled, the row it sits in taken into account. Left out, the\n * `disabled` prop answers alone.\n */\n disabled?: Ref<boolean>\n /**\n * Attributes to withhold from the visible control too. VFileInput withholds\n * `aria-describedby`, which it re-aggregates itself: a binding written after the spread\n * would replace the consumer's rather than merge with it.\n */\n excludeFromControl?: readonly string[]\n /** Called once the selection has been emptied by `clear`, before `change` is emitted. */\n onClear?: () => void\n /**\n * The component's own development warnings, one message per condition that holds. They\n * follow the same rule as the shared ones: each is given once per instance.\n */\n warnings?: () => (string | false)[]\n}\n\nexport function useFileField(options: FileFieldOptions) {\n const { model, props, emit } = options\n\n const fileEl = ref<HTMLInputElement | null>(null)\n\n const disabled = () => options.disabled?.value ?? props.disabled\n\n /** Whether files may come in or go out — no dialog, no drop and no removal otherwise. */\n const enabled = computed(() => !disabled() && !props.readonly)\n\n const controlAttrs = computed(() => {\n const excluded = options.excludeFromControl ?? []\n return Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(\n ([key]) => !NATIVE_ONLY.includes(key) && !excluded.includes(key),\n ),\n )\n })\n\n // @core\n /**\n * Writes the selection back into the hidden input, which is what the FORM reads: `name`,\n * `form` and `required` land there, and an input left holding the last batch the dialog\n * produced (or emptied) would submit that rather than the selection, or nothing at all.\n * A `FileList` cannot be built, but a `DataTransfer` hands one out.\n *\n * TRAP — it also keeps every file that LEFT the selection pickable again. The input holds\n * exactly the selection, so choosing a file that was cleared, removed or refused differs\n * from what it holds and fires `change`; left holding it, the dialog would answer the\n * same file with no event at all.\n */\n function syncNative() {\n const el = fileEl.value\n if (!el) return\n // @fallback\n // An environment without `DataTransfer` (jsdom) can only empty the input,\n // which keeps a file pickable again and leaves the form without it.\n if (typeof DataTransfer === 'undefined') {\n el.value = ''\n return\n }\n const transfer = new DataTransfer()\n for (const file of model.value) transfer.items.add(file)\n el.files = transfer.files\n }\n\n // A value set from outside, a consumer emptying the list after an upload, reaches the\n // input the same way.\n watch(model, syncNative, { flush: 'post' })\n\n /**\n * The single entry into the model: the dialog and a drop both arrive here.\n *\n * A refused file never enters the model at all, and every refusal is reported so the\n * component can say why. The screening is a pure function in `utils/file`, which is what\n * makes the ORDER of the reasons testable without a mount.\n */\n function acceptFiles(incoming: File[]) {\n const current = props.multiple ? model.value : []\n const { accepted, rejected } = screenFiles(incoming, current, {\n accept: props.accept,\n maxSize: props.maxSize,\n // A single-file field is a list capped at one, so extra files are refused for the\n // same reason as anywhere else: too many.\n maxFiles: props.multiple ? props.maxFiles : 1,\n maxTotalSize: props.maxTotalSize,\n })\n\n for (const rejection of rejected) emit('reject', rejection)\n\n // The dialog has just written ITS batch into the input, whatever happens to it here.\n syncNative()\n if (accepted.length === 0) return\n\n // A single file replaced by another has left the selection as surely as one taken out\n // by its cross, and `remove` is how an upload already under way for it is cancelled.\n const replaced = props.multiple ? undefined : model.value[0]\n model.value = [...current, ...accepted]\n if (replaced && replaced !== model.value[0]) emit('remove', replaced, 0)\n emit('change', model.value)\n }\n\n function onNativeChange(event: Event) {\n acceptFiles([...((event.target as HTMLInputElement).files ?? [])])\n }\n\n /**\n * Everything the hidden input is bound with, the consumer's form attributes included. It\n * is taken out of the tab order and hidden from screen readers, which leaves the visible\n * control as the single stop and the single announcement; it stays disabled only when the\n * component is, since a read-only one still submits what it holds (`syncNative`).\n */\n const nativeInputAttrs = computed(() => ({\n ...Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(([key]) => NATIVE_ONLY.includes(key)),\n ),\n type: 'file',\n tabindex: -1,\n 'aria-hidden': 'true' as const,\n accept: props.accept,\n multiple: props.multiple || undefined,\n disabled: disabled() || undefined,\n onChange: onNativeChange,\n }))\n\n // @fallback\n /**\n * `.click()` and not `showPicker()`. Both need a transient user activation, but\n * `showPicker()` THROWS without one — and inside a cross-origin iframe — where a click is\n * simply inert. On a file input there is nothing to gain in exchange.\n */\n function openPicker() {\n if (!enabled.value) return\n fileEl.value?.click()\n }\n\n /**\n * Takes ONE file out of the selection, and says which: `remove` with the file and the\n * position it held, then `change` with what is left. It returns the file removed, or\n * `undefined` when nothing was — so the component knows whether it has a focus to catch.\n */\n function removeAt(index: number): File | undefined {\n const file = model.value[index]\n if (!file || !enabled.value) return undefined\n\n model.value = model.value.filter((_, i) => i !== index)\n syncNative()\n emit('remove', file, index)\n emit('change', model.value)\n return file\n }\n\n /**\n * Empties the selection, and the hidden input with it. Refused where `removeAt` is, and\n * silent when there is nothing to empty: no `change` for a selection that did not change.\n */\n function clear() {\n if (!enabled.value || model.value.length === 0) return\n model.value = []\n syncNative()\n options.onClear?.()\n emit('change', model.value)\n }\n\n // @devwarn\n /*\n * Both shared guards describe something that fails SILENTLY at runtime. Each message is given\n * once per instance: they sit in an effect, which re-runs on every change of what it reads,\n * and the same sentence repeated at every keystroke is how a warning stops being read.\n */\n if (isDev) {\n // The prefix is the component's own name, read off the instance rather than passed in:\n // this whole block is dropped from a production build, and the name with it.\n const name = getCurrentInstance()?.type.__name\n const warned = new Set<string>()\n const warn = (message: string) => {\n if (warned.has(message)) return\n warned.add(message)\n console.warn(`[${name}] ${message}`)\n }\n watchEffect(() => {\n if (props.maxFiles !== undefined && !props.multiple)\n warn('`maxFiles` ignored without `multiple`: single mode already caps at one file.')\n if (options.forwardedAttrs.value.required !== undefined)\n warn(\n '`required` lands on the hidden file input: the browser does block an empty submission, but its message points at an input nobody can see. Validate the v-model yourself and use the `invalid` prop.',\n )\n for (const message of options.warnings?.() ?? []) if (message) warn(message)\n })\n }\n\n return {\n fileEl,\n enabled,\n nativeInputAttrs,\n controlAttrs,\n acceptFiles,\n openPicker,\n removeAt,\n clear,\n }\n}\n"],"mappings":"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"}
|
|
@@ -19,13 +19,13 @@ export declare function iconClickHandlers(): {
|
|
|
19
19
|
end: boolean;
|
|
20
20
|
};
|
|
21
21
|
/**
|
|
22
|
-
* The start-icon listener a field COMPOSED on top of VInput
|
|
23
|
-
* VTimeInput, VFileInput
|
|
22
|
+
* The start-icon listener a field COMPOSED on top of VInput: VCombobox, VDateInput,
|
|
23
|
+
* VTimeInput, VFileInput, binds onto that inner VInput, ready to spread beside the
|
|
24
24
|
* consumer's attributes. Call it during setup: it reads the component's own vnode.
|
|
25
25
|
*
|
|
26
26
|
* Each of those fields declares `click:icon-start` as its own event, which puts it in the API
|
|
27
27
|
* tables and takes it out of `$attrs`, hence out of what is forwarded: the listener has to be
|
|
28
|
-
* bound by hand.
|
|
28
|
+
* bound by hand. It has to follow what the consumer actually wrote: bound unconditionally,
|
|
29
29
|
* VInput would read a handler on every instance and turn a decorative icon into a focusable
|
|
30
30
|
* button nobody asked for. So it is `undefined` when there is nothing to relay.
|
|
31
31
|
*/
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDev as e } from "../utils/env.js";
|
|
2
|
+
import { getCurrentInstance as t } from "vue";
|
|
2
3
|
//#region src/composables/useIconClickHandlers.ts
|
|
3
|
-
function
|
|
4
|
-
let
|
|
4
|
+
function n() {
|
|
5
|
+
let e = t()?.vnode.props ?? {};
|
|
5
6
|
return {
|
|
6
|
-
start: "onClick:iconStart" in
|
|
7
|
-
end: "onClick:iconEnd" in
|
|
7
|
+
start: "onClick:iconStart" in e || "onClick:icon-start" in e,
|
|
8
|
+
end: "onClick:iconEnd" in e || "onClick:icon-end" in e
|
|
8
9
|
};
|
|
9
10
|
}
|
|
10
|
-
function n(e) {
|
|
11
|
-
return t().start ? { "onClick:icon-start": e } : void 0;
|
|
12
|
-
}
|
|
13
11
|
function r(e) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
return n().start ? { "onClick:icon-start": e } : void 0;
|
|
13
|
+
}
|
|
14
|
+
function i(t) {
|
|
15
|
+
let { start: r, end: i } = n();
|
|
16
|
+
return e && (r && !t.iconStartLabel && console.warn(`[${t.name}] clickable start icon without iconStartLabel — provide an accessible label.`), i && !t.iconEndLabel && console.warn(`[${t.name}] clickable end icon without iconEndLabel — provide an accessible label.`)), {
|
|
17
|
+
hasIconStartHandler: r,
|
|
18
|
+
hasIconEndHandler: i
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
//#endregion
|
|
21
|
-
export {
|
|
22
|
+
export { n as iconClickHandlers, r as iconStartListener, i as useIconClickHandlers };
|
|
22
23
|
|
|
23
24
|
//# sourceMappingURL=useIconClickHandlers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useIconClickHandlers.js","names":[],"sources":["../../src/composables/useIconClickHandlers.ts"],"sourcesContent":["// @core\n/**\n * Whether VInput/VTextarea's icons should render as real buttons: an icon is decoration\n * until someone listens for `@click:icon-*`, at which point it needs a tab stop and a name.\n *\n * Declared emits are removed from `$attrs`, so the only place left to look is\n * `vnode.props` — in BOTH spellings, a template writing them with dashes and a render\n * function in camel case. Read ONCE and never revised: a listener attached later is not\n * picked up, which is marginal enough to accept.\n *\n * The missing-name warning lives here rather than in each field: a button nothing can\n * announce is a flaw of the arrangement, not of the component using it.\n */\n\nimport { getCurrentInstance } from 'vue'\n\nimport { isDev } from '../utils/env'\n\n/**\n * Whether the component being set up was handed a `@click:icon-start` / `@click:icon-end`\n * listener, in either spelling.\n */\nexport function iconClickHandlers(): { start: boolean; end: boolean } {\n const vnodeProps = getCurrentInstance()?.vnode.props ?? {}\n return {\n start: 'onClick:iconStart' in vnodeProps || 'onClick:icon-start' in vnodeProps,\n end: 'onClick:iconEnd' in vnodeProps || 'onClick:icon-end' in vnodeProps,\n }\n}\n\n/**\n * The start-icon listener a field COMPOSED on top of VInput
|
|
1
|
+
{"version":3,"file":"useIconClickHandlers.js","names":[],"sources":["../../src/composables/useIconClickHandlers.ts"],"sourcesContent":["// @core\n/**\n * Whether VInput/VTextarea's icons should render as real buttons: an icon is decoration\n * until someone listens for `@click:icon-*`, at which point it needs a tab stop and a name.\n *\n * Declared emits are removed from `$attrs`, so the only place left to look is\n * `vnode.props` — in BOTH spellings, a template writing them with dashes and a render\n * function in camel case. Read ONCE and never revised: a listener attached later is not\n * picked up, which is marginal enough to accept.\n *\n * The missing-name warning lives here rather than in each field: a button nothing can\n * announce is a flaw of the arrangement, not of the component using it.\n */\n\nimport { getCurrentInstance } from 'vue'\n\nimport { isDev } from '../utils/env'\n\n/**\n * Whether the component being set up was handed a `@click:icon-start` / `@click:icon-end`\n * listener, in either spelling.\n */\nexport function iconClickHandlers(): { start: boolean; end: boolean } {\n const vnodeProps = getCurrentInstance()?.vnode.props ?? {}\n return {\n start: 'onClick:iconStart' in vnodeProps || 'onClick:icon-start' in vnodeProps,\n end: 'onClick:iconEnd' in vnodeProps || 'onClick:icon-end' in vnodeProps,\n }\n}\n\n/**\n * The start-icon listener a field COMPOSED on top of VInput: VCombobox, VDateInput,\n * VTimeInput, VFileInput, binds onto that inner VInput, ready to spread beside the\n * consumer's attributes. Call it during setup: it reads the component's own vnode.\n *\n * Each of those fields declares `click:icon-start` as its own event, which puts it in the API\n * tables and takes it out of `$attrs`, hence out of what is forwarded: the listener has to be\n * bound by hand. It has to follow what the consumer actually wrote: bound unconditionally,\n * VInput would read a handler on every instance and turn a decorative icon into a focusable\n * button nobody asked for. So it is `undefined` when there is nothing to relay.\n */\nexport function iconStartListener(\n relay: (event: MouseEvent) => void,\n): { 'onClick:icon-start': (event: MouseEvent) => void } | undefined {\n return iconClickHandlers().start ? { 'onClick:icon-start': relay } : undefined\n}\n\nexport function useIconClickHandlers(options: {\n name: string\n iconStartLabel?: string\n iconEndLabel?: string\n}): { hasIconStartHandler: boolean; hasIconEndHandler: boolean } {\n const { start: hasIconStartHandler, end: hasIconEndHandler } = iconClickHandlers()\n\n // @a11y @devwarn\n if (isDev) {\n if (hasIconStartHandler && !options.iconStartLabel)\n console.warn(\n `[${options.name}] clickable start icon without iconStartLabel — provide an accessible label.`,\n )\n if (hasIconEndHandler && !options.iconEndLabel)\n console.warn(\n `[${options.name}] clickable end icon without iconEndLabel — provide an accessible label.`,\n )\n }\n\n return { hasIconStartHandler, hasIconEndHandler }\n}\n"],"mappings":";;;AAsBA,SAAgB,IAAsD;CACpE,IAAM,IAAa,EAAmB,CAAC,EAAE,MAAM,SAAS,CAAC;CACzD,OAAO;EACL,OAAO,uBAAuB,KAAc,wBAAwB;EACpE,KAAK,qBAAqB,KAAc,sBAAsB;CAChE;AACF;AAaA,SAAgB,EACd,GACmE;CACnE,OAAO,EAAkB,CAAC,CAAC,QAAQ,EAAE,sBAAsB,EAAM,IAAI,KAAA;AACvE;AAEA,SAAgB,EAAqB,GAI4B;CAC/D,IAAM,EAAE,OAAO,GAAqB,KAAK,MAAsB,EAAkB;CAcjF,OAXI,MACE,KAAuB,CAAC,EAAQ,kBAClC,QAAQ,KACN,IAAI,EAAQ,KAAK,6EACnB,GACE,KAAqB,CAAC,EAAQ,gBAChC,QAAQ,KACN,IAAI,EAAQ,KAAK,yEACnB,IAGG;EAAE;EAAqB;CAAkB;AAClD"}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { computed as e } from "vue";
|
|
2
2
|
//#region src/composables/useInertLink.ts
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
var t = /^onClick(?:Once|Capture|Passive)*$/;
|
|
4
|
+
function n(n) {
|
|
5
|
+
let r = e(() => n.href() !== void 0), i = e(() => r.value && n.inert());
|
|
5
6
|
return {
|
|
6
|
-
isLink:
|
|
7
|
-
isInertLink:
|
|
8
|
-
linkHref: e(() =>
|
|
7
|
+
isLink: r,
|
|
8
|
+
isInertLink: i,
|
|
9
|
+
linkHref: e(() => i.value ? void 0 : n.href()),
|
|
9
10
|
attrs: e(() => {
|
|
10
|
-
let e =
|
|
11
|
-
if (!
|
|
12
|
-
let
|
|
11
|
+
let e = n.attrs();
|
|
12
|
+
if (!i.value) return e;
|
|
13
|
+
let r = {
|
|
13
14
|
role: "link",
|
|
14
15
|
...e
|
|
15
16
|
};
|
|
16
|
-
|
|
17
|
+
for (let e of Object.keys(r)) t.test(e) && delete r[e];
|
|
18
|
+
return r;
|
|
17
19
|
})
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
//#endregion
|
|
21
|
-
export {
|
|
23
|
+
export { n as useInertLink };
|
|
22
24
|
|
|
23
25
|
//# sourceMappingURL=useInertLink.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useInertLink.js","names":[],"sources":["../../src/composables/useInertLink.ts"],"sourcesContent":["// @a11y\n/**\n * The inert link of every component that may render as an `<a>`: VButton, VChip and VAvatar.\n *\n * A link has no `disabled` attribute, so the complete inertness a `<button disabled>` gets for\n * free is rebuilt by hand, in parts that only work together. The address is dropped, so the\n * link can be neither focused nor followed; `aria-disabled` tells assistive technology why; and\n * the consumer's
|
|
1
|
+
{"version":3,"file":"useInertLink.js","names":[],"sources":["../../src/composables/useInertLink.ts"],"sourcesContent":["// @a11y\n/**\n * The inert link of every component that may render as an `<a>`: VButton, VChip and VAvatar.\n *\n * A link has no `disabled` attribute, so the complete inertness a `<button disabled>` gets for\n * free is rebuilt by hand, in parts that only work together. The address is dropped, so the\n * link can be neither focused nor followed; `aria-disabled` tells assistive technology why; and\n * the consumer's click listeners are filtered out of the attributes, since an `<a>` without an\n * `href` still dispatches clicks and a handler would otherwise run on a control announced as\n * disabled. TRAP — a listener written with a modifier is compiled to a key of its own\n * (`@click.capture` → `onClickCapture`, `.once` → `onClickOnce`), so the filter is a pattern\n * and not the single `onClick` key, which let those handlers run on a disabled link.\n *\n * TRAP — the attributes also gain `role=\"link\"`. An `<a>` without `href` is no longer a link\n * but a generic element, on which an `aria-label` is PROHIBITED (axe `aria-prohibited-attr`)\n * and `aria-disabled` describes nothing: an icon-only VIconButton turned inert would lose its\n * name. The role is spread BEFORE the consumer's attributes, so a role of their own still wins —\n * which is also why it travels in the attributes and is never bound after them, where an\n * `undefined` would erase the `role=\"tab\"` a VTab hands its button.\n *\n * Each of the three decides for itself WHEN it is a link and WHEN it is inert (VChip gives\n * `selectable` precedence over `href`, VButton counts `loading` as inert), which is why both\n * arrive as getters; the consequences are the same everywhere and live here.\n */\nimport { computed } from 'vue'\nimport type { ComputedRef } from 'vue'\n\nexport interface InertLinkOptions {\n /** The address the component would link to, or `undefined` when it is not a link. */\n href: () => string | undefined\n /** Whether the component is unusable right now. */\n inert: () => boolean\n /** The attributes the component forwards to the element that acts. */\n attrs: () => Record<string, unknown>\n}\n\nexport interface InertLink {\n /** Whether the component renders an `<a>`. */\n isLink: ComputedRef<boolean>\n /** Whether that `<a>` is inert, which is what `aria-disabled` is bound to. */\n isInertLink: ComputedRef<boolean>\n /** The `href` to bind: the address, or nothing on an inert link. */\n linkHref: ComputedRef<string | undefined>\n /** The attributes to bind, without the click handler on an inert link. */\n attrs: ComputedRef<Record<string, unknown>>\n}\n\n/** `onClick` and the keys Vue compiles its event modifiers to, in any combination. */\nconst CLICK_LISTENER = /^onClick(?:Once|Capture|Passive)*$/\n\nexport function useInertLink(options: InertLinkOptions): InertLink {\n const isLink = computed(() => options.href() !== undefined)\n const isInertLink = computed(() => isLink.value && options.inert())\n return {\n isLink,\n isInertLink,\n linkHref: computed(() => (isInertLink.value ? undefined : options.href())),\n attrs: computed(() => {\n const attrs = options.attrs()\n if (!isInertLink.value) return attrs\n const rest: Record<string, unknown> = { role: 'link', ...attrs }\n for (const key of Object.keys(rest)) if (CLICK_LISTENER.test(key)) delete rest[key]\n return rest\n }),\n }\n}\n"],"mappings":";;AAgDA,IAAM,IAAiB;AAEvB,SAAgB,EAAa,GAAsC;CACjE,IAAM,IAAS,QAAe,EAAQ,KAAK,MAAM,KAAA,CAAS,GACpD,IAAc,QAAe,EAAO,SAAS,EAAQ,MAAM,CAAC;CAClE,OAAO;EACL;EACA;EACA,UAAU,QAAgB,EAAY,QAAQ,KAAA,IAAY,EAAQ,KAAK,CAAE;EACzE,OAAO,QAAe;GACpB,IAAM,IAAQ,EAAQ,MAAM;GAC5B,IAAI,CAAC,EAAY,OAAO,OAAO;GAC/B,IAAM,IAAgC;IAAE,MAAM;IAAQ,GAAG;GAAM;GAC/D,KAAK,IAAM,KAAO,OAAO,KAAK,CAAI,GAAG,AAAI,EAAe,KAAK,CAAG,KAAG,OAAO,EAAK;GAC/E,OAAO;EACT,CAAC;CACH;AACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { nextTick as e, ref as t } from "vue";
|
|
2
|
+
//#region src/composables/useLiveAnnouncer.ts
|
|
3
|
+
function n() {
|
|
4
|
+
let n = t(""), r = t(""), i = {
|
|
5
|
+
polite: [],
|
|
6
|
+
assertive: []
|
|
7
|
+
};
|
|
8
|
+
function a(t, a) {
|
|
9
|
+
let o = a ? r : n, s = a ? i.assertive : i.polite;
|
|
10
|
+
s.push(t), !(s.length > 1) && (o.value = "", e(() => {
|
|
11
|
+
o.value = s.join("\n"), s.length = 0;
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
polite: n,
|
|
16
|
+
assertive: r,
|
|
17
|
+
announce: a
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { n as useLiveAnnouncer };
|
|
22
|
+
|
|
23
|
+
//# sourceMappingURL=useLiveAnnouncer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLiveAnnouncer.js","names":[],"sources":["../../src/composables/useLiveAnnouncer.ts"],"sourcesContent":["// @a11y\n/**\n * Two live regions that are ALREADY on the page, and a way to say something through them.\n *\n * A live region inserted at the same time as its text is not reliably announced: screen\n * readers watch regions for CHANGES, and a region that did not exist a moment ago has no\n * before to change from. A notification card or a snackbar bar is exactly that, created\n * with its message, so the message was announced sometimes and dropped at others. The\n * components render the two regions permanently (a polite `status`, an assertive `alert`),\n * visually hidden and outside their popover containers, and hand each message to them.\n *\n * The text is cleared first and written on the next tick, so the same message raised twice\n * in a row is a change the second time too.\n *\n * TRAP: messages said in the SAME tick are written together, one per line. Each used to\n * schedule its own write into the same region, and only the last ever reached the page: two\n * notifications raised by one handler were announced as one.\n *\n * Consumed by VToaster, VSnackbar and VCalendar.\n */\nimport { nextTick, ref } from 'vue'\n\nexport function useLiveAnnouncer() {\n const polite = ref('')\n const assertive = ref('')\n const pending = { polite: [] as string[], assertive: [] as string[] }\n\n /** Says `text`, interrupting what is being read when `urgent`, waiting for a pause otherwise. */\n function announce(text: string, urgent: boolean) {\n const region = urgent ? assertive : polite\n const queue = urgent ? pending.assertive : pending.polite\n queue.push(text)\n // A write is already on its way this tick: this message joins it.\n if (queue.length > 1) return\n region.value = ''\n void nextTick(() => {\n region.value = queue.join('\\n')\n queue.length = 0\n })\n }\n\n return { polite, assertive, announce }\n}\n"],"mappings":";;AAsBA,SAAgB,IAAmB;CACjC,IAAM,IAAS,EAAI,EAAE,GACf,IAAY,EAAI,EAAE,GAClB,IAAU;EAAE,QAAQ,CAAC;EAAe,WAAW,CAAC;CAAc;CAGpE,SAAS,EAAS,GAAc,GAAiB;EAC/C,IAAM,IAAS,IAAS,IAAY,GAC9B,IAAQ,IAAS,EAAQ,YAAY,EAAQ;EACnD,EAAM,KAAK,CAAI,GAEX,IAAM,SAAS,OACnB,EAAO,QAAQ,IACf,QAAoB;GAElB,AADA,EAAO,QAAQ,EAAM,KAAK,IAAI,GAC9B,EAAM,SAAS;EACjB,CAAC;CACH;CAEA,OAAO;EAAE;EAAQ;EAAW;CAAS;AACvC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Ref, WritableComputedRef } from 'vue';
|
|
2
2
|
export interface MaskedFieldOptions<T extends string> {
|
|
3
|
-
/** The real input, which is written to directly
|
|
3
|
+
/** The real input, which is written to directly: see below. */
|
|
4
4
|
fieldEl: Ref<HTMLInputElement | null>;
|
|
5
5
|
/** Whether the field can be typed into at all. Everything here is inert when it cannot. */
|
|
6
6
|
typing: () => boolean;
|
|
@@ -33,7 +33,7 @@ export interface MaskedFieldOptions<T extends string> {
|
|
|
33
33
|
/** Writes a value as masked text, and nothing at all for no value. */
|
|
34
34
|
toMask: (value: T | null) => string;
|
|
35
35
|
/**
|
|
36
|
-
* Whether a value the reader has finished typing may be taken
|
|
36
|
+
* Whether a value the reader has finished typing may be taken: within the bounds, not
|
|
37
37
|
* one of the excluded days. Everything is acceptable by default.
|
|
38
38
|
*/
|
|
39
39
|
acceptable?: (value: T) => boolean;
|
|
@@ -41,7 +41,7 @@ export interface MaskedFieldOptions<T extends string> {
|
|
|
41
41
|
/** What a component adds to the mask's own keys. */
|
|
42
42
|
export interface MaskedKeyHooks {
|
|
43
43
|
/**
|
|
44
|
-
* A key that is not a digit was typed
|
|
44
|
+
* A key that is not a digit was typed: a separator, most often. Its character never
|
|
45
45
|
* reaches the field; what it MEANS is the component's: a date pads the field being typed
|
|
46
46
|
* and moves on to the next, a time pads the hour.
|
|
47
47
|
*/
|
|
@@ -59,7 +59,7 @@ export interface MaskedField<T extends string> {
|
|
|
59
59
|
/** The text currently in the field while it is being typed into. */
|
|
60
60
|
draft: Ref<string>;
|
|
61
61
|
/**
|
|
62
|
-
* TRAP
|
|
62
|
+
* TRAP: bind with `v-model` and NEVER `:model-value`. Without an `onUpdate:modelValue`
|
|
63
63
|
* listener, `useModel` keeps an internal copy of the RAW typed text and rewrites it on the
|
|
64
64
|
* next patch, erasing the mask exactly when the masked text did NOT change: a rejected
|
|
65
65
|
* character, or a digit past the last. Both components lock it with a test.
|
|
@@ -80,7 +80,7 @@ export interface MaskedField<T extends string> {
|
|
|
80
80
|
onKeydown: (event: KeyboardEvent, hooks: MaskedKeyHooks) => void;
|
|
81
81
|
/**
|
|
82
82
|
* The handler for the field's paste event. `recognize` reads the pasted text as a WHOLE
|
|
83
|
-
* value when it is one
|
|
83
|
+
* value when it is one (a date in ISO form or a canonical time) and answers nothing
|
|
84
84
|
* otherwise, in which case only the digits of what was pasted are taken.
|
|
85
85
|
*/
|
|
86
86
|
onPaste: (event: ClipboardEvent, recognize: (pasted: string) => T | null) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMaskedField.js","names":[],"sources":["../../src/composables/useMaskedField.ts"],"sourcesContent":["// @core — module-wide: mask, caret and commit are the field's own behaviour.\n/**\n * The masked field of VDateInput and VTimeInput: the reader types digits, the separators\n * appear as they go. The FIELD half, `useFieldPanel` being the panel half.\n *\n * Only the mask's vocabulary is injected — how many digits, how they become text, where the\n * caret lands, how the text becomes a value. A date's separator is locale-dependent where a\n * time's is universal, so the two compute the caret differently; nothing here cares beyond\n * telling them whether the edit was an INSERTION.\n */\nimport { computed, ref, watch, type Ref, type WritableComputedRef } from 'vue'\n\nimport { digitsOf } from '../utils/text'\n\nexport interface MaskedFieldOptions<T extends string> {\n /** The real input, which is written to directly — see below. */\n fieldEl: Ref<HTMLInputElement | null>\n /** Whether the field can be typed into at all. Everything here is inert when it cannot. */\n typing: () => boolean\n /** What the field shows when it is NOT being typed into: the value, written out in full. */\n displayText: () => string\n /** The current value, or nothing. */\n readValue: () => T | null\n /** Writes a new value. */\n writeValue: (value: T | null) => void\n /** How many digits the mask holds: eight for a date, four for a time. */\n maxDigits: () => number\n /** Lays a run of digits out as masked text. */\n format: (digits: string) => string\n /**\n * Where the caret goes so as to sit after a given number of digits.\n *\n * The flag says whether something was INSERTED rather than deleted, and it matters: on\n * a deletion the caret must stay IN FRONT of the separator, or the next press of the key\n * would step over it instead of erasing, and the key would appear to do nothing.\n */\n caret: (text: string, digitsBefore: number, inserting: boolean) => number\n /**\n * Reads masked text back into a value, or nothing when it is not one yet.\n *\n * The flag marks the FINAL reading, when the reader leaves the field. A date expands a\n * two-digit year only there: \"26\" must not be committed as 2026 while it may still be on\n * its way to becoming it.\n */\n parse: (text: string, final: boolean) => T | null\n /** Writes a value as masked text, and nothing at all for no value. */\n toMask: (value: T | null) => string\n /**\n * Whether a value the reader has finished typing may be taken — within the bounds, not\n * one of the excluded days. Everything is acceptable by default.\n */\n acceptable?: (value: T) => boolean\n}\n\n/** What a component adds to the mask's own keys. */\nexport interface MaskedKeyHooks {\n /**\n * A key that is not a digit was typed — a separator, most often. Its character never\n * reaches the field; what it MEANS is the component's: a date pads the field being typed\n * and moves on to the next, a time pads the hour.\n */\n onSeparator: (el: HTMLInputElement) => void\n /**\n * The down arrow, which a component with a panel turns into the way into it. It answers\n * whether it handled the press: only then is the default cancelled, and a press left\n * alone travels on to whatever opens the panel.\n */\n onArrowDown?: () => boolean\n /** Runs once Enter has committed or reverted what was typed: closing a panel, say. */\n onEnter?: () => void\n}\n\nexport interface MaskedField<T extends string> {\n /** The text currently in the field while it is being typed into. */\n draft: Ref<string>\n /**\n * TRAP — bind with `v-model` and NEVER `:model-value`. Without an `onUpdate:modelValue`\n * listener, `useModel` keeps an internal copy of the RAW typed text and rewrites it on the\n * next patch, erasing the mask exactly when the masked text did NOT change: a rejected\n * character, or a digit past the last. Both components lock it with a test.\n */\n fieldModel: WritableComputedRef<string | number>\n /**\n * Writes into both the draft and the input. Both are needed: Vue does not patch an element\n * whose value it believes unchanged, and after a reformat it often is.\n */\n writeField: (text: string, caret?: number) => void\n /** Takes the value as soon as what has been typed is complete and acceptable. */\n commitLive: () => void\n /** Called as the reader leaves the field. It is safe to call twice. */\n commitOrRevert: () => void\n /** The handler to bind to the field's input event. */\n onFieldInput: (event: Event) => void\n /** The handler for the field's keydown event, with what the component adds to it. */\n onKeydown: (event: KeyboardEvent, hooks: MaskedKeyHooks) => void\n /**\n * The handler for the field's paste event. `recognize` reads the pasted text as a WHOLE\n * value when it is one — a date in ISO form, a canonical time — and answers nothing\n * otherwise, in which case only the digits of what was pasted are taken.\n */\n onPaste: (event: ClipboardEvent, recognize: (pasted: string) => T | null) => void\n}\n\nexport function useMaskedField<T extends string>(options: MaskedFieldOptions<T>): MaskedField<T> {\n const draft = ref('')\n const acceptable = options.acceptable ?? (() => true)\n\n /*\n * The value as masked text. Being a computed it also tracks what the conversion reads —\n * the locale's mask, the hour cycle — so neither component lists those dependencies.\n */\n const maskedValue = computed(() => options.toMask(options.readValue()))\n\n watch(\n maskedValue,\n (next) => {\n // TRAP — the anti-loop guard. A commit made while typing writes the value, which comes\n // straight back here; without the test, the text being typed and its caret would be\n // overwritten by text identical to what is already there.\n if (next !== draft.value) draft.value = next\n },\n { immediate: true },\n )\n\n const fieldModel = computed<string | number>({\n get: () => (options.typing() ? draft.value : options.displayText()),\n set: (value) => {\n if (options.typing()) draft.value = String(value ?? '')\n },\n })\n\n function writeField(text: string, caret?: number) {\n draft.value = text\n const el = options.fieldEl.value\n if (!el) return\n if (el.value !== text) el.value = text\n if (caret !== undefined) el.setSelectionRange(caret, caret)\n }\n\n function commitLive() {\n const value = options.parse(draft.value, false)\n if (value && acceptable(value) && value !== options.readValue()) options.writeValue(value)\n }\n\n /**\n * Commits what was typed, or SILENTLY reverts to the current value.\n *\n * An entry that is incomplete, impossible (31 February), out of bounds or excluded simply\n * disappears. There is deliberately no error state of our own competing with the `invalid`\n * prop the consumer controls.\n */\n function commitOrRevert() {\n if (!options.typing()) return\n if (!digitsOf(draft.value)) {\n if (options.readValue() !== null) options.writeValue(null)\n writeField('')\n return\n }\n const value = options.parse(draft.value, true)\n if (value && acceptable(value)) {\n if (value !== options.readValue()) options.writeValue(value)\n // Rewritten in full: \"5/6/26\" becomes \"05/06/2026\".\n writeField(options.toMask(value))\n return\n }\n writeField(options.toMask(options.readValue()))\n }\n\n /**\n * Reformats the field on every keystroke.\n *\n * The invariant to keep is that the number of digits to the LEFT of the caret survives\n * the reformatting. Restoring an absolute position instead would misplace the caret on\n * exactly the keystrokes that matter: a position jumps by one the moment a separator\n * appears or disappears.\n */\n function onFieldInput(event: Event) {\n if (!options.typing()) return\n const el = event.target as HTMLInputElement\n const raw = el.value\n const caret = el.selectionStart ?? raw.length\n const before = digitsOf(raw.slice(0, caret)).length\n const digits = digitsOf(raw).slice(0, options.maxDigits())\n const inserting = !String((event as InputEvent).inputType ?? '').startsWith('delete')\n const text = options.format(digits)\n writeField(text, options.caret(text, Math.min(before, digits.length), inserting))\n commitLive()\n }\n\n /*\n * Backspace over a SEPARATOR, which erases the digit in front of it instead.\n *\n * TRAP — a separator is placed by the mask and never typed, so erasing one has to erase\n * the DIGIT before it, which is what the reader believes they are erasing. Left alone, the\n * mask writes the separator straight back and the key looks dead.\n *\n * It answers whether it handled the press: `false` means the caret was not sitting after a\n * separator and the browser's own Backspace should run.\n */\n function backspaceOverSeparator(el: HTMLInputElement) {\n const start = el.selectionStart\n if (\n start === null ||\n start !== el.selectionEnd ||\n start === 0 ||\n /\\d/.test(el.value[start - 1] ?? '')\n ) {\n return false\n }\n const before = digitsOf(el.value.slice(0, start)).length\n const digits = digitsOf(el.value)\n const text = options.format(digits.slice(0, before - 1) + digits.slice(before))\n writeField(text, options.caret(text, before - 1, false))\n commitLive()\n return true\n }\n\n /**\n * Splices a pasted run of digits into the one already in the field, at the selection, and\n * puts the caret after what was inserted.\n */\n function pasteDigits(el: HTMLInputElement, pasted: string) {\n const start = el.selectionStart ?? el.value.length\n const end = el.selectionEnd ?? start\n const digits = digitsOf(el.value)\n const from = digitsOf(el.value.slice(0, start)).length\n const to = digitsOf(el.value.slice(0, end)).length\n const inserted = digitsOf(pasted)\n const next = (digits.slice(0, from) + inserted + digits.slice(to)).slice(0, options.maxDigits())\n const text = options.format(next)\n writeField(text, options.caret(text, Math.min(from + inserted.length, next.length), true))\n commitLive()\n }\n\n // @keyboard\n function onKeydown(event: KeyboardEvent, hooks: MaskedKeyHooks) {\n if (!options.typing()) return\n const el = options.fieldEl.value\n if (!el) return\n\n if (event.key === 'Enter') {\n // Cancelling the default does two things at once: it stops the surrounding form from\n // being submitted, and it stops a panel this keystroke closes from being reopened as\n // the event travels up to the component's root.\n event.preventDefault()\n commitOrRevert()\n hooks.onEnter?.()\n return\n }\n if (event.key === 'ArrowDown') {\n if (hooks.onArrowDown?.()) event.preventDefault()\n return\n }\n if (event.key === 'Backspace') {\n if (backspaceOverSeparator(el)) event.preventDefault()\n return\n }\n if (\n event.key.length === 1 &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey &&\n !/\\d/.test(event.key)\n ) {\n event.preventDefault()\n hooks.onSeparator(el)\n }\n }\n\n /**\n * Pasting. Without the whole-value path, pasting \"2026-06-10\" into a field expecting day,\n * month, year would produce \"20/26/0610\": the digits would be taken in order and the\n * separators ignored.\n *\n * A whole value is written as it stands rather than read back from the mask, which is not\n * a detail on a 12-hour clock: \"19:05\" shows as \"07:05\", and reading that text back would\n * take the half of the day from the AM/PM button instead of from what was pasted.\n */\n function onPaste(event: ClipboardEvent, recognize: (pasted: string) => T | null) {\n if (!options.typing()) return\n const el = options.fieldEl.value\n if (!el) return\n event.preventDefault()\n const pasted = (event.clipboardData?.getData('text') ?? '').trim()\n const whole = recognize(pasted)\n if (whole) {\n const text = options.toMask(whole)\n writeField(text, text.length)\n if (acceptable(whole) && whole !== options.readValue()) options.writeValue(whole)\n return\n }\n pasteDigits(el, pasted)\n }\n\n return {\n draft,\n fieldModel,\n writeField,\n commitLive,\n commitOrRevert,\n onFieldInput,\n onKeydown,\n onPaste,\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useMaskedField.js","names":[],"sources":["../../src/composables/useMaskedField.ts"],"sourcesContent":["// @core — module-wide: mask, caret and commit are the field's own behaviour.\n/**\n * The masked field of VDateInput and VTimeInput: the reader types digits, the separators\n * appear as they go. The FIELD half, `useFieldPanel` being the panel half.\n *\n * Only the mask's vocabulary is injected — how many digits, how they become text, where the\n * caret lands, how the text becomes a value. A date's separator is locale-dependent where a\n * time's is universal, so the two compute the caret differently; nothing here cares beyond\n * telling them whether the edit was an INSERTION.\n */\nimport { computed, ref, watch, type Ref, type WritableComputedRef } from 'vue'\n\nimport { digitsOf } from '../utils/text'\n\nexport interface MaskedFieldOptions<T extends string> {\n /** The real input, which is written to directly: see below. */\n fieldEl: Ref<HTMLInputElement | null>\n /** Whether the field can be typed into at all. Everything here is inert when it cannot. */\n typing: () => boolean\n /** What the field shows when it is NOT being typed into: the value, written out in full. */\n displayText: () => string\n /** The current value, or nothing. */\n readValue: () => T | null\n /** Writes a new value. */\n writeValue: (value: T | null) => void\n /** How many digits the mask holds: eight for a date, four for a time. */\n maxDigits: () => number\n /** Lays a run of digits out as masked text. */\n format: (digits: string) => string\n /**\n * Where the caret goes so as to sit after a given number of digits.\n *\n * The flag says whether something was INSERTED rather than deleted, and it matters: on\n * a deletion the caret must stay IN FRONT of the separator, or the next press of the key\n * would step over it instead of erasing, and the key would appear to do nothing.\n */\n caret: (text: string, digitsBefore: number, inserting: boolean) => number\n /**\n * Reads masked text back into a value, or nothing when it is not one yet.\n *\n * The flag marks the FINAL reading, when the reader leaves the field. A date expands a\n * two-digit year only there: \"26\" must not be committed as 2026 while it may still be on\n * its way to becoming it.\n */\n parse: (text: string, final: boolean) => T | null\n /** Writes a value as masked text, and nothing at all for no value. */\n toMask: (value: T | null) => string\n /**\n * Whether a value the reader has finished typing may be taken: within the bounds, not\n * one of the excluded days. Everything is acceptable by default.\n */\n acceptable?: (value: T) => boolean\n}\n\n/** What a component adds to the mask's own keys. */\nexport interface MaskedKeyHooks {\n /**\n * A key that is not a digit was typed: a separator, most often. Its character never\n * reaches the field; what it MEANS is the component's: a date pads the field being typed\n * and moves on to the next, a time pads the hour.\n */\n onSeparator: (el: HTMLInputElement) => void\n /**\n * The down arrow, which a component with a panel turns into the way into it. It answers\n * whether it handled the press: only then is the default cancelled, and a press left\n * alone travels on to whatever opens the panel.\n */\n onArrowDown?: () => boolean\n /** Runs once Enter has committed or reverted what was typed: closing a panel, say. */\n onEnter?: () => void\n}\n\nexport interface MaskedField<T extends string> {\n /** The text currently in the field while it is being typed into. */\n draft: Ref<string>\n /**\n * TRAP: bind with `v-model` and NEVER `:model-value`. Without an `onUpdate:modelValue`\n * listener, `useModel` keeps an internal copy of the RAW typed text and rewrites it on the\n * next patch, erasing the mask exactly when the masked text did NOT change: a rejected\n * character, or a digit past the last. Both components lock it with a test.\n */\n fieldModel: WritableComputedRef<string | number>\n /**\n * Writes into both the draft and the input. Both are needed: Vue does not patch an element\n * whose value it believes unchanged, and after a reformat it often is.\n */\n writeField: (text: string, caret?: number) => void\n /** Takes the value as soon as what has been typed is complete and acceptable. */\n commitLive: () => void\n /** Called as the reader leaves the field. It is safe to call twice. */\n commitOrRevert: () => void\n /** The handler to bind to the field's input event. */\n onFieldInput: (event: Event) => void\n /** The handler for the field's keydown event, with what the component adds to it. */\n onKeydown: (event: KeyboardEvent, hooks: MaskedKeyHooks) => void\n /**\n * The handler for the field's paste event. `recognize` reads the pasted text as a WHOLE\n * value when it is one (a date in ISO form or a canonical time) and answers nothing\n * otherwise, in which case only the digits of what was pasted are taken.\n */\n onPaste: (event: ClipboardEvent, recognize: (pasted: string) => T | null) => void\n}\n\nexport function useMaskedField<T extends string>(options: MaskedFieldOptions<T>): MaskedField<T> {\n const draft = ref('')\n const acceptable = options.acceptable ?? (() => true)\n\n /*\n * The value as masked text. Being a computed it also tracks what the conversion reads —\n * the locale's mask, the hour cycle — so neither component lists those dependencies.\n */\n const maskedValue = computed(() => options.toMask(options.readValue()))\n\n watch(\n maskedValue,\n (next) => {\n // TRAP — the anti-loop guard. A commit made while typing writes the value, which comes\n // straight back here; without the test, the text being typed and its caret would be\n // overwritten by text identical to what is already there.\n if (next !== draft.value) draft.value = next\n },\n { immediate: true },\n )\n\n const fieldModel = computed<string | number>({\n get: () => (options.typing() ? draft.value : options.displayText()),\n set: (value) => {\n if (options.typing()) draft.value = String(value ?? '')\n },\n })\n\n function writeField(text: string, caret?: number) {\n draft.value = text\n const el = options.fieldEl.value\n if (!el) return\n if (el.value !== text) el.value = text\n if (caret !== undefined) el.setSelectionRange(caret, caret)\n }\n\n function commitLive() {\n const value = options.parse(draft.value, false)\n if (value && acceptable(value) && value !== options.readValue()) options.writeValue(value)\n }\n\n /**\n * Commits what was typed, or SILENTLY reverts to the current value.\n *\n * An entry that is incomplete, impossible (31 February), out of bounds or excluded simply\n * disappears. There is deliberately no error state of our own competing with the `invalid`\n * prop the consumer controls.\n */\n function commitOrRevert() {\n if (!options.typing()) return\n if (!digitsOf(draft.value)) {\n if (options.readValue() !== null) options.writeValue(null)\n writeField('')\n return\n }\n const value = options.parse(draft.value, true)\n if (value && acceptable(value)) {\n if (value !== options.readValue()) options.writeValue(value)\n // Rewritten in full: \"5/6/26\" becomes \"05/06/2026\".\n writeField(options.toMask(value))\n return\n }\n writeField(options.toMask(options.readValue()))\n }\n\n /**\n * Reformats the field on every keystroke.\n *\n * The invariant to keep is that the number of digits to the LEFT of the caret survives\n * the reformatting. Restoring an absolute position instead would misplace the caret on\n * exactly the keystrokes that matter: a position jumps by one the moment a separator\n * appears or disappears.\n */\n function onFieldInput(event: Event) {\n if (!options.typing()) return\n const el = event.target as HTMLInputElement\n const raw = el.value\n const caret = el.selectionStart ?? raw.length\n const before = digitsOf(raw.slice(0, caret)).length\n const digits = digitsOf(raw).slice(0, options.maxDigits())\n const inserting = !String((event as InputEvent).inputType ?? '').startsWith('delete')\n const text = options.format(digits)\n writeField(text, options.caret(text, Math.min(before, digits.length), inserting))\n commitLive()\n }\n\n /*\n * Backspace over a SEPARATOR, which erases the digit in front of it instead.\n *\n * TRAP — a separator is placed by the mask and never typed, so erasing one has to erase\n * the DIGIT before it, which is what the reader believes they are erasing. Left alone, the\n * mask writes the separator straight back and the key looks dead.\n *\n * It answers whether it handled the press: `false` means the caret was not sitting after a\n * separator and the browser's own Backspace should run.\n */\n function backspaceOverSeparator(el: HTMLInputElement) {\n const start = el.selectionStart\n if (\n start === null ||\n start !== el.selectionEnd ||\n start === 0 ||\n /\\d/.test(el.value[start - 1] ?? '')\n ) {\n return false\n }\n const before = digitsOf(el.value.slice(0, start)).length\n const digits = digitsOf(el.value)\n const text = options.format(digits.slice(0, before - 1) + digits.slice(before))\n writeField(text, options.caret(text, before - 1, false))\n commitLive()\n return true\n }\n\n /**\n * Splices a pasted run of digits into the one already in the field, at the selection, and\n * puts the caret after what was inserted.\n */\n function pasteDigits(el: HTMLInputElement, pasted: string) {\n const start = el.selectionStart ?? el.value.length\n const end = el.selectionEnd ?? start\n const digits = digitsOf(el.value)\n const from = digitsOf(el.value.slice(0, start)).length\n const to = digitsOf(el.value.slice(0, end)).length\n const inserted = digitsOf(pasted)\n const next = (digits.slice(0, from) + inserted + digits.slice(to)).slice(0, options.maxDigits())\n const text = options.format(next)\n writeField(text, options.caret(text, Math.min(from + inserted.length, next.length), true))\n commitLive()\n }\n\n // @keyboard\n function onKeydown(event: KeyboardEvent, hooks: MaskedKeyHooks) {\n if (!options.typing()) return\n const el = options.fieldEl.value\n if (!el) return\n\n if (event.key === 'Enter') {\n // Cancelling the default does two things at once: it stops the surrounding form from\n // being submitted, and it stops a panel this keystroke closes from being reopened as\n // the event travels up to the component's root.\n event.preventDefault()\n commitOrRevert()\n hooks.onEnter?.()\n return\n }\n if (event.key === 'ArrowDown') {\n if (hooks.onArrowDown?.()) event.preventDefault()\n return\n }\n if (event.key === 'Backspace') {\n if (backspaceOverSeparator(el)) event.preventDefault()\n return\n }\n if (\n event.key.length === 1 &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey &&\n !/\\d/.test(event.key)\n ) {\n event.preventDefault()\n hooks.onSeparator(el)\n }\n }\n\n /**\n * Pasting. Without the whole-value path, pasting \"2026-06-10\" into a field expecting day,\n * month, year would produce \"20/26/0610\": the digits would be taken in order and the\n * separators ignored.\n *\n * A whole value is written as it stands rather than read back from the mask, which is not\n * a detail on a 12-hour clock: \"19:05\" shows as \"07:05\", and reading that text back would\n * take the half of the day from the AM/PM button instead of from what was pasted.\n */\n function onPaste(event: ClipboardEvent, recognize: (pasted: string) => T | null) {\n if (!options.typing()) return\n const el = options.fieldEl.value\n if (!el) return\n event.preventDefault()\n const pasted = (event.clipboardData?.getData('text') ?? '').trim()\n const whole = recognize(pasted)\n if (whole) {\n const text = options.toMask(whole)\n writeField(text, text.length)\n if (acceptable(whole) && whole !== options.readValue()) options.writeValue(whole)\n return\n }\n pasteDigits(el, pasted)\n }\n\n return {\n draft,\n fieldModel,\n writeField,\n commitLive,\n commitOrRevert,\n onFieldInput,\n onKeydown,\n onPaste,\n }\n}\n"],"mappings":"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"}
|
|
@@ -7,14 +7,14 @@ export declare function usePopover(el: Readonly<Ref<HTMLElement | null>>): {
|
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
9
|
* The model half of a popover's `v-model:open`, for a component that publishes its open
|
|
10
|
-
* state: a change to the model opens or closes the panel. The other half
|
|
11
|
-
* DOM's state back into the model
|
|
10
|
+
* state: a change to the model opens or closes the panel. The other half: writing the
|
|
11
|
+
* DOM's state back into the model, stays with the component, in its own toggle handler,
|
|
12
12
|
* because that is where it knows the state has settled.
|
|
13
13
|
*
|
|
14
14
|
* `shown` is a getter so that the state may live in a child: VPopover reads its own
|
|
15
15
|
* `usePopover`, VMenu reads the one VMenuPanel exposes.
|
|
16
16
|
*
|
|
17
|
-
* TRAP
|
|
17
|
+
* TRAP: the `value === shown()` guard is what keeps the two directions from chasing each
|
|
18
18
|
* other. A panel the browser has just dismissed writes `false` into the model; without the
|
|
19
19
|
* guard that write would come back here as a request to close a panel already closed.
|
|
20
20
|
*/
|
|
@@ -13,17 +13,19 @@ function r(e) {
|
|
|
13
13
|
if (!(!i || n.value)) try {
|
|
14
14
|
i.showPopover(t ? { source: t } : void 0);
|
|
15
15
|
} catch {
|
|
16
|
-
|
|
16
|
+
let e = o;
|
|
17
|
+
r && queueMicrotask(() => e === o && a(t, !1));
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
let o = 0;
|
|
21
|
+
function s() {
|
|
22
|
+
o += 1, n.value && e.value?.hidePopover();
|
|
21
23
|
}
|
|
22
24
|
return {
|
|
23
25
|
shown: n,
|
|
24
26
|
syncShown: r,
|
|
25
27
|
show: i,
|
|
26
|
-
hide:
|
|
28
|
+
hide: s
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
31
|
function i(t, r, i, a) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePopover.js","names":[],"sources":["../../src/composables/usePopover.ts"],"sourcesContent":["// @core\n/**\n * The state plumbing of a `[popover]`: whether it is open, and how to open or close it.\n * The JS half of `styles/floating.css`, which places the panel.\n *\n * The Popover API is imperative where the rest of the library is declarative, and it sets\n * three traps a plain boolean would fall into. Closing an already-closed panel throws, hence\n * the idempotence guards. Opening one while the browser is busy with another popover throws\n * as well, hence the retry on `show`. And an `auto` panel dismisses itself on a click outside\n * or on Escape, so the state is READ from the DOM: wire `syncShown` to both `beforetoggle`\n * and `toggle`, the first being the only one that catches light dismiss in time and the\n * second the only one the jsdom stub emits.\n *\n * TRAP — never assign `shown` by hand. A panel the browser dismissed would then still read\n * as open, and the guard would swallow the next request to close it.\n */\n\nimport { onMounted, ref, watch, type Ref } from 'vue'\n\n// @fallback\n// The option below is newer than the type definitions shipped with TypeScript, so the\n// element is described locally rather than being cast about at each call site.\ntype PopoverWithSource = HTMLElement & { showPopover(options?: { source?: HTMLElement }): void }\n\nexport function usePopover(el: Readonly<Ref<HTMLElement | null>>) {\n const shown = ref(false)\n\n /**\n * Reads the state back off the panel. Bind it to both `@beforetoggle` and `@toggle`, and\n * on the popover element itself: ToggleEvents do not bubble.\n */\n function syncShown(event: Event) {\n shown.value = (event as ToggleEvent).newState === 'open'\n }\n\n /**\n * Opens the panel.\n *\n * TRAP — opening from code rather than from a click must name the invoker as `source`.\n * That is what recreates the relationship a click would have: the implicit anchor and the\n * panel's place in the popover stack. Without it the panel has nothing to position\n * against and lands at the corner of the viewport.\n */\n function show(source?: HTMLElement) {\n attempt(source, true)\n }\n\n /*\n * TRAP — the browser REFUSES to open a popover while it is in the middle of another\n * popover operation, and it throws rather than queueing the request\n * (\"Invalid to show a popover during another show operation\").\n *\n * The case that reaches it is ordinary: closing a popover that holds the focus hands\n * that focus back to its invoker SYNCHRONOUSLY, inside the hide, and a component\n * listening for that focus opens a panel of its own from the handler — a tooltip on the\n * button a menu has just closed. Nothing in the calling code can see it coming.\n *\n * A microtask runs once the stack has unwound, hence after the operation the browser was\n * in, which is all it takes. A second refusal is left alone: the panel stays closed\n * rather than the page taking an uncaught error.\n */\n function attempt(source: HTMLElement | undefined, retry: boolean) {\n const panel = el.value as PopoverWithSource | null\n if (!panel || shown.value) return\n try {\n panel.showPopover(source ? { source } : undefined)\n } catch {\n if (retry) queueMicrotask(() => attempt(source, false))\n }\n }\n\n /** Closes the panel. */\n function hide() {\n if (shown.value) el.value?.hidePopover()\n }\n\n return { shown, syncShown, show, hide }\n}\n\n// @core\n/**\n * The model half of a popover's `v-model:open`, for a component that publishes its open\n * state: a change to the model opens or closes the panel. The other half
|
|
1
|
+
{"version":3,"file":"usePopover.js","names":[],"sources":["../../src/composables/usePopover.ts"],"sourcesContent":["// @core\n/**\n * The state plumbing of a `[popover]`: whether it is open, and how to open or close it.\n * The JS half of `styles/floating.css`, which places the panel.\n *\n * The Popover API is imperative where the rest of the library is declarative, and it sets\n * three traps a plain boolean would fall into. Closing an already-closed panel throws, hence\n * the idempotence guards. Opening one while the browser is busy with another popover throws\n * as well, hence the retry on `show`. And an `auto` panel dismisses itself on a click outside\n * or on Escape, so the state is READ from the DOM: wire `syncShown` to both `beforetoggle`\n * and `toggle`, the first being the only one that catches light dismiss in time and the\n * second the only one the jsdom stub emits.\n *\n * TRAP — never assign `shown` by hand. A panel the browser dismissed would then still read\n * as open, and the guard would swallow the next request to close it.\n */\n\nimport { onMounted, ref, watch, type Ref } from 'vue'\n\n// @fallback\n// The option below is newer than the type definitions shipped with TypeScript, so the\n// element is described locally rather than being cast about at each call site.\ntype PopoverWithSource = HTMLElement & { showPopover(options?: { source?: HTMLElement }): void }\n\nexport function usePopover(el: Readonly<Ref<HTMLElement | null>>) {\n const shown = ref(false)\n\n /**\n * Reads the state back off the panel. Bind it to both `@beforetoggle` and `@toggle`, and\n * on the popover element itself: ToggleEvents do not bubble.\n */\n function syncShown(event: Event) {\n shown.value = (event as ToggleEvent).newState === 'open'\n }\n\n /**\n * Opens the panel.\n *\n * TRAP — opening from code rather than from a click must name the invoker as `source`.\n * That is what recreates the relationship a click would have: the implicit anchor and the\n * panel's place in the popover stack. Without it the panel has nothing to position\n * against and lands at the corner of the viewport.\n */\n function show(source?: HTMLElement) {\n attempt(source, true)\n }\n\n /*\n * TRAP — the browser REFUSES to open a popover while it is in the middle of another\n * popover operation, and it throws rather than queueing the request\n * (\"Invalid to show a popover during another show operation\").\n *\n * The case that reaches it is ordinary: closing a popover that holds the focus hands\n * that focus back to its invoker SYNCHRONOUSLY, inside the hide, and a component\n * listening for that focus opens a panel of its own from the handler — a tooltip on the\n * button a menu has just closed. Nothing in the calling code can see it coming.\n *\n * A microtask runs once the stack has unwound, hence after the operation the browser was\n * in, which is all it takes. A second refusal is left alone: the panel stays closed\n * rather than the page taking an uncaught error.\n */\n function attempt(source: HTMLElement | undefined, retry: boolean) {\n const panel = el.value as PopoverWithSource | null\n if (!panel || shown.value) return\n try {\n panel.showPopover(source ? { source } : undefined)\n } catch {\n // TRAP — a close asked for before the microtask runs must win: the retry checks\n // that no `hide` came in between, or it would reopen a panel just closed.\n const asked = generation\n if (retry) queueMicrotask(() => asked === generation && attempt(source, false))\n }\n }\n\n /** Bumped by every close, so a retry queued before it knows it was overtaken. */\n let generation = 0\n\n /** Closes the panel. */\n function hide() {\n generation += 1\n if (shown.value) el.value?.hidePopover()\n }\n\n return { shown, syncShown, show, hide }\n}\n\n// @core\n/**\n * The model half of a popover's `v-model:open`, for a component that publishes its open\n * state: a change to the model opens or closes the panel. The other half: writing the\n * DOM's state back into the model, stays with the component, in its own toggle handler,\n * because that is where it knows the state has settled.\n *\n * `shown` is a getter so that the state may live in a child: VPopover reads its own\n * `usePopover`, VMenu reads the one VMenuPanel exposes.\n *\n * TRAP: the `value === shown()` guard is what keeps the two directions from chasing each\n * other. A panel the browser has just dismissed writes `false` into the model; without the\n * guard that write would come back here as a request to close a panel already closed.\n */\nexport function usePopoverModel(\n open: Ref<boolean>,\n shown: () => boolean,\n show: () => void,\n hide: () => void,\n) {\n watch(open, (value) => {\n if (value === shown()) return\n if (value) show()\n else hide()\n })\n\n // @ssr\n // A watcher does not run during the server render, so a panel asked to be open\n // from the start would never be told to open. Replaying the initial state on mount is\n // what covers that case.\n onMounted(() => {\n if (open.value) show()\n })\n}\n"],"mappings":";;AAwBA,SAAgB,EAAW,GAAuC;CAChE,IAAM,IAAQ,EAAI,EAAK;CAMvB,SAAS,EAAU,GAAc;EAC/B,EAAM,QAAS,EAAsB,aAAa;CACpD;CAUA,SAAS,EAAK,GAAsB;EAClC,EAAQ,GAAQ,EAAI;CACtB;CAgBA,SAAS,EAAQ,GAAiC,GAAgB;EAChE,IAAM,IAAQ,EAAG;EACb,OAAC,KAAS,EAAM,QACpB,IAAI;GACF,EAAM,YAAY,IAAS,EAAE,UAAO,IAAI,KAAA,CAAS;EACnD,QAAQ;GAGN,IAAM,IAAQ;GACd,AAAI,KAAO,qBAAqB,MAAU,KAAc,EAAQ,GAAQ,EAAK,CAAC;EAChF;CACF;CAGA,IAAI,IAAa;CAGjB,SAAS,IAAO;EAEd,AADA,KAAc,GACV,EAAM,SAAO,EAAG,OAAO,YAAY;CACzC;CAEA,OAAO;EAAE;EAAO;EAAW;EAAM;CAAK;AACxC;AAgBA,SAAgB,EACd,GACA,GACA,GACA,GACA;CAWA,AAVA,EAAM,IAAO,MAAU;EACjB,MAAU,EAAM,MAChB,IAAO,EAAK,IACX,EAAK;CACZ,CAAC,GAMD,QAAgB;EACd,AAAI,EAAK,SAAO,EAAK;CACvB,CAAC;AACH"}
|
|
@@ -2,13 +2,22 @@ import { clamp as e } from "../utils/number.js";
|
|
|
2
2
|
import { computed as t } from "vue";
|
|
3
3
|
//#region src/composables/useProgressValue.ts
|
|
4
4
|
function n(n, r) {
|
|
5
|
-
let i = t(() =>
|
|
5
|
+
let i = t(() => {
|
|
6
|
+
let e = r();
|
|
7
|
+
return Number.isFinite(e) ? Math.max(e, 0) : 100;
|
|
8
|
+
}), a = t(() => {
|
|
9
|
+
let t = n();
|
|
10
|
+
return Number.isNaN(t) ? 0 : e(t, 0, i.value);
|
|
11
|
+
}), o = t(() => a.value / (i.value || 1)), s = t(() => o.value * 100);
|
|
6
12
|
return {
|
|
7
13
|
max: i,
|
|
8
14
|
clamped: a,
|
|
9
15
|
fraction: o,
|
|
10
16
|
percent: s,
|
|
11
|
-
roundedPercent: t(() =>
|
|
17
|
+
roundedPercent: t(() => {
|
|
18
|
+
let t = s.value;
|
|
19
|
+
return t <= 0 || t >= 100 ? Math.round(t) : e(Math.round(t), 1, 99);
|
|
20
|
+
})
|
|
12
21
|
};
|
|
13
22
|
}
|
|
14
23
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProgressValue.js","names":[],"sources":["../../src/composables/useProgressValue.ts"],"sourcesContent":["// @core\n/**\n * A progress value normalized for VProgressLinear and VProgressCircular: the bound brought\n * back to zero or above, the value clamped into range, and its position in that range as a\n * 0-to-1 fraction and as a percentage. Everything either component draws comes from those —\n * the fill, the arc, the percentage written inside, the ARIA values.\n *\n * The bound is returned rather than left for each component to read off its prop: the value\n * is clamped against the NORMALIZED bound, so announcing the raw one would give assistive\n * technology a `aria-valuenow` above an `aria-valuemax` of -5.\n */\n\nimport { computed, type ComputedRef } from 'vue'\nimport { clamp } from '../utils/number'\n\nexport function useProgressValue(\n value: () => number,\n max: () => number,\n): {\n max: ComputedRef<number>\n clamped: ComputedRef<number>\n fraction: ComputedRef<number>\n /** The fraction as a percentage, unrounded: what the default slot receives. */\n percent: ComputedRef<number>\n /** The percentage rounded to a whole number: what the component writes by default. */\n roundedPercent: ComputedRef<number>\n} {\n const normalizedMax = computed(() => Math.max(
|
|
1
|
+
{"version":3,"file":"useProgressValue.js","names":[],"sources":["../../src/composables/useProgressValue.ts"],"sourcesContent":["// @core\n/**\n * A progress value normalized for VProgressLinear and VProgressCircular: the bound brought\n * back to zero or above, the value clamped into range, and its position in that range as a\n * 0-to-1 fraction and as a percentage. Everything either component draws comes from those —\n * the fill, the arc, the percentage written inside, the ARIA values.\n *\n * The bound is returned rather than left for each component to read off its prop: the value\n * is clamped against the NORMALIZED bound, so announcing the raw one would give assistive\n * technology a `aria-valuenow` above an `aria-valuemax` of -5.\n */\n\nimport { computed, type ComputedRef } from 'vue'\nimport { clamp } from '../utils/number'\n\nexport function useProgressValue(\n value: () => number,\n max: () => number,\n): {\n max: ComputedRef<number>\n clamped: ComputedRef<number>\n fraction: ComputedRef<number>\n /** The fraction as a percentage, unrounded: what the default slot receives. */\n percent: ComputedRef<number>\n /** The percentage rounded to a whole number: what the component writes by default. */\n roundedPercent: ComputedRef<number>\n} {\n /* A value computed by the consumer (`loaded / total * 100` before `total` is known) can\n be NaN, which `clamp` and `Math.max` both hand back unchanged: it would reach\n `aria-valuenow` and the text as the word \"NaN\". A value that is not a number is read as\n no progress, and a bound that is not a finite number as the default of 100. */\n const normalizedMax = computed(() => {\n const bound = max()\n return Number.isFinite(bound) ? Math.max(bound, 0) : 100\n })\n const clamped = computed(() => {\n const current = value()\n return Number.isNaN(current) ? 0 : clamp(current, 0, normalizedMax.value)\n })\n\n // The `|| 1` denominator is what keeps a max of 0 from dividing by nothing.\n const fraction = computed(() => clamped.value / (normalizedMax.value || 1))\n const percent = computed(() => fraction.value * 100)\n /* Rounded to the nearest whole number, except at the two ends: \"100%\" is written only\n once the work is complete and \"0%\" only while nothing has been done, so 99.6 reads 99%\n and 0.4 reads 1%. A plain round would announce a finished task that is still running. */\n const roundedPercent = computed(() => {\n const p = percent.value\n return p <= 0 || p >= 100 ? Math.round(p) : clamp(Math.round(p), 1, 99)\n })\n\n return { max: normalizedMax, clamped, fraction, percent, roundedPercent }\n}\n"],"mappings":";;;AAeA,SAAgB,EACd,GACA,GASA;CAKA,IAAM,IAAgB,QAAe;EACnC,IAAM,IAAQ,EAAI;EAClB,OAAO,OAAO,SAAS,CAAK,IAAI,KAAK,IAAI,GAAO,CAAC,IAAI;CACvD,CAAC,GACK,IAAU,QAAe;EAC7B,IAAM,IAAU,EAAM;EACtB,OAAO,OAAO,MAAM,CAAO,IAAI,IAAI,EAAM,GAAS,GAAG,EAAc,KAAK;CAC1E,CAAC,GAGK,IAAW,QAAe,EAAQ,SAAS,EAAc,SAAS,EAAE,GACpE,IAAU,QAAe,EAAS,QAAQ,GAAG;CASnD,OAAO;EAAE,KAAK;EAAe;EAAS;EAAU;EAAS,gBALlC,QAAe;GACpC,IAAM,IAAI,EAAQ;GAClB,OAAO,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM,CAAC,IAAI,EAAM,KAAK,MAAM,CAAC,GAAG,GAAG,EAAE;EACxE,CAEyD;CAAe;AAC1E"}
|
|
@@ -14,7 +14,7 @@ type ClassBinding = false | null | undefined | string | Record<string, unknown>
|
|
|
14
14
|
* `aria-*` land on a decorative wrapper, where the form ignores them and assistive
|
|
15
15
|
* technology never finds them.
|
|
16
16
|
*
|
|
17
|
-
* `defineOptions({ inheritAttrs: false })` stays in the SFC
|
|
17
|
+
* `defineOptions({ inheritAttrs: false })` stays in the SFC: it is a compilation option.
|
|
18
18
|
* `attrs` is returned whole for the components that need to read one back: a consumer `id`
|
|
19
19
|
* that must beat the generated one, a `describedby` to merge rather than replace.
|
|
20
20
|
*/
|