vectis-ui 0.1.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. package/README.md +40 -6
  2. package/dist/components/VAccordion/VAccordion.css +1 -1
  3. package/dist/components/VAccordion/VAccordion.d.ts +9 -16
  4. package/dist/components/VAccordion/VAccordion.js.map +1 -1
  5. package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js +3 -3
  6. package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  7. package/dist/components/VAccordion/VAccordionItem.css +1 -1
  8. package/dist/components/VAccordion/VAccordionItem.d.ts +33 -19
  9. package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
  10. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js +50 -38
  11. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  12. package/dist/components/VAccordion/context.d.ts +1 -8
  13. package/dist/components/VAccordion/context.js.map +1 -1
  14. package/dist/components/VAvatar/VAvatar.css +1 -1
  15. package/dist/components/VAvatar/VAvatar.d.ts +44 -29
  16. package/dist/components/VAvatar/VAvatar.js.map +1 -1
  17. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +38 -35
  18. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/VAvatar/VAvatarGroup.d.ts +5 -16
  20. package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
  21. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +7 -7
  22. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  23. package/dist/components/VAvatar/context.d.ts +6 -10
  24. package/dist/components/VAvatar/context.js.map +1 -1
  25. package/dist/components/VBadge/VBadge.css +1 -1
  26. package/dist/components/VBadge/VBadge.d.ts +31 -10
  27. package/dist/components/VBadge/VBadge.js.map +1 -1
  28. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +39 -24
  29. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  30. package/dist/components/VBreadcrumb/VBreadcrumb.css +1 -1
  31. package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +4 -17
  32. package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
  33. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +23 -25
  34. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  35. package/dist/components/VButton/VButton.css +1 -1
  36. package/dist/components/VButton/VButton.d.ts +30 -10
  37. package/dist/components/VButton/VButton.js.map +1 -1
  38. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +39 -30
  39. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  40. package/dist/components/VButton/VButtonGroup.css +1 -1
  41. package/dist/components/VButton/VButtonGroup.d.ts +76 -13
  42. package/dist/components/VButton/VButtonGroup.js.map +1 -1
  43. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js +74 -8
  44. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  45. package/dist/components/VButton/context.d.ts +30 -0
  46. package/dist/components/VButton/context.js +6 -0
  47. package/dist/components/VButton/context.js.map +1 -0
  48. package/dist/components/VCalendar/VCalendar.css +1 -1
  49. package/dist/components/VCalendar/VCalendar.d.ts +50 -36
  50. package/dist/components/VCalendar/VCalendar.js.map +1 -1
  51. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +99 -85
  52. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  53. package/dist/components/VCalendar/VCalendarEvent.css +1 -1
  54. package/dist/components/VCalendar/VCalendarEvent.d.ts +5 -11
  55. package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
  56. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js +7 -1
  57. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  58. package/dist/components/VCalendar/VCalendarMonth.css +1 -1
  59. package/dist/components/VCalendar/VCalendarMonth.d.ts +10 -13
  60. package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
  61. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +183 -244
  62. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
  63. package/dist/components/VCalendar/VCalendarTimeGrid.css +1 -1
  64. package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +15 -20
  65. package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
  66. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +385 -448
  67. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  68. package/dist/components/VCalendar/VCalendarYear.css +1 -1
  69. package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
  70. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js +53 -49
  71. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  72. package/dist/components/VCalendar/gesture.d.ts +158 -0
  73. package/dist/components/VCalendar/gesture.js +170 -0
  74. package/dist/components/VCalendar/gesture.js.map +1 -0
  75. package/dist/components/VCalendar/keyboard.d.ts +9 -3
  76. package/dist/components/VCalendar/keyboard.js +2 -6
  77. package/dist/components/VCalendar/keyboard.js.map +1 -1
  78. package/dist/components/VCalendar/layout.d.ts +67 -54
  79. package/dist/components/VCalendar/layout.js +77 -69
  80. package/dist/components/VCalendar/layout.js.map +1 -1
  81. package/dist/components/VCalendar/timeGrid.d.ts +55 -0
  82. package/dist/components/VCalendar/timeGrid.js +71 -0
  83. package/dist/components/VCalendar/timeGrid.js.map +1 -0
  84. package/dist/components/VCalendar/types.d.ts +41 -16
  85. package/dist/components/VCarousel/VCarousel.css +1 -1
  86. package/dist/components/VCarousel/VCarousel.d.ts +21 -41
  87. package/dist/components/VCarousel/VCarousel.js.map +1 -1
  88. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +157 -129
  89. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  90. package/dist/components/VCarousel/VCarouselItem.d.ts +0 -17
  91. package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
  92. package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  93. package/dist/components/VCarousel/context.d.ts +0 -12
  94. package/dist/components/VCarousel/context.js.map +1 -1
  95. package/dist/components/VCheckbox/VCheckbox.css +1 -1
  96. package/dist/components/VCheckbox/VCheckbox.d.ts +31 -16
  97. package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
  98. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +62 -29
  99. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  100. package/dist/components/VChip/VChip.css +1 -1
  101. package/dist/components/VChip/VChip.d.ts +36 -26
  102. package/dist/components/VChip/VChip.js.map +1 -1
  103. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +57 -48
  104. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  105. package/dist/components/VCombobox/VCombobox.css +1 -1
  106. package/dist/components/VCombobox/VCombobox.d.ts +127 -31
  107. package/dist/components/VCombobox/VCombobox.js.map +1 -1
  108. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +336 -247
  109. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  110. package/dist/components/VCombobox/VComboboxGroup.css +1 -1
  111. package/dist/components/VCombobox/VComboboxGroup.d.ts +0 -9
  112. package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
  113. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  114. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  115. package/dist/components/VCombobox/VComboboxOption.css +1 -1
  116. package/dist/components/VCombobox/VComboboxOption.d.ts +0 -15
  117. package/dist/components/VCombobox/VComboboxOption.js.map +1 -1
  118. package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js +4 -4
  119. package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
  120. package/dist/components/VCombobox/VComboboxSeparator.js.map +1 -1
  121. package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  122. package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
  123. package/dist/components/VDataTable/VDataTable.css +1 -1
  124. package/dist/components/VDataTable/VDataTable.d.ts +50 -28
  125. package/dist/components/VDataTable/VDataTable.js.map +1 -1
  126. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +183 -194
  127. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  128. package/dist/components/VDateInput/VDateInput.css +1 -1
  129. package/dist/components/VDateInput/VDateInput.d.ts +90 -42
  130. package/dist/components/VDateInput/VDateInput.js.map +1 -1
  131. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +159 -191
  132. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  133. package/dist/components/VDatePicker/VDatePicker.css +1 -1
  134. package/dist/components/VDatePicker/VDatePicker.d.ts +61 -30
  135. package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
  136. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +243 -221
  137. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  138. package/dist/components/VDialog/VDialog.css +1 -1
  139. package/dist/components/VDialog/VDialog.d.ts +45 -33
  140. package/dist/components/VDialog/VDialog.js.map +1 -1
  141. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +65 -64
  142. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  143. package/dist/components/VDialog/VDialogAlert.d.ts +24 -20
  144. package/dist/components/VDialog/VDialogAlert.js.map +1 -1
  145. package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js +36 -25
  146. package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  147. package/dist/components/VFileInput/VFileInput.css +1 -1
  148. package/dist/components/VFileInput/VFileInput.d.ts +78 -26
  149. package/dist/components/VFileInput/VFileInput.js.map +1 -1
  150. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +144 -132
  151. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  152. package/dist/components/VFilePicker/VFilePicker.css +1 -1
  153. package/dist/components/VFilePicker/VFilePicker.d.ts +47 -30
  154. package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
  155. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +166 -163
  156. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  157. package/dist/components/VFilePicker/fileKind.d.ts +2 -27
  158. package/dist/components/VFilePicker/fileKind.js +9 -8
  159. package/dist/components/VFilePicker/fileKind.js.map +1 -1
  160. package/dist/components/VHotkeys/VHotkeys.css +1 -1
  161. package/dist/components/VHotkeys/VHotkeys.d.ts +8 -30
  162. package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
  163. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +4 -4
  164. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
  165. package/dist/components/VHotkeys/platform.js.map +1 -1
  166. package/dist/components/VIcon/VIcon.css +1 -1
  167. package/dist/components/VIcon/VIcon.d.ts +15 -6
  168. package/dist/components/VIcon/VIcon.js.map +1 -1
  169. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js +45 -37
  170. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  171. package/dist/components/VIcon/iconProps.js.map +1 -1
  172. package/dist/components/VIcon/icons/index.d.ts +1 -1
  173. package/dist/components/VIcon/icons/index.js.map +1 -1
  174. package/dist/components/VIcon/icons/names.d.ts +1 -1
  175. package/dist/components/VIcon/icons/names.js.map +1 -1
  176. package/dist/components/VIcon/resolver.d.ts +3 -3
  177. package/dist/components/VIcon/resolver.js.map +1 -1
  178. package/dist/components/VIcon/types.d.ts +12 -12
  179. package/dist/components/VIconButton/VIconButton.css +1 -1
  180. package/dist/components/VIconButton/VIconButton.d.ts +28 -11
  181. package/dist/components/VIconButton/VIconButton.js.map +1 -1
  182. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js +26 -20
  183. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  184. package/dist/components/VInput/VInput.css +1 -1
  185. package/dist/components/VInput/VInput.d.ts +26 -7
  186. package/dist/components/VInput/VInput.js.map +1 -1
  187. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +86 -75
  188. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  189. package/dist/components/VInput/VInputGroup.css +2 -0
  190. package/dist/components/VInput/VInputGroup.d.ts +47 -0
  191. package/dist/components/VInput/VInputGroup.js +9 -0
  192. package/dist/components/VInput/VInputGroup.js.map +1 -0
  193. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +80 -0
  194. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
  195. package/dist/components/VInput/context.d.ts +11 -0
  196. package/dist/components/VInput/context.js +6 -0
  197. package/dist/components/VInput/context.js.map +1 -0
  198. package/dist/components/VInputOTP/VInputOTP.css +1 -1
  199. package/dist/components/VInputOTP/VInputOTP.d.ts +37 -22
  200. package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
  201. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +98 -61
  202. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
  203. package/dist/components/VMenu/VMenu.d.ts +23 -30
  204. package/dist/components/VMenu/VMenu.js.map +1 -1
  205. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js +31 -27
  206. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  207. package/dist/components/VMenu/VMenuGroup.css +1 -1
  208. package/dist/components/VMenu/VMenuGroup.d.ts +12 -8
  209. package/dist/components/VMenu/VMenuGroup.js.map +1 -1
  210. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js +11 -11
  211. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  212. package/dist/components/VMenu/VMenuItem.css +1 -1
  213. package/dist/components/VMenu/VMenuItem.d.ts +11 -19
  214. package/dist/components/VMenu/VMenuItem.js.map +1 -1
  215. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +65 -57
  216. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  217. package/dist/components/VMenu/VMenuPanel.d.ts +14 -30
  218. package/dist/components/VMenu/VMenuPanel.js.map +1 -1
  219. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +43 -44
  220. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  221. package/dist/components/VMenu/context.d.ts +2 -6
  222. package/dist/components/VMenu/context.js.map +1 -1
  223. package/dist/components/VPagination/VPagination.css +1 -1
  224. package/dist/components/VPagination/VPagination.d.ts +60 -22
  225. package/dist/components/VPagination/VPagination.js.map +1 -1
  226. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +113 -161
  227. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  228. package/dist/components/VPopover/VPopover.css +1 -1
  229. package/dist/components/VPopover/VPopover.d.ts +26 -7
  230. package/dist/components/VPopover/VPopover.js.map +1 -1
  231. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js +43 -37
  232. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  233. package/dist/components/VProgressCircular/VProgressCircular.css +1 -1
  234. package/dist/components/VProgressCircular/VProgressCircular.d.ts +15 -4
  235. package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
  236. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js +32 -30
  237. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
  238. package/dist/components/VProgressLinear/VProgressLinear.css +1 -1
  239. package/dist/components/VProgressLinear/VProgressLinear.d.ts +23 -8
  240. package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
  241. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js +34 -32
  242. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  243. package/dist/components/VRadio/VRadio.css +1 -1
  244. package/dist/components/VRadio/VRadio.d.ts +36 -18
  245. package/dist/components/VRadio/VRadio.js.map +1 -1
  246. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +55 -26
  247. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  248. package/dist/components/VSeparator/VSeparator.d.ts +4 -2
  249. package/dist/components/VSeparator/VSeparator.js.map +1 -1
  250. package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  251. package/dist/components/VSideNavigation/VSideNavigation.css +1 -1
  252. package/dist/components/VSideNavigation/VSideNavigation.d.ts +5 -19
  253. package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
  254. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +5 -5
  255. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
  256. package/dist/components/VSideNavigation/VSideNavigationGroup.css +1 -1
  257. package/dist/components/VSideNavigation/VSideNavigationGroup.d.ts +9 -17
  258. package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
  259. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js +2 -2
  260. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  261. package/dist/components/VSideNavigation/VSideNavigationItem.css +1 -1
  262. package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +22 -30
  263. package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
  264. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +77 -87
  265. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  266. package/dist/components/VSideNavigation/context.d.ts +0 -14
  267. package/dist/components/VSideNavigation/context.js.map +1 -1
  268. package/dist/components/VSkeletonLoader/VSkeletonLoader.css +1 -1
  269. package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +9 -9
  270. package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
  271. package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  272. package/dist/components/VSlider/VSlider.css +1 -1
  273. package/dist/components/VSlider/VSlider.d.ts +52 -27
  274. package/dist/components/VSlider/VSlider.js.map +1 -1
  275. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +202 -118
  276. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  277. package/dist/components/VSnackbar/VSnackbar.css +1 -1
  278. package/dist/components/VSnackbar/VSnackbar.d.ts +0 -23
  279. package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
  280. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +38 -38
  281. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  282. package/dist/components/VSnackbar/state.d.ts +0 -27
  283. package/dist/components/VSnackbar/state.js.map +1 -1
  284. package/dist/components/VSpinner/VSpinner.css +1 -1
  285. package/dist/components/VSpinner/VSpinner.d.ts +6 -6
  286. package/dist/components/VSpinner/VSpinner.js.map +1 -1
  287. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js +10 -9
  288. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
  289. package/dist/components/VSwitch/VSwitch.css +1 -1
  290. package/dist/components/VSwitch/VSwitch.d.ts +35 -13
  291. package/dist/components/VSwitch/VSwitch.js.map +1 -1
  292. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +62 -21
  293. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  294. package/dist/components/VTabs/VTab.css +1 -1
  295. package/dist/components/VTabs/VTab.d.ts +12 -12
  296. package/dist/components/VTabs/VTab.js.map +1 -1
  297. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +36 -24
  298. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
  299. package/dist/components/VTabs/VTabPanel.d.ts +2 -10
  300. package/dist/components/VTabs/VTabPanel.js.map +1 -1
  301. package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js +4 -4
  302. package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  303. package/dist/components/VTabs/VTabs.css +1 -1
  304. package/dist/components/VTabs/VTabs.d.ts +27 -18
  305. package/dist/components/VTabs/VTabs.js.map +1 -1
  306. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +92 -81
  307. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  308. package/dist/components/VTabs/context.d.ts +9 -19
  309. package/dist/components/VTabs/context.js +1 -1
  310. package/dist/components/VTabs/context.js.map +1 -1
  311. package/dist/components/VTextarea/VTextarea.css +1 -1
  312. package/dist/components/VTextarea/VTextarea.d.ts +34 -6
  313. package/dist/components/VTextarea/VTextarea.js.map +1 -1
  314. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +91 -74
  315. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  316. package/dist/components/VTimeInput/VTimeInput.css +1 -1
  317. package/dist/components/VTimeInput/VTimeInput.d.ts +123 -33
  318. package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
  319. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +294 -291
  320. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  321. package/dist/components/VTimeInput/search.d.ts +24 -0
  322. package/dist/components/VTimeInput/search.js +22 -0
  323. package/dist/components/VTimeInput/search.js.map +1 -0
  324. package/dist/components/VTimePicker/VTimePicker.css +1 -1
  325. package/dist/components/VTimePicker/VTimePicker.d.ts +51 -30
  326. package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
  327. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +216 -151
  328. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  329. package/dist/components/VTimePicker/limits.d.ts +88 -0
  330. package/dist/components/VTimePicker/limits.js +70 -0
  331. package/dist/components/VTimePicker/limits.js.map +1 -0
  332. package/dist/components/VToast/VToast.css +1 -1
  333. package/dist/components/VToast/VToast.d.ts +0 -10
  334. package/dist/components/VToast/VToast.js.map +1 -1
  335. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +10 -10
  336. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  337. package/dist/components/VToast/VToaster.css +1 -1
  338. package/dist/components/VToast/VToaster.d.ts +0 -17
  339. package/dist/components/VToast/VToaster.js.map +1 -1
  340. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +29 -28
  341. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
  342. package/dist/components/VToast/state.d.ts +10 -19
  343. package/dist/components/VToast/state.js +1 -1
  344. package/dist/components/VToast/state.js.map +1 -1
  345. package/dist/components/VToggle/VToggle.css +1 -1
  346. package/dist/components/VToggle/VToggle.d.ts +56 -11
  347. package/dist/components/VToggle/VToggle.js.map +1 -1
  348. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +80 -46
  349. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
  350. package/dist/components/VToggle/VToggleItem.d.ts +16 -2
  351. package/dist/components/VToggle/VToggleItem.js +0 -2
  352. package/dist/components/VToggle/VToggleItem.js.map +1 -1
  353. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js +23 -15
  354. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  355. package/dist/components/VToggle/context.d.ts +4 -24
  356. package/dist/components/VToggle/context.js.map +1 -1
  357. package/dist/components/VTooltip/VTooltip.css +1 -1
  358. package/dist/components/VTooltip/VTooltip.d.ts +9 -33
  359. package/dist/components/VTooltip/VTooltip.js.map +1 -1
  360. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +28 -24
  361. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  362. package/dist/components/VTypography/VTypography.js.map +1 -1
  363. package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
  364. package/dist/composables/useAriaLabel.d.ts +0 -17
  365. package/dist/composables/useAriaLabel.js.map +1 -1
  366. package/dist/composables/useClearable.d.ts +42 -0
  367. package/dist/composables/useClearable.js +19 -0
  368. package/dist/composables/useClearable.js.map +1 -0
  369. package/dist/composables/useControlShape.d.ts +22 -0
  370. package/dist/composables/useControlShape.js +13 -0
  371. package/dist/composables/useControlShape.js.map +1 -0
  372. package/dist/composables/useDetailsOpen.d.ts +22 -0
  373. package/dist/composables/useDetailsOpen.js +21 -0
  374. package/dist/composables/useDetailsOpen.js.map +1 -0
  375. package/dist/composables/useFieldIds.d.ts +0 -15
  376. package/dist/composables/useFieldIds.js +8 -7
  377. package/dist/composables/useFieldIds.js.map +1 -1
  378. package/dist/composables/useFieldPanel.d.ts +21 -18
  379. package/dist/composables/useFieldPanel.js +43 -23
  380. package/dist/composables/useFieldPanel.js.map +1 -1
  381. package/dist/composables/useFileDrop.d.ts +0 -22
  382. package/dist/composables/useFileDrop.js.map +1 -1
  383. package/dist/composables/useFileField.d.ts +45 -17
  384. package/dist/composables/useFileField.js +40 -20
  385. package/dist/composables/useFileField.js.map +1 -1
  386. package/dist/composables/useFocusoutDismiss.d.ts +0 -15
  387. package/dist/composables/useFocusoutDismiss.js.map +1 -1
  388. package/dist/composables/useIconClickHandlers.d.ts +29 -14
  389. package/dist/composables/useIconClickHandlers.js +15 -5
  390. package/dist/composables/useIconClickHandlers.js.map +1 -1
  391. package/dist/composables/useInertLink.d.ts +20 -0
  392. package/dist/composables/useInertLink.js +23 -0
  393. package/dist/composables/useInertLink.js.map +1 -0
  394. package/dist/composables/useMaskedField.d.ts +33 -10
  395. package/dist/composables/useMaskedField.js +45 -1
  396. package/dist/composables/useMaskedField.js.map +1 -1
  397. package/dist/composables/usePopover.d.ts +15 -1
  398. package/dist/composables/usePopover.js +23 -8
  399. package/dist/composables/usePopover.js.map +1 -1
  400. package/dist/composables/useProgressValue.d.ts +5 -8
  401. package/dist/composables/useProgressValue.js +6 -3
  402. package/dist/composables/useProgressValue.js.map +1 -1
  403. package/dist/composables/useRootAttrs.d.ts +12 -19
  404. package/dist/composables/useRootAttrs.js.map +1 -1
  405. package/dist/composables/useTimer.d.ts +8 -10
  406. package/dist/composables/useTimer.js.map +1 -1
  407. package/dist/i18n/en.d.ts +2 -10
  408. package/dist/i18n/en.js +18 -19
  409. package/dist/i18n/en.js.map +1 -1
  410. package/dist/i18n/fr.d.ts +2 -18
  411. package/dist/i18n/fr.js +18 -19
  412. package/dist/i18n/fr.js.map +1 -1
  413. package/dist/i18n/state.d.ts +44 -47
  414. package/dist/i18n/state.js +20 -17
  415. package/dist/i18n/state.js.map +1 -1
  416. package/dist/i18n/types.d.ts +53 -44
  417. package/dist/icons.d.ts +3 -3
  418. package/dist/index.d.ts +43 -17
  419. package/dist/index.js +45 -44
  420. package/dist/styles.css +1 -1
  421. package/dist/tokens/css.js.map +1 -1
  422. package/dist/tokens/index.d.ts +5 -1
  423. package/dist/tokens/primitives.js.map +1 -1
  424. package/dist/tokens/semantic.d.ts +5 -1
  425. package/dist/tokens/semantic.js +6 -2
  426. package/dist/tokens/semantic.js.map +1 -1
  427. package/dist/tokens/themes/dark.js.map +1 -1
  428. package/dist/tokens/types.d.ts +7 -15
  429. package/dist/tokens/types.js.map +1 -1
  430. package/dist/tokens.json +25 -5
  431. package/dist/types.d.ts +24 -0
  432. package/dist/utils/array.d.ts +3 -6
  433. package/dist/utils/array.js.map +1 -1
  434. package/dist/utils/arrowNav.d.ts +17 -17
  435. package/dist/utils/arrowNav.js +5 -3
  436. package/dist/utils/arrowNav.js.map +1 -1
  437. package/dist/utils/chip.d.ts +11 -15
  438. package/dist/utils/chip.js.map +1 -1
  439. package/dist/utils/css.d.ts +22 -11
  440. package/dist/utils/css.js +5 -2
  441. package/dist/utils/css.js.map +1 -1
  442. package/dist/utils/date.d.ts +42 -19
  443. package/dist/utils/date.js +97 -98
  444. package/dist/utils/date.js.map +1 -1
  445. package/dist/utils/direction.d.ts +6 -12
  446. package/dist/utils/direction.js.map +1 -1
  447. package/dist/utils/env.d.ts +5 -6
  448. package/dist/utils/file.d.ts +46 -58
  449. package/dist/utils/file.js +43 -36
  450. package/dist/utils/file.js.map +1 -1
  451. package/dist/utils/focus.d.ts +8 -17
  452. package/dist/utils/focus.js.map +1 -1
  453. package/dist/utils/hostWarns.d.ts +12 -0
  454. package/dist/utils/hostWarns.js +6 -0
  455. package/dist/utils/hostWarns.js.map +1 -0
  456. package/dist/utils/ids.d.ts +10 -0
  457. package/dist/utils/ids.js +8 -0
  458. package/dist/utils/ids.js.map +1 -0
  459. package/dist/utils/matcher.d.ts +5 -9
  460. package/dist/utils/matcher.js.map +1 -1
  461. package/dist/utils/memo.d.ts +12 -0
  462. package/dist/utils/memo.js +9 -0
  463. package/dist/utils/memo.js.map +1 -0
  464. package/dist/utils/number.d.ts +3 -4
  465. package/dist/utils/number.js.map +1 -1
  466. package/dist/utils/text.d.ts +20 -9
  467. package/dist/utils/text.js +17 -3
  468. package/dist/utils/text.js.map +1 -1
  469. package/dist/utils/time.d.ts +53 -61
  470. package/dist/utils/time.js +49 -45
  471. package/dist/utils/time.js.map +1 -1
  472. package/dist/utils/vnode.d.ts +8 -15
  473. package/dist/utils/vnode.js.map +1 -1
  474. package/package.json +6 -6
  475. package/dist/components/VToggle/VToggleItem.css +0 -2
