vectis-ui 0.10.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/VAccordion/VAccordion.js.map +1 -1
- package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.d.ts +6 -21
- package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/context.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.d.ts +2 -12
- package/dist/components/VAvatar/VAvatar.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.d.ts +2 -2
- package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/context.d.ts +1 -5
- package/dist/components/VAvatar/context.js.map +1 -1
- package/dist/components/VBadge/VBadge.d.ts +13 -27
- package/dist/components/VBadge/VBadge.js.map +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +3 -9
- package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButton.d.ts +10 -17
- package/dist/components/VButton/VButton.js.map +1 -1
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.d.ts +6 -15
- package/dist/components/VButton/VButtonGroup.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/context.d.ts +1 -9
- package/dist/components/VButton/context.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.d.ts +9 -24
- package/dist/components/VCalendar/VCalendar.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.d.ts +4 -13
- package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +1 -5
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/color.d.ts +1 -7
- package/dist/components/VCalendar/color.js.map +1 -1
- package/dist/components/VCalendar/edgeStep.d.ts +5 -15
- package/dist/components/VCalendar/edgeStep.js.map +1 -1
- package/dist/components/VCalendar/gesture.d.ts +12 -36
- package/dist/components/VCalendar/gesture.js.map +1 -1
- package/dist/components/VCalendar/keyboard.d.ts +5 -42
- package/dist/components/VCalendar/keyboard.js.map +1 -1
- package/dist/components/VCalendar/layout.d.ts +38 -199
- package/dist/components/VCalendar/layout.js.map +1 -1
- package/dist/components/VCalendar/timeGrid.d.ts +3 -25
- package/dist/components/VCalendar/timeGrid.js.map +1 -1
- package/dist/components/VCalendar/types.d.ts +6 -23
- package/dist/components/VCarousel/VCarousel.d.ts +25 -96
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +6 -6
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.d.ts +1 -6
- package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/context.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.d.ts +1 -5
- package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VChip/VChip.d.ts +2 -8
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +21 -59
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +117 -119
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxOption.js.map +1 -1
- package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxSeparator.js.map +1 -1
- package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/infiniteScroll.d.ts +4 -10
- package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +11 -36
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +23 -20
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.d.ts +12 -21
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/mask.d.ts +14 -59
- package/dist/components/VDateInput/mask.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.d.ts +6 -12
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/keyboard.d.ts +10 -42
- package/dist/components/VDatePicker/keyboard.js.map +1 -1
- package/dist/components/VDatePicker/names.d.ts +8 -22
- package/dist/components/VDatePicker/names.js.map +1 -1
- package/dist/components/VDialog/VDialog.d.ts +5 -10
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +51 -53
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDialog/VDialogAlert.d.ts +2 -3
- package/dist/components/VDialog/VDialogAlert.js.map +1 -1
- package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +11 -37
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/truncate.d.ts +3 -23
- package/dist/components/VFileInput/truncate.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.d.ts +15 -65
- package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/fileKind.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.d.ts +6 -15
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.d.ts +10 -49
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VIcon/VIcon.d.ts +7 -19
- package/dist/components/VIcon/VIcon.js.map +1 -1
- package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VIcon/iconProps.d.ts +5 -19
- package/dist/components/VIcon/iconProps.js.map +1 -1
- package/dist/components/VIcon/resolver.d.ts +15 -43
- package/dist/components/VIcon/resolver.js.map +1 -1
- package/dist/components/VIcon/types.d.ts +7 -34
- package/dist/components/VIconButton/VIconButton.d.ts +2 -3
- package/dist/components/VIconButton/VIconButton.js.map +1 -1
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInput.d.ts +14 -29
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.js.map +1 -1
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +34 -50
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/context.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +8 -16
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +55 -63
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenu.js.map +1 -1
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.d.ts +1 -6
- package/dist/components/VMenu/VMenuGroup.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.d.ts +0 -5
- package/dist/components/VMenu/VMenuItem.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.d.ts +1 -6
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuSeparator.js.map +1 -1
- package/dist/components/VMenu/VMenuSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/context.d.ts +3 -11
- package/dist/components/VMenu/context.js.map +1 -1
- package/dist/components/VPagination/VPagination.d.ts +9 -20
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VPopover/VPopover.d.ts +7 -18
- package/dist/components/VPopover/VPopover.js.map +1 -1
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js +23 -27
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.d.ts +2 -6
- package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.d.ts +4 -15
- package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VRadio/VRadio.d.ts +1 -5
- package/dist/components/VRadio/VRadio.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.d.ts +2 -15
- package/dist/components/VSeparator/VSeparator.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.d.ts +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.d.ts +1 -5
- package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +1 -5
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +80 -75
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationSeparator.js.map +1 -1
- package/dist/components/VSideNavigation/context.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +6 -19
- package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSlider/VSlider.d.ts +5 -13
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/state.d.ts +4 -17
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.d.ts +3 -5
- package/dist/components/VSpinner/VSpinner.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTab.d.ts +1 -5
- package/dist/components/VTabs/VTab.js.map +1 -1
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabPanel.js.map +1 -1
- package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabs.d.ts +5 -17
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/context.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +13 -20
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.d.ts +8 -21
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/search.d.ts +3 -34
- package/dist/components/VTimeInput/search.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.d.ts +9 -31
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/limits.d.ts +18 -45
- package/dist/components/VTimePicker/limits.js.map +1 -1
- package/dist/components/VToast/VToast.js.map +1 -1
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/VToaster.js.map +1 -1
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/state.d.ts +2 -7
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/components/VToggle/VToggle.d.ts +5 -9
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.d.ts +1 -9
- package/dist/components/VToggle/VToggleItem.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/context.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.d.ts +3 -16
- package/dist/components/VTooltip/VTooltip.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTypography/VTypography.d.ts +4 -8
- package/dist/components/VTypography/VTypography.js.map +1 -1
- package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/useAriaLabel.js.map +1 -1
- package/dist/composables/useClearable.d.ts +2 -8
- package/dist/composables/useClearable.js.map +1 -1
- package/dist/composables/useControlShape.js.map +1 -1
- package/dist/composables/useDetailsOpen.d.ts +2 -10
- package/dist/composables/useDetailsOpen.js.map +1 -1
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.d.ts +4 -17
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileDrop.js.map +1 -1
- package/dist/composables/useFileField.d.ts +1 -5
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useFocusoutDismiss.js.map +1 -1
- package/dist/composables/useIconClickHandlers.d.ts +4 -19
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/composables/useInertLink.d.ts +1 -1
- package/dist/composables/useInertLink.js.map +1 -1
- package/dist/composables/useLiveAnnouncer.js.map +1 -1
- package/dist/composables/useMaskedField.d.ts +12 -27
- package/dist/composables/useMaskedField.js.map +1 -1
- package/dist/composables/usePopover.d.ts +3 -11
- package/dist/composables/usePopover.js.map +1 -1
- package/dist/composables/useProgressValue.js.map +1 -1
- package/dist/composables/useRootAttrs.d.ts +0 -9
- package/dist/composables/useRootAttrs.js +11 -4
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useSlotNodes.d.ts +3 -14
- package/dist/composables/useSlotNodes.js.map +1 -1
- package/dist/composables/useTextLimit.d.ts +4 -16
- package/dist/composables/useTextLimit.js.map +1 -1
- package/dist/composables/useTimer.d.ts +2 -15
- package/dist/composables/useTimer.js.map +1 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.d.ts +9 -38
- package/dist/i18n/state.js.map +1 -1
- package/dist/i18n/types.d.ts +19 -81
- package/dist/icons.d.ts +2 -12
- package/dist/tokens/css.d.ts +3 -16
- package/dist/tokens/css.js.map +1 -1
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/primitives.js.map +1 -1
- package/dist/tokens/semantic.d.ts +1 -23
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens/types.d.ts +2 -12
- package/dist/tokens/types.js.map +1 -1
- package/dist/types.d.ts +3 -21
- package/dist/utils/array.d.ts +2 -4
- package/dist/utils/array.js.map +1 -1
- package/dist/utils/arrowNav.d.ts +2 -26
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/chip.d.ts +1 -8
- package/dist/utils/chip.js.map +1 -1
- package/dist/utils/clock.d.ts +16 -39
- package/dist/utils/clock.js.map +1 -1
- package/dist/utils/css.d.ts +5 -16
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/date.d.ts +17 -60
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/direction.d.ts +2 -9
- package/dist/utils/direction.js.map +1 -1
- package/dist/utils/env.d.ts +2 -15
- package/dist/utils/env.js.map +1 -1
- package/dist/utils/file.d.ts +8 -46
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/focus.d.ts +1 -14
- package/dist/utils/focus.js +1 -6
- package/dist/utils/focus.js.map +1 -1
- package/dist/utils/hostWarns.d.ts +3 -8
- package/dist/utils/hostWarns.js.map +1 -1
- package/dist/utils/ids.d.ts +2 -7
- package/dist/utils/ids.js.map +1 -1
- package/dist/utils/matcher.d.ts +1 -8
- package/dist/utils/matcher.js.map +1 -1
- package/dist/utils/memo.d.ts +1 -11
- package/dist/utils/memo.js.map +1 -1
- package/dist/utils/number.d.ts +1 -7
- package/dist/utils/number.js.map +1 -1
- package/dist/utils/text.d.ts +4 -23
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/time.d.ts +4 -12
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/vnode.d.ts +0 -10
- package/dist/utils/vnode.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VHotkeys.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VHotkeys/VHotkeys.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * A keyboard shortcut, DISPLAYED. Written as a plain string — `mod+k` — and rendered with\n * the symbols of the system the reader is on: ⌘K on a Mac, Ctrl+K elsewhere.\n *\n * Two things here are unique in the library. It carries the ONLY `navigator` read, in\n * `onMounted` and nowhere else: the server cannot know the OS and the first client render\n * has to match what it sent, so a Mac visitor pays one frame of `Ctrl`. The `platform` prop\n * is the escape hatch, and what makes stories and tests deterministic.\n *\n * And `listen` attaches the library's ONLY document-level listener, which is why it must be\n * asked for: a component whose job is to display a shortcut must not silently capture the\n * page's keyboard.\n *\n * It is not interactive and not focusable, so it carries no hover, active or focus rule, and\n * therefore no transition and no reduced-motion block. That absence is deliberate — do not\n * copy VButton's state rules into it.\n */\n\nimport { computed, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'\n\nimport {\n DEFAULT_PLATFORM,\n capLabel,\n detectPlatform,\n isEditableTarget,\n matchesEvent,\n parseHotkeys,\n resolveKeys,\n} from './platform'\nimport type { HotkeysPlatform } from './platform'\nimport { useMessages } from '../../i18n/state'\n\n/** How a key cap is drawn. */\nexport type HotkeysVariant = 'soft' | 'outline' | 'elevated'\n/** The size of the caps, from the two smallest steps of the control scale. */\nexport type HotkeysSize = 'xs' | 'sm'\n\ninterface HotkeysProps {\n /**\n * The combination, `+`-separated: `mod+k`, `ctrl+shift+p`, `alt+enter`, `esc`.\n * Case- and space-insensitive. `mod` is the CROSS-PLATFORM modifier: ⌘ on\n * macOS, Ctrl everywhere else; `meta` is the literal Command/Windows/Super key.\n * Aliases: cmd/command/win/super → meta, control → ctrl, option/opt → alt,\n * return → enter, escape → esc, del → delete, arrowup… → up…. An unknown token\n * is displayed as declared (`k` → K, `f5` → F5). The `+` key is `plus`.\n */\n keys: string\n /** How a key cap is drawn: tinted (`soft`), outlined (`outline`), or raised off the page (`elevated`). */\n variant?: HotkeysVariant\n /**\n * Draws the whole combination as a SINGLE key rather than as several: the decoration\n * moves from each cap to the shortcut as a whole, so the separator ends up inside\n * the key instead of between two of them. It is purely visual: the markup and the\n * announced name are identical either way.\n */\n attached?: boolean\n /** The size of the caps, `xs` by default: a shortcut is chrome beside other text. */\n size?: HotkeysSize\n /** Takes 4px off the height, leaving the padding and the text as they are. */\n compact?: boolean\n /**\n * Forces the keyboard's OS instead of detecting it, for a deterministic\n * rendering (stories, tests, a table showing all three) or a host that already\n * knows (Electron, Tauri, a server reading the User-Agent).\n */\n platform?: HotkeysPlatform\n /**\n * What is written between two caps. An empty string gives the macOS convention,\n * where the symbols simply follow one another: ⇧⌘K.\n */\n separator?: string\n /**\n * Actually listens for the combination and reports it. It is off by default: a\n * component whose job is to display a shortcut must not capture the page's keyboard\n * without being asked.\n */\n listen?: boolean\n /**\n * While listening, lets the browser go on doing whatever the combination normally\n * does. Left out, the browser is stopped, which is the entire point of taking over\n * something like ⌘K. Escape is never stopped: it has to stay the close request of dialogs.\n */\n allowDefault?: boolean\n /**\n * While listening, fires even when the reader is typing in a field. It is off by\n * default, so a shortcut cannot fire in the middle of a sentence.\n */\n allowInInput?: boolean\n /**\n * What screen readers announce, \"Keyboard shortcut: Ctrl + K\" by default, from the\n * design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<HotkeysProps>(), {\n variant: 'soft',\n attached: false,\n size: 'xs',\n compact: false,\n platform: undefined,\n separator: '+',\n listen: false,\n allowDefault: false,\n allowInInput: false,\n label: undefined,\n})\n\nconst emit = defineEmits<{\n /** The combination was pressed. It is only ever emitted while `listen` is on. */\n trigger: [event: KeyboardEvent]\n}>()\n\nconst m = useMessages()\n\n// @ssr — the library's ONLY reading of the visitor's platform, and the reason it\n// happens after mounting.\n/* The operating system is read once the component is in the page and never during\n setup: a server has nothing to read it from, so rendering the detected value\n directly would make the two markups differ.\n\n TRAP — the value is held PER INSTANCE and not at module level. A shared one, having\n already resolved to macOS, would make a component hydrated later — a Nuxt island, a\n route loaded on demand — render ⌘ on its very first client pass where the server had\n written Ctrl. */\nconst detected = ref<HotkeysPlatform>(DEFAULT_PLATFORM)\nonMounted(() => {\n detected.value = detectPlatform()\n if (props.listen) attach()\n})\n\nconst platform = computed(() => props.platform ?? detected.value)\nconst tokens = computed(() => parseHotkeys(props.keys))\nconst resolved = computed(() => resolveKeys(tokens.value, platform.value))\n\n// @a11y\n/* The same table is read twice, in opposite directions. On SCREEN the symbol wins, ⌘\n being what is engraved on the key; in the ANNOUNCED NAME the word does, because that\n symbol is either passed over in silence or read out as \"place of interest sign\",\n depending on the screen reader. Inverting the preference is what lets one table\n serve both. */\nconst caps = computed(() =>\n resolved.value.map(\n (key) => key.glyph ?? (key.word ? m.value.hotkeys[key.word] : capLabel(key.token)),\n ),\n)\nconst spoken = computed(() =>\n resolved.value\n .map((key) => (key.word ? m.value.hotkeys[key.word] : (key.glyph ?? capLabel(key.token))))\n .join(' + '),\n)\nconst resolvedLabel = computed(() => props.label ?? m.value.hotkeys.label(spoken.value))\n\n// @core\n/* A plain variable and not a reactive one: nothing renders it, and making it reactive\n would cause renders for no reason — the same reasoning as in `useTimer`.\n\n TRAP — it is called `listening` and NOT `attached`. Every top-level binding of a\n `<script setup>` is exposed to the template, where it SHADOWS the prop of the same\n name: called `attached`, this flag would silently be what the template read instead\n of the prop, and the joined rendering would follow whether a listener happened to be\n installed. */\nlet listening = false\n\nfunction attach() {\n if (listening || typeof document === 'undefined') return\n document.addEventListener('keydown', onKeydown)\n listening = true\n}\n\nfunction detach() {\n if (!listening) return\n document.removeEventListener('keydown', onKeydown)\n listening = false\n}\n\n// @keyboard @core — the listening half. Deciding whether a key event IS the\n// combination is pure and lives in `platform.ts`.\nfunction onKeydown(event: KeyboardEvent) {\n /* A held-down combination repeats at the system's auto-repeat rate, which would\n reopen the consumer's palette a dozen times a second; only the first press\n counts. */\n if (!props.listen || event.repeat) return\n /* A key another handler has already dealt with (a menu's arrow, a field's Enter) is not\n a shortcut any more. And the Enter that confirms an input method's candidate is the\n end of a word being typed, not a command. */\n if (event.defaultPrevented || event.isComposing) return\n /* The document sees a key typed inside a shadow root as coming from the shadow HOST, a\n plain element: the field itself is the first entry of the composed path. */\n if (!props.allowInInput && isEditableTarget(event.composedPath()[0] ?? event.target)) return\n if (!matchesEvent(event, tokens.value, platform.value)) return\n /* TRAP — Escape is never cancelled. A cancelled Escape is not turned into a close request,\n so every open dialog and light-dismiss popover on the page would stop closing on it. */\n if (!props.allowDefault && !tokens.value.includes('esc')) event.preventDefault()\n emit('trigger', event)\n}\n\n/* Attaching the listener once and simply returning early inside it would be shorter to\n write, but a documentation page listing fifty shortcuts would then install fifty\n document listeners that do nothing. Following the prop is what keeps that at zero. */\nwatch(\n () => props.listen,\n (on) => (on ? attach() : detach()),\n)\nonBeforeUnmount(detach)\n/* A view kept alive by <KeepAlive> is never unmounted, only put aside: its shortcut must\n stop with it, or a hidden page would go on answering the keyboard. */\nonDeactivated(detach)\nonActivated(() => {\n if (props.listen) attach()\n})\n</script>\n\n<template>\n <kbd\n class=\"v-hotkeys v-control\"\n :data-variant=\"variant\"\n :data-attached=\"attached ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-platform=\"platform\"\n >\n <span class=\"v-hotkeys-keys\" aria-hidden=\"true\">\n <template v-for=\"(cap, index) in caps\" :key=\"index\">\n <span v-if=\"index > 0 && separator\" class=\"v-hotkeys-separator\">{{ separator }}</span>\n <kbd class=\"v-hotkeys-key\">{{ cap }}</kbd>\n </template>\n </span>\n <span class=\"v-visually-hidden\">{{ resolvedLabel }}</span>\n </kbd>\n</template>\n\n<style>\n@layer vectis.components {\n /* TRAP — the browser gives a `<kbd>` a monospaced family AND a smaller size, and\n that reduction COMPOUNDS when one `<kbd>` sits inside another, as it does here:\n the caps would come out at roughly 69% of the surrounding text. Both are reset\n here and again on the caps themselves, and deleting either line silently\n miniaturizes the whole component.\n\n The vertical alignment is the second correction: a box laid out this way sits on\n the baseline of its FIRST item, which drops the caps below the line of prose\n around them. */\n .v-hotkeys {\n display: inline-flex;\n align-items: center;\n /* Centres the content in the cases where the minimum width wins over it: a\n single-character combination drawn as one key. */\n justify-content: center;\n vertical-align: middle;\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n --hotkeys-pad: var(--control-padding-inline);\n }\n\n /* The size comes from the shared class set on the ROOT: its variables inherit down,\n and it is the CAPS that read the height from them. The scale itself holds five\n steps; the component's type restricts it to the two smallest, a key cap being\n smaller than a control. */\n .v-hotkeys-keys {\n display: inline-flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-hotkeys-key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n }\n\n /* The three variants are tinted (`soft`, the word VButton and VChip use for a tinted\n surface, where `flat` names the absence of decoration on VTabs, VAccordion and\n VDataTable), outlined and raised, and EVERY paint here derives\n from `currentcolor` rather than from a surface token — the argument the separator\n at the bottom of this sheet already makes, carried through to the caps themselves.\n\n A shortcut is chrome, so it sits in whatever surrounds it: a paragraph, a field, a\n menu row, a tooltip painted on an inverse surface. Greys chosen against the page\n are wrong in half of those, and the failure is silent in the worst way — the caps\n kept the page's text colour while the ground under them was the opposite one, so\n on a light theme a shortcut inside a tooltip wrote dark keys on a dark panel.\n Deriving the whole cap from the inherited colour is what makes the component\n ground-independent, and it is the same reason there is no tone table at all.\n\n They set VARIABLES rather than declaring the look straight away, because that look\n has two possible carriers (see below). The names are qualified on purpose: these\n variables inherit, so a bare `--bg` would be captured by any ancestor in the host\n application that happened to define one. `currentcolor` inside them is resolved on\n whichever element ends up USING them, which is exactly what is wanted here. */\n .v-hotkeys[data-variant='soft'] {\n --hotkeys-bg: color-mix(in oklab, currentcolor, transparent 90%);\n --hotkeys-border: transparent;\n --hotkeys-shadow: none;\n }\n\n .v-hotkeys[data-variant='outline'] {\n --hotkeys-bg: transparent;\n --hotkeys-border: color-mix(in oklab, currentcolor, transparent 70%);\n --hotkeys-shadow: none;\n }\n\n /* Lifted rather than settled into its ground, so the tint is the lighter of the two\n and the shadow does the work. That shadow is the one paint left that is not\n derived: it is a cast rather than a colour, so on a dark ground it simply has\n nothing to fall on, and the tint is what still separates the cap there. */\n .v-hotkeys[data-variant='elevated'] {\n --hotkeys-bg: color-mix(in oklab, currentcolor, transparent 94%);\n --hotkeys-border: transparent;\n --hotkeys-shadow: var(--vectis-shadow-sm);\n }\n\n /* When one key holds the WHOLE combination, its ends take the same breathing room as\n the gaps inside it, which gives the key a single rhythm. The usual control padding\n is sized to wrap ONE short label; around three runs of text already spaced from\n one another, it reads as slack at the edges. */\n .v-hotkeys[data-attached] {\n --hotkeys-pad: var(--control-gap);\n }\n\n /* THE key recipe, written once for its two possible carriers: every cap by default,\n and the whole shortcut alone when it is drawn as a single key — which is exactly\n what puts the separator inside the key rather than between two of them. Written\n out twice, the two renderings would drift apart at the first token change. */\n .v-hotkeys[data-attached],\n .v-hotkeys:not([data-attached]) .v-hotkeys-key {\n height: var(--control-height);\n /* A minimum equal to the height makes a single-character key read as a square\n rather than as a sliver. */\n min-inline-size: var(--control-height);\n padding-inline: var(--hotkeys-pad);\n border: 1px solid var(--hotkeys-border);\n border-radius: var(--vectis-radius-interactive);\n background: var(--hotkeys-bg);\n box-shadow: var(--hotkeys-shadow);\n }\n\n /* Softened rather than full strength: the separator is punctuation between two\n caps, and at the same weight as them it competes with what it separates. */\n .v-hotkeys-separator {\n color: color-mix(in oklab, currentcolor, transparent 40%);\n }\n\n /* Forced colours flatten the tint and drop the shadow, which is all a soft or an\n elevated cap is made of. Every variant takes the edge of the outlined one, in the\n system text colour. At (0,2,0) and later in the sheet, this beats the variant rules. */\n @media (forced-colors: active) {\n .v-hotkeys[data-variant] {\n --hotkeys-border: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgGA,IAAM,IAAQ,GAaR,IAAO,GAKP,IAAI,EAAY,GAYhB,IAAW,EAAqB,CAAgB;EACtD,QAAgB;GAEd,AADA,EAAS,QAAQ,EAAe,GAC5B,EAAM,UAAQ,EAAO;EAC3B,CAAC;EAED,IAAM,IAAW,QAAe,EAAM,YAAY,EAAS,KAAK,GAC1D,IAAS,QAAe,EAAa,EAAM,IAAI,CAAC,GAChD,IAAW,QAAe,EAAY,EAAO,OAAO,EAAS,KAAK,CAAC,GAQnE,IAAO,QACX,EAAS,MAAM,KACZ,MAAQ,EAAI,UAAU,EAAI,OAAO,EAAE,MAAM,QAAQ,EAAI,QAAQ,EAAS,EAAI,KAAK,EAClF,CACF,GACM,IAAS,QACb,EAAS,MACN,KAAK,MAAS,EAAI,OAAO,EAAE,MAAM,QAAQ,EAAI,QAAS,EAAI,SAAS,EAAS,EAAI,KAAK,CAAG,CAAA,CACxF,KAAK,KAAK,CACf,GACM,IAAgB,QAAe,EAAM,SAAS,EAAE,MAAM,QAAQ,MAAM,EAAO,KAAK,CAAC,GAWnF,IAAY;EAEhB,SAAS,IAAS;GACZ,KAAa,OAAO,WAAa,QACrC,SAAS,iBAAiB,WAAW,CAAS,GAC9C,IAAY;EACd;EAEA,SAAS,IAAS;GACX,AAEL,OADA,SAAS,oBAAoB,WAAW,CAAS,GACrC;EACd;EAIA,SAAS,EAAU,GAAsB;GAInC,CAAC,EAAM,UAAU,EAAM,UAIvB,EAAM,oBAAoB,EAAM,eAGhC,CAAC,EAAM,gBAAgB,EAAiB,EAAM,aAAa,CAAC,CAAC,MAAM,EAAM,MAAM,KAC9E,EAAa,GAAO,EAAO,OAAO,EAAS,KAAK,MAGjD,CAAC,EAAM,gBAAgB,CAAC,EAAO,MAAM,SAAS,KAAK,KAAG,EAAM,eAAe,GAC/E,EAAK,WAAW,CAAK;EACvB;SAKA,QACQ,EAAM,SACX,MAAQ,IAAK,EAAO,IAAI,EAAO,CAClC,GACA,EAAgB,CAAM,GAGtB,EAAc,CAAM,GACpB,QAAkB;GAChB,AAAI,EAAM,UAAQ,EAAO;EAC3B,CAAC,mBAIC,EAeM,OAAA;GAdJ,OAAM;GACL,gBAAc,EAAA;GACd,iBAAe,EAAA,WAAQ,KAAQ,KAAA;GAC/B,aAAW,EAAA;GACX,gBAAc,EAAA,UAAO,KAAQ,KAAA;GAC7B,iBAAe,EAAA;MAEhB,EAKO,QALP,GAKO,EAAA,EAAA,EAAA,GAJL,EAGW,GAAA,MAAA,EAHsB,EAAA,QAAf,GAAK,wBAAsB,EAAK,GAAA,CACpC,IAAK,KAAQ,EAAA,aAAA,EAAA,GAAzB,EAAsF,QAAtF,GAAsF,EAAnB,EAAA,SAAS,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAC5E,EAA0C,OAA1C,GAA0C,EAAZ,CAAG,GAAA,CAAA,CAAA,GAAA,EAAA,cAGrC,EAA0D,QAA1D,GAA0D,EAAvB,EAAA,KAAa,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VHotkeys.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VHotkeys/VHotkeys.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Render platform-specific shortcut glyphs. Detect the platform after mount for SSR; attach\n * keyboard listeners only when listen is enabled.\n */\n\nimport { computed, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'\n\nimport {\n DEFAULT_PLATFORM,\n capLabel,\n detectPlatform,\n isEditableTarget,\n matchesEvent,\n parseHotkeys,\n resolveKeys,\n} from './platform'\nimport type { HotkeysPlatform } from './platform'\nimport { useMessages } from '../../i18n/state'\n\n/** How a key cap is drawn. */\nexport type HotkeysVariant = 'soft' | 'outline' | 'elevated'\n/** The size of the caps, from the two smallest steps of the control scale. */\nexport type HotkeysSize = 'xs' | 'sm'\n\ninterface HotkeysProps {\n /** The combination, `+`-separated: `mod+k`, `ctrl+shift+p`, `alt+enter`, `esc`. */\n keys: string\n /** How a key cap is drawn: tinted (`soft`), outlined (`outline`), or raised off the page (`elevated`). */\n variant?: HotkeysVariant\n /**\n * Draws the whole combination as a SINGLE key rather than as several: the decoration moves\n * from each cap to the shortcut as a whole, so the separator ends up inside the key instead\n * of between two of them.\n */\n attached?: boolean\n /** The size of the caps, `xs` by default: a shortcut is chrome beside other text. */\n size?: HotkeysSize\n /** Takes 4px off the height, leaving the padding and the text as they are. */\n compact?: boolean\n /**\n * Forces the keyboard's OS instead of detecting it, for a deterministic\n * rendering (stories, tests, a table showing all three) or a host that already\n * knows (Electron, Tauri, a server reading the User-Agent).\n */\n platform?: HotkeysPlatform\n /**\n * What is written between two caps. An empty string gives the macOS convention,\n * where the symbols simply follow one another: ⇧⌘K.\n */\n separator?: string\n /**\n * Actually listens for the combination and reports it. It is off by default: a\n * component whose job is to display a shortcut must not capture the page's keyboard\n * without being asked.\n */\n listen?: boolean\n /**\n * While listening, lets the browser go on doing whatever the combination normally does. Left\n * out, the browser is stopped, which is the entire point of taking over something like ⌘K.\n */\n allowDefault?: boolean\n /**\n * While listening, fires even when the reader is typing in a field. It is off by\n * default, so a shortcut cannot fire in the middle of a sentence.\n */\n allowInInput?: boolean\n /**\n * What screen readers announce, \"Keyboard shortcut: Ctrl + K\" by default, from the\n * design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<HotkeysProps>(), {\n variant: 'soft',\n attached: false,\n size: 'xs',\n compact: false,\n platform: undefined,\n separator: '+',\n listen: false,\n allowDefault: false,\n allowInInput: false,\n label: undefined,\n})\n\nconst emit = defineEmits<{\n /** The combination was pressed. It is only ever emitted while `listen` is on. */\n trigger: [event: KeyboardEvent]\n}>()\n\nconst m = useMessages()\n\n// @ssr\n/*\n * The value is held per instance and not at module level. A shared one, having already resolved\n * to macOS, would make a component hydrated later; a Nuxt island, a route loaded on demand;\n * render ⌘ on its very first client pass where the server had written Ctrl.\n */\nconst detected = ref<HotkeysPlatform>(DEFAULT_PLATFORM)\nonMounted(() => {\n detected.value = detectPlatform()\n if (props.listen) attach()\n})\n\nconst platform = computed(() => props.platform ?? detected.value)\nconst tokens = computed(() => parseHotkeys(props.keys))\nconst resolved = computed(() => resolveKeys(tokens.value, platform.value))\n\n// @a11y\n/*\n * On SCREEN the symbol wins, ⌘ being what is engraved on the key; in the ANNOUNCED NAME the\n * word does, because that symbol is either passed over in silence or read out as \"place of\n * interest sign\", depending on the screen reader. Inverting the preference is what lets one\n * table serve both.\n */\nconst caps = computed(() =>\n resolved.value.map(\n (key) => key.glyph ?? (key.word ? m.value.hotkeys[key.word] : capLabel(key.token)),\n ),\n)\nconst spoken = computed(() =>\n resolved.value\n .map((key) => (key.word ? m.value.hotkeys[key.word] : (key.glyph ?? capLabel(key.token))))\n .join(' + '),\n)\nconst resolvedLabel = computed(() => props.label ?? m.value.hotkeys.label(spoken.value))\n\n// @core\n/*\n * It is called `listening` and not `attached`. Every top-level binding of a `<script setup>` is\n * exposed to the template, where it SHADOWS the prop of the same name: called `attached`, this\n * flag would silently be what the template read instead of the prop, and the joined rendering\n * would follow whether a listener happened to be installed.\n */\nlet listening = false\n\nfunction attach() {\n if (listening || typeof document === 'undefined') return\n document.addEventListener('keydown', onKeydown)\n listening = true\n}\n\nfunction detach() {\n if (!listening) return\n document.removeEventListener('keydown', onKeydown)\n listening = false\n}\n\n// @keyboard @core\nfunction onKeydown(event: KeyboardEvent) {\n /* A held-down combination repeats at the system's auto-repeat rate, which would\n reopen the consumer's palette a dozen times a second; only the first press\n counts. */\n if (!props.listen || event.repeat) return\n /* A key another handler has already dealt with (a menu's arrow, a field's Enter) is not\n a shortcut any more. And the Enter that confirms an input method's candidate is the\n end of a word being typed, not a command. */\n if (event.defaultPrevented || event.isComposing) return\n /* The document sees a key typed inside a shadow root as coming from the shadow HOST, a\n plain element: the field itself is the first entry of the composed path. */\n if (!props.allowInInput && isEditableTarget(event.composedPath()[0] ?? event.target)) return\n if (!matchesEvent(event, tokens.value, platform.value)) return\n /*\n * Escape is never cancelled. A cancelled Escape is not turned into a close request, so every\n * open dialog and light-dismiss popover on the page would stop closing on it.\n */\n if (!props.allowDefault && !tokens.value.includes('esc')) event.preventDefault()\n emit('trigger', event)\n}\n\n/* Attaching the listener once and simply returning early inside it would be shorter to\n write, but a documentation page listing fifty shortcuts would then install fifty\n document listeners that do nothing. Following the prop is what keeps that at zero. */\nwatch(\n () => props.listen,\n (on) => (on ? attach() : detach()),\n)\nonBeforeUnmount(detach)\n/* A view kept alive by <KeepAlive> is never unmounted, only put aside: its shortcut must\n stop with it, or a hidden page would go on answering the keyboard. */\nonDeactivated(detach)\nonActivated(() => {\n if (props.listen) attach()\n})\n</script>\n\n<template>\n <kbd\n class=\"v-hotkeys v-control\"\n :data-variant=\"variant\"\n :data-attached=\"attached ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-platform=\"platform\"\n >\n <span class=\"v-hotkeys-keys\" aria-hidden=\"true\">\n <template v-for=\"(cap, index) in caps\" :key=\"index\">\n <span v-if=\"index > 0 && separator\" class=\"v-hotkeys-separator\">{{ separator }}</span>\n <kbd class=\"v-hotkeys-key\">{{ cap }}</kbd>\n </template>\n </span>\n <span class=\"v-visually-hidden\">{{ resolvedLabel }}</span>\n </kbd>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The browser gives a `<kbd>` a monospaced family and a smaller size, and that reduction\n * COMPOUNDS when one `<kbd>` sits inside another, as it does here: the caps would come out at\n * roughly 69% of the surrounding text. Both are reset here and again on the caps themselves,\n * and deleting either line silently miniaturizes the whole component.\n */\n .v-hotkeys {\n display: inline-flex;\n align-items: center;\n /* Centres the content in the cases where the minimum width wins over it: a\n single-character combination drawn as one key. */\n justify-content: center;\n vertical-align: middle;\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n --hotkeys-pad: var(--control-padding-inline);\n }\n\n /* The size comes from the shared class set on the ROOT: its variables inherit down,\n and it is the CAPS that read the height from them. The scale itself holds five\n steps; the component's type restricts it to the two smallest, a key cap being\n smaller than a control. */\n .v-hotkeys-keys {\n display: inline-flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-hotkeys-key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n font-size: inherit;\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n }\n\n /*\n * Deriving the whole cap from the inherited colour is what makes the component\n * ground-independent, and it is the same reason there is no tone table at all. They set\n * VARIABLES rather than declaring the look straight away, because that look has two possible\n * carriers (see below).\n */\n .v-hotkeys[data-variant='soft'] {\n --hotkeys-bg: color-mix(in oklab, currentcolor, transparent 90%);\n --hotkeys-border: transparent;\n --hotkeys-shadow: none;\n }\n\n .v-hotkeys[data-variant='outline'] {\n --hotkeys-bg: transparent;\n --hotkeys-border: color-mix(in oklab, currentcolor, transparent 70%);\n --hotkeys-shadow: none;\n }\n\n /* Lifted rather than settled into its ground, so the tint is the lighter of the two\n and the shadow does the work. That shadow is the one paint left that is not\n derived: it is a cast rather than a colour, so on a dark ground it simply has\n nothing to fall on, and the tint is what still separates the cap there. */\n .v-hotkeys[data-variant='elevated'] {\n --hotkeys-bg: color-mix(in oklab, currentcolor, transparent 94%);\n --hotkeys-border: transparent;\n --hotkeys-shadow: var(--vectis-shadow-sm);\n }\n\n /*\n * When one key holds the whole combination, its ends take the same breathing room as the gaps\n * inside it, which gives the key a single rhythm. The usual control padding is sized to wrap\n * ONE short label; around three runs of text already spaced from one another, it reads as\n * slack at the edges.\n */\n .v-hotkeys[data-attached] {\n --hotkeys-pad: var(--control-gap);\n }\n\n /*\n * THE key recipe, written once for its two possible carriers: every cap by default, and the\n * whole shortcut alone when it is drawn as a single key; which is exactly what puts the\n * separator inside the key rather than between two of them. Written out twice, the two\n * renderings would drift apart at the first token change.\n */\n .v-hotkeys[data-attached],\n .v-hotkeys:not([data-attached]) .v-hotkeys-key {\n height: var(--control-height);\n /* A minimum equal to the height makes a single-character key read as a square\n rather than as a sliver. */\n min-inline-size: var(--control-height);\n padding-inline: var(--hotkeys-pad);\n border: 1px solid var(--hotkeys-border);\n border-radius: var(--vectis-radius-interactive);\n background: var(--hotkeys-bg);\n box-shadow: var(--hotkeys-shadow);\n }\n\n /* Softened rather than full strength: the separator is punctuation between two\n caps, and at the same weight as them it competes with what it separates. */\n .v-hotkeys-separator {\n color: color-mix(in oklab, currentcolor, transparent 40%);\n }\n\n /*\n * Forced colours flatten the tint and drop the shadow, which is all a soft or an elevated cap\n * is made of. Every variant takes the edge of the outlined one, in the system text colour.\n */\n @media (forced-colors: active) {\n .v-hotkeys[data-variant] {\n --hotkeys-border: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0EA,IAAM,IAAQ,GAaR,IAAO,GAKP,IAAI,EAAY,GAQhB,IAAW,EAAqB,CAAgB;EACtD,QAAgB;GAEd,AADA,EAAS,QAAQ,EAAe,GAC5B,EAAM,UAAQ,EAAO;EAC3B,CAAC;EAED,IAAM,IAAW,QAAe,EAAM,YAAY,EAAS,KAAK,GAC1D,IAAS,QAAe,EAAa,EAAM,IAAI,CAAC,GAChD,IAAW,QAAe,EAAY,EAAO,OAAO,EAAS,KAAK,CAAC,GASnE,IAAO,QACX,EAAS,MAAM,KACZ,MAAQ,EAAI,UAAU,EAAI,OAAO,EAAE,MAAM,QAAQ,EAAI,QAAQ,EAAS,EAAI,KAAK,EAClF,CACF,GACM,IAAS,QACb,EAAS,MACN,KAAK,MAAS,EAAI,OAAO,EAAE,MAAM,QAAQ,EAAI,QAAS,EAAI,SAAS,EAAS,EAAI,KAAK,CAAG,CAAA,CACxF,KAAK,KAAK,CACf,GACM,IAAgB,QAAe,EAAM,SAAS,EAAE,MAAM,QAAQ,MAAM,EAAO,KAAK,CAAC,GASnF,IAAY;EAEhB,SAAS,IAAS;GACZ,KAAa,OAAO,WAAa,QACrC,SAAS,iBAAiB,WAAW,CAAS,GAC9C,IAAY;EACd;EAEA,SAAS,IAAS;GACX,AAEL,OADA,SAAS,oBAAoB,WAAW,CAAS,GACrC;EACd;EAGA,SAAS,EAAU,GAAsB;GAInC,CAAC,EAAM,UAAU,EAAM,UAIvB,EAAM,oBAAoB,EAAM,eAGhC,CAAC,EAAM,gBAAgB,EAAiB,EAAM,aAAa,CAAC,CAAC,MAAM,EAAM,MAAM,KAC9E,EAAa,GAAO,EAAO,OAAO,EAAS,KAAK,MAKjD,CAAC,EAAM,gBAAgB,CAAC,EAAO,MAAM,SAAS,KAAK,KAAG,EAAM,eAAe,GAC/E,EAAK,WAAW,CAAK;EACvB;SAKA,QACQ,EAAM,SACX,MAAQ,IAAK,EAAO,IAAI,EAAO,CAClC,GACA,EAAgB,CAAM,GAGtB,EAAc,CAAM,GACpB,QAAkB;GAChB,AAAI,EAAM,UAAQ,EAAO;EAC3B,CAAC,mBAIC,EAeM,OAAA;GAdJ,OAAM;GACL,gBAAc,EAAA;GACd,iBAAe,EAAA,WAAQ,KAAQ,KAAA;GAC/B,aAAW,EAAA;GACX,gBAAc,EAAA,UAAO,KAAQ,KAAA;GAC7B,iBAAe,EAAA;MAEhB,EAKO,QALP,GAKO,EAAA,EAAA,EAAA,GAJL,EAGW,GAAA,MAAA,EAHsB,EAAA,QAAf,GAAK,wBAAsB,EAAK,GAAA,CACpC,IAAK,KAAQ,EAAA,aAAA,EAAA,GAAzB,EAAsF,QAAtF,GAAsF,EAAnB,EAAA,SAAS,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAC5E,EAA0C,OAA1C,GAA0C,EAAZ,CAAG,GAAA,CAAA,CAAA,GAAA,EAAA,cAGrC,EAA0D,QAA1D,GAA0D,EAAvB,EAAA,KAAa,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA"}
|
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* it, and to what the browser reports when it is pressed.
|
|
5
|
-
*
|
|
6
|
-
* An operating system is not a language, and the distinction matters here. Whether a
|
|
7
|
-
* key shows ⌘ or Ctrl is a fact about the hardware, so it does NOT depend on the
|
|
8
|
-
* reader's language. Only the WORDS do — Shift becomes Maj in French — and those live
|
|
9
|
-
* in the dictionary: this module hands back the dictionary KEY and never the text
|
|
10
|
-
* itself.
|
|
11
|
-
*
|
|
12
|
-
* What earns the module its existence is the alias table. The SAME table normalizes
|
|
13
|
-
* the tokens a consumer writes in `keys` and the key names the browser reports, so a
|
|
14
|
-
* shortcut cannot be declared in a spelling the matcher would fail to recognize.
|
|
15
|
-
*
|
|
16
|
-
* TRAP — detecting the platform must only ever be done once a component is mounted.
|
|
17
|
-
* There is nothing to read it from on a server, and sniffing a request header is not
|
|
18
|
-
* this library's business.
|
|
2
|
+
* Detecting the platform must only ever be done once a component is mounted. There is nothing
|
|
3
|
+
* to read it from on a server, and sniffing a request header is not this library's business.
|
|
19
4
|
*/
|
|
20
5
|
/** The system a keyboard belongs to, which decides its symbols and some of its words. */
|
|
21
6
|
export type HotkeysPlatform = 'mac' | 'windows' | 'linux' | 'other';
|
|
22
7
|
/**
|
|
23
|
-
* The dictionary entries naming a key, and only those: the sentence framing the
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* The list is written out here rather than derived from the dictionary's own type,
|
|
27
|
-
* which would tie this module to the dictionary's shape. Nothing is lost: reading an
|
|
28
|
-
* entry by one of these names is checked just as strictly at the point of use.
|
|
8
|
+
* The dictionary entries naming a key, and only those: the sentence framing the shortcut is not
|
|
9
|
+
* one of them.
|
|
29
10
|
*/
|
|
30
11
|
export type HotkeysWord = 'command' | 'ctrl' | 'alt' | 'shift' | 'windows' | 'super' | 'enter' | 'escape' | 'space' | 'backspace' | 'delete' | 'tab' | 'up' | 'down' | 'left' | 'right';
|
|
31
12
|
/** One key of a shortcut, once resolved for a given platform. */
|
|
32
13
|
export interface ResolvedKey {
|
|
33
|
-
/** Its canonical name, which
|
|
14
|
+
/** Its canonical name, which the matcher compares a key press against. */
|
|
34
15
|
token: string;
|
|
35
16
|
/** The symbol engraved on that key, where the system engraves one. */
|
|
36
17
|
glyph?: string;
|
|
@@ -40,27 +21,13 @@ export interface ResolvedKey {
|
|
|
40
21
|
*/
|
|
41
22
|
word?: HotkeysWord;
|
|
42
23
|
}
|
|
43
|
-
/**
|
|
44
|
-
* What a SERVER renders, and therefore what the browser's FIRST render must be as
|
|
45
|
-
* well. It shares Ctrl, Alt and Shift with Windows and Linux, so a Mac visitor pays a
|
|
46
|
-
* single frame of "Ctrl" before it is corrected to ⌘. The literal `meta` key is the one
|
|
47
|
-
* word it shares with Linux alone: a Windows visitor sees "Super" for a frame before "Win".
|
|
48
|
-
*/
|
|
24
|
+
/** What a SERVER renders, and therefore what the browser's first render must be as well. */
|
|
49
25
|
export declare const DEFAULT_PLATFORM: HotkeysPlatform;
|
|
50
|
-
/**
|
|
51
|
-
* Works out which system the keyboard belongs to, from two sources in order. The
|
|
52
|
-
* modern one is the more reliable; the older one is deprecated and frozen, but it is
|
|
53
|
-
* still the only one Firefox and Safari provide.
|
|
54
|
-
*
|
|
55
|
-
* The modern one is missing from TypeScript's own definitions, so it is described by a
|
|
56
|
-
* type declared LOCALLY rather than added to the global ones: the latter would leak
|
|
57
|
-
* out into every consumer's compilation.
|
|
58
|
-
*/
|
|
26
|
+
/** Works out which system the keyboard belongs to, from two sources in order. */
|
|
59
27
|
export declare function detectPlatform(): HotkeysPlatform;
|
|
60
28
|
/**
|
|
61
|
-
* Reads a combination as it was written and returns its keys in canonical form:
|
|
62
|
-
* `'
|
|
63
|
-
* trailing separator is harmless, and the `+` KEY itself is written `plus`.
|
|
29
|
+
* Reads a combination as it was written and returns its keys in canonical form: `'mod+k'` and
|
|
30
|
+
* `' Mod + K '` both give the same two.
|
|
64
31
|
*/
|
|
65
32
|
export declare function parseHotkeys(keys: string): string[];
|
|
66
33
|
/**
|
|
@@ -73,12 +40,6 @@ export declare function capLabel(token: string): string;
|
|
|
73
40
|
* on it and the dictionary entry naming it, where either exists.
|
|
74
41
|
*/
|
|
75
42
|
export declare function resolveKeys(tokens: string[], platform: HotkeysPlatform): ResolvedKey[];
|
|
76
|
-
/**
|
|
77
|
-
* Whether a key press IS this combination.
|
|
78
|
-
*
|
|
79
|
-
* The modifiers are compared EXACTLY, and never as "at least these ones". Without
|
|
80
|
-
* that, `mod+k` and `mod+shift+k` could not coexist in one application: the first
|
|
81
|
-
* would swallow every press meant for the second.
|
|
82
|
-
*/
|
|
43
|
+
/** Whether a key press IS this combination. */
|
|
83
44
|
export declare function matchesEvent(event: KeyboardEvent, tokens: string[], platform: HotkeysPlatform): boolean;
|
|
84
45
|
export declare function isEditableTarget(target: EventTarget | null | undefined): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.js","names":[],"sources":["../../../src/components/VHotkeys/platform.ts"],"sourcesContent":["// @core — module-wide: pure, no Vue and no lifecycle. `detectPlatform` is the\n// exception below.\n/**\n * Everything VHotkeys needs to know about keyboards: which system this one belongs to,\n * and how a written token relates to the symbol engraved on the key, to the word for\n * it, and to what the browser reports when it is pressed.\n *\n * An operating system is not a language, and the distinction matters here. Whether a\n * key shows ⌘ or Ctrl is a fact about the hardware, so it does NOT depend on the\n * reader's language. Only the WORDS do — Shift becomes Maj in French — and those live\n * in the dictionary: this module hands back the dictionary KEY and never the text\n * itself.\n *\n * What earns the module its existence is the alias table. The SAME table normalizes\n * the tokens a consumer writes in `keys` and the key names the browser reports, so a\n * shortcut cannot be declared in a spelling the matcher would fail to recognize.\n *\n * TRAP — detecting the platform must only ever be done once a component is mounted.\n * There is nothing to read it from on a server, and sniffing a request header is not\n * this library's business.\n */\n\nimport { isDev } from '../../utils/env'\n\n/** The system a keyboard belongs to, which decides its symbols and some of its words. */\nexport type HotkeysPlatform = 'mac' | 'windows' | 'linux' | 'other'\n\n/**\n * The dictionary entries naming a key, and only those: the sentence framing the\n * shortcut is not one of them.\n *\n * The list is written out here rather than derived from the dictionary's own type,\n * which would tie this module to the dictionary's shape. Nothing is lost: reading an\n * entry by one of these names is checked just as strictly at the point of use.\n */\nexport type HotkeysWord =\n | 'command'\n | 'ctrl'\n | 'alt'\n | 'shift'\n | 'windows'\n | 'super'\n | 'enter'\n | 'escape'\n | 'space'\n | 'backspace'\n | 'delete'\n | 'tab'\n | 'up'\n | 'down'\n | 'left'\n | 'right'\n\n/** One key of a shortcut, once resolved for a given platform. */\nexport interface ResolvedKey {\n /** Its canonical name, which is what the matcher compares a key press against. */\n token: string\n /** The symbol engraved on that key, where the system engraves one. */\n glyph?: string\n /**\n * Which dictionary entry names it. Nothing here means the design system does not\n * know this key, and it is printed as it was written.\n */\n word?: HotkeysWord\n}\n\n// @ssr\n/**\n * What a SERVER renders, and therefore what the browser's FIRST render must be as\n * well. It shares Ctrl, Alt and Shift with Windows and Linux, so a Mac visitor pays a\n * single frame of \"Ctrl\" before it is corrected to ⌘. The literal `meta` key is the one\n * word it shares with Linux alone: a Windows visitor sees \"Super\" for a frame before \"Win\".\n */\nexport const DEFAULT_PLATFORM: HotkeysPlatform = 'other'\n\n// @ssr @fallback — the DS's ONLY `navigator` read, with a two-source ladder.\n/**\n * Works out which system the keyboard belongs to, from two sources in order. The\n * modern one is the more reliable; the older one is deprecated and frozen, but it is\n * still the only one Firefox and Safari provide.\n *\n * The modern one is missing from TypeScript's own definitions, so it is described by a\n * type declared LOCALLY rather than added to the global ones: the latter would leak\n * out into every consumer's compilation.\n */\nexport function detectPlatform(): HotkeysPlatform {\n if (typeof navigator === 'undefined') return DEFAULT_PLATFORM\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } }\n const source = (nav.userAgentData?.platform || nav.platform || '').toLowerCase()\n /* iPadOS reports itself as a Mac, which is fine here: an attached keyboard carries\n the same modifier symbols either way. */\n if (/mac|iphone|ipad|ipod/.test(source)) return 'mac'\n if (source.includes('win')) return 'windows'\n /* ChromeOS spells itself two different ways depending on which of the two sources\n answered. Both have to be listed, or the branch is dead precisely where it\n matters. */\n if (/linux|android|cros|chrome os/.test(source)) return 'linux'\n return DEFAULT_PLATFORM\n}\n\n/* Every spelling accepted in the `keys` prop, and at the same time the table that\n normalizes what the browser reports. Serving both is what guarantees the two can\n never disagree.\n\n The `+` key is canonically named `plus`, since `+` is the separator and cannot name\n itself in the prop — but the browser does report it as `'+'`, hence its presence\n here. */\nconst ALIASES: Record<string, string> = {\n cmd: 'meta',\n command: 'meta',\n win: 'meta',\n windows: 'meta',\n super: 'meta',\n os: 'meta',\n cmdorctrl: 'mod',\n commandorcontrol: 'mod',\n control: 'ctrl',\n option: 'alt',\n opt: 'alt',\n return: 'enter',\n escape: 'esc',\n spacebar: 'space',\n ' ': 'space',\n del: 'delete',\n arrowup: 'up',\n arrowdown: 'down',\n arrowleft: 'left',\n arrowright: 'right',\n '+': 'plus',\n}\n\n/* The symbols engraved on an Apple keyboard, and on no other. Escape and the space bar\n are deliberately absent: the Mac key really does read \"esc\", and the space bar\n carries no symbol at all. */\nconst MAC_GLYPHS: Record<string, string> = {\n mod: '⌘',\n meta: '⌘',\n ctrl: '⌃',\n alt: '⌥',\n shift: '⇧',\n enter: '↵',\n backspace: '⌫',\n delete: '⌦',\n tab: '⇥',\n}\n\n/* Symbols that are the same everywhere: an arrow key is engraved with an arrow on\n every keyboard ever made. */\nconst GLYPHS: Record<string, string> = {\n up: '↑',\n down: '↓',\n left: '←',\n right: '→',\n plus: '+',\n}\n\nconst WORDS: Record<string, HotkeysWord> = {\n ctrl: 'ctrl',\n alt: 'alt',\n shift: 'shift',\n enter: 'enter',\n esc: 'escape',\n space: 'space',\n backspace: 'backspace',\n delete: 'delete',\n tab: 'tab',\n up: 'up',\n down: 'down',\n left: 'left',\n right: 'right',\n}\n\nconst MODIFIERS = new Set(['mod', 'meta', 'ctrl', 'alt', 'shift'])\n\n/**\n * The kinds of field one cannot type prose into. A shortcut stays live over a\n * checkbox, a button or a colour picker: there is no sentence to interrupt there.\n */\nconst NON_TEXT_INPUT_TYPES = new Set([\n 'button',\n 'submit',\n 'reset',\n 'checkbox',\n 'radio',\n 'file',\n 'range',\n 'color',\n 'image',\n])\n\n/**\n * Reads a combination as it was written and returns its keys in canonical form:\n * `'mod+k'` and `' Mod + K '` both give the same two. Empty segments are dropped, so a\n * trailing separator is harmless, and the `+` KEY itself is written `plus`.\n */\nexport function parseHotkeys(keys: string): string[] {\n const tokens = keys\n .split('+')\n .map((token) => token.trim().toLowerCase())\n .filter(Boolean)\n .map((token) => ALIASES[token] ?? token)\n if (isDev) {\n const pressed = tokens.filter((token) => !isModifier(token)).length\n if (tokens.length === 0) {\n console.warn(`[VHotkeys] \\`keys\\` is empty: \"${keys}\" declares no key.`)\n } else if (pressed === 0) {\n /* `ctrl++` reads as Ctrl and the + key, but the second + is a separator: the key\n itself is written `plus`. */\n console.warn(\n `[VHotkeys] \\`keys\\` holds only modifiers: \"${keys}\" never fires. The + key is written \\`plus\\`.`,\n )\n } else if (pressed > 1) {\n /* A shortcut is modifiers and ONE key: `g+h` is not a sequence, and only its first\n key would ever be matched. */\n console.warn(\n `[VHotkeys] \\`keys\\` names more than one key: \"${keys}\" is matched on the first alone.`,\n )\n }\n }\n return tokens\n}\n\n/** Whether this key is one that is held down rather than pressed. */\nfunction isModifier(token: string): boolean {\n return MODIFIERS.has(token)\n}\n\n/**\n * How a key nobody has a word or a symbol for is printed: as it was written, merely\n * capitalized: `k` becomes K, `f5` becomes F5, and a slash stays a slash.\n */\nexport function capLabel(token: string): string {\n return token.charAt(0).toUpperCase() + token.slice(1)\n}\n\nfunction wordOf(token: string, platform: HotkeysPlatform): HotkeysWord | undefined {\n /* The two keys whose name depends on the system. `mod` is the modifier a consumer\n writes when they mean \"the usual one here\" — Command on a Mac, Ctrl elsewhere —\n while `meta` names that physical key literally, and it is called something\n different on each system. */\n if (token === 'mod') return platform === 'mac' ? 'command' : 'ctrl'\n if (token === 'meta') {\n if (platform === 'mac') return 'command'\n return platform === 'windows' ? 'windows' : 'super'\n }\n return WORDS[token]\n}\n\n/**\n * Resolves each key of a combination for a given system, attaching the symbol engraved\n * on it and the dictionary entry naming it, where either exists.\n */\nexport function resolveKeys(tokens: string[], platform: HotkeysPlatform): ResolvedKey[] {\n /* A modifier named twice is one key held down: `ctrl+ctrl+k`, or `mod+ctrl+k` away from a\n Mac, where `mod` IS Ctrl. Drawn twice it would read as a chord of two keys. */\n const held = new Set<string>()\n const once = tokens.filter((token) => {\n if (!isModifier(token)) return true\n const key = token === 'mod' ? (platform === 'mac' ? 'meta' : 'ctrl') : token\n if (held.has(key)) return false\n held.add(key)\n return true\n })\n return once.map((token) => ({\n token,\n glyph: (platform === 'mac' ? MAC_GLYPHS[token] : undefined) ?? GLYPHS[token],\n word: wordOf(token, platform),\n }))\n}\n\nfunction normalizeEventKey(event: KeyboardEvent): string {\n const key = event.key.toLowerCase()\n return ALIASES[key] ?? key\n}\n\n/**\n * Whether a key press IS this combination.\n *\n * The modifiers are compared EXACTLY, and never as \"at least these ones\". Without\n * that, `mod+k` and `mod+shift+k` could not coexist in one application: the first\n * would swallow every press meant for the second.\n */\nexport function matchesEvent(\n event: KeyboardEvent,\n tokens: string[],\n platform: HotkeysPlatform,\n): boolean {\n const wantCtrl = tokens.includes('ctrl') || (platform !== 'mac' && tokens.includes('mod'))\n const wantMeta = tokens.includes('meta') || (platform === 'mac' && tokens.includes('mod'))\n if (event.ctrlKey !== wantCtrl) return false\n if (event.metaKey !== wantMeta) return false\n if (event.altKey !== tokens.includes('alt')) return false\n if (event.shiftKey !== tokens.includes('shift')) return false\n const main = tokens.find((token) => !isModifier(token))\n /* A combination made of modifiers alone never fires: there is no key to press. */\n if (main === undefined) return false\n /* Both sides go through the same normalization, which is also what makes a press\n reported as \"K\" — because Shift was held — match a shortcut written `k`. */\n return normalizeEventKey(event) === main\n}\n\n// @keyboard — what `allowInInput` is asked about: a shortcut must not fire in the\n// middle of someone typing a sentence.\nexport function isEditableTarget(target: EventTarget | null | undefined): boolean {\n if (!(target instanceof HTMLElement)) return false\n if (target.isContentEditable) return true\n const tag = target.tagName\n if (tag === 'TEXTAREA' || tag === 'SELECT') return true\n if (tag !== 'INPUT') return false\n return !NON_TEXT_INPUT_TYPES.has((target as HTMLInputElement).type)\n}\n"],"mappings":";;AAyEA,IAAa,IAAoC;AAYjD,SAAgB,IAAkC;CAChD,IAAI,OAAO,YAAc,KAAa,OAAO;CAC7C,IAAM,IAAM,WACN,KAAU,EAAI,eAAe,YAAY,EAAI,YAAY,GAAA,CAAI,YAAY;CAS/E,OANI,uBAAuB,KAAK,CAAM,IAAU,QAC5C,EAAO,SAAS,KAAK,IAAU,YAI/B,+BAA+B,KAAK,CAAM,IAAU,UACjD;AACT;AASA,IAAM,IAAkC;CACtC,KAAK;CACL,SAAS;CACT,KAAK;CACL,SAAS;CACT,OAAO;CACP,IAAI;CACJ,WAAW;CACX,kBAAkB;CAClB,SAAS;CACT,QAAQ;CACR,KAAK;CACL,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,KAAK;CACL,KAAK;CACL,SAAS;CACT,WAAW;CACX,WAAW;CACX,YAAY;CACZ,KAAK;AACP,GAKM,IAAqC;CACzC,KAAK;CACL,MAAM;CACN,MAAM;CACN,KAAK;CACL,OAAO;CACP,OAAO;CACP,WAAW;CACX,QAAQ;CACR,KAAK;AACP,GAIM,IAAiC;CACrC,IAAI;CACJ,MAAM;CACN,MAAM;CACN,OAAO;CACP,MAAM;AACR,GAEM,IAAqC;CACzC,MAAM;CACN,KAAK;CACL,OAAO;CACP,OAAO;CACP,KAAK;CACL,OAAO;CACP,WAAW;CACX,QAAQ;CACR,KAAK;CACL,IAAI;CACJ,MAAM;CACN,MAAM;CACN,OAAO;AACT,GAEM,oBAAY,IAAI,IAAI;CAAC;CAAO;CAAQ;CAAQ;CAAO;AAAO,CAAC,GAM3D,oBAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAOD,SAAgB,EAAa,GAAwB;CACnD,IAAM,IAAS,EACZ,MAAM,GAAG,CAAC,CACV,KAAK,MAAU,EAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAC1C,OAAO,OAAO,CAAC,CACf,KAAK,MAAU,EAAQ,MAAU,CAAK;CACzC,IAAI,GAAO;EACT,IAAM,IAAU,EAAO,QAAQ,MAAU,CAAC,EAAW,CAAK,CAAC,CAAC,CAAC;EAC7D,AAAI,EAAO,WAAW,IACpB,QAAQ,KAAK,kCAAkC,EAAK,mBAAmB,IAC9D,MAAY,IAGrB,QAAQ,KACN,8CAA8C,EAAK,8CACrD,IACS,IAAU,KAGnB,QAAQ,KACN,iDAAiD,EAAK,iCACxD;CAEJ;CACA,OAAO;AACT;AAGA,SAAS,EAAW,GAAwB;CAC1C,OAAO,EAAU,IAAI,CAAK;AAC5B;AAMA,SAAgB,EAAS,GAAuB;CAC9C,OAAO,EAAM,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAM,MAAM,CAAC;AACtD;AAEA,SAAS,EAAO,GAAe,GAAoD;CAUjF,OALI,MAAU,QAAc,MAAa,QAAQ,YAAY,SACzD,MAAU,SACR,MAAa,QAAc,YACxB,MAAa,YAAY,YAAY,UAEvC,EAAM;AACf;AAMA,SAAgB,EAAY,GAAkB,GAA0C;CAGtF,IAAM,oBAAO,IAAI,IAAY;CAQ7B,OAPa,EAAO,QAAQ,MAAU;EACpC,IAAI,CAAC,EAAW,CAAK,GAAG,OAAO;EAC/B,IAAM,IAAM,MAAU,QAAS,MAAa,QAAQ,SAAS,SAAU;EAGvE,OAFI,EAAK,IAAI,CAAG,IAAU,MAC1B,EAAK,IAAI,CAAG,GACL;CACT,CACO,CAAA,CAAK,KAAK,OAAW;EAC1B;EACA,QAAQ,MAAa,QAAQ,EAAW,KAAS,KAAA,MAAc,EAAO;EACtE,MAAM,EAAO,GAAO,CAAQ;CAC9B,EAAE;AACJ;AAEA,SAAS,EAAkB,GAA8B;CACvD,IAAM,IAAM,EAAM,IAAI,YAAY;CAClC,OAAO,EAAQ,MAAQ;AACzB;AASA,SAAgB,EACd,GACA,GACA,GACS;CACT,IAAM,IAAW,EAAO,SAAS,MAAM,KAAM,MAAa,SAAS,EAAO,SAAS,KAAK,GAClF,IAAW,EAAO,SAAS,MAAM,KAAM,MAAa,SAAS,EAAO,SAAS,KAAK;CAIxF,IAHI,EAAM,YAAY,KAClB,EAAM,YAAY,KAClB,EAAM,WAAW,EAAO,SAAS,KAAK,KACtC,EAAM,aAAa,EAAO,SAAS,OAAO,GAAG,OAAO;CACxD,IAAM,IAAO,EAAO,MAAM,MAAU,CAAC,EAAW,CAAK,CAAC;CAKtD,OAHI,MAAS,KAAA,KAGN,EAAkB,CAAK,MAAM;AACtC;AAIA,SAAgB,EAAiB,GAAiD;CAChF,IAAI,EAAE,aAAkB,cAAc,OAAO;CAC7C,IAAI,EAAO,mBAAmB,OAAO;CACrC,IAAM,IAAM,EAAO;CAGnB,OAFI,MAAQ,cAAc,MAAQ,YAC9B,MAAQ,WACL,CAAC,EAAqB,IAAK,EAA4B,IAAI;AACpE"}
|
|
1
|
+
{"version":3,"file":"platform.js","names":[],"sources":["../../../src/components/VHotkeys/platform.ts"],"sourcesContent":["// @core\n// Shortcut parsing and platform glyphs are pure; read navigator only after mount to preserve\n// deterministic SSR output.\n/**\n * Detecting the platform must only ever be done once a component is mounted. There is nothing\n * to read it from on a server, and sniffing a request header is not this library's business.\n */\n\nimport { isDev } from '../../utils/env'\n\n/** The system a keyboard belongs to, which decides its symbols and some of its words. */\nexport type HotkeysPlatform = 'mac' | 'windows' | 'linux' | 'other'\n\n/**\n * The dictionary entries naming a key, and only those: the sentence framing the shortcut is not\n * one of them.\n */\nexport type HotkeysWord =\n | 'command'\n | 'ctrl'\n | 'alt'\n | 'shift'\n | 'windows'\n | 'super'\n | 'enter'\n | 'escape'\n | 'space'\n | 'backspace'\n | 'delete'\n | 'tab'\n | 'up'\n | 'down'\n | 'left'\n | 'right'\n\n/** One key of a shortcut, once resolved for a given platform. */\nexport interface ResolvedKey {\n /** Its canonical name, which the matcher compares a key press against. */\n token: string\n /** The symbol engraved on that key, where the system engraves one. */\n glyph?: string\n /**\n * Which dictionary entry names it. Nothing here means the design system does not\n * know this key, and it is printed as it was written.\n */\n word?: HotkeysWord\n}\n\n// @ssr\n/** What a SERVER renders, and therefore what the browser's first render must be as well. */\nexport const DEFAULT_PLATFORM: HotkeysPlatform = 'other'\n\n// @ssr @fallback\n/** Works out which system the keyboard belongs to, from two sources in order. */\nexport function detectPlatform(): HotkeysPlatform {\n if (typeof navigator === 'undefined') return DEFAULT_PLATFORM\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } }\n const source = (nav.userAgentData?.platform || nav.platform || '').toLowerCase()\n /*\n * IPadOS reports itself as a Mac, which is fine here: an attached keyboard carries the same\n * modifier symbols either way.\n */\n if (/mac|iphone|ipad|ipod/.test(source)) return 'mac'\n if (source.includes('win')) return 'windows'\n /* ChromeOS spells itself two different ways depending on which of the two sources\n answered. Both have to be listed, or the branch is dead precisely where it\n matters. */\n if (/linux|android|cros|chrome os/.test(source)) return 'linux'\n return DEFAULT_PLATFORM\n}\n\n/*\n * Serving both is what guarantees the two can never disagree. The `+` key is canonically named\n * `plus`, since `+` is the separator and cannot name itself in the prop; but the browser does\n * report it as `'+'`, hence its presence here.\n */\nconst ALIASES: Record<string, string> = {\n cmd: 'meta',\n command: 'meta',\n win: 'meta',\n windows: 'meta',\n super: 'meta',\n os: 'meta',\n cmdorctrl: 'mod',\n commandorcontrol: 'mod',\n control: 'ctrl',\n option: 'alt',\n opt: 'alt',\n return: 'enter',\n escape: 'esc',\n spacebar: 'space',\n ' ': 'space',\n del: 'delete',\n arrowup: 'up',\n arrowdown: 'down',\n arrowleft: 'left',\n arrowright: 'right',\n '+': 'plus',\n}\n\n/* The symbols engraved on an Apple keyboard, and on no other. Escape and the space bar\n are deliberately absent: the Mac key really does read \"esc\", and the space bar\n carries no symbol at all. */\nconst MAC_GLYPHS: Record<string, string> = {\n mod: '⌘',\n meta: '⌘',\n ctrl: '⌃',\n alt: '⌥',\n shift: '⇧',\n enter: '↵',\n backspace: '⌫',\n delete: '⌦',\n tab: '⇥',\n}\n\n/* Symbols that are the same everywhere: an arrow key is engraved with an arrow on\n every keyboard ever made. */\nconst GLYPHS: Record<string, string> = {\n up: '↑',\n down: '↓',\n left: '←',\n right: '→',\n plus: '+',\n}\n\nconst WORDS: Record<string, HotkeysWord> = {\n ctrl: 'ctrl',\n alt: 'alt',\n shift: 'shift',\n enter: 'enter',\n esc: 'escape',\n space: 'space',\n backspace: 'backspace',\n delete: 'delete',\n tab: 'tab',\n up: 'up',\n down: 'down',\n left: 'left',\n right: 'right',\n}\n\nconst MODIFIERS = new Set(['mod', 'meta', 'ctrl', 'alt', 'shift'])\n\n/**\n * The kinds of field one cannot type prose into. A shortcut stays live over a\n * checkbox, a button or a colour picker: there is no sentence to interrupt there.\n */\nconst NON_TEXT_INPUT_TYPES = new Set([\n 'button',\n 'submit',\n 'reset',\n 'checkbox',\n 'radio',\n 'file',\n 'range',\n 'color',\n 'image',\n])\n\n/**\n * Reads a combination as it was written and returns its keys in canonical form: `'mod+k'` and\n * `' Mod + K '` both give the same two.\n */\nexport function parseHotkeys(keys: string): string[] {\n const tokens = keys\n .split('+')\n .map((token) => token.trim().toLowerCase())\n .filter(Boolean)\n .map((token) => ALIASES[token] ?? token)\n if (isDev) {\n const pressed = tokens.filter((token) => !isModifier(token)).length\n if (tokens.length === 0) {\n console.warn(`[VHotkeys] \\`keys\\` is empty: \"${keys}\" declares no key.`)\n } else if (pressed === 0) {\n /* `ctrl++` reads as Ctrl and the + key, but the second + is a separator: the key\n itself is written `plus`. */\n console.warn(\n `[VHotkeys] \\`keys\\` holds only modifiers: \"${keys}\" never fires. The + key is written \\`plus\\`.`,\n )\n } else if (pressed > 1) {\n /* A shortcut is modifiers and ONE key: `g+h` is not a sequence, and only its first\n key would ever be matched. */\n console.warn(\n `[VHotkeys] \\`keys\\` names more than one key: \"${keys}\" is matched on the first alone.`,\n )\n }\n }\n return tokens\n}\n\n/** Whether this key is one that is held down rather than pressed. */\nfunction isModifier(token: string): boolean {\n return MODIFIERS.has(token)\n}\n\n/**\n * How a key nobody has a word or a symbol for is printed: as it was written, merely\n * capitalized: `k` becomes K, `f5` becomes F5, and a slash stays a slash.\n */\nexport function capLabel(token: string): string {\n return token.charAt(0).toUpperCase() + token.slice(1)\n}\n\nfunction wordOf(token: string, platform: HotkeysPlatform): HotkeysWord | undefined {\n /*\n * The two keys whose name depends on the system. `mod` is the modifier a consumer writes when\n * they mean \"the usual one here\"; Command on a Mac, Ctrl elsewhere; while `meta` names that\n * physical key literally, and it is called something different on each system.\n */\n if (token === 'mod') return platform === 'mac' ? 'command' : 'ctrl'\n if (token === 'meta') {\n if (platform === 'mac') return 'command'\n return platform === 'windows' ? 'windows' : 'super'\n }\n return WORDS[token]\n}\n\n/**\n * Resolves each key of a combination for a given system, attaching the symbol engraved\n * on it and the dictionary entry naming it, where either exists.\n */\nexport function resolveKeys(tokens: string[], platform: HotkeysPlatform): ResolvedKey[] {\n /* A modifier named twice is one key held down: `ctrl+ctrl+k`, or `mod+ctrl+k` away from a\n Mac, where `mod` IS Ctrl. Drawn twice it would read as a chord of two keys. */\n const held = new Set<string>()\n const once = tokens.filter((token) => {\n if (!isModifier(token)) return true\n const key = token === 'mod' ? (platform === 'mac' ? 'meta' : 'ctrl') : token\n if (held.has(key)) return false\n held.add(key)\n return true\n })\n return once.map((token) => ({\n token,\n glyph: (platform === 'mac' ? MAC_GLYPHS[token] : undefined) ?? GLYPHS[token],\n word: wordOf(token, platform),\n }))\n}\n\nfunction normalizeEventKey(event: KeyboardEvent): string {\n const key = event.key.toLowerCase()\n return ALIASES[key] ?? key\n}\n\n/** Whether a key press IS this combination. */\nexport function matchesEvent(\n event: KeyboardEvent,\n tokens: string[],\n platform: HotkeysPlatform,\n): boolean {\n const wantCtrl = tokens.includes('ctrl') || (platform !== 'mac' && tokens.includes('mod'))\n const wantMeta = tokens.includes('meta') || (platform === 'mac' && tokens.includes('mod'))\n if (event.ctrlKey !== wantCtrl) return false\n if (event.metaKey !== wantMeta) return false\n if (event.altKey !== tokens.includes('alt')) return false\n if (event.shiftKey !== tokens.includes('shift')) return false\n const main = tokens.find((token) => !isModifier(token))\n /* A combination made of modifiers alone never fires: there is no key to press. */\n if (main === undefined) return false\n /*\n * Both sides go through the same normalization, which is also what makes a press reported as\n * \"K\"; because Shift was held; match a shortcut written `k`.\n */\n return normalizeEventKey(event) === main\n}\n\n// @keyboard\nexport function isEditableTarget(target: EventTarget | null | undefined): boolean {\n if (!(target instanceof HTMLElement)) return false\n if (target.isContentEditable) return true\n const tag = target.tagName\n if (tag === 'TEXTAREA' || tag === 'SELECT') return true\n if (tag !== 'INPUT') return false\n return !NON_TEXT_INPUT_TYPES.has((target as HTMLInputElement).type)\n}\n"],"mappings":";;AAkDA,IAAa,IAAoC;AAIjD,SAAgB,IAAkC;CAChD,IAAI,OAAO,YAAc,KAAa,OAAO;CAC7C,IAAM,IAAM,WACN,KAAU,EAAI,eAAe,YAAY,EAAI,YAAY,GAAA,CAAI,YAAY;CAW/E,OANI,uBAAuB,KAAK,CAAM,IAAU,QAC5C,EAAO,SAAS,KAAK,IAAU,YAI/B,+BAA+B,KAAK,CAAM,IAAU,UACjD;AACT;AAOA,IAAM,IAAkC;CACtC,KAAK;CACL,SAAS;CACT,KAAK;CACL,SAAS;CACT,OAAO;CACP,IAAI;CACJ,WAAW;CACX,kBAAkB;CAClB,SAAS;CACT,QAAQ;CACR,KAAK;CACL,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,KAAK;CACL,KAAK;CACL,SAAS;CACT,WAAW;CACX,WAAW;CACX,YAAY;CACZ,KAAK;AACP,GAKM,IAAqC;CACzC,KAAK;CACL,MAAM;CACN,MAAM;CACN,KAAK;CACL,OAAO;CACP,OAAO;CACP,WAAW;CACX,QAAQ;CACR,KAAK;AACP,GAIM,IAAiC;CACrC,IAAI;CACJ,MAAM;CACN,MAAM;CACN,OAAO;CACP,MAAM;AACR,GAEM,IAAqC;CACzC,MAAM;CACN,KAAK;CACL,OAAO;CACP,OAAO;CACP,KAAK;CACL,OAAO;CACP,WAAW;CACX,QAAQ;CACR,KAAK;CACL,IAAI;CACJ,MAAM;CACN,MAAM;CACN,OAAO;AACT,GAEM,oBAAY,IAAI,IAAI;CAAC;CAAO;CAAQ;CAAQ;CAAO;AAAO,CAAC,GAM3D,oBAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAMD,SAAgB,EAAa,GAAwB;CACnD,IAAM,IAAS,EACZ,MAAM,GAAG,CAAC,CACV,KAAK,MAAU,EAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAC1C,OAAO,OAAO,CAAC,CACf,KAAK,MAAU,EAAQ,MAAU,CAAK;CACzC,IAAI,GAAO;EACT,IAAM,IAAU,EAAO,QAAQ,MAAU,CAAC,EAAW,CAAK,CAAC,CAAC,CAAC;EAC7D,AAAI,EAAO,WAAW,IACpB,QAAQ,KAAK,kCAAkC,EAAK,mBAAmB,IAC9D,MAAY,IAGrB,QAAQ,KACN,8CAA8C,EAAK,8CACrD,IACS,IAAU,KAGnB,QAAQ,KACN,iDAAiD,EAAK,iCACxD;CAEJ;CACA,OAAO;AACT;AAGA,SAAS,EAAW,GAAwB;CAC1C,OAAO,EAAU,IAAI,CAAK;AAC5B;AAMA,SAAgB,EAAS,GAAuB;CAC9C,OAAO,EAAM,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,EAAM,MAAM,CAAC;AACtD;AAEA,SAAS,EAAO,GAAe,GAAoD;CAWjF,OALI,MAAU,QAAc,MAAa,QAAQ,YAAY,SACzD,MAAU,SACR,MAAa,QAAc,YACxB,MAAa,YAAY,YAAY,UAEvC,EAAM;AACf;AAMA,SAAgB,EAAY,GAAkB,GAA0C;CAGtF,IAAM,oBAAO,IAAI,IAAY;CAQ7B,OAPa,EAAO,QAAQ,MAAU;EACpC,IAAI,CAAC,EAAW,CAAK,GAAG,OAAO;EAC/B,IAAM,IAAM,MAAU,QAAS,MAAa,QAAQ,SAAS,SAAU;EAGvE,OAFI,EAAK,IAAI,CAAG,IAAU,MAC1B,EAAK,IAAI,CAAG,GACL;CACT,CACO,CAAA,CAAK,KAAK,OAAW;EAC1B;EACA,QAAQ,MAAa,QAAQ,EAAW,KAAS,KAAA,MAAc,EAAO;EACtE,MAAM,EAAO,GAAO,CAAQ;CAC9B,EAAE;AACJ;AAEA,SAAS,EAAkB,GAA8B;CACvD,IAAM,IAAM,EAAM,IAAI,YAAY;CAClC,OAAO,EAAQ,MAAQ;AACzB;AAGA,SAAgB,EACd,GACA,GACA,GACS;CACT,IAAM,IAAW,EAAO,SAAS,MAAM,KAAM,MAAa,SAAS,EAAO,SAAS,KAAK,GAClF,IAAW,EAAO,SAAS,MAAM,KAAM,MAAa,SAAS,EAAO,SAAS,KAAK;CAIxF,IAHI,EAAM,YAAY,KAClB,EAAM,YAAY,KAClB,EAAM,WAAW,EAAO,SAAS,KAAK,KACtC,EAAM,aAAa,EAAO,SAAS,OAAO,GAAG,OAAO;CACxD,IAAM,IAAO,EAAO,MAAM,MAAU,CAAC,EAAW,CAAK,CAAC;CAOtD,OALI,MAAS,KAAA,KAKN,EAAkB,CAAK,MAAM;AACtC;AAGA,SAAgB,EAAiB,GAAiD;CAChF,IAAI,EAAE,aAAkB,cAAc,OAAO;CAC7C,IAAI,EAAO,mBAAmB,OAAO;CACrC,IAAM,IAAM,EAAO;CAGnB,OAFI,MAAQ,cAAc,MAAQ,YAC9B,MAAQ,WACL,CAAC,EAAqB,IAAK,EAA4B,IAAI;AACpE"}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { BuiltinIcon, IconRender } from './types.js';
|
|
2
2
|
interface IconProps {
|
|
3
|
-
/**
|
|
4
|
-
* Which icon to draw. A plain string is a name: it is offered to the consumer's
|
|
5
|
-
* resolver and then left to the icon font as a ligature. One of the design
|
|
6
|
-
* system's own icons, imported from `vectis-ui/icons`, carries its drawing with
|
|
7
|
-
* it: the resolver is still asked first, and the drawing is what answers when
|
|
8
|
-
* nothing else does.
|
|
9
|
-
*/
|
|
3
|
+
/** Which icon to draw. */
|
|
10
4
|
name?: string | BuiltinIcon;
|
|
11
5
|
/**
|
|
12
|
-
* An explicit description of what to draw (an image, a component, a path, a class),
|
|
13
|
-
*
|
|
14
|
-
* the design system takes when it is given something other than a plain name.
|
|
6
|
+
* An explicit description of what to draw (an image, a component, a path, a class), which
|
|
7
|
+
* wins over everything else.
|
|
15
8
|
*/
|
|
16
9
|
render?: IconRender;
|
|
17
10
|
/** The address of an image to use as the icon. It wins over `name`. */
|
|
@@ -29,17 +22,12 @@ interface IconProps {
|
|
|
29
22
|
* surrounding text already says what it says.
|
|
30
23
|
*/
|
|
31
24
|
label?: string;
|
|
32
|
-
/**
|
|
33
|
-
* Draws the filled version of the icon. The built-in icons honour it wherever
|
|
34
|
-
* filling actually changes the drawing, and the ligature font always does. It
|
|
35
|
-
* means nothing for an image or an inline SVG, whose shape is fixed.
|
|
36
|
-
*/
|
|
25
|
+
/** Draws the filled version of the icon. */
|
|
37
26
|
filled?: boolean;
|
|
38
27
|
/**
|
|
39
|
-
* Flips the icon horizontally in a right-to-left context, for a glyph that points
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* The direction is the one the browser computed, so an ancestor's `dir` is enough.
|
|
28
|
+
* Flips the icon horizontally in a right-to-left context, for a glyph that points at a
|
|
29
|
+
* physical direction: a "previous" chevron points left in English and right in Arabic. Off by
|
|
30
|
+
* default, since most icons mean the same thing in both directions.
|
|
43
31
|
*/
|
|
44
32
|
mirrored?: boolean;
|
|
45
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VIcon.js","names":[],"sources":["../../../src/components/VIcon/VIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * An icon, whatever the host application's icons are made of. The real work is deciding\n * WHERE the drawing comes from, asked in a fixed order: `render`, then `src`, then `name` —\n * offered to the consumer's resolver if one was installed, then to the drawing the icon\n * carries, and finally to a ligature font. A raw SVG can always come through the slot.\n *\n * That order IS the contract. The resolver comes before the built-in drawing, or the\n * library's own icons would stay Material for a consumer who wired in their own set; and a\n * resolver returning `undefined` hands over to that drawing, which is what makes a PARTIAL\n * mapping usable.\n *\n * It is what lets the library require no icon font: the icons it draws by default are\n * imported by the components that render them and travel as a name AND its paths, so a\n * consumer downloads only the ones their components actually draw.\n *\n * Despite the `computed`, there is no behavioural JS here — no event, no lifecycle, no DOM.\n * A pure choice of source, made identically on the server and in the browser.\n */\nimport { computed, type Component } from 'vue'\n\nimport { iconName as nameOf } from './iconProps'\nimport { ICON_VIEW_BOX } from './icons/viewBox'\nimport { resolveIcon } from './resolver'\nimport type { BuiltinIcon, IconRender } from './types'\n\nimport { px } from '../../utils/css'\n\ninterface IconProps {\n /**\n * Which icon to draw. A plain string is a name: it is offered to the consumer's\n * resolver and then left to the icon font as a ligature. One of the design\n * system's own icons, imported from `vectis-ui/icons`, carries its drawing with\n * it: the resolver is still asked first, and the drawing is what answers when\n * nothing else does.\n */\n name?: string | BuiltinIcon\n /**\n * An explicit description of what to draw (an image, a component, a path, a class),\n * which wins over everything else. This is the route every `IconSource` prop of\n * the design system takes when it is given something other than a plain name.\n */\n render?: IconRender\n /** The address of an image to use as the icon. It wins over `name`. */\n src?: string\n /**\n * A size in pixels, as a number or a numeric string. Left out, or given something that\n * is not a number, the icon takes the size its context imposes (a button sets one for\n * the icons inside it), and failing that 1em, which makes it follow the surrounding\n * text.\n */\n size?: number | string\n /**\n * What the icon means, for screen readers. Leaving it out marks the icon as\n * decorative and hides it from them, which is the right thing whenever the\n * surrounding text already says what it says.\n */\n label?: string\n /**\n * Draws the filled version of the icon. The built-in icons honour it wherever\n * filling actually changes the drawing, and the ligature font always does. It\n * means nothing for an image or an inline SVG, whose shape is fixed.\n */\n filled?: boolean\n /**\n * Flips the icon horizontally in a right-to-left context, for a glyph that points\n * at a physical direction: a \"previous\" chevron points left in English and right in\n * Arabic. Off by default, since most icons mean the same thing in both directions.\n * The direction is the one the browser computed, so an ancestor's `dir` is enough.\n */\n mirrored?: boolean\n}\n\nconst props = withDefaults(defineProps<IconProps>(), {\n name: undefined,\n render: undefined,\n src: undefined,\n size: undefined,\n label: undefined,\n filled: false,\n mirrored: false,\n})\n\ndefineSlots<{\n /** An inline SVG, used when none of `render`, `src` and `name` was given. */\n default?(): unknown\n}>()\n\n/**\n * The chosen source, labelled with its kind. The public `IconRender` union carries\n * no such label — the consumer writes it by hand and has nothing to tag it with —\n * but the template needs one thing it can branch on reliably, and applying the tag\n * is also where the documented precedence between the shapes gets decided.\n */\ntype Resolved =\n | { kind: 'path'; path: string; viewBox: string }\n | { kind: 'component'; component: Component; props?: Record<string, unknown> }\n | { kind: 'src'; src: string }\n | { kind: 'text'; text: string; class?: string }\n | { kind: 'class'; class: string }\n\nfunction tag(render: IconRender): Resolved {\n if ('path' in render)\n return { kind: 'path', path: render.path, viewBox: render.viewBox ?? ICON_VIEW_BOX }\n if ('component' in render)\n return { kind: 'component', component: render.component, props: render.props }\n if ('src' in render) return { kind: 'src', src: render.src }\n if ('text' in render) return { kind: 'text', text: render.text, class: render.class }\n return { kind: 'class', class: render.class }\n}\n\n/**\n * The icon's identity: the name the resolver is asked for, and the one `data-icon`\n * records whatever the drawing turned out to come from.\n */\nconst iconName = computed(() => nameOf(props.name))\n\n// @fallback — the ladder itself: every rung hands over to the next one, and the\n// ligature is the last resort, reached when neither the resolver nor the registry\n// recognized the name.\n/**\n * Works out what to draw: an explicit `render` if there is one, then the consumer's\n * resolver, then the icons built into the library. Answering `undefined` here is not\n * a failure — it means the template falls through to the image, the ligature or the\n * slot.\n */\nconst resolved = computed<Resolved | undefined>(() => {\n if (props.render) return tag(props.render)\n // TRUTHINESS on `src`, the test the template renders the image with: an empty `src=\"\"`\n // renders no image, so it must not stop the name from being drawn either.\n if (props.src || props.name === undefined) return undefined\n\n const custom = resolveIcon(iconName.value!, { filled: props.filled })\n if (custom) return tag(custom)\n\n // A bare name has no drawing of its own, so it falls through to the ligature; only\n // one of the library's own icons brings its paths along.\n if (typeof props.name === 'string') return undefined\n\n const { paths } = props.name\n // A second path is only stored when filling actually changes the drawing, so most\n // icons have one path and fall back to it (see the `BuiltinIcon` type).\n const path = (props.filled ? paths[1] : undefined) ?? paths[0]\n return { kind: 'path', path, viewBox: ICON_VIEW_BOX }\n})\n</script>\n\n<template>\n <span\n class=\"v-icon\"\n :style=\"px(size) !== undefined ? { '--vectis-icon-size': px(size) } : undefined\"\n :data-icon=\"iconName\"\n :data-filled=\"filled || undefined\"\n :data-mirror=\"mirrored || undefined\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <template v-if=\"resolved\">\n <svg\n v-if=\"resolved.kind === 'path'\"\n class=\"v-icon-svg\"\n :viewBox=\"resolved.viewBox\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path :d=\"resolved.path\" />\n </svg>\n <component\n :is=\"resolved.component\"\n v-else-if=\"resolved.kind === 'component'\"\n v-bind=\"resolved.props\"\n />\n <img v-else-if=\"resolved.kind === 'src'\" class=\"v-icon-img\" :src=\"resolved.src\" alt=\"\" />\n <!-- The interpolation is written flush against the tags: a ligature is matched\n on the exact text, and any surrounding whitespace would break it -->\n <span v-else-if=\"resolved.kind === 'text'\" class=\"v-icon-symbol\" :class=\"resolved.class\">{{\n resolved.text\n }}</span>\n <span v-else class=\"v-icon-glyph\" :class=\"resolved.class\" />\n </template>\n <img v-else-if=\"src\" class=\"v-icon-img\" :src=\"src\" alt=\"\" />\n <span v-else-if=\"name\" class=\"v-icon-symbol\">{{ iconName }}</span>\n <slot v-else />\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n .v-icon {\n /*\n * How the size is settled, in order of priority:\n * 1. the `size` prop, written inline on the element as --vectis-icon-size — a\n * declaration made on the element itself beats both inheritance and the\n * layer, so it always wins;\n * 2. the variables inherited from an ancestor, which is the context API: a\n * VButton sets them from its own size, and the icons inside follow;\n * 3. 1em, which makes the icon follow the size of the text around it.\n */\n --icon-size: var(--vectis-icon-size, 1em);\n --icon-opsz: var(--vectis-icon-opsz, 24);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n /* A ligature is text, so it is the font-size that sizes it, and the box follows as 1em.\n TRAP — the box must NOT read `--icon-size` again: an `em` there resolves against\n this element's own font-size, already enlarged by the line below, so a context size\n of 1.5em drew a box of 2.25em around a glyph of 1.5em. */\n font-size: var(--icon-size);\n inline-size: 1em;\n block-size: 1em;\n }\n\n /* When the icon font has not loaded, a ligature shows as its own name in plain\n text. Clipping keeps that text inside the square instead of letting it push the\n layout apart. The rule is scoped to the ONE branch it protects rather than to\n `.v-icon`: a third-party icon font may legitimately draw outside its 1em box,\n and clipping everything would cut those glyphs. */\n .v-icon:has(> .v-icon-symbol) {\n overflow: hidden;\n }\n\n .v-icon-symbol {\n /* Declared here and not only under `[data-filled]`: a custom property inherits, so a\n host application setting `--icon-fill` on an ancestor would otherwise fill every\n icon below it. */\n --icon-fill: 0;\n font-family: var(--vectis-font-family-icon);\n font-weight: var(--vectis-font-weight-regular);\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n letter-spacing: normal;\n text-transform: none;\n white-space: nowrap;\n direction: ltr;\n /* The variable axes of the Material Symbols font. These numbers are the font's\n own technical contract and not design decisions, which is why they are written\n literally here rather than taken from tokens — the same tolerance as the\n opacities. */\n font-variation-settings:\n 'FILL' var(--icon-fill),\n 'wght' 400,\n 'GRAD' 0,\n 'opsz' var(--icon-opsz);\n }\n\n .v-icon[data-filled] .v-icon-symbol {\n --icon-fill: 1;\n }\n\n /* `:dir()` reads the direction the browser computed rather than an attribute spelled on\n an ancestor, and `scale` is the individual property, so it composes with a rotation\n a consumer applies (a disclosure chevron) instead of replacing its transform. */\n .v-icon[data-mirror]:dir(rtl) {\n scale: -1 1;\n }\n\n .v-icon-img,\n .v-icon > svg {\n inline-size: 100%;\n block-size: 100%;\n object-fit: contain;\n }\n\n /* An icon coming from a class-based font, as Font Awesome and Phosphor ship them:\n the glyph is drawn by a `::before`, at the font-size `.v-icon` has set. Because\n the design system renders this element itself rather than accepting the usual\n `<i>`, one rule normalizes it for every such library — `font-style` undoes the\n italics those conventions carry, and `line-height` stops the inherited line box\n from pushing the glyph off centre. */\n .v-icon-glyph {\n display: block;\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n }\n\n /* An SVG drawn from the built-in icons. The fill is set on OUR class and never on\n `.v-icon > svg`, so that a multicoloured SVG handed in through the slot keeps\n its own colours instead of being flattened to the text colour. `display: block`\n removes the gap an inline element leaves under its baseline. */\n .v-icon-svg {\n display: block;\n fill: currentcolor;\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VIcon.js","names":[],"sources":["../../../src/components/VIcon/VIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Resolve icon render, src and name in priority order. Named icons consult the consumer\n * resolver, embedded drawing and ligature fallback; explicit drawing needs no font.\n */\nimport { computed, type Component } from 'vue'\n\nimport { iconName as nameOf } from './iconProps'\nimport { ICON_VIEW_BOX } from './icons/viewBox'\nimport { resolveIcon } from './resolver'\nimport type { BuiltinIcon, IconRender } from './types'\n\nimport { px } from '../../utils/css'\n\ninterface IconProps {\n /** Which icon to draw. */\n name?: string | BuiltinIcon\n /**\n * An explicit description of what to draw (an image, a component, a path, a class), which\n * wins over everything else.\n */\n render?: IconRender\n /** The address of an image to use as the icon. It wins over `name`. */\n src?: string\n /**\n * A size in pixels, as a number or a numeric string. Left out, or given something that\n * is not a number, the icon takes the size its context imposes (a button sets one for\n * the icons inside it), and failing that 1em, which makes it follow the surrounding\n * text.\n */\n size?: number | string\n /**\n * What the icon means, for screen readers. Leaving it out marks the icon as\n * decorative and hides it from them, which is the right thing whenever the\n * surrounding text already says what it says.\n */\n label?: string\n /** Draws the filled version of the icon. */\n filled?: boolean\n /**\n * Flips the icon horizontally in a right-to-left context, for a glyph that points at a\n * physical direction: a \"previous\" chevron points left in English and right in Arabic. Off by\n * default, since most icons mean the same thing in both directions.\n */\n mirrored?: boolean\n}\n\nconst props = withDefaults(defineProps<IconProps>(), {\n name: undefined,\n render: undefined,\n src: undefined,\n size: undefined,\n label: undefined,\n filled: false,\n mirrored: false,\n})\n\ndefineSlots<{\n /** An inline SVG, used when none of `render`, `src` and `name` was given. */\n default?(): unknown\n}>()\n\n/**\n * The chosen source, labelled with its kind. The public `IconRender` union carries no such\n * label; the consumer writes it by hand and has nothing to tag it with; but the template needs\n * one thing it can branch on reliably, and applying the tag is also where the documented\n * precedence between the shapes gets decided.\n */\ntype Resolved =\n | { kind: 'path'; path: string; viewBox: string }\n | { kind: 'component'; component: Component; props?: Record<string, unknown> }\n | { kind: 'src'; src: string }\n | { kind: 'text'; text: string; class?: string }\n | { kind: 'class'; class: string }\n\nfunction tag(render: IconRender): Resolved {\n if ('path' in render)\n return { kind: 'path', path: render.path, viewBox: render.viewBox ?? ICON_VIEW_BOX }\n if ('component' in render)\n return { kind: 'component', component: render.component, props: render.props }\n if ('src' in render) return { kind: 'src', src: render.src }\n if ('text' in render) return { kind: 'text', text: render.text, class: render.class }\n return { kind: 'class', class: render.class }\n}\n\n/**\n * The icon's identity: the name the resolver is asked for, and the one `data-icon`\n * records whatever the drawing turned out to come from.\n */\nconst iconName = computed(() => nameOf(props.name))\n\n// @fallback\n// Ligature is the last resort, reached when neither the resolver nor the registry recognized\n// the name.\n/**\n * Works out what to draw: an explicit `render` if there is one, then the consumer's resolver,\n * then the icons built into the library. Answering `undefined` here is not a failure; it means\n * the template falls through to the image, the ligature or the slot.\n */\nconst resolved = computed<Resolved | undefined>(() => {\n if (props.render) return tag(props.render)\n // TRUTHINESS on `src`, the test the template renders the image with: an empty `src=\"\"`\n // renders no image, so it must not stop the name from being drawn either.\n if (props.src || props.name === undefined) return undefined\n\n const custom = resolveIcon(iconName.value!, { filled: props.filled })\n if (custom) return tag(custom)\n\n // A bare name has no drawing of its own, so it falls through to the ligature; only\n // one of the library's own icons brings its paths along.\n if (typeof props.name === 'string') return undefined\n\n const { paths } = props.name\n // A second path is only stored when filling actually changes the drawing, so most\n // icons have one path and fall back to it (see the `BuiltinIcon` type).\n const path = (props.filled ? paths[1] : undefined) ?? paths[0]\n return { kind: 'path', path, viewBox: ICON_VIEW_BOX }\n})\n</script>\n\n<template>\n <span\n class=\"v-icon\"\n :style=\"px(size) !== undefined ? { '--vectis-icon-size': px(size) } : undefined\"\n :data-icon=\"iconName\"\n :data-filled=\"filled || undefined\"\n :data-mirror=\"mirrored || undefined\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <template v-if=\"resolved\">\n <svg\n v-if=\"resolved.kind === 'path'\"\n class=\"v-icon-svg\"\n :viewBox=\"resolved.viewBox\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path :d=\"resolved.path\" />\n </svg>\n <component\n :is=\"resolved.component\"\n v-else-if=\"resolved.kind === 'component'\"\n v-bind=\"resolved.props\"\n />\n <img v-else-if=\"resolved.kind === 'src'\" class=\"v-icon-img\" :src=\"resolved.src\" alt=\"\" />\n <!-- The interpolation is written flush against the tags: a ligature is matched\n on the exact text, and any surrounding whitespace would break it -->\n <span v-else-if=\"resolved.kind === 'text'\" class=\"v-icon-symbol\" :class=\"resolved.class\">{{\n resolved.text\n }}</span>\n <span v-else class=\"v-icon-glyph\" :class=\"resolved.class\" />\n </template>\n <img v-else-if=\"src\" class=\"v-icon-img\" :src=\"src\" alt=\"\" />\n <span v-else-if=\"name\" class=\"v-icon-symbol\">{{ iconName }}</span>\n <slot v-else />\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n .v-icon {\n /*\n * How the size is settled, in order of priority: 1. the `size` prop, written inline on the\n * element as --vectis-icon-size; a declaration made on the element itself beats both\n * inheritance and the layer, so it always wins; 2. the variables inherited from an\n * ancestor, which is the context API: a VButton sets them from its own size, and the icons\n * inside follow; 3. 1em, which makes the icon follow the size of the text around it.\n */\n --icon-size: var(--vectis-icon-size, 1em);\n --icon-opsz: var(--vectis-icon-opsz, 24);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n /*\n * The box must not read `--icon-size` again: an `em` there resolves against this element's\n * own font-size, already enlarged by the line below, so a context size of 1.5em drew a box\n * of 2.25em around a glyph of 1.5em.\n */\n font-size: var(--icon-size);\n inline-size: 1em;\n block-size: 1em;\n }\n\n /*\n * When the icon font has not loaded, a ligature shows as its own name in plain text. Clipping\n * keeps that text inside the square instead of letting it push the layout apart.\n */\n .v-icon:has(> .v-icon-symbol) {\n overflow: hidden;\n }\n\n .v-icon-symbol {\n /* Declared here and not only under `[data-filled]`: a custom property inherits, so a\n host application setting `--icon-fill` on an ancestor would otherwise fill every\n icon below it. */\n --icon-fill: 0;\n font-family: var(--vectis-font-family-icon);\n font-weight: var(--vectis-font-weight-regular);\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n letter-spacing: normal;\n text-transform: none;\n white-space: nowrap;\n direction: ltr;\n /*\n * The variable axes of the Material Symbols font. These numbers are the font's own\n * technical contract and not design decisions, which is why they are written literally here\n * rather than taken from tokens; the same tolerance as the opacities.\n */\n font-variation-settings:\n 'FILL' var(--icon-fill),\n 'wght' 400,\n 'GRAD' 0,\n 'opsz' var(--icon-opsz);\n }\n\n .v-icon[data-filled] .v-icon-symbol {\n --icon-fill: 1;\n }\n\n /* `:dir()` reads the direction the browser computed rather than an attribute spelled on\n an ancestor, and `scale` is the individual property, so it composes with a rotation\n a consumer applies (a disclosure chevron) instead of replacing its transform. */\n .v-icon[data-mirror]:dir(rtl) {\n scale: -1 1;\n }\n\n .v-icon-img,\n .v-icon > svg {\n inline-size: 100%;\n block-size: 100%;\n object-fit: contain;\n }\n\n /*\n * Because the design system renders this element itself rather than accepting the usual\n * `<i>`, one rule normalizes it for every such library; `font-style` undoes the italics those\n * conventions carry, and `line-height` stops the inherited line box from pushing the glyph\n * off centre.\n */\n .v-icon-glyph {\n display: block;\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n }\n\n /*\n * The fill is set on OUR class and never on `.v-icon > svg`, so that a multicoloured SVG\n * handed in through the slot keeps its own colours instead of being flattened to the text\n * colour. `display: block` removes the gap an inline element leaves under its baseline.\n */\n .v-icon-svg {\n display: block;\n fill: currentcolor;\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VIcon.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VIcon/VIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * An icon, whatever the host application's icons are made of. The real work is deciding\n * WHERE the drawing comes from, asked in a fixed order: `render`, then `src`, then `name` —\n * offered to the consumer's resolver if one was installed, then to the drawing the icon\n * carries, and finally to a ligature font. A raw SVG can always come through the slot.\n *\n * That order IS the contract. The resolver comes before the built-in drawing, or the\n * library's own icons would stay Material for a consumer who wired in their own set; and a\n * resolver returning `undefined` hands over to that drawing, which is what makes a PARTIAL\n * mapping usable.\n *\n * It is what lets the library require no icon font: the icons it draws by default are\n * imported by the components that render them and travel as a name AND its paths, so a\n * consumer downloads only the ones their components actually draw.\n *\n * Despite the `computed`, there is no behavioural JS here — no event, no lifecycle, no DOM.\n * A pure choice of source, made identically on the server and in the browser.\n */\nimport { computed, type Component } from 'vue'\n\nimport { iconName as nameOf } from './iconProps'\nimport { ICON_VIEW_BOX } from './icons/viewBox'\nimport { resolveIcon } from './resolver'\nimport type { BuiltinIcon, IconRender } from './types'\n\nimport { px } from '../../utils/css'\n\ninterface IconProps {\n /**\n * Which icon to draw. A plain string is a name: it is offered to the consumer's\n * resolver and then left to the icon font as a ligature. One of the design\n * system's own icons, imported from `vectis-ui/icons`, carries its drawing with\n * it: the resolver is still asked first, and the drawing is what answers when\n * nothing else does.\n */\n name?: string | BuiltinIcon\n /**\n * An explicit description of what to draw (an image, a component, a path, a class),\n * which wins over everything else. This is the route every `IconSource` prop of\n * the design system takes when it is given something other than a plain name.\n */\n render?: IconRender\n /** The address of an image to use as the icon. It wins over `name`. */\n src?: string\n /**\n * A size in pixels, as a number or a numeric string. Left out, or given something that\n * is not a number, the icon takes the size its context imposes (a button sets one for\n * the icons inside it), and failing that 1em, which makes it follow the surrounding\n * text.\n */\n size?: number | string\n /**\n * What the icon means, for screen readers. Leaving it out marks the icon as\n * decorative and hides it from them, which is the right thing whenever the\n * surrounding text already says what it says.\n */\n label?: string\n /**\n * Draws the filled version of the icon. The built-in icons honour it wherever\n * filling actually changes the drawing, and the ligature font always does. It\n * means nothing for an image or an inline SVG, whose shape is fixed.\n */\n filled?: boolean\n /**\n * Flips the icon horizontally in a right-to-left context, for a glyph that points\n * at a physical direction: a \"previous\" chevron points left in English and right in\n * Arabic. Off by default, since most icons mean the same thing in both directions.\n * The direction is the one the browser computed, so an ancestor's `dir` is enough.\n */\n mirrored?: boolean\n}\n\nconst props = withDefaults(defineProps<IconProps>(), {\n name: undefined,\n render: undefined,\n src: undefined,\n size: undefined,\n label: undefined,\n filled: false,\n mirrored: false,\n})\n\ndefineSlots<{\n /** An inline SVG, used when none of `render`, `src` and `name` was given. */\n default?(): unknown\n}>()\n\n/**\n * The chosen source, labelled with its kind. The public `IconRender` union carries\n * no such label — the consumer writes it by hand and has nothing to tag it with —\n * but the template needs one thing it can branch on reliably, and applying the tag\n * is also where the documented precedence between the shapes gets decided.\n */\ntype Resolved =\n | { kind: 'path'; path: string; viewBox: string }\n | { kind: 'component'; component: Component; props?: Record<string, unknown> }\n | { kind: 'src'; src: string }\n | { kind: 'text'; text: string; class?: string }\n | { kind: 'class'; class: string }\n\nfunction tag(render: IconRender): Resolved {\n if ('path' in render)\n return { kind: 'path', path: render.path, viewBox: render.viewBox ?? ICON_VIEW_BOX }\n if ('component' in render)\n return { kind: 'component', component: render.component, props: render.props }\n if ('src' in render) return { kind: 'src', src: render.src }\n if ('text' in render) return { kind: 'text', text: render.text, class: render.class }\n return { kind: 'class', class: render.class }\n}\n\n/**\n * The icon's identity: the name the resolver is asked for, and the one `data-icon`\n * records whatever the drawing turned out to come from.\n */\nconst iconName = computed(() => nameOf(props.name))\n\n// @fallback — the ladder itself: every rung hands over to the next one, and the\n// ligature is the last resort, reached when neither the resolver nor the registry\n// recognized the name.\n/**\n * Works out what to draw: an explicit `render` if there is one, then the consumer's\n * resolver, then the icons built into the library. Answering `undefined` here is not\n * a failure — it means the template falls through to the image, the ligature or the\n * slot.\n */\nconst resolved = computed<Resolved | undefined>(() => {\n if (props.render) return tag(props.render)\n // TRUTHINESS on `src`, the test the template renders the image with: an empty `src=\"\"`\n // renders no image, so it must not stop the name from being drawn either.\n if (props.src || props.name === undefined) return undefined\n\n const custom = resolveIcon(iconName.value!, { filled: props.filled })\n if (custom) return tag(custom)\n\n // A bare name has no drawing of its own, so it falls through to the ligature; only\n // one of the library's own icons brings its paths along.\n if (typeof props.name === 'string') return undefined\n\n const { paths } = props.name\n // A second path is only stored when filling actually changes the drawing, so most\n // icons have one path and fall back to it (see the `BuiltinIcon` type).\n const path = (props.filled ? paths[1] : undefined) ?? paths[0]\n return { kind: 'path', path, viewBox: ICON_VIEW_BOX }\n})\n</script>\n\n<template>\n <span\n class=\"v-icon\"\n :style=\"px(size) !== undefined ? { '--vectis-icon-size': px(size) } : undefined\"\n :data-icon=\"iconName\"\n :data-filled=\"filled || undefined\"\n :data-mirror=\"mirrored || undefined\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <template v-if=\"resolved\">\n <svg\n v-if=\"resolved.kind === 'path'\"\n class=\"v-icon-svg\"\n :viewBox=\"resolved.viewBox\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path :d=\"resolved.path\" />\n </svg>\n <component\n :is=\"resolved.component\"\n v-else-if=\"resolved.kind === 'component'\"\n v-bind=\"resolved.props\"\n />\n <img v-else-if=\"resolved.kind === 'src'\" class=\"v-icon-img\" :src=\"resolved.src\" alt=\"\" />\n <!-- The interpolation is written flush against the tags: a ligature is matched\n on the exact text, and any surrounding whitespace would break it -->\n <span v-else-if=\"resolved.kind === 'text'\" class=\"v-icon-symbol\" :class=\"resolved.class\">{{\n resolved.text\n }}</span>\n <span v-else class=\"v-icon-glyph\" :class=\"resolved.class\" />\n </template>\n <img v-else-if=\"src\" class=\"v-icon-img\" :src=\"src\" alt=\"\" />\n <span v-else-if=\"name\" class=\"v-icon-symbol\">{{ iconName }}</span>\n <slot v-else />\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n .v-icon {\n /*\n * How the size is settled, in order of priority:\n * 1. the `size` prop, written inline on the element as --vectis-icon-size — a\n * declaration made on the element itself beats both inheritance and the\n * layer, so it always wins;\n * 2. the variables inherited from an ancestor, which is the context API: a\n * VButton sets them from its own size, and the icons inside follow;\n * 3. 1em, which makes the icon follow the size of the text around it.\n */\n --icon-size: var(--vectis-icon-size, 1em);\n --icon-opsz: var(--vectis-icon-opsz, 24);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n /* A ligature is text, so it is the font-size that sizes it, and the box follows as 1em.\n TRAP — the box must NOT read `--icon-size` again: an `em` there resolves against\n this element's own font-size, already enlarged by the line below, so a context size\n of 1.5em drew a box of 2.25em around a glyph of 1.5em. */\n font-size: var(--icon-size);\n inline-size: 1em;\n block-size: 1em;\n }\n\n /* When the icon font has not loaded, a ligature shows as its own name in plain\n text. Clipping keeps that text inside the square instead of letting it push the\n layout apart. The rule is scoped to the ONE branch it protects rather than to\n `.v-icon`: a third-party icon font may legitimately draw outside its 1em box,\n and clipping everything would cut those glyphs. */\n .v-icon:has(> .v-icon-symbol) {\n overflow: hidden;\n }\n\n .v-icon-symbol {\n /* Declared here and not only under `[data-filled]`: a custom property inherits, so a\n host application setting `--icon-fill` on an ancestor would otherwise fill every\n icon below it. */\n --icon-fill: 0;\n font-family: var(--vectis-font-family-icon);\n font-weight: var(--vectis-font-weight-regular);\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n letter-spacing: normal;\n text-transform: none;\n white-space: nowrap;\n direction: ltr;\n /* The variable axes of the Material Symbols font. These numbers are the font's\n own technical contract and not design decisions, which is why they are written\n literally here rather than taken from tokens — the same tolerance as the\n opacities. */\n font-variation-settings:\n 'FILL' var(--icon-fill),\n 'wght' 400,\n 'GRAD' 0,\n 'opsz' var(--icon-opsz);\n }\n\n .v-icon[data-filled] .v-icon-symbol {\n --icon-fill: 1;\n }\n\n /* `:dir()` reads the direction the browser computed rather than an attribute spelled on\n an ancestor, and `scale` is the individual property, so it composes with a rotation\n a consumer applies (a disclosure chevron) instead of replacing its transform. */\n .v-icon[data-mirror]:dir(rtl) {\n scale: -1 1;\n }\n\n .v-icon-img,\n .v-icon > svg {\n inline-size: 100%;\n block-size: 100%;\n object-fit: contain;\n }\n\n /* An icon coming from a class-based font, as Font Awesome and Phosphor ship them:\n the glyph is drawn by a `::before`, at the font-size `.v-icon` has set. Because\n the design system renders this element itself rather than accepting the usual\n `<i>`, one rule normalizes it for every such library — `font-style` undoes the\n italics those conventions carry, and `line-height` stops the inherited line box\n from pushing the glyph off centre. */\n .v-icon-glyph {\n display: block;\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n }\n\n /* An SVG drawn from the built-in icons. The fill is set on OUR class and never on\n `.v-icon > svg`, so that a multicoloured SVG handed in through the slot keeps\n its own colours instead of being flattened to the text colour. `display: block`\n removes the gap an inline element leaves under its baseline. */\n .v-icon-svg {\n display: block;\n fill: currentcolor;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyEA,IAAM,IAAQ;EA4Bd,SAAS,EAAI,GAA8B;GAOzC,OANI,UAAU,IACL;IAAE,MAAM;IAAQ,MAAM,EAAO;IAAM,SAAS,EAAO,WAAA;GAAyB,IACjF,eAAe,IACV;IAAE,MAAM;IAAa,WAAW,EAAO;IAAW,OAAO,EAAO;GAAM,IAC3E,SAAS,IAAe;IAAE,MAAM;IAAO,KAAK,EAAO;GAAI,IACvD,UAAU,IAAe;IAAE,MAAM;IAAQ,MAAM,EAAO;IAAM,OAAO,EAAO;GAAM,IAC7E;IAAE,MAAM;IAAS,OAAO,EAAO;GAAM;EAC9C;EAMA,IAAM,IAAW,QAAe,EAAO,EAAM,IAAI,CAAC,GAW5C,IAAW,QAAqC;GACpD,IAAI,EAAM,QAAQ,OAAO,EAAI,EAAM,MAAM;GAGzC,IAAI,EAAM,OAAO,EAAM,SAAS,KAAA,GAAW;GAE3C,IAAM,IAAS,EAAY,EAAS,OAAQ,EAAE,QAAQ,EAAM,OAAO,CAAC;GACpE,IAAI,GAAQ,OAAO,EAAI,CAAM;GAI7B,IAAI,OAAO,EAAM,QAAS,UAAU;GAEpC,IAAM,EAAE,aAAU,EAAM;GAIxB,OAAO;IAAE,MAAM;IAAQ,OADT,EAAM,SAAS,EAAM,KAAK,KAAA,MAAc,EAAM;IAC/B,SAAS;GAAc;EACtD,CAAC;yBAIC,EAoCO,QAAA;GAnCL,OAAM;GACL,OAAK,EAAE,EAAA,CAAA,CAAE,CAAC,EAAA,IAAI,MAAM,KAAA,IAAiD,KAAA,IAAxC,EAAA,sBAA2B,EAAA,CAAA,CAAE,CAAC,EAAA,IAAI,EAAA,CAAe;GAC9E,aAAW,EAAA;GACX,eAAa,EAAA,UAAU,KAAA;GACvB,eAAa,EAAA,YAAY,KAAA;GACzB,MAAM,EAAA,QAAK,QAAW,KAAA;GACtB,cAAY,EAAA;GACZ,eAAa,EAAA,QAAQ,KAAA,IAAS;MAEf,EAAA,SAAA,EAAA,GAAhB,EAsBW,GAAA,EAAA,KAAA,EAAA,GAAA,CApBD,EAAA,MAAS,SAAI,UAAA,EAAA,GADrB,EAQM,OAAA;;GANJ,OAAM;GACL,SAAS,EAAA,MAAS;GACnB,eAAY;GACZ,WAAU;MAEV,EAA2B,QAAA,EAApB,GAAG,EAAA,MAAS,KAAA,GAAA,MAAA,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA,KAIR,EAAA,MAAS,SAAI,eAAA,EAAA,GAF1B,EAIE,EAHK,EAAA,MAAS,SAAS,GAAA,EAAA,EAAA,EAAA,KAAA,EAAA,GAEf,EAAA,MAAS,KAAK,CAAA,GAAA,MAAA,EAAA,KAER,EAAA,MAAS,SAAI,SAAA,EAAA,GAA7B,EAAyF,OAAA;;GAAhD,OAAM;GAAc,KAAK,EAAA,MAAS;GAAK,KAAI;oBAGnE,EAAA,MAAS,SAAI,UAAA,EAAA,GAA9B,EAES,QAAA;;GAFkC,OAAK,EAAA,CAAC,iBAAwB,EAAA,MAAS,KAAK,CAAA;OACrF,EAAA,MAAS,IAAI,GAAA,CAAA,MAAA,EAAA,GAEf,EAA4D,QAAA;;GAA/C,OAAK,EAAA,CAAC,gBAAuB,EAAA,MAAS,KAAK,CAAA;wBAE1C,EAAA,OAAA,EAAA,GAAhB,EAA4D,OAAA;;GAAvC,OAAM;GAAc,KAAK,EAAA;GAAK,KAAI;oBACtC,EAAA,QAAA,EAAA,GAAjB,EAAkE,QAAlE,GAAkE,EAAlB,EAAA,KAAQ,GAAA,CAAA,KACxD,EAAe,EAAA,QAAA,WAAA,CAAA,GAAA,KAAA,GAAA,KAAA,GAAA,CAAA,CAAA,GAAA,IAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"VIcon.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VIcon/VIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Resolve icon render, src and name in priority order. Named icons consult the consumer\n * resolver, embedded drawing and ligature fallback; explicit drawing needs no font.\n */\nimport { computed, type Component } from 'vue'\n\nimport { iconName as nameOf } from './iconProps'\nimport { ICON_VIEW_BOX } from './icons/viewBox'\nimport { resolveIcon } from './resolver'\nimport type { BuiltinIcon, IconRender } from './types'\n\nimport { px } from '../../utils/css'\n\ninterface IconProps {\n /** Which icon to draw. */\n name?: string | BuiltinIcon\n /**\n * An explicit description of what to draw (an image, a component, a path, a class), which\n * wins over everything else.\n */\n render?: IconRender\n /** The address of an image to use as the icon. It wins over `name`. */\n src?: string\n /**\n * A size in pixels, as a number or a numeric string. Left out, or given something that\n * is not a number, the icon takes the size its context imposes (a button sets one for\n * the icons inside it), and failing that 1em, which makes it follow the surrounding\n * text.\n */\n size?: number | string\n /**\n * What the icon means, for screen readers. Leaving it out marks the icon as\n * decorative and hides it from them, which is the right thing whenever the\n * surrounding text already says what it says.\n */\n label?: string\n /** Draws the filled version of the icon. */\n filled?: boolean\n /**\n * Flips the icon horizontally in a right-to-left context, for a glyph that points at a\n * physical direction: a \"previous\" chevron points left in English and right in Arabic. Off by\n * default, since most icons mean the same thing in both directions.\n */\n mirrored?: boolean\n}\n\nconst props = withDefaults(defineProps<IconProps>(), {\n name: undefined,\n render: undefined,\n src: undefined,\n size: undefined,\n label: undefined,\n filled: false,\n mirrored: false,\n})\n\ndefineSlots<{\n /** An inline SVG, used when none of `render`, `src` and `name` was given. */\n default?(): unknown\n}>()\n\n/**\n * The chosen source, labelled with its kind. The public `IconRender` union carries no such\n * label; the consumer writes it by hand and has nothing to tag it with; but the template needs\n * one thing it can branch on reliably, and applying the tag is also where the documented\n * precedence between the shapes gets decided.\n */\ntype Resolved =\n | { kind: 'path'; path: string; viewBox: string }\n | { kind: 'component'; component: Component; props?: Record<string, unknown> }\n | { kind: 'src'; src: string }\n | { kind: 'text'; text: string; class?: string }\n | { kind: 'class'; class: string }\n\nfunction tag(render: IconRender): Resolved {\n if ('path' in render)\n return { kind: 'path', path: render.path, viewBox: render.viewBox ?? ICON_VIEW_BOX }\n if ('component' in render)\n return { kind: 'component', component: render.component, props: render.props }\n if ('src' in render) return { kind: 'src', src: render.src }\n if ('text' in render) return { kind: 'text', text: render.text, class: render.class }\n return { kind: 'class', class: render.class }\n}\n\n/**\n * The icon's identity: the name the resolver is asked for, and the one `data-icon`\n * records whatever the drawing turned out to come from.\n */\nconst iconName = computed(() => nameOf(props.name))\n\n// @fallback\n// Ligature is the last resort, reached when neither the resolver nor the registry recognized\n// the name.\n/**\n * Works out what to draw: an explicit `render` if there is one, then the consumer's resolver,\n * then the icons built into the library. Answering `undefined` here is not a failure; it means\n * the template falls through to the image, the ligature or the slot.\n */\nconst resolved = computed<Resolved | undefined>(() => {\n if (props.render) return tag(props.render)\n // TRUTHINESS on `src`, the test the template renders the image with: an empty `src=\"\"`\n // renders no image, so it must not stop the name from being drawn either.\n if (props.src || props.name === undefined) return undefined\n\n const custom = resolveIcon(iconName.value!, { filled: props.filled })\n if (custom) return tag(custom)\n\n // A bare name has no drawing of its own, so it falls through to the ligature; only\n // one of the library's own icons brings its paths along.\n if (typeof props.name === 'string') return undefined\n\n const { paths } = props.name\n // A second path is only stored when filling actually changes the drawing, so most\n // icons have one path and fall back to it (see the `BuiltinIcon` type).\n const path = (props.filled ? paths[1] : undefined) ?? paths[0]\n return { kind: 'path', path, viewBox: ICON_VIEW_BOX }\n})\n</script>\n\n<template>\n <span\n class=\"v-icon\"\n :style=\"px(size) !== undefined ? { '--vectis-icon-size': px(size) } : undefined\"\n :data-icon=\"iconName\"\n :data-filled=\"filled || undefined\"\n :data-mirror=\"mirrored || undefined\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <template v-if=\"resolved\">\n <svg\n v-if=\"resolved.kind === 'path'\"\n class=\"v-icon-svg\"\n :viewBox=\"resolved.viewBox\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path :d=\"resolved.path\" />\n </svg>\n <component\n :is=\"resolved.component\"\n v-else-if=\"resolved.kind === 'component'\"\n v-bind=\"resolved.props\"\n />\n <img v-else-if=\"resolved.kind === 'src'\" class=\"v-icon-img\" :src=\"resolved.src\" alt=\"\" />\n <!-- The interpolation is written flush against the tags: a ligature is matched\n on the exact text, and any surrounding whitespace would break it -->\n <span v-else-if=\"resolved.kind === 'text'\" class=\"v-icon-symbol\" :class=\"resolved.class\">{{\n resolved.text\n }}</span>\n <span v-else class=\"v-icon-glyph\" :class=\"resolved.class\" />\n </template>\n <img v-else-if=\"src\" class=\"v-icon-img\" :src=\"src\" alt=\"\" />\n <span v-else-if=\"name\" class=\"v-icon-symbol\">{{ iconName }}</span>\n <slot v-else />\n </span>\n</template>\n\n<style>\n@layer vectis.components {\n .v-icon {\n /*\n * How the size is settled, in order of priority: 1. the `size` prop, written inline on the\n * element as --vectis-icon-size; a declaration made on the element itself beats both\n * inheritance and the layer, so it always wins; 2. the variables inherited from an\n * ancestor, which is the context API: a VButton sets them from its own size, and the icons\n * inside follow; 3. 1em, which makes the icon follow the size of the text around it.\n */\n --icon-size: var(--vectis-icon-size, 1em);\n --icon-opsz: var(--vectis-icon-opsz, 24);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: none;\n /*\n * The box must not read `--icon-size` again: an `em` there resolves against this element's\n * own font-size, already enlarged by the line below, so a context size of 1.5em drew a box\n * of 2.25em around a glyph of 1.5em.\n */\n font-size: var(--icon-size);\n inline-size: 1em;\n block-size: 1em;\n }\n\n /*\n * When the icon font has not loaded, a ligature shows as its own name in plain text. Clipping\n * keeps that text inside the square instead of letting it push the layout apart.\n */\n .v-icon:has(> .v-icon-symbol) {\n overflow: hidden;\n }\n\n .v-icon-symbol {\n /* Declared here and not only under `[data-filled]`: a custom property inherits, so a\n host application setting `--icon-fill` on an ancestor would otherwise fill every\n icon below it. */\n --icon-fill: 0;\n font-family: var(--vectis-font-family-icon);\n font-weight: var(--vectis-font-weight-regular);\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n letter-spacing: normal;\n text-transform: none;\n white-space: nowrap;\n direction: ltr;\n /*\n * The variable axes of the Material Symbols font. These numbers are the font's own\n * technical contract and not design decisions, which is why they are written literally here\n * rather than taken from tokens; the same tolerance as the opacities.\n */\n font-variation-settings:\n 'FILL' var(--icon-fill),\n 'wght' 400,\n 'GRAD' 0,\n 'opsz' var(--icon-opsz);\n }\n\n .v-icon[data-filled] .v-icon-symbol {\n --icon-fill: 1;\n }\n\n /* `:dir()` reads the direction the browser computed rather than an attribute spelled on\n an ancestor, and `scale` is the individual property, so it composes with a rotation\n a consumer applies (a disclosure chevron) instead of replacing its transform. */\n .v-icon[data-mirror]:dir(rtl) {\n scale: -1 1;\n }\n\n .v-icon-img,\n .v-icon > svg {\n inline-size: 100%;\n block-size: 100%;\n object-fit: contain;\n }\n\n /*\n * Because the design system renders this element itself rather than accepting the usual\n * `<i>`, one rule normalizes it for every such library; `font-style` undoes the italics those\n * conventions carry, and `line-height` stops the inherited line box from pushing the glyph\n * off centre.\n */\n .v-icon-glyph {\n display: block;\n font-style: normal;\n line-height: var(--vectis-font-leading-none);\n }\n\n /*\n * The fill is set on OUR class and never on `.v-icon > svg`, so that a multicoloured SVG\n * handed in through the slot keeps its own colours instead of being flattened to the text\n * colour. `display: block` removes the gap an inline element leaves under its baseline.\n */\n .v-icon-svg {\n display: block;\n fill: currentcolor;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+CA,IAAM,IAAQ;EA4Bd,SAAS,EAAI,GAA8B;GAOzC,OANI,UAAU,IACL;IAAE,MAAM;IAAQ,MAAM,EAAO;IAAM,SAAS,EAAO,WAAA;GAAyB,IACjF,eAAe,IACV;IAAE,MAAM;IAAa,WAAW,EAAO;IAAW,OAAO,EAAO;GAAM,IAC3E,SAAS,IAAe;IAAE,MAAM;IAAO,KAAK,EAAO;GAAI,IACvD,UAAU,IAAe;IAAE,MAAM;IAAQ,MAAM,EAAO;IAAM,OAAO,EAAO;GAAM,IAC7E;IAAE,MAAM;IAAS,OAAO,EAAO;GAAM;EAC9C;EAMA,IAAM,IAAW,QAAe,EAAO,EAAM,IAAI,CAAC,GAU5C,IAAW,QAAqC;GACpD,IAAI,EAAM,QAAQ,OAAO,EAAI,EAAM,MAAM;GAGzC,IAAI,EAAM,OAAO,EAAM,SAAS,KAAA,GAAW;GAE3C,IAAM,IAAS,EAAY,EAAS,OAAQ,EAAE,QAAQ,EAAM,OAAO,CAAC;GACpE,IAAI,GAAQ,OAAO,EAAI,CAAM;GAI7B,IAAI,OAAO,EAAM,QAAS,UAAU;GAEpC,IAAM,EAAE,aAAU,EAAM;GAIxB,OAAO;IAAE,MAAM;IAAQ,OADT,EAAM,SAAS,EAAM,KAAK,KAAA,MAAc,EAAM;IAC/B,SAAS;GAAc;EACtD,CAAC;yBAIC,EAoCO,QAAA;GAnCL,OAAM;GACL,OAAK,EAAE,EAAA,CAAA,CAAE,CAAC,EAAA,IAAI,MAAM,KAAA,IAAiD,KAAA,IAAxC,EAAA,sBAA2B,EAAA,CAAA,CAAE,CAAC,EAAA,IAAI,EAAA,CAAe;GAC9E,aAAW,EAAA;GACX,eAAa,EAAA,UAAU,KAAA;GACvB,eAAa,EAAA,YAAY,KAAA;GACzB,MAAM,EAAA,QAAK,QAAW,KAAA;GACtB,cAAY,EAAA;GACZ,eAAa,EAAA,QAAQ,KAAA,IAAS;MAEf,EAAA,SAAA,EAAA,GAAhB,EAsBW,GAAA,EAAA,KAAA,EAAA,GAAA,CApBD,EAAA,MAAS,SAAI,UAAA,EAAA,GADrB,EAQM,OAAA;;GANJ,OAAM;GACL,SAAS,EAAA,MAAS;GACnB,eAAY;GACZ,WAAU;MAEV,EAA2B,QAAA,EAApB,GAAG,EAAA,MAAS,KAAA,GAAA,MAAA,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA,KAIR,EAAA,MAAS,SAAI,eAAA,EAAA,GAF1B,EAIE,EAHK,EAAA,MAAS,SAAS,GAAA,EAAA,EAAA,EAAA,KAAA,EAAA,GAEf,EAAA,MAAS,KAAK,CAAA,GAAA,MAAA,EAAA,KAER,EAAA,MAAS,SAAI,SAAA,EAAA,GAA7B,EAAyF,OAAA;;GAAhD,OAAM;GAAc,KAAK,EAAA,MAAS;GAAK,KAAI;oBAGnE,EAAA,MAAS,SAAI,UAAA,EAAA,GAA9B,EAES,QAAA;;GAFkC,OAAK,EAAA,CAAC,iBAAwB,EAAA,MAAS,KAAK,CAAA;OACrF,EAAA,MAAS,IAAI,GAAA,CAAA,MAAA,EAAA,GAEf,EAA4D,QAAA;;GAA/C,OAAK,EAAA,CAAC,gBAAuB,EAAA,MAAS,KAAK,CAAA;wBAE1C,EAAA,OAAA,EAAA,GAAhB,EAA4D,OAAA;;GAAvC,OAAM;GAAc,KAAK,EAAA;GAAK,KAAI;oBACtC,EAAA,QAAA,EAAA,GAAjB,EAAkE,QAAlE,GAAkE,EAAlB,EAAA,KAAQ,GAAA,CAAA,KACxD,EAAe,EAAA,QAAA,WAAA,CAAA,GAAA,KAAA,GAAA,KAAA,GAAA,CAAA,CAAA,GAAA,IAAA,CAAA"}
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { BuiltinIcon, IconRender, IconSource } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Turns what a consumer wrote in an icon prop into the props VIcon expects. It is
|
|
4
|
-
* what every component of the design system calls before rendering an icon it was
|
|
5
|
-
* given.
|
|
6
|
-
*
|
|
7
|
-
* There is NO guesswork in the conversion: a plain string is ALWAYS a name, and an
|
|
8
|
-
* image or a component is declared explicitly as an object, `{ src: '/logo.svg' }`.
|
|
9
|
-
* That is what keeps the usual naming conventions (`mdi:close`, `fa6-solid:xmark`)
|
|
10
|
-
* reaching the resolver intact rather than being mistaken for addresses.
|
|
11
|
-
*/
|
|
2
|
+
/** Turns what a consumer wrote in an icon prop into the props VIcon expects. */
|
|
12
3
|
export declare const iconProps: (icon: IconSource) => {
|
|
13
4
|
name: string | BuiltinIcon;
|
|
14
5
|
render?: undefined;
|
|
@@ -17,14 +8,9 @@ export declare const iconProps: (icon: IconSource) => {
|
|
|
17
8
|
name?: undefined;
|
|
18
9
|
};
|
|
19
10
|
/**
|
|
20
|
-
* A readable name for an icon, used as a last-resort accessible label when the
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* image, a component, a class), because such a description has no name to give and
|
|
25
|
-
* an `[object Object]` read out by a screen reader would be worse than silence.
|
|
26
|
-
* `useIconClickHandlers` is what warns the integrator when that happens on a control
|
|
27
|
-
* that needs a name. One of the library's own icons does carry its name, so it
|
|
28
|
-
* answers here with that name, as a bare string does.
|
|
11
|
+
* A readable name for an icon, used as a last-resort accessible label when the consumer
|
|
12
|
+
* supplied none. It answers `undefined` as soon as the icon was DESCRIBED rather than named (an
|
|
13
|
+
* image, a component, a class), because such a description has no name to give and an
|
|
14
|
+
* `[object Object]` read out by a screen reader would be worse than silence.
|
|
29
15
|
*/
|
|
30
16
|
export declare const iconName: (icon: IconSource | undefined) => string | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iconProps.js","names":[],"sources":["../../../src/components/VIcon/iconProps.ts"],"sourcesContent":["import type { BuiltinIcon, IconRender, IconSource } from './types'\n\n/**\n * Tells one of the library's own icons from something the consumer described by\n * hand. Only a `BuiltinIcon` carries `paths`, and the distinction matters: it is\n * what routes a built-in default to `name`, where the resolver still gets first\n * refusal, instead of to `render`, which wins outright and would silence it.\n */\nconst isBuiltinIcon = (icon: BuiltinIcon | IconRender): icon is BuiltinIcon => 'paths' in icon\n\n
|
|
1
|
+
{"version":3,"file":"iconProps.js","names":[],"sources":["../../../src/components/VIcon/iconProps.ts"],"sourcesContent":["import type { BuiltinIcon, IconRender, IconSource } from './types'\n\n/**\n * Tells one of the library's own icons from something the consumer described by\n * hand. Only a `BuiltinIcon` carries `paths`, and the distinction matters: it is\n * what routes a built-in default to `name`, where the resolver still gets first\n * refusal, instead of to `render`, which wins outright and would silence it.\n */\nconst isBuiltinIcon = (icon: BuiltinIcon | IconRender): icon is BuiltinIcon => 'paths' in icon\n\n/** Turns what a consumer wrote in an icon prop into the props VIcon expects. */\nexport const iconProps = (icon: IconSource) =>\n typeof icon === 'string' || isBuiltinIcon(icon) ? { name: icon } : { render: icon }\n\n/**\n * A readable name for an icon, used as a last-resort accessible label when the consumer\n * supplied none. It answers `undefined` as soon as the icon was DESCRIBED rather than named (an\n * image, a component, a class), because such a description has no name to give and an\n * `[object Object]` read out by a screen reader would be worse than silence.\n */\nexport const iconName = (icon: IconSource | undefined) =>\n typeof icon === 'string' ? icon : icon && isBuiltinIcon(icon) ? icon.name : undefined\n"],"mappings":";AAQA,IAAM,KAAiB,MAAwD,WAAW,GAG7E,KAAa,MACxB,OAAO,KAAS,YAAY,EAAc,CAAI,IAAI,EAAE,MAAM,EAAK,IAAI,EAAE,QAAQ,EAAK,GAQvE,KAAY,MACvB,OAAO,KAAS,WAAW,IAAO,KAAQ,EAAc,CAAI,IAAI,EAAK,OAAO,KAAA"}
|
|
@@ -2,55 +2,31 @@ import { Component } from 'vue';
|
|
|
2
2
|
import { IconName } from './icons/names.js';
|
|
3
3
|
import { IconContext, IconRender } from './types.js';
|
|
4
4
|
/**
|
|
5
|
-
* Turns an icon name into a description of what to draw. Answering `undefined` means
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* makes a PARTIAL mapping usable: map the five names you care about and let the
|
|
9
|
-
* rest be.
|
|
5
|
+
* Turns an icon name into a description of what to draw. Answering `undefined` means "I do not
|
|
6
|
+
* know this name", and not "draw nothing": VIcon then falls back to the built-in icons, and
|
|
7
|
+
* after that to the ligature font.
|
|
10
8
|
*/
|
|
11
9
|
export type IconResolver = (name: string, ctx: IconContext) => IconRender | undefined;
|
|
12
|
-
/**
|
|
13
|
-
* A table mapping the design system's icon names to your own. The IDE suggests the
|
|
14
|
-
* names the library ships with, and any other key is accepted too, so your
|
|
15
|
-
* application's own icons can be aliased through the same table.
|
|
16
|
-
*/
|
|
10
|
+
/** A table mapping the design system's icon names to your own. */
|
|
17
11
|
export type IconAliases = Partial<Record<IconName, string>> & Record<string, string>;
|
|
18
|
-
/**
|
|
19
|
-
* Installs the resolver every VIcon will consult, or removes it when passed
|
|
20
|
-
* `undefined`.
|
|
21
|
-
*
|
|
22
|
-
* Call it at MODULE level (from a Nuxt plugin or from `main.ts`) and never inside
|
|
23
|
-
* a component's `setup()`. Two traps follow from where the state lives. On a server
|
|
24
|
-
* it belongs to the process rather than to a request, which is right for
|
|
25
|
-
* configuration and wrong for anything varying per visitor. And installing it on the
|
|
26
|
-
* client only, from a `plugins/*.client.ts`, makes the browser draw different icons
|
|
27
|
-
* from the ones the server sent, which is a hydration mismatch.
|
|
28
|
-
*/
|
|
12
|
+
/** Installs the resolver every VIcon will consult, or removes it when passed `undefined`. */
|
|
29
13
|
export declare function setIconResolver(next: IconResolver | undefined): void;
|
|
30
14
|
/** Asks the installed resolver, if there is one. Internal to VIcon, not public API. */
|
|
31
15
|
export declare function resolveIcon(name: string, ctx: IconContext): IconRender | undefined;
|
|
32
16
|
/**
|
|
33
|
-
* A resolver for a LIGATURE font: Material Symbols in any of its variants, or an
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* Installing it is also how to have the design system's own icons drawn by the font
|
|
38
|
-
* instead of by the SVGs shipped with the library, and therefore how to get the
|
|
39
|
-
* optical size axis back: those SVGs are drawn at one optical size and cannot follow
|
|
17
|
+
* A resolver for a LIGATURE font: Material Symbols in any of its variants, or an IcoMoon build
|
|
18
|
+
* made that way. Installing it is also how to have the design system's own icons drawn by the
|
|
19
|
+
* font instead of by the SVGs shipped with the library, and therefore how to get the optical
|
|
20
|
+
* size axis back: those SVGs are drawn at one optical size and cannot follow
|
|
40
21
|
* `--vectis-icon-opsz`.
|
|
41
22
|
*/
|
|
42
23
|
export declare function ligatureIconResolver(options?: {
|
|
43
24
|
aliases?: IconAliases;
|
|
44
25
|
}): IconResolver;
|
|
45
26
|
/**
|
|
46
|
-
* A resolver for a font driven by a CLASS and a pseudo-element: Font Awesome,
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* `strict`, which is the default, protects the design system's own icons. One of its
|
|
50
|
-
* names that is NOT in your alias table would otherwise be turned into a class the
|
|
51
|
-
* font does not define, and the icon would render as an empty square; refusing to
|
|
52
|
-
* answer instead lets it fall back to the SVG shipped with the library. Names of
|
|
53
|
-
* your own always pass, since they are already written in your vocabulary.
|
|
27
|
+
* A resolver for a font driven by a CLASS and a pseudo-element: Font Awesome, Phosphor,
|
|
28
|
+
* Bootstrap Icons and their kind. `strict`, which is the default, protects the design system's
|
|
29
|
+
* own icons.
|
|
54
30
|
*/
|
|
55
31
|
export declare function classIconResolver(options: {
|
|
56
32
|
aliases?: IconAliases;
|
|
@@ -62,13 +38,9 @@ export declare function classIconResolver(options: {
|
|
|
62
38
|
strict?: boolean;
|
|
63
39
|
}): IconResolver;
|
|
64
40
|
/**
|
|
65
|
-
* A resolver for an icon set shipped as Vue COMPONENTS: Lucide, Untitled UI and
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* ligature.
|
|
69
|
-
*
|
|
70
|
-
* One contract to honour: each component must have a single `<svg>` as its root,
|
|
71
|
-
* because that is the element the stylesheet sizes.
|
|
41
|
+
* A resolver for an icon set shipped as Vue COMPONENTS: Lucide, Untitled UI and their kind. It
|
|
42
|
+
* is strict by construction, since a name absent from the table has no component to return: it
|
|
43
|
+
* falls back to the built-in icons, and then to the ligature.
|
|
72
44
|
*/
|
|
73
45
|
export declare function componentIconResolver(options: {
|
|
74
46
|
components: Partial<Record<IconName, Component>> & Record<string, Component>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolver.js","names":[],"sources":["../../../src/components/VIcon/resolver.ts"],"sourcesContent":["// @ssr @core
|
|
1
|
+
{"version":3,"file":"resolver.js","names":[],"sources":["../../../src/components/VIcon/resolver.ts"],"sourcesContent":["// @ssr @core\n// Install the icon resolver before SSR and hydration so server and client resolve the same\n// drawings.\n/** The way to plug a third-party icon library into the design system. */\n\nimport { shallowRef, type Component } from 'vue'\n\nimport { builtinIconNames, type IconName } from './icons/names'\nimport type { IconContext, IconRender } from './types'\n\n/*\n * A lookup by name that answers only for the table's own keys: `aliases['constructor']` would\n * otherwise hand back a function from Object.prototype, and the \"I do not know this name\"\n * contract (`undefined`) would break for any name that happens to be one of its members.\n */\nfunction own<T>(table: Record<string, T> | undefined, name: string): T | undefined {\n return table !== undefined && Object.hasOwn(table, name) ? table[name] : undefined\n}\n\n/**\n * Turns an icon name into a description of what to draw. Answering `undefined` means \"I do not\n * know this name\", and not \"draw nothing\": VIcon then falls back to the built-in icons, and\n * after that to the ligature font.\n */\nexport type IconResolver = (name: string, ctx: IconContext) => IconRender | undefined\n\n/** A table mapping the design system's icon names to your own. */\nexport type IconAliases = Partial<Record<IconName, string>> & Record<string, string>\n\nconst resolver = shallowRef<IconResolver | undefined>(undefined)\n\n/** Installs the resolver every VIcon will consult, or removes it when passed `undefined`. */\nexport function setIconResolver(next: IconResolver | undefined): void {\n resolver.value = next\n}\n\n/** Asks the installed resolver, if there is one. Internal to VIcon, not public API. */\nexport function resolveIcon(name: string, ctx: IconContext): IconRender | undefined {\n return resolver.value?.(name, ctx)\n}\n\n/**\n * A resolver for a LIGATURE font: Material Symbols in any of its variants, or an IcoMoon build\n * made that way. Installing it is also how to have the design system's own icons drawn by the\n * font instead of by the SVGs shipped with the library, and therefore how to get the optical\n * size axis back: those SVGs are drawn at one optical size and cannot follow\n * `--vectis-icon-opsz`.\n */\nexport function ligatureIconResolver(options: { aliases?: IconAliases } = {}): IconResolver {\n const { aliases } = options\n return (name) => ({ text: own(aliases, name) ?? name })\n}\n\n/**\n * A resolver for a font driven by a CLASS and a pseudo-element: Font Awesome, Phosphor,\n * Bootstrap Icons and their kind. `strict`, which is the default, protects the design system's\n * own icons.\n */\nexport function classIconResolver(options: {\n aliases?: IconAliases\n /**\n * Builds the class list for one icon. `mapped` is the alias when the table has\n * one, and the original name otherwise.\n */\n className: (mapped: string, filled: boolean) => string\n strict?: boolean\n}): IconResolver {\n const { aliases, className, strict = true } = options\n return (name, ctx) => {\n const mapped = own(aliases, name)\n // The SET of names, never the icons themselves: this asks whether the design system ships\n // the name, and reaching for the drawings to answer it would make a consumer who wired in\n // their own icon library download all 34 Material paths.\n if (mapped === undefined && strict && builtinIconNames.has(name)) return undefined\n return { class: className(mapped ?? name, ctx.filled) }\n }\n}\n\n/**\n * A resolver for an icon set shipped as Vue COMPONENTS: Lucide, Untitled UI and their kind. It\n * is strict by construction, since a name absent from the table has no component to return: it\n * falls back to the built-in icons, and then to the ligature.\n */\nexport function componentIconResolver(options: {\n components: Partial<Record<IconName, Component>> & Record<string, Component>\n props?: (name: string, filled: boolean) => Record<string, unknown>\n}): IconResolver {\n const { components, props } = options\n return (name, ctx) => {\n const component = own<Component>(components, name)\n return component ? { component, props: props?.(name, ctx.filled) } : undefined\n }\n}\n"],"mappings":";;;AAeA,SAAS,EAAO,GAAsC,GAA6B;CACjF,OAAO,MAAU,KAAA,KAAa,OAAO,OAAO,GAAO,CAAI,IAAI,EAAM,KAAQ,KAAA;AAC3E;AAYA,IAAM,IAAW,EAAqC,KAAA,CAAS;AAG/D,SAAgB,EAAgB,GAAsC;CACpE,EAAS,QAAQ;AACnB;AAGA,SAAgB,EAAY,GAAc,GAA0C;CAClF,OAAO,EAAS,QAAQ,GAAM,CAAG;AACnC;AASA,SAAgB,EAAqB,IAAqC,CAAC,GAAiB;CAC1F,IAAM,EAAE,eAAY;CACpB,QAAQ,OAAU,EAAE,MAAM,EAAI,GAAS,CAAI,KAAK,EAAK;AACvD;AAOA,SAAgB,EAAkB,GAQjB;CACf,IAAM,EAAE,YAAS,cAAW,YAAS,OAAS;CAC9C,QAAQ,GAAM,MAAQ;EACpB,IAAM,IAAS,EAAI,GAAS,CAAI;EAI5B,YAAW,KAAA,KAAa,KAAU,EAAiB,IAAI,CAAI,IAC/D,OAAO,EAAE,OAAO,EAAU,KAAU,GAAM,EAAI,MAAM,EAAE;CACxD;AACF;AAOA,SAAgB,EAAsB,GAGrB;CACf,IAAM,EAAE,eAAY,aAAU;CAC9B,QAAQ,GAAM,MAAQ;EACpB,IAAM,IAAY,EAAe,GAAY,CAAI;EACjD,OAAO,IAAY;GAAE;GAAW,OAAO,IAAQ,GAAM,EAAI,MAAM;EAAE,IAAI,KAAA;CACvE;AACF"}
|