vectis-ui 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/README.md +3 -3
  2. package/dist/components/VAccordion/VAccordionItem.d.ts +4 -6
  3. package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
  4. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js +21 -19
  5. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/VAccordion/context.d.ts +6 -1
  7. package/dist/components/VAccordion/context.js.map +1 -1
  8. package/dist/components/VAvatar/VAvatar.css +1 -1
  9. package/dist/components/VAvatar/VAvatar.d.ts +5 -3
  10. package/dist/components/VAvatar/VAvatar.js.map +1 -1
  11. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +53 -39
  12. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  13. package/dist/components/VAvatar/VAvatarGroup.d.ts +12 -3
  14. package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
  15. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +27 -20
  16. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  17. package/dist/components/VAvatar/context.d.ts +2 -2
  18. package/dist/components/VAvatar/context.js.map +1 -1
  19. package/dist/components/VBadge/VBadge.css +1 -1
  20. package/dist/components/VBadge/VBadge.d.ts +8 -4
  21. package/dist/components/VBadge/VBadge.js.map +1 -1
  22. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +6 -3
  23. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  24. package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +5 -4
  25. package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
  26. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +8 -3
  27. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  28. package/dist/components/VButton/VButton.css +1 -1
  29. package/dist/components/VButton/VButton.d.ts +4 -1
  30. package/dist/components/VButton/VButton.js.map +1 -1
  31. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +8 -7
  32. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  33. package/dist/components/VButton/VButtonGroup.js.map +1 -1
  34. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  35. package/dist/components/VCalendar/VCalendar.css +1 -1
  36. package/dist/components/VCalendar/VCalendar.d.ts +15 -6
  37. package/dist/components/VCalendar/VCalendar.js.map +1 -1
  38. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +144 -126
  39. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  40. package/dist/components/VCalendar/VCalendarEvent.css +1 -1
  41. package/dist/components/VCalendar/VCalendarEvent.d.ts +1 -1
  42. package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
  43. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  44. package/dist/components/VCalendar/VCalendarMonth.css +1 -1
  45. package/dist/components/VCalendar/VCalendarMonth.d.ts +1 -1
  46. package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
  47. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +99 -90
  48. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
  49. package/dist/components/VCalendar/VCalendarTimeGrid.css +1 -1
  50. package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +3 -3
  51. package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
  52. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +256 -246
  53. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  54. package/dist/components/VCalendar/VCalendarYear.css +1 -1
  55. package/dist/components/VCalendar/VCalendarYear.d.ts +8 -1
  56. package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
  57. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js +39 -32
  58. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  59. package/dist/components/VCalendar/color.d.ts +1 -1
  60. package/dist/components/VCalendar/color.js.map +1 -1
  61. package/dist/components/VCalendar/edgeStep.d.ts +3 -3
  62. package/dist/components/VCalendar/edgeStep.js +5 -0
  63. package/dist/components/VCalendar/edgeStep.js.map +1 -1
  64. package/dist/components/VCalendar/gesture.d.ts +10 -9
  65. package/dist/components/VCalendar/gesture.js +60 -45
  66. package/dist/components/VCalendar/gesture.js.map +1 -1
  67. package/dist/components/VCalendar/keyboard.d.ts +22 -5
  68. package/dist/components/VCalendar/keyboard.js +17 -16
  69. package/dist/components/VCalendar/keyboard.js.map +1 -1
  70. package/dist/components/VCalendar/layout.d.ts +47 -37
  71. package/dist/components/VCalendar/layout.js +47 -37
  72. package/dist/components/VCalendar/layout.js.map +1 -1
  73. package/dist/components/VCalendar/timeGrid.d.ts +4 -4
  74. package/dist/components/VCalendar/timeGrid.js +6 -6
  75. package/dist/components/VCalendar/timeGrid.js.map +1 -1
  76. package/dist/components/VCalendar/types.d.ts +5 -5
  77. package/dist/components/VCarousel/VCarousel.css +1 -1
  78. package/dist/components/VCarousel/VCarousel.d.ts +59 -38
  79. package/dist/components/VCarousel/VCarousel.js.map +1 -1
  80. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +161 -132
  81. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  82. package/dist/components/VCarousel/VCarouselItem.d.ts +1 -1
  83. package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
  84. package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  85. package/dist/components/VCarousel/context.d.ts +1 -1
  86. package/dist/components/VCarousel/context.js.map +1 -1
  87. package/dist/components/VCheckbox/VCheckbox.css +1 -1
  88. package/dist/components/VCheckbox/VCheckbox.d.ts +2 -2
  89. package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
  90. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +10 -8
  91. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  92. package/dist/components/VChip/VChip.css +1 -1
  93. package/dist/components/VChip/VChip.d.ts +7 -2
  94. package/dist/components/VChip/VChip.js.map +1 -1
  95. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +69 -55
  96. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  97. package/dist/components/VCombobox/VCombobox.css +1 -1
  98. package/dist/components/VCombobox/VCombobox.d.ts +50 -36
  99. package/dist/components/VCombobox/VCombobox.js.map +1 -1
  100. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +167 -161
  101. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  102. package/dist/components/VCombobox/VComboboxGroup.css +1 -1
  103. package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
  104. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  105. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  106. package/dist/components/VCombobox/infiniteScroll.d.ts +12 -3
  107. package/dist/components/VCombobox/infiniteScroll.js +18 -12
  108. package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
  109. package/dist/components/VDataTable/VDataTable.css +1 -1
  110. package/dist/components/VDataTable/VDataTable.d.ts +55 -24
  111. package/dist/components/VDataTable/VDataTable.js.map +1 -1
  112. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +192 -155
  113. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  114. package/dist/components/VDateInput/VDateInput.css +1 -1
  115. package/dist/components/VDateInput/VDateInput.d.ts +15 -12
  116. package/dist/components/VDateInput/VDateInput.js.map +1 -1
  117. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +180 -160
  118. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  119. package/dist/components/VDateInput/mask.d.ts +95 -0
  120. package/dist/components/VDateInput/mask.js +118 -0
  121. package/dist/components/VDateInput/mask.js.map +1 -0
  122. package/dist/components/VDatePicker/VDatePicker.css +1 -1
  123. package/dist/components/VDatePicker/VDatePicker.d.ts +6 -5
  124. package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
  125. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +204 -173
  126. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  127. package/dist/components/VDatePicker/keyboard.d.ts +3 -3
  128. package/dist/components/VDatePicker/keyboard.js.map +1 -1
  129. package/dist/components/VDatePicker/names.d.ts +34 -0
  130. package/dist/components/VDatePicker/names.js +42 -0
  131. package/dist/components/VDatePicker/names.js.map +1 -0
  132. package/dist/components/VDialog/VDialog.css +1 -1
  133. package/dist/components/VDialog/VDialog.d.ts +2 -2
  134. package/dist/components/VDialog/VDialog.js.map +1 -1
  135. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +63 -41
  136. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  137. package/dist/components/VFileInput/VFileInput.d.ts +38 -25
  138. package/dist/components/VFileInput/VFileInput.js.map +1 -1
  139. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +74 -71
  140. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  141. package/dist/components/VFileInput/truncate.d.ts +4 -4
  142. package/dist/components/VFileInput/truncate.js.map +1 -1
  143. package/dist/components/VFilePicker/VFilePicker.css +1 -1
  144. package/dist/components/VFilePicker/VFilePicker.d.ts +54 -37
  145. package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
  146. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +152 -135
  147. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  148. package/dist/components/VFilePicker/fileKind.d.ts +2 -2
  149. package/dist/components/VFilePicker/fileKind.js.map +1 -1
  150. package/dist/components/VHotkeys/VHotkeys.css +1 -1
  151. package/dist/components/VHotkeys/VHotkeys.d.ts +10 -8
  152. package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
  153. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +22 -20
  154. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
  155. package/dist/components/VHotkeys/platform.d.ts +9 -7
  156. package/dist/components/VHotkeys/platform.js +41 -30
  157. package/dist/components/VHotkeys/platform.js.map +1 -1
  158. package/dist/components/VIcon/VIcon.css +1 -1
  159. package/dist/components/VIcon/VIcon.d.ts +6 -6
  160. package/dist/components/VIcon/VIcon.js.map +1 -1
  161. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  162. package/dist/components/VIcon/iconProps.d.ts +4 -4
  163. package/dist/components/VIcon/iconProps.js.map +1 -1
  164. package/dist/components/VIcon/icons/index.d.ts +1 -1
  165. package/dist/components/VIcon/icons/index.js.map +1 -1
  166. package/dist/components/VIcon/icons/names.d.ts +1 -1
  167. package/dist/components/VIcon/icons/names.js.map +1 -1
  168. package/dist/components/VIcon/resolver.d.ts +3 -3
  169. package/dist/components/VIcon/resolver.js +23 -20
  170. package/dist/components/VIcon/resolver.js.map +1 -1
  171. package/dist/components/VIcon/types.d.ts +4 -4
  172. package/dist/components/VIconButton/VIconButton.d.ts +4 -4
  173. package/dist/components/VIconButton/VIconButton.js.map +1 -1
  174. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  175. package/dist/components/VInput/VInput.d.ts +16 -8
  176. package/dist/components/VInput/VInput.js.map +1 -1
  177. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +22 -16
  178. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  179. package/dist/components/VInput/VInputGroup.css +1 -1
  180. package/dist/components/VInput/VInputGroup.js.map +1 -1
  181. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +43 -28
  182. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  183. package/dist/components/VInputOTP/VInputOTP.css +1 -1
  184. package/dist/components/VInputOTP/VInputOTP.d.ts +3 -3
  185. package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
  186. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +152 -85
  187. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
  188. package/dist/components/VMenu/VMenu.d.ts +4 -4
  189. package/dist/components/VMenu/VMenu.js.map +1 -1
  190. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  191. package/dist/components/VMenu/VMenuGroup.css +1 -1
  192. package/dist/components/VMenu/VMenuGroup.js.map +1 -1
  193. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js +18 -11
  194. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  195. package/dist/components/VMenu/VMenuItem.css +1 -1
  196. package/dist/components/VMenu/VMenuItem.d.ts +6 -6
  197. package/dist/components/VMenu/VMenuItem.js.map +1 -1
  198. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +83 -69
  199. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  200. package/dist/components/VMenu/VMenuPanel.css +1 -1
  201. package/dist/components/VMenu/VMenuPanel.d.ts +2 -2
  202. package/dist/components/VMenu/VMenuPanel.js.map +1 -1
  203. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +56 -55
  204. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  205. package/dist/components/VMenu/context.d.ts +2 -2
  206. package/dist/components/VMenu/context.js.map +1 -1
  207. package/dist/components/VPagination/VPagination.css +1 -1
  208. package/dist/components/VPagination/VPagination.d.ts +22 -9
  209. package/dist/components/VPagination/VPagination.js.map +1 -1
  210. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +67 -57
  211. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  212. package/dist/components/VPopover/VPopover.css +1 -1
  213. package/dist/components/VPopover/VPopover.d.ts +1 -1
  214. package/dist/components/VPopover/VPopover.js.map +1 -1
  215. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  216. package/dist/components/VProgressCircular/VProgressCircular.css +1 -1
  217. package/dist/components/VProgressCircular/VProgressCircular.d.ts +12 -7
  218. package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
  219. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
  220. package/dist/components/VProgressLinear/VProgressLinear.css +1 -1
  221. package/dist/components/VProgressLinear/VProgressLinear.d.ts +15 -10
  222. package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
  223. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  224. package/dist/components/VRadio/VRadio.css +1 -1
  225. package/dist/components/VRadio/VRadio.js.map +1 -1
  226. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +3 -3
  227. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  228. package/dist/components/VSeparator/VSeparator.d.ts +1 -1
  229. package/dist/components/VSeparator/VSeparator.js.map +1 -1
  230. package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  231. package/dist/components/VSideNavigation/VSideNavigation.d.ts +2 -2
  232. package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
  233. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +1 -1
  234. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
  235. package/dist/components/VSideNavigation/VSideNavigationGroup.css +1 -1
  236. package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
  237. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  238. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  239. package/dist/components/VSideNavigation/VSideNavigationItem.css +1 -1
  240. package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +12 -13
  241. package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
  242. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +87 -68
  243. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  244. package/dist/components/VSkeletonLoader/VSkeletonLoader.css +1 -1
  245. package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +10 -7
  246. package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
  247. package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js +5 -5
  248. package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  249. package/dist/components/VSlider/VSlider.css +1 -1
  250. package/dist/components/VSlider/VSlider.d.ts +21 -7
  251. package/dist/components/VSlider/VSlider.js.map +1 -1
  252. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +159 -140
  253. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  254. package/dist/components/VSnackbar/VSnackbar.css +1 -1
  255. package/dist/components/VSnackbar/VSnackbar.d.ts +4 -4
  256. package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
  257. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +85 -59
  258. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  259. package/dist/components/VSnackbar/state.d.ts +7 -7
  260. package/dist/components/VSnackbar/state.js +13 -11
  261. package/dist/components/VSnackbar/state.js.map +1 -1
  262. package/dist/components/VSpinner/VSpinner.css +1 -1
  263. package/dist/components/VSpinner/VSpinner.js.map +1 -1
  264. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
  265. package/dist/components/VSwitch/VSwitch.css +1 -1
  266. package/dist/components/VSwitch/VSwitch.d.ts +1 -1
  267. package/dist/components/VSwitch/VSwitch.js.map +1 -1
  268. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +4 -4
  269. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  270. package/dist/components/VTabs/VTab.css +1 -1
  271. package/dist/components/VTabs/VTab.d.ts +4 -0
  272. package/dist/components/VTabs/VTab.js.map +1 -1
  273. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +9 -8
  274. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
  275. package/dist/components/VTabs/VTabs.css +1 -1
  276. package/dist/components/VTabs/VTabs.d.ts +17 -7
  277. package/dist/components/VTabs/VTabs.js.map +1 -1
  278. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +84 -77
  279. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  280. package/dist/components/VTextarea/VTextarea.d.ts +8 -8
  281. package/dist/components/VTextarea/VTextarea.js.map +1 -1
  282. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +71 -69
  283. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  284. package/dist/components/VTimeInput/VTimeInput.css +1 -1
  285. package/dist/components/VTimeInput/VTimeInput.d.ts +21 -12
  286. package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
  287. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +230 -206
  288. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  289. package/dist/components/VTimeInput/search.d.ts +17 -2
  290. package/dist/components/VTimeInput/search.js +19 -9
  291. package/dist/components/VTimeInput/search.js.map +1 -1
  292. package/dist/components/VTimePicker/VTimePicker.css +1 -1
  293. package/dist/components/VTimePicker/VTimePicker.d.ts +8 -7
  294. package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
  295. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +128 -106
  296. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  297. package/dist/components/VTimePicker/limits.d.ts +11 -4
  298. package/dist/components/VTimePicker/limits.js +3 -2
  299. package/dist/components/VTimePicker/limits.js.map +1 -1
  300. package/dist/components/VToast/VToast.css +1 -1
  301. package/dist/components/VToast/VToast.js.map +1 -1
  302. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +33 -37
  303. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  304. package/dist/components/VToast/VToaster.js.map +1 -1
  305. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +91 -61
  306. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
  307. package/dist/components/VToast/state.d.ts +8 -6
  308. package/dist/components/VToast/state.js +15 -13
  309. package/dist/components/VToast/state.js.map +1 -1
  310. package/dist/components/VToggle/VToggle.css +1 -1
  311. package/dist/components/VToggle/VToggle.d.ts +9 -4
  312. package/dist/components/VToggle/VToggle.js.map +1 -1
  313. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +2 -1
  314. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
  315. package/dist/components/VToggle/VToggleItem.d.ts +4 -0
  316. package/dist/components/VToggle/VToggleItem.js.map +1 -1
  317. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js +5 -5
  318. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  319. package/dist/components/VTooltip/VTooltip.css +1 -1
  320. package/dist/components/VTooltip/VTooltip.d.ts +17 -10
  321. package/dist/components/VTooltip/VTooltip.js.map +1 -1
  322. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +46 -26
  323. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  324. package/dist/components/VTypography/VTypography.css +1 -1
  325. package/dist/components/VTypography/VTypography.d.ts +6 -4
  326. package/dist/components/VTypography/VTypography.js.map +1 -1
  327. package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
  328. package/dist/composables/useAriaLabel.js +1 -1
  329. package/dist/composables/useAriaLabel.js.map +1 -1
  330. package/dist/composables/useClearable.d.ts +2 -2
  331. package/dist/composables/useClearable.js.map +1 -1
  332. package/dist/composables/useDetailsOpen.d.ts +3 -3
  333. package/dist/composables/useDetailsOpen.js +14 -12
  334. package/dist/composables/useDetailsOpen.js.map +1 -1
  335. package/dist/composables/useFieldIds.d.ts +2 -1
  336. package/dist/composables/useFieldIds.js +6 -5
  337. package/dist/composables/useFieldIds.js.map +1 -1
  338. package/dist/composables/useFieldPanel.d.ts +2 -2
  339. package/dist/composables/useFieldPanel.js +47 -37
  340. package/dist/composables/useFieldPanel.js.map +1 -1
  341. package/dist/composables/useFileDrop.js +3 -3
  342. package/dist/composables/useFileDrop.js.map +1 -1
  343. package/dist/composables/useFileField.d.ts +1 -1
  344. package/dist/composables/useFileField.js +62 -41
  345. package/dist/composables/useFileField.js.map +1 -1
  346. package/dist/composables/useIconClickHandlers.d.ts +3 -3
  347. package/dist/composables/useIconClickHandlers.js +14 -13
  348. package/dist/composables/useIconClickHandlers.js.map +1 -1
  349. package/dist/composables/useInertLink.js +12 -10
  350. package/dist/composables/useInertLink.js.map +1 -1
  351. package/dist/composables/useLiveAnnouncer.d.ts +5 -0
  352. package/dist/composables/useLiveAnnouncer.js +23 -0
  353. package/dist/composables/useLiveAnnouncer.js.map +1 -0
  354. package/dist/composables/useMaskedField.d.ts +5 -5
  355. package/dist/composables/useMaskedField.js.map +1 -1
  356. package/dist/composables/usePopover.d.ts +3 -3
  357. package/dist/composables/usePopover.js +6 -4
  358. package/dist/composables/usePopover.js.map +1 -1
  359. package/dist/composables/useProgressValue.js +11 -2
  360. package/dist/composables/useProgressValue.js.map +1 -1
  361. package/dist/composables/useRootAttrs.d.ts +1 -1
  362. package/dist/composables/useRootAttrs.js.map +1 -1
  363. package/dist/composables/useSlotNodes.d.ts +18 -0
  364. package/dist/composables/useSlotNodes.js +13 -0
  365. package/dist/composables/useSlotNodes.js.map +1 -0
  366. package/dist/composables/useTextLimit.d.ts +2 -1
  367. package/dist/composables/useTextLimit.js +2 -2
  368. package/dist/composables/useTextLimit.js.map +1 -1
  369. package/dist/composables/useTimer.d.ts +5 -0
  370. package/dist/composables/useTimer.js +22 -11
  371. package/dist/composables/useTimer.js.map +1 -1
  372. package/dist/i18n/en.js +1 -1
  373. package/dist/i18n/en.js.map +1 -1
  374. package/dist/i18n/fr.js +14 -14
  375. package/dist/i18n/fr.js.map +1 -1
  376. package/dist/i18n/state.d.ts +7 -7
  377. package/dist/i18n/state.js +37 -28
  378. package/dist/i18n/state.js.map +1 -1
  379. package/dist/i18n/types.d.ts +12 -12
  380. package/dist/icons.d.ts +2 -2
  381. package/dist/index.d.ts +12 -11
  382. package/dist/styles.css +1 -1
  383. package/dist/tokens/css.d.ts +1 -1
  384. package/dist/tokens/css.js.map +1 -1
  385. package/dist/tokens/index.d.ts +6 -0
  386. package/dist/tokens/semantic.d.ts +6 -0
  387. package/dist/tokens/semantic.js +66 -60
  388. package/dist/tokens/semantic.js.map +1 -1
  389. package/dist/tokens/themes/dark.js +3 -3
  390. package/dist/tokens/themes/dark.js.map +1 -1
  391. package/dist/tokens.json +130 -66
  392. package/dist/types.d.ts +1 -1
  393. package/dist/utils/arrowNav.d.ts +4 -1
  394. package/dist/utils/arrowNav.js +7 -7
  395. package/dist/utils/arrowNav.js.map +1 -1
  396. package/dist/utils/chip.d.ts +1 -1
  397. package/dist/utils/chip.js.map +1 -1
  398. package/dist/utils/clock.d.ts +113 -0
  399. package/dist/utils/clock.js +84 -0
  400. package/dist/utils/clock.js.map +1 -0
  401. package/dist/utils/css.d.ts +6 -4
  402. package/dist/utils/css.js +2 -2
  403. package/dist/utils/css.js.map +1 -1
  404. package/dist/utils/date.d.ts +7 -102
  405. package/dist/utils/date.js +33 -160
  406. package/dist/utils/date.js.map +1 -1
  407. package/dist/utils/direction.d.ts +1 -1
  408. package/dist/utils/direction.js.map +1 -1
  409. package/dist/utils/env.d.ts +12 -3
  410. package/dist/utils/env.js +6 -0
  411. package/dist/utils/env.js.map +1 -0
  412. package/dist/utils/file.d.ts +3 -3
  413. package/dist/utils/file.js +1 -1
  414. package/dist/utils/file.js.map +1 -1
  415. package/dist/utils/focus.d.ts +1 -1
  416. package/dist/utils/focus.js.map +1 -1
  417. package/dist/utils/hostWarns.d.ts +1 -1
  418. package/dist/utils/hostWarns.js.map +1 -1
  419. package/dist/utils/ids.d.ts +1 -1
  420. package/dist/utils/ids.js.map +1 -1
  421. package/dist/utils/matcher.d.ts +2 -2
  422. package/dist/utils/matcher.js.map +1 -1
  423. package/dist/utils/memo.d.ts +1 -1
  424. package/dist/utils/memo.js.map +1 -1
  425. package/dist/utils/text.d.ts +3 -3
  426. package/dist/utils/text.js.map +1 -1
  427. package/dist/utils/time.d.ts +2 -101
  428. package/dist/utils/time.js +22 -98
  429. package/dist/utils/time.js.map +1 -1
  430. package/dist/utils/vnode.d.ts +5 -3
  431. package/dist/utils/vnode.js.map +1 -1
  432. package/package.json +2 -2