@@ -0,0 +1,6 @@
1
+ //#region src/components/VInput/context.ts
2
+ var e = Symbol("v-input-group");
3
+ //#endregion
4
+ export { e as inputGroupKey };
5
+
6
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","names":[],"sources":["../../../src/components/VInput/context.ts"],"sourcesContent":["/**\n * What a VInputGroup passes down to the fields it joins. A joined row is one object, so the\n * shape of the controls in it is a decision that belongs to the row rather than to each of\n * its segments, and the group announces it once instead of the writer repeating the same\n * props on every child.\n *\n * The arbitration follows VButtonGroup's, minus the tone — a field has none:\n *\n * - `size` and `compact` are the SHAPE of the control, and the group wins. A segment of\n * another height no longer lines up with its neighbours, and the merged border stops\n * reading as a single object (`group?.x ?? props.x`).\n * - `disabled` is read as an OR (`group?.disabled || props.disabled`). The two answers are\n * cumulative: a row switched off cannot have one of its segments opt back in, and a\n * segment disabled on its own is not revived by a row that says nothing.\n *\n * Everything is exposed through getters, which is what keeps the group's props reactive on\n * the other side of the injection. Every member is optional: a group that sets none of them\n * provides `undefined` throughout, which `??` hands straight back to the child's own prop.\n *\n * A composed field resolves this twice — once for itself, once inside the VInput it renders\n * — and that is harmless: `group?.size ?? (group?.size ?? props.size)` is the first term\n * whenever the group names one, and the child's prop otherwise. The redundancy even helps, a\n * composed field that forgot to resolve still following the row through its inner VInput.\n *\n * TRAP — this key is injected in DEPTH, not only into direct children. It carries only what\n * a FIELD reads, never what a button would: the buttons of a floating panel are inside a\n * segment's subtree and would otherwise take the row's size. See `NO_BUTTON_GROUP`, which\n * VDateInput and VTimeInput provide for exactly that reason.\n */\n\nimport type { InjectionKey } from 'vue'\n\nimport type { InputGroupSize } from './VInputGroup.vue'\n\nexport interface InputGroupContext {\n /** The height of the segments. */\n readonly size?: InputGroupSize\n /** The reduced density. */\n readonly compact?: boolean\n /** Whether the whole row is unusable, which no segment can refuse. */\n readonly disabled?: boolean\n}\n\nexport const inputGroupKey: InjectionKey<InputGroupContext> = Symbol('v-input-group')\n"],"mappings":";AA2CA,IAAa,IAAiD,OAAO,eAAe"}
@@ -1,2 +1,2 @@
1
1
  @layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
