vectis-ui 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/VAccordion/VAccordion.css +1 -1
- package/dist/components/VAccordion/VAccordion.d.ts +6 -5
- 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.css +1 -1
- package/dist/components/VAccordion/VAccordionItem.d.ts +1 -1
- package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAlert/VAlert.css +2 -0
- package/dist/components/VAlert/VAlert.d.ts +73 -0
- package/dist/components/VAlert/VAlert.js +9 -0
- package/dist/components/VAlert/VAlert.js.map +1 -0
- package/dist/components/VAlert/VAlert.vue_vue_type_script_setup_true_lang.js +96 -0
- package/dist/components/VAlert/VAlert.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VAvatar/VAvatar.d.ts +2 -2
- package/dist/components/VAvatar/VAvatarGroup.d.ts +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/components/VBadge/VBadge.d.ts +3 -3
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/VButton/VButton.d.ts +4 -4
- package/dist/components/VButton/VButtonGroup.css +1 -1
- package/dist/components/VButton/VButtonGroup.d.ts +2 -2
- package/dist/components/VButton/VButtonGroup.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.css +1 -1
- package/dist/components/VCalendar/VCalendar.d.ts +7 -1
- package/dist/components/VCalendar/VCalendar.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +20 -18
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.css +1 -1
- package/dist/components/VCalendar/VCalendarMonth.d.ts +2 -2
- 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.css +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +3 -3
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.css +1 -1
- package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/gesture.d.ts +1 -16
- package/dist/components/VCalendar/gesture.js +1 -1
- package/dist/components/VCalendar/gesture.js.map +1 -1
- package/dist/components/VCalendar/types.d.ts +2 -0
- package/dist/components/VCard/VCard.css +2 -0
- package/dist/components/VCard/VCard.d.ts +87 -0
- package/dist/components/VCard/VCard.js +9 -0
- package/dist/components/VCard/VCard.js.map +1 -0
- package/dist/components/VCard/VCard.vue_vue_type_script_setup_true_lang.js +103 -0
- package/dist/components/VCard/VCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VCarousel/VCarousel.d.ts +1 -1
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +18 -18
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCheckbox/VCheckMark.css +2 -0
- package/dist/components/VCheckbox/VCheckMark.js +9 -0
- package/dist/components/VCheckbox/VCheckMark.js.map +1 -0
- package/dist/components/VCheckbox/VCheckMark.vue_vue_type_script_setup_true_lang.js +33 -0
- package/dist/components/VCheckbox/VCheckMark.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VCheckbox/VCheckbox.css +1 -1
- package/dist/components/VCheckbox/VCheckbox.d.ts +8 -3
- package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +54 -69
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VChip/VChip.d.ts +6 -6
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +26 -26
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VColorInput/VColorInput.css +2 -0
- package/dist/components/VColorInput/VColorInput.d.ts +113 -0
- package/dist/components/VColorInput/VColorInput.js +9 -0
- package/dist/components/VColorInput/VColorInput.js.map +1 -0
- package/dist/components/VColorInput/VColorInput.vue_vue_type_script_setup_true_lang.js +230 -0
- package/dist/components/VColorInput/VColorInput.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VColorPicker/VColorPicker.css +2 -0
- package/dist/components/VColorPicker/VColorPicker.d.ts +67 -0
- package/dist/components/VColorPicker/VColorPicker.js +9 -0
- package/dist/components/VColorPicker/VColorPicker.js.map +1 -0
- package/dist/components/VColorPicker/VColorPicker.vue_vue_type_script_setup_true_lang.js +390 -0
- package/dist/components/VColorPicker/VColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VColorPicker/color.d.ts +42 -0
- package/dist/components/VColorPicker/color.js +158 -0
- package/dist/components/VColorPicker/color.js.map +1 -0
- package/dist/components/VCombobox/VCombobox.css +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +46 -60
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +271 -372
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCommandPalette/VCommandPalette.css +2 -0
- package/dist/components/VCommandPalette/VCommandPalette.d.ts +163 -0
- package/dist/components/VCommandPalette/VCommandPalette.js +9 -0
- package/dist/components/VCommandPalette/VCommandPalette.js.map +1 -0
- package/dist/components/VCommandPalette/VCommandPalette.vue_vue_type_script_setup_true_lang.js +321 -0
- package/dist/components/VCommandPalette/VCommandPalette.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VCommandPalette/VCommandPaletteItem.css +2 -0
- package/dist/components/VCommandPalette/VCommandPaletteItem.d.ts +38 -0
- package/dist/components/VCommandPalette/VCommandPaletteItem.js +9 -0
- package/dist/components/VCommandPalette/VCommandPaletteItem.js.map +1 -0
- package/dist/components/VCommandPalette/VCommandPaletteItem.vue_vue_type_script_setup_true_lang.js +69 -0
- package/dist/components/VCommandPalette/VCommandPaletteItem.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VContextMenu/VContextMenu.css +2 -0
- package/dist/components/VContextMenu/VContextMenu.d.ts +66 -0
- package/dist/components/VContextMenu/VContextMenu.js +9 -0
- package/dist/components/VContextMenu/VContextMenu.js.map +1 -0
- package/dist/components/VContextMenu/VContextMenu.vue_vue_type_script_setup_true_lang.js +148 -0
- package/dist/components/VContextMenu/VContextMenu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VDataTable/VDataTable.css +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +41 -16
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +307 -194
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.d.ts +27 -7
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +170 -162
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.d.ts +3 -3
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +228 -227
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/keyboard.d.ts +7 -7
- package/dist/components/VDatePicker/keyboard.js +8 -6
- package/dist/components/VDatePicker/keyboard.js.map +1 -1
- package/dist/components/VDatePicker/names.js +16 -20
- package/dist/components/VDatePicker/names.js.map +1 -1
- package/dist/components/VDialog/VDialog.d.ts +2 -4
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +46 -77
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDrawer/VDrawer.css +2 -0
- package/dist/components/VDrawer/VDrawer.d.ts +102 -0
- package/dist/components/VDrawer/VDrawer.js +9 -0
- package/dist/components/VDrawer/VDrawer.js.map +1 -0
- package/dist/components/VDrawer/VDrawer.vue_vue_type_script_setup_true_lang.js +128 -0
- package/dist/components/VDrawer/VDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VEmptyState/VEmptyState.css +2 -0
- package/dist/components/VEmptyState/VEmptyState.d.ts +43 -0
- package/dist/components/VEmptyState/VEmptyState.js +9 -0
- package/dist/components/VEmptyState/VEmptyState.js.map +1 -0
- package/dist/components/VEmptyState/VEmptyState.vue_vue_type_script_setup_true_lang.js +48 -0
- package/dist/components/VEmptyState/VEmptyState.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VField/VField.css +2 -0
- package/dist/components/VField/VField.d.ts +84 -0
- package/dist/components/VField/VField.js +9 -0
- package/dist/components/VField/VField.js.map +1 -0
- package/dist/components/VField/VField.vue_vue_type_script_setup_true_lang.js +99 -0
- package/dist/components/VField/VField.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VField/VFieldAnnouncer.d.ts +7 -0
- package/dist/components/VField/VFieldAnnouncer.js +7 -0
- package/dist/components/VField/VFieldAnnouncer.js.map +1 -0
- package/dist/components/VField/VFieldAnnouncer.vue_vue_type_script_setup_true_lang.js +19 -0
- package/dist/components/VField/VFieldAnnouncer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VField/VFieldMessage.d.ts +11 -0
- package/dist/components/VField/VFieldMessage.js +7 -0
- package/dist/components/VField/VFieldMessage.js.map +1 -0
- package/dist/components/VField/VFieldMessage.vue_vue_type_script_setup_true_lang.js +32 -0
- package/dist/components/VField/VFieldMessage.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VFieldset/VFieldset.css +2 -0
- package/dist/components/VFieldset/VFieldset.d.ts +52 -0
- package/dist/components/VFieldset/VFieldset.js +9 -0
- package/dist/components/VFieldset/VFieldset.js.map +1 -0
- package/dist/components/VFieldset/VFieldset.vue_vue_type_script_setup_true_lang.js +63 -0
- package/dist/components/VFieldset/VFieldset.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VFileInput/VFileInput.css +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +31 -7
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +168 -161
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.d.ts +4 -4
- package/dist/components/VHotkeys/VHotkeys.css +1 -1
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +27 -30
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.d.ts +2 -0
- package/dist/components/VHotkeys/platform.js +24 -5
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VHoverCard/VHoverCard.css +2 -0
- package/dist/components/VHoverCard/VHoverCard.d.ts +62 -0
- package/dist/components/VHoverCard/VHoverCard.js +9 -0
- package/dist/components/VHoverCard/VHoverCard.js.map +1 -0
- package/dist/components/VHoverCard/VHoverCard.vue_vue_type_script_setup_true_lang.js +97 -0
- package/dist/components/VHoverCard/VHoverCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VIcon/icons/add.d.ts +5 -0
- package/dist/components/VIcon/icons/add.js +9 -0
- package/dist/components/VIcon/icons/add.js.map +1 -0
- package/dist/components/VIcon/icons/colorize.d.ts +5 -0
- package/dist/components/VIcon/icons/colorize.js +9 -0
- package/dist/components/VIcon/icons/colorize.js.map +1 -0
- package/dist/components/VIcon/icons/inbox.d.ts +5 -0
- package/dist/components/VIcon/icons/inbox.js +9 -0
- package/dist/components/VIcon/icons/inbox.js.map +1 -0
- package/dist/components/VIcon/icons/index.d.ts +42 -2
- package/dist/components/VIcon/icons/index.js +88 -72
- package/dist/components/VIcon/icons/index.js.map +1 -1
- package/dist/components/VIcon/icons/names.d.ts +2 -2
- package/dist/components/VIcon/icons/names.js +1 -1
- package/dist/components/VIcon/icons/names.js.map +1 -1
- package/dist/components/VIcon/icons/open_in_new.d.ts +5 -0
- package/dist/components/VIcon/icons/open_in_new.js +9 -0
- package/dist/components/VIcon/icons/open_in_new.js.map +1 -0
- package/dist/components/VIcon/icons/priority_high.d.ts +5 -0
- package/dist/components/VIcon/icons/priority_high.js +9 -0
- package/dist/components/VIcon/icons/priority_high.js.map +1 -0
- package/dist/components/VIcon/icons/remove.d.ts +5 -0
- package/dist/components/VIcon/icons/remove.js +9 -0
- package/dist/components/VIcon/icons/remove.js.map +1 -0
- package/dist/components/VIcon/icons/search_off.d.ts +5 -0
- package/dist/components/VIcon/icons/search_off.js +9 -0
- package/dist/components/VIcon/icons/search_off.js.map +1 -0
- package/dist/components/VIcon/icons/star.d.ts +5 -0
- package/dist/components/VIcon/icons/star.js +9 -0
- package/dist/components/VIcon/icons/star.js.map +1 -0
- package/dist/components/VIconButton/VIconButton.d.ts +4 -4
- package/dist/components/VInput/VInput.css +1 -1
- package/dist/components/VInput/VInput.d.ts +51 -2
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +129 -106
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.css +1 -1
- package/dist/components/VInput/VInputGroup.d.ts +20 -0
- package/dist/components/VInput/VInputGroup.js.map +1 -1
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +50 -51
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.css +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +27 -4
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +131 -113
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VLink/VLink.css +2 -0
- package/dist/components/VLink/VLink.d.ts +53 -0
- package/dist/components/VLink/VLink.js +9 -0
- package/dist/components/VLink/VLink.js.map +1 -0
- package/dist/components/VLink/VLink.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/components/VLink/VLink.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VListbox/VListboxChevron.css +2 -0
- package/dist/components/VListbox/VListboxChevron.d.ts +12 -0
- package/dist/components/VListbox/VListboxChevron.js +9 -0
- package/dist/components/VListbox/VListboxChevron.js.map +1 -0
- package/dist/components/VListbox/VListboxChevron.vue_vue_type_script_setup_true_lang.js +25 -0
- package/dist/components/VListbox/VListboxChevron.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VListbox/VListboxGroup.css +2 -0
- package/dist/components/VListbox/VListboxGroup.d.ts +27 -0
- package/dist/components/VListbox/VListboxGroup.js +9 -0
- package/dist/components/VListbox/VListboxGroup.js.map +1 -0
- package/dist/components/VListbox/VListboxGroup.vue_vue_type_script_setup_true_lang.js +21 -0
- package/dist/components/VListbox/VListboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VListbox/VListboxOption.css +2 -0
- package/dist/components/{VCombobox/VComboboxOption.d.ts → VListbox/VListboxOption.d.ts} +4 -4
- package/dist/components/VListbox/VListboxOption.js +9 -0
- package/dist/components/VListbox/VListboxOption.js.map +1 -0
- package/dist/components/{VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js → VListbox/VListboxOption.vue_vue_type_script_setup_true_lang.js} +6 -6
- package/dist/components/VListbox/VListboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VListbox/VListboxPanel.css +2 -0
- package/dist/components/VListbox/VListboxPanel.d.ts +59 -0
- package/dist/components/VListbox/VListboxPanel.js +9 -0
- package/dist/components/VListbox/VListboxPanel.js.map +1 -0
- package/dist/components/VListbox/VListboxPanel.vue_vue_type_script_setup_true_lang.js +123 -0
- package/dist/components/VListbox/VListboxPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VListbox/VListboxSeparator.css +2 -0
- package/dist/components/VListbox/VListboxSeparator.d.ts +3 -0
- package/dist/components/VListbox/VListboxSeparator.js +9 -0
- package/dist/components/VListbox/VListboxSeparator.js.map +1 -0
- package/dist/components/{VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js → VListbox/VListboxSeparator.vue_vue_type_script_setup_true_lang.js} +4 -4
- package/dist/components/VListbox/VListboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VListbox/VListboxValues.css +2 -0
- package/dist/components/VListbox/VListboxValues.d.ts +52 -0
- package/dist/components/VListbox/VListboxValues.js +9 -0
- package/dist/components/VListbox/VListboxValues.js.map +1 -0
- package/dist/components/VListbox/VListboxValues.vue_vue_type_script_setup_true_lang.js +103 -0
- package/dist/components/VListbox/VListboxValues.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VMenu/VMenu.d.ts +1 -1
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/VMenu/VMenuItem.d.ts +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +16 -16
- package/dist/components/VMenu/VMenuPanel.d.ts +2 -2
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +7 -7
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMeter/VMeter.css +2 -0
- package/dist/components/VMeter/VMeter.d.ts +77 -0
- package/dist/components/VMeter/VMeter.js +9 -0
- package/dist/components/VMeter/VMeter.js.map +1 -0
- package/dist/components/VMeter/VMeter.vue_vue_type_script_setup_true_lang.js +113 -0
- package/dist/components/VMeter/VMeter.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VNumberInput/VNumberInput.css +2 -0
- package/dist/components/VNumberInput/VNumberInput.d.ts +114 -0
- package/dist/components/VNumberInput/VNumberInput.js +9 -0
- package/dist/components/VNumberInput/VNumberInput.js.map +1 -0
- package/dist/components/VNumberInput/VNumberInput.vue_vue_type_script_setup_true_lang.js +251 -0
- package/dist/components/VNumberInput/VNumberInput.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VNumberInput/number.d.ts +30 -0
- package/dist/components/VNumberInput/number.js +38 -0
- package/dist/components/VNumberInput/number.js.map +1 -0
- package/dist/components/VPagination/VPagination.d.ts +1 -1
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +77 -77
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.d.ts +2 -2
- package/dist/components/VProgressLinear/VProgressLinear.css +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.d.ts +10 -7
- package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js +52 -34
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VRadio/VRadio.d.ts +8 -3
- package/dist/components/VRadio/VRadio.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +52 -46
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VRating/VRating.css +2 -0
- package/dist/components/VRating/VRating.d.ts +92 -0
- package/dist/components/VRating/VRating.js +9 -0
- package/dist/components/VRating/VRating.js.map +1 -0
- package/dist/components/VRating/VRating.vue_vue_type_script_setup_true_lang.js +158 -0
- package/dist/components/VRating/VRating.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VResizable/VResizable.css +2 -0
- package/dist/components/VResizable/VResizable.d.ts +50 -0
- package/dist/components/VResizable/VResizable.js +9 -0
- package/dist/components/VResizable/VResizable.js.map +1 -0
- package/dist/components/VResizable/VResizable.vue_vue_type_script_setup_true_lang.js +225 -0
- package/dist/components/VResizable/VResizable.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VResizable/VResizablePanel.css +2 -0
- package/dist/components/VResizable/VResizablePanel.d.ts +60 -0
- package/dist/components/VResizable/VResizablePanel.js +9 -0
- package/dist/components/VResizable/VResizablePanel.js.map +1 -0
- package/dist/components/VResizable/VResizablePanel.vue_vue_type_script_setup_true_lang.js +83 -0
- package/dist/components/VResizable/VResizablePanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VResizable/context.d.ts +24 -0
- package/dist/components/VResizable/context.js +6 -0
- package/dist/components/VResizable/context.js.map +1 -0
- package/dist/components/VResizable/layout.d.ts +28 -0
- package/dist/components/VResizable/layout.js +41 -0
- package/dist/components/VResizable/layout.js.map +1 -0
- package/dist/components/VSelect/VSelect.css +2 -0
- package/dist/components/VSelect/VSelect.d.ts +193 -0
- package/dist/components/VSelect/VSelect.js +9 -0
- package/dist/components/VSelect/VSelect.js.map +1 -0
- package/dist/components/VSelect/VSelect.vue_vue_type_script_setup_true_lang.js +418 -0
- package/dist/components/VSelect/VSelect.vue_vue_type_script_setup_true_lang.js.map +1 -0
- 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.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +2 -2
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +3 -3
- package/dist/components/VSlider/VSlider.css +1 -1
- package/dist/components/VSlider/VSlider.d.ts +34 -8
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +236 -215
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VSplitButton/VSplitButton.css +2 -0
- package/dist/components/VSplitButton/VSplitButton.d.ts +114 -0
- package/dist/components/VSplitButton/VSplitButton.js +9 -0
- package/dist/components/VSplitButton/VSplitButton.js.map +1 -0
- package/dist/components/VSplitButton/VSplitButton.vue_vue_type_script_setup_true_lang.js +148 -0
- package/dist/components/VSplitButton/VSplitButton.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VStepper/VStepper.css +2 -0
- package/dist/components/VStepper/VStepper.d.ts +73 -0
- package/dist/components/VStepper/VStepper.js +9 -0
- package/dist/components/VStepper/VStepper.js.map +1 -0
- package/dist/components/VStepper/VStepper.vue_vue_type_script_setup_true_lang.js +96 -0
- package/dist/components/VStepper/VStepper.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VSwitch/VSwitch.d.ts +8 -3
- package/dist/components/VSwitch/VSwitch.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +53 -47
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTab.css +1 -1
- package/dist/components/VTabs/VTab.d.ts +1 -1
- 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/VTabs.css +1 -1
- package/dist/components/VTabs/VTabs.d.ts +13 -5
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +20 -20
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.css +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +22 -2
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +108 -100
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.d.ts +27 -7
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +187 -171
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.d.ts +2 -2
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +16 -16
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeline/VTimeline.css +2 -0
- package/dist/components/VTimeline/VTimeline.d.ts +59 -0
- package/dist/components/VTimeline/VTimeline.js +9 -0
- package/dist/components/VTimeline/VTimeline.js.map +1 -0
- package/dist/components/VTimeline/VTimeline.vue_vue_type_script_setup_true_lang.js +53 -0
- package/dist/components/VTimeline/VTimeline.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VTimeline/VTimelineItem.css +2 -0
- package/dist/components/VTimeline/VTimelineItem.d.ts +48 -0
- package/dist/components/VTimeline/VTimelineItem.js +9 -0
- package/dist/components/VTimeline/VTimelineItem.js.map +1 -0
- package/dist/components/VTimeline/VTimelineItem.vue_vue_type_script_setup_true_lang.js +70 -0
- package/dist/components/VTimeline/VTimelineItem.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VTimeline/context.d.ts +11 -0
- package/dist/components/VTimeline/context.js +6 -0
- package/dist/components/VTimeline/context.js.map +1 -0
- package/dist/components/VTimeline/datetime.d.ts +8 -0
- package/dist/components/VTimeline/datetime.js +36 -0
- package/dist/components/VTimeline/datetime.js.map +1 -0
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +8 -8
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +12 -12
- package/dist/components/VToggle/VToggle.d.ts +4 -4
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +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 -1
- package/dist/components/VTooltip/VTooltip.d.ts +8 -2
- package/dist/components/VTooltip/VTooltip.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +45 -43
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTreeView/VTreeView.css +2 -0
- package/dist/components/VTreeView/VTreeView.d.ts +112 -0
- package/dist/components/VTreeView/VTreeView.js +9 -0
- package/dist/components/VTreeView/VTreeView.js.map +1 -0
- package/dist/components/VTreeView/VTreeView.vue_vue_type_script_setup_true_lang.js +317 -0
- package/dist/components/VTreeView/VTreeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/VTypography/VTypography.d.ts +1 -1
- package/dist/components/VVirtualList/VVirtualList.css +2 -0
- package/dist/components/VVirtualList/VVirtualList.d.ts +78 -0
- package/dist/components/VVirtualList/VVirtualList.js +9 -0
- package/dist/components/VVirtualList/VVirtualList.js.map +1 -0
- package/dist/components/VVirtualList/VVirtualList.vue_vue_type_script_setup_true_lang.js +114 -0
- package/dist/components/VVirtualList/VVirtualList.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/composables/useChoice.d.ts +28 -0
- package/dist/composables/useChoice.js +28 -0
- package/dist/composables/useChoice.js.map +1 -0
- package/dist/composables/useDebouncedSearch.d.ts +17 -0
- package/dist/composables/useDebouncedSearch.js +18 -0
- package/dist/composables/useDebouncedSearch.js.map +1 -0
- package/dist/composables/useEscapeDismiss.d.ts +8 -0
- package/dist/composables/useEscapeDismiss.js +16 -0
- package/dist/composables/useEscapeDismiss.js.map +1 -0
- package/dist/composables/useFieldIds.d.ts +2 -1
- package/dist/composables/useFieldIds.js +7 -6
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.d.ts +1 -0
- package/dist/composables/useFieldPanel.js +52 -42
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileField.d.ts +1 -3
- package/dist/composables/useFileField.js +48 -50
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useHotkeyListener.d.ts +17 -0
- package/dist/composables/useHotkeyListener.js +29 -0
- package/dist/composables/useHotkeyListener.js.map +1 -0
- package/dist/composables/useIconClickHandlers.d.ts +2 -10
- package/dist/composables/useIconClickHandlers.js +1 -1
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/{components/VCombobox/infiniteScroll.d.ts → composables/useInfiniteScroll.d.ts} +6 -0
- package/dist/{components/VCombobox/infiniteScroll.js → composables/useInfiniteScroll.js} +9 -9
- package/dist/composables/useInfiniteScroll.js.map +1 -0
- package/dist/composables/useListbox.d.ts +132 -0
- package/dist/composables/useListbox.js +227 -0
- package/dist/composables/useListbox.js.map +1 -0
- package/dist/composables/useModalDialog.d.ts +28 -0
- package/dist/composables/useModalDialog.js +60 -0
- package/dist/composables/useModalDialog.js.map +1 -0
- package/dist/composables/useRootAttrs.d.ts +2 -3
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useTypeahead.d.ts +13 -0
- package/dist/composables/useTypeahead.js +23 -0
- package/dist/composables/useTypeahead.js.map +1 -0
- package/dist/composables/useVirtualList.d.ts +46 -0
- package/dist/composables/useVirtualList.js +145 -0
- package/dist/composables/useVirtualList.js.map +1 -0
- package/dist/i18n/en.js +50 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js +50 -0
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.d.ts +1 -1
- package/dist/i18n/state.js +17 -6
- package/dist/i18n/state.js.map +1 -1
- package/dist/i18n/types.d.ts +103 -2
- package/dist/icons.js +45 -37
- package/dist/index.d.ts +49 -3
- package/dist/index.js +80 -56
- package/dist/styles.css +1 -1
- package/dist/tokens/index.d.ts +43 -0
- package/dist/tokens/semantic.d.ts +42 -0
- package/dist/tokens/semantic.js +43 -1
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.d.ts +1 -0
- package/dist/tokens/themes/dark.js +1 -0
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens.json +215 -1
- package/dist/types.d.ts +57 -4
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/attrs.d.ts +9 -0
- package/dist/utils/attrs.js +14 -0
- package/dist/utils/attrs.js.map +1 -0
- package/dist/utils/date.d.ts +1 -0
- package/dist/utils/date.js +5 -4
- package/dist/utils/date.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/VCombobox/VComboboxGroup.css +0 -2
- package/dist/components/VCombobox/VComboboxGroup.d.ts +0 -17
- package/dist/components/VCombobox/VComboboxGroup.js +0 -9
- package/dist/components/VCombobox/VComboboxGroup.js.map +0 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js +0 -21
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +0 -1
- package/dist/components/VCombobox/VComboboxOption.css +0 -2
- package/dist/components/VCombobox/VComboboxOption.js +0 -9
- package/dist/components/VCombobox/VComboboxOption.js.map +0 -1
- package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +0 -1
- package/dist/components/VCombobox/VComboboxSeparator.css +0 -2
- package/dist/components/VCombobox/VComboboxSeparator.js +0 -9
- package/dist/components/VCombobox/VComboboxSeparator.js.map +0 -1
- package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +0 -1
- package/dist/components/VCombobox/infiniteScroll.js.map +0 -1
- /package/dist/components/{VCombobox/VComboboxSeparator.d.ts → VCheckbox/VCheckMark.d.ts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VInputOTP.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * Coordinate character inputs because HTML has no segmented code field. JavaScript filters\n * values, distributes paste and moves focus; pattern literals stay outside the model.\n */\n\nimport { computed, ref, useAttrs, useId, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a separator\n * shown between the boxes without ever being part of the value, as in `'GT-###'` or\n * `'###.###.###'`.\n */\n pattern?: string\n /** An icon drawn in place of every separator of the pattern. */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code can\n * still be selected and copied, which separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design system\n * dictionary.\n */\n label?: string\n /**\n * A line of help under the boxes: where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\n// @a11y\n/*\n * `aria-describedby` is a list, and the hint is appended to whatever the consumer already\n * pointed at instead of replacing it; left to the fallthrough, their value would land last and\n * take the hint out of the announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n\n// What the FORM reads goes on the hidden native input, and everything else on the group:\n// on the group a `name` submits nothing and `required` validates nothing, silently.\nconst NATIVE_ONLY = ['name', 'form', 'required']\nconst groupAttrs = computed(() =>\n Object.fromEntries(Object.entries(attrs).filter(([key]) => !NATIVE_ONLY.includes(key))),\n)\nconst nativeAttrs = computed(() =>\n Object.fromEntries(Object.entries(attrs).filter(([key]) => NATIVE_ONLY.includes(key))),\n)\n// No `useFieldIds` here: the row is named by `aria-label` and renders no `<label>`, so the\n// field id that composable generates would have nothing to point it at.\nconst hintId = useId()\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /**\n * Emitted the moment the code BECOMES complete, carrying it. Retyping a character of a\n * complete code with the same one does not emit it again.\n */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\n// @keyboard\n// Read the rendered order on demand; changing a pattern can move or replace its input cells.\nfunction inputAt(slot: number) {\n return rootEl.value?.querySelectorAll<HTMLInputElement>('.v-input-otp-input')[slot]\n}\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and one holding characters the\n // format refuses is shown filtered; but the model is not rewritten to match: writing back\n // here would feed the watcher below and the two would keep correcting each other.\n const clean = sanitize(value)\n digits.value = Array.from({ length: slotCount.value }, (_, i) => clean[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/** The first empty box, or -1 once the code is complete. Every box before it is filled. */\nconst firstEmpty = () => digits.value.findIndex((d) => !d)\n\n/** Takes a character out and moves the following ones back, so no gap is left behind. */\nfunction removeAt(slot: number) {\n digits.value.splice(slot, 1)\n digits.value.push('')\n}\n\nfunction focusBox(slot: number) {\n const el = inputAt(slot)\n el?.focus()\n // Selected, so the next keystroke REPLACES the character: a box that already has the\n // focus receives no focus event to select it.\n el?.select()\n}\n\nfunction commit() {\n const code = digits.value.join('')\n const previous = model.value\n model.value = code\n // Only when the code CHANGES into a full one: retyping a character of a complete code\n // must not submit it a second time for a consumer listening to `complete`.\n if (code.length === slotCount.value && code !== previous) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received them. It\n * returns the last box it filled, so the caller knows where to put the focus, and `null` when\n * nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n const chars = [...raw]\n // Never for ONE character: a `G` typed into the first box of `GT-###` is the code's first\n // character, not the separator, and swallowing it would make such a code impossible to type.\n if (chars.length > 1) while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core\n// Separate inputs into something that types like one field; spreading the characters is the\n// core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n const empty = firstEmpty()\n const filled = empty === -1 || slotIndex < empty\n\n // A filled box whose character was not selected receives the new one NEXT to it. What\n // was inserted is what lies just before the caret, and it is what counts.\n const previous = digits.value[slotIndex] ?? ''\n let raw = el.value\n const inserted = raw.length - previous.length\n if (previous && inserted > 0 && el.selectionStart !== null) {\n raw = raw.slice(Math.max(0, el.selectionStart - inserted), el.selectionStart)\n }\n\n // The same path serves a single keystroke and a pasted code: both are spread from this box\n // onwards; or from the first empty box, when this one lies past it.\n const lastFilled = distribute(raw, filled ? slotIndex : empty)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format. A filled box\n // loses its character and the ones after it close the gap.\n if (filled) removeAt(slotIndex)\n el.value = digits.value[slotIndex] ?? ''\n commit()\n return\n }\n // Written by hand: when the characters went to another box, this one's value is still\n // '' in the render, and the patch would leave the typed character on screen.\n el.value = digits.value[slotIndex] ?? ''\n focusBox(Math.min(lastFilled + 1, slotCount.value - 1))\n commit()\n}\n\n// @keyboard\n// Sends the focus there instead: a pointer landing on box 5 of a two-character code types into\n// box 3.\nfunction onFocus(slotIndex: number, event: FocusEvent) {\n const empty = firstEmpty()\n if (empty !== -1 && slotIndex > empty) inputAt(empty)?.focus()\n else (event.target as HTMLInputElement).select()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n const empty = firstEmpty()\n // The furthest box the focus may reach: the first empty one, or the last of a full code.\n const reachable = empty === -1 ? slotCount.value - 1 : empty\n let target: number | null = null\n switch (event.key) {\n case 'Backspace':\n // A read-only box refuses its own edits natively, but this erases ANOTHER box, which\n // nothing native guards.\n if (props.readonly || props.disabled || digits.value[slotIndex] || slotIndex === 0) return\n removeAt(slotIndex - 1)\n commit()\n target = slotIndex - 1\n break\n case 'ArrowLeft':\n if (slotIndex > 0) target = slotIndex - 1\n break\n case 'ArrowRight':\n if (slotIndex < reachable) target = slotIndex + 1\n break\n case 'Home':\n target = 0\n break\n case 'End':\n target = reachable\n break\n }\n if (target === null) return\n event.preventDefault()\n focusBox(target)\n}\n\n/*\n * `focus` goes to the first empty box rather than to the first box outright, which is where a\n * reader resuming a half-entered code expects to land.\n */\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => {\n const empty = digits.value.findIndex((d) => !d)\n const target = empty === -1 ? slotCount.value - 1 : empty\n inputAt(target)?.focus(options)\n },\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = rootEl.value?.querySelector<HTMLInputElement>('.v-input-otp-input:focus')\n ;(active ?? inputAt(0))?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-bind=\"groupAttrs\"\n class=\"v-input-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"onFocus(cell.slotIndex, $event)\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <!--\n Out of the tab order and hidden from assistive technology, the boxes being the control; a\n browser focusing it to report an invalid code is sent on to the box to fill.\n -->\n <input\n v-bind=\"nativeAttrs\"\n class=\"v-input-otp-native v-visually-hidden\"\n type=\"text\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n autocomplete=\"off\"\n :value=\"model\"\n :pattern=\"`.{${slotCount}}`\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n @focus=\"focusBox(firstEmpty() === -1 ? 0 : firstEmpty())\"\n />\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-otp-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input-otp {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n\n /* A column, so the hint sits under the boxes rather than beside them; the row itself\n is the box below. `inline-flex` keeps the component's own width, and the start\n alignment stops the hint from stretching the row to its own length. */\n display: inline-flex;\n flex-direction: column;\n align-items: start;\n gap: var(--vectis-space-1);\n }\n\n /* A code reads left to right in every language, like the HH:MM of VTimeInput: mirrored\n under `dir=\"rtl\"`, the arrows would walk against the boxes and a `GT-###` pattern would\n be drawn backwards. */\n .v-input-otp-boxes {\n direction: ltr;\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * The states follow VInput's, in VInput's order: read-only, hover, focus, invalid, disabled.\n * Read-only, focus, invalid and disabled all weigh (0,2,0), so the source order arbitrates\n * between them and read-only has to come first, or it would repaint the focus and error\n * borders grey.\n */\n .v-input-otp[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n /* The hover weighs (0,5,0) and keeps itself off a focused, invalid or disabled box\n through its own `:not()`, as VInput's does. */\n .v-input-otp:not([data-invalid]) .v-input-otp-input:hover:not(:focus, :disabled) {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /*\n * `:focus` rather than `:focus-visible`, like any text field, which shows its focus when\n * clicked into too. The transparent outline is the safety net for Windows forced colours,\n * which drop box-shadows entirely.\n */\n .v-input-otp-input:focus {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input:focus {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /*\n * A disabled row greys out through the colour tokens and never through opacity, the same\n * treatment as VInput; `text-muted` inside the box, where `text-subtle` would fall under\n * 4.5:1 against `surface-muted`, and `text-subtle` for what sits on the page.\n */\n .v-input-otp[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp[data-disabled] .v-input-otp-literal,\n .v-input-otp[data-disabled] .v-input-otp-hint {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"VInputOTP.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * Coordinate character inputs because HTML has no segmented code field. JavaScript filters\n * values, distributes paste and moves focus; pattern literals stay outside the model.\n */\n\nimport { computed, ref, useAttrs, watch } from 'vue'\nimport VField from '../VField/VField.vue'\nimport type { FieldLabelPosition } from '../VField/VField.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { partitionAttrs } from '../../utils/attrs'\nimport { isDev } from '../../utils/env'\n\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a separator\n * shown between the boxes without ever being part of the value, as in `'GT-###'` or\n * `'###.###.###'`.\n */\n pattern?: string\n /** An icon drawn in place of every separator of the pattern. */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code can\n * still be selected and copied, which separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * The label above the boxes, which names the row. Without it, the row is named by its\n * `aria-label` or by the design system dictionary.\n */\n label?: string\n /**\n * A line of help under the boxes: where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n /**\n * A message saying what is wrong with the value, shown in place of the hint so that the field\n * does not grow. It marks the field invalid, is read along with it, and is announced when it\n * appears or changes.\n */\n error?: string\n /**\n * Marks the code as required: an asterisk follows the label, and the hidden input a form reads\n * is `required`.\n */\n required?: boolean\n /** Hides the label visually. It still names the row for assistive technology. */\n hideLabel?: boolean\n /**\n * Where the label sits: above the boxes (`top`, the default), or at their start, in a column\n * of its own, moving back above when there is not room for both.\n */\n labelPosition?: FieldLabelPosition\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n error: undefined,\n required: false,\n hideLabel: false,\n labelPosition: 'top',\n})\n\nconst m = useMessages()\n\n// @a11y\n/*\n * `aria-describedby` is a list, and the hint is appended to whatever the consumer already\n * pointed at instead of replacing it; left to the fallthrough, their value would land last and\n * take the hint out of the announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n\n// What the FORM reads goes on the hidden native input, and everything else on the group:\n// on the group a `name` submits nothing and `required` validates nothing, silently.\nconst NATIVE_ONLY = ['name', 'form']\nconst split = computed(() => partitionAttrs(attrs, NATIVE_ONLY))\nconst groupAttrs = computed(() => split.value.rest)\nconst nativeAttrs = computed(() => split.value.picked)\n// @a11y\n// The row has no visible label to be named by when a consumer gives none or names it\n// themselves (VField's group mode): the dictionary names it then.\nfunction rowLabel(fieldProps: Record<string, unknown>): string | undefined {\n if (fieldProps['aria-labelledby']) return undefined\n return (fieldProps['aria-label'] as string | undefined) ?? m.value.inputOTP.label\n}\n\nconst isInvalid = computed(() => props.invalid || !!props.error)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /**\n * Emitted the moment the code BECOMES complete, carrying it. Retyping a character of a\n * complete code with the same one does not emit it again.\n */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<InputOTPFormat, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\n// @keyboard\n// Read the rendered order on demand; changing a pattern can move or replace its input cells.\nfunction inputAt(slot: number) {\n return rootEl.value?.querySelectorAll<HTMLInputElement>('.v-input-otp-input')[slot]\n}\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and one holding characters the\n // format refuses is shown filtered; but the model is not rewritten to match: writing back\n // here would feed the watcher below and the two would keep correcting each other.\n const clean = sanitize(value)\n digits.value = Array.from({ length: slotCount.value }, (_, i) => clean[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/** The first empty box, or -1 once the code is complete. Every box before it is filled. */\nconst firstEmpty = () => digits.value.findIndex((d) => !d)\n\n/** The furthest box the focus may reach: the first empty one, or the last of a full code. */\nfunction reachable() {\n const empty = firstEmpty()\n return empty === -1 ? slotCount.value - 1 : empty\n}\n\n/** Takes a character out and moves the following ones back, so no gap is left behind. */\nfunction removeAt(slot: number) {\n digits.value.splice(slot, 1)\n digits.value.push('')\n}\n\nfunction focusBox(slot: number) {\n const el = inputAt(slot)\n el?.focus()\n // Selected, so the next keystroke REPLACES the character: a box that already has the\n // focus receives no focus event to select it.\n el?.select()\n}\n\nfunction commit() {\n const code = digits.value.join('')\n const previous = model.value\n model.value = code\n // Only when the code CHANGES into a full one: retyping a character of a complete code\n // must not submit it a second time for a consumer listening to `complete`.\n if (code.length === slotCount.value && code !== previous) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received them. It\n * returns the last box it filled, so the caller knows where to put the focus, and `null` when\n * nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n const chars = [...raw]\n // Never for ONE character: a `G` typed into the first box of `GT-###` is the code's first\n // character, not the separator, and swallowing it would make such a code impossible to type.\n if (chars.length > 1) while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core\n// Separate inputs into something that types like one field; spreading the characters is the\n// core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n const empty = firstEmpty()\n const filled = empty === -1 || slotIndex < empty\n\n // A filled box whose character was not selected receives the new one NEXT to it. What\n // was inserted is what lies just before the caret, and it is what counts.\n const previous = digits.value[slotIndex] ?? ''\n let raw = el.value\n const inserted = raw.length - previous.length\n if (previous && inserted > 0 && el.selectionStart !== null) {\n raw = raw.slice(Math.max(0, el.selectionStart - inserted), el.selectionStart)\n }\n\n // The same path serves a single keystroke and a pasted code: both are spread from this box\n // onwards; or from the first empty box, when this one lies past it.\n const lastFilled = distribute(raw, filled ? slotIndex : empty)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format. A filled box\n // loses its character and the ones after it close the gap.\n if (filled) removeAt(slotIndex)\n el.value = digits.value[slotIndex] ?? ''\n commit()\n return\n }\n // Written by hand: when the characters went to another box, this one's value is still\n // '' in the render, and the patch would leave the typed character on screen.\n el.value = digits.value[slotIndex] ?? ''\n focusBox(Math.min(lastFilled + 1, slotCount.value - 1))\n commit()\n}\n\n// @keyboard\n// Sends the focus there instead: a pointer landing on box 5 of a two-character code types into\n// box 3.\nfunction onFocus(slotIndex: number, event: FocusEvent) {\n const empty = firstEmpty()\n if (empty !== -1 && slotIndex > empty) inputAt(empty)?.focus()\n else (event.target as HTMLInputElement).select()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n const last = reachable()\n let target: number | null = null\n switch (event.key) {\n case 'Backspace':\n // A read-only box refuses its own edits natively, but this erases ANOTHER box, which\n // nothing native guards.\n if (props.readonly || props.disabled || digits.value[slotIndex] || slotIndex === 0) return\n removeAt(slotIndex - 1)\n commit()\n target = slotIndex - 1\n break\n case 'ArrowLeft':\n if (slotIndex > 0) target = slotIndex - 1\n break\n case 'ArrowRight':\n if (slotIndex < last) target = slotIndex + 1\n break\n case 'Home':\n target = 0\n break\n case 'End':\n target = last\n break\n }\n if (target === null) return\n event.preventDefault()\n focusBox(target)\n}\n\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => inputAt(reachable())?.focus(options),\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = rootEl.value?.querySelector<HTMLInputElement>('.v-input-otp-input:focus')\n ;(active ?? inputAt(0))?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <VField\n v-bind=\"groupAttrs\"\n class=\"v-input-otp\"\n :label=\"label\"\n :hint=\"hint\"\n :error=\"error\"\n :required=\"required\"\n :disabled=\"disabled\"\n :hide-label=\"hideLabel\"\n :label-position=\"labelPosition\"\n group\n >\n <template #default=\"{ fieldProps }\">\n <div\n ref=\"rootEl\"\n v-bind=\"fieldProps\"\n class=\"v-input-otp-group v-control\"\n role=\"group\"\n :aria-label=\"rowLabel(fieldProps)\"\n :data-invalid=\"isInvalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"isInvalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"onFocus(cell.slotIndex, $event)\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <!--\n Out of the tab order and hidden from assistive technology, the boxes being the control; a\n browser focusing it to report an invalid code is sent on to the box to fill.\n -->\n <input\n v-bind=\"nativeAttrs\"\n class=\"v-input-otp-native v-visually-hidden\"\n type=\"text\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n autocomplete=\"off\"\n :value=\"model\"\n :pattern=\"`.{${slotCount}}`\"\n :required=\"required || undefined\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n @focus=\"focusBox(reachable())\"\n />\n </div>\n </template>\n </VField>\n</template>\n\n<style>\n@layer vectis.components {\n /* Its own width rather than the line's, so that a start label stays next to the boxes. */\n .v-input-otp.v-field {\n display: inline-flex;\n align-items: start;\n }\n\n .v-input-otp-group {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n }\n\n /* A code reads left to right in every language, like the HH:MM of VTimeInput: mirrored\n under `dir=\"rtl\"`, the arrows would walk against the boxes and a `GT-###` pattern would\n be drawn backwards. */\n .v-input-otp-boxes {\n direction: ltr;\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * The states follow VInput's, in VInput's order: read-only, hover, focus, invalid, disabled.\n * Read-only, focus, invalid and disabled all weigh (0,2,0), so the source order arbitrates\n * between them and read-only has to come first, or it would repaint the focus and error\n * borders grey.\n */\n .v-input-otp-group[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n /* The hover weighs (0,5,0) and keeps itself off a focused, invalid or disabled box\n through its own `:not()`, as VInput's does. */\n .v-input-otp-group:not([data-invalid]) .v-input-otp-input:hover:not(:focus, :disabled) {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /*\n * `:focus` rather than `:focus-visible`, like any text field, which shows its focus when\n * clicked into too. The transparent outline is the safety net for Windows forced colours,\n * which drop box-shadows entirely.\n */\n .v-input-otp-input:focus {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n .v-input-otp-group[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp-group[data-invalid] .v-input-otp-input:focus {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /*\n * A disabled row greys out through the colour tokens and never through opacity, the same\n * treatment as VInput; `text-muted` inside the box, where `text-subtle` would fall under\n * 4.5:1 against `surface-muted`, and `text-subtle` for what sits on the page.\n */\n .v-input-otp-group[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp-group[data-disabled] .v-input-otp-literal {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp-group[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp-group[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
|
|
@@ -2,20 +2,18 @@ import { isDev as e } from "../../utils/env.js";
|
|
|
2
2
|
import { useMessages as t } from "../../i18n/state.js";
|
|
3
3
|
import { iconProps as n } from "../VIcon/iconProps.js";
|
|
4
4
|
import r from "../VIcon/VIcon.js";
|
|
5
|
-
import i from "../
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { Fragment as s, computed as c, createBlock as l, createCommentVNode as u, createElementBlock as d, createElementVNode as f, createTextVNode as p, defineComponent as m, mergeModels as h, mergeProps as g, openBlock as _, ref as v, renderList as y, toDisplayString as b, unref as x, useAttrs as S, useId as C, useModel as w, watch as T, withCtx as ee } from "vue";
|
|
5
|
+
import i from "../VField/VField.js";
|
|
6
|
+
import { partitionAttrs as a } from "../../utils/attrs.js";
|
|
7
|
+
import { Fragment as o, computed as s, createBlock as c, createElementBlock as l, createElementVNode as u, createTextVNode as d, defineComponent as f, mergeModels as p, mergeProps as m, openBlock as h, ref as g, renderList as _, toDisplayString as v, unref as y, useAttrs as b, useModel as x, watch as S, withCtx as C } from "vue";
|
|
9
8
|
//#region src/components/VInputOTP/VInputOTP.vue?vue&type=script&setup=true&lang.ts
|
|
10
|
-
var
|
|
9
|
+
var w = [
|
|
11
10
|
"aria-label",
|
|
12
|
-
"aria-describedby",
|
|
13
11
|
"data-invalid",
|
|
14
12
|
"data-size",
|
|
15
13
|
"data-compact",
|
|
16
14
|
"data-disabled",
|
|
17
15
|
"data-readonly"
|
|
18
|
-
],
|
|
16
|
+
], T = { class: "v-input-otp-boxes" }, E = [
|
|
19
17
|
"inputmode",
|
|
20
18
|
"autocomplete",
|
|
21
19
|
"value",
|
|
@@ -26,19 +24,20 @@ var te = [
|
|
|
26
24
|
"onInput",
|
|
27
25
|
"onKeydown",
|
|
28
26
|
"onFocus"
|
|
29
|
-
],
|
|
27
|
+
], D = {
|
|
30
28
|
key: 1,
|
|
31
29
|
class: "v-input-otp-literal",
|
|
32
30
|
"aria-hidden": "true"
|
|
33
|
-
},
|
|
31
|
+
}, O = [
|
|
34
32
|
"value",
|
|
35
33
|
"pattern",
|
|
34
|
+
"required",
|
|
36
35
|
"disabled",
|
|
37
36
|
"readonly"
|
|
38
|
-
],
|
|
37
|
+
], k = /*@__PURE__*/ f({
|
|
39
38
|
inheritAttrs: !1,
|
|
40
39
|
__name: "VInputOTP",
|
|
41
|
-
props: /*@__PURE__*/
|
|
40
|
+
props: /*@__PURE__*/ p({
|
|
42
41
|
length: { default: 6 },
|
|
43
42
|
format: { default: "numeric" },
|
|
44
43
|
pattern: { default: void 0 },
|
|
@@ -61,21 +60,31 @@ var te = [
|
|
|
61
60
|
default: !1
|
|
62
61
|
},
|
|
63
62
|
label: { default: void 0 },
|
|
64
|
-
hint: { default: void 0 }
|
|
63
|
+
hint: { default: void 0 },
|
|
64
|
+
error: { default: void 0 },
|
|
65
|
+
required: {
|
|
66
|
+
type: Boolean,
|
|
67
|
+
default: !1
|
|
68
|
+
},
|
|
69
|
+
hideLabel: {
|
|
70
|
+
type: Boolean,
|
|
71
|
+
default: !1
|
|
72
|
+
},
|
|
73
|
+
labelPosition: { default: "top" }
|
|
65
74
|
}, {
|
|
66
75
|
modelValue: { default: "" },
|
|
67
76
|
modelModifiers: {}
|
|
68
77
|
}),
|
|
69
|
-
emits: /*@__PURE__*/
|
|
70
|
-
setup(
|
|
71
|
-
let j =
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
78
|
+
emits: /*@__PURE__*/ p(["complete"], ["update:modelValue"]),
|
|
79
|
+
setup(f, { expose: p, emit: k }) {
|
|
80
|
+
let A = f, j = t(), M = b(), N = ["name", "form"], P = s(() => a(M, N)), F = s(() => P.value.rest), I = s(() => P.value.picked);
|
|
81
|
+
function L(e) {
|
|
82
|
+
if (!e["aria-labelledby"]) return e["aria-label"] ?? j.value.inputOTP.label;
|
|
83
|
+
}
|
|
84
|
+
let R = s(() => A.invalid || !!A.error), z = x(f, "modelValue"), B = k, V = s(() => {
|
|
85
|
+
if (A.pattern?.includes("#")) {
|
|
77
86
|
let e = 0;
|
|
78
|
-
return [...
|
|
87
|
+
return [...A.pattern].map((t) => t === "#" ? {
|
|
79
88
|
type: "slot",
|
|
80
89
|
slotIndex: e++
|
|
81
90
|
} : {
|
|
@@ -83,45 +92,49 @@ var te = [
|
|
|
83
92
|
char: t
|
|
84
93
|
});
|
|
85
94
|
}
|
|
86
|
-
return Array.from({ length:
|
|
95
|
+
return Array.from({ length: A.length }, (e, t) => ({
|
|
87
96
|
type: "slot",
|
|
88
97
|
slotIndex: t
|
|
89
98
|
}));
|
|
90
|
-
}),
|
|
91
|
-
e &&
|
|
92
|
-
let
|
|
99
|
+
}), H = s(() => V.value.filter((e) => e.type === "slot").length);
|
|
100
|
+
e && A.pattern && !A.pattern.includes("#") && console.warn("[VInputOTP] pattern without '#' — falling back to `length`.");
|
|
101
|
+
let U = {
|
|
93
102
|
numeric: /[^0-9]/g,
|
|
94
103
|
alpha: /[^A-Z]/g,
|
|
95
104
|
alphanumeric: /[^A-Z0-9]/g
|
|
96
105
|
};
|
|
97
|
-
function
|
|
98
|
-
return (
|
|
106
|
+
function W(e) {
|
|
107
|
+
return (A.format === "numeric" ? e : e.toUpperCase()).replace(U[A.format], "");
|
|
99
108
|
}
|
|
100
|
-
let
|
|
101
|
-
function
|
|
102
|
-
return
|
|
109
|
+
let G = g(null), K = g([]);
|
|
110
|
+
function q(e) {
|
|
111
|
+
return G.value?.querySelectorAll(".v-input-otp-input")[e];
|
|
103
112
|
}
|
|
104
|
-
function
|
|
105
|
-
let t =
|
|
106
|
-
|
|
113
|
+
function J(e) {
|
|
114
|
+
let t = W(e);
|
|
115
|
+
K.value = Array.from({ length: H.value }, (e, n) => t[n] ?? "");
|
|
107
116
|
}
|
|
108
|
-
|
|
109
|
-
(e !==
|
|
117
|
+
J(z.value), S([z, H], ([e, t]) => {
|
|
118
|
+
(e !== K.value.join("") || K.value.length !== t) && J(e);
|
|
110
119
|
});
|
|
111
|
-
let
|
|
120
|
+
let Y = () => K.value.findIndex((e) => !e);
|
|
121
|
+
function X() {
|
|
122
|
+
let e = Y();
|
|
123
|
+
return e === -1 ? H.value - 1 : e;
|
|
124
|
+
}
|
|
112
125
|
function Z(e) {
|
|
113
|
-
|
|
126
|
+
K.value.splice(e, 1), K.value.push("");
|
|
114
127
|
}
|
|
115
128
|
function Q(e) {
|
|
116
|
-
let t =
|
|
129
|
+
let t = q(e);
|
|
117
130
|
t?.focus(), t?.select();
|
|
118
131
|
}
|
|
119
132
|
function $() {
|
|
120
|
-
let e =
|
|
121
|
-
|
|
133
|
+
let e = K.value.join(""), t = z.value;
|
|
134
|
+
z.value = e, e.length === H.value && e !== t && B("complete", e);
|
|
122
135
|
}
|
|
123
|
-
function
|
|
124
|
-
let n =
|
|
136
|
+
function ee(e, t) {
|
|
137
|
+
let n = V.value, r = n.findIndex((e) => e.type === "slot" && e.slotIndex === t), i = [...e];
|
|
125
138
|
if (i.length > 1) for (; r > 0 && n[r - 1]?.type === "literal";) r--;
|
|
126
139
|
let a = 0, o = null;
|
|
127
140
|
for (let e of n.slice(r)) {
|
|
@@ -131,115 +144,120 @@ var te = [
|
|
|
131
144
|
continue;
|
|
132
145
|
}
|
|
133
146
|
let t = "";
|
|
134
|
-
for (; a < i.length && !t;) t =
|
|
147
|
+
for (; a < i.length && !t;) t = W(i[a] ?? ""), a++;
|
|
135
148
|
if (!t) break;
|
|
136
|
-
|
|
149
|
+
K.value[e.slotIndex] = t, o = e.slotIndex;
|
|
137
150
|
}
|
|
138
151
|
return o;
|
|
139
152
|
}
|
|
140
|
-
function
|
|
141
|
-
let n = t.target, r =
|
|
153
|
+
function te(e, t) {
|
|
154
|
+
let n = t.target, r = Y(), i = r === -1 || e < r, a = K.value[e] ?? "", o = n.value, s = o.length - a.length;
|
|
142
155
|
a && s > 0 && n.selectionStart !== null && (o = o.slice(Math.max(0, n.selectionStart - s), n.selectionStart));
|
|
143
|
-
let c =
|
|
156
|
+
let c = ee(o, i ? e : r);
|
|
144
157
|
if (c === null) {
|
|
145
|
-
i && Z(e), n.value =
|
|
158
|
+
i && Z(e), n.value = K.value[e] ?? "", $();
|
|
146
159
|
return;
|
|
147
160
|
}
|
|
148
|
-
n.value =
|
|
161
|
+
n.value = K.value[e] ?? "", Q(Math.min(c + 1, H.value - 1)), $();
|
|
149
162
|
}
|
|
150
|
-
function
|
|
151
|
-
let n =
|
|
152
|
-
n !== -1 && e > n ?
|
|
163
|
+
function ne(e, t) {
|
|
164
|
+
let n = Y();
|
|
165
|
+
n !== -1 && e > n ? q(n)?.focus() : t.target.select();
|
|
153
166
|
}
|
|
154
|
-
function
|
|
155
|
-
let n = X(), r =
|
|
167
|
+
function re(e, t) {
|
|
168
|
+
let n = X(), r = null;
|
|
156
169
|
switch (t.key) {
|
|
157
170
|
case "Backspace":
|
|
158
|
-
if (
|
|
159
|
-
Z(e - 1), $(),
|
|
171
|
+
if (A.readonly || A.disabled || K.value[e] || e === 0) return;
|
|
172
|
+
Z(e - 1), $(), r = e - 1;
|
|
160
173
|
break;
|
|
161
174
|
case "ArrowLeft":
|
|
162
|
-
e > 0 && (
|
|
175
|
+
e > 0 && (r = e - 1);
|
|
163
176
|
break;
|
|
164
177
|
case "ArrowRight":
|
|
165
|
-
e <
|
|
178
|
+
e < n && (r = e + 1);
|
|
166
179
|
break;
|
|
167
180
|
case "Home":
|
|
168
|
-
|
|
181
|
+
r = 0;
|
|
169
182
|
break;
|
|
170
183
|
case "End":
|
|
171
|
-
|
|
184
|
+
r = n;
|
|
172
185
|
break;
|
|
173
186
|
}
|
|
174
|
-
|
|
187
|
+
r !== null && (t.preventDefault(), Q(r));
|
|
175
188
|
}
|
|
176
|
-
return
|
|
177
|
-
focus: (e) =>
|
|
178
|
-
let t = q.value.findIndex((e) => !e);
|
|
179
|
-
J(t === -1 ? U.value - 1 : t)?.focus(e);
|
|
180
|
-
},
|
|
189
|
+
return p({
|
|
190
|
+
focus: (e) => q(X())?.focus(e),
|
|
181
191
|
select: () => {
|
|
182
|
-
(
|
|
192
|
+
(G.value?.querySelector(".v-input-otp-input:focus") ?? q(0))?.select();
|
|
183
193
|
},
|
|
184
|
-
el:
|
|
185
|
-
}), (e, t) => (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
el: G
|
|
195
|
+
}), (e, t) => (h(), c(i, m(F.value, {
|
|
196
|
+
class: "v-input-otp",
|
|
197
|
+
label: f.label,
|
|
198
|
+
hint: f.hint,
|
|
199
|
+
error: f.error,
|
|
200
|
+
required: f.required,
|
|
201
|
+
disabled: f.disabled,
|
|
202
|
+
"hide-label": f.hideLabel,
|
|
203
|
+
"label-position": f.labelPosition,
|
|
204
|
+
group: ""
|
|
205
|
+
}), {
|
|
206
|
+
default: C(({ fieldProps: e }) => [u("div", m({
|
|
207
|
+
ref_key: "rootEl",
|
|
208
|
+
ref: G
|
|
209
|
+
}, e, {
|
|
210
|
+
class: "v-input-otp-group v-control",
|
|
211
|
+
role: "group",
|
|
212
|
+
"aria-label": L(e),
|
|
213
|
+
"data-invalid": R.value ? "" : void 0,
|
|
214
|
+
"data-size": f.size,
|
|
215
|
+
"data-compact": f.compact ? "" : void 0,
|
|
216
|
+
"data-disabled": f.disabled ? "" : void 0,
|
|
217
|
+
"data-readonly": f.readonly ? "" : void 0
|
|
218
|
+
}), [u("div", T, [(h(!0), l(o, null, _(V.value, (e, t) => (h(), l(o, { key: t }, [e.type === "slot" ? (h(), l("input", {
|
|
200
219
|
key: 0,
|
|
201
220
|
type: "text",
|
|
202
221
|
class: "v-input-otp-input",
|
|
203
|
-
inputmode:
|
|
222
|
+
inputmode: f.format === "numeric" ? "numeric" : "text",
|
|
204
223
|
autocomplete: e.slotIndex === 0 ? "one-time-code" : "off",
|
|
205
|
-
value:
|
|
206
|
-
disabled:
|
|
207
|
-
readonly:
|
|
208
|
-
"aria-label":
|
|
209
|
-
"aria-invalid":
|
|
210
|
-
onInput: (t) =>
|
|
211
|
-
onKeydown: (t) =>
|
|
212
|
-
onFocus: (t) =>
|
|
213
|
-
}, null, 40,
|
|
224
|
+
value: K.value[e.slotIndex],
|
|
225
|
+
disabled: f.disabled,
|
|
226
|
+
readonly: f.readonly || void 0,
|
|
227
|
+
"aria-label": y(j).inputOTP.slot(e.slotIndex + 1, H.value),
|
|
228
|
+
"aria-invalid": R.value || void 0,
|
|
229
|
+
onInput: (t) => te(e.slotIndex, t),
|
|
230
|
+
onKeydown: (t) => re(e.slotIndex, t),
|
|
231
|
+
onFocus: (t) => ne(e.slotIndex, t)
|
|
232
|
+
}, null, 40, E)) : (h(), l("span", D, [f.separatorIcon ? (h(), c(r, m({
|
|
214
233
|
key: 0,
|
|
215
234
|
ref_for: !0
|
|
216
|
-
},
|
|
217
|
-
f("input", g(L.value, {
|
|
235
|
+
}, y(n)(f.separatorIcon)), null, 16)) : (h(), l(o, { key: 1 }, [d(v(e.char), 1)], 64))]))], 64))), 128))]), u("input", m(I.value, {
|
|
218
236
|
class: "v-input-otp-native v-visually-hidden",
|
|
219
237
|
type: "text",
|
|
220
238
|
tabindex: "-1",
|
|
221
239
|
"aria-hidden": "true",
|
|
222
240
|
autocomplete: "off",
|
|
223
|
-
value:
|
|
224
|
-
pattern: `.{${
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
]
|
|
241
|
+
value: z.value,
|
|
242
|
+
pattern: `.{${H.value}}`,
|
|
243
|
+
required: f.required || void 0,
|
|
244
|
+
disabled: f.disabled,
|
|
245
|
+
readonly: f.readonly || void 0,
|
|
246
|
+
onFocus: t[0] ||= (e) => Q(X())
|
|
247
|
+
}), null, 16, O)], 16, w)]),
|
|
248
|
+
_: 1
|
|
249
|
+
}, 16, [
|
|
250
|
+
"label",
|
|
251
|
+
"hint",
|
|
252
|
+
"error",
|
|
253
|
+
"required",
|
|
254
|
+
"disabled",
|
|
255
|
+
"hide-label",
|
|
256
|
+
"label-position"
|
|
257
|
+
]));
|
|
240
258
|
}
|
|
241
259
|
});
|
|
242
260
|
//#endregion
|
|
243
|
-
export {
|
|
261
|
+
export { k as default };
|
|
244
262
|
|
|
245
263
|
//# sourceMappingURL=VInputOTP.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VInputOTP.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * Coordinate character inputs because HTML has no segmented code field. JavaScript filters\n * values, distributes paste and moves focus; pattern literals stay outside the model.\n */\n\nimport { computed, ref, useAttrs, useId, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a separator\n * shown between the boxes without ever being part of the value, as in `'GT-###'` or\n * `'###.###.###'`.\n */\n pattern?: string\n /** An icon drawn in place of every separator of the pattern. */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code can\n * still be selected and copied, which separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design system\n * dictionary.\n */\n label?: string\n /**\n * A line of help under the boxes: where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\n// @a11y\n/*\n * `aria-describedby` is a list, and the hint is appended to whatever the consumer already\n * pointed at instead of replacing it; left to the fallthrough, their value would land last and\n * take the hint out of the announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n\n// What the FORM reads goes on the hidden native input, and everything else on the group:\n// on the group a `name` submits nothing and `required` validates nothing, silently.\nconst NATIVE_ONLY = ['name', 'form', 'required']\nconst groupAttrs = computed(() =>\n Object.fromEntries(Object.entries(attrs).filter(([key]) => !NATIVE_ONLY.includes(key))),\n)\nconst nativeAttrs = computed(() =>\n Object.fromEntries(Object.entries(attrs).filter(([key]) => NATIVE_ONLY.includes(key))),\n)\n// No `useFieldIds` here: the row is named by `aria-label` and renders no `<label>`, so the\n// field id that composable generates would have nothing to point it at.\nconst hintId = useId()\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /**\n * Emitted the moment the code BECOMES complete, carrying it. Retyping a character of a\n * complete code with the same one does not emit it again.\n */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\n// @keyboard\n// Read the rendered order on demand; changing a pattern can move or replace its input cells.\nfunction inputAt(slot: number) {\n return rootEl.value?.querySelectorAll<HTMLInputElement>('.v-input-otp-input')[slot]\n}\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and one holding characters the\n // format refuses is shown filtered; but the model is not rewritten to match: writing back\n // here would feed the watcher below and the two would keep correcting each other.\n const clean = sanitize(value)\n digits.value = Array.from({ length: slotCount.value }, (_, i) => clean[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/** The first empty box, or -1 once the code is complete. Every box before it is filled. */\nconst firstEmpty = () => digits.value.findIndex((d) => !d)\n\n/** Takes a character out and moves the following ones back, so no gap is left behind. */\nfunction removeAt(slot: number) {\n digits.value.splice(slot, 1)\n digits.value.push('')\n}\n\nfunction focusBox(slot: number) {\n const el = inputAt(slot)\n el?.focus()\n // Selected, so the next keystroke REPLACES the character: a box that already has the\n // focus receives no focus event to select it.\n el?.select()\n}\n\nfunction commit() {\n const code = digits.value.join('')\n const previous = model.value\n model.value = code\n // Only when the code CHANGES into a full one: retyping a character of a complete code\n // must not submit it a second time for a consumer listening to `complete`.\n if (code.length === slotCount.value && code !== previous) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received them. It\n * returns the last box it filled, so the caller knows where to put the focus, and `null` when\n * nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n const chars = [...raw]\n // Never for ONE character: a `G` typed into the first box of `GT-###` is the code's first\n // character, not the separator, and swallowing it would make such a code impossible to type.\n if (chars.length > 1) while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core\n// Separate inputs into something that types like one field; spreading the characters is the\n// core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n const empty = firstEmpty()\n const filled = empty === -1 || slotIndex < empty\n\n // A filled box whose character was not selected receives the new one NEXT to it. What\n // was inserted is what lies just before the caret, and it is what counts.\n const previous = digits.value[slotIndex] ?? ''\n let raw = el.value\n const inserted = raw.length - previous.length\n if (previous && inserted > 0 && el.selectionStart !== null) {\n raw = raw.slice(Math.max(0, el.selectionStart - inserted), el.selectionStart)\n }\n\n // The same path serves a single keystroke and a pasted code: both are spread from this box\n // onwards; or from the first empty box, when this one lies past it.\n const lastFilled = distribute(raw, filled ? slotIndex : empty)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format. A filled box\n // loses its character and the ones after it close the gap.\n if (filled) removeAt(slotIndex)\n el.value = digits.value[slotIndex] ?? ''\n commit()\n return\n }\n // Written by hand: when the characters went to another box, this one's value is still\n // '' in the render, and the patch would leave the typed character on screen.\n el.value = digits.value[slotIndex] ?? ''\n focusBox(Math.min(lastFilled + 1, slotCount.value - 1))\n commit()\n}\n\n// @keyboard\n// Sends the focus there instead: a pointer landing on box 5 of a two-character code types into\n// box 3.\nfunction onFocus(slotIndex: number, event: FocusEvent) {\n const empty = firstEmpty()\n if (empty !== -1 && slotIndex > empty) inputAt(empty)?.focus()\n else (event.target as HTMLInputElement).select()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n const empty = firstEmpty()\n // The furthest box the focus may reach: the first empty one, or the last of a full code.\n const reachable = empty === -1 ? slotCount.value - 1 : empty\n let target: number | null = null\n switch (event.key) {\n case 'Backspace':\n // A read-only box refuses its own edits natively, but this erases ANOTHER box, which\n // nothing native guards.\n if (props.readonly || props.disabled || digits.value[slotIndex] || slotIndex === 0) return\n removeAt(slotIndex - 1)\n commit()\n target = slotIndex - 1\n break\n case 'ArrowLeft':\n if (slotIndex > 0) target = slotIndex - 1\n break\n case 'ArrowRight':\n if (slotIndex < reachable) target = slotIndex + 1\n break\n case 'Home':\n target = 0\n break\n case 'End':\n target = reachable\n break\n }\n if (target === null) return\n event.preventDefault()\n focusBox(target)\n}\n\n/*\n * `focus` goes to the first empty box rather than to the first box outright, which is where a\n * reader resuming a half-entered code expects to land.\n */\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => {\n const empty = digits.value.findIndex((d) => !d)\n const target = empty === -1 ? slotCount.value - 1 : empty\n inputAt(target)?.focus(options)\n },\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = rootEl.value?.querySelector<HTMLInputElement>('.v-input-otp-input:focus')\n ;(active ?? inputAt(0))?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-bind=\"groupAttrs\"\n class=\"v-input-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"onFocus(cell.slotIndex, $event)\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <!--\n Out of the tab order and hidden from assistive technology, the boxes being the control; a\n browser focusing it to report an invalid code is sent on to the box to fill.\n -->\n <input\n v-bind=\"nativeAttrs\"\n class=\"v-input-otp-native v-visually-hidden\"\n type=\"text\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n autocomplete=\"off\"\n :value=\"model\"\n :pattern=\"`.{${slotCount}}`\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n @focus=\"focusBox(firstEmpty() === -1 ? 0 : firstEmpty())\"\n />\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-otp-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input-otp {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n\n /* A column, so the hint sits under the boxes rather than beside them; the row itself\n is the box below. `inline-flex` keeps the component's own width, and the start\n alignment stops the hint from stretching the row to its own length. */\n display: inline-flex;\n flex-direction: column;\n align-items: start;\n gap: var(--vectis-space-1);\n }\n\n /* A code reads left to right in every language, like the HH:MM of VTimeInput: mirrored\n under `dir=\"rtl\"`, the arrows would walk against the boxes and a `GT-###` pattern would\n be drawn backwards. */\n .v-input-otp-boxes {\n direction: ltr;\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * The states follow VInput's, in VInput's order: read-only, hover, focus, invalid, disabled.\n * Read-only, focus, invalid and disabled all weigh (0,2,0), so the source order arbitrates\n * between them and read-only has to come first, or it would repaint the focus and error\n * borders grey.\n */\n .v-input-otp[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n /* The hover weighs (0,5,0) and keeps itself off a focused, invalid or disabled box\n through its own `:not()`, as VInput's does. */\n .v-input-otp:not([data-invalid]) .v-input-otp-input:hover:not(:focus, :disabled) {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /*\n * `:focus` rather than `:focus-visible`, like any text field, which shows its focus when\n * clicked into too. The transparent outline is the safety net for Windows forced colours,\n * which drop box-shadows entirely.\n */\n .v-input-otp-input:focus {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input:focus {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /*\n * A disabled row greys out through the colour tokens and never through opacity, the same\n * treatment as VInput; `text-muted` inside the box, where `text-subtle` would fall under\n * 4.5:1 against `surface-muted`, and `text-subtle` for what sits on the page.\n */\n .v-input-otp[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp[data-disabled] .v-input-otp-literal,\n .v-input-otp[data-disabled] .v-input-otp-hint {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VInputOTP.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * Coordinate character inputs because HTML has no segmented code field. JavaScript filters\n * values, distributes paste and moves focus; pattern literals stay outside the model.\n */\n\nimport { computed, ref, useAttrs, watch } from 'vue'\nimport VField from '../VField/VField.vue'\nimport type { FieldLabelPosition } from '../VField/VField.vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { partitionAttrs } from '../../utils/attrs'\nimport { isDev } from '../../utils/env'\n\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a separator\n * shown between the boxes without ever being part of the value, as in `'GT-###'` or\n * `'###.###.###'`.\n */\n pattern?: string\n /** An icon drawn in place of every separator of the pattern. */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code can\n * still be selected and copied, which separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * The label above the boxes, which names the row. Without it, the row is named by its\n * `aria-label` or by the design system dictionary.\n */\n label?: string\n /**\n * A line of help under the boxes: where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n /**\n * A message saying what is wrong with the value, shown in place of the hint so that the field\n * does not grow. It marks the field invalid, is read along with it, and is announced when it\n * appears or changes.\n */\n error?: string\n /**\n * Marks the code as required: an asterisk follows the label, and the hidden input a form reads\n * is `required`.\n */\n required?: boolean\n /** Hides the label visually. It still names the row for assistive technology. */\n hideLabel?: boolean\n /**\n * Where the label sits: above the boxes (`top`, the default), or at their start, in a column\n * of its own, moving back above when there is not room for both.\n */\n labelPosition?: FieldLabelPosition\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n error: undefined,\n required: false,\n hideLabel: false,\n labelPosition: 'top',\n})\n\nconst m = useMessages()\n\n// @a11y\n/*\n * `aria-describedby` is a list, and the hint is appended to whatever the consumer already\n * pointed at instead of replacing it; left to the fallthrough, their value would land last and\n * take the hint out of the announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n\n// What the FORM reads goes on the hidden native input, and everything else on the group:\n// on the group a `name` submits nothing and `required` validates nothing, silently.\nconst NATIVE_ONLY = ['name', 'form']\nconst split = computed(() => partitionAttrs(attrs, NATIVE_ONLY))\nconst groupAttrs = computed(() => split.value.rest)\nconst nativeAttrs = computed(() => split.value.picked)\n// @a11y\n// The row has no visible label to be named by when a consumer gives none or names it\n// themselves (VField's group mode): the dictionary names it then.\nfunction rowLabel(fieldProps: Record<string, unknown>): string | undefined {\n if (fieldProps['aria-labelledby']) return undefined\n return (fieldProps['aria-label'] as string | undefined) ?? m.value.inputOTP.label\n}\n\nconst isInvalid = computed(() => props.invalid || !!props.error)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /**\n * Emitted the moment the code BECOMES complete, carrying it. Retyping a character of a\n * complete code with the same one does not emit it again.\n */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<InputOTPFormat, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\n// @keyboard\n// Read the rendered order on demand; changing a pattern can move or replace its input cells.\nfunction inputAt(slot: number) {\n return rootEl.value?.querySelectorAll<HTMLInputElement>('.v-input-otp-input')[slot]\n}\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and one holding characters the\n // format refuses is shown filtered; but the model is not rewritten to match: writing back\n // here would feed the watcher below and the two would keep correcting each other.\n const clean = sanitize(value)\n digits.value = Array.from({ length: slotCount.value }, (_, i) => clean[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/** The first empty box, or -1 once the code is complete. Every box before it is filled. */\nconst firstEmpty = () => digits.value.findIndex((d) => !d)\n\n/** The furthest box the focus may reach: the first empty one, or the last of a full code. */\nfunction reachable() {\n const empty = firstEmpty()\n return empty === -1 ? slotCount.value - 1 : empty\n}\n\n/** Takes a character out and moves the following ones back, so no gap is left behind. */\nfunction removeAt(slot: number) {\n digits.value.splice(slot, 1)\n digits.value.push('')\n}\n\nfunction focusBox(slot: number) {\n const el = inputAt(slot)\n el?.focus()\n // Selected, so the next keystroke REPLACES the character: a box that already has the\n // focus receives no focus event to select it.\n el?.select()\n}\n\nfunction commit() {\n const code = digits.value.join('')\n const previous = model.value\n model.value = code\n // Only when the code CHANGES into a full one: retyping a character of a complete code\n // must not submit it a second time for a consumer listening to `complete`.\n if (code.length === slotCount.value && code !== previous) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received them. It\n * returns the last box it filled, so the caller knows where to put the focus, and `null` when\n * nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n const chars = [...raw]\n // Never for ONE character: a `G` typed into the first box of `GT-###` is the code's first\n // character, not the separator, and swallowing it would make such a code impossible to type.\n if (chars.length > 1) while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core\n// Separate inputs into something that types like one field; spreading the characters is the\n// core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n const empty = firstEmpty()\n const filled = empty === -1 || slotIndex < empty\n\n // A filled box whose character was not selected receives the new one NEXT to it. What\n // was inserted is what lies just before the caret, and it is what counts.\n const previous = digits.value[slotIndex] ?? ''\n let raw = el.value\n const inserted = raw.length - previous.length\n if (previous && inserted > 0 && el.selectionStart !== null) {\n raw = raw.slice(Math.max(0, el.selectionStart - inserted), el.selectionStart)\n }\n\n // The same path serves a single keystroke and a pasted code: both are spread from this box\n // onwards; or from the first empty box, when this one lies past it.\n const lastFilled = distribute(raw, filled ? slotIndex : empty)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format. A filled box\n // loses its character and the ones after it close the gap.\n if (filled) removeAt(slotIndex)\n el.value = digits.value[slotIndex] ?? ''\n commit()\n return\n }\n // Written by hand: when the characters went to another box, this one's value is still\n // '' in the render, and the patch would leave the typed character on screen.\n el.value = digits.value[slotIndex] ?? ''\n focusBox(Math.min(lastFilled + 1, slotCount.value - 1))\n commit()\n}\n\n// @keyboard\n// Sends the focus there instead: a pointer landing on box 5 of a two-character code types into\n// box 3.\nfunction onFocus(slotIndex: number, event: FocusEvent) {\n const empty = firstEmpty()\n if (empty !== -1 && slotIndex > empty) inputAt(empty)?.focus()\n else (event.target as HTMLInputElement).select()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n const last = reachable()\n let target: number | null = null\n switch (event.key) {\n case 'Backspace':\n // A read-only box refuses its own edits natively, but this erases ANOTHER box, which\n // nothing native guards.\n if (props.readonly || props.disabled || digits.value[slotIndex] || slotIndex === 0) return\n removeAt(slotIndex - 1)\n commit()\n target = slotIndex - 1\n break\n case 'ArrowLeft':\n if (slotIndex > 0) target = slotIndex - 1\n break\n case 'ArrowRight':\n if (slotIndex < last) target = slotIndex + 1\n break\n case 'Home':\n target = 0\n break\n case 'End':\n target = last\n break\n }\n if (target === null) return\n event.preventDefault()\n focusBox(target)\n}\n\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => inputAt(reachable())?.focus(options),\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = rootEl.value?.querySelector<HTMLInputElement>('.v-input-otp-input:focus')\n ;(active ?? inputAt(0))?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <VField\n v-bind=\"groupAttrs\"\n class=\"v-input-otp\"\n :label=\"label\"\n :hint=\"hint\"\n :error=\"error\"\n :required=\"required\"\n :disabled=\"disabled\"\n :hide-label=\"hideLabel\"\n :label-position=\"labelPosition\"\n group\n >\n <template #default=\"{ fieldProps }\">\n <div\n ref=\"rootEl\"\n v-bind=\"fieldProps\"\n class=\"v-input-otp-group v-control\"\n role=\"group\"\n :aria-label=\"rowLabel(fieldProps)\"\n :data-invalid=\"isInvalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"isInvalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"onFocus(cell.slotIndex, $event)\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <!--\n Out of the tab order and hidden from assistive technology, the boxes being the control; a\n browser focusing it to report an invalid code is sent on to the box to fill.\n -->\n <input\n v-bind=\"nativeAttrs\"\n class=\"v-input-otp-native v-visually-hidden\"\n type=\"text\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n autocomplete=\"off\"\n :value=\"model\"\n :pattern=\"`.{${slotCount}}`\"\n :required=\"required || undefined\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n @focus=\"focusBox(reachable())\"\n />\n </div>\n </template>\n </VField>\n</template>\n\n<style>\n@layer vectis.components {\n /* Its own width rather than the line's, so that a start label stays next to the boxes. */\n .v-input-otp.v-field {\n display: inline-flex;\n align-items: start;\n }\n\n .v-input-otp-group {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n }\n\n /* A code reads left to right in every language, like the HH:MM of VTimeInput: mirrored\n under `dir=\"rtl\"`, the arrows would walk against the boxes and a `GT-###` pattern would\n be drawn backwards. */\n .v-input-otp-boxes {\n direction: ltr;\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * The states follow VInput's, in VInput's order: read-only, hover, focus, invalid, disabled.\n * Read-only, focus, invalid and disabled all weigh (0,2,0), so the source order arbitrates\n * between them and read-only has to come first, or it would repaint the focus and error\n * borders grey.\n */\n .v-input-otp-group[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n border-color: var(--vectis-color-border);\n }\n\n /* The hover weighs (0,5,0) and keeps itself off a focused, invalid or disabled box\n through its own `:not()`, as VInput's does. */\n .v-input-otp-group:not([data-invalid]) .v-input-otp-input:hover:not(:focus, :disabled) {\n border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /*\n * `:focus` rather than `:focus-visible`, like any text field, which shows its focus when\n * clicked into too. The transparent outline is the safety net for Windows forced colours,\n * which drop box-shadows entirely.\n */\n .v-input-otp-input:focus {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n .v-input-otp-group[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp-group[data-invalid] .v-input-otp-input:focus {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /*\n * A disabled row greys out through the colour tokens and never through opacity, the same\n * treatment as VInput; `text-muted` inside the box, where `text-subtle` would fall under\n * 4.5:1 against `surface-muted`, and `text-subtle` for what sits on the page.\n */\n .v-input-otp-group[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp-group[data-disabled] .v-input-otp-literal {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp-group[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp-group[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
@layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
|
|
2
|
+
@layer vectis.components{.v-link{--vectis-icon-size:1em;border-radius:var(--vectis-radius-xs);color:var(--tone-text-tinted);text-decoration-line:none;-webkit-text-decoration-color:color-mix(in oklab, currentcolor 50%, transparent);text-decoration-color:color-mix(in oklab, currentcolor 50%, transparent);cursor:pointer;transition:text-decoration-color var(--vectis-duration-fast) var(--vectis-ease-default)}.v-link[data-underline=always],.v-link[data-underline=hover]:is(:hover,:focus-visible){text-decoration-line:underline}.v-link:hover{text-decoration-color:currentColor}.v-link[data-tone=inherit]{color:inherit}.v-link[data-tone=inherit]:hover:not([aria-disabled=true]){color:var(--vectis-color-accent-text)}.v-link:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:var(--vectis-focus-ring-offset)}.v-link-external{vertical-align:middle;margin-inline-start:var(--vectis-space-1)}.v-link[aria-disabled=true]{color:var(--vectis-color-text-subtle);cursor:not-allowed;text-decoration-color:currentColor}@media (forced-colors:active){.v-link[aria-disabled=true]{color:graytext}}@media (prefers-reduced-motion:reduce){.v-link{transition:none}}}
|