vectis-ui 0.1.0 → 0.8.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 +40 -6
- package/dist/components/VAccordion/VAccordion.css +1 -1
- package/dist/components/VAccordion/VAccordion.d.ts +9 -16
- package/dist/components/VAccordion/VAccordion.js.map +1 -1
- package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.css +1 -1
- package/dist/components/VAccordion/VAccordionItem.d.ts +33 -19
- package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js +50 -38
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/context.d.ts +1 -8
- package/dist/components/VAccordion/context.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.css +1 -1
- package/dist/components/VAvatar/VAvatar.d.ts +44 -29
- package/dist/components/VAvatar/VAvatar.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +38 -35
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.d.ts +5 -16
- package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +7 -7
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/context.d.ts +6 -10
- package/dist/components/VAvatar/context.js.map +1 -1
- package/dist/components/VBadge/VBadge.css +1 -1
- package/dist/components/VBadge/VBadge.d.ts +31 -10
- package/dist/components/VBadge/VBadge.js.map +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +39 -24
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.css +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +4 -17
- package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +23 -25
- 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 +30 -10
- package/dist/components/VButton/VButton.js.map +1 -1
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +39 -30
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.css +1 -1
- package/dist/components/VButton/VButtonGroup.d.ts +76 -13
- package/dist/components/VButton/VButtonGroup.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js +74 -8
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/context.d.ts +30 -0
- package/dist/components/VButton/context.js +6 -0
- package/dist/components/VButton/context.js.map +1 -0
- package/dist/components/VCalendar/VCalendar.css +1 -1
- package/dist/components/VCalendar/VCalendar.d.ts +50 -36
- package/dist/components/VCalendar/VCalendar.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +99 -85
- 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 +5 -11
- package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js +7 -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 +10 -13
- package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +183 -244
- 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 +15 -20
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +385 -448
- 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.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js +53 -49
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/gesture.d.ts +158 -0
- package/dist/components/VCalendar/gesture.js +170 -0
- package/dist/components/VCalendar/gesture.js.map +1 -0
- package/dist/components/VCalendar/keyboard.d.ts +9 -3
- package/dist/components/VCalendar/keyboard.js +2 -6
- package/dist/components/VCalendar/keyboard.js.map +1 -1
- package/dist/components/VCalendar/layout.d.ts +67 -54
- package/dist/components/VCalendar/layout.js +77 -69
- package/dist/components/VCalendar/layout.js.map +1 -1
- package/dist/components/VCalendar/timeGrid.d.ts +55 -0
- package/dist/components/VCalendar/timeGrid.js +71 -0
- package/dist/components/VCalendar/timeGrid.js.map +1 -0
- package/dist/components/VCalendar/types.d.ts +41 -16
- package/dist/components/VCarousel/VCarousel.css +1 -1
- package/dist/components/VCarousel/VCarousel.d.ts +21 -41
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +157 -129
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.d.ts +0 -17
- 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 +0 -12
- package/dist/components/VCarousel/context.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.css +1 -1
- package/dist/components/VCheckbox/VCheckbox.d.ts +31 -16
- package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +62 -29
- 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 +36 -26
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +57 -48
- 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 +127 -31
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +336 -247
- 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.d.ts +0 -9
- 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/VComboboxOption.css +1 -1
- package/dist/components/VCombobox/VComboboxOption.d.ts +0 -15
- package/dist/components/VCombobox/VComboboxOption.js.map +1 -1
- package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxSeparator.js.map +1 -1
- package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.css +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +50 -28
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +183 -194
- 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 +90 -42
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +159 -191
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.css +1 -1
- package/dist/components/VDatePicker/VDatePicker.d.ts +61 -30
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +243 -221
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDialog/VDialog.css +1 -1
- package/dist/components/VDialog/VDialog.d.ts +45 -33
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +65 -64
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDialog/VDialogAlert.d.ts +24 -20
- package/dist/components/VDialog/VDialogAlert.js.map +1 -1
- package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js +36 -25
- package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.css +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +78 -26
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +144 -132
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.css +1 -1
- package/dist/components/VFilePicker/VFilePicker.d.ts +47 -30
- package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +166 -163
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/fileKind.d.ts +2 -27
- package/dist/components/VFilePicker/fileKind.js +9 -8
- package/dist/components/VFilePicker/fileKind.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.css +1 -1
- package/dist/components/VHotkeys/VHotkeys.d.ts +8 -30
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VIcon/VIcon.css +1 -1
- package/dist/components/VIcon/VIcon.d.ts +15 -6
- package/dist/components/VIcon/VIcon.js.map +1 -1
- package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js +45 -37
- package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VIcon/iconProps.js.map +1 -1
- package/dist/components/VIcon/icons/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.map +1 -1
- package/dist/components/VIcon/types.d.ts +12 -12
- package/dist/components/VIconButton/VIconButton.css +1 -1
- package/dist/components/VIconButton/VIconButton.d.ts +28 -11
- package/dist/components/VIconButton/VIconButton.js.map +1 -1
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js +26 -20
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInput.css +1 -1
- package/dist/components/VInput/VInput.d.ts +26 -7
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +86 -75
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.css +2 -0
- package/dist/components/VInput/VInputGroup.d.ts +47 -0
- package/dist/components/VInput/VInputGroup.js +9 -0
- package/dist/components/VInput/VInputGroup.js.map +1 -0
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +80 -0
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VInput/context.d.ts +11 -0
- package/dist/components/VInput/context.js +6 -0
- package/dist/components/VInput/context.js.map +1 -0
- package/dist/components/VInputOTP/VInputOTP.css +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +37 -22
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +98 -61
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenu.d.ts +23 -30
- package/dist/components/VMenu/VMenu.js.map +1 -1
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js +31 -27
- 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.d.ts +12 -8
- package/dist/components/VMenu/VMenuGroup.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js +11 -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 +11 -19
- package/dist/components/VMenu/VMenuItem.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +65 -57
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.d.ts +14 -30
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +43 -44
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/context.d.ts +2 -6
- package/dist/components/VMenu/context.js.map +1 -1
- package/dist/components/VPagination/VPagination.css +1 -1
- package/dist/components/VPagination/VPagination.d.ts +60 -22
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +113 -161
- 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 +26 -7
- package/dist/components/VPopover/VPopover.js.map +1 -1
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js +43 -37
- 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 +15 -4
- package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js +32 -30
- 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 +23 -8
- package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js +34 -32
- 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.d.ts +36 -18
- package/dist/components/VRadio/VRadio.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +55 -26
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.d.ts +4 -2
- 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.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.d.ts +5 -19
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +5 -5
- 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.d.ts +9 -17
- package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js +2 -2
- 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 +22 -30
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +77 -87
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/context.d.ts +0 -14
- package/dist/components/VSideNavigation/context.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.css +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +9 -9
- package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSlider/VSlider.css +1 -1
- package/dist/components/VSlider/VSlider.d.ts +52 -27
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +202 -118
- 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 +0 -23
- package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +38 -38
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/state.d.ts +0 -27
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.css +1 -1
- package/dist/components/VSpinner/VSpinner.d.ts +6 -6
- package/dist/components/VSpinner/VSpinner.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js +10 -9
- 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 +35 -13
- package/dist/components/VSwitch/VSwitch.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +62 -21
- 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 +12 -12
- package/dist/components/VTabs/VTab.js.map +1 -1
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +36 -24
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabPanel.d.ts +2 -10
- package/dist/components/VTabs/VTabPanel.js.map +1 -1
- package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabs.css +1 -1
- package/dist/components/VTabs/VTabs.d.ts +27 -18
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +92 -81
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/context.d.ts +9 -19
- package/dist/components/VTabs/context.js +1 -1
- package/dist/components/VTabs/context.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.css +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +34 -6
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +91 -74
- 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 +123 -33
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +294 -291
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/search.d.ts +24 -0
- package/dist/components/VTimeInput/search.js +22 -0
- package/dist/components/VTimeInput/search.js.map +1 -0
- package/dist/components/VTimePicker/VTimePicker.css +1 -1
- package/dist/components/VTimePicker/VTimePicker.d.ts +51 -30
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +216 -151
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/limits.d.ts +88 -0
- package/dist/components/VTimePicker/limits.js +70 -0
- package/dist/components/VTimePicker/limits.js.map +1 -0
- package/dist/components/VToast/VToast.css +1 -1
- package/dist/components/VToast/VToast.d.ts +0 -10
- package/dist/components/VToast/VToast.js.map +1 -1
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +10 -10
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/VToaster.css +1 -1
- package/dist/components/VToast/VToaster.d.ts +0 -17
- package/dist/components/VToast/VToaster.js.map +1 -1
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +29 -28
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/state.d.ts +10 -19
- package/dist/components/VToast/state.js +1 -1
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/components/VToggle/VToggle.css +1 -1
- package/dist/components/VToggle/VToggle.d.ts +56 -11
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +80 -46
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.d.ts +16 -2
- package/dist/components/VToggle/VToggleItem.js +0 -2
- package/dist/components/VToggle/VToggleItem.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js +23 -15
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/context.d.ts +4 -24
- package/dist/components/VToggle/context.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.css +1 -1
- package/dist/components/VTooltip/VTooltip.d.ts +9 -33
- package/dist/components/VTooltip/VTooltip.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +28 -24
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTypography/VTypography.js.map +1 -1
- package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/useAriaLabel.d.ts +0 -17
- package/dist/composables/useAriaLabel.js.map +1 -1
- package/dist/composables/useClearable.d.ts +42 -0
- package/dist/composables/useClearable.js +19 -0
- package/dist/composables/useClearable.js.map +1 -0
- package/dist/composables/useControlShape.d.ts +22 -0
- package/dist/composables/useControlShape.js +13 -0
- package/dist/composables/useControlShape.js.map +1 -0
- package/dist/composables/useDetailsOpen.d.ts +22 -0
- package/dist/composables/useDetailsOpen.js +21 -0
- package/dist/composables/useDetailsOpen.js.map +1 -0
- package/dist/composables/useFieldIds.d.ts +0 -15
- package/dist/composables/useFieldIds.js +8 -7
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.d.ts +21 -18
- package/dist/composables/useFieldPanel.js +43 -23
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileDrop.d.ts +0 -22
- package/dist/composables/useFileDrop.js.map +1 -1
- package/dist/composables/useFileField.d.ts +45 -17
- package/dist/composables/useFileField.js +40 -20
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useFocusoutDismiss.d.ts +0 -15
- package/dist/composables/useFocusoutDismiss.js.map +1 -1
- package/dist/composables/useIconClickHandlers.d.ts +29 -14
- package/dist/composables/useIconClickHandlers.js +15 -5
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/composables/useInertLink.d.ts +20 -0
- package/dist/composables/useInertLink.js +23 -0
- package/dist/composables/useInertLink.js.map +1 -0
- package/dist/composables/useMaskedField.d.ts +33 -10
- package/dist/composables/useMaskedField.js +45 -1
- package/dist/composables/useMaskedField.js.map +1 -1
- package/dist/composables/usePopover.d.ts +15 -1
- package/dist/composables/usePopover.js +23 -8
- package/dist/composables/usePopover.js.map +1 -1
- package/dist/composables/useProgressValue.d.ts +5 -8
- package/dist/composables/useProgressValue.js +6 -3
- package/dist/composables/useProgressValue.js.map +1 -1
- package/dist/composables/useRootAttrs.d.ts +12 -19
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useTimer.d.ts +8 -10
- package/dist/composables/useTimer.js.map +1 -1
- package/dist/i18n/en.d.ts +2 -10
- package/dist/i18n/en.js +18 -19
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.d.ts +2 -18
- package/dist/i18n/fr.js +18 -19
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.d.ts +44 -47
- package/dist/i18n/state.js +20 -17
- package/dist/i18n/state.js.map +1 -1
- package/dist/i18n/types.d.ts +53 -44
- package/dist/icons.d.ts +3 -3
- package/dist/index.d.ts +43 -17
- package/dist/index.js +45 -44
- package/dist/styles.css +1 -1
- package/dist/tokens/css.js.map +1 -1
- package/dist/tokens/index.d.ts +5 -1
- package/dist/tokens/primitives.js.map +1 -1
- package/dist/tokens/semantic.d.ts +5 -1
- package/dist/tokens/semantic.js +6 -2
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens/types.d.ts +7 -15
- package/dist/tokens/types.js.map +1 -1
- package/dist/tokens.json +25 -5
- package/dist/types.d.ts +24 -0
- package/dist/utils/array.d.ts +3 -6
- package/dist/utils/array.js.map +1 -1
- package/dist/utils/arrowNav.d.ts +17 -17
- package/dist/utils/arrowNav.js +5 -3
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/chip.d.ts +11 -15
- package/dist/utils/chip.js.map +1 -1
- package/dist/utils/css.d.ts +22 -11
- package/dist/utils/css.js +5 -2
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/date.d.ts +42 -19
- package/dist/utils/date.js +97 -98
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/direction.d.ts +6 -12
- package/dist/utils/direction.js.map +1 -1
- package/dist/utils/env.d.ts +5 -6
- package/dist/utils/file.d.ts +46 -58
- package/dist/utils/file.js +43 -36
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/focus.d.ts +8 -17
- package/dist/utils/focus.js.map +1 -1
- package/dist/utils/hostWarns.d.ts +12 -0
- package/dist/utils/hostWarns.js +6 -0
- package/dist/utils/hostWarns.js.map +1 -0
- package/dist/utils/ids.d.ts +10 -0
- package/dist/utils/ids.js +8 -0
- package/dist/utils/ids.js.map +1 -0
- package/dist/utils/matcher.d.ts +5 -9
- package/dist/utils/matcher.js.map +1 -1
- package/dist/utils/memo.d.ts +12 -0
- package/dist/utils/memo.js +9 -0
- package/dist/utils/memo.js.map +1 -0
- package/dist/utils/number.d.ts +3 -4
- package/dist/utils/number.js.map +1 -1
- package/dist/utils/text.d.ts +20 -9
- package/dist/utils/text.js +17 -3
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/time.d.ts +53 -61
- package/dist/utils/time.js +49 -45
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/vnode.d.ts +8 -15
- package/dist/utils/vnode.js.map +1 -1
- package/package.json +6 -6
- package/dist/components/VToggle/VToggleItem.css +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VToaster.js","names":[],"sources":["../../../src/components/VToast/VToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport VToast from './VToast.vue'\nimport { dismissToast, toasts, type ToastItem, type ToastPlacement } from './state'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n// @a11y @core\n/**\n * Where notifications appear. It is mounted ONCE, at the root of the application, and\n * from then on any call to `toast()` shows up here.\n *\n * It renders one container per corner of the screen rather than one floating box per\n * notification, and each container simply stacks its notifications with CSS. The\n * reason is that these boxes are drawn above the whole page, at fixed coordinates and\n * anchored to nothing: individually they would all land on the same spot, and keeping\n * them apart would mean measuring and offsetting each one in code. The six containers\n * exist at all times — empty, they cost nothing and the browser does not display them.\n *\n * The JavaScript covers three things the platform does not: putting the queue and the\n * containers in step, since drawing above the page can only be asked for by calling a\n * method; the timers that dismiss a notification when its time is up; and pausing\n * those timers while the pointer rests on a stack, so that something disappearing on a\n * clock can be held in place long enough to read (WCAG 2.2.1).\n */\ninterface ToasterProps {\n /** Which corner notifications appear in, unless one of them asks for another. */\n placement?: ToastPlacement\n /**\n * How long a notification stays, in milliseconds, unless it asks for something else.\n * A notification given 0 stays until it is dismissed.\n */\n duration?: number\n /** What the close cross does, in words. It falls back to the design system dictionary. */\n closeLabel?: string\n /**\n * What screen readers announce for the notification areas themselves, which are\n * landmarks of the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToasterProps>(), {\n placement: 'bottom-right',\n duration: 5000,\n closeLabel: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.toaster.label)\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? m.value.common.close)\n\nconst PLACEMENTS: ToastPlacement[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n]\n\n/**\n * The queue sorted into the corner each notification actually belongs to: the one it\n * asked for, or failing that the one set on this component.\n */\nconst groups = computed(() => {\n const map = new Map<ToastPlacement, ToastItem[]>()\n for (const item of toasts) {\n const placement = item.placement ?? props.placement\n const list = map.get(placement)\n if (list) list.push(item)\n else map.set(placement, [item])\n }\n return map\n})\n\n/*\n * One instance of the popover plumbing PER corner. Each carries its own element, its\n * own open state — fed by that element's events — and the guards that make opening and\n * closing safe to call twice: asking the browser to show an already-shown popover\n * throws.\n *\n * The containers being permanent, this runs once and never again.\n */\nconst stacks = new Map(\n PLACEMENTS.map((placement) => {\n const el = ref<HTMLElement | null>(null)\n return [placement, { el, ...usePopover(el) }] as const\n }),\n)\n\nfunction setStackEl(placement: ToastPlacement, el: unknown) {\n const stack = stacks.get(placement)\n if (stack) stack.el.value = (el as HTMLElement | null) ?? null\n}\n\nfunction syncStack(placement: ToastPlacement, event: Event) {\n stacks.get(placement)?.syncShown(event)\n}\n\n/* The running countdowns, and the notifications already given one — a notification is\n armed once and only once, however many times the queue changes around it. */\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\nconst armed = new Set<number>()\n/* The corners the pointer is currently resting on, whose countdowns are suspended. */\nconst paused = new Set<ToastPlacement>()\n\nfunction effectivePlacement(item: ToastItem): ToastPlacement {\n return item.placement ?? props.placement\n}\n\nfunction startTimer(item: ToastItem) {\n const duration = item.duration ?? props.duration\n if (duration <= 0 || timers.has(item.id)) return\n timers.set(\n item.id,\n setTimeout(() => {\n timers.delete(item.id)\n dismissToast(item.id)\n }, duration),\n )\n}\n\n// @core\n/**\n * Brings the page into line with the queue: it starts the countdown of every\n * notification that has just arrived, throws away the countdowns of those that have\n * gone, and then shows the corners holding something while hiding the empty ones.\n *\n * It runs once on mount — which is what makes a notification raised before this\n * component existed appear all the same — and after that on every change to the queue.\n */\nfunction sync() {\n const alive = new Set(toasts.map((item) => item.id))\n for (const [id, timer] of timers) {\n if (!alive.has(id)) {\n clearTimeout(timer)\n timers.delete(id)\n }\n }\n for (const id of armed) if (!alive.has(id)) armed.delete(id)\n for (const item of toasts) {\n if (armed.has(item.id)) continue\n armed.add(item.id)\n if (!paused.has(effectivePlacement(item))) startTimer(item)\n }\n\n // Showing and hiding are safe to call on a container already in that state, the\n // guards living in the popover plumbing — so there is no need to remember which\n // corners are currently open.\n for (const placement of PLACEMENTS) {\n const stack = stacks.get(placement)\n if (!stack?.el.value) continue\n if ((groups.value.get(placement)?.length ?? 0) > 0) stack.show()\n else stack.hide()\n }\n}\n\n/*\n * Watching the grouped queue covers a change to the notifications AND a change to the\n * default corner. The `post` timing is load-bearing: the notification must already be\n * in the page before its container is told to show itself.\n */\nwatch(groups, sync, { flush: 'post' })\n// @ssr — a watcher does not run during the server render, so a notification raised\n// before this component mounted would never be picked up. Running the same\n// synchronization on mount is what brings it in.\nonMounted(sync)\n\n// @a11y — WCAG 2.2.1: something that disappears on a clock has to be holdable, or a\n// slow reader simply never finishes it.\n/*\n * Resting the pointer on a corner suspends its countdowns. Leaving restarts them from\n * the FULL duration rather than from what was left: simpler, and more generous to the\n * reader who has just interrupted themselves.\n */\nfunction pause(placement: ToastPlacement) {\n paused.add(placement)\n for (const item of toasts) {\n if (effectivePlacement(item) !== placement) continue\n const timer = timers.get(item.id)\n if (timer !== undefined) {\n clearTimeout(timer)\n timers.delete(item.id)\n }\n }\n}\n\nfunction resume(placement: ToastPlacement) {\n paused.delete(placement)\n for (const item of toasts) {\n if (effectivePlacement(item) === placement) startTimer(item)\n }\n}\n\n/* The queue lives outside this component and survives it being unmounted and mounted\n again; only the countdowns are cleared here, and they are started afresh next time. */\nonBeforeUnmount(() => {\n for (const timer of timers.values()) clearTimeout(timer)\n timers.clear()\n})\n</script>\n\n<template>\n <div\n v-for=\"p in PLACEMENTS\"\n :key=\"p\"\n :ref=\"(el) => setStackEl(p, el)\"\n class=\"v-overlay v-toast-stack\"\n popover=\"manual\"\n :data-placement=\"p\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"pause(p)\"\n @pointerleave=\"resume(p)\"\n @beforetoggle=\"syncStack(p, $event)\"\n @toggle=\"syncStack(p, $event)\"\n >\n <VToast\n v-for=\"item in groups.get(p) ?? []\"\n :key=\"item.id\"\n :item=\"item\"\n :close-label=\"resolvedCloseLabel\"\n @close=\"dismissToast($event)\"\n />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The fixed positioning and the guard hiding a closed container come from the shared\n `.v-overlay` class, set on this same element. What stays here is the browser's own\n popover decoration to undo — its border, its padding, its opaque background — and\n the stack's own layout. */\n .v-toast-stack {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n }\n\n /*\n * These coordinates are deliberately PHYSICAL rather than logical: a notification\n * appears at a place on the screen, and that place does not flip with the reading\n * direction — the operating system's own notifications behave the same way.\n */\n .v-toast-stack[data-placement^='top-'] {\n top: var(--vectis-space-4);\n /* The newest notification should sit nearest the screen edge. The queue only ever\n grows at the end, so the stack is simply drawn in reverse. */\n flex-direction: column-reverse;\n /* The direction each card slides in from, read by `.v-banner` in styles/banner.css\n — the stack is the only thing that knows which edge of the screen it sits on. */\n --banner-enter-y: calc(-1 * var(--vectis-space-4));\n }\n\n .v-toast-stack[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The stack fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-toast-stack {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-toast-stack:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-toast-stack:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-toast-stack {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VToaster.js","names":[],"sources":["../../../src/components/VToast/VToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Where notifications appear: mounted ONCE at the root, after which every `toast()` call\n * shows up here.\n *\n * One container per placement rather than one popover per notification, each stacking its\n * own with CSS. These boxes are drawn above the page at physical coordinates, anchored to\n * nothing, so individually they would all land on the same spot and keeping them apart would\n * mean measuring and offsetting each in code. The six exist at all times; empty, they cost\n * nothing and are not displayed.\n *\n * The JS covers three things the platform does not: keeping the queue and the containers in\n * step, the popover being imperative; the per-notification dismissal timers; and holding\n * them while the pointer rests on a stack OR the keyboard is inside it, so something that\n * disappears on a clock can be read and closed (WCAG 2.2.1).\n */\n\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport VToast from './VToast.vue'\nimport { dismissToast, toasts, type ToastItem, type ToastPlacement } from './state'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\ninterface ToasterProps {\n /** Which corner notifications appear in, unless one of them asks for another. */\n placement?: ToastPlacement\n /**\n * How long a notification stays, in milliseconds, unless it asks for something else.\n * A notification given 0 stays until it is dismissed.\n */\n duration?: number\n /** What the close cross does, in words. It falls back to the design system dictionary. */\n closeLabel?: string\n /**\n * What screen readers announce for the notification areas themselves, which are\n * landmarks of the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToasterProps>(), {\n placement: 'bottom-right',\n duration: 5000,\n closeLabel: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.toaster.label)\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? m.value.common.close)\n\nconst PLACEMENTS: ToastPlacement[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n]\n\n/**\n * The queue sorted into the corner each notification actually belongs to: the one it\n * asked for, or failing that the one set on this component.\n */\nconst groups = computed(() => {\n const map = new Map<ToastPlacement, ToastItem[]>()\n for (const item of toasts) {\n const placement = effectivePlacement(item)\n const list = map.get(placement)\n if (list) list.push(item)\n else map.set(placement, [item])\n }\n return map\n})\n\n/* The six containers, collected by the `v-for` itself. A plain template ref rather than a\n function one: a function written inline in the template is a new function on every\n render, which Vue answers by calling the old one with null and the new one with the\n element — twelve calls each time a notification comes or goes. */\nconst stackEls = ref<HTMLElement[]>([])\n\n/*\n * One instance of the popover plumbing PER corner. Each carries its own element, its\n * own open state — fed by that element's events — and the guards that make opening and\n * closing safe to call twice: asking the browser to show an already-shown popover\n * throws.\n *\n * The element is looked up by its placement rather than by its index: Vue does not\n * promise that a `v-for` ref array follows the order of the list.\n *\n * The containers being permanent, this runs once and never again.\n */\nconst stacks = new Map(\n PLACEMENTS.map((placement) => {\n const el = computed(\n () => stackEls.value.find((stack) => stack.dataset.placement === placement) ?? null,\n )\n return [placement, { el, ...usePopover(el) }] as const\n }),\n)\n\nfunction syncStack(placement: ToastPlacement, event: Event) {\n stacks.get(placement)?.syncShown(event)\n}\n\n/* The running countdowns, one per notification. */\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\n/* The corners holding their countdowns, one set per reason. Two sets rather than one\n because the reasons overlap and end independently: a reader tabs to a close cross, then\n moves the pointer over the stack and away, and the corner must stay held while the cross\n still has the focus — VSnackbar's two flags, per corner. */\nconst hovered = new Set<ToastPlacement>()\nconst focused = new Set<ToastPlacement>()\n\nfunction effectivePlacement(item: ToastItem): ToastPlacement {\n return item.placement ?? props.placement\n}\n\nconst isHeld = (placement: ToastPlacement) => hovered.has(placement) || focused.has(placement)\n\nfunction stopTimer(id: number) {\n const timer = timers.get(id)\n if (timer === undefined) return\n clearTimeout(timer)\n timers.delete(id)\n}\n\nfunction startTimer(item: ToastItem) {\n const duration = item.duration ?? props.duration\n if (duration <= 0 || timers.has(item.id)) return\n timers.set(\n item.id,\n setTimeout(() => {\n timers.delete(item.id)\n dismissToast(item.id)\n }, duration),\n )\n}\n\n// @core\n/**\n * Brings the page into line with the queue: it throws away the countdowns of the\n * notifications that have gone, gives every other one a countdown exactly when its corner\n * is not held, and then shows the corners holding something while hiding the empty ones.\n *\n * TRAP — the countdown follows the corner a notification is in NOW, re-derived on every\n * pass rather than remembered per notification. The default corner is a prop, so a\n * notification can change corner while the pointer rests on the old one: a memory of\n * \"already armed\" then outlived the countdown the hold had cancelled, the release looked\n * in the old corner, and the notification never got a countdown again.\n *\n * It runs once on mount — which is what makes a notification raised before this\n * component existed appear all the same — and after that on every change to the queue.\n */\nfunction sync() {\n const alive = new Set(toasts.map((item) => item.id))\n for (const id of timers.keys()) if (!alive.has(id)) stopTimer(id)\n\n for (const placement of PLACEMENTS) {\n const stackEl = stacks.get(placement)?.el.value\n // TRAP — a hold is dropped here as soon as it can no longer be true, rather than\n // trusted to its closing event. A close cross leaves the page while it has the focus,\n // and an engine that sends no `focusout` for a removed element would keep the corner\n // held for good; an emptied stack is hidden under the pointer, with no `pointerleave`.\n if (!stackEl?.contains(document.activeElement)) focused.delete(placement)\n if ((groups.value.get(placement)?.length ?? 0) === 0) hovered.delete(placement)\n }\n\n for (const item of toasts) {\n if (isHeld(effectivePlacement(item))) stopTimer(item.id)\n else startTimer(item)\n }\n\n // Showing and hiding are safe to call on a container already in that state, the\n // guards living in the popover plumbing — so there is no need to remember which\n // corners are currently open.\n for (const placement of PLACEMENTS) {\n const stack = stacks.get(placement)\n if (!stack?.el.value) continue\n if ((groups.value.get(placement)?.length ?? 0) > 0) stack.show()\n else stack.hide()\n }\n}\n\n/*\n * Watching the grouped queue covers a change to the notifications AND a change to the\n * default corner. The `post` timing is load-bearing: the notification must already be\n * in the page before its container is told to show itself.\n */\nwatch(groups, sync, { flush: 'post' })\n// @ssr — a watcher does not run during the server render, so a notification raised\n// before this component mounted would never be picked up. Running the same\n// synchronization on mount is what brings it in.\nonMounted(sync)\n\n// @a11y — WCAG 2.2.1: something that disappears on a clock has to be holdable, or a\n// slow reader simply never finishes it.\n/*\n * Resting the pointer on a corner suspends its countdowns, and so does moving the keyboard\n * into it: each notification carries a close cross, a real button a reader tabs to, which\n * would otherwise vanish from under the focus ring. The same verbs and the same two\n * reasons as VSnackbar.\n *\n * Leaving restarts them from the FULL duration rather than from what was left: simpler,\n * and more generous to the reader who has just interrupted themselves.\n */\nfunction hold(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.add(placement)\n for (const item of groups.value.get(placement) ?? []) stopTimer(item.id)\n}\n\nfunction release(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.delete(placement)\n if (isHeld(placement)) return\n for (const item of groups.value.get(placement) ?? []) startTimer(item)\n}\n\n/* The queue lives outside this component and survives it being unmounted and mounted\n again; only the countdowns are cleared here, and they are started afresh next time. */\nonBeforeUnmount(() => {\n for (const timer of timers.values()) clearTimeout(timer)\n timers.clear()\n})\n</script>\n\n<template>\n <div\n v-for=\"p in PLACEMENTS\"\n :key=\"p\"\n ref=\"stackEls\"\n class=\"v-overlay v-toast-stack\"\n popover=\"manual\"\n :data-placement=\"p\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"hold(p, 'pointer')\"\n @pointerleave=\"release(p, 'pointer')\"\n @focusin=\"hold(p, 'focus')\"\n @focusout=\"release(p, 'focus')\"\n @beforetoggle=\"syncStack(p, $event)\"\n @toggle=\"syncStack(p, $event)\"\n >\n <VToast\n v-for=\"item in groups.get(p) ?? []\"\n :key=\"item.id\"\n :item=\"item\"\n :close-label=\"resolvedCloseLabel\"\n @close=\"dismissToast($event)\"\n />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The container. Its rules are an ALIGNED COPY of VSnackbar's host: the two containers are the\n * same object, a popover at a physical corner that fades when it empties, and the copy is\n * kept line for line, the one difference being the top edge, which a confirmation never takes.\n * They are not factored into styles/banner.css because that sheet is paid for by every\n * consumer, and these rules cost more than the size gate allows; change one, change the other.\n *\n * The fixed positioning and the guard hiding a closed container come from the shared\n * `.v-overlay` class, set on this same element. What is undone here is the browser's own\n * popover decoration: its border, its padding, its opaque background.\n */\n .v-toast-stack {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * PHYSICAL coordinates rather than logical ones: a message appears at a place on the\n * screen, and that place does not flip with the reading direction — the operating\n * system's own notifications behave the same way.\n *\n * `--banner-enter-y` is the direction each card slides in from, read by `.v-banner` in\n * styles/banner.css: the container is the only thing that knows which edge of the screen\n * it sits on. That sheet reads it with a `, 0` fallback, so dropping the declaration costs\n * the slide and nothing else, and nothing reports it.\n */\n .v-toast-stack[data-placement^='top-'] {\n top: var(--vectis-space-4);\n --banner-enter-y: calc(-1 * var(--vectis-space-4));\n }\n\n .v-toast-stack[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-toast-stack {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-toast-stack:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-toast-stack:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-toast-stack {\n transition: none;\n }\n }\n\n /* The stack's own layout, which the snackbar's single bar has no use for. The newest\n notification should sit nearest the screen edge; the queue only ever grows at the end,\n so a stack along the top is simply drawn in reverse. */\n .v-toast-stack {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n }\n\n .v-toast-stack[data-placement^='top-'] {\n flex-direction: column-reverse;\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -10,6 +10,8 @@ var y = [
|
|
|
10
10
|
"aria-label",
|
|
11
11
|
"onPointerenter",
|
|
12
12
|
"onPointerleave",
|
|
13
|
+
"onFocusin",
|
|
14
|
+
"onFocusout",
|
|
13
15
|
"onBeforetoggle",
|
|
14
16
|
"onToggle"
|
|
15
17
|
], b = /*@__PURE__*/ d({
|
|
@@ -31,56 +33,52 @@ var y = [
|
|
|
31
33
|
], T = s(() => {
|
|
32
34
|
let e = /* @__PURE__ */ new Map();
|
|
33
35
|
for (let t of a) {
|
|
34
|
-
let n = t
|
|
36
|
+
let n = M(t), r = e.get(n);
|
|
35
37
|
r ? r.push(t) : e.set(n, [t]);
|
|
36
38
|
}
|
|
37
39
|
return e;
|
|
38
|
-
}), E = new Map(w.map((e) => {
|
|
39
|
-
let n =
|
|
40
|
+
}), E = h([]), D = new Map(w.map((e) => {
|
|
41
|
+
let n = s(() => E.value.find((t) => t.dataset.placement === e) ?? null);
|
|
40
42
|
return [e, {
|
|
41
43
|
el: n,
|
|
42
44
|
...t(n)
|
|
43
45
|
}];
|
|
44
46
|
}));
|
|
45
|
-
function D(e, t) {
|
|
46
|
-
let n = E.get(e);
|
|
47
|
-
n && (n.el.value = t ?? null);
|
|
48
|
-
}
|
|
49
47
|
function O(e, t) {
|
|
50
|
-
|
|
48
|
+
D.get(e)?.syncShown(t);
|
|
51
49
|
}
|
|
52
50
|
let k = /* @__PURE__ */ new Map(), A = /* @__PURE__ */ new Set(), j = /* @__PURE__ */ new Set();
|
|
53
51
|
function M(e) {
|
|
54
52
|
return e.placement ?? b.placement;
|
|
55
53
|
}
|
|
56
|
-
|
|
54
|
+
let N = (e) => A.has(e) || j.has(e);
|
|
55
|
+
function P(e) {
|
|
56
|
+
let t = k.get(e);
|
|
57
|
+
t !== void 0 && (clearTimeout(t), k.delete(e));
|
|
58
|
+
}
|
|
59
|
+
function F(e) {
|
|
57
60
|
let t = e.duration ?? b.duration;
|
|
58
61
|
t <= 0 || k.has(e.id) || k.set(e.id, setTimeout(() => {
|
|
59
62
|
k.delete(e.id), i(e.id);
|
|
60
63
|
}, t));
|
|
61
64
|
}
|
|
62
|
-
function
|
|
65
|
+
function I() {
|
|
63
66
|
let e = new Set(a.map((e) => e.id));
|
|
64
|
-
for (let
|
|
65
|
-
for (let
|
|
66
|
-
for (let e of a)
|
|
67
|
+
for (let t of k.keys()) e.has(t) || P(t);
|
|
68
|
+
for (let e of w) (D.get(e)?.el.value)?.contains(document.activeElement) || j.delete(e), (T.value.get(e)?.length ?? 0) === 0 && A.delete(e);
|
|
69
|
+
for (let e of a) N(M(e)) ? P(e.id) : F(e);
|
|
67
70
|
for (let e of w) {
|
|
68
|
-
let t =
|
|
71
|
+
let t = D.get(e);
|
|
69
72
|
t?.el.value && ((T.value.get(e)?.length ?? 0) > 0 ? t.show() : t.hide());
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
|
-
v(T,
|
|
73
|
-
function
|
|
74
|
-
j.add(e);
|
|
75
|
-
for (let t of
|
|
76
|
-
if (M(t) !== e) continue;
|
|
77
|
-
let n = k.get(t.id);
|
|
78
|
-
n !== void 0 && (clearTimeout(n), k.delete(t.id));
|
|
79
|
-
}
|
|
75
|
+
v(T, I, { flush: "post" }), p(I);
|
|
76
|
+
function L(e, t) {
|
|
77
|
+
(t === "pointer" ? A : j).add(e);
|
|
78
|
+
for (let t of T.value.get(e) ?? []) P(t.id);
|
|
80
79
|
}
|
|
81
|
-
function
|
|
82
|
-
j.delete(e);
|
|
83
|
-
for (let t of a) M(t) === e && N(t);
|
|
80
|
+
function R(e, t) {
|
|
81
|
+
if ((t === "pointer" ? A : j).delete(e), !N(e)) for (let t of T.value.get(e) ?? []) F(t);
|
|
84
82
|
}
|
|
85
83
|
return f(() => {
|
|
86
84
|
for (let e of k.values()) clearTimeout(e);
|
|
@@ -88,14 +86,17 @@ var y = [
|
|
|
88
86
|
}), (e, t) => (m(), l(o, null, g(w, (e) => u("div", {
|
|
89
87
|
key: e,
|
|
90
88
|
ref_for: !0,
|
|
91
|
-
|
|
89
|
+
ref_key: "stackEls",
|
|
90
|
+
ref: E,
|
|
92
91
|
class: "v-overlay v-toast-stack",
|
|
93
92
|
popover: "manual",
|
|
94
93
|
"data-placement": e,
|
|
95
94
|
role: "region",
|
|
96
95
|
"aria-label": _(S),
|
|
97
|
-
onPointerenter: (t) =>
|
|
98
|
-
onPointerleave: (t) =>
|
|
96
|
+
onPointerenter: (t) => L(e, "pointer"),
|
|
97
|
+
onPointerleave: (t) => R(e, "pointer"),
|
|
98
|
+
onFocusin: (t) => L(e, "focus"),
|
|
99
|
+
onFocusout: (t) => R(e, "focus"),
|
|
99
100
|
onBeforetoggle: (t) => O(e, t),
|
|
100
101
|
onToggle: (t) => O(e, t)
|
|
101
102
|
}, [(m(!0), l(o, null, g(T.value.get(e) ?? [], (e) => (m(), c(r, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VToaster.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VToast/VToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport VToast from './VToast.vue'\nimport { dismissToast, toasts, type ToastItem, type ToastPlacement } from './state'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n// @a11y @core\n/**\n * Where notifications appear. It is mounted ONCE, at the root of the application, and\n * from then on any call to `toast()` shows up here.\n *\n * It renders one container per corner of the screen rather than one floating box per\n * notification, and each container simply stacks its notifications with CSS. The\n * reason is that these boxes are drawn above the whole page, at fixed coordinates and\n * anchored to nothing: individually they would all land on the same spot, and keeping\n * them apart would mean measuring and offsetting each one in code. The six containers\n * exist at all times — empty, they cost nothing and the browser does not display them.\n *\n * The JavaScript covers three things the platform does not: putting the queue and the\n * containers in step, since drawing above the page can only be asked for by calling a\n * method; the timers that dismiss a notification when its time is up; and pausing\n * those timers while the pointer rests on a stack, so that something disappearing on a\n * clock can be held in place long enough to read (WCAG 2.2.1).\n */\ninterface ToasterProps {\n /** Which corner notifications appear in, unless one of them asks for another. */\n placement?: ToastPlacement\n /**\n * How long a notification stays, in milliseconds, unless it asks for something else.\n * A notification given 0 stays until it is dismissed.\n */\n duration?: number\n /** What the close cross does, in words. It falls back to the design system dictionary. */\n closeLabel?: string\n /**\n * What screen readers announce for the notification areas themselves, which are\n * landmarks of the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToasterProps>(), {\n placement: 'bottom-right',\n duration: 5000,\n closeLabel: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.toaster.label)\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? m.value.common.close)\n\nconst PLACEMENTS: ToastPlacement[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n]\n\n/**\n * The queue sorted into the corner each notification actually belongs to: the one it\n * asked for, or failing that the one set on this component.\n */\nconst groups = computed(() => {\n const map = new Map<ToastPlacement, ToastItem[]>()\n for (const item of toasts) {\n const placement = item.placement ?? props.placement\n const list = map.get(placement)\n if (list) list.push(item)\n else map.set(placement, [item])\n }\n return map\n})\n\n/*\n * One instance of the popover plumbing PER corner. Each carries its own element, its\n * own open state — fed by that element's events — and the guards that make opening and\n * closing safe to call twice: asking the browser to show an already-shown popover\n * throws.\n *\n * The containers being permanent, this runs once and never again.\n */\nconst stacks = new Map(\n PLACEMENTS.map((placement) => {\n const el = ref<HTMLElement | null>(null)\n return [placement, { el, ...usePopover(el) }] as const\n }),\n)\n\nfunction setStackEl(placement: ToastPlacement, el: unknown) {\n const stack = stacks.get(placement)\n if (stack) stack.el.value = (el as HTMLElement | null) ?? null\n}\n\nfunction syncStack(placement: ToastPlacement, event: Event) {\n stacks.get(placement)?.syncShown(event)\n}\n\n/* The running countdowns, and the notifications already given one — a notification is\n armed once and only once, however many times the queue changes around it. */\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\nconst armed = new Set<number>()\n/* The corners the pointer is currently resting on, whose countdowns are suspended. */\nconst paused = new Set<ToastPlacement>()\n\nfunction effectivePlacement(item: ToastItem): ToastPlacement {\n return item.placement ?? props.placement\n}\n\nfunction startTimer(item: ToastItem) {\n const duration = item.duration ?? props.duration\n if (duration <= 0 || timers.has(item.id)) return\n timers.set(\n item.id,\n setTimeout(() => {\n timers.delete(item.id)\n dismissToast(item.id)\n }, duration),\n )\n}\n\n// @core\n/**\n * Brings the page into line with the queue: it starts the countdown of every\n * notification that has just arrived, throws away the countdowns of those that have\n * gone, and then shows the corners holding something while hiding the empty ones.\n *\n * It runs once on mount — which is what makes a notification raised before this\n * component existed appear all the same — and after that on every change to the queue.\n */\nfunction sync() {\n const alive = new Set(toasts.map((item) => item.id))\n for (const [id, timer] of timers) {\n if (!alive.has(id)) {\n clearTimeout(timer)\n timers.delete(id)\n }\n }\n for (const id of armed) if (!alive.has(id)) armed.delete(id)\n for (const item of toasts) {\n if (armed.has(item.id)) continue\n armed.add(item.id)\n if (!paused.has(effectivePlacement(item))) startTimer(item)\n }\n\n // Showing and hiding are safe to call on a container already in that state, the\n // guards living in the popover plumbing — so there is no need to remember which\n // corners are currently open.\n for (const placement of PLACEMENTS) {\n const stack = stacks.get(placement)\n if (!stack?.el.value) continue\n if ((groups.value.get(placement)?.length ?? 0) > 0) stack.show()\n else stack.hide()\n }\n}\n\n/*\n * Watching the grouped queue covers a change to the notifications AND a change to the\n * default corner. The `post` timing is load-bearing: the notification must already be\n * in the page before its container is told to show itself.\n */\nwatch(groups, sync, { flush: 'post' })\n// @ssr — a watcher does not run during the server render, so a notification raised\n// before this component mounted would never be picked up. Running the same\n// synchronization on mount is what brings it in.\nonMounted(sync)\n\n// @a11y — WCAG 2.2.1: something that disappears on a clock has to be holdable, or a\n// slow reader simply never finishes it.\n/*\n * Resting the pointer on a corner suspends its countdowns. Leaving restarts them from\n * the FULL duration rather than from what was left: simpler, and more generous to the\n * reader who has just interrupted themselves.\n */\nfunction pause(placement: ToastPlacement) {\n paused.add(placement)\n for (const item of toasts) {\n if (effectivePlacement(item) !== placement) continue\n const timer = timers.get(item.id)\n if (timer !== undefined) {\n clearTimeout(timer)\n timers.delete(item.id)\n }\n }\n}\n\nfunction resume(placement: ToastPlacement) {\n paused.delete(placement)\n for (const item of toasts) {\n if (effectivePlacement(item) === placement) startTimer(item)\n }\n}\n\n/* The queue lives outside this component and survives it being unmounted and mounted\n again; only the countdowns are cleared here, and they are started afresh next time. */\nonBeforeUnmount(() => {\n for (const timer of timers.values()) clearTimeout(timer)\n timers.clear()\n})\n</script>\n\n<template>\n <div\n v-for=\"p in PLACEMENTS\"\n :key=\"p\"\n :ref=\"(el) => setStackEl(p, el)\"\n class=\"v-overlay v-toast-stack\"\n popover=\"manual\"\n :data-placement=\"p\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"pause(p)\"\n @pointerleave=\"resume(p)\"\n @beforetoggle=\"syncStack(p, $event)\"\n @toggle=\"syncStack(p, $event)\"\n >\n <VToast\n v-for=\"item in groups.get(p) ?? []\"\n :key=\"item.id\"\n :item=\"item\"\n :close-label=\"resolvedCloseLabel\"\n @close=\"dismissToast($event)\"\n />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The fixed positioning and the guard hiding a closed container come from the shared\n `.v-overlay` class, set on this same element. What stays here is the browser's own\n popover decoration to undo — its border, its padding, its opaque background — and\n the stack's own layout. */\n .v-toast-stack {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n }\n\n /*\n * These coordinates are deliberately PHYSICAL rather than logical: a notification\n * appears at a place on the screen, and that place does not flip with the reading\n * direction — the operating system's own notifications behave the same way.\n */\n .v-toast-stack[data-placement^='top-'] {\n top: var(--vectis-space-4);\n /* The newest notification should sit nearest the screen edge. The queue only ever\n grows at the end, so the stack is simply drawn in reverse. */\n flex-direction: column-reverse;\n /* The direction each card slides in from, read by `.v-banner` in styles/banner.css\n — the stack is the only thing that knows which edge of the screen it sits on. */\n --banner-enter-y: calc(-1 * var(--vectis-space-4));\n }\n\n .v-toast-stack[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The stack fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-toast-stack {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-toast-stack:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-toast-stack:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-toast-stack {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VToaster.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VToast/VToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Where notifications appear: mounted ONCE at the root, after which every `toast()` call\n * shows up here.\n *\n * One container per placement rather than one popover per notification, each stacking its\n * own with CSS. These boxes are drawn above the page at physical coordinates, anchored to\n * nothing, so individually they would all land on the same spot and keeping them apart would\n * mean measuring and offsetting each in code. The six exist at all times; empty, they cost\n * nothing and are not displayed.\n *\n * The JS covers three things the platform does not: keeping the queue and the containers in\n * step, the popover being imperative; the per-notification dismissal timers; and holding\n * them while the pointer rests on a stack OR the keyboard is inside it, so something that\n * disappears on a clock can be read and closed (WCAG 2.2.1).\n */\n\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport VToast from './VToast.vue'\nimport { dismissToast, toasts, type ToastItem, type ToastPlacement } from './state'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\ninterface ToasterProps {\n /** Which corner notifications appear in, unless one of them asks for another. */\n placement?: ToastPlacement\n /**\n * How long a notification stays, in milliseconds, unless it asks for something else.\n * A notification given 0 stays until it is dismissed.\n */\n duration?: number\n /** What the close cross does, in words. It falls back to the design system dictionary. */\n closeLabel?: string\n /**\n * What screen readers announce for the notification areas themselves, which are\n * landmarks of the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToasterProps>(), {\n placement: 'bottom-right',\n duration: 5000,\n closeLabel: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.toaster.label)\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? m.value.common.close)\n\nconst PLACEMENTS: ToastPlacement[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n]\n\n/**\n * The queue sorted into the corner each notification actually belongs to: the one it\n * asked for, or failing that the one set on this component.\n */\nconst groups = computed(() => {\n const map = new Map<ToastPlacement, ToastItem[]>()\n for (const item of toasts) {\n const placement = effectivePlacement(item)\n const list = map.get(placement)\n if (list) list.push(item)\n else map.set(placement, [item])\n }\n return map\n})\n\n/* The six containers, collected by the `v-for` itself. A plain template ref rather than a\n function one: a function written inline in the template is a new function on every\n render, which Vue answers by calling the old one with null and the new one with the\n element — twelve calls each time a notification comes or goes. */\nconst stackEls = ref<HTMLElement[]>([])\n\n/*\n * One instance of the popover plumbing PER corner. Each carries its own element, its\n * own open state — fed by that element's events — and the guards that make opening and\n * closing safe to call twice: asking the browser to show an already-shown popover\n * throws.\n *\n * The element is looked up by its placement rather than by its index: Vue does not\n * promise that a `v-for` ref array follows the order of the list.\n *\n * The containers being permanent, this runs once and never again.\n */\nconst stacks = new Map(\n PLACEMENTS.map((placement) => {\n const el = computed(\n () => stackEls.value.find((stack) => stack.dataset.placement === placement) ?? null,\n )\n return [placement, { el, ...usePopover(el) }] as const\n }),\n)\n\nfunction syncStack(placement: ToastPlacement, event: Event) {\n stacks.get(placement)?.syncShown(event)\n}\n\n/* The running countdowns, one per notification. */\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\n/* The corners holding their countdowns, one set per reason. Two sets rather than one\n because the reasons overlap and end independently: a reader tabs to a close cross, then\n moves the pointer over the stack and away, and the corner must stay held while the cross\n still has the focus — VSnackbar's two flags, per corner. */\nconst hovered = new Set<ToastPlacement>()\nconst focused = new Set<ToastPlacement>()\n\nfunction effectivePlacement(item: ToastItem): ToastPlacement {\n return item.placement ?? props.placement\n}\n\nconst isHeld = (placement: ToastPlacement) => hovered.has(placement) || focused.has(placement)\n\nfunction stopTimer(id: number) {\n const timer = timers.get(id)\n if (timer === undefined) return\n clearTimeout(timer)\n timers.delete(id)\n}\n\nfunction startTimer(item: ToastItem) {\n const duration = item.duration ?? props.duration\n if (duration <= 0 || timers.has(item.id)) return\n timers.set(\n item.id,\n setTimeout(() => {\n timers.delete(item.id)\n dismissToast(item.id)\n }, duration),\n )\n}\n\n// @core\n/**\n * Brings the page into line with the queue: it throws away the countdowns of the\n * notifications that have gone, gives every other one a countdown exactly when its corner\n * is not held, and then shows the corners holding something while hiding the empty ones.\n *\n * TRAP — the countdown follows the corner a notification is in NOW, re-derived on every\n * pass rather than remembered per notification. The default corner is a prop, so a\n * notification can change corner while the pointer rests on the old one: a memory of\n * \"already armed\" then outlived the countdown the hold had cancelled, the release looked\n * in the old corner, and the notification never got a countdown again.\n *\n * It runs once on mount — which is what makes a notification raised before this\n * component existed appear all the same — and after that on every change to the queue.\n */\nfunction sync() {\n const alive = new Set(toasts.map((item) => item.id))\n for (const id of timers.keys()) if (!alive.has(id)) stopTimer(id)\n\n for (const placement of PLACEMENTS) {\n const stackEl = stacks.get(placement)?.el.value\n // TRAP — a hold is dropped here as soon as it can no longer be true, rather than\n // trusted to its closing event. A close cross leaves the page while it has the focus,\n // and an engine that sends no `focusout` for a removed element would keep the corner\n // held for good; an emptied stack is hidden under the pointer, with no `pointerleave`.\n if (!stackEl?.contains(document.activeElement)) focused.delete(placement)\n if ((groups.value.get(placement)?.length ?? 0) === 0) hovered.delete(placement)\n }\n\n for (const item of toasts) {\n if (isHeld(effectivePlacement(item))) stopTimer(item.id)\n else startTimer(item)\n }\n\n // Showing and hiding are safe to call on a container already in that state, the\n // guards living in the popover plumbing — so there is no need to remember which\n // corners are currently open.\n for (const placement of PLACEMENTS) {\n const stack = stacks.get(placement)\n if (!stack?.el.value) continue\n if ((groups.value.get(placement)?.length ?? 0) > 0) stack.show()\n else stack.hide()\n }\n}\n\n/*\n * Watching the grouped queue covers a change to the notifications AND a change to the\n * default corner. The `post` timing is load-bearing: the notification must already be\n * in the page before its container is told to show itself.\n */\nwatch(groups, sync, { flush: 'post' })\n// @ssr — a watcher does not run during the server render, so a notification raised\n// before this component mounted would never be picked up. Running the same\n// synchronization on mount is what brings it in.\nonMounted(sync)\n\n// @a11y — WCAG 2.2.1: something that disappears on a clock has to be holdable, or a\n// slow reader simply never finishes it.\n/*\n * Resting the pointer on a corner suspends its countdowns, and so does moving the keyboard\n * into it: each notification carries a close cross, a real button a reader tabs to, which\n * would otherwise vanish from under the focus ring. The same verbs and the same two\n * reasons as VSnackbar.\n *\n * Leaving restarts them from the FULL duration rather than from what was left: simpler,\n * and more generous to the reader who has just interrupted themselves.\n */\nfunction hold(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.add(placement)\n for (const item of groups.value.get(placement) ?? []) stopTimer(item.id)\n}\n\nfunction release(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.delete(placement)\n if (isHeld(placement)) return\n for (const item of groups.value.get(placement) ?? []) startTimer(item)\n}\n\n/* The queue lives outside this component and survives it being unmounted and mounted\n again; only the countdowns are cleared here, and they are started afresh next time. */\nonBeforeUnmount(() => {\n for (const timer of timers.values()) clearTimeout(timer)\n timers.clear()\n})\n</script>\n\n<template>\n <div\n v-for=\"p in PLACEMENTS\"\n :key=\"p\"\n ref=\"stackEls\"\n class=\"v-overlay v-toast-stack\"\n popover=\"manual\"\n :data-placement=\"p\"\n role=\"region\"\n :aria-label=\"ariaLabel\"\n @pointerenter=\"hold(p, 'pointer')\"\n @pointerleave=\"release(p, 'pointer')\"\n @focusin=\"hold(p, 'focus')\"\n @focusout=\"release(p, 'focus')\"\n @beforetoggle=\"syncStack(p, $event)\"\n @toggle=\"syncStack(p, $event)\"\n >\n <VToast\n v-for=\"item in groups.get(p) ?? []\"\n :key=\"item.id\"\n :item=\"item\"\n :close-label=\"resolvedCloseLabel\"\n @close=\"dismissToast($event)\"\n />\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The container. Its rules are an ALIGNED COPY of VSnackbar's host: the two containers are the\n * same object, a popover at a physical corner that fades when it empties, and the copy is\n * kept line for line, the one difference being the top edge, which a confirmation never takes.\n * They are not factored into styles/banner.css because that sheet is paid for by every\n * consumer, and these rules cost more than the size gate allows; change one, change the other.\n *\n * The fixed positioning and the guard hiding a closed container come from the shared\n * `.v-overlay` class, set on this same element. What is undone here is the browser's own\n * popover decoration: its border, its padding, its opaque background.\n */\n .v-toast-stack {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * PHYSICAL coordinates rather than logical ones: a message appears at a place on the\n * screen, and that place does not flip with the reading direction — the operating\n * system's own notifications behave the same way.\n *\n * `--banner-enter-y` is the direction each card slides in from, read by `.v-banner` in\n * styles/banner.css: the container is the only thing that knows which edge of the screen\n * it sits on. That sheet reads it with a `, 0` fallback, so dropping the declaration costs\n * the slide and nothing else, and nothing reports it.\n */\n .v-toast-stack[data-placement^='top-'] {\n top: var(--vectis-space-4);\n --banner-enter-y: calc(-1 * var(--vectis-space-4));\n }\n\n .v-toast-stack[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-toast-stack {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-toast-stack:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-toast-stack:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-toast-stack {\n transition: none;\n }\n }\n\n /* The stack's own layout, which the snackbar's single bar has no use for. The newest\n notification should sit nearest the screen edge; the queue only ever grows at the end,\n so a stack along the top is simply drawn in reverse. */\n .v-toast-stack {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n }\n\n .v-toast-stack[data-placement^='top-'] {\n flex-direction: column-reverse;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EA4CA,IAAM,IAAQ,GAOR,IAAI,EAAY,GAChB,IAAY,QAAmB,EAAM,SAAS,EAAE,MAAM,QAAQ,KAAK,GACnE,IAAqB,QAAe,EAAM,cAAc,EAAE,MAAM,OAAO,KAAK,GAE5E,IAA+B;GACnC;GACA;GACA;GACA;GACA;GACA;EACF,GAMM,IAAS,QAAe;GAC5B,IAAM,oBAAM,IAAI,IAAiC;GACjD,KAAK,IAAM,KAAQ,GAAQ;IACzB,IAAM,IAAY,EAAmB,CAAI,GACnC,IAAO,EAAI,IAAI,CAAS;IAC9B,AAAI,IAAM,EAAK,KAAK,CAAI,IACnB,EAAI,IAAI,GAAW,CAAC,CAAI,CAAC;GAChC;GACA,OAAO;EACT,CAAC,GAMK,IAAW,EAAmB,CAAC,CAAC,GAahC,IAAS,IAAI,IACjB,EAAW,KAAK,MAAc;GAC5B,IAAM,IAAK,QACH,EAAS,MAAM,MAAM,MAAU,EAAM,QAAQ,cAAc,CAAS,KAAK,IACjF;GACA,OAAO,CAAC,GAAW;IAAE;IAAI,GAAG,EAAW,CAAE;GAAE,CAAC;EAC9C,CAAC,CACH;EAEA,SAAS,EAAU,GAA2B,GAAc;GAC1D,EAAO,IAAI,CAAS,CAAC,EAAE,UAAU,CAAK;EACxC;EAGA,IAAM,oBAAS,IAAI,IAA2C,GAKxD,oBAAU,IAAI,IAAoB,GAClC,oBAAU,IAAI,IAAoB;EAExC,SAAS,EAAmB,GAAiC;GAC3D,OAAO,EAAK,aAAa,EAAM;EACjC;EAEA,IAAM,KAAU,MAA8B,EAAQ,IAAI,CAAS,KAAK,EAAQ,IAAI,CAAS;EAE7F,SAAS,EAAU,GAAY;GAC7B,IAAM,IAAQ,EAAO,IAAI,CAAE;GACvB,MAAU,KAAA,MACd,aAAa,CAAK,GAClB,EAAO,OAAO,CAAE;EAClB;EAEA,SAAS,EAAW,GAAiB;GACnC,IAAM,IAAW,EAAK,YAAY,EAAM;GACpC,KAAY,KAAK,EAAO,IAAI,EAAK,EAAE,KACvC,EAAO,IACL,EAAK,IACL,iBAAiB;IAEf,AADA,EAAO,OAAO,EAAK,EAAE,GACrB,EAAa,EAAK,EAAE;GACtB,GAAG,CAAQ,CACb;EACF;EAiBA,SAAS,IAAO;GACd,IAAM,IAAQ,IAAI,IAAI,EAAO,KAAK,MAAS,EAAK,EAAE,CAAC;GACnD,KAAK,IAAM,KAAM,EAAO,KAAK,GAAG,AAAK,EAAM,IAAI,CAAE,KAAG,EAAU,CAAE;GAEhE,KAAK,IAAM,KAAa,GAOtB,CANgB,EAAO,IAAI,CAAS,CAAC,EAAE,GAAG,MAAA,EAK5B,SAAS,SAAS,aAAa,KAAG,EAAQ,OAAO,CAAS,IACnE,EAAO,MAAM,IAAI,CAAS,CAAC,EAAE,UAAU,OAAO,KAAG,EAAQ,OAAO,CAAS;GAGhF,KAAK,IAAM,KAAQ,GACjB,AAAI,EAAO,EAAmB,CAAI,CAAC,IAAG,EAAU,EAAK,EAAE,IAClD,EAAW,CAAI;GAMtB,KAAK,IAAM,KAAa,GAAY;IAClC,IAAM,IAAQ,EAAO,IAAI,CAAS;IAC7B,GAAO,GAAG,WACV,EAAO,MAAM,IAAI,CAAS,CAAC,EAAE,UAAU,KAAK,IAAG,EAAM,KAAK,IAC1D,EAAM,KAAK;GAClB;EACF;EAWA,AAJA,EAAM,GAAQ,GAAM,EAAE,OAAO,OAAO,CAAC,GAIrC,EAAU,CAAI;EAad,SAAS,EAAK,GAA2B,GAA4B;GAEnE,CADe,MAAU,YAAY,IAAU,EAAA,CACxC,IAAI,CAAS;GACpB,KAAK,IAAM,KAAQ,EAAO,MAAM,IAAI,CAAS,KAAK,CAAC,GAAG,EAAU,EAAK,EAAE;EACzE;EAEA,SAAS,EAAQ,GAA2B,GAA4B;GAEtE,KADe,MAAU,YAAY,IAAU,EAAA,CACxC,OAAO,CAAS,GACnB,GAAO,CAAS,GACpB,KAAK,IAAM,KAAQ,EAAO,MAAM,IAAI,CAAS,KAAK,CAAC,GAAG,EAAW,CAAI;EACvE;SAIA,QAAsB;GACpB,KAAK,IAAM,KAAS,EAAO,OAAO,GAAG,aAAa,CAAK;GACvD,EAAO,MAAM;EACf,CAAC,mBAIC,EAuBM,GAAA,MAAA,EAtBQ,IAAL,MADT,EAuBM,OAAA;GArBH,KAAK;;YACF;GAAJ,KAAI;GACJ,OAAM;GACN,SAAQ;GACP,kBAAgB;GACjB,MAAK;GACJ,cAAY,EAAA,CAAA;GACZ,iBAAY,MAAE,EAAK,GAAC,SAAA;GACpB,iBAAY,MAAE,EAAQ,GAAC,SAAA;GACvB,YAAO,MAAE,EAAK,GAAC,OAAA;GACf,aAAQ,MAAE,EAAQ,GAAC,OAAA;GACnB,iBAAY,MAAE,EAAU,GAAG,CAAM;GACjC,WAAM,MAAE,EAAU,GAAG,CAAM;cAE5B,EAME,GAAA,MAAA,EALe,EAAA,MAAO,IAAI,CAAC,KAAA,CAAA,IAApB,YADT,EAME,GAAA;GAJC,KAAK,EAAK;GACJ;GACN,eAAa,EAAA;GACb,SAAK,AAAA,EAAA,QAAA,MAAE,EAAA,CAAA,CAAY,CAAC,CAAM"}
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
import { IconSource } from '../VIcon/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* The queue of notifications waiting to be shown. Nothing in HTML covers this, so it
|
|
4
|
-
* is code by necessity.
|
|
5
|
-
*
|
|
6
|
-
* The queue is held at module level rather than inside a component, and that is the
|
|
7
|
-
* whole point: `toast()` can then be called from ANYWHERE — a component, a store, an
|
|
8
|
-
* error handler in an API layer — without anything having to be passed down to it.
|
|
9
|
-
* The price is a contract to respect: since module state is shared by every request a
|
|
10
|
-
* server handles, `toast()` must only ever be called in the browser, from a handler or
|
|
11
|
-
* when a request comes back, and never while a page is being rendered on the server.
|
|
12
|
-
*
|
|
13
|
-
* The only thing that touches the DOM, and the only thing that arms the timers, is the
|
|
14
|
-
* mounted VToaster.
|
|
15
|
-
*/
|
|
16
2
|
export type ToastTone = 'neutral' | 'accent' | 'success' | 'danger' | 'warning';
|
|
3
|
+
/** How strongly a notification is painted: a tinted background with a border, or the full colour. */
|
|
4
|
+
export type ToastVariant = 'soft' | 'solid';
|
|
17
5
|
export type ToastPlacement = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right';
|
|
18
6
|
/** Everything one can say when raising a notification. */
|
|
19
7
|
export interface ToastOptions {
|
|
@@ -24,7 +12,7 @@ export interface ToastOptions {
|
|
|
24
12
|
/** What the notification means, expressed as a colour, and which icon it takes by default. */
|
|
25
13
|
tone?: ToastTone;
|
|
26
14
|
/** How strongly it is painted: a tinted background with a border, or the full colour. */
|
|
27
|
-
variant?:
|
|
15
|
+
variant?: ToastVariant;
|
|
28
16
|
/**
|
|
29
17
|
* The icon to show. Left out, the tone brings its own; `false` removes it
|
|
30
18
|
* altogether.
|
|
@@ -37,8 +25,11 @@ export interface ToastOptions {
|
|
|
37
25
|
duration?: number;
|
|
38
26
|
/** Which corner it appears in. Left out, it takes the one set on the VToaster. */
|
|
39
27
|
placement?: ToastPlacement;
|
|
40
|
-
/**
|
|
41
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Takes the close cross away, leaving the notification to its duration or to
|
|
30
|
+
* `dismissToast`. Keep the cross on a notification that stays until dismissed by hand.
|
|
31
|
+
*/
|
|
32
|
+
hideClose?: boolean;
|
|
42
33
|
/**
|
|
43
34
|
* How wide it is, as a CSS length. It is never allowed past the width of the
|
|
44
35
|
* viewport, margins included.
|
|
@@ -52,8 +43,8 @@ export interface ToastOptions {
|
|
|
52
43
|
export interface ToastItem extends ToastOptions {
|
|
53
44
|
id: number;
|
|
54
45
|
tone: ToastTone;
|
|
55
|
-
variant:
|
|
56
|
-
|
|
46
|
+
variant: ToastVariant;
|
|
47
|
+
hideClose: boolean;
|
|
57
48
|
}
|
|
58
49
|
/** The queue itself. It is internal, read only by VToaster, and not public API. */
|
|
59
50
|
export declare const toasts: import('vue').Reactive<ToastItem[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","names":[],"sources":["../../../src/components/VToast/state.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"state.js","names":[],"sources":["../../../src/components/VToast/state.ts"],"sourcesContent":["// @ssr @core\n/**\n * The queue of notifications waiting to be shown. Nothing in HTML covers this, so it\n * is code by necessity.\n *\n * The queue is held at module level rather than inside a component, and that is the\n * whole point: `toast()` can then be called from ANYWHERE — a component, a store, an\n * error handler in an API layer — without anything having to be passed down to it.\n * The price is a contract to respect: since module state is shared by every request a\n * server handles, `toast()` must only ever be called in the browser, from a handler or\n * when a request comes back, and never while a page is being rendered on the server.\n *\n * The only thing that touches the DOM, and the only thing that arms the timers, is the\n * mounted VToaster.\n */\n\nimport { reactive } from 'vue'\n\nimport type { IconSource } from '../VIcon/types'\n\nexport type ToastTone = 'neutral' | 'accent' | 'success' | 'danger' | 'warning'\n\n/** How strongly a notification is painted: a tinted background with a border, or the full colour. */\nexport type ToastVariant = 'soft' | 'solid'\n\nexport type ToastPlacement =\n 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'\n\n/** Everything one can say when raising a notification. */\nexport interface ToastOptions {\n /** What the notification says. */\n message: string\n /** A short heading above the message, when the message alone needs framing. */\n title?: string\n /** What the notification means, expressed as a colour, and which icon it takes by default. */\n tone?: ToastTone\n /** How strongly it is painted: a tinted background with a border, or the full colour. */\n variant?: ToastVariant\n /**\n * The icon to show. Left out, the tone brings its own; `false` removes it\n * altogether.\n */\n icon?: IconSource | false\n /**\n * How long it stays, in milliseconds. Setting it to 0 keeps it until it is\n * dismissed by hand, and leaving it out takes the duration set on the VToaster.\n */\n duration?: number\n /** Which corner it appears in. Left out, it takes the one set on the VToaster. */\n placement?: ToastPlacement\n /**\n * Takes the close cross away, leaving the notification to its duration or to\n * `dismissToast`. Keep the cross on a notification that stays until dismissed by hand.\n */\n hideClose?: boolean\n /**\n * How wide it is, as a CSS length. It is never allowed past the width of the\n * viewport, margins included.\n */\n width?: string\n}\n\n/**\n * A notification once it is in the queue, with the choices the caller left out filled\n * in and an id of its own. Internal: it is what VToaster renders.\n */\nexport interface ToastItem extends ToastOptions {\n id: number\n tone: ToastTone\n variant: ToastVariant\n hideClose: boolean\n}\n\n/** The queue itself. It is internal, read only by VToaster, and not public API. */\nexport const toasts = reactive<ToastItem[]>([])\n\nlet nextId = 0\n\n/**\n * Raises a notification, and hands back an id that `dismissToast` can use to take it\n * away again before its time.\n *\n * The two defaults that belong to the VToaster — where notifications appear and how\n * long they stay — are deliberately NOT resolved here: they are read when the\n * notification is rendered, so the toaster remains the single source of truth for its\n * own settings.\n */\nexport function toast(options: ToastOptions): number {\n const id = nextId++\n toasts.push({ tone: 'neutral', variant: 'soft', hideClose: false, ...options, id })\n return id\n}\n\n/**\n * Takes a notification away by its id, or clears every one of them when called with\n * no argument.\n */\nexport function dismissToast(id?: number): void {\n if (id === undefined) {\n toasts.length = 0\n return\n }\n const index = toasts.findIndex((item) => item.id === id)\n if (index !== -1) toasts.splice(index, 1)\n}\n"],"mappings":";;AA0EA,IAAa,IAAS,EAAsB,CAAC,CAAC,GAE1C,IAAS;AAWb,SAAgB,EAAM,GAA+B;CACnD,IAAM,IAAK;CAEX,OADA,EAAO,KAAK;EAAE,MAAM;EAAW,SAAS;EAAQ,WAAW;EAAO,GAAG;EAAS;CAAG,CAAC,GAC3E;AACT;AAMA,SAAgB,EAAa,GAAmB;CAC9C,IAAI,MAAO,KAAA,GAAW;EACpB,EAAO,SAAS;EAChB;CACF;CACA,IAAM,IAAQ,EAAO,WAAW,MAAS,EAAK,OAAO,CAAE;CACvD,AAAI,MAAU,MAAI,EAAO,OAAO,GAAO,CAAC;AAC1C"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
@layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
|
|
2
|
-
@layer vectis.components{.v-toggle:not(.v-button-group){
|
|
2
|
+
@layer vectis.components{.v-toggle[data-item-variant=outline]{--toggle-frame:var(--vectis-color-border-strong)}.v-toggle[data-item-variant=outline]>.v-toggle-item:is(:disabled,[aria-disabled=true]),.v-toggle[data-item-variant=outline]>:not(:where(.v-overlay,.v-button-group)) .v-toggle-item:is(:disabled,[aria-disabled=true]):not(:where(.v-overlay *)){--toggle-frame:var(--vectis-color-border)}.v-toggle[data-item-variant=outline]>.v-toggle-item[aria-pressed=true]:is([data-variant=soft],[data-variant=ghost]),.v-toggle[data-item-variant=outline]>:not(:where(.v-overlay,.v-button-group)) .v-toggle-item[aria-pressed=true]:is([data-variant=soft],[data-variant=ghost]):not(:where(.v-overlay *)){border-color:var(--toggle-frame)}}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { ItemValue } from '../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* What identifies an item. It is the design system's own `ItemValue`, re-exported
|
|
4
|
+
* under the family's name so a consumer typing a toggle need not reach for another module.
|
|
5
|
+
*/
|
|
6
|
+
export type ToggleValue = ItemValue;
|
|
2
7
|
/**
|
|
3
8
|
* What the v-model holds, which follows `multiple`: one value or nothing when a single
|
|
4
9
|
* choice is allowed, and a list of them when several are.
|
|
5
10
|
*/
|
|
6
11
|
export type ToggleModelValue = ToggleValue | ToggleValue[] | null;
|
|
7
|
-
export type
|
|
12
|
+
export type ToggleItemVariant = 'ghost' | 'outline';
|
|
13
|
+
export type ToggleSelectedVariant = 'solid' | 'soft' | 'ghost';
|
|
8
14
|
export type ToggleTone = 'accent' | 'neutral' | 'danger';
|
|
9
15
|
export type ToggleSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
10
16
|
export type ToggleOrientation = 'horizontal' | 'vertical';
|
|
@@ -18,23 +24,42 @@ interface ToggleProps {
|
|
|
18
24
|
*/
|
|
19
25
|
mandatory?: boolean;
|
|
20
26
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
27
|
+
* Leaves the items as separate buttons with a gap between them. Left out, they are
|
|
28
|
+
* joined into one segmented control.
|
|
29
|
+
*/
|
|
30
|
+
detached?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Takes the lines out from between the joined items, so the row reads as one frame
|
|
33
|
+
* rather than as segments. It has no effect under `detached`, where the items are
|
|
34
|
+
* separate buttons already.
|
|
23
35
|
*/
|
|
24
|
-
|
|
36
|
+
seamless?: boolean;
|
|
25
37
|
/** Whether the items run across the page or down it. */
|
|
26
38
|
orientation?: ToggleOrientation;
|
|
27
39
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
40
|
+
* Stretches the row across the whole inline size of its parent, every item taking an equal
|
|
41
|
+
* share of it, on the terms of VButtonGroup's own `fullWidth`.
|
|
42
|
+
*/
|
|
43
|
+
fullWidth?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* How the UNSELECTED items are drawn. What the selected one takes is `selectedVariant`.
|
|
46
|
+
* It is named for the ITEMS because that is what it paints: on VTabs and VDataTable
|
|
47
|
+
* `variant` names the decoration of the frame instead.
|
|
30
48
|
*/
|
|
31
|
-
|
|
49
|
+
itemVariant?: ToggleItemVariant;
|
|
50
|
+
/**
|
|
51
|
+
* How the SELECTED item is drawn, in the group's tone: filled, tinted, or the colour
|
|
52
|
+
* of its text alone.
|
|
53
|
+
*/
|
|
54
|
+
selectedVariant?: ToggleSelectedVariant;
|
|
32
55
|
/** The colour a selected item takes. The others stay neutral. */
|
|
33
56
|
tone?: ToggleTone;
|
|
34
57
|
/** The height of the items, from the scale shared by every control. */
|
|
35
58
|
size?: ToggleSize;
|
|
36
59
|
/** Takes 4px off the height of every item. */
|
|
37
60
|
compact?: boolean;
|
|
61
|
+
/** Raises the row off the page, on the terms of VButtonGroup's own `elevated`. */
|
|
62
|
+
elevated?: boolean;
|
|
38
63
|
/** Makes the whole group unusable. */
|
|
39
64
|
disabled?: boolean;
|
|
40
65
|
/**
|
|
@@ -54,24 +79,44 @@ type __VLS_Slots = {
|
|
|
54
79
|
default(): unknown;
|
|
55
80
|
};
|
|
56
81
|
type __VLS_ModelProps = {
|
|
82
|
+
/**
|
|
83
|
+
* What is selected, and its SHAPE follows `multiple`: a single value — or `null`, which is
|
|
84
|
+
* where it starts — when one item may be chosen, and an array when several may. A null or
|
|
85
|
+
* scalar value passed in `multiple` mode is read as an empty selection.
|
|
86
|
+
*
|
|
87
|
+
* The array is never mutated in place; each change is a new one, which is what wakes the
|
|
88
|
+
* consumer's binding. Re-clicking the selected item deselects it unless `mandatory` is set.
|
|
89
|
+
*/
|
|
57
90
|
modelValue?: ToggleModelValue;
|
|
58
91
|
};
|
|
59
92
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
60
|
-
declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {
|
|
93
|
+
declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {
|
|
94
|
+
/**
|
|
95
|
+
* Moves the focus to the selected item (the first of them when several are), or to the
|
|
96
|
+
* first item that can take it when nothing is selected.
|
|
97
|
+
*/
|
|
98
|
+
focus: (options?: FocusOptions) => void;
|
|
99
|
+
/** The `role="group"` row, which is also where the consumer's attributes land. */
|
|
100
|
+
el: import('vue').ComputedRef<HTMLElement | null>;
|
|
101
|
+
}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
61
102
|
"update:modelValue": (value: ToggleModelValue) => any;
|
|
62
103
|
}, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
63
104
|
"onUpdate:modelValue"?: ((value: ToggleModelValue) => any) | undefined;
|
|
64
105
|
}>, {
|
|
65
106
|
label: string;
|
|
66
107
|
size: ToggleSize;
|
|
67
|
-
variant: ToggleVariant;
|
|
68
108
|
tone: ToggleTone;
|
|
69
109
|
orientation: ToggleOrientation;
|
|
110
|
+
elevated: boolean;
|
|
70
111
|
compact: boolean;
|
|
112
|
+
fullWidth: boolean;
|
|
71
113
|
disabled: boolean;
|
|
72
|
-
|
|
114
|
+
detached: boolean;
|
|
115
|
+
seamless: boolean;
|
|
116
|
+
itemVariant: ToggleItemVariant;
|
|
73
117
|
multiple: boolean;
|
|
74
118
|
mandatory: boolean;
|
|
119
|
+
selectedVariant: ToggleSelectedVariant;
|
|
75
120
|
selectedIconFilled: boolean;
|
|
76
121
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
77
122
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VToggle.js","names":[],"sources":["../../../src/components/VToggle/VToggle.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * A row of buttons where one — or several — stay pressed: choosing a text alignment,\n * a view, a set of filters. A single v-model holds what is chosen.\n *\n * Every item is a VButton, the selected one filled and the others quiet, and by\n * default the whole row is joined into one segmented control. Nothing about hovering,\n * focusing or disabling is written here: it all comes from VButton.\n *\n * The JavaScript covers the two things HTML has no answer for: what a click does to\n * the selection — toggling it off, or refusing to when the last choice may not be\n * given up — and moving between the items with the arrow keys.\n */\nimport { provide } from 'vue'\n\nimport VButtonGroup from '../VButton/VButtonGroup.vue'\nimport { toggleKey } from './context'\n\nimport { toggleValue } from '../../utils/array'\nimport { arrowNavigate, navigableItems } from '../../utils/arrowNav'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\n\nexport type ToggleValue = string | number\n/**\n * What the v-model holds, which follows `multiple`: one value or nothing when a single\n * choice is allowed, and a list of them when several are.\n */\nexport type ToggleModelValue = ToggleValue | ToggleValue[] | null\nexport type ToggleVariant = 'ghost' | 'outline'\nexport type ToggleTone = 'accent' | 'neutral' | 'danger'\nexport type ToggleSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\nexport type ToggleOrientation = 'horizontal' | 'vertical'\n\ninterface ToggleProps {\n /** Allows several items to be chosen at once, which makes the v-model a list. */\n multiple?: boolean\n /**\n * Refuses to let the last chosen item be given up, so that something is always\n * selected once something has been. It is a guard and nothing more: it selects\n * nothing on its own at the start.\n */\n mandatory?: boolean\n /**\n * Joins the items into one segmented control. Turning it off leaves them as separate\n * buttons with a gap between them.\n */\n attached?: boolean\n /** Whether the items run across the page or down it. */\n orientation?: ToggleOrientation\n /**\n * How the UNSELECTED items are drawn. A selected item is always filled, whatever\n * this says.\n */\n variant?: ToggleVariant\n /** The colour a selected item takes. The others stay neutral. */\n tone?: ToggleTone\n /** The height of the items, from the scale shared by every control. */\n size?: ToggleSize\n /** Takes 4px off the height of every item. */\n compact?: boolean\n /** Makes the whole group unusable. */\n disabled?: boolean\n /**\n * Draws the selected item's icon in its filled form, a common way of reinforcing\n * that it is the one in effect.\n */\n selectedIconFilled?: boolean\n /**\n * What screen readers announce for the group — \"Text alignment\", \"Filters\". It is\n * strongly recommended: no default could say what a group of buttons is for.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToggleProps>(), {\n multiple: false,\n mandatory: false,\n attached: true,\n orientation: 'horizontal',\n variant: 'ghost',\n tone: 'accent',\n size: 'md',\n compact: false,\n disabled: false,\n selectedIconFilled: false,\n label: undefined,\n})\n\ndefineSlots<{\n /** The items of the group. */\n default(): unknown\n}>()\n\nconst model = defineModel<ToggleModelValue>({ default: null })\n\nconst ariaLabel = useAriaLabel(() => props.label)\n\nfunction isSelected(value: ToggleValue): boolean {\n return props.multiple\n ? Array.isArray(model.value) && model.value.includes(value)\n : model.value === value\n}\n\n/*\n * What a click does to the selection.\n *\n * With several choices allowed, the list is NEVER changed in place: each write\n * produces a new one. That is what makes a consumer's own watchers and computed values\n * notice; mutating the existing list would leave some of them silent. A model still\n * holding a single value, or nothing, is treated as an empty list rather than\n * refused.\n *\n * There is no check for the disabled state here, and none is needed: a disabled button\n * emits no click at all.\n */\nfunction select(value: ToggleValue) {\n if (props.multiple) {\n const current = Array.isArray(model.value) ? model.value : []\n if (props.mandatory && current.length === 1 && current.includes(value)) return\n model.value = toggleValue(current, value)\n return\n }\n if (model.value === value) {\n if (!props.mandatory) model.value = null\n return\n }\n model.value = value\n}\n\nprovide(toggleKey, {\n isSelected,\n select,\n get variant() {\n return props.variant\n },\n get tone() {\n return props.tone\n },\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get disabled() {\n return props.disabled\n },\n get selectedIconFilled() {\n return props.selectedIconFilled\n },\n})\n\n// @keyboard @a11y\n/*\n * The arrow keys, through the shared implementation in `utils/arrowNav`.\n *\n * Every visible item stays a stop in the tab order here — the VPagination model. The\n * single-stop treatment is reserved for the patterns that require it, a row of tabs or\n * a radio group, and a group of buttons is neither. Disabled items are really disabled\n * buttons, which the selector leaves out.\n *\n * The container is read from the event rather than kept as a reference, because the\n * root is rendered dynamically: a reference would hand back sometimes a plain element,\n * sometimes a component instance.\n */\nfunction onKeydown(event: KeyboardEvent) {\n const group = event.currentTarget as HTMLElement\n arrowNavigate(event, group, navigableItems(group, '.v-toggle-item:not(:disabled)'), {\n vertical: props.orientation === 'vertical',\n })\n}\n</script>\n\n<template>\n <!-- Joined, the row is a VButtonGroup, which merges the borders of its DIRECT\n button children — which is why an item renders the button as its own root, with\n no wrapper in between.\n\n The orientation is passed through ONE key or the other, never both: an\n attribute bound to `undefined` would still be forwarded and would overwrite the\n one VButtonGroup sets for itself. So the group receives a prop, and the plain\n container an attribute.\n\n The role is needed for the plain container, and simply repeats what\n VButtonGroup already sets when the row is joined. -->\n <component\n :is=\"attached ? VButtonGroup : 'div'\"\n v-bind=\"attached ? { orientation } : { 'data-orientation': orientation }\"\n class=\"v-toggle\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n @keydown=\"onKeydown\"\n >\n <slot />\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * These rules are for the separated row alone, and the exclusion is what makes that\n * true: when the row is joined, the root carries BOTH classes at once. Without it,\n * the centring here and the stretching VButtonGroup declares would collide at equal\n * specificity, and the winner would be decided by whichever sheet the consumer's\n * bundler put last.\n */\n .v-toggle:not(.v-button-group) {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-1);\n }\n\n .v-toggle:not(.v-button-group)[data-orientation='vertical'] {\n flex-direction: column;\n align-items: stretch;\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VToggle.js","names":[],"sources":["../../../src/components/VToggle/VToggle.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * A row of buttons where one — or several — stay pressed: choosing a text alignment,\n * a view, a set of filters. A single v-model holds what is chosen.\n *\n * Every item is a VButton, the selected one drawn in the group's tone and the others\n * quiet. The row itself is a VButtonGroup, which is where the drawing lives: joining the\n * items or leaving them apart, the lines between them, the shared size and the shared\n * elevation are that component's, and the props for them are handed straight over.\n * Nothing about hovering, focusing or disabling is written here either: it all comes\n * from VButton.\n *\n * The JavaScript covers the two things HTML has no answer for: what a click does to\n * the selection — toggling it off, or refusing to when the last choice may not be\n * given up — and moving between the items with the arrow keys.\n */\nimport { computed, provide, ref } from 'vue'\n\nimport VButtonGroup from '../VButton/VButtonGroup.vue'\nimport { toggleKey } from './context'\n\nimport { toggleValue } from '../../utils/array'\nimport { arrowNavigate, navigableItems } from '../../utils/arrowNav'\n\nimport type { ItemValue } from '../../types'\n\n/**\n * What identifies an item. It is the design system's own `ItemValue`, re-exported\n * under the family's name so a consumer typing a toggle need not reach for another module.\n */\nexport type ToggleValue = ItemValue\n/**\n * What the v-model holds, which follows `multiple`: one value or nothing when a single\n * choice is allowed, and a list of them when several are.\n */\nexport type ToggleModelValue = ToggleValue | ToggleValue[] | null\nexport type ToggleItemVariant = 'ghost' | 'outline'\nexport type ToggleSelectedVariant = 'solid' | 'soft' | 'ghost'\nexport type ToggleTone = 'accent' | 'neutral' | 'danger'\nexport type ToggleSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\nexport type ToggleOrientation = 'horizontal' | 'vertical'\n\ninterface ToggleProps {\n /** Allows several items to be chosen at once, which makes the v-model a list. */\n multiple?: boolean\n /**\n * Refuses to let the last chosen item be given up, so that something is always\n * selected once something has been. It is a guard and nothing more: it selects\n * nothing on its own at the start.\n */\n mandatory?: boolean\n /**\n * Leaves the items as separate buttons with a gap between them. Left out, they are\n * joined into one segmented control.\n */\n detached?: boolean\n /**\n * Takes the lines out from between the joined items, so the row reads as one frame\n * rather than as segments. It has no effect under `detached`, where the items are\n * separate buttons already.\n */\n seamless?: boolean\n /** Whether the items run across the page or down it. */\n orientation?: ToggleOrientation\n /**\n * Stretches the row across the whole inline size of its parent, every item taking an equal\n * share of it, on the terms of VButtonGroup's own `fullWidth`.\n */\n fullWidth?: boolean\n /**\n * How the UNSELECTED items are drawn. What the selected one takes is `selectedVariant`.\n * It is named for the ITEMS because that is what it paints: on VTabs and VDataTable\n * `variant` names the decoration of the frame instead.\n */\n itemVariant?: ToggleItemVariant\n /**\n * How the SELECTED item is drawn, in the group's tone: filled, tinted, or the colour\n * of its text alone.\n */\n selectedVariant?: ToggleSelectedVariant\n /** The colour a selected item takes. The others stay neutral. */\n tone?: ToggleTone\n /** The height of the items, from the scale shared by every control. */\n size?: ToggleSize\n /** Takes 4px off the height of every item. */\n compact?: boolean\n /** Raises the row off the page, on the terms of VButtonGroup's own `elevated`. */\n elevated?: boolean\n /** Makes the whole group unusable. */\n disabled?: boolean\n /**\n * Draws the selected item's icon in its filled form, a common way of reinforcing\n * that it is the one in effect.\n */\n selectedIconFilled?: boolean\n /**\n * What screen readers announce for the group — \"Text alignment\", \"Filters\". It is\n * strongly recommended: no default could say what a group of buttons is for.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToggleProps>(), {\n multiple: false,\n mandatory: false,\n detached: false,\n seamless: false,\n orientation: 'horizontal',\n fullWidth: false,\n itemVariant: 'ghost',\n selectedVariant: 'solid',\n tone: 'accent',\n size: 'md',\n compact: false,\n elevated: false,\n disabled: false,\n selectedIconFilled: false,\n label: undefined,\n})\n\ndefineSlots<{\n /** The items of the group. */\n default(): unknown\n}>()\n\n/**\n * What is selected, and its SHAPE follows `multiple`: a single value — or `null`, which is\n * where it starts — when one item may be chosen, and an array when several may. A null or\n * scalar value passed in `multiple` mode is read as an empty selection.\n *\n * The array is never mutated in place; each change is a new one, which is what wakes the\n * consumer's binding. Re-clicking the selected item deselects it unless `mandatory` is set.\n */\nconst model = defineModel<ToggleModelValue>({ default: null })\n\nfunction isSelected(value: ToggleValue): boolean {\n return props.multiple\n ? Array.isArray(model.value) && model.value.includes(value)\n : model.value === value\n}\n\n/*\n * What a click does to the selection.\n *\n * With several choices allowed, the list is NEVER changed in place: each write\n * produces a new one. That is what makes a consumer's own watchers and computed values\n * notice; mutating the existing list would leave some of them silent. A model still\n * holding a single value, or nothing, is treated as an empty list rather than\n * refused.\n *\n * There is no check for the disabled state here, and none is needed: a disabled button\n * emits no click at all.\n */\nfunction select(value: ToggleValue) {\n if (props.multiple) {\n const current = Array.isArray(model.value) ? model.value : []\n if (props.mandatory && current.length === 1 && current.includes(value)) return\n model.value = toggleValue(current, value)\n return\n }\n if (model.value === value) {\n if (!props.mandatory) model.value = null\n return\n }\n model.value = value\n}\n\nprovide(toggleKey, {\n isSelected,\n select,\n get itemVariant() {\n return props.itemVariant\n },\n get selectedVariant() {\n return props.selectedVariant\n },\n get tone() {\n return props.tone\n },\n get selectedIconFilled() {\n return props.selectedIconFilled\n },\n})\n\n// @keyboard @a11y\n/*\n * The arrow keys, through the shared implementation in `utils/arrowNav`.\n *\n * Every visible item stays a stop in the tab order here — the VPagination model. The\n * single-stop treatment is reserved for the patterns that require it, a row of tabs or\n * a radio group, and a group of buttons is neither. Disabled items are really disabled\n * buttons, which the selector leaves out.\n *\n * The container is read from the event rather than kept as a reference: the root is a\n * VButtonGroup, so a template ref would hand back its component instance rather than the\n * element the items are navigated inside.\n */\nfunction onKeydown(event: KeyboardEvent) {\n const group = event.currentTarget as HTMLElement\n arrowNavigate(event, group, () => navigableItems(group, '.v-toggle-item:not(:disabled)'), {\n vertical: props.orientation === 'vertical',\n })\n}\n\nconst groupRef = ref<InstanceType<typeof VButtonGroup> | null>(null)\nconst groupEl = computed(() => groupRef.value?.el ?? null)\n\n// The root is a VButtonGroup and the items come from the consumer's slot, so a template ref\n// on the toggle reaches no button. `focus` goes to the item that stands for the current\n// choice, the one a keyboard user would expect to resume from.\ndefineExpose({\n /**\n * Moves the focus to the selected item (the first of them when several are), or to the\n * first item that can take it when nothing is selected.\n */\n focus: (options?: FocusOptions) => {\n const group = groupEl.value\n const enabled = '.v-toggle-item:not(:disabled, [aria-disabled=\"true\"])'\n const item =\n group?.querySelector<HTMLElement>(`${enabled}[aria-pressed=\"true\"]`) ??\n group?.querySelector<HTMLElement>(enabled)\n item?.focus(options)\n },\n /** The `role=\"group\"` row, which is also where the consumer's attributes land. */\n el: groupEl,\n})\n</script>\n\n<template>\n <!-- The row is a VButtonGroup, which merges the borders of the buttons among its\n children — which is why an item renders the button as its own root, and why what\n may stand between the two is the wrappers a VTooltip, a VPopover or a VBadge put\n there, and nothing else. It brings the role, its accessible name and the orientation\n attribute with it, and of the props handed to it here it forwards the size, the density,\n the elevation and the disabled state to the buttons itself.\n\n The three booleans go down as `|| undefined`, the VPagination idiom: `undefined` is\n what means \"no opinion\" to a group, where `false` is an order every item would obey,\n and a toggle left at its defaults has no opinion to give. The size is always one, the\n toggle having a default of its own.\n\n The name is resolved by the group, which sees a consumer's `aria-label` or\n `aria-labelledby` too: they fall through this component onto it.\n\n `variant` is deliberately NOT among them, and neither is the tone: the group wins\n over a button that names its own variant, which would erase the one the selected\n item carries. Both stay with the items, through the context.\n\n `data-variant` is a plain attribute, and it is what tells the sheet an outline row\n from a ghost one — something the items cannot say for themselves, since the\n selected one carries its own variant rather than the row's. -->\n <VButtonGroup\n ref=\"groupRef\"\n class=\"v-toggle\"\n :orientation=\"orientation\"\n :detached=\"detached\"\n :seamless=\"seamless\"\n :full-width=\"fullWidth\"\n :size=\"size\"\n :compact=\"compact || undefined\"\n :elevated=\"elevated || undefined\"\n :disabled=\"disabled || undefined\"\n :label=\"label\"\n :data-item-variant=\"itemVariant\"\n @keydown=\"onKeydown\"\n >\n <slot />\n </VButtonGroup>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * Both rules below are written for the two shapes an item can take, exactly as\n * VButtonGroup's own sheet is and for the same reason: a VTooltip, a VPopover or a\n * VBadge puts a wrapper between the row and the item, and a rule reaching for a direct\n * child alone would leave a gap in the frame around whichever item carries one. The\n * `:not(:where(.v-overlay))` guard weighs nothing and keeps a panel from passing for an\n * item; the pair is one specificity, so the arbitration with the group's seamless rules\n * holds on both shapes.\n *\n * The frame of an `outline` row, which only that row has: the colour its unselected\n * items already paint. They are drawn in the NEUTRAL tone, so this is what their\n * `--tone-border-soft` resolves to — naming that variable here instead would read the\n * SELECTED item's tone and tint one segment of the frame accent or red.\n *\n * It is a variable on the root rather than a value in the rule below because a\n * disabled segment greys, and VButton greys an outline border to a different token.\n * Nothing else declares it, so the two rules never arbitrate a border-color between\n * them.\n */\n .v-toggle[data-item-variant='outline'] {\n --toggle-frame: var(--vectis-color-border-strong);\n }\n\n .v-toggle[data-item-variant='outline'] > .v-toggle-item:is(:disabled, [aria-disabled='true']),\n .v-toggle[data-item-variant='outline']\n > :not(:where(.v-overlay, .v-button-group))\n .v-toggle-item:is(:disabled, [aria-disabled='true']):not(:where(.v-overlay *)) {\n --toggle-frame: var(--vectis-color-border);\n }\n\n /*\n * `soft` and `ghost` leave VButton's border transparent, which in an outline row\n * opens a gap in the frame for the whole width of the selected segment. Restoring it\n * on all four sides is what keeps the frame closed wherever the selection sits, in\n * either orientation and in RTL. It is not conditioned on `seamless`, and deliberately\n * so: the border is transparent whether the internal lines are drawn or not. Seamless,\n * the shared edges are cleared again by VButtonGroup's own (0,6,0) rules, which is why\n * they are written at that weight — a middle segment then keeps its top and bottom\n * alone.\n */\n .v-toggle[data-item-variant='outline']\n > .v-toggle-item[aria-pressed='true']:is([data-variant='soft'], [data-variant='ghost']),\n .v-toggle[data-item-variant='outline']\n > :not(:where(.v-overlay, .v-button-group))\n .v-toggle-item[aria-pressed='true']:is([data-variant='soft'], [data-variant='ghost']):not(\n :where(.v-overlay *)\n ) {\n border-color: var(--toggle-frame);\n }\n}\n</style>\n"],"mappings":""}
|