2
- @layer vectis.components{.v-otp{--otp-font-size:var(--vectis-font-size-lg);align-items:center;gap:var(--control-gap);display:inline-flex}.v-otp-input{width:var(--control-height);height:var(--control-height);text-align:center;background:var(--vectis-color-surface);color:var(--vectis-color-text);border:1px solid var(--vectis-color-border-strong);border-radius:var(--vectis-radius-interactive);font-family:var(--vectis-text-family-code);font-size:var(--otp-font-size);transition:border-color var(--vectis-duration-fast) var(--vectis-ease-default)}.v-otp-input:focus-visible{border-color:var(--vectis-color-accent);box-shadow:0 0 0 1px var(--vectis-color-accent);outline:var(--vectis-focus-ring-width) solid transparent}.v-otp[data-invalid] .v-otp-input{border-color:var(--vectis-color-danger)}.v-otp[data-invalid] .v-otp-input:focus-visible{box-shadow:0 0 0 1px var(--vectis-color-danger)}.v-otp-literal{color:var(--vectis-color-text-muted);font-family:var(--vectis-text-family-code);font-size:var(--otp-font-size);-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;display:inline-flex}.v-otp[data-disabled] .v-otp-input{background:var(--vectis-color-surface-muted);color:var(--vectis-color-text-subtle);border-color:var(--vectis-color-border);cursor:not-allowed}.v-otp[data-disabled] .v-otp-literal{color:var(--vectis-color-text-subtle)}.v-otp[data-size=sm]{--otp-font-size:var(--vectis-font-size-md)}.v-otp[data-size=lg]{--otp-font-size:var(--vectis-font-size-xl)}@media (prefers-reduced-motion:reduce){.v-otp-input{transition:none}}}
2
+ @layer vectis.components{.v-input-otp{--input-otp-font-size:var(--vectis-font-size-lg);align-items:start;gap:var(--vectis-space-1);flex-direction:column;display:inline-flex}.v-input-otp-boxes{align-items:center;gap:var(--control-gap);display:flex}.v-input-otp-input{width:var(--control-height);height:var(--control-height);text-align:center;background:var(--vectis-color-surface);color:var(--vectis-color-text);border:1px solid var(--vectis-color-border-strong);border-radius:var(--vectis-radius-interactive);font-family:var(--vectis-text-family-code);font-size:var(--input-otp-font-size);transition:border-color var(--vectis-duration-fast) var(--vectis-ease-default)}.v-input-otp-input:focus-visible{border-color:var(--vectis-color-accent);box-shadow:0 0 0 1px var(--vectis-color-accent);outline:var(--vectis-focus-ring-width) solid transparent}.v-input-otp[data-readonly] .v-input-otp-input{background:var(--vectis-color-surface-sunken)}.v-input-otp[data-invalid] .v-input-otp-input{border-color:var(--vectis-color-danger)}.v-input-otp[data-invalid] .v-input-otp-input:focus-visible{box-shadow:0 0 0 1px var(--vectis-color-danger)}.v-input-otp-literal{color:var(--vectis-color-text-muted);font-family:var(--vectis-text-family-code);font-size:var(--input-otp-font-size);-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;display:inline-flex}.v-input-otp[data-disabled] .v-input-otp-input{background:var(--vectis-color-surface-muted);color:var(--vectis-color-text-subtle);border-color:var(--vectis-color-border);cursor:not-allowed}.v-input-otp[data-disabled] .v-input-otp-literal,.v-input-otp[data-disabled] .v-input-otp-hint{color:var(--vectis-color-text-subtle)}.v-input-otp[data-size=sm]{--input-otp-font-size:var(--vectis-font-size-md)}.v-input-otp[data-size=lg]{--input-otp-font-size:var(--vectis-font-size-xl)}@media (prefers-reduced-motion:reduce){.v-input-otp-input{transition:none}}}
@@ -1,21 +1,8 @@
1
1
  import { IconSource } from '../VIcon/types.js';