@@ -10,7 +10,7 @@ interface InputProps {
10
10
  compact?: boolean;
11
11
  /**
12
12
  * The native type of the input, which is also what tells a phone which keyboard to
13
- * offer — a numeric pad for `number`, an @ key for `email`.
13
+ * offer: a numeric pad for `number`, an @ key for `email`.
14
14
  */
15
15
  type?: InputType;
16
16
  /**
@@ -23,10 +23,17 @@ interface InputProps {
23
23
  disabled?: boolean;
24
24
  /**
25
25
  * Shows the value without allowing it to be changed. The field can still be
26
- * focused and copied from, and it hides the clear button — unless `clearVisible`
26
+ * focused and copied from, and it hides the clear button, unless `clearVisible`
27
27
  * answers that question explicitly.
28
28
  */
29
29
  readonly?: boolean;
30
+ /**
31
+ * Refuses the keyboard without drawing the field as read-only: the native attribute is
32
+ * set, but the field keeps its ordinary look and its clear cross. For a field whose value
33
+ * comes from somewhere else, a picker or a file dialog, and which is no less editable for
34
+ * it.
35
+ */
36
+ noTyping?: boolean;
30
37
  /** The label above the field, tied to it so that clicking it focuses the field. */
31
38
  label?: string;
32
39
  /**
@@ -36,7 +43,7 @@ interface InputProps {
36
43
  hint?: string;
37
44
  /**
38
45
  * An icon inside the field, at the start. It is decorative by default and becomes
39
- * a real button as soon as a `@click:icon-start` listener is attached — in which
46
+ * a real button as soon as a `@click:icon-start` listener is attached, in which
40
47
  * case it needs `iconStartLabel`. The `#start` slot is rendered after it, so a
41
48
  * field composed on top of this one can show both.
42
49
  */
@@ -56,7 +63,7 @@ interface InputProps {
56
63
  * What screen readers announce while the spinner turns. It falls back to the
57
64
  * design system dictionary.
58
65
  */
59
- loadingLabel?: string;
66
+ loadingText?: string;
60
67
  /**
61
68
  * Offers a cross that empties the field. It appears when there is something to
62
69
  * clear and the field can be edited.
@@ -64,7 +71,7 @@ interface InputProps {
64
71
  clearable?: boolean;
65
72
  /**
66
73
  * Decides whether the cross is shown, instead of letting the field work it out
67
- * from its own content — a read-only field included.
74
+ * from its own content (a read-only field included).
68
75
  *
69
76
  * It exists for the components built on top of this one, where what there is to
70
77
  * clear is not the text: VCombobox holds its selection as chips beside the field,
@@ -95,12 +102,12 @@ type __VLS_Props = InputProps;
95
102
  type __VLS_Slots = {
96
103
  /**
97
104
  * Content at the start of the field, rendered after `iconStart` rather than in its
98
- * place — which is what lets a field composed on top of this one put the chips
105
+ * place, which is what lets a field composed on top of this one put the chips
99
106
  * standing for its values here beside an icon it still wants drawn.
100
107
  */
101
108
  start?(): unknown;
102
109
  /**
103
- * Controls of your own inside the field, placed before the field's own — the clear
110
+ * Controls of your own inside the field, placed before the field's own: the clear
104
111
  * cross and the end icon. It is the slot for something that acts on the VALUE, such
105
112
  * as the AM/PM button of a 12-hour time field, which belongs beside the text rather
106
113
  * than past the controls that clear and open.
@@ -151,9 +158,10 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {
151
158
  invalid: boolean;
152
159
  hint: string;
153
160
  readonly: boolean;
161
+ noTyping: boolean;
154
162
  iconStartLabel: string;
155
163
  iconEndLabel: string;
156
- loadingLabel: string;
164
+ loadingText: string;
157
165
  clearable: boolean;
158
166
  clearVisible: boolean;
159
167
  clearLabel: string;
@@ -1 +1 @@
1
- {"version":3,"file":"VInput.js","names":[],"sources":["../../../src/components/VInput/VInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * A complete text field: label above, hint below, icons inside (clickable ones included), a\n * counter, a clear cross and a loading state, all arranged around a real `<input>`.\n *\n * Wrapper-root, so the attrs split: `class`/`style` stay on the root, where a consumer\n * expects to style the field, and everything else is forwarded to the input, where `name`,\n * `required` and the `aria-*` actually do something.\n *\n * Validation stays the browser's. The field turns red through `:user-invalid`, which only\n * reacts once the reader has left it, and even the soft limit is reported by\n * `setCustomValidity` rather than an event of our own — so a consumer's `<form>` sees\n * exactly what a bare input would give it.\n *\n * The behavioural JS is the v-model and the clear cross, which has to move focus back to the\n * field: it disappears the moment it is clicked, and focus would otherwise fall to the page.\n */\nimport { computed, inject, ref } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconName, iconProps } from '../VIcon/iconProps'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useIconClickHandlers } from '../../composables/useIconClickHandlers'\nimport { useClearable } from '../../composables/useClearable'\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTextLimit } from '../../composables/useTextLimit'\nimport { useMessages } from '../../i18n/state'\n\nimport { inputGroupKey } from './context'\n\n/** The height of the field: 32, 40 or 48 pixels. */\nexport type InputSize = 'sm' | 'md' | 'lg'\n\n/** The native type of the input, which also decides the keyboard a phone offers. */\nexport type InputType = 'text' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'url'\n\ninterface InputProps {\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: InputSize\n /** Takes 4px off the height, leaving the padding, the text and the icons as they are. */\n compact?: boolean\n /**\n * The native type of the input, which is also what tells a phone which keyboard to\n * offer — a numeric pad for `number`, an @ key for `email`.\n */\n type?: InputType\n /**\n * Marks the field as invalid whatever the browser thinks. This is the route for a\n * rule only the server can check; anything the browser can validate on its own\n * already colours the field without this prop.\n */\n invalid?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the value without allowing it to be changed. The field can still be\n * focused and copied from, and it hides the clear button — unless `clearVisible`\n * answers that question explicitly.\n */\n readonly?: boolean\n /** The label above the field, tied to it so that clicking it focuses the field. */\n label?: string\n /**\n * A line of help under the field. It is tied to the input for assistive\n * technology, so it is read out along with the label.\n */\n hint?: string\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes\n * a real button as soon as a `@click:icon-start` listener is attached — in which\n * case it needs `iconStartLabel`. The `#start` slot is rendered after it, so a\n * field composed on top of this one can show both.\n */\n iconStart?: IconSource\n /**\n * The same at the end of the field. The `#end` slot replaces it, and the loading\n * spinner takes its place while it turns.\n */\n iconEnd?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /** What the end icon does, in words, once it is clickable. */\n iconEndLabel?: string\n /** Shows a spinner at the end of the field, in place of the end icon or slot. */\n loading?: boolean\n /**\n * What screen readers announce while the spinner turns. It falls back to the\n * design system dictionary.\n */\n loadingLabel?: string\n /**\n * Offers a cross that empties the field. It appears when there is something to\n * clear and the field can be edited.\n */\n clearable?: boolean\n /**\n * Decides whether the cross is shown, instead of letting the field work it out\n * from its own content — a read-only field included.\n *\n * It exists for the components built on top of this one, where what there is to\n * clear is not the text: VCombobox holds its selection as chips beside the field,\n * and a read-only date or time picker changes its value through a panel rather\n * than by typing.\n */\n clearVisible?: boolean\n /** What the clear button does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The maximum number of characters. By default this is the browser's own limit,\n * which simply refuses anything beyond it.\n */\n maxlength?: number\n /**\n * Turns that limit into a soft one: the reader may type past it, and the field\n * goes into error instead of silently refusing the keystrokes. It is reported\n * through the native validity, so a form cannot be submitted over the limit.\n */\n softLimit?: boolean\n /**\n * Shows how much has been typed, at the end of the field: \"12/80\" against a limit,\n * or just \"12\" without one.\n */\n counter?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<InputProps>(), {\n size: 'md',\n compact: false,\n type: 'text',\n invalid: false,\n disabled: false,\n readonly: false,\n label: undefined,\n hint: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconStartLabel: undefined,\n iconEndLabel: undefined,\n loading: false,\n loadingLabel: undefined,\n clearable: false,\n clearVisible: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n softLimit: false,\n counter: false,\n})\n\nconst emit = defineEmits<{\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n /** The end icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-end': [event: MouseEvent]\n /** The clear button was pressed. The field has already been emptied. */\n clear: []\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its\n * place — which is what lets a field composed on top of this one put the chips\n * standing for its values here beside an icon it still wants drawn.\n */\n start?(): unknown\n /**\n * Controls of your own inside the field, placed before the field's own — the clear\n * cross and the end icon. It is the slot for something that acts on the VALUE, such\n * as the AM/PM button of a 12-hour time field, which belongs beside the text rather\n * than past the controls that clear and open.\n */\n 'value-end'?(): unknown\n /**\n * Content at the end of the field, which replaces `iconEnd`. It is hidden while\n * the field is loading, the spinner taking that place.\n */\n end?(): unknown\n}>()\n\n/**\n * The value, typed as text OR a number rather than text alone. On an\n * `<input type=\"number\">` Vue converts the value to a number by itself, so a\n * string-only model would hand a number back to a consumer who passed a string in,\n * and Vue would report an invalid prop.\n */\nconst model = defineModel<string | number>({ default: '' })\n\n/**\n * The value seen as text. Every measurement of its length — the counter, the soft\n * limit, whether the clear cross has anything to clear — goes through this, since a\n * number has no length of its own.\n */\nconst modelText = computed(() => String(model.value ?? ''))\n\nconst { attrs, rootClass, rootStyle, forwardedAttrs: restAttrs } = useRootAttrs()\n\n// A VInputGroup joins several controls into one object, so the shape of this field is the\n// row's decision rather than its own: a segment of another height stops lining up with its\n// neighbours and the merged border no longer reads as a single box. `disabled` is the one\n// read as an OR, the two answers being cumulative (VInput/context.ts).\nconst group = inject(inputGroupKey, null)\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape(props, group)\n\n// The prop keeps priority; what it falls back to is the dictionary, so the default\n// wording follows the language the design system is set to.\nconst m = useMessages()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.common.clear)\n\nconst { fieldId, hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\nconst { hasIconStartHandler, hasIconEndHandler } = useIconClickHandlers({\n name: 'VInput',\n iconStartLabel: props.iconStartLabel,\n iconEndLabel: props.iconEndLabel,\n})\n\nconst controlEl = ref<HTMLInputElement | null>(null)\n\nconst { showClear, onClear } = useClearable({\n clearable: () => props.clearable,\n clearVisible: () => props.clearVisible,\n disabled: () => resolvedDisabled.value,\n readonly: () => props.readonly,\n text: () => modelText.value,\n controlEl,\n onCleared: () => {\n model.value = ''\n emit('clear')\n },\n})\n\n// The counter and the soft limit both measure `modelText` rather than the model\n// itself, a number having no length.\nconst { counterText, over } = useTextLimit({\n el: controlEl,\n text: () => modelText.value,\n maxlength: () => props.maxlength,\n softLimit: () => props.softLimit,\n})\n\n// The real input sits inside the wrapper, out of reach of whoever renders this component.\n// These three are how the components built on it get there — VCombobox refocuses the field\n// and selects its text this way.\ndefineExpose({\n /** Moves the focus to the real input. */\n focus: (options?: FocusOptions) => controlEl.value?.focus(options),\n /** Selects everything in the field, as VCombobox does when it reopens its panel. */\n select: () => controlEl.value?.select(),\n /** The real `<input>`, for what neither of the two above covers. */\n el: controlEl,\n})\n</script>\n\n<template>\n <div\n class=\"v-input v-control\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-disabled=\"resolvedDisabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <VTypography v-if=\"label\" as=\"label\" variant=\"label\" class=\"v-input-label\" :for=\"fieldId\">\n {{ label }}\n </VTypography>\n\n <div class=\"v-input-field\">\n <!-- TRAP — the start icon is rendered BEFORE the slot, where the end icon is the\n slot's own fallback. That asymmetry is what the composed fields need: VCombobox\n and VFileInput fill `#start` with the chips standing for their values, which is\n OTHER content in the same zone rather than another way of drawing the icon. As a\n fallback the icon would vanish the moment a value was chosen — silently, and the\n clickable variant with it. The end zone has no such consumer: what is put there\n really does replace the icon. -->\n <button\n v-if=\"iconStart && hasIconStartHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-start\"\n :aria-label=\"iconStartLabel ?? iconName(iconStart)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-start', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconStart)\" />\n </button>\n <VIcon v-else-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" class=\"v-input-icon-start\" />\n <slot name=\"start\" />\n\n <input\n v-bind=\"restAttrs\"\n :id=\"fieldId\"\n ref=\"controlEl\"\n v-model=\"model\"\n class=\"v-input-control\"\n :type=\"type\"\n :maxlength=\"softLimit ? undefined : maxlength\"\n :disabled=\"resolvedDisabled\"\n :readonly=\"readonly || undefined\"\n :aria-invalid=\"invalid || undefined\"\n :aria-describedby=\"describedBy\"\n />\n\n <span\n v-if=\"counter\"\n class=\"v-input-counter v-field-counter\"\n :data-over=\"over ? '' : undefined\"\n >\n {{ counterText }}\n </span>\n\n <!-- Before the clear cross rather than after it, so that a consumer's control reads\n and tabs in the same order: what acts on the value sits next to the value, and\n the field's own controls stay together at the end. -->\n <slot name=\"value-end\" />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-clear\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"onClear\"\n >\n <VIcon :name=\"closeIcon\" />\n </button>\n\n <VSpinner v-if=\"loading\" :label=\"loadingLabel\" />\n <slot v-else name=\"end\">\n <button\n v-if=\"iconEnd && hasIconEndHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-end\"\n :aria-label=\"iconEndLabel ?? iconName(iconEnd)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-end', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconEnd)\" />\n </button>\n <VIcon v-else-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" class=\"v-input-icon-end\" />\n </slot>\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The label and the hint are rendered by VTypography, which carries their type. The\n .v-input-label and .v-input-hint classes remain as hooks: a consumer overrides\n through them, and the disabled state below reaches them that way. */\n\n /* This is the box that carries the border, the background and the focus ring.\n `--field-border-color` is the single source of truth for its colour, and the\n hover, error and disabled states do nothing but redefine it.\n\n Nothing here restates the size scale: the `--control-*` variables are inherited\n from the v-control root (styles/control-size.css), the icon context included. */\n .v-input-field {\n --field-border-color: var(--vectis-color-border-strong);\n\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n height: var(--control-height);\n padding-inline: var(--control-padding-inline-field);\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--field-border-color);\n border-radius: var(--vectis-radius-interactive);\n font-size: var(--control-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-input-control {\n flex: 1;\n min-width: 0;\n height: 100%;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n outline: none; /* the focus ring is drawn by the field around it, not here */\n }\n\n .v-input-control::placeholder {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The browsers' own in-field controls are removed, because the design system draws\n its own: WebKit's search cross would sit beside the `clearable` one, the number\n spinners and Edge's password-reveal eye would clash with the icons. */\n .v-input-control::-webkit-search-cancel-button,\n .v-input-control::-webkit-search-decoration,\n .v-input-control::-webkit-search-results-button,\n .v-input-control::-webkit-search-results-decoration,\n .v-input-control::-webkit-inner-spin-button,\n .v-input-control::-webkit-outer-spin-button {\n -webkit-appearance: none;\n appearance: none;\n }\n\n .v-input-control[type='number'] {\n appearance: textfield;\n }\n\n .v-input-control::-ms-reveal {\n display: none;\n }\n\n /* The yellow autofill background is painted by the browser on the input itself,\n inside our field. Its colour cannot be changed, but matching the field's radius\n at least stops it from showing square corners inside a rounded box. */\n .v-input-control:-webkit-autofill {\n border-radius: var(--vectis-radius-interactive);\n }\n\n /* A read-only field sinks slightly into the page but keeps its text at full\n strength — the value is there to be read — and still shows the accent focus ring.\n The state is read from [data-readonly] and never from `:read-only`, which the\n browser also matches on a disabled field.\n\n TRAP — this block must stay FIRST in the sequence of states: read-only, then\n hover, focus, invalid and disabled. Every one of these selectors weighs (0,3,0),\n `:has()` taking the specificity of what it contains, so nothing but the source\n order arbitrates between them. Moved further down, this rule would repaint the\n error border and the accent focus ring grey, with no error anywhere. What\n belongs to a read-only field is its BASE colour alone. */\n .v-input[data-readonly] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-input-field:hover:not(:has(.v-input-control:focus)):not(\n :has(\n .v-input-control:disabled,\n .v-input-control:user-invalid,\n .v-input-control[aria-invalid='true']\n )\n ) {\n --field-border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /* The focused field appears to have a two-pixel border: it is really its own 1px\n border plus a 1px shadow of the same colour just outside it, which costs no\n layout and therefore makes nothing jump.\n\n The selector watches the CONTROL's focus and not `:focus-within`, so that when\n one of the field's own buttons — the cross, a clickable icon — takes keyboard\n focus, only that button's outline lights up: two indicators at once would be\n unreadable. And it is `:focus` rather than `:focus-visible`, because a text field\n shows its focus even when it was reached with the mouse.\n\n The transparent outline is the safety net for Windows forced colours, which drop\n box-shadows entirely; an outline survives and keeps the field marked. */\n .v-input-field:has(.v-input-control:focus) {\n --field-border-color: var(--vectis-color-accent);\n\n box-shadow: 0 0 0 1px var(--field-border-color);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /* The invalid state, from the browser's own verdict first and from the `invalid`\n prop second. Only the colour variable is changed, which is why the border AND\n the focus ring both turn red without either being restated. */\n .v-input-field:has(.v-input-control:user-invalid),\n .v-input-field:has(.v-input-control[aria-invalid='true']) {\n --field-border-color: var(--vectis-color-danger);\n }\n\n /* The counter's type and its overflow colour are `.v-field-counter`'s (styles/field.css),\n shared with the counters VTextarea and VFileInput draw under their field. Inside the\n box it must not shrink, or a long value would squeeze its figures. */\n .v-input-counter {\n flex: none;\n }\n\n /* A decorative icon is drawn in a muted grey, so it stays quieter than the text\n being typed beside it. */\n .v-input-field > .v-icon {\n color: var(--vectis-color-text-muted);\n }\n\n .v-input-field > .v-spinner {\n font-size: var(--vectis-icon-size);\n }\n\n /* A disabled field greys out through the colour tokens, the same ones VCheckbox and\n VRadio use, and never through opacity. It comes LAST in the sequence of states,\n which at equal specificity is what makes it win over all of them, the error\n included: a disabled field is not submitted, so it has nothing to report.\n\n The text here is `text-muted` and not the `text-subtle` used by the label and the\n hint below, because those sit on the page surface where this sits on\n `surface-muted` — against which subtle falls to a 4.4:1 contrast. The control\n itself is exempt from that rule, being natively disabled, but the icons the field\n CONTAINS are not, and they inherit this colour. */\n .v-input[data-disabled] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n cursor: not-allowed;\n }\n\n .v-input[data-disabled] .v-input-label,\n .v-input[data-disabled] .v-input-hint,\n .v-input[data-disabled] .v-input-counter {\n color: var(--vectis-color-text-subtle);\n }\n\n .v-input[data-disabled] .v-input-action,\n .v-input[data-disabled] .v-input-field > .v-icon {\n color: inherit;\n cursor: not-allowed;\n }\n\n .v-input-control:disabled {\n cursor: not-allowed;\n }\n\n /* Two arrangements a field composed on top of this one opts into with a class on this\n root: VCombobox and VFileInput, the two that put chips inside the field. They live in\n THIS sheet because it is the one both are guaranteed to load, being built on VInput —\n the core sheet would charge every consumer for them.\n\n `.v-input-end-pinned` lifts the clear cross and whatever carries `.v-input-icon-end` —\n the end icon, or a composed field's own chevron and spinner — out of the flow, so they\n stay pinned to the end of the field and vertically centred whatever the chips do. The\n room they occupy is then reserved with padding, or the text and the chips would run\n underneath. That reservation is written as exactly what the flow would have produced\n with them left in place — one glyph's width each from the field's padding, one gap\n between two of them — and reads the same two variables as the insets below, so the room\n reserved and the glyphs it protects cannot drift apart.\n\n Whether the cross takes its room is read off the markup (`:has`): the cross is rendered\n exactly when there is something to clear, so the padding cannot disagree with it.\n\n The centring is a translation kept SEPARATE from `rotate`, which VCombobox's chevron\n turns when its panel opens. */\n .v-input-end-pinned .v-input-field {\n position: relative;\n padding-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n .v-input-end-pinned .v-input-field:has(> .v-input-clear) {\n padding-inline-end: calc(\n var(--control-padding-inline-field) + 2 * var(--vectis-icon-size) + 2 * var(--control-gap)\n );\n }\n\n .v-input-end-pinned :is(.v-input-clear, .v-input-icon-end) {\n position: absolute;\n inset-inline-end: var(--control-padding-inline-field);\n top: 50%;\n translate: 0 -50%;\n }\n\n /* TRAP — this inset reads in GLYPHS and not in buttons. The cross is one of the field's\n buttons, whose negative margin — half the difference between icon and button — already\n cancels its own overhang, so an inset lands on the glyph's edge. Measured in button\n widths instead, it pushes the cross a whole gap too far, with nothing to signal it.\n It overrides the rule above at the same weight, so it has to stay after it. */\n .v-input-end-pinned .v-input-clear {\n inset-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n /* `.v-input-chips` lets the chips WRAP onto several rows, the field growing instead of\n scrolling but never shrinking below an ordinary control.\n\n The input is forced to the chips' height rather than the full height it inherits: its\n natural height is greater than a chip's, and the field would grow the moment it was\n focused. That height is `--chip-height`, written inline on the composed field's root\n by the helper that also picks the chips' size (utils/chip.ts), so the two cannot drift.\n\n TRAP — the control rule must stay at (0,2,0). VCombobox folds its search input away\n with a (0,3,0) rule that zeroes this height, and at equal weight the winner would be\n whichever of the two sheets a bundler put last. */\n .v-input-chips .v-input-field {\n flex-wrap: wrap;\n height: auto;\n min-height: var(--control-height);\n padding-block: var(--vectis-space-1);\n }\n\n .v-input-chips .v-input-control {\n height: var(--chip-height);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-field {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VInput.js","names":[],"sources":["../../../src/components/VInput/VInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * A complete text field: label above, hint below, icons inside (clickable ones included), a\n * counter, a clear cross and a loading state, all arranged around a real `<input>`.\n *\n * Wrapper-root, so the attrs split: `class`/`style` stay on the root, where a consumer\n * expects to style the field, and everything else is forwarded to the input, where `name`,\n * `required` and the `aria-*` actually do something.\n *\n * Validation stays the browser's. The field turns red through `:user-invalid`, which only\n * reacts once the reader has left it, and even the soft limit is reported by\n * `setCustomValidity` rather than an event of our own — so a consumer's `<form>` sees\n * exactly what a bare input would give it.\n *\n * The behavioural JS is the v-model and the clear cross, which has to move focus back to the\n * field: it disappears the moment it is clicked, and focus would otherwise fall to the page.\n */\nimport { computed, inject, ref } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconName, iconProps } from '../VIcon/iconProps'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useIconClickHandlers } from '../../composables/useIconClickHandlers'\nimport { useClearable } from '../../composables/useClearable'\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTextLimit } from '../../composables/useTextLimit'\nimport { useMessages } from '../../i18n/state'\n\nimport { inputGroupKey } from './context'\n\n/** The height of the field: 32, 40 or 48 pixels. */\nexport type InputSize = 'sm' | 'md' | 'lg'\n\n/** The native type of the input, which also decides the keyboard a phone offers. */\nexport type InputType = 'text' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'url'\n\ninterface InputProps {\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: InputSize\n /** Takes 4px off the height, leaving the padding, the text and the icons as they are. */\n compact?: boolean\n /**\n * The native type of the input, which is also what tells a phone which keyboard to\n * offer: a numeric pad for `number`, an @ key for `email`.\n */\n type?: InputType\n /**\n * Marks the field as invalid whatever the browser thinks. This is the route for a\n * rule only the server can check; anything the browser can validate on its own\n * already colours the field without this prop.\n */\n invalid?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the value without allowing it to be changed. The field can still be\n * focused and copied from, and it hides the clear button, unless `clearVisible`\n * answers that question explicitly.\n */\n readonly?: boolean\n /**\n * Refuses the keyboard without drawing the field as read-only: the native attribute is\n * set, but the field keeps its ordinary look and its clear cross. For a field whose value\n * comes from somewhere else, a picker or a file dialog, and which is no less editable for\n * it.\n */\n noTyping?: boolean\n /** The label above the field, tied to it so that clicking it focuses the field. */\n label?: string\n /**\n * A line of help under the field. It is tied to the input for assistive\n * technology, so it is read out along with the label.\n */\n hint?: string\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes\n * a real button as soon as a `@click:icon-start` listener is attached, in which\n * case it needs `iconStartLabel`. The `#start` slot is rendered after it, so a\n * field composed on top of this one can show both.\n */\n iconStart?: IconSource\n /**\n * The same at the end of the field. The `#end` slot replaces it, and the loading\n * spinner takes its place while it turns.\n */\n iconEnd?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /** What the end icon does, in words, once it is clickable. */\n iconEndLabel?: string\n /** Shows a spinner at the end of the field, in place of the end icon or slot. */\n loading?: boolean\n /**\n * What screen readers announce while the spinner turns. It falls back to the\n * design system dictionary.\n */\n loadingText?: string\n /**\n * Offers a cross that empties the field. It appears when there is something to\n * clear and the field can be edited.\n */\n clearable?: boolean\n /**\n * Decides whether the cross is shown, instead of letting the field work it out\n * from its own content (a read-only field included).\n *\n * It exists for the components built on top of this one, where what there is to\n * clear is not the text: VCombobox holds its selection as chips beside the field,\n * and a read-only date or time picker changes its value through a panel rather\n * than by typing.\n */\n clearVisible?: boolean\n /** What the clear button does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The maximum number of characters. By default this is the browser's own limit,\n * which simply refuses anything beyond it.\n */\n maxlength?: number\n /**\n * Turns that limit into a soft one: the reader may type past it, and the field\n * goes into error instead of silently refusing the keystrokes. It is reported\n * through the native validity, so a form cannot be submitted over the limit.\n */\n softLimit?: boolean\n /**\n * Shows how much has been typed, at the end of the field: \"12/80\" against a limit,\n * or just \"12\" without one.\n */\n counter?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<InputProps>(), {\n size: 'md',\n compact: false,\n type: 'text',\n invalid: false,\n disabled: false,\n readonly: false,\n label: undefined,\n hint: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconStartLabel: undefined,\n iconEndLabel: undefined,\n loading: false,\n loadingText: undefined,\n clearable: false,\n clearVisible: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n softLimit: false,\n noTyping: false,\n counter: false,\n})\n\nconst emit = defineEmits<{\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n /** The end icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-end': [event: MouseEvent]\n /** The clear button was pressed. The field has already been emptied. */\n clear: []\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its\n * place, which is what lets a field composed on top of this one put the chips\n * standing for its values here beside an icon it still wants drawn.\n */\n start?(): unknown\n /**\n * Controls of your own inside the field, placed before the field's own: the clear\n * cross and the end icon. It is the slot for something that acts on the VALUE, such\n * as the AM/PM button of a 12-hour time field, which belongs beside the text rather\n * than past the controls that clear and open.\n */\n 'value-end'?(): unknown\n /**\n * Content at the end of the field, which replaces `iconEnd`. It is hidden while\n * the field is loading, the spinner taking that place.\n */\n end?(): unknown\n}>()\n\n/**\n * The value, typed as text OR a number rather than text alone. On an\n * `<input type=\"number\">` Vue converts the value to a number by itself, so a\n * string-only model would hand a number back to a consumer who passed a string in,\n * and Vue would report an invalid prop.\n */\nconst model = defineModel<string | number>({ default: '' })\n\n/**\n * The value seen as text. Every measurement of its length — the counter, the soft\n * limit, whether the clear cross has anything to clear — goes through this, since a\n * number has no length of its own.\n */\nconst modelText = computed(() => String(model.value ?? ''))\n\nconst { attrs, rootClass, rootStyle, forwardedAttrs: restAttrs } = useRootAttrs()\n\n// A VInputGroup joins several controls into one object, so the shape of this field is the\n// row's decision rather than its own: a segment of another height stops lining up with its\n// neighbours and the merged border no longer reads as a single box. `disabled` is the one\n// read as an OR, the two answers being cumulative (VInput/context.ts).\nconst group = inject(inputGroupKey, null)\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape(props, group)\n\n// The prop keeps priority; what it falls back to is the dictionary, so the default\n// wording follows the language the design system is set to.\nconst m = useMessages()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.common.clear)\n\nconst { fieldId, hintId, counterId, describedBy } = useFieldIds(\n attrs,\n () => !!props.hint,\n () => props.counter,\n)\n\nconst { hasIconStartHandler, hasIconEndHandler } = useIconClickHandlers({\n name: 'VInput',\n iconStartLabel: props.iconStartLabel,\n iconEndLabel: props.iconEndLabel,\n})\n\nconst controlEl = ref<HTMLInputElement | null>(null)\n\nconst { showClear, onClear } = useClearable({\n clearable: () => props.clearable,\n clearVisible: () => props.clearVisible,\n disabled: () => resolvedDisabled.value,\n readonly: () => props.readonly,\n text: () => modelText.value,\n controlEl,\n onCleared: () => {\n model.value = ''\n emit('clear')\n },\n})\n\n// The counter and the soft limit both measure `modelText` rather than the model\n// itself, a number having no length.\nconst { counterText, over } = useTextLimit({\n el: controlEl,\n text: () => modelText.value,\n maxlength: () => props.maxlength,\n softLimit: () => props.softLimit,\n})\n\n// The real input sits inside the wrapper, out of reach of whoever renders this component.\n// These three are how the components built on it get there — VCombobox refocuses the field\n// and selects its text this way.\ndefineExpose({\n /** Moves the focus to the real input. */\n focus: (options?: FocusOptions) => controlEl.value?.focus(options),\n /** Selects everything in the field, as VCombobox does when it reopens its panel. */\n select: () => controlEl.value?.select(),\n /** The real `<input>`, for what neither of the two above covers. */\n el: controlEl,\n})\n</script>\n\n<template>\n <div\n class=\"v-input v-control\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-disabled=\"resolvedDisabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <VTypography v-if=\"label\" as=\"label\" variant=\"label\" class=\"v-input-label\" :for=\"fieldId\">\n {{ label }}\n </VTypography>\n\n <div class=\"v-input-field\">\n <!-- TRAP — the start icon is rendered BEFORE the slot, where the end icon is the\n slot's own fallback. That asymmetry is what the composed fields need: VCombobox\n and VFileInput fill `#start` with the chips standing for their values, which is\n OTHER content in the same zone rather than another way of drawing the icon. As a\n fallback the icon would vanish the moment a value was chosen — silently, and the\n clickable variant with it. The end zone has no such consumer: what is put there\n really does replace the icon. -->\n <button\n v-if=\"iconStart && hasIconStartHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-start\"\n :aria-label=\"iconStartLabel ?? iconName(iconStart)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-start', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconStart)\" />\n </button>\n <VIcon v-else-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" class=\"v-input-icon-start\" />\n <slot name=\"start\" />\n\n <input\n :id=\"fieldId\"\n ref=\"controlEl\"\n v-model=\"model\"\n :aria-invalid=\"invalid || undefined\"\n v-bind=\"restAttrs\"\n class=\"v-input-control\"\n :type=\"type\"\n :maxlength=\"softLimit ? undefined : maxlength\"\n :disabled=\"resolvedDisabled\"\n :readonly=\"readonly || noTyping || undefined\"\n :aria-describedby=\"describedBy\"\n />\n\n <span\n v-if=\"counter\"\n :id=\"counterId\"\n class=\"v-input-counter v-field-counter\"\n :data-over=\"over ? '' : undefined\"\n >\n {{ counterText }}\n </span>\n\n <!-- Before the clear cross rather than after it, so that a consumer's control reads\n and tabs in the same order: what acts on the value sits next to the value, and\n the field's own controls stay together at the end. -->\n <slot name=\"value-end\" />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-clear\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"onClear\"\n >\n <VIcon :name=\"closeIcon\" />\n </button>\n\n <VSpinner v-if=\"loading\" :label=\"loadingText\" />\n <slot v-else name=\"end\">\n <button\n v-if=\"iconEnd && hasIconEndHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-end\"\n :aria-label=\"iconEndLabel ?? iconName(iconEnd)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-end', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconEnd)\" />\n </button>\n <VIcon v-else-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" class=\"v-input-icon-end\" />\n </slot>\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The label and the hint are rendered by VTypography, which carries their type. The\n .v-input-label and .v-input-hint classes remain as hooks: a consumer overrides\n through them, and the disabled state below reaches them that way. */\n\n /* This is the box that carries the border, the background and the focus ring.\n `--field-border-color` is the single source of truth for its colour, and the\n hover, error and disabled states do nothing but redefine it.\n\n Nothing here restates the size scale: the `--control-*` variables are inherited\n from the v-control root (styles/control-size.css), the icon context included. */\n .v-input-field {\n --field-border-color: var(--vectis-color-border-strong);\n\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n height: var(--control-height);\n padding-inline: var(--control-padding-inline-field);\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--field-border-color);\n border-radius: var(--vectis-radius-interactive);\n font-size: var(--control-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-input-control {\n flex: 1;\n min-width: 0;\n height: 100%;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n outline: none; /* the focus ring is drawn by the field around it, not here */\n }\n\n .v-input-control::placeholder {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The browsers' own in-field controls are removed, because the design system draws\n its own: WebKit's search cross would sit beside the `clearable` one, the number\n spinners and Edge's password-reveal eye would clash with the icons. */\n .v-input-control::-webkit-search-cancel-button,\n .v-input-control::-webkit-search-decoration,\n .v-input-control::-webkit-search-results-button,\n .v-input-control::-webkit-search-results-decoration,\n .v-input-control::-webkit-inner-spin-button,\n .v-input-control::-webkit-outer-spin-button {\n -webkit-appearance: none;\n appearance: none;\n }\n\n .v-input-control[type='number'] {\n appearance: textfield;\n }\n\n .v-input-control::-ms-reveal {\n display: none;\n }\n\n /* The yellow autofill background is painted by the browser on the input itself,\n inside our field. Its colour cannot be changed, but matching the field's radius\n at least stops it from showing square corners inside a rounded box. */\n .v-input-control:-webkit-autofill {\n border-radius: var(--vectis-radius-interactive);\n }\n\n /* A read-only field sinks slightly into the page but keeps its text at full\n strength — the value is there to be read — and still shows the accent focus ring.\n The state is read from [data-readonly] and never from `:read-only`, which the\n browser also matches on a disabled field.\n\n TRAP — this block must stay FIRST in the sequence of states: read-only, then\n hover, focus, invalid and disabled. The read-only, focus, invalid and disabled\n selectors all weigh (0,3,0), `:has()` taking the specificity of what it contains, so\n nothing but the source order arbitrates between them. Hover alone weighs more, (0,6,0):\n its `:not()` keeps it off a focused, invalid or disabled field, and on a read-only\n one it darkens the border as on any other. Moved further down, this rule would repaint the\n error border and the accent focus ring grey, with no error anywhere. What\n belongs to a read-only field is its BASE colour alone. */\n .v-input[data-readonly] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-input-field:hover:not(:has(.v-input-control:focus)):not(\n :has(\n .v-input-control:disabled,\n .v-input-control:user-invalid,\n .v-input-control[aria-invalid='true']\n )\n ) {\n --field-border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /* The focused field appears to have a two-pixel border: it is really its own 1px\n border plus a 1px shadow of the same colour just outside it, which costs no\n layout and therefore makes nothing jump.\n\n The selector watches the CONTROL's focus and not `:focus-within`, so that when\n one of the field's own buttons — the cross, a clickable icon — takes keyboard\n focus, only that button's outline lights up: two indicators at once would be\n unreadable. And it is `:focus` rather than `:focus-visible`, because a text field\n shows its focus even when it was reached with the mouse.\n\n The transparent outline is the safety net for Windows forced colours, which drop\n box-shadows entirely; an outline survives and keeps the field marked. */\n .v-input-field:has(.v-input-control:focus) {\n --field-border-color: var(--vectis-color-accent);\n\n box-shadow: 0 0 0 1px var(--field-border-color);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /* The invalid state, from the browser's own verdict first and from the `invalid`\n prop second. Only the colour variable is changed, which is why the border AND\n the focus ring both turn red without either being restated. */\n .v-input-field:has(.v-input-control:user-invalid),\n .v-input-field:has(.v-input-control[aria-invalid='true']) {\n --field-border-color: var(--vectis-color-danger);\n }\n\n /* The counter's type and its overflow colour are `.v-field-counter`'s (styles/field.css),\n shared with the counters VTextarea and VFileInput draw under their field. Inside the\n box it must not shrink, or a long value would squeeze its figures. */\n .v-input-counter {\n flex: none;\n }\n\n /* A decorative icon is drawn in a muted grey, so it stays quieter than the text\n being typed beside it. */\n .v-input-field > .v-icon {\n color: var(--vectis-color-text-muted);\n }\n\n .v-input-field > .v-spinner {\n font-size: var(--vectis-icon-size);\n }\n\n /* A disabled field greys out through the colour tokens, the same ones VCheckbox and\n VRadio use, and never through opacity. It comes LAST in the sequence of states,\n which at equal specificity is what makes it win over all of them, the error\n included: a disabled field is not submitted, so it has nothing to report.\n\n The text here is `text-muted` and not the `text-subtle` used by the label and the\n hint below, because those sit on the page surface where this sits on\n `surface-muted` — against which subtle falls to a 4.4:1 contrast. The control\n itself is exempt from that rule, being natively disabled, but the icons the field\n CONTAINS are not, and they inherit this colour. */\n .v-input[data-disabled] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n cursor: not-allowed;\n }\n\n .v-input[data-disabled] .v-input-label,\n .v-input[data-disabled] .v-input-hint,\n .v-input[data-disabled] .v-input-counter {\n color: var(--vectis-color-text-subtle);\n }\n\n .v-input[data-disabled] .v-input-action,\n .v-input[data-disabled] .v-input-field > .v-icon {\n color: inherit;\n cursor: not-allowed;\n }\n\n .v-input-control:disabled {\n cursor: not-allowed;\n }\n\n /* Two arrangements a field composed on top of this one opts into with a class on this\n root: VCombobox and VFileInput, the two that put chips inside the field. They live in\n THIS sheet because it is the one both are guaranteed to load, being built on VInput —\n the core sheet would charge every consumer for them.\n\n `.v-input-end-pinned` lifts the clear cross and whatever carries `.v-input-icon-end` —\n the end icon, or a composed field's own chevron and spinner — out of the flow, so they\n stay pinned to the end of the field and vertically centred whatever the chips do. The\n room they occupy is then reserved with padding, or the text and the chips would run\n underneath. That reservation is written as exactly what the flow would have produced\n with them left in place — one glyph's width each from the field's padding, one gap\n between two of them — and reads the same two variables as the insets below, so the room\n reserved and the glyphs it protects cannot drift apart.\n\n Whether the cross takes its room is read off the markup (`:has`): the cross is rendered\n exactly when there is something to clear, so the padding cannot disagree with it.\n\n The centring is a translation kept SEPARATE from `rotate`, which VCombobox's chevron\n turns when its panel opens. */\n .v-input-end-pinned .v-input-field {\n position: relative;\n padding-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n .v-input-end-pinned .v-input-field:has(> .v-input-clear) {\n padding-inline-end: calc(\n var(--control-padding-inline-field) + 2 * var(--vectis-icon-size) + 2 * var(--control-gap)\n );\n }\n\n .v-input-end-pinned :is(.v-input-clear, .v-input-icon-end) {\n position: absolute;\n inset-inline-end: var(--control-padding-inline-field);\n top: 50%;\n translate: 0 -50%;\n }\n\n /* TRAP — this inset reads in GLYPHS and not in buttons. The cross is one of the field's\n buttons, whose negative margin — half the difference between icon and button — already\n cancels its own overhang, so an inset lands on the glyph's edge. Measured in button\n widths instead, it pushes the cross a whole gap too far, with nothing to signal it.\n It overrides the rule above at the same weight, so it has to stay after it. */\n .v-input-end-pinned .v-input-clear {\n inset-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n /* `.v-input-chips` lets the chips WRAP onto several rows, the field growing instead of\n scrolling but never shrinking below an ordinary control.\n\n The input is forced to the chips' height rather than the full height it inherits: its\n natural height is greater than a chip's, and the field would grow the moment it was\n focused. That height is `--chip-height`, written inline on the composed field's root\n by the helper that also picks the chips' size (utils/chip.ts), so the two cannot drift.\n\n TRAP — the control rule must stay at (0,2,0). VCombobox folds its search input away\n with a (0,3,0) rule that zeroes this height, and at equal weight the winner would be\n whichever of the two sheets a bundler put last. */\n .v-input-chips .v-input-field {\n flex-wrap: wrap;\n height: auto;\n min-height: var(--control-height);\n padding-block: var(--vectis-space-1);\n }\n\n .v-input-chips .v-input-control {\n height: var(--chip-height);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-field {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
@@ -20,13 +20,13 @@ var N = [
20
20
  "data-readonly"
21
21
  ], P = { class: "v-input-field" }, F = ["aria-label", "disabled"], I = [
22
22
  "id",
23
+ "aria-invalid",
23
24
  "type",
24
25
  "maxlength",
25
26
  "disabled",
26
27
  "readonly",
27
- "aria-invalid",
28
28
  "aria-describedby"
29
- ], L = ["data-over"], R = ["aria-label"], z = ["aria-label", "disabled"], B = /*@__PURE__*/ _({
29
+ ], L = ["id", "data-over"], R = ["aria-label"], z = ["aria-label", "disabled"], B = /*@__PURE__*/ _({
30
30
  inheritAttrs: !1,
31
31
  __name: "VInput",
32
32
  props: /*@__PURE__*/ b({
@@ -48,6 +48,10 @@ var N = [
48
48
  type: Boolean,
49
49
  default: !1
50
50
  },
51
+ noTyping: {
52
+ type: Boolean,
53
+ default: !1
54
+ },
51
55
  label: { default: void 0 },
52
56
  hint: { default: void 0 },
53
57
  iconStart: { default: void 0 },
@@ -58,7 +62,7 @@ var N = [
58
62
  type: Boolean,
59
63
  default: !1
60
64
  },
61
- loadingLabel: { default: void 0 },
65
+ loadingText: { default: void 0 },
62
66
  clearable: {
63
67
  type: Boolean,
64
68
  default: !1
@@ -87,11 +91,11 @@ var N = [
87
91
  "clear"
88
92
  ], ["update:modelValue"]),
89
93
  setup(_, { expose: b, emit: B }) {
90
- let V = _, H = B, U = k(_, "modelValue"), W = u(() => String(U.value ?? "")), { attrs: G, rootClass: K, rootStyle: q, forwardedAttrs: J } = s(), { size: Y, compact: ae, disabled: X } = a(V, y(re, null)), oe = e(), se = u(() => V.clearLabel ?? oe.value.common.clear), { fieldId: Z, hintId: ce, describedBy: le } = ee(G, () => !!V.hint), { hasIconStartHandler: ue, hasIconEndHandler: de } = te({
94
+ let V = _, H = B, U = k(_, "modelValue"), W = u(() => String(U.value ?? "")), { attrs: G, rootClass: K, rootStyle: q, forwardedAttrs: J } = s(), { size: Y, compact: ae, disabled: X } = a(V, y(re, null)), oe = e(), se = u(() => V.clearLabel ?? oe.value.common.clear), { fieldId: Z, hintId: ce, counterId: le, describedBy: ue } = ee(G, () => !!V.hint, () => V.counter), { hasIconStartHandler: de, hasIconEndHandler: fe } = te({
91
95
  name: "VInput",
92
96
  iconStartLabel: V.iconStartLabel,
93
97
  iconEndLabel: V.iconEndLabel
94
- }), Q = ie(null), { showClear: fe, onClear: $ } = ne({
98
+ }), Q = ie(null), { showClear: pe, onClear: $ } = ne({
95
99
  clearable: () => V.clearable,
96
100
  clearVisible: () => V.clearVisible,
97
101
  disabled: () => X.value,
@@ -101,7 +105,7 @@ var N = [
101
105
  onCleared: () => {
102
106
  U.value = "", H("clear");
103
107
  }
104
- }), { counterText: pe, over: me } = l({
108
+ }), { counterText: me, over: he } = l({
105
109
  el: Q,
106
110
  text: () => W.value,
107
111
  maxlength: () => V.maxlength,
@@ -130,7 +134,7 @@ var N = [
130
134
  _: 1
131
135
  }, 8, ["for"])) : f("", !0),
132
136
  m("div", P, [
133
- _.iconStart && O(ue) ? (T(), p("button", {
137
+ _.iconStart && O(de) ? (T(), p("button", {
134
138
  key: 0,
135
139
  type: "button",
136
140
  class: "v-input-action v-field-action v-input-icon-start",
@@ -139,26 +143,28 @@ var N = [
139
143
  onClick: a[0] ||= (e) => H("click:icon-start", e)
140
144
  }, [g(r, C(v(O(n)(_.iconStart))), null, 16)], 8, F)) : _.iconStart ? (T(), d(r, x({ key: 1 }, O(n)(_.iconStart), { class: "v-input-icon-start" }), null, 16)) : f("", !0),
141
145
  E(e.$slots, "start"),
142
- M(m("input", x(O(J), {
146
+ M(m("input", x({
143
147
  id: O(Z),
144
148
  ref_key: "controlEl",
145
149
  ref: Q,
146
150
  "onUpdate:modelValue": a[1] ||= (e) => U.value = e,
151
+ "aria-invalid": _.invalid || void 0
152
+ }, O(J), {
147
153
  class: "v-input-control",
148
154
  type: _.type,
149
155
  maxlength: _.softLimit ? void 0 : _.maxlength,
150
156
  disabled: O(X),
151
- readonly: _.readonly || void 0,
152
- "aria-invalid": _.invalid || void 0,
153
- "aria-describedby": O(le)
157
+ readonly: _.readonly || _.noTyping || void 0,
158
+ "aria-describedby": O(ue)
154
159
  }), null, 16, I), [[A, U.value]]),
155
160
  _.counter ? (T(), p("span", {
156
161
  key: 2,
162
+ id: O(le),
157
163
  class: "v-input-counter v-field-counter",
158
- "data-over": O(me) ? "" : void 0
159
- }, D(O(pe)), 9, L)) : f("", !0),
164
+ "data-over": O(he) ? "" : void 0
165
+ }, D(O(me)), 9, L)) : f("", !0),
160
166
  E(e.$slots, "value-end"),
161
- O(fe) ? (T(), p("button", {
167
+ O(pe) ? (T(), p("button", {
162
168
  key: 3,
163
169
  type: "button",
164
170
  class: "v-input-action v-field-action v-input-clear",
@@ -167,8 +173,8 @@ var N = [
167
173
  }, [g(r, { name: O(c) }, null, 8, ["name"])], 8, R)) : f("", !0),
168
174
  _.loading ? (T(), d(o, {
169
175
  key: 4,
170
- label: _.loadingLabel
171
- }, null, 8, ["label"])) : E(e.$slots, "end", {}, () => [_.iconEnd && O(de) ? (T(), p("button", {
176
+ label: _.loadingText
177
+ }, null, 8, ["label"])) : E(e.$slots, "end", {}, () => [_.iconEnd && O(fe) ? (T(), p("button", {
172
178
  key: 0,
173
179
  type: "button",
174
180
  class: "v-input-action v-field-action v-input-icon-end",
@@ -1 +1 @@
1
- {"version":3,"file":"VInput.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VInput/VInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * A complete text field: label above, hint below, icons inside (clickable ones included), a\n * counter, a clear cross and a loading state, all arranged around a real `<input>`.\n *\n * Wrapper-root, so the attrs split: `class`/`style` stay on the root, where a consumer\n * expects to style the field, and everything else is forwarded to the input, where `name`,\n * `required` and the `aria-*` actually do something.\n *\n * Validation stays the browser's. The field turns red through `:user-invalid`, which only\n * reacts once the reader has left it, and even the soft limit is reported by\n * `setCustomValidity` rather than an event of our own — so a consumer's `<form>` sees\n * exactly what a bare input would give it.\n *\n * The behavioural JS is the v-model and the clear cross, which has to move focus back to the\n * field: it disappears the moment it is clicked, and focus would otherwise fall to the page.\n */\nimport { computed, inject, ref } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconName, iconProps } from '../VIcon/iconProps'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useIconClickHandlers } from '../../composables/useIconClickHandlers'\nimport { useClearable } from '../../composables/useClearable'\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTextLimit } from '../../composables/useTextLimit'\nimport { useMessages } from '../../i18n/state'\n\nimport { inputGroupKey } from './context'\n\n/** The height of the field: 32, 40 or 48 pixels. */\nexport type InputSize = 'sm' | 'md' | 'lg'\n\n/** The native type of the input, which also decides the keyboard a phone offers. */\nexport type InputType = 'text' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'url'\n\ninterface InputProps {\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: InputSize\n /** Takes 4px off the height, leaving the padding, the text and the icons as they are. */\n compact?: boolean\n /**\n * The native type of the input, which is also what tells a phone which keyboard to\n * offer — a numeric pad for `number`, an @ key for `email`.\n */\n type?: InputType\n /**\n * Marks the field as invalid whatever the browser thinks. This is the route for a\n * rule only the server can check; anything the browser can validate on its own\n * already colours the field without this prop.\n */\n invalid?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the value without allowing it to be changed. The field can still be\n * focused and copied from, and it hides the clear button — unless `clearVisible`\n * answers that question explicitly.\n */\n readonly?: boolean\n /** The label above the field, tied to it so that clicking it focuses the field. */\n label?: string\n /**\n * A line of help under the field. It is tied to the input for assistive\n * technology, so it is read out along with the label.\n */\n hint?: string\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes\n * a real button as soon as a `@click:icon-start` listener is attached — in which\n * case it needs `iconStartLabel`. The `#start` slot is rendered after it, so a\n * field composed on top of this one can show both.\n */\n iconStart?: IconSource\n /**\n * The same at the end of the field. The `#end` slot replaces it, and the loading\n * spinner takes its place while it turns.\n */\n iconEnd?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /** What the end icon does, in words, once it is clickable. */\n iconEndLabel?: string\n /** Shows a spinner at the end of the field, in place of the end icon or slot. */\n loading?: boolean\n /**\n * What screen readers announce while the spinner turns. It falls back to the\n * design system dictionary.\n */\n loadingLabel?: string\n /**\n * Offers a cross that empties the field. It appears when there is something to\n * clear and the field can be edited.\n */\n clearable?: boolean\n /**\n * Decides whether the cross is shown, instead of letting the field work it out\n * from its own content — a read-only field included.\n *\n * It exists for the components built on top of this one, where what there is to\n * clear is not the text: VCombobox holds its selection as chips beside the field,\n * and a read-only date or time picker changes its value through a panel rather\n * than by typing.\n */\n clearVisible?: boolean\n /** What the clear button does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The maximum number of characters. By default this is the browser's own limit,\n * which simply refuses anything beyond it.\n */\n maxlength?: number\n /**\n * Turns that limit into a soft one: the reader may type past it, and the field\n * goes into error instead of silently refusing the keystrokes. It is reported\n * through the native validity, so a form cannot be submitted over the limit.\n */\n softLimit?: boolean\n /**\n * Shows how much has been typed, at the end of the field: \"12/80\" against a limit,\n * or just \"12\" without one.\n */\n counter?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<InputProps>(), {\n size: 'md',\n compact: false,\n type: 'text',\n invalid: false,\n disabled: false,\n readonly: false,\n label: undefined,\n hint: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconStartLabel: undefined,\n iconEndLabel: undefined,\n loading: false,\n loadingLabel: undefined,\n clearable: false,\n clearVisible: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n softLimit: false,\n counter: false,\n})\n\nconst emit = defineEmits<{\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n /** The end icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-end': [event: MouseEvent]\n /** The clear button was pressed. The field has already been emptied. */\n clear: []\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its\n * place — which is what lets a field composed on top of this one put the chips\n * standing for its values here beside an icon it still wants drawn.\n */\n start?(): unknown\n /**\n * Controls of your own inside the field, placed before the field's own — the clear\n * cross and the end icon. It is the slot for something that acts on the VALUE, such\n * as the AM/PM button of a 12-hour time field, which belongs beside the text rather\n * than past the controls that clear and open.\n */\n 'value-end'?(): unknown\n /**\n * Content at the end of the field, which replaces `iconEnd`. It is hidden while\n * the field is loading, the spinner taking that place.\n */\n end?(): unknown\n}>()\n\n/**\n * The value, typed as text OR a number rather than text alone. On an\n * `<input type=\"number\">` Vue converts the value to a number by itself, so a\n * string-only model would hand a number back to a consumer who passed a string in,\n * and Vue would report an invalid prop.\n */\nconst model = defineModel<string | number>({ default: '' })\n\n/**\n * The value seen as text. Every measurement of its length — the counter, the soft\n * limit, whether the clear cross has anything to clear — goes through this, since a\n * number has no length of its own.\n */\nconst modelText = computed(() => String(model.value ?? ''))\n\nconst { attrs, rootClass, rootStyle, forwardedAttrs: restAttrs } = useRootAttrs()\n\n// A VInputGroup joins several controls into one object, so the shape of this field is the\n// row's decision rather than its own: a segment of another height stops lining up with its\n// neighbours and the merged border no longer reads as a single box. `disabled` is the one\n// read as an OR, the two answers being cumulative (VInput/context.ts).\nconst group = inject(inputGroupKey, null)\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape(props, group)\n\n// The prop keeps priority; what it falls back to is the dictionary, so the default\n// wording follows the language the design system is set to.\nconst m = useMessages()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.common.clear)\n\nconst { fieldId, hintId, describedBy } = useFieldIds(attrs, () => !!props.hint)\n\nconst { hasIconStartHandler, hasIconEndHandler } = useIconClickHandlers({\n name: 'VInput',\n iconStartLabel: props.iconStartLabel,\n iconEndLabel: props.iconEndLabel,\n})\n\nconst controlEl = ref<HTMLInputElement | null>(null)\n\nconst { showClear, onClear } = useClearable({\n clearable: () => props.clearable,\n clearVisible: () => props.clearVisible,\n disabled: () => resolvedDisabled.value,\n readonly: () => props.readonly,\n text: () => modelText.value,\n controlEl,\n onCleared: () => {\n model.value = ''\n emit('clear')\n },\n})\n\n// The counter and the soft limit both measure `modelText` rather than the model\n// itself, a number having no length.\nconst { counterText, over } = useTextLimit({\n el: controlEl,\n text: () => modelText.value,\n maxlength: () => props.maxlength,\n softLimit: () => props.softLimit,\n})\n\n// The real input sits inside the wrapper, out of reach of whoever renders this component.\n// These three are how the components built on it get there — VCombobox refocuses the field\n// and selects its text this way.\ndefineExpose({\n /** Moves the focus to the real input. */\n focus: (options?: FocusOptions) => controlEl.value?.focus(options),\n /** Selects everything in the field, as VCombobox does when it reopens its panel. */\n select: () => controlEl.value?.select(),\n /** The real `<input>`, for what neither of the two above covers. */\n el: controlEl,\n})\n</script>\n\n<template>\n <div\n class=\"v-input v-control\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-disabled=\"resolvedDisabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <VTypography v-if=\"label\" as=\"label\" variant=\"label\" class=\"v-input-label\" :for=\"fieldId\">\n {{ label }}\n </VTypography>\n\n <div class=\"v-input-field\">\n <!-- TRAP — the start icon is rendered BEFORE the slot, where the end icon is the\n slot's own fallback. That asymmetry is what the composed fields need: VCombobox\n and VFileInput fill `#start` with the chips standing for their values, which is\n OTHER content in the same zone rather than another way of drawing the icon. As a\n fallback the icon would vanish the moment a value was chosen — silently, and the\n clickable variant with it. The end zone has no such consumer: what is put there\n really does replace the icon. -->\n <button\n v-if=\"iconStart && hasIconStartHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-start\"\n :aria-label=\"iconStartLabel ?? iconName(iconStart)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-start', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconStart)\" />\n </button>\n <VIcon v-else-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" class=\"v-input-icon-start\" />\n <slot name=\"start\" />\n\n <input\n v-bind=\"restAttrs\"\n :id=\"fieldId\"\n ref=\"controlEl\"\n v-model=\"model\"\n class=\"v-input-control\"\n :type=\"type\"\n :maxlength=\"softLimit ? undefined : maxlength\"\n :disabled=\"resolvedDisabled\"\n :readonly=\"readonly || undefined\"\n :aria-invalid=\"invalid || undefined\"\n :aria-describedby=\"describedBy\"\n />\n\n <span\n v-if=\"counter\"\n class=\"v-input-counter v-field-counter\"\n :data-over=\"over ? '' : undefined\"\n >\n {{ counterText }}\n </span>\n\n <!-- Before the clear cross rather than after it, so that a consumer's control reads\n and tabs in the same order: what acts on the value sits next to the value, and\n the field's own controls stay together at the end. -->\n <slot name=\"value-end\" />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-clear\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"onClear\"\n >\n <VIcon :name=\"closeIcon\" />\n </button>\n\n <VSpinner v-if=\"loading\" :label=\"loadingLabel\" />\n <slot v-else name=\"end\">\n <button\n v-if=\"iconEnd && hasIconEndHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-end\"\n :aria-label=\"iconEndLabel ?? iconName(iconEnd)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-end', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconEnd)\" />\n </button>\n <VIcon v-else-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" class=\"v-input-icon-end\" />\n </slot>\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The label and the hint are rendered by VTypography, which carries their type. The\n .v-input-label and .v-input-hint classes remain as hooks: a consumer overrides\n through them, and the disabled state below reaches them that way. */\n\n /* This is the box that carries the border, the background and the focus ring.\n `--field-border-color` is the single source of truth for its colour, and the\n hover, error and disabled states do nothing but redefine it.\n\n Nothing here restates the size scale: the `--control-*` variables are inherited\n from the v-control root (styles/control-size.css), the icon context included. */\n .v-input-field {\n --field-border-color: var(--vectis-color-border-strong);\n\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n height: var(--control-height);\n padding-inline: var(--control-padding-inline-field);\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--field-border-color);\n border-radius: var(--vectis-radius-interactive);\n font-size: var(--control-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-input-control {\n flex: 1;\n min-width: 0;\n height: 100%;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n outline: none; /* the focus ring is drawn by the field around it, not here */\n }\n\n .v-input-control::placeholder {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The browsers' own in-field controls are removed, because the design system draws\n its own: WebKit's search cross would sit beside the `clearable` one, the number\n spinners and Edge's password-reveal eye would clash with the icons. */\n .v-input-control::-webkit-search-cancel-button,\n .v-input-control::-webkit-search-decoration,\n .v-input-control::-webkit-search-results-button,\n .v-input-control::-webkit-search-results-decoration,\n .v-input-control::-webkit-inner-spin-button,\n .v-input-control::-webkit-outer-spin-button {\n -webkit-appearance: none;\n appearance: none;\n }\n\n .v-input-control[type='number'] {\n appearance: textfield;\n }\n\n .v-input-control::-ms-reveal {\n display: none;\n }\n\n /* The yellow autofill background is painted by the browser on the input itself,\n inside our field. Its colour cannot be changed, but matching the field's radius\n at least stops it from showing square corners inside a rounded box. */\n .v-input-control:-webkit-autofill {\n border-radius: var(--vectis-radius-interactive);\n }\n\n /* A read-only field sinks slightly into the page but keeps its text at full\n strength — the value is there to be read — and still shows the accent focus ring.\n The state is read from [data-readonly] and never from `:read-only`, which the\n browser also matches on a disabled field.\n\n TRAP — this block must stay FIRST in the sequence of states: read-only, then\n hover, focus, invalid and disabled. Every one of these selectors weighs (0,3,0),\n `:has()` taking the specificity of what it contains, so nothing but the source\n order arbitrates between them. Moved further down, this rule would repaint the\n error border and the accent focus ring grey, with no error anywhere. What\n belongs to a read-only field is its BASE colour alone. */\n .v-input[data-readonly] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-input-field:hover:not(:has(.v-input-control:focus)):not(\n :has(\n .v-input-control:disabled,\n .v-input-control:user-invalid,\n .v-input-control[aria-invalid='true']\n )\n ) {\n --field-border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /* The focused field appears to have a two-pixel border: it is really its own 1px\n border plus a 1px shadow of the same colour just outside it, which costs no\n layout and therefore makes nothing jump.\n\n The selector watches the CONTROL's focus and not `:focus-within`, so that when\n one of the field's own buttons — the cross, a clickable icon — takes keyboard\n focus, only that button's outline lights up: two indicators at once would be\n unreadable. And it is `:focus` rather than `:focus-visible`, because a text field\n shows its focus even when it was reached with the mouse.\n\n The transparent outline is the safety net for Windows forced colours, which drop\n box-shadows entirely; an outline survives and keeps the field marked. */\n .v-input-field:has(.v-input-control:focus) {\n --field-border-color: var(--vectis-color-accent);\n\n box-shadow: 0 0 0 1px var(--field-border-color);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /* The invalid state, from the browser's own verdict first and from the `invalid`\n prop second. Only the colour variable is changed, which is why the border AND\n the focus ring both turn red without either being restated. */\n .v-input-field:has(.v-input-control:user-invalid),\n .v-input-field:has(.v-input-control[aria-invalid='true']) {\n --field-border-color: var(--vectis-color-danger);\n }\n\n /* The counter's type and its overflow colour are `.v-field-counter`'s (styles/field.css),\n shared with the counters VTextarea and VFileInput draw under their field. Inside the\n box it must not shrink, or a long value would squeeze its figures. */\n .v-input-counter {\n flex: none;\n }\n\n /* A decorative icon is drawn in a muted grey, so it stays quieter than the text\n being typed beside it. */\n .v-input-field > .v-icon {\n color: var(--vectis-color-text-muted);\n }\n\n .v-input-field > .v-spinner {\n font-size: var(--vectis-icon-size);\n }\n\n /* A disabled field greys out through the colour tokens, the same ones VCheckbox and\n VRadio use, and never through opacity. It comes LAST in the sequence of states,\n which at equal specificity is what makes it win over all of them, the error\n included: a disabled field is not submitted, so it has nothing to report.\n\n The text here is `text-muted` and not the `text-subtle` used by the label and the\n hint below, because those sit on the page surface where this sits on\n `surface-muted` — against which subtle falls to a 4.4:1 contrast. The control\n itself is exempt from that rule, being natively disabled, but the icons the field\n CONTAINS are not, and they inherit this colour. */\n .v-input[data-disabled] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n cursor: not-allowed;\n }\n\n .v-input[data-disabled] .v-input-label,\n .v-input[data-disabled] .v-input-hint,\n .v-input[data-disabled] .v-input-counter {\n color: var(--vectis-color-text-subtle);\n }\n\n .v-input[data-disabled] .v-input-action,\n .v-input[data-disabled] .v-input-field > .v-icon {\n color: inherit;\n cursor: not-allowed;\n }\n\n .v-input-control:disabled {\n cursor: not-allowed;\n }\n\n /* Two arrangements a field composed on top of this one opts into with a class on this\n root: VCombobox and VFileInput, the two that put chips inside the field. They live in\n THIS sheet because it is the one both are guaranteed to load, being built on VInput —\n the core sheet would charge every consumer for them.\n\n `.v-input-end-pinned` lifts the clear cross and whatever carries `.v-input-icon-end` —\n the end icon, or a composed field's own chevron and spinner — out of the flow, so they\n stay pinned to the end of the field and vertically centred whatever the chips do. The\n room they occupy is then reserved with padding, or the text and the chips would run\n underneath. That reservation is written as exactly what the flow would have produced\n with them left in place — one glyph's width each from the field's padding, one gap\n between two of them — and reads the same two variables as the insets below, so the room\n reserved and the glyphs it protects cannot drift apart.\n\n Whether the cross takes its room is read off the markup (`:has`): the cross is rendered\n exactly when there is something to clear, so the padding cannot disagree with it.\n\n The centring is a translation kept SEPARATE from `rotate`, which VCombobox's chevron\n turns when its panel opens. */\n .v-input-end-pinned .v-input-field {\n position: relative;\n padding-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n .v-input-end-pinned .v-input-field:has(> .v-input-clear) {\n padding-inline-end: calc(\n var(--control-padding-inline-field) + 2 * var(--vectis-icon-size) + 2 * var(--control-gap)\n );\n }\n\n .v-input-end-pinned :is(.v-input-clear, .v-input-icon-end) {\n position: absolute;\n inset-inline-end: var(--control-padding-inline-field);\n top: 50%;\n translate: 0 -50%;\n }\n\n /* TRAP — this inset reads in GLYPHS and not in buttons. The cross is one of the field's\n buttons, whose negative margin — half the difference between icon and button — already\n cancels its own overhang, so an inset lands on the glyph's edge. Measured in button\n widths instead, it pushes the cross a whole gap too far, with nothing to signal it.\n It overrides the rule above at the same weight, so it has to stay after it. */\n .v-input-end-pinned .v-input-clear {\n inset-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n /* `.v-input-chips` lets the chips WRAP onto several rows, the field growing instead of\n scrolling but never shrinking below an ordinary control.\n\n The input is forced to the chips' height rather than the full height it inherits: its\n natural height is greater than a chip's, and the field would grow the moment it was\n focused. That height is `--chip-height`, written inline on the composed field's root\n by the helper that also picks the chips' size (utils/chip.ts), so the two cannot drift.\n\n TRAP — the control rule must stay at (0,2,0). VCombobox folds its search input away\n with a (0,3,0) rule that zeroes this height, and at equal weight the winner would be\n whichever of the two sheets a bundler put last. */\n .v-input-chips .v-input-field {\n flex-wrap: wrap;\n height: auto;\n min-height: var(--control-height);\n padding-block: var(--vectis-space-1);\n }\n\n .v-input-chips .v-input-control {\n height: var(--chip-height);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-field {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsIA,IAAM,IAAQ,GAuBR,IAAO,GAoCP,IAAQ,EAA4B,GAAA,YAAgB,GAOpD,IAAY,QAAe,OAAO,EAAM,SAAS,EAAE,CAAC,GAEpD,EAAE,UAAO,cAAW,cAAW,gBAAgB,MAAc,EAAa,GAO1E,EACJ,MAAM,GACN,SAAS,IACT,UAAU,MACR,EAAgB,GALN,EAAO,IAAe,IAKT,CAAK,GAI1B,KAAI,EAAY,GAChB,KAAqB,QAAe,EAAM,cAAc,GAAE,MAAM,OAAO,KAAK,GAE5E,EAAE,YAAS,YAAQ,oBAAgB,GAAY,SAAa,CAAC,CAAC,EAAM,IAAI,GAExE,EAAE,yBAAqB,0BAAsB,GAAqB;GACtE,MAAM;GACN,gBAAgB,EAAM;GACtB,cAAc,EAAM;EACtB,CAAC,GAEK,IAAY,GAA6B,IAAI,GAE7C,EAAE,eAAW,eAAY,GAAa;GAC1C,iBAAiB,EAAM;GACvB,oBAAoB,EAAM;GAC1B,gBAAgB,EAAiB;GACjC,gBAAgB,EAAM;GACtB,YAAY,EAAU;GACtB;GACA,iBAAiB;IAEf,AADA,EAAM,QAAQ,IACd,EAAK,OAAO;GACd;EACF,CAAC,GAIK,EAAE,iBAAa,aAAS,EAAa;GACzC,IAAI;GACJ,YAAY,EAAU;GACtB,iBAAiB,EAAM;GACvB,iBAAiB,EAAM;EACzB,CAAC;SAKD,EAAa;GAEX,QAAQ,MAA2B,EAAU,OAAO,MAAM,CAAO;GAEjE,cAAc,EAAU,OAAO,OAAO;GAEtC,IAAI;EACN,CAAC,mBAIC,EA0FM,OAAA;GAzFJ,OAAK,EAAA,CAAC,qBACE,EAAA,CAAA,CAAS,CAAA;GAChB,OAAK,EAAE,EAAA,CAAA,CAAS;GAChB,aAAW,EAAA,CAAA;GACX,gBAAc,EAAA,EAAA,IAAe,KAAQ,KAAA;GACrC,iBAAe,EAAA,CAAA,IAAgB,KAAQ,KAAA;GACvC,iBAAe,EAAA,WAAQ,KAAQ,KAAA;;GAEb,EAAA,SAAA,EAAA,GAAnB,EAEc,GAAA;;IAFY,IAAG;IAAQ,SAAQ;IAAQ,OAAM;IAAiB,KAAK,EAAA,CAAA;;qBACpE,CAAA,EAAA,EAAR,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA;;;GAGV,EAwEM,OAxEN,GAwEM;IA/DI,EAAA,aAAa,EAAA,EAAA,KAAA,EAAA,GADrB,EASS,UAAA;;KAPP,MAAK;KACL,OAAM;KACL,cAAY,EAAA,kBAAkB,EAAA,CAAA,CAAQ,CAAC,EAAA,SAAS;KAChD,UAAU,EAAA,CAAA;KACV,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,oBAAqB,CAAM;QAEvC,EAAuC,GAAA,EAAA,EAAxB,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,CAAA,CAAA,GAAA,MAAA,EAAA,CAAA,GAAA,GAAA,CAAA,KAElB,EAAA,aAAA,EAAA,GAAlB,EAAwF,GAAxF,EAAwF,EAAA,KAAA,EAAA,GAAnD,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,GAAA,EAAG,OAAM,qBAAoB,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA;IACrF,EAAqB,EAAA,QAAA,OAAA;MAErB,EAYE,SAZF,EACU,EAWR,CAAA,GAXiB;KAChB,IAAI,EAAA,CAAA;cACD;KAAJ,KAAI;8CACU,QAAA;KACd,OAAM;KACL,MAAM,EAAA;KACN,WAAW,EAAA,YAAY,KAAA,IAAY,EAAA;KACnC,UAAU,EAAA,CAAA;KACV,UAAU,EAAA,YAAY,KAAA;KACtB,gBAAc,EAAA,WAAW,KAAA;KACzB,oBAAkB,EAAA,EAAA;2BAPV,EAAA,KAAK,CAAA,CAAA;IAWR,EAAA,WAAA,EAAA,GADR,EAMO,QAAA;;KAJL,OAAM;KACL,aAAW,EAAA,EAAA,IAAI,KAAQ,KAAA;SAErB,EAAA,EAAA,CAAW,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;IAMhB,EAAyB,EAAA,QAAA,WAAA;IAGjB,EAAA,EAAA,KAAA,EAAA,GADR,EAQS,UAAA;;KANP,MAAK;KACL,OAAM;KACL,cAAY,GAAA;KACZ,SAAK,AAAA,EAAA,QAAA,GAAA,MAAE,EAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA,GAAA,CAAA;QAER,EAA2B,GAAA,EAAnB,MAAM,EAAA,CAAA,EAAS,GAAA,MAAA,GAAA,CAAA,MAAA,CAAA,CAAA,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;IAGT,EAAA,WAAA,EAAA,GAAhB,EAAiD,GAAA;;KAAvB,OAAO,EAAA;8BACjC,EAYO,EAAA,QAAA,OAAA,CAAA,SAAA,CAVG,EAAA,WAAW,EAAA,EAAA,KAAA,EAAA,GADnB,EASS,UAAA;;KAPP,MAAK;KACL,OAAM;KACL,cAAY,EAAA,gBAAgB,EAAA,CAAA,CAAQ,CAAC,EAAA,OAAO;KAC5C,UAAU,EAAA,CAAA;KACV,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,kBAAmB,CAAM;QAErC,EAAqC,GAAA,EAAA,EAAtB,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,CAAA,CAAA,GAAA,MAAA,EAAA,CAAA,GAAA,GAAA,CAAA,KAEhB,EAAA,WAAA,EAAA,GAAlB,EAAkF,GAAlF,EAAkF,EAAA,KAAA,EAAA,GAA/C,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,GAAA,EAAG,OAAM,mBAAkB,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;;GAIhE,EAAA,QAAA,EAAA,GAAnB,EAEc,GAAA;;IAFY,IAAI,EAAA,EAAA;IAAQ,SAAQ;IAAU,MAAK;IAAQ,OAAM;;qBAC/D,CAAA,EAAA,EAAP,EAAA,IAAI,GAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"VInput.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VInput/VInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * A complete text field: label above, hint below, icons inside (clickable ones included), a\n * counter, a clear cross and a loading state, all arranged around a real `<input>`.\n *\n * Wrapper-root, so the attrs split: `class`/`style` stay on the root, where a consumer\n * expects to style the field, and everything else is forwarded to the input, where `name`,\n * `required` and the `aria-*` actually do something.\n *\n * Validation stays the browser's. The field turns red through `:user-invalid`, which only\n * reacts once the reader has left it, and even the soft limit is reported by\n * `setCustomValidity` rather than an event of our own — so a consumer's `<form>` sees\n * exactly what a bare input would give it.\n *\n * The behavioural JS is the v-model and the clear cross, which has to move focus back to the\n * field: it disappears the moment it is clicked, and focus would otherwise fall to the page.\n */\nimport { computed, inject, ref } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconName, iconProps } from '../VIcon/iconProps'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { useFieldIds } from '../../composables/useFieldIds'\nimport { useIconClickHandlers } from '../../composables/useIconClickHandlers'\nimport { useClearable } from '../../composables/useClearable'\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useTextLimit } from '../../composables/useTextLimit'\nimport { useMessages } from '../../i18n/state'\n\nimport { inputGroupKey } from './context'\n\n/** The height of the field: 32, 40 or 48 pixels. */\nexport type InputSize = 'sm' | 'md' | 'lg'\n\n/** The native type of the input, which also decides the keyboard a phone offers. */\nexport type InputType = 'text' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'url'\n\ninterface InputProps {\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: InputSize\n /** Takes 4px off the height, leaving the padding, the text and the icons as they are. */\n compact?: boolean\n /**\n * The native type of the input, which is also what tells a phone which keyboard to\n * offer: a numeric pad for `number`, an @ key for `email`.\n */\n type?: InputType\n /**\n * Marks the field as invalid whatever the browser thinks. This is the route for a\n * rule only the server can check; anything the browser can validate on its own\n * already colours the field without this prop.\n */\n invalid?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the value without allowing it to be changed. The field can still be\n * focused and copied from, and it hides the clear button, unless `clearVisible`\n * answers that question explicitly.\n */\n readonly?: boolean\n /**\n * Refuses the keyboard without drawing the field as read-only: the native attribute is\n * set, but the field keeps its ordinary look and its clear cross. For a field whose value\n * comes from somewhere else, a picker or a file dialog, and which is no less editable for\n * it.\n */\n noTyping?: boolean\n /** The label above the field, tied to it so that clicking it focuses the field. */\n label?: string\n /**\n * A line of help under the field. It is tied to the input for assistive\n * technology, so it is read out along with the label.\n */\n hint?: string\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes\n * a real button as soon as a `@click:icon-start` listener is attached, in which\n * case it needs `iconStartLabel`. The `#start` slot is rendered after it, so a\n * field composed on top of this one can show both.\n */\n iconStart?: IconSource\n /**\n * The same at the end of the field. The `#end` slot replaces it, and the loading\n * spinner takes its place while it turns.\n */\n iconEnd?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /** What the end icon does, in words, once it is clickable. */\n iconEndLabel?: string\n /** Shows a spinner at the end of the field, in place of the end icon or slot. */\n loading?: boolean\n /**\n * What screen readers announce while the spinner turns. It falls back to the\n * design system dictionary.\n */\n loadingText?: string\n /**\n * Offers a cross that empties the field. It appears when there is something to\n * clear and the field can be edited.\n */\n clearable?: boolean\n /**\n * Decides whether the cross is shown, instead of letting the field work it out\n * from its own content (a read-only field included).\n *\n * It exists for the components built on top of this one, where what there is to\n * clear is not the text: VCombobox holds its selection as chips beside the field,\n * and a read-only date or time picker changes its value through a panel rather\n * than by typing.\n */\n clearVisible?: boolean\n /** What the clear button does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The maximum number of characters. By default this is the browser's own limit,\n * which simply refuses anything beyond it.\n */\n maxlength?: number\n /**\n * Turns that limit into a soft one: the reader may type past it, and the field\n * goes into error instead of silently refusing the keystrokes. It is reported\n * through the native validity, so a form cannot be submitted over the limit.\n */\n softLimit?: boolean\n /**\n * Shows how much has been typed, at the end of the field: \"12/80\" against a limit,\n * or just \"12\" without one.\n */\n counter?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<InputProps>(), {\n size: 'md',\n compact: false,\n type: 'text',\n invalid: false,\n disabled: false,\n readonly: false,\n label: undefined,\n hint: undefined,\n iconStart: undefined,\n iconEnd: undefined,\n iconStartLabel: undefined,\n iconEndLabel: undefined,\n loading: false,\n loadingText: undefined,\n clearable: false,\n clearVisible: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n softLimit: false,\n noTyping: false,\n counter: false,\n})\n\nconst emit = defineEmits<{\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n /** The end icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-end': [event: MouseEvent]\n /** The clear button was pressed. The field has already been emptied. */\n clear: []\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its\n * place, which is what lets a field composed on top of this one put the chips\n * standing for its values here beside an icon it still wants drawn.\n */\n start?(): unknown\n /**\n * Controls of your own inside the field, placed before the field's own: the clear\n * cross and the end icon. It is the slot for something that acts on the VALUE, such\n * as the AM/PM button of a 12-hour time field, which belongs beside the text rather\n * than past the controls that clear and open.\n */\n 'value-end'?(): unknown\n /**\n * Content at the end of the field, which replaces `iconEnd`. It is hidden while\n * the field is loading, the spinner taking that place.\n */\n end?(): unknown\n}>()\n\n/**\n * The value, typed as text OR a number rather than text alone. On an\n * `<input type=\"number\">` Vue converts the value to a number by itself, so a\n * string-only model would hand a number back to a consumer who passed a string in,\n * and Vue would report an invalid prop.\n */\nconst model = defineModel<string | number>({ default: '' })\n\n/**\n * The value seen as text. Every measurement of its length — the counter, the soft\n * limit, whether the clear cross has anything to clear — goes through this, since a\n * number has no length of its own.\n */\nconst modelText = computed(() => String(model.value ?? ''))\n\nconst { attrs, rootClass, rootStyle, forwardedAttrs: restAttrs } = useRootAttrs()\n\n// A VInputGroup joins several controls into one object, so the shape of this field is the\n// row's decision rather than its own: a segment of another height stops lining up with its\n// neighbours and the merged border no longer reads as a single box. `disabled` is the one\n// read as an OR, the two answers being cumulative (VInput/context.ts).\nconst group = inject(inputGroupKey, null)\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape(props, group)\n\n// The prop keeps priority; what it falls back to is the dictionary, so the default\n// wording follows the language the design system is set to.\nconst m = useMessages()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.common.clear)\n\nconst { fieldId, hintId, counterId, describedBy } = useFieldIds(\n attrs,\n () => !!props.hint,\n () => props.counter,\n)\n\nconst { hasIconStartHandler, hasIconEndHandler } = useIconClickHandlers({\n name: 'VInput',\n iconStartLabel: props.iconStartLabel,\n iconEndLabel: props.iconEndLabel,\n})\n\nconst controlEl = ref<HTMLInputElement | null>(null)\n\nconst { showClear, onClear } = useClearable({\n clearable: () => props.clearable,\n clearVisible: () => props.clearVisible,\n disabled: () => resolvedDisabled.value,\n readonly: () => props.readonly,\n text: () => modelText.value,\n controlEl,\n onCleared: () => {\n model.value = ''\n emit('clear')\n },\n})\n\n// The counter and the soft limit both measure `modelText` rather than the model\n// itself, a number having no length.\nconst { counterText, over } = useTextLimit({\n el: controlEl,\n text: () => modelText.value,\n maxlength: () => props.maxlength,\n softLimit: () => props.softLimit,\n})\n\n// The real input sits inside the wrapper, out of reach of whoever renders this component.\n// These three are how the components built on it get there — VCombobox refocuses the field\n// and selects its text this way.\ndefineExpose({\n /** Moves the focus to the real input. */\n focus: (options?: FocusOptions) => controlEl.value?.focus(options),\n /** Selects everything in the field, as VCombobox does when it reopens its panel. */\n select: () => controlEl.value?.select(),\n /** The real `<input>`, for what neither of the two above covers. */\n el: controlEl,\n})\n</script>\n\n<template>\n <div\n class=\"v-input v-control\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-disabled=\"resolvedDisabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <VTypography v-if=\"label\" as=\"label\" variant=\"label\" class=\"v-input-label\" :for=\"fieldId\">\n {{ label }}\n </VTypography>\n\n <div class=\"v-input-field\">\n <!-- TRAP — the start icon is rendered BEFORE the slot, where the end icon is the\n slot's own fallback. That asymmetry is what the composed fields need: VCombobox\n and VFileInput fill `#start` with the chips standing for their values, which is\n OTHER content in the same zone rather than another way of drawing the icon. As a\n fallback the icon would vanish the moment a value was chosen — silently, and the\n clickable variant with it. The end zone has no such consumer: what is put there\n really does replace the icon. -->\n <button\n v-if=\"iconStart && hasIconStartHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-start\"\n :aria-label=\"iconStartLabel ?? iconName(iconStart)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-start', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconStart)\" />\n </button>\n <VIcon v-else-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" class=\"v-input-icon-start\" />\n <slot name=\"start\" />\n\n <input\n :id=\"fieldId\"\n ref=\"controlEl\"\n v-model=\"model\"\n :aria-invalid=\"invalid || undefined\"\n v-bind=\"restAttrs\"\n class=\"v-input-control\"\n :type=\"type\"\n :maxlength=\"softLimit ? undefined : maxlength\"\n :disabled=\"resolvedDisabled\"\n :readonly=\"readonly || noTyping || undefined\"\n :aria-describedby=\"describedBy\"\n />\n\n <span\n v-if=\"counter\"\n :id=\"counterId\"\n class=\"v-input-counter v-field-counter\"\n :data-over=\"over ? '' : undefined\"\n >\n {{ counterText }}\n </span>\n\n <!-- Before the clear cross rather than after it, so that a consumer's control reads\n and tabs in the same order: what acts on the value sits next to the value, and\n the field's own controls stay together at the end. -->\n <slot name=\"value-end\" />\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-clear\"\n :aria-label=\"resolvedClearLabel\"\n @click=\"onClear\"\n >\n <VIcon :name=\"closeIcon\" />\n </button>\n\n <VSpinner v-if=\"loading\" :label=\"loadingText\" />\n <slot v-else name=\"end\">\n <button\n v-if=\"iconEnd && hasIconEndHandler\"\n type=\"button\"\n class=\"v-input-action v-field-action v-input-icon-end\"\n :aria-label=\"iconEndLabel ?? iconName(iconEnd)\"\n :disabled=\"resolvedDisabled\"\n @click=\"emit('click:icon-end', $event)\"\n >\n <VIcon v-bind=\"iconProps(iconEnd)\" />\n </button>\n <VIcon v-else-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" class=\"v-input-icon-end\" />\n </slot>\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-input {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The label and the hint are rendered by VTypography, which carries their type. The\n .v-input-label and .v-input-hint classes remain as hooks: a consumer overrides\n through them, and the disabled state below reaches them that way. */\n\n /* This is the box that carries the border, the background and the focus ring.\n `--field-border-color` is the single source of truth for its colour, and the\n hover, error and disabled states do nothing but redefine it.\n\n Nothing here restates the size scale: the `--control-*` variables are inherited\n from the v-control root (styles/control-size.css), the icon context included. */\n .v-input-field {\n --field-border-color: var(--vectis-color-border-strong);\n\n display: flex;\n align-items: center;\n gap: var(--control-gap);\n height: var(--control-height);\n padding-inline: var(--control-padding-inline-field);\n background: var(--vectis-color-surface);\n color: var(--vectis-color-text);\n border: 1px solid var(--field-border-color);\n border-radius: var(--vectis-radius-interactive);\n font-size: var(--control-font-size);\n transition:\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-input-control {\n flex: 1;\n min-width: 0;\n height: 100%;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n outline: none; /* the focus ring is drawn by the field around it, not here */\n }\n\n .v-input-control::placeholder {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The browsers' own in-field controls are removed, because the design system draws\n its own: WebKit's search cross would sit beside the `clearable` one, the number\n spinners and Edge's password-reveal eye would clash with the icons. */\n .v-input-control::-webkit-search-cancel-button,\n .v-input-control::-webkit-search-decoration,\n .v-input-control::-webkit-search-results-button,\n .v-input-control::-webkit-search-results-decoration,\n .v-input-control::-webkit-inner-spin-button,\n .v-input-control::-webkit-outer-spin-button {\n -webkit-appearance: none;\n appearance: none;\n }\n\n .v-input-control[type='number'] {\n appearance: textfield;\n }\n\n .v-input-control::-ms-reveal {\n display: none;\n }\n\n /* The yellow autofill background is painted by the browser on the input itself,\n inside our field. Its colour cannot be changed, but matching the field's radius\n at least stops it from showing square corners inside a rounded box. */\n .v-input-control:-webkit-autofill {\n border-radius: var(--vectis-radius-interactive);\n }\n\n /* A read-only field sinks slightly into the page but keeps its text at full\n strength — the value is there to be read — and still shows the accent focus ring.\n The state is read from [data-readonly] and never from `:read-only`, which the\n browser also matches on a disabled field.\n\n TRAP — this block must stay FIRST in the sequence of states: read-only, then\n hover, focus, invalid and disabled. The read-only, focus, invalid and disabled\n selectors all weigh (0,3,0), `:has()` taking the specificity of what it contains, so\n nothing but the source order arbitrates between them. Hover alone weighs more, (0,6,0):\n its `:not()` keeps it off a focused, invalid or disabled field, and on a read-only\n one it darkens the border as on any other. Moved further down, this rule would repaint the\n error border and the accent focus ring grey, with no error anywhere. What\n belongs to a read-only field is its BASE colour alone. */\n .v-input[data-readonly] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-sunken);\n }\n\n .v-input-field:hover:not(:has(.v-input-control:focus)):not(\n :has(\n .v-input-control:disabled,\n .v-input-control:user-invalid,\n .v-input-control[aria-invalid='true']\n )\n ) {\n --field-border-color: color-mix(\n in oklab,\n var(--vectis-color-border-strong),\n var(--vectis-color-text) 15%\n );\n }\n\n /* The focused field appears to have a two-pixel border: it is really its own 1px\n border plus a 1px shadow of the same colour just outside it, which costs no\n layout and therefore makes nothing jump.\n\n The selector watches the CONTROL's focus and not `:focus-within`, so that when\n one of the field's own buttons — the cross, a clickable icon — takes keyboard\n focus, only that button's outline lights up: two indicators at once would be\n unreadable. And it is `:focus` rather than `:focus-visible`, because a text field\n shows its focus even when it was reached with the mouse.\n\n The transparent outline is the safety net for Windows forced colours, which drop\n box-shadows entirely; an outline survives and keeps the field marked. */\n .v-input-field:has(.v-input-control:focus) {\n --field-border-color: var(--vectis-color-accent);\n\n box-shadow: 0 0 0 1px var(--field-border-color);\n outline: var(--vectis-focus-ring-width) solid transparent;\n }\n\n /* The invalid state, from the browser's own verdict first and from the `invalid`\n prop second. Only the colour variable is changed, which is why the border AND\n the focus ring both turn red without either being restated. */\n .v-input-field:has(.v-input-control:user-invalid),\n .v-input-field:has(.v-input-control[aria-invalid='true']) {\n --field-border-color: var(--vectis-color-danger);\n }\n\n /* The counter's type and its overflow colour are `.v-field-counter`'s (styles/field.css),\n shared with the counters VTextarea and VFileInput draw under their field. Inside the\n box it must not shrink, or a long value would squeeze its figures. */\n .v-input-counter {\n flex: none;\n }\n\n /* A decorative icon is drawn in a muted grey, so it stays quieter than the text\n being typed beside it. */\n .v-input-field > .v-icon {\n color: var(--vectis-color-text-muted);\n }\n\n .v-input-field > .v-spinner {\n font-size: var(--vectis-icon-size);\n }\n\n /* A disabled field greys out through the colour tokens, the same ones VCheckbox and\n VRadio use, and never through opacity. It comes LAST in the sequence of states,\n which at equal specificity is what makes it win over all of them, the error\n included: a disabled field is not submitted, so it has nothing to report.\n\n The text here is `text-muted` and not the `text-subtle` used by the label and the\n hint below, because those sit on the page surface where this sits on\n `surface-muted` — against which subtle falls to a 4.4:1 contrast. The control\n itself is exempt from that rule, being natively disabled, but the icons the field\n CONTAINS are not, and they inherit this colour. */\n .v-input[data-disabled] .v-input-field {\n --field-border-color: var(--vectis-color-border);\n\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-muted);\n cursor: not-allowed;\n }\n\n .v-input[data-disabled] .v-input-label,\n .v-input[data-disabled] .v-input-hint,\n .v-input[data-disabled] .v-input-counter {\n color: var(--vectis-color-text-subtle);\n }\n\n .v-input[data-disabled] .v-input-action,\n .v-input[data-disabled] .v-input-field > .v-icon {\n color: inherit;\n cursor: not-allowed;\n }\n\n .v-input-control:disabled {\n cursor: not-allowed;\n }\n\n /* Two arrangements a field composed on top of this one opts into with a class on this\n root: VCombobox and VFileInput, the two that put chips inside the field. They live in\n THIS sheet because it is the one both are guaranteed to load, being built on VInput —\n the core sheet would charge every consumer for them.\n\n `.v-input-end-pinned` lifts the clear cross and whatever carries `.v-input-icon-end` —\n the end icon, or a composed field's own chevron and spinner — out of the flow, so they\n stay pinned to the end of the field and vertically centred whatever the chips do. The\n room they occupy is then reserved with padding, or the text and the chips would run\n underneath. That reservation is written as exactly what the flow would have produced\n with them left in place — one glyph's width each from the field's padding, one gap\n between two of them — and reads the same two variables as the insets below, so the room\n reserved and the glyphs it protects cannot drift apart.\n\n Whether the cross takes its room is read off the markup (`:has`): the cross is rendered\n exactly when there is something to clear, so the padding cannot disagree with it.\n\n The centring is a translation kept SEPARATE from `rotate`, which VCombobox's chevron\n turns when its panel opens. */\n .v-input-end-pinned .v-input-field {\n position: relative;\n padding-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n .v-input-end-pinned .v-input-field:has(> .v-input-clear) {\n padding-inline-end: calc(\n var(--control-padding-inline-field) + 2 * var(--vectis-icon-size) + 2 * var(--control-gap)\n );\n }\n\n .v-input-end-pinned :is(.v-input-clear, .v-input-icon-end) {\n position: absolute;\n inset-inline-end: var(--control-padding-inline-field);\n top: 50%;\n translate: 0 -50%;\n }\n\n /* TRAP — this inset reads in GLYPHS and not in buttons. The cross is one of the field's\n buttons, whose negative margin — half the difference between icon and button — already\n cancels its own overhang, so an inset lands on the glyph's edge. Measured in button\n widths instead, it pushes the cross a whole gap too far, with nothing to signal it.\n It overrides the rule above at the same weight, so it has to stay after it. */\n .v-input-end-pinned .v-input-clear {\n inset-inline-end: calc(\n var(--control-padding-inline-field) + var(--vectis-icon-size) + var(--control-gap)\n );\n }\n\n /* `.v-input-chips` lets the chips WRAP onto several rows, the field growing instead of\n scrolling but never shrinking below an ordinary control.\n\n The input is forced to the chips' height rather than the full height it inherits: its\n natural height is greater than a chip's, and the field would grow the moment it was\n focused. That height is `--chip-height`, written inline on the composed field's root\n by the helper that also picks the chips' size (utils/chip.ts), so the two cannot drift.\n\n TRAP — the control rule must stay at (0,2,0). VCombobox folds its search input away\n with a (0,3,0) rule that zeroes this height, and at equal weight the winner would be\n whichever of the two sheets a bundler put last. */\n .v-input-chips .v-input-field {\n flex-wrap: wrap;\n height: auto;\n min-height: var(--control-height);\n padding-block: var(--vectis-space-1);\n }\n\n .v-input-chips .v-input-control {\n height: var(--chip-height);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-input-field {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6IA,IAAM,IAAQ,GAwBR,IAAO,GAoCP,IAAQ,EAA4B,GAAA,YAAgB,GAOpD,IAAY,QAAe,OAAO,EAAM,SAAS,EAAE,CAAC,GAEpD,EAAE,UAAO,cAAW,cAAW,gBAAgB,MAAc,EAAa,GAO1E,EACJ,MAAM,GACN,SAAS,IACT,UAAU,MACR,EAAgB,GALN,EAAO,IAAe,IAKT,CAAK,GAI1B,KAAI,EAAY,GAChB,KAAqB,QAAe,EAAM,cAAc,GAAE,MAAM,OAAO,KAAK,GAE5E,EAAE,YAAS,YAAQ,eAAW,oBAAgB,GAClD,SACM,CAAC,CAAC,EAAM,YACR,EAAM,OACd,GAEM,EAAE,yBAAqB,0BAAsB,GAAqB;GACtE,MAAM;GACN,gBAAgB,EAAM;GACtB,cAAc,EAAM;EACtB,CAAC,GAEK,IAAY,GAA6B,IAAI,GAE7C,EAAE,eAAW,eAAY,GAAa;GAC1C,iBAAiB,EAAM;GACvB,oBAAoB,EAAM;GAC1B,gBAAgB,EAAiB;GACjC,gBAAgB,EAAM;GACtB,YAAY,EAAU;GACtB;GACA,iBAAiB;IAEf,AADA,EAAM,QAAQ,IACd,EAAK,OAAO;GACd;EACF,CAAC,GAIK,EAAE,iBAAa,aAAS,EAAa;GACzC,IAAI;GACJ,YAAY,EAAU;GACtB,iBAAiB,EAAM;GACvB,iBAAiB,EAAM;EACzB,CAAC;SAKD,EAAa;GAEX,QAAQ,MAA2B,EAAU,OAAO,MAAM,CAAO;GAEjE,cAAc,EAAU,OAAO,OAAO;GAEtC,IAAI;EACN,CAAC,mBAIC,EA2FM,OAAA;GA1FJ,OAAK,EAAA,CAAC,qBACE,EAAA,CAAA,CAAS,CAAA;GAChB,OAAK,EAAE,EAAA,CAAA,CAAS;GAChB,aAAW,EAAA,CAAA;GACX,gBAAc,EAAA,EAAA,IAAe,KAAQ,KAAA;GACrC,iBAAe,EAAA,CAAA,IAAgB,KAAQ,KAAA;GACvC,iBAAe,EAAA,WAAQ,KAAQ,KAAA;;GAEb,EAAA,SAAA,EAAA,GAAnB,EAEc,GAAA;;IAFY,IAAG;IAAQ,SAAQ;IAAQ,OAAM;IAAiB,KAAK,EAAA,CAAA;;qBACpE,CAAA,EAAA,EAAR,EAAA,KAAK,GAAA,CAAA,CAAA,CAAA;;;GAGV,EAyEM,OAzEN,GAyEM;IAhEI,EAAA,aAAa,EAAA,EAAA,KAAA,EAAA,GADrB,EASS,UAAA;;KAPP,MAAK;KACL,OAAM;KACL,cAAY,EAAA,kBAAkB,EAAA,CAAA,CAAQ,CAAC,EAAA,SAAS;KAChD,UAAU,EAAA,CAAA;KACV,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,oBAAqB,CAAM;QAEvC,EAAuC,GAAA,EAAA,EAAxB,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,CAAA,CAAA,GAAA,MAAA,EAAA,CAAA,GAAA,GAAA,CAAA,KAElB,EAAA,aAAA,EAAA,GAAlB,EAAwF,GAAxF,EAAwF,EAAA,KAAA,EAAA,GAAnD,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,GAAA,EAAG,OAAM,qBAAoB,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA;IACrF,EAAqB,EAAA,QAAA,OAAA;MAErB,EAYE,SAZF,EAYE;KAXC,IAAI,EAAA,CAAA;cACD;KAAJ,KAAI;8CACU,QAAA;KACb,gBAAc,EAAA,WAAW,KAAA;OAClB,EAAA,CAAA,GAAS;KACjB,OAAM;KACL,MAAM,EAAA;KACN,WAAW,EAAA,YAAY,KAAA,IAAY,EAAA;KACnC,UAAU,EAAA,CAAA;KACV,UAAU,EAAA,YAAY,EAAA,YAAY,KAAA;KAClC,oBAAkB,EAAA,EAAA;2BARV,EAAA,KAAK,CAAA,CAAA;IAYR,EAAA,WAAA,EAAA,GADR,EAOO,QAAA;;KALJ,IAAI,EAAA,EAAA;KACL,OAAM;KACL,aAAW,EAAA,EAAA,IAAI,KAAQ,KAAA;SAErB,EAAA,EAAA,CAAW,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;IAMhB,EAAyB,EAAA,QAAA,WAAA;IAGjB,EAAA,EAAA,KAAA,EAAA,GADR,EAQS,UAAA;;KANP,MAAK;KACL,OAAM;KACL,cAAY,GAAA;KACZ,SAAK,AAAA,EAAA,QAAA,GAAA,MAAE,EAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA,GAAA,CAAA;QAER,EAA2B,GAAA,EAAnB,MAAM,EAAA,CAAA,EAAS,GAAA,MAAA,GAAA,CAAA,MAAA,CAAA,CAAA,GAAA,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;IAGT,EAAA,WAAA,EAAA,GAAhB,EAAgD,GAAA;;KAAtB,OAAO,EAAA;8BACjC,EAYO,EAAA,QAAA,OAAA,CAAA,SAAA,CAVG,EAAA,WAAW,EAAA,EAAA,KAAA,EAAA,GADnB,EASS,UAAA;;KAPP,MAAK;KACL,OAAM;KACL,cAAY,EAAA,gBAAgB,EAAA,CAAA,CAAQ,CAAC,EAAA,OAAO;KAC5C,UAAU,EAAA,CAAA;KACV,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,kBAAmB,CAAM;QAErC,EAAqC,GAAA,EAAA,EAAtB,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,CAAA,CAAA,GAAA,MAAA,EAAA,CAAA,GAAA,GAAA,CAAA,KAEhB,EAAA,WAAA,EAAA,GAAlB,EAAkF,GAAlF,EAAkF,EAAA,KAAA,EAAA,GAA/C,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,GAAA,EAAG,OAAM,mBAAkB,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;;GAIhE,EAAA,QAAA,EAAA,GAAnB,EAEc,GAAA;;IAFY,IAAI,EAAA,EAAA;IAAQ,SAAQ;IAAU,MAAK;IAAQ,OAAM;;qBAC/D,CAAA,EAAA,EAAP,EAAA,IAAI,GAAA,CAAA,CAAA,CAAA"}
@@ -1,2 +1,2 @@
1
1
  @layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
2
- @layer vectis.components{.v-input-group{gap:var(--vectis-space-1);width:100%;font-family:var(--vectis-text-family);flex-direction:column;display:flex}.v-input-group-row{align-items:stretch;display:flex}.v-input-group-row>:not(:where(.v-overlay)){flex:none;min-inline-size:0}.v-input-group-row>:not(:where(.v-overlay)):has(.v-input-field){flex:1 1 0}.v-input-group-row>.v-button,.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) :is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *)){position:relative}.v-input-group-row>.v-button:not(:first-child),.v-input-group-row>:not(:where(.v-overlay,.v-button-group)):has(:is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *))):not(:first-child){margin-inline-start:-1px}.v-input-group-row>.v-button:not(:first-child),.v-input-group-row>:not(:where(.v-overlay,.v-button-group)):not(:first-child) :is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *)){border-start-start-radius:0;border-end-start-radius:0}.v-input-group-row>.v-button:has(~:not(.v-overlay)),.v-input-group-row>:not(:where(.v-overlay,.v-button-group)):has(~:not(.v-overlay)) :is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *)){border-start-end-radius:0;border-end-end-radius:0}.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) .v-input-field:is(:has(.v-input-control:user-invalid),:has(.v-input-control[aria-invalid=true])){z-index:1}.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) .v-input-field:has(.v-input-control:focus),.v-input-group-row>.v-button:focus-visible,.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) .v-button:focus-visible:not(:where(.v-overlay *,.v-button-group *)){z-index:2}.v-input-group[data-disabled] :is(.v-input-group-label,.v-input-group-hint){color:var(--vectis-color-text-subtle)}}
2
+ @layer vectis.components{.v-input-group{gap:var(--vectis-space-1);width:100%;font-family:var(--vectis-text-family);flex-direction:column;display:flex}.v-input-group-row{align-items:stretch;display:flex}.v-input-group-row>:not(:where(.v-overlay)){flex:none;min-inline-size:0}.v-input-group-row>:not(:where(.v-overlay)):has(.v-input-field:not(:where(.v-overlay *))){flex:1 1 0}.v-input-group-row>.v-button,.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) :is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *)){position:relative}.v-input-group-row>.v-button:not(:first-child),.v-input-group-row>:not(:where(.v-overlay,.v-button-group)):has(:is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *))):not(:first-child){margin-inline-start:-1px}.v-input-group-row>.v-button:not(:first-child),.v-input-group-row>:not(:where(.v-overlay,.v-button-group)):not(:first-child) :is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *)){border-start-start-radius:0;border-end-start-radius:0}.v-input-group-row>.v-button:has(~:not(.v-overlay)),.v-input-group-row>:not(:where(.v-overlay,.v-button-group)):has(~:not(.v-overlay)) :is(.v-input-field,.v-button):not(:where(.v-overlay *,.v-button-group *)){border-start-end-radius:0;border-end-end-radius:0}.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) .v-input-field:is(:has(.v-input-control:user-invalid),:has(.v-input-control[aria-invalid=true])){z-index:1}.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) .v-input-field:has(.v-input-control:focus),.v-input-group-row>.v-button:focus-visible,.v-input-group-row>:not(:where(.v-overlay,.v-button-group)) .v-button:focus-visible:not(:where(.v-overlay *,.v-button-group *)){z-index:2}.v-input-group[data-disabled] :is(.v-input-group-label,.v-input-group-hint){color:var(--vectis-color-text-subtle)}}
@@ -1 +1 @@
1
- {"version":3,"file":"VInputGroup.js","names":[],"sources":["../../../src/components/VInput/VInputGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Joins two or more form controls into a single object: a country code against a phone\n * number, a search field against its magnifier. The shared borders melt into one line and\n * only the two ends of the row stay rounded.\n *\n * It does for fields what VButtonGroup does for buttons, with one structural difference that\n * shapes the whole sheet. For a button, the component's root IS the bordered box; for a\n * field it never is — `.v-input` is a column holding a label, the field and a hint, and the\n * bordered box is `.v-input-field`, a descendant. So the PULL that melts two borders lands\n * on the segment the row lays out, and the PAINT lands on the box that carries them.\n *\n * The row owns the label and the hint, and that is the reason this component exists rather\n * than a CSS recipe: a label above one segment alone pushes its box a line out of the row.\n * The group renders one for all of them and names itself with it, each segment taking an\n * `aria-label` instead — the ARIA pattern for a composed field.\n *\n * `role=\"group\"` on a div, not `<fieldset>`/`<legend>`, and not only for consistency with\n * VButtonGroup and VToggle. The accessibility tree is identical either way: browsers map\n * `<fieldset>` to `role=\"group\"` and `<legend>` to its accessible name. The layout is not. A\n * `<fieldset>` carries `min-inline-size: min-content` in the UA sheet, so it refuses to\n * shrink below its content, which is exactly the right segments in `flex: 1 1 0` are asking\n * for; and a rendered `<legend>` is pulled out of flow and painted into the border area.\n * The native primitive is the right answer when it buys something, and here it costs two\n * resets to impose the invisible UA behaviour that rule is meant to spare us.\n *\n * Horizontal only, and probably for good. A column of fields is a form, and the fields of a\n * form carry a label each: melting their borders would remove the very separation a form\n * needs. VButtonGroup has a vertical because a column of buttons is a toolbar.\n *\n * The behavioural JS is one development warning. The row cannot draw itself out of a segment\n * that brought its own label, and the misalignment is silent, so it says so.\n */\nimport { computed, provide, useId, useSlots } from 'vue'\n\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { buttonGroupKey } from '../VButton/context'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\nimport { flattenSlot } from '../../utils/vnode'\n\nimport { inputGroupKey } from './context'\n\n/** The heights a joined row offers, matching the field scale: 32, 40 or 48 pixels. */\nexport type InputGroupSize = 'sm' | 'md' | 'lg'\n\ninterface InputGroupProps {\n /**\n * The label above the row, rendered once for all of its segments and used as the\n * group's accessible name. A segment carrying one of its own is pushed out of line,\n * so name each of them with `aria-label` instead.\n */\n label?: string\n /**\n * A line of help under the row, tied to the group so assistive technology reads it\n * out along with the label.\n */\n hint?: string\n /**\n * The height every segment takes, whatever it names for itself: a row of controls of\n * two heights stops reading as one object. Left out, each segment keeps its own.\n */\n size?: InputGroupSize\n /** Takes 4px off the height of every segment, the way `compact` does on a lone field. */\n compact?: boolean\n /**\n * Makes the whole row unusable. It adds to what each segment says rather than\n * replacing it: a segment disabled on its own stays disabled under a row that says\n * nothing.\n */\n disabled?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\n// TRAP — every one of the three below defaults to `undefined`, the booleans included. It is\n// `undefined`, and not `false`, that means \"the group has no opinion\", and that is what makes\n// a bare VInputGroup change nothing about what it contains. Writing `false` here would force\n// every segment out of compact and out of disabled, whatever it had asked for itself.\nconst props = withDefaults(defineProps<InputGroupProps>(), {\n label: undefined,\n hint: undefined,\n size: undefined,\n compact: undefined,\n disabled: undefined,\n})\n\ndefineSlots<{\n /** The fields and buttons to join. Each one is a segment of the row. */\n default(): unknown\n}>()\n\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\nconst slots = useSlots()\n\nconst labelId = useId()\nconst hintId = useId()\n\n// @a11y — the canonical cascade, resolved HERE rather than left to fallthrough: the binding\n// in the template comes after `v-bind=\"forwardedAttrs\"` and would otherwise overwrite what\n// the consumer wrote, so this computed has to hand their value back itself.\n//\n// An `aria-label` or an `aria-labelledby` written by the consumer names the group, and the\n// VISIBLE label must not double it: `aria-labelledby` wins over `aria-label` in the\n// accessible name computation, so emitting ours on top of a consumer's `aria-label` would\n// silently cancel it.\nconst labelledBy = computed(() => {\n const own = attrs['aria-labelledby'] as string | undefined\n if (own !== undefined) return own\n // TRUTHINESS, the test the template renders the label with: an empty `label=\"\"` renders\n // no element, and naming the group after an id nothing carries leaves it nameless.\n return props.label && attrs['aria-label'] === undefined ? labelId : undefined\n})\n\n// @a11y — `aria-describedby` is a LIST, so the hint is ADDED to whatever the consumer\n// pointed at rather than replacing it (the `useFieldIds` idiom). This aggregation is also\n// why the component takes `inheritAttrs: false` despite having a single root: left to\n// fallthrough, a consumer's `aria-describedby` would overwrite ours and the hint would stop\n// being announced, with nothing to show for it.\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The components whose `label` prop renders a real `<label>` above the field. VIconButton is\n * deliberately absent: its `label` is an accessible name for a control with no text, so\n * warning about it would be wrong.\n */\nconst LABELLED_FIELDS = new Set([\n 'VInput',\n 'VTextarea',\n 'VCombobox',\n 'VDateInput',\n 'VTimeInput',\n 'VFileInput',\n])\n\n// A dev warning fires from a computed that the render consumes, so a slot whose content\n// depends on a model would repeat it on every keystroke. One line per offender is enough to\n// be found and fixed.\nconst warned = new Set<string>()\n\n// @devwarn @ssr — the segments are read from the slot's VNODES, never from a registry the\n// children would fill at mount: a registry is empty on the server and full in the browser.\n//\n// TRAP — the read has to stay inside this computed, and the computed has to be consumed by\n// the render, which is what `<Segments />` does. Called from a `watchEffect`, which runs\n// BEFORE the render, Vue warns \"Slot invoked outside of the render function\" on any slot\n// passed as a raw function — which is exactly what a unit test passes.\nconst segments = computed(() => {\n const nodes = flattenSlot(slots.default?.())\n\n if (isDev) {\n for (const node of nodes) {\n const type = node.type as { name?: string; __name?: string } | string\n const name =\n typeof type === 'object' && type !== null ? (type.name ?? type.__name) : undefined\n if (name === undefined || !LABELLED_FIELDS.has(name)) continue\n\n for (const prop of ['label', 'hint'] as const) {\n if (node.props?.[prop] === undefined || warned.has(`${name}.${prop}`)) continue\n warned.add(`${name}.${prop}`)\n console.warn(\n `[VInputGroup] <${name}> carries its own \\`${prop}\\`. The group renders one for the whole row, and a segment carrying one of its own is pushed a line out of the row. Move it to VInputGroup and name the segment with \\`aria-label\\`.`,\n )\n }\n }\n }\n\n return nodes\n})\n\n// A functional component is the only way to render VNodes that have already been captured:\n// `<component :is>` expects a component definition rather than a vnode. Returning an array\n// produces a Fragment, so no DOM element is inserted and the sheet's `>` combinator holds.\nconst Segments = () => segments.value\n\n// Getters, so the group's props stay reactive on the other side of the injection.\nconst rowContext = {\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get disabled() {\n return props.disabled\n },\n}\n\nprovide(inputGroupKey, rowContext)\n\n// The group provides VButtonGroup's context TOO, which is what makes a VButton or a\n// VIconButton segment follow the row's height without a new line in VButton: the arbitration\n// there is already `group?.x ?? props.x`, exactly this one. Variant, tone and elevation stay\n// `undefined` — in a row of fields those are the button's own decisions.\nprovide(buttonGroupKey, rowContext)\n</script>\n\n<template>\n <div\n class=\"v-input-group\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n role=\"group\"\n v-bind=\"forwardedAttrs\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :data-disabled=\"disabled ? '' : undefined\"\n >\n <!-- A span and not a `<label for>`: a label points at ONE control, and this names a row\n of them. The group takes it through aria-labelledby, the ARIA pattern for a\n composed field. -->\n <VTypography v-if=\"label\" :id=\"labelId\" as=\"span\" variant=\"label\" class=\"v-input-group-label\">\n {{ label }}\n </VTypography>\n\n <div class=\"v-input-group-row\">\n <Segments />\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-group-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A SEGMENT is a direct child of the row, and it is almost never the box carrying the\n * border. For a button the two coincide; for a field they never do — VInput's bordered box\n * is `.v-input-field`, one level under its root, two or three under those of VCombobox,\n * VDateInput, VTimeInput and VFileInput. Companions add more: VTooltip renders `.v-tooltip`\n * around its trigger, VPopover `.v-popover`, VBadge `.v-badge-host`, and they stack.\n *\n * So every rule is written for BOTH forms:\n *\n * > .v-button<tests> the button IS the segment\n * > WRAPPER<tests> BOX the box is somewhere inside it\n *\n * WRAPPER = :not(:where(.v-overlay, .v-button-group))\n * BOX = :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *))\n *\n * The PULL — the negative margin that melts two borders into one line — belongs to the\n * SEGMENT, the box the row lays out. The PAINT — the corners, the stacking order —\n * belongs to the BOX, which carries them. Swapped, a field would be pulled by its field\n * box while its root stayed put, and the row would show a one-pixel gap.\n *\n * The tests weigh the same on both sides, so a pair is always ONE specificity.\n *\n * TRAP — `.v-overlay` is in both guards and neither is optional. A panel must not pass for\n * a segment: VMenu renders its own as a SIBLING of its trigger. And a panel's CONTENT must\n * never be painted: an open VDatePicker carries buttons, a combobox list carries options,\n * and they would all take a segment's flattened corners.\n *\n * TRAP — `.v-button-group` is in both guards, and the case easy to miss is VTimeInput: in\n * 12 hour form it renders an AM/PM VToggle beside its field, and a VToggle IS a\n * `.v-button-group`. Without `.v-button-group *` in BOX, those two buttons would be\n * painted as segments of THIS row, on top of the row VToggle draws for itself.\n *\n * No doubled class is needed anywhere here, which is worth stating so nobody adds one \"for\n * safety\". Our corners weigh (0,3,0) against the (0,1,0) that `.v-input-field` and\n * `.v-button` put on themselves; `position: relative` matches the (0,2,0) of\n * `.v-input-end-pinned > .v-input-field` at the same value; nothing else sets a `z-index` on these\n * elements. VButtonGroup's own (0,4,0) corners are unreachable — a button cannot be both a\n * direct child of `.v-input-group-row` and inside a `.v-button-group`, the WRAPPER guard\n * excludes it.\n */\n\n .v-input-group {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The label and the hint are rendered by VTypography, which carries their type. The\n classes stay as hooks: a consumer overrides through them, and the disabled state\n below reaches them that way. */\n\n /* The row carries NEITHER `v-control` NOR `data-size`: nothing in this sheet reads a\n `--control-*` variable, and `.v-control` set a second time above a subtree that already\n has it re-derives `--control-height` from the base height, dropping the compact its\n fields had applied (styles/control-size.css). */\n .v-input-group-row {\n display: flex;\n /* Same height whatever a segment measures. This changes nothing in an ordinary row,\n where everything is --control-height, and it is what stops a segment that GROWS from\n leaving its neighbours hanging at the top. */\n align-items: stretch;\n }\n\n /* A segment holding a field takes its share of the row and accepts shrinking below its own\n content: the intrinsic width of a field is that of its placeholder, which would push a\n row of two fields out of its parent. Everything else keeps its natural width, a button\n being measured by its label.\n\n The logic is written this way round because it is the one that enumerates nothing: a\n bare VButton, or one under two companions, has no `.v-input-field`, so it never stretches\n without our having to name it. The proportions belong to the consumer, an inline `flex`\n on the segment (style=\"flex: 0 0 9rem\").\n\n A panel is not a segment and takes no share. Closed it is `display: none`, open it is\n fixed, so it is not a flex item either way — the guard is there so the selector says\n what it does. */\n .v-input-group-row > :not(:where(.v-overlay)) {\n flex: none;\n min-inline-size: 0;\n }\n\n .v-input-group-row > :not(:where(.v-overlay)):has(.v-input-field) {\n flex: 1 1 0;\n }\n\n /* Every box is positioned, so they all paint in the SAME phase and in document order.\n\n TRAP — without this rule the paint order would depend on which components the row\n happens to hold. A combobox's field is already `position: relative` (VInput's\n `.v-input-end-pinned`, which lifts its chevron and its cross out of the flow) where a bare `.v-input-field` is not, and a\n positioned element paints AFTER every in-flow block whatever the document order. So a\n combobox would paint over a VInput placed BEFORE it, and the joint would show the wrong\n one of the two borders. The symptom is a hairline that changes colour when the template\n is reordered, with nothing in the sheet to explain it. */\n .v-input-group-row > .v-button,\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *)) {\n position: relative;\n }\n\n /* The pull: each segment rides one pixel onto the one before it, so their two 1px borders\n collapse into a single line.\n\n That line is why this row needs no seam, where VButtonGroup does. VButton's base is\n `border: 1px solid transparent`, so a row of solid buttons has no line to inherit and the\n seam is the only separation possible. A field carries a real opaque border, and melting\n it with its neighbour's PRODUCES the line — a seam here would double it, and in\n VButtonGroup's `--vectis-color-border` it would come out LIGHTER than the row's own\n outline. Under a filled button it would draw a grey hair across the accent, where the\n button's background covering the edge is exactly what an attached search button should\n look like. */\n .v-input-group-row > .v-button:not(:first-child),\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group)):has(\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *))\n ):not(:first-child) {\n margin-inline-start: -1px;\n }\n\n /* Only the two outer corners of the row stay rounded. Logical properties throughout, so a\n right-to-left row rounds its other end with no second rule, and the pull above is\n `margin-inline-start`. */\n .v-input-group-row > .v-button:not(:first-child),\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *)) {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n\n /* TRAP — the last segment reads `:has(~ :not(.v-overlay))` and not `:not(:last-child)`,\n and that is VMenu's doing: it renders its panel as a SIBLING of its trigger, so a menu\n closing a row would leave its trigger without `:last-child` and the row would lose its\n outer corner with nothing in the DOM to say why. Asking whether a real SEGMENT follows\n weighs exactly what the test it replaces weighed. The first segment needs none of this:\n a panel is rendered AFTER its trigger. */\n .v-input-group-row > .v-button:has(~ :not(.v-overlay)),\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *)) {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n /* A field in error rises above its neighbours. Its red border shares a one-pixel column\n with the neighbour's grey one, and the neighbour, painted after it, would repaint it:\n the invalid field would lose that whole edge and read as open on one side.\n\n TRAP — this rule must stay ABOVE the focus rule. A field both invalid and focused\n matches the two, they weigh the same (0,4,0), and nothing but the source order arbitrates\n between them; it is the accent ring the reader has to see. And it is the two VALUES, 1\n and 2, that settle the other case: an invalid field NEXT TO a focused one. */\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n .v-input-field:is(\n :has(.v-input-control:user-invalid),\n :has(.v-input-control[aria-invalid='true'])\n ) {\n z-index: 1;\n }\n\n /* A focused segment rises above everything. The field draws its focus as its own 1px\n border PLUS a 1px shadow just outside the border box, and that shadow falls into the\n neighbour the pull brought over it: without this rule the accent edge disappears from\n one side of every segment but the last.\n\n Two tests, because the two kinds of segment mark focus differently. A field lights up as\n soon as its control has focus, pointer included — VInput's own `:has(.v-input-control:focus)`,\n deliberately not `:focus-within`, so a clickable icon inside it takes its own ring —\n where a button only rises for `:focus-visible`, which is when it draws one.\n\n Hover and active are deliberately absent, as in VButtonGroup: the overlapping pixel IS\n the edge, and a segment rising under the pointer would rub it out for as long as it is\n there. */\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n .v-input-field:has(.v-input-control:focus),\n .v-input-group-row > .v-button:focus-visible,\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n .v-button:focus-visible:not(:where(.v-overlay *, .v-button-group *)) {\n z-index: 2;\n }\n\n /* VInput's rule, at the same values: through the colour tokens, never through opacity. */\n .v-input-group[data-disabled] :is(.v-input-group-label, .v-input-group-hint) {\n color: var(--vectis-color-text-subtle);\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VInputGroup.js","names":[],"sources":["../../../src/components/VInput/VInputGroup.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Joins two or more form controls into a single object: a country code against a phone\n * number, a search field against its magnifier. The shared borders melt into one line and\n * only the two ends of the row stay rounded.\n *\n * It does for fields what VButtonGroup does for buttons, with one structural difference that\n * shapes the whole sheet. For a button, the component's root IS the bordered box; for a\n * field it never is — `.v-input` is a column holding a label, the field and a hint, and the\n * bordered box is `.v-input-field`, a descendant. So the PULL that melts two borders lands\n * on the segment the row lays out, and the PAINT lands on the box that carries them.\n *\n * The row owns the label and the hint, and that is the reason this component exists rather\n * than a CSS recipe: a label above one segment alone pushes its box a line out of the row.\n * The group renders one for all of them and names itself with it, each segment taking an\n * `aria-label` instead — the ARIA pattern for a composed field.\n *\n * `role=\"group\"` on a div, not `<fieldset>`/`<legend>`, and not only for consistency with\n * VButtonGroup and VToggle. The accessibility tree is identical either way: browsers map\n * `<fieldset>` to `role=\"group\"` and `<legend>` to its accessible name. The layout is not. A\n * `<fieldset>` carries `min-inline-size: min-content` in the UA sheet, so it refuses to\n * shrink below its content, which is exactly the right segments in `flex: 1 1 0` are asking\n * for; and a rendered `<legend>` is pulled out of flow and painted into the border area.\n * The native primitive is the right answer when it buys something, and here it costs two\n * resets to impose the invisible UA behaviour that rule is meant to spare us.\n *\n * Horizontal only, and probably for good. A column of fields is a form, and the fields of a\n * form carry a label each: melting their borders would remove the very separation a form\n * needs. VButtonGroup has a vertical because a column of buttons is a toolbar.\n *\n * The behavioural JS is one development warning. The row cannot draw itself out of a segment\n * that brought its own label, and the misalignment is silent, so it says so.\n */\nimport { computed, provide, useId } from 'vue'\n\nimport VTypography from '../VTypography/VTypography.vue'\n\nimport { buttonGroupKey } from '../VButton/context'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\nimport { useSlotNodes } from '../../composables/useSlotNodes'\nimport { isDev } from '../../utils/env'\nimport { joinIds } from '../../utils/ids'\n\nimport { inputGroupKey } from './context'\n\n/** The heights a joined row offers, matching the field scale: 32, 40 or 48 pixels. */\nexport type InputGroupSize = 'sm' | 'md' | 'lg'\n\ninterface InputGroupProps {\n /**\n * The label above the row, rendered once for all of its segments and used as the\n * group's accessible name. A segment carrying one of its own is pushed out of line,\n * so name each of them with `aria-label` instead.\n */\n label?: string\n /**\n * A line of help under the row, tied to the group so assistive technology reads it\n * out along with the label.\n */\n hint?: string\n /**\n * The height every segment takes, whatever it names for itself: a row of controls of\n * two heights stops reading as one object. Left out, each segment keeps its own.\n */\n size?: InputGroupSize\n /** Takes 4px off the height of every segment, the way `compact` does on a lone field. */\n compact?: boolean\n /**\n * Makes the whole row unusable. It adds to what each segment says rather than\n * replacing it: a segment disabled on its own stays disabled under a row that says\n * nothing.\n */\n disabled?: boolean\n}\n\ndefineOptions({ inheritAttrs: false })\n\n// TRAP — every one of the three below defaults to `undefined`, the booleans included. It is\n// `undefined`, and not `false`, that means \"the group has no opinion\", and that is what makes\n// a bare VInputGroup change nothing about what it contains. Writing `false` here would force\n// every segment out of compact and out of disabled, whatever it had asked for itself.\nconst props = withDefaults(defineProps<InputGroupProps>(), {\n label: undefined,\n hint: undefined,\n size: undefined,\n compact: undefined,\n disabled: undefined,\n})\n\ndefineSlots<{\n /** The fields and buttons to join. Each one is a segment of the row. */\n default(): unknown\n}>()\n\nconst { attrs, rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\n\nconst labelId = useId()\nconst hintId = useId()\n\n// @a11y — the canonical cascade, resolved HERE rather than left to fallthrough: the binding\n// in the template comes after `v-bind=\"forwardedAttrs\"` and would otherwise overwrite what\n// the consumer wrote, so this computed has to hand their value back itself.\n//\n// An `aria-label` or an `aria-labelledby` written by the consumer names the group, and the\n// VISIBLE label must not double it: `aria-labelledby` wins over `aria-label` in the\n// accessible name computation, so emitting ours on top of a consumer's `aria-label` would\n// silently cancel it.\nconst labelledBy = computed(() => {\n const own = attrs['aria-labelledby'] as string | undefined\n if (own !== undefined) return own\n // TRUTHINESS, the test the template renders the label with: an empty `label=\"\"` renders\n // no element, and naming the group after an id nothing carries leaves it nameless.\n return props.label && attrs['aria-label'] === undefined ? labelId : undefined\n})\n\n// @a11y — `aria-describedby` is a LIST, so the hint is ADDED to whatever the consumer\n// pointed at rather than replacing it (the `useFieldIds` idiom). This aggregation is also\n// why the component takes `inheritAttrs: false` despite having a single root: left to\n// fallthrough, a consumer's `aria-describedby` would overwrite ours and the hint would stop\n// being announced, with nothing to show for it.\nconst describedBy = computed(() =>\n joinIds(attrs['aria-describedby'] as string | undefined, !!props.hint && hintId),\n)\n\n/**\n * The components whose `label` prop renders a real `<label>` above the field. VIconButton is\n * deliberately absent: its `label` is an accessible name for a control with no text, so\n * warning about it would be wrong.\n */\nconst LABELLED_FIELDS = new Set([\n 'VInput',\n 'VTextarea',\n 'VCombobox',\n 'VDateInput',\n 'VTimeInput',\n 'VFileInput',\n])\n\n// A dev warning fires from a computed that the render consumes, so a slot whose content\n// depends on a model would repeat it on every keystroke. One line per offender is enough to\n// be found and fixed.\nconst warned = new Set<string>()\n\n// @devwarn @ssr — the segments are read from the slot's VNODES, never from a registry the\n// children would fill at mount: a registry is empty on the server and full in the browser.\n//\n// TRAP — the read has to stay inside a computed, and the computed has to be consumed by\n// the render, which is what `<Segments />` does. Called from a `watchEffect`, which runs\n// BEFORE the render, Vue warns \"Slot invoked outside of the render function\" on any slot\n// passed as a raw function — which is exactly what a unit test passes.\nconst slotNodes = useSlotNodes()\nconst segments = computed(() => {\n const nodes = slotNodes.value\n\n if (isDev) {\n for (const node of nodes) {\n const type = node.type as { name?: string; __name?: string } | string\n const name =\n typeof type === 'object' && type !== null ? (type.name ?? type.__name) : undefined\n if (name === undefined || !LABELLED_FIELDS.has(name)) continue\n\n for (const prop of ['label', 'hint'] as const) {\n if (node.props?.[prop] === undefined || warned.has(`${name}.${prop}`)) continue\n warned.add(`${name}.${prop}`)\n console.warn(\n `[VInputGroup] <${name}> carries its own \\`${prop}\\`. The group renders one for the whole row, and a segment carrying one of its own is pushed a line out of the row. Move it to VInputGroup and name the segment with \\`aria-label\\`.`,\n )\n }\n }\n }\n\n return nodes\n})\n\n// A functional component is the only way to render VNodes that have already been captured:\n// `<component :is>` expects a component definition rather than a vnode. Returning an array\n// produces a Fragment, so no DOM element is inserted and the sheet's `>` combinator holds.\nconst Segments = () => segments.value\n\n// Getters, so the group's props stay reactive on the other side of the injection.\nconst rowContext = {\n get size() {\n return props.size\n },\n get compact() {\n return props.compact\n },\n get disabled() {\n return props.disabled\n },\n}\n\nprovide(inputGroupKey, rowContext)\n\n// The group provides VButtonGroup's context TOO, which is what makes a VButton or a\n// VIconButton segment follow the row's height without a new line in VButton: the arbitration\n// there is already `group?.x ?? props.x`, exactly this one. Variant, tone and elevation stay\n// `undefined` — in a row of fields those are the button's own decisions.\nprovide(buttonGroupKey, rowContext)\n</script>\n\n<template>\n <div\n class=\"v-input-group\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n role=\"group\"\n v-bind=\"forwardedAttrs\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :data-disabled=\"disabled ? '' : undefined\"\n >\n <!-- A span and not a `<label for>`: a label points at ONE control, and this names a row\n of them. The group takes it through aria-labelledby, the ARIA pattern for a\n composed field. -->\n <VTypography v-if=\"label\" :id=\"labelId\" as=\"span\" variant=\"label\" class=\"v-input-group-label\">\n {{ label }}\n </VTypography>\n\n <div class=\"v-input-group-row\">\n <Segments />\n </div>\n\n <VTypography v-if=\"hint\" :id=\"hintId\" variant=\"caption\" tone=\"muted\" class=\"v-input-group-hint\">\n {{ hint }}\n </VTypography>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * A SEGMENT is a direct child of the row, and it is almost never the box carrying the\n * border. For a button the two coincide; for a field they never do — VInput's bordered box\n * is `.v-input-field`, one level under its root, two or three under those of VCombobox,\n * VDateInput, VTimeInput and VFileInput. Companions add more: VTooltip renders `.v-tooltip`\n * around its trigger, VPopover `.v-popover`, VBadge `.v-badge-host`, and they stack.\n *\n * So every rule is written for BOTH forms:\n *\n * > .v-button<tests> the button IS the segment\n * > WRAPPER<tests> BOX the box is somewhere inside it\n *\n * WRAPPER = :not(:where(.v-overlay, .v-button-group))\n * BOX = :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *))\n *\n * The PULL — the negative margin that melts two borders into one line — belongs to the\n * SEGMENT, the box the row lays out. The PAINT — the corners, the stacking order —\n * belongs to the BOX, which carries them. Swapped, a field would be pulled by its field\n * box while its root stayed put, and the row would show a one-pixel gap.\n *\n * The tests weigh the same on both sides, so a pair is always ONE specificity.\n *\n * TRAP — `.v-overlay` is in both guards and neither is optional. A panel must not pass for\n * a segment: VMenu renders its own as a SIBLING of its trigger. And a panel's CONTENT must\n * never be painted: an open VDatePicker carries buttons, a combobox list carries options,\n * and they would all take a segment's flattened corners.\n *\n * TRAP — `.v-button-group` is in both guards, and the case easy to miss is VTimeInput: in\n * 12 hour form it renders an AM/PM VToggle beside its field, and a VToggle IS a\n * `.v-button-group`. Without `.v-button-group *` in BOX, those two buttons would be\n * painted as segments of THIS row, on top of the row VToggle draws for itself.\n *\n * No doubled class is needed anywhere here, which is worth stating so nobody adds one \"for\n * safety\". Our corners weigh (0,3,0) against the (0,1,0) that `.v-input-field` and\n * `.v-button` put on themselves; `position: relative` matches the (0,2,0) of\n * `.v-input-end-pinned > .v-input-field` at the same value; nothing else sets a `z-index` on these\n * elements. VButtonGroup's own (0,4,0) corners are unreachable — a button cannot be both a\n * direct child of `.v-input-group-row` and inside a `.v-button-group`, the WRAPPER guard\n * excludes it.\n */\n\n .v-input-group {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-1);\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The label and the hint are rendered by VTypography, which carries their type. The\n classes stay as hooks: a consumer overrides through them, and the disabled state\n below reaches them that way. */\n\n /* The row carries NEITHER `v-control` NOR `data-size`: nothing in this sheet reads a\n `--control-*` variable, and `.v-control` set a second time above a subtree that already\n has it re-derives `--control-height` from the base height, dropping the compact its\n fields had applied (styles/control-size.css). */\n .v-input-group-row {\n display: flex;\n /* Same height whatever a segment measures. This changes nothing in an ordinary row,\n where everything is --control-height, and it is what stops a segment that GROWS from\n leaving its neighbours hanging at the top. */\n align-items: stretch;\n }\n\n /* A segment holding a field takes its share of the row and accepts shrinking below its own\n content: the intrinsic width of a field is that of its placeholder, which would push a\n row of two fields out of its parent. Everything else keeps its natural width, a button\n being measured by its label.\n\n The logic is written this way round because it is the one that enumerates nothing: a\n bare VButton, or one under two companions, has no `.v-input-field`, so it never stretches\n without our having to name it. The proportions belong to the consumer, an inline `flex`\n on the segment (style=\"flex: 0 0 9rem\").\n\n A panel is not a segment and takes no share. Closed it is `display: none`, open it is\n fixed, so it is not a flex item either way — the guard is there so the selector says\n what it does. */\n .v-input-group-row > :not(:where(.v-overlay)) {\n flex: none;\n min-inline-size: 0;\n }\n\n /* The field has to be the segment's OWN, never one inside a panel it opens: a VPopover\n segment holding a VInput in its panel is a button, and stays its natural width. */\n .v-input-group-row > :not(:where(.v-overlay)):has(.v-input-field:not(:where(.v-overlay *))) {\n flex: 1 1 0;\n }\n\n /* Every box is positioned, so they all paint in the SAME phase and in document order.\n\n TRAP — without this rule the paint order would depend on which components the row\n happens to hold. A combobox's field is already `position: relative` (VInput's\n `.v-input-end-pinned`, which lifts its chevron and its cross out of the flow) where a bare `.v-input-field` is not, and a\n positioned element paints AFTER every in-flow block whatever the document order. So a\n combobox would paint over a VInput placed BEFORE it, and the joint would show the wrong\n one of the two borders. The symptom is a hairline that changes colour when the template\n is reordered, with nothing in the sheet to explain it. */\n .v-input-group-row > .v-button,\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *)) {\n position: relative;\n }\n\n /* The pull: each segment rides one pixel onto the one before it, so their two 1px borders\n collapse into a single line.\n\n That line is why this row needs no seam, where VButtonGroup does. VButton's base is\n `border: 1px solid transparent`, so a row of solid buttons has no line to inherit and the\n seam is the only separation possible. A field carries a real opaque border, and melting\n it with its neighbour's PRODUCES the line — a seam here would double it, and in\n VButtonGroup's `--vectis-color-border` it would come out LIGHTER than the row's own\n outline. Under a filled button it would draw a grey hair across the accent, where the\n button's background covering the edge is exactly what an attached search button should\n look like. */\n .v-input-group-row > .v-button:not(:first-child),\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group)):has(\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *))\n ):not(:first-child) {\n margin-inline-start: -1px;\n }\n\n /* Only the two outer corners of the row stay rounded. Logical properties throughout, so a\n right-to-left row rounds its other end with no second rule, and the pull above is\n `margin-inline-start`. */\n .v-input-group-row > .v-button:not(:first-child),\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group)):not(:first-child)\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *)) {\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n\n /* TRAP — the last segment reads `:has(~ :not(.v-overlay))` and not `:not(:last-child)`,\n and that is VMenu's doing: it renders its panel as a SIBLING of its trigger, so a menu\n closing a row would leave its trigger without `:last-child` and the row would lose its\n outer corner with nothing in the DOM to say why. Asking whether a real SEGMENT follows\n weighs exactly what the test it replaces weighed. The first segment needs none of this:\n a panel is rendered AFTER its trigger. */\n .v-input-group-row > .v-button:has(~ :not(.v-overlay)),\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group)):has(~ :not(.v-overlay))\n :is(.v-input-field, .v-button):not(:where(.v-overlay *, .v-button-group *)) {\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n\n /* A field in error rises above its neighbours. Its red border shares a one-pixel column\n with the neighbour's grey one, and the neighbour, painted after it, would repaint it:\n the invalid field would lose that whole edge and read as open on one side.\n\n TRAP — this rule must stay ABOVE the focus rule. A field both invalid and focused\n matches the two, they weigh the same (0,4,0), and nothing but the source order arbitrates\n between them; it is the accent ring the reader has to see. And it is the two VALUES, 1\n and 2, that settle the other case: an invalid field NEXT TO a focused one. */\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n .v-input-field:is(\n :has(.v-input-control:user-invalid),\n :has(.v-input-control[aria-invalid='true'])\n ) {\n z-index: 1;\n }\n\n /* A focused segment rises above everything. The field draws its focus as its own 1px\n border PLUS a 1px shadow just outside the border box, and that shadow falls into the\n neighbour the pull brought over it: without this rule the accent edge disappears from\n one side of every segment but the last.\n\n Two tests, because the two kinds of segment mark focus differently. A field lights up as\n soon as its control has focus, pointer included — VInput's own `:has(.v-input-control:focus)`,\n deliberately not `:focus-within`, so a clickable icon inside it takes its own ring —\n where a button only rises for `:focus-visible`, which is when it draws one.\n\n Hover and active are deliberately absent, as in VButtonGroup: the overlapping pixel IS\n the edge, and a segment rising under the pointer would rub it out for as long as it is\n there. */\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n .v-input-field:has(.v-input-control:focus),\n .v-input-group-row > .v-button:focus-visible,\n .v-input-group-row\n > :not(:where(.v-overlay, .v-button-group))\n .v-button:focus-visible:not(:where(.v-overlay *, .v-button-group *)) {\n z-index: 2;\n }\n\n /* VInput's rule, at the same values: through the colour tokens, never through opacity. */\n .v-input-group[data-disabled] :is(.v-input-group-label, .v-input-group-hint) {\n color: var(--vectis-color-text-subtle);\n }\n}\n</style>\n"],"mappings":""}