2
- /**
3
- * The row of boxes a one-time code is typed into, one character per box.
4
- *
5
- * The platform has no such control, so each box is a real `<input>` and the
6
- * JavaScript is what makes the row behave as a single field: typing a character moves
7
- * to the next box, Backspace on an empty one goes back, the arrows walk along, and a
8
- * code pasted anywhere is spread across the boxes. The first box carries
9
- * `autocomplete="one-time-code"`, so a code filled in from an SMS or a password
10
- * manager is spread the same way.
11
- *
12
- * `pattern` is what cuts the row up: every `#` becomes a box, and every other
13
- * character becomes a separator shown between them, which is displayed but never part
14
- * of the value. Pasting understands those separators — a code copied in its formatted
15
- * shape, "GT-123" or "123.456.789", is consumed with them in place — and everything
16
- * else is filtered by `format`, which also forces capitals outside a numeric code so
17
- * that the value has one canonical form.
18
- */
2
+ /** The height of the boxes: 32, 40 or 48 pixels. */
3
+ export type InputOTPSize = 'sm' | 'md' | 'lg';
4
+ /** Which characters the code is made of. */
5
+ export type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric';
19
6
  interface InputOTPProps {
20
7
  /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */
21
8
  length?: number;
@@ -23,7 +10,7 @@ interface InputOTPProps {
23
10
  * Which characters the code is made of. It filters what can be typed or pasted, and
24
11
  * decides which keyboard a phone offers.
25
12
  */
26
- format?: 'numeric' | 'alpha' | 'alphanumeric';
13
+ format?: InputOTPFormat;
27
14
  /**
28
15
  * The shape of the code: each `#` is a box to fill, and every other character is a
29
16
  * separator shown between the boxes without ever being part of the value —
@@ -37,25 +24,51 @@ interface InputOTPProps {
37
24
  */
38
25
  separatorIcon?: IconSource;
39
26
  /** The size of the boxes: 32, 40 or 48 pixels. */
40
- size?: 'sm' | 'md' | 'lg';
27
+ size?: InputOTPSize;
41
28
  /** Takes 4px off the boxes, leaving the text and the icons as they are. */
42
29
  compact?: boolean;
43
30
  /** Makes every box unusable, greyed out through the colour tokens. */
44
31
  disabled?: boolean;
32
+ /**
33
+ * Shows the code without letting it be changed. The boxes keep their focus and the code
34
+ * can still be selected and copied, which is what separates it from `disabled`.
35
+ */
36
+ readonly?: boolean;
45
37
  /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */
46
38
  invalid?: boolean;
47
39
  /**
48
40
  * What screen readers announce for the row as a whole. It falls back to the design
49
41
  * system dictionary.
42
+ *
43
+ * Unlike the `label` of VInput and the other text fields, NOTHING is rendered from it:
44
+ * a row of boxes carries its own instructions above it, written by the page. Use `hint`
45
+ * for a line the reader can see.
50
46
  */
51
47
  label?: string;
48
+ /**
49
+ * A line of help under the boxes — where the code was sent, how long it lasts. It is
50
+ * tied to the row for assistive technology, so it is read out along with the label.
51
+ */
52
+ hint?: string;
52
53
  }
53
54
  type __VLS_Props = InputOTPProps;
54
55
  type __VLS_ModelProps = {
56
+ /**
57
+ * The code as one string, without the separators: a `GT-###` template still yields three
58
+ * characters. It is empty to begin with, and shorter than the full length while it is being
59
+ * typed.
60
+ */
55
61
  modelValue?: string;
56
62
  };
57
63
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
58
- declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
64
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {
65
+ /** Moves the focus to the first empty box, or to the last one when the code is complete. */
66
+ focus: (options?: FocusOptions) => void;
67
+ /** Selects the box the focus is on, as clicking into one already does. */
68
+ select: () => void;
69
+ /** The row itself, for what neither of the two above covers. */
70
+ el: import('vue').Ref<HTMLElement | null, HTMLElement | null>;
71
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
59
72
  "update:modelValue": (value: string) => any;
60
73
  complete: (code: string) => any;
61
74
  }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
@@ -63,14 +76,16 @@ declare const __VLS_export: import('vue').DefineComponent<__VLS_PublicProps, {},
63
76
  onComplete?: ((code: string) => any) | undefined;
64
77
  }>, {
65
78
  label: string;
66
- size: "sm" | "md" | "lg";
79
+ size: InputOTPSize;
67
80
  length: number;
68
81
  pattern: string;
69
82
  compact: boolean;
70
83
  disabled: boolean;
71
84
  invalid: boolean;
72
- format: "numeric" | "alpha" | "alphanumeric";
85
+ hint: string;
86
+ readonly: boolean;
73
87
  separatorIcon: IconSource;
88
+ format: InputOTPFormat;
74
89
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
75
90
  declare const _default: typeof __VLS_export;
76
91
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"VInputOTP.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { isDev } from '../../utils/env'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n// @keyboard @core\n/**\n * The row of boxes a one-time code is typed into, one character per box.\n *\n * The platform has no such control, so each box is a real `<input>` and the\n * JavaScript is what makes the row behave as a single field: typing a character moves\n * to the next box, Backspace on an empty one goes back, the arrows walk along, and a\n * code pasted anywhere is spread across the boxes. The first box carries\n * `autocomplete=\"one-time-code\"`, so a code filled in from an SMS or a password\n * manager is spread the same way.\n *\n * `pattern` is what cuts the row up: every `#` becomes a box, and every other\n * character becomes a separator shown between them, which is displayed but never part\n * of the value. Pasting understands those separators — a code copied in its formatted\n * shape, \"GT-123\" or \"123.456.789\", is consumed with them in place — and everything\n * else is filtered by `format`, which also forces capitals outside a numeric code so\n * that the value has one canonical form.\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?: 'numeric' | 'alpha' | 'alphanumeric'\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a\n * separator shown between the boxes without ever being part of the value —\n * `'GT-###'`, `'###.###.###'`. It wins over `length`.\n */\n pattern?: string\n /**\n * An icon drawn in place of EVERY separator of the pattern. It suits a template\n * whose separators are purely decorative, `'###-###'`, and should not be used with\n * one carrying meaningful text such as `'GT-###'`, which the icon would erase.\n */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: 'sm' | 'md' | 'lg'\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 /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design\n * system dictionary.\n */\n label?: 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 invalid: false,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /** Emitted the moment every box is filled, carrying the complete code. */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst inputs = ref<(HTMLInputElement | null)[]>([])\nconst digits = ref<string[]>([])\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and the model is NOT\n // rewritten to match: writing back here would feed the watcher below and the two\n // would keep correcting each other.\n digits.value = Array.from({ length: slotCount.value }, (_, i) => value[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\nfunction commit() {\n const code = digits.value.join('')\n model.value = code\n if (code.length === slotCount.value) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received\n * them. It walks the row: a separator swallows the incoming character when the two\n * match, which is what lets a code pasted in its formatted shape line up, and a box\n * takes the next character the format accepts.\n *\n * It returns the last box it filled, so the caller knows where to put the focus, and\n * `null` when nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n // Step back over the separators immediately before that box: pasting the whole\n // string onto the first box means pasting its prefix too, and \"GT-\" has to be\n // matched rather than treated as characters of the code.\n while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n const chars = [...raw]\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core — moving the focus forward on its own is what turns a row of\n// separate inputs into something that types like one field; spreading the characters\n// is the core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n // The same path serves a single keystroke and a pasted code: both are spread from\n // this box onwards.\n const lastFilled = distribute(el.value, slotIndex)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format.\n digits.value[slotIndex] = ''\n el.value = ''\n commit()\n return\n }\n el.value = digits.value[slotIndex] ?? ''\n inputs.value[Math.min(lastFilled + 1, slotCount.value - 1)]?.focus()\n commit()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n if (event.key === 'Backspace' && !digits.value[slotIndex] && slotIndex > 0) {\n event.preventDefault()\n digits.value[slotIndex - 1] = ''\n commit()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowLeft' && slotIndex > 0) {\n event.preventDefault()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowRight' && slotIndex < slotCount.value - 1) {\n event.preventDefault()\n inputs.value[slotIndex + 1]?.focus()\n }\n}\n</script>\n\n<template>\n <div\n class=\"v-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n >\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n :ref=\"\n (el) => {\n inputs[cell.slotIndex] = el as HTMLInputElement | null\n }\n \"\n type=\"text\"\n class=\"v-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 :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"($event.target as HTMLInputElement).select()\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-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</template>\n\n<style>\n@layer vectis.components {\n .v-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 --otp-font-size: var(--vectis-font-size-lg);\n\n display: inline-flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-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(--otp-font-size);\n transition: border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The focused box appears to have a two-pixel border, exactly as in VInput and\n VTextarea: its own 1px border plus a 1px shadow of the same colour just outside\n it. The transparent outline is the safety net for Windows forced colours, which\n drop box-shadows entirely. */\n .v-otp-input:focus-visible {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n .v-otp[data-invalid] .v-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-otp[data-invalid] .v-otp-input:focus-visible {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-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(--otp-font-size);\n user-select: none;\n }\n\n /* A disabled row greys out through the colour tokens and never through opacity, the\n same treatment as VInput. */\n .v-otp[data-disabled] .v-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-otp[data-disabled] .v-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-otp[data-size='sm'] {\n --otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-otp[data-size='lg'] {\n --otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-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 * The row of boxes a one-time code is typed into, one character per box.\n *\n * The platform has no such control, so each box is a real `<input>` and the JS is what makes\n * the row behave as one field: a character moves to the next box, Backspace on an empty one\n * goes back, the arrows walk along, and a code pasted anywhere is spread across them. The\n * first box carries `autocomplete=\"one-time-code\"`, so a code arriving from an SMS or a\n * password manager is spread the same way.\n *\n * `pattern` cuts the row up: every `#` is a box, every other character a decorative literal\n * shown between them and never part of the value. Pasting understands those literals — a\n * code copied formatted, `GT-123`, is consumed with them in place — and `format` filters the\n * rest, forcing capitals outside a numeric code so the value has one canonical form.\n */\n\nimport { computed, ref, useAttrs, useId, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a\n * separator shown between the boxes without ever being part of the value —\n * `'GT-###'`, `'###.###.###'`. It wins over `length`.\n */\n pattern?: string\n /**\n * An icon drawn in place of EVERY separator of the pattern. It suits a template\n * whose separators are purely decorative, `'###-###'`, and should not be used with\n * one carrying meaningful text such as `'GT-###'`, which the icon would erase.\n */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code\n * can still be selected and copied, which is what separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design\n * system dictionary.\n *\n * Unlike the `label` of VInput and the other text fields, NOTHING is rendered from it:\n * a row of boxes carries its own instructions above it, written by the page. Use `hint`\n * for a line the reader can see.\n */\n label?: string\n /**\n * A line of help under the boxes — where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\n// @a11y\n/*\n * The root IS the group, so the consumer's attributes belong on it — but they are bound\n * BEFORE the two the component decides itself. `aria-describedby` is a list, and the hint\n * is appended to whatever the consumer already pointed at instead of replacing it; left\n * to the fallthrough, their value would land last and take the hint out of the\n * announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n// No `useFieldIds` here: the row is named by `aria-label` and renders no `<label>`, so the\n// field id that composable generates would have nothing to point it at.\nconst hintId = useId()\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /** Emitted the moment every box is filled, carrying the complete code. */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst inputs = ref<(HTMLInputElement | null)[]>([])\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and the model is NOT\n // rewritten to match: writing back here would feed the watcher below and the two\n // would keep correcting each other.\n digits.value = Array.from({ length: slotCount.value }, (_, i) => value[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/*\n * TRAP — the list of boxes is trimmed AFTER the render, never before it. A box that\n * disappears calls its function ref with `null` while it is being unmounted, at its OLD\n * position: trimmed in a `pre` watcher, that late assignment would stretch the array back\n * out, and a pattern that shortened would keep its departed boxes as trailing entries.\n */\nwatch(\n slotCount,\n (count) => {\n inputs.value.length = count\n },\n { flush: 'post' },\n)\n\nfunction commit() {\n const code = digits.value.join('')\n model.value = code\n if (code.length === slotCount.value) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received\n * them. It walks the row: a separator swallows the incoming character when the two\n * match, which is what lets a code pasted in its formatted shape line up, and a box\n * takes the next character the format accepts.\n *\n * It returns the last box it filled, so the caller knows where to put the focus, and\n * `null` when nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n // Step back over the separators immediately before that box: pasting the whole\n // string onto the first box means pasting its prefix too, and \"GT-\" has to be\n // matched rather than treated as characters of the code.\n while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n const chars = [...raw]\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core — moving the focus forward on its own is what turns a row of\n// separate inputs into something that types like one field; spreading the characters\n// is the core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n // The same path serves a single keystroke and a pasted code: both are spread from\n // this box onwards.\n const lastFilled = distribute(el.value, slotIndex)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format.\n digits.value[slotIndex] = ''\n el.value = ''\n commit()\n return\n }\n el.value = digits.value[slotIndex] ?? ''\n inputs.value[Math.min(lastFilled + 1, slotCount.value - 1)]?.focus()\n commit()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n if (event.key === 'Backspace' && !digits.value[slotIndex] && slotIndex > 0) {\n event.preventDefault()\n digits.value[slotIndex - 1] = ''\n commit()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowLeft' && slotIndex > 0) {\n event.preventDefault()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowRight' && slotIndex < slotCount.value - 1) {\n event.preventDefault()\n inputs.value[slotIndex + 1]?.focus()\n }\n}\n/*\n * The same trio every other field of the size scale exposes. `focus` goes to the FIRST\n * EMPTY box rather than to the first box outright, which is where a reader resuming a\n * half-entered code expects to land.\n */\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => {\n const empty = digits.value.findIndex((d) => !d)\n const target = empty === -1 ? slotCount.value - 1 : empty\n inputs.value[target]?.focus(options)\n },\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = inputs.value.find((el) => el === document.activeElement)\n ;(active ?? inputs.value[0])?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-bind=\"attrs\"\n class=\"v-input-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n :ref=\"\n (el) => {\n inputs[cell.slotIndex] = el as HTMLInputElement | null\n }\n \"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"($event.target as HTMLInputElement).select()\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-otp-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input-otp {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n\n /* A column, so the hint sits under the boxes rather than beside them; the row itself\n is the box below. `inline-flex` keeps the component's own width, and the start\n alignment stops the hint from stretching the row to its own length. */\n display: inline-flex;\n flex-direction: column;\n align-items: start;\n gap: var(--vectis-space-1);\n }\n\n .v-input-otp-boxes {\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition: border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The focused box appears to have a two-pixel border, exactly as in VInput and\n VTextarea: its own 1px border plus a 1px shadow of the same colour just outside\n it. The transparent outline is the safety net for Windows forced colours, which\n drop box-shadows entirely. */\n .v-input-otp-input:focus-visible {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /* A read-only row takes the sunken background of a read-only VInput, and the state is\n read from [data-readonly] rather than from `:read-only`, which the browser also matches\n on a disabled box. It is declared BEFORE the invalid and disabled blocks, all three\n weighing (0,2,0), so those still win over it. */\n .v-input-otp[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input:focus-visible {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /* A disabled row greys out through the colour tokens and never through opacity, the\n same treatment as VInput. */\n .v-input-otp[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp[data-disabled] .v-input-otp-literal,\n .v-input-otp[data-disabled] .v-input-otp-hint {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
@@ -1,31 +1,37 @@
1
1
  import { useMessages as e } from "../../i18n/state.js";
2
- import t from "../VIcon/VIcon.js";
3
- import { iconProps as n } from "../VIcon/iconProps.js";
4
- import { useAriaLabel as r } from "../../composables/useAriaLabel.js";
5
- import { Fragment as i, computed as a, createBlock as o, createElementBlock as s, createTextVNode as c, defineComponent as l, mergeModels as u, mergeProps as d, openBlock as f, ref as p, renderList as m, toDisplayString as h, unref as g, useModel as _, watch as v } from "vue";
2
+ import { iconProps as t } from "../VIcon/iconProps.js";
3
+ import n from "../VIcon/VIcon.js";
4
+ import r from "../VTypography/VTypography.js";
5
+ import { useAriaLabel as i } from "../../composables/useAriaLabel.js";
6
+ import { joinIds as a } from "../../utils/ids.js";
7
+ import { Fragment as o, computed as s, createBlock as c, createCommentVNode as l, createElementBlock as u, createElementVNode as d, createTextVNode as f, defineComponent as p, mergeModels as m, mergeProps as h, openBlock as g, ref as _, renderList as v, toDisplayString as y, unref as b, useAttrs as x, useId as S, useModel as C, watch as w, withCtx as T } from "vue";
6
8
  //#region src/components/VInputOTP/VInputOTP.vue?vue&type=script&setup=true&lang.ts
7
- var y = [
9
+ var E = [
8
10
  "aria-label",
11
+ "aria-describedby",
9
12
  "data-invalid",
10
13
  "data-size",
11
14
  "data-compact",
12
- "data-disabled"
13
- ], b = [
15
+ "data-disabled",
16
+ "data-readonly"
17
+ ], D = { class: "v-input-otp-boxes" }, O = [
14
18
  "inputmode",
15
19
  "autocomplete",
16
20
  "value",
17
21
  "disabled",
22
+ "readonly",
18
23
  "aria-label",
19
24
  "aria-invalid",
20
25
  "onInput",
21
26
  "onKeydown"
22
- ], x = {
27
+ ], k = {
23
28
  key: 1,
24
- class: "v-otp-literal",
29
+ class: "v-input-otp-literal",
25
30
  "aria-hidden": "true"
26
- }, S = /*@__PURE__*/ l({
31
+ }, A = /*@__PURE__*/ p({
32
+ inheritAttrs: !1,
27
33
  __name: "VInputOTP",
28
- props: /*@__PURE__*/ u({
34
+ props: /*@__PURE__*/ m({
29
35
  length: { default: 6 },
30
36
  format: { default: "numeric" },
31
37
  pattern: { default: void 0 },
@@ -39,21 +45,26 @@ var y = [
39
45
  type: Boolean,
40
46
  default: !1
41
47
  },
48
+ readonly: {
49
+ type: Boolean,
50
+ default: !1
51
+ },
42
52
  invalid: {
43
53
  type: Boolean,
44
54
  default: !1
45
55
  },
46
- label: { default: void 0 }
56
+ label: { default: void 0 },
57
+ hint: { default: void 0 }
47
58
  }, {
48
59
  modelValue: { default: "" },
49
60
  modelModifiers: {}
50
61
  }),
51
- emits: /*@__PURE__*/ u(["complete"], ["update:modelValue"]),
52
- setup(l, { emit: u }) {
53
- let S = l, C = e(), w = r(() => S.label ?? C.value.inputOTP.label), T = _(l, "modelValue"), E = u, D = a(() => {
54
- if (S.pattern?.includes("#")) {
62
+ emits: /*@__PURE__*/ m(["complete"], ["update:modelValue"]),
63
+ setup(p, { expose: m, emit: A }) {
64
+ let j = p, M = e(), N = i(() => j.label ?? M.value.inputOTP.label), P = x(), F = S(), I = s(() => a(P["aria-describedby"], !!j.hint && F)), L = C(p, "modelValue"), R = A, z = s(() => {
65
+ if (j.pattern?.includes("#")) {
55
66
  let e = 0;
56
- return [...S.pattern].map((t) => t === "#" ? {
67
+ return [...j.pattern].map((t) => t === "#" ? {
57
68
  type: "slot",
58
69
  slotIndex: e++
59
70
  } : {
@@ -61,31 +72,33 @@ var y = [
61
72
  char: t
62
73
  });
63
74
  }
64
- return Array.from({ length: S.length }, (e, t) => ({
75
+ return Array.from({ length: j.length }, (e, t) => ({
65
76
  type: "slot",
66
77
  slotIndex: t
67
78
  }));
68
- }), O = a(() => D.value.filter((e) => e.type === "slot").length), k = {
79
+ }), B = s(() => z.value.filter((e) => e.type === "slot").length), V = {
69
80
  numeric: /[^0-9]/g,
70
81
  alpha: /[^A-Z]/g,
71
82
  alphanumeric: /[^A-Z0-9]/g
72
83
  };
73
- function A(e) {
74
- return (S.format === "numeric" ? e : e.toUpperCase()).replace(k[S.format], "");
84
+ function H(e) {
85
+ return (j.format === "numeric" ? e : e.toUpperCase()).replace(V[j.format], "");
75
86
  }
76
- let j = p([]), M = p([]);
77
- function N(e) {
78
- M.value = Array.from({ length: O.value }, (t, n) => e[n] ?? "");
87
+ let U = _([]), W = _(null), G = _([]);
88
+ function K(e) {
89
+ G.value = Array.from({ length: B.value }, (t, n) => e[n] ?? "");
79
90
  }
80
- N(T.value), v([T, O], ([e, t]) => {
81
- (e !== M.value.join("") || M.value.length !== t) && N(e);
82
- });
83
- function P() {
84
- let e = M.value.join("");
85
- T.value = e, e.length === O.value && E("complete", e);
91
+ K(L.value), w([L, B], ([e, t]) => {
92
+ (e !== G.value.join("") || G.value.length !== t) && K(e);
93
+ }), w(B, (e) => {
94
+ U.value.length = e;
95
+ }, { flush: "post" });
96
+ function q() {
97
+ let e = G.value.join("");
98
+ L.value = e, e.length === B.value && R("complete", e);
86
99
  }
87
- function F(e, t) {
88
- let n = D.value, r = n.findIndex((e) => e.type === "slot" && e.slotIndex === t);
100
+ function J(e, t) {
101
+ let n = z.value, r = n.findIndex((e) => e.type === "slot" && e.slotIndex === t);
89
102
  for (; r > 0 && n[r - 1]?.type === "literal";) r--;
90
103
  let i = [...e], a = 0, o = null;
91
104
  for (let e of n.slice(r)) {
@@ -95,55 +108,79 @@ var y = [
95
108
  continue;
96
109
  }
97
110
  let t = "";
98
- for (; a < i.length && !t;) t = A(i[a] ?? ""), a++;
111
+ for (; a < i.length && !t;) t = H(i[a] ?? ""), a++;
99
112
  if (!t) break;
100
- M.value[e.slotIndex] = t, o = e.slotIndex;
113
+ G.value[e.slotIndex] = t, o = e.slotIndex;
101
114
  }
102
115
  return o;
103
116
  }
104
- function I(e, t) {
105
- let n = t.target, r = F(n.value, e);
117
+ function Y(e, t) {
118
+ let n = t.target, r = J(n.value, e);
106
119
  if (r === null) {
107
- M.value[e] = "", n.value = "", P();
120
+ G.value[e] = "", n.value = "", q();
108
121
  return;
109
122
  }
110
- n.value = M.value[e] ?? "", j.value[Math.min(r + 1, O.value - 1)]?.focus(), P();
123
+ n.value = G.value[e] ?? "", U.value[Math.min(r + 1, B.value - 1)]?.focus(), q();
111
124
  }
112
- function L(e, t) {
113
- t.key === "Backspace" && !M.value[e] && e > 0 ? (t.preventDefault(), M.value[e - 1] = "", P(), j.value[e - 1]?.focus()) : t.key === "ArrowLeft" && e > 0 ? (t.preventDefault(), j.value[e - 1]?.focus()) : t.key === "ArrowRight" && e < O.value - 1 && (t.preventDefault(), j.value[e + 1]?.focus());
125
+ function X(e, t) {
126
+ t.key === "Backspace" && !G.value[e] && e > 0 ? (t.preventDefault(), G.value[e - 1] = "", q(), U.value[e - 1]?.focus()) : t.key === "ArrowLeft" && e > 0 ? (t.preventDefault(), U.value[e - 1]?.focus()) : t.key === "ArrowRight" && e < B.value - 1 && (t.preventDefault(), U.value[e + 1]?.focus());
114
127
  }
115
- return (e, r) => (f(), s("div", {
116
- class: "v-otp v-control",
128
+ return m({
129
+ focus: (e) => {
130
+ let t = G.value.findIndex((e) => !e), n = t === -1 ? B.value - 1 : t;
131
+ U.value[n]?.focus(e);
132
+ },
133
+ select: () => {
134
+ (U.value.find((e) => e === document.activeElement) ?? U.value[0])?.select();
135
+ },
136
+ el: W
137
+ }), (e, i) => (g(), u("div", h({
138
+ ref_key: "rootEl",
139
+ ref: W
140
+ }, b(P), {
141
+ class: "v-input-otp v-control",
117
142
  role: "group",
118
- "aria-label": g(w),
119
- "data-invalid": l.invalid ? "" : void 0,
120
- "data-size": l.size,
121
- "data-compact": l.compact ? "" : void 0,
122
- "data-disabled": l.disabled ? "" : void 0
123
- }, [(f(!0), s(i, null, m(D.value, (e, a) => (f(), s(i, { key: a }, [e.type === "slot" ? (f(), s("input", {
143
+ "aria-label": b(N),
144
+ "aria-describedby": I.value,
145
+ "data-invalid": p.invalid ? "" : void 0,
146
+ "data-size": p.size,
147
+ "data-compact": p.compact ? "" : void 0,
148
+ "data-disabled": p.disabled ? "" : void 0,
149
+ "data-readonly": p.readonly ? "" : void 0
150
+ }), [d("div", D, [(g(!0), u(o, null, v(z.value, (e, r) => (g(), u(o, { key: r }, [e.type === "slot" ? (g(), u("input", {
124
151
  key: 0,
125
152
  ref_for: !0,
126
153
  ref: (t) => {
127
- j.value[e.slotIndex] = t;
154
+ U.value[e.slotIndex] = t;
128
155
  },
129
156
  type: "text",
130
- class: "v-otp-input",
131
- inputmode: l.format === "numeric" ? "numeric" : "text",
157
+ class: "v-input-otp-input",
158
+ inputmode: p.format === "numeric" ? "numeric" : "text",
132
159
  autocomplete: e.slotIndex === 0 ? "one-time-code" : "off",
133
- value: M.value[e.slotIndex],
134
- disabled: l.disabled,
135
- "aria-label": g(C).inputOTP.slot(e.slotIndex + 1, O.value),
136
- "aria-invalid": l.invalid || void 0,
137
- onInput: (t) => I(e.slotIndex, t),
138
- onKeydown: (t) => L(e.slotIndex, t),
139
- onFocus: r[0] ||= (e) => e.target.select()
140
- }, null, 40, b)) : (f(), s("span", x, [l.separatorIcon ? (f(), o(t, d({
160
+ value: G.value[e.slotIndex],
161
+ disabled: p.disabled,
162
+ readonly: p.readonly || void 0,
163
+ "aria-label": b(M).inputOTP.slot(e.slotIndex + 1, B.value),
164
+ "aria-invalid": p.invalid || void 0,
165
+ onInput: (t) => Y(e.slotIndex, t),
166
+ onKeydown: (t) => X(e.slotIndex, t),
167
+ onFocus: i[0] ||= (e) => e.target.select()
168
+ }, null, 40, O)) : (g(), u("span", k, [p.separatorIcon ? (g(), c(n, h({
141
169
  key: 0,
142
170
  ref_for: !0
143
- }, g(n)(l.separatorIcon)), null, 16)) : (f(), s(i, { key: 1 }, [c(h(e.char), 1)], 64))]))], 64))), 128))], 8, y));
171
+ }, b(t)(p.separatorIcon)), null, 16)) : (g(), u(o, { key: 1 }, [f(y(e.char), 1)], 64))]))], 64))), 128))]), p.hint ? (g(), c(r, {
172
+ key: 0,
173
+ id: b(F),
174
+ variant: "caption",
175
+ tone: "muted",
176
+ class: "v-input-otp-hint"
177
+ }, {
178
+ default: T(() => [f(y(p.hint), 1)]),
179
+ _: 1
180
+ }, 8, ["id"])) : l("", !0)], 16, E));
144
181
  }
145
182
  });
146
183
  //#endregion
147
- export { S as default };
184
+ export { A as default };
148
185
 
149
186
  //# 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\">\nimport { computed, ref, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { isDev } from '../../utils/env'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n// @keyboard @core\n/**\n * The row of boxes a one-time code is typed into, one character per box.\n *\n * The platform has no such control, so each box is a real `<input>` and the\n * JavaScript is what makes the row behave as a single field: typing a character moves\n * to the next box, Backspace on an empty one goes back, the arrows walk along, and a\n * code pasted anywhere is spread across the boxes. The first box carries\n * `autocomplete=\"one-time-code\"`, so a code filled in from an SMS or a password\n * manager is spread the same way.\n *\n * `pattern` is what cuts the row up: every `#` becomes a box, and every other\n * character becomes a separator shown between them, which is displayed but never part\n * of the value. Pasting understands those separators — a code copied in its formatted\n * shape, \"GT-123\" or \"123.456.789\", is consumed with them in place — and everything\n * else is filtered by `format`, which also forces capitals outside a numeric code so\n * that the value has one canonical form.\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?: 'numeric' | 'alpha' | 'alphanumeric'\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a\n * separator shown between the boxes without ever being part of the value —\n * `'GT-###'`, `'###.###.###'`. It wins over `length`.\n */\n pattern?: string\n /**\n * An icon drawn in place of EVERY separator of the pattern. It suits a template\n * whose separators are purely decorative, `'###-###'`, and should not be used with\n * one carrying meaningful text such as `'GT-###'`, which the icon would erase.\n */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: 'sm' | 'md' | 'lg'\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 /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design\n * system dictionary.\n */\n label?: 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 invalid: false,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /** Emitted the moment every box is filled, carrying the complete code. */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst inputs = ref<(HTMLInputElement | null)[]>([])\nconst digits = ref<string[]>([])\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and the model is NOT\n // rewritten to match: writing back here would feed the watcher below and the two\n // would keep correcting each other.\n digits.value = Array.from({ length: slotCount.value }, (_, i) => value[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\nfunction commit() {\n const code = digits.value.join('')\n model.value = code\n if (code.length === slotCount.value) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received\n * them. It walks the row: a separator swallows the incoming character when the two\n * match, which is what lets a code pasted in its formatted shape line up, and a box\n * takes the next character the format accepts.\n *\n * It returns the last box it filled, so the caller knows where to put the focus, and\n * `null` when nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n // Step back over the separators immediately before that box: pasting the whole\n // string onto the first box means pasting its prefix too, and \"GT-\" has to be\n // matched rather than treated as characters of the code.\n while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n const chars = [...raw]\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core — moving the focus forward on its own is what turns a row of\n// separate inputs into something that types like one field; spreading the characters\n// is the core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n // The same path serves a single keystroke and a pasted code: both are spread from\n // this box onwards.\n const lastFilled = distribute(el.value, slotIndex)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format.\n digits.value[slotIndex] = ''\n el.value = ''\n commit()\n return\n }\n el.value = digits.value[slotIndex] ?? ''\n inputs.value[Math.min(lastFilled + 1, slotCount.value - 1)]?.focus()\n commit()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n if (event.key === 'Backspace' && !digits.value[slotIndex] && slotIndex > 0) {\n event.preventDefault()\n digits.value[slotIndex - 1] = ''\n commit()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowLeft' && slotIndex > 0) {\n event.preventDefault()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowRight' && slotIndex < slotCount.value - 1) {\n event.preventDefault()\n inputs.value[slotIndex + 1]?.focus()\n }\n}\n</script>\n\n<template>\n <div\n class=\"v-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n >\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n :ref=\"\n (el) => {\n inputs[cell.slotIndex] = el as HTMLInputElement | null\n }\n \"\n type=\"text\"\n class=\"v-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 :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"($event.target as HTMLInputElement).select()\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-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</template>\n\n<style>\n@layer vectis.components {\n .v-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 --otp-font-size: var(--vectis-font-size-lg);\n\n display: inline-flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-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(--otp-font-size);\n transition: border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The focused box appears to have a two-pixel border, exactly as in VInput and\n VTextarea: its own 1px border plus a 1px shadow of the same colour just outside\n it. The transparent outline is the safety net for Windows forced colours, which\n drop box-shadows entirely. */\n .v-otp-input:focus-visible {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n .v-otp[data-invalid] .v-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-otp[data-invalid] .v-otp-input:focus-visible {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-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(--otp-font-size);\n user-select: none;\n }\n\n /* A disabled row greys out through the colour tokens and never through opacity, the\n same treatment as VInput. */\n .v-otp[data-disabled] .v-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-otp[data-disabled] .v-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-otp[data-size='sm'] {\n --otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-otp[data-size='lg'] {\n --otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"VInputOTP.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VInputOTP/VInputOTP.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @keyboard @core\n/**\n * The row of boxes a one-time code is typed into, one character per box.\n *\n * The platform has no such control, so each box is a real `<input>` and the JS is what makes\n * the row behave as one field: a character moves to the next box, Backspace on an empty one\n * goes back, the arrows walk along, and a code pasted anywhere is spread across them. The\n * first box carries `autocomplete=\"one-time-code\"`, so a code arriving from an SMS or a\n * password manager is spread the same way.\n *\n * `pattern` cuts the row up: every `#` is a box, every other character a decorative literal\n * shown between them and never part of the value. Pasting understands those literals — a\n * code copied formatted, `GT-123`, is consumed with them in place — and `format` filters the\n * rest, forcing capitals outside a numeric code so the value has one canonical form.\n */\n\nimport { computed, ref, useAttrs, useId, watch } from 'vue'\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\n/** The height of the boxes: 32, 40 or 48 pixels. */\nexport type InputOTPSize = 'sm' | 'md' | 'lg'\n\n/** Which characters the code is made of. */\nexport type InputOTPFormat = 'numeric' | 'alpha' | 'alphanumeric'\n\ninterface InputOTPProps {\n /** How many boxes the code has. It is ignored as soon as a `pattern` is given. */\n length?: number\n /**\n * Which characters the code is made of. It filters what can be typed or pasted, and\n * decides which keyboard a phone offers.\n */\n format?: InputOTPFormat\n /**\n * The shape of the code: each `#` is a box to fill, and every other character is a\n * separator shown between the boxes without ever being part of the value —\n * `'GT-###'`, `'###.###.###'`. It wins over `length`.\n */\n pattern?: string\n /**\n * An icon drawn in place of EVERY separator of the pattern. It suits a template\n * whose separators are purely decorative, `'###-###'`, and should not be used with\n * one carrying meaningful text such as `'GT-###'`, which the icon would erase.\n */\n separatorIcon?: IconSource\n /** The size of the boxes: 32, 40 or 48 pixels. */\n size?: InputOTPSize\n /** Takes 4px off the boxes, leaving the text and the icons as they are. */\n compact?: boolean\n /** Makes every box unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the code without letting it be changed. The boxes keep their focus and the code\n * can still be selected and copied, which is what separates it from `disabled`.\n */\n readonly?: boolean\n /** Marks the code as wrong, which colours the boxes and tells assistive technology so. */\n invalid?: boolean\n /**\n * What screen readers announce for the row as a whole. It falls back to the design\n * system dictionary.\n *\n * Unlike the `label` of VInput and the other text fields, NOTHING is rendered from it:\n * a row of boxes carries its own instructions above it, written by the page. Use `hint`\n * for a line the reader can see.\n */\n label?: string\n /**\n * A line of help under the boxes — where the code was sent, how long it lasts. It is\n * tied to the row for assistive technology, so it is read out along with the label.\n */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<InputOTPProps>(), {\n length: 6,\n format: 'numeric',\n pattern: undefined,\n separatorIcon: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n label: undefined,\n hint: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.inputOTP.label)\n\n// @a11y\n/*\n * The root IS the group, so the consumer's attributes belong on it — but they are bound\n * BEFORE the two the component decides itself. `aria-describedby` is a list, and the hint\n * is appended to whatever the consumer already pointed at instead of replacing it; left\n * to the fallthrough, their value would land last and take the hint out of the\n * announcement with nothing to show for it.\n */\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\n// No `useFieldIds` here: the row is named by `aria-label` and renders no `<label>`, so the\n// field id that composable generates would have nothing to point it at.\nconst hintId = useId()\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The code as one string, without the separators: a `GT-###` template still yields three\n * characters. It is empty to begin with, and shorter than the full length while it is being\n * typed.\n */\nconst model = defineModel<string>({ default: '' })\n\nconst emit = defineEmits<{\n /** Emitted the moment every box is filled, carrying the complete code. */\n complete: [code: string]\n}>()\n\ntype Cell = { type: 'slot'; slotIndex: number } | { type: 'literal'; char: string }\n\nconst cells = computed<Cell[]>(() => {\n if (props.pattern?.includes('#')) {\n let slotIndex = 0\n return [...props.pattern].map((char): Cell =>\n char === '#' ? { type: 'slot', slotIndex: slotIndex++ } : { type: 'literal', char },\n )\n }\n return Array.from({ length: props.length }, (_, i) => ({ type: 'slot', slotIndex: i }))\n})\nconst slotCount = computed(() => cells.value.filter((cell) => cell.type === 'slot').length)\n\n// @devwarn\nif (isDev) {\n if (props.pattern && !props.pattern.includes('#'))\n console.warn(\"[VInputOTP] pattern without '#' — falling back to `length`.\")\n}\n\nconst filters: Record<NonNullable<InputOTPProps['format']>, RegExp> = {\n numeric: /[^0-9]/g,\n alpha: /[^A-Z]/g,\n alphanumeric: /[^A-Z0-9]/g,\n}\n\n/**\n * Keeps only what the format allows. Outside a numeric code the text is put in\n * capitals first, so that the value has a single canonical form whichever case the\n * reader typed or pasted.\n */\nfunction sanitize(text: string) {\n const upper = props.format === 'numeric' ? text : text.toUpperCase()\n return upper.replace(filters[props.format], '')\n}\n\nconst inputs = ref<(HTMLInputElement | null)[]>([])\nconst rootEl = ref<HTMLElement | null>(null)\nconst digits = ref<string[]>([])\n\nfunction syncFromModel(value: string) {\n // A value longer than the row is simply shown cut short, and the model is NOT\n // rewritten to match: writing back here would feed the watcher below and the two\n // would keep correcting each other.\n digits.value = Array.from({ length: slotCount.value }, (_, i) => value[i] ?? '')\n}\nsyncFromModel(model.value)\nwatch([model, slotCount], ([value, count]) => {\n if (value !== digits.value.join('') || digits.value.length !== count) syncFromModel(value)\n})\n\n/*\n * TRAP — the list of boxes is trimmed AFTER the render, never before it. A box that\n * disappears calls its function ref with `null` while it is being unmounted, at its OLD\n * position: trimmed in a `pre` watcher, that late assignment would stretch the array back\n * out, and a pattern that shortened would keep its departed boxes as trailing entries.\n */\nwatch(\n slotCount,\n (count) => {\n inputs.value.length = count\n },\n { flush: 'post' },\n)\n\nfunction commit() {\n const code = digits.value.join('')\n model.value = code\n if (code.length === slotCount.value) emit('complete', code)\n}\n\n// @core\n/**\n * Spreads a run of characters across the boxes, starting from the one that received\n * them. It walks the row: a separator swallows the incoming character when the two\n * match, which is what lets a code pasted in its formatted shape line up, and a box\n * takes the next character the format accepts.\n *\n * It returns the last box it filled, so the caller knows where to put the focus, and\n * `null` when nothing in the text was usable.\n */\nfunction distribute(raw: string, startSlot: number): number | null {\n const allCells = cells.value\n let start = allCells.findIndex((cell) => cell.type === 'slot' && cell.slotIndex === startSlot)\n // Step back over the separators immediately before that box: pasting the whole\n // string onto the first box means pasting its prefix too, and \"GT-\" has to be\n // matched rather than treated as characters of the code.\n while (start > 0 && allCells[start - 1]?.type === 'literal') start--\n const chars = [...raw]\n let charIndex = 0\n let lastFilled: number | null = null\n for (const cell of allCells.slice(start)) {\n if (charIndex >= chars.length) break\n if (cell.type === 'literal') {\n if (chars[charIndex]?.toUpperCase() === cell.char.toUpperCase()) charIndex++\n continue\n }\n let char = ''\n while (charIndex < chars.length && !char) {\n char = sanitize(chars[charIndex] ?? '')\n charIndex++\n }\n if (!char) break\n digits.value[cell.slotIndex] = char\n lastFilled = cell.slotIndex\n }\n return lastFilled\n}\n\n// @keyboard @core — moving the focus forward on its own is what turns a row of\n// separate inputs into something that types like one field; spreading the characters\n// is the core behaviour underneath it.\nfunction onInput(slotIndex: number, event: Event) {\n const el = event.target as HTMLInputElement\n // The same path serves a single keystroke and a pasted code: both are spread from\n // this box onwards.\n const lastFilled = distribute(el.value, slotIndex)\n if (lastFilled === null) {\n // Either the box was emptied, or nothing typed was valid for this format.\n digits.value[slotIndex] = ''\n el.value = ''\n commit()\n return\n }\n el.value = digits.value[slotIndex] ?? ''\n inputs.value[Math.min(lastFilled + 1, slotCount.value - 1)]?.focus()\n commit()\n}\n\n// @keyboard\nfunction onKeydown(slotIndex: number, event: KeyboardEvent) {\n if (event.key === 'Backspace' && !digits.value[slotIndex] && slotIndex > 0) {\n event.preventDefault()\n digits.value[slotIndex - 1] = ''\n commit()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowLeft' && slotIndex > 0) {\n event.preventDefault()\n inputs.value[slotIndex - 1]?.focus()\n } else if (event.key === 'ArrowRight' && slotIndex < slotCount.value - 1) {\n event.preventDefault()\n inputs.value[slotIndex + 1]?.focus()\n }\n}\n/*\n * The same trio every other field of the size scale exposes. `focus` goes to the FIRST\n * EMPTY box rather than to the first box outright, which is where a reader resuming a\n * half-entered code expects to land.\n */\ndefineExpose({\n /** Moves the focus to the first empty box, or to the last one when the code is complete. */\n focus: (options?: FocusOptions) => {\n const empty = digits.value.findIndex((d) => !d)\n const target = empty === -1 ? slotCount.value - 1 : empty\n inputs.value[target]?.focus(options)\n },\n /** Selects the box the focus is on, as clicking into one already does. */\n select: () => {\n const active = inputs.value.find((el) => el === document.activeElement)\n ;(active ?? inputs.value[0])?.select()\n },\n /** The row itself, for what neither of the two above covers. */\n el: rootEl,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-bind=\"attrs\"\n class=\"v-input-otp v-control\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :data-invalid=\"invalid ? '' : undefined\"\n :data-size=\"size\"\n :data-compact=\"compact ? '' : undefined\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <div class=\"v-input-otp-boxes\">\n <template v-for=\"(cell, i) in cells\" :key=\"i\">\n <input\n v-if=\"cell.type === 'slot'\"\n :ref=\"\n (el) => {\n inputs[cell.slotIndex] = el as HTMLInputElement | null\n }\n \"\n type=\"text\"\n class=\"v-input-otp-input\"\n :inputmode=\"format === 'numeric' ? 'numeric' : 'text'\"\n :autocomplete=\"cell.slotIndex === 0 ? 'one-time-code' : 'off'\"\n :value=\"digits[cell.slotIndex]\"\n :disabled=\"disabled\"\n :readonly=\"readonly || undefined\"\n :aria-label=\"m.inputOTP.slot(cell.slotIndex + 1, slotCount)\"\n :aria-invalid=\"invalid || undefined\"\n @input=\"onInput(cell.slotIndex, $event)\"\n @keydown=\"onKeydown(cell.slotIndex, $event)\"\n @focus=\"($event.target as HTMLInputElement).select()\"\n />\n <!-- A separator from the pattern: shown, never focusable, and never part of the\n value. It is hidden from screen readers, each box already announcing its\n own position in the code. -->\n <span v-else class=\"v-input-otp-literal\" aria-hidden=\"true\">\n <VIcon v-if=\"separatorIcon\" v-bind=\"iconProps(separatorIcon)\" />\n <template v-else>{{ cell.char }}</template>\n </span>\n </template>\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-otp-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input-otp {\n /*\n * The heights and the icon context come from the shared v-control class\n * (styles/control-size.css). The type is the one thing kept local, and set one or\n * two steps above the other fields: a single character has a whole square to\n * itself, and at the usual field size it would look lost in it.\n */\n --input-otp-font-size: var(--vectis-font-size-lg);\n\n /* A column, so the hint sits under the boxes rather than beside them; the row itself\n is the box below. `inline-flex` keeps the component's own width, and the start\n alignment stops the hint from stretching the row to its own length. */\n display: inline-flex;\n flex-direction: column;\n align-items: start;\n gap: var(--vectis-space-1);\n }\n\n .v-input-otp-boxes {\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n }\n\n .v-input-otp-input {\n /* The boxes are square: reading the same variable for both dimensions is what\n makes the size and the density scale them together. */\n width: var(--control-height);\n height: var(--control-height);\n text-align: center;\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--vectis-color-border-strong);\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n transition: border-color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* The focused box appears to have a two-pixel border, exactly as in VInput and\n VTextarea: its own 1px border plus a 1px shadow of the same colour just outside\n it. The transparent outline is the safety net for Windows forced colours, which\n drop box-shadows entirely. */\n .v-input-otp-input:focus-visible {\n border-color: var(--vectis-color-accent);\n box-shadow: 0 0 0 1px var(--vectis-color-accent);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /* A read-only row takes the sunken background of a read-only VInput, and the state is\n read from [data-readonly] rather than from `:read-only`, which the browser also matches\n on a disabled box. It is declared BEFORE the invalid and disabled blocks, all three\n weighing (0,2,0), so those still win over it. */\n .v-input-otp[data-readonly] .v-input-otp-input {\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input {\n border-color: var(--vectis-color-danger);\n }\n\n .v-input-otp[data-invalid] .v-input-otp-input:focus-visible {\n box-shadow: 0 0 0 1px var(--vectis-color-danger);\n }\n\n .v-input-otp-literal {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--vectis-color-text-muted);\n font-family: var(--vectis-text-family-code);\n font-size: var(--input-otp-font-size);\n user-select: none;\n }\n\n /* A disabled row greys out through the colour tokens and never through opacity, the\n same treatment as VInput. */\n .v-input-otp[data-disabled] .v-input-otp-input {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n cursor: not-allowed;\n }\n\n .v-input-otp[data-disabled] .v-input-otp-literal,\n .v-input-otp[data-disabled] .v-input-otp-hint {\n color: var(--vectis-color-text-subtle);\n }\n\n /* Of the whole size scale, only the raised type is restated here; the dimensions\n themselves come from v-control. */\n .v-input-otp[data-size='sm'] {\n --input-otp-font-size: var(--vectis-font-size-md);\n }\n\n .v-input-otp[data-size='lg'] {\n --input-otp-font-size: var(--vectis-font-size-xl);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-otp-input {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}