vectis-ui 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/dist/components/VAccordion/VAccordion.js.map +1 -1
  2. package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  3. package/dist/components/VAccordion/VAccordionItem.d.ts +6 -21
  4. package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
  5. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/VAccordion/context.js.map +1 -1
  7. package/dist/components/VAvatar/VAvatar.d.ts +2 -12
  8. package/dist/components/VAvatar/VAvatar.js.map +1 -1
  9. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  10. package/dist/components/VAvatar/VAvatarGroup.d.ts +2 -2
  11. package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
  12. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +6 -6
  13. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  14. package/dist/components/VAvatar/context.d.ts +1 -5
  15. package/dist/components/VAvatar/context.js.map +1 -1
  16. package/dist/components/VBadge/VBadge.d.ts +13 -27
  17. package/dist/components/VBadge/VBadge.js.map +1 -1
  18. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +3 -9
  20. package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
  21. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/VButton/VButton.d.ts +10 -17
  23. package/dist/components/VButton/VButton.js.map +1 -1
  24. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  25. package/dist/components/VButton/VButtonGroup.d.ts +6 -15
  26. package/dist/components/VButton/VButtonGroup.js.map +1 -1
  27. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  28. package/dist/components/VButton/context.d.ts +1 -9
  29. package/dist/components/VButton/context.js.map +1 -1
  30. package/dist/components/VCalendar/VCalendar.d.ts +9 -24
  31. package/dist/components/VCalendar/VCalendar.js.map +1 -1
  32. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  33. package/dist/components/VCalendar/VCalendarEvent.d.ts +4 -13
  34. package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
  35. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  36. package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
  37. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
  38. package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +1 -5
  39. package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
  40. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  41. package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
  42. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  43. package/dist/components/VCalendar/color.d.ts +1 -7
  44. package/dist/components/VCalendar/color.js.map +1 -1
  45. package/dist/components/VCalendar/edgeStep.d.ts +5 -15
  46. package/dist/components/VCalendar/edgeStep.js.map +1 -1
  47. package/dist/components/VCalendar/gesture.d.ts +12 -36
  48. package/dist/components/VCalendar/gesture.js.map +1 -1
  49. package/dist/components/VCalendar/keyboard.d.ts +5 -42
  50. package/dist/components/VCalendar/keyboard.js.map +1 -1
  51. package/dist/components/VCalendar/layout.d.ts +38 -199
  52. package/dist/components/VCalendar/layout.js.map +1 -1
  53. package/dist/components/VCalendar/timeGrid.d.ts +3 -25
  54. package/dist/components/VCalendar/timeGrid.js.map +1 -1
  55. package/dist/components/VCalendar/types.d.ts +6 -23
  56. package/dist/components/VCarousel/VCarousel.d.ts +25 -96
  57. package/dist/components/VCarousel/VCarousel.js.map +1 -1
  58. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +6 -6
  59. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  60. package/dist/components/VCarousel/VCarouselItem.d.ts +1 -6
  61. package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
  62. package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  63. package/dist/components/VCarousel/context.js.map +1 -1
  64. package/dist/components/VCheckbox/VCheckbox.d.ts +1 -5
  65. package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
  66. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  67. package/dist/components/VChip/VChip.d.ts +2 -8
  68. package/dist/components/VChip/VChip.js.map +1 -1
  69. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  70. package/dist/components/VCombobox/VCombobox.d.ts +21 -59
  71. package/dist/components/VCombobox/VCombobox.js.map +1 -1
  72. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +117 -119
  73. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  74. package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
  75. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  76. package/dist/components/VCombobox/VComboboxOption.js.map +1 -1
  77. package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
  78. package/dist/components/VCombobox/VComboboxSeparator.js.map +1 -1
  79. package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  80. package/dist/components/VCombobox/infiniteScroll.d.ts +4 -10
  81. package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
  82. package/dist/components/VDataTable/VDataTable.d.ts +11 -36
  83. package/dist/components/VDataTable/VDataTable.js.map +1 -1
  84. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +23 -20
  85. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  86. package/dist/components/VDateInput/VDateInput.d.ts +12 -21
  87. package/dist/components/VDateInput/VDateInput.js.map +1 -1
  88. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  89. package/dist/components/VDateInput/mask.d.ts +14 -59
  90. package/dist/components/VDateInput/mask.js.map +1 -1
  91. package/dist/components/VDatePicker/VDatePicker.d.ts +6 -12
  92. package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
  93. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  94. package/dist/components/VDatePicker/keyboard.d.ts +10 -42
  95. package/dist/components/VDatePicker/keyboard.js.map +1 -1
  96. package/dist/components/VDatePicker/names.d.ts +8 -22
  97. package/dist/components/VDatePicker/names.js.map +1 -1
  98. package/dist/components/VDialog/VDialog.d.ts +5 -10
  99. package/dist/components/VDialog/VDialog.js.map +1 -1
  100. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +51 -53
  101. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  102. package/dist/components/VDialog/VDialogAlert.d.ts +2 -3
  103. package/dist/components/VDialog/VDialogAlert.js.map +1 -1
  104. package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  105. package/dist/components/VFileInput/VFileInput.d.ts +11 -37
  106. package/dist/components/VFileInput/VFileInput.js.map +1 -1
  107. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  108. package/dist/components/VFileInput/truncate.d.ts +3 -23
  109. package/dist/components/VFileInput/truncate.js.map +1 -1
  110. package/dist/components/VFilePicker/VFilePicker.d.ts +15 -65
  111. package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
  112. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  113. package/dist/components/VFilePicker/fileKind.js.map +1 -1
  114. package/dist/components/VHotkeys/VHotkeys.d.ts +6 -15
  115. package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
  116. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
  117. package/dist/components/VHotkeys/platform.d.ts +10 -49
  118. package/dist/components/VHotkeys/platform.js.map +1 -1
  119. package/dist/components/VIcon/VIcon.d.ts +7 -19
  120. package/dist/components/VIcon/VIcon.js.map +1 -1
  121. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  122. package/dist/components/VIcon/iconProps.d.ts +5 -19
  123. package/dist/components/VIcon/iconProps.js.map +1 -1
  124. package/dist/components/VIcon/resolver.d.ts +15 -43
  125. package/dist/components/VIcon/resolver.js.map +1 -1
  126. package/dist/components/VIcon/types.d.ts +7 -34
  127. package/dist/components/VIconButton/VIconButton.d.ts +2 -3
  128. package/dist/components/VIconButton/VIconButton.js.map +1 -1
  129. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  130. package/dist/components/VInput/VInput.d.ts +14 -29
  131. package/dist/components/VInput/VInput.js.map +1 -1
  132. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  133. package/dist/components/VInput/VInputGroup.js.map +1 -1
  134. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +34 -50
  135. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  136. package/dist/components/VInput/context.js.map +1 -1
  137. package/dist/components/VInputOTP/VInputOTP.d.ts +8 -16
  138. package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
  139. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +55 -63
  140. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
  141. package/dist/components/VMenu/VMenu.js.map +1 -1
  142. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  143. package/dist/components/VMenu/VMenuGroup.d.ts +1 -6
  144. package/dist/components/VMenu/VMenuGroup.js.map +1 -1
  145. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  146. package/dist/components/VMenu/VMenuItem.d.ts +0 -5
  147. package/dist/components/VMenu/VMenuItem.js.map +1 -1
  148. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  149. package/dist/components/VMenu/VMenuPanel.d.ts +1 -6
  150. package/dist/components/VMenu/VMenuPanel.js.map +1 -1
  151. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +2 -2
  152. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  153. package/dist/components/VMenu/VMenuSeparator.js.map +1 -1
  154. package/dist/components/VMenu/VMenuSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  155. package/dist/components/VMenu/context.d.ts +3 -11
  156. package/dist/components/VMenu/context.js.map +1 -1
  157. package/dist/components/VPagination/VPagination.d.ts +9 -20
  158. package/dist/components/VPagination/VPagination.js.map +1 -1
  159. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  160. package/dist/components/VPopover/VPopover.d.ts +7 -18
  161. package/dist/components/VPopover/VPopover.js.map +1 -1
  162. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js +23 -27
  163. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  164. package/dist/components/VProgressCircular/VProgressCircular.d.ts +2 -6
  165. package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
  166. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
  167. package/dist/components/VProgressLinear/VProgressLinear.d.ts +4 -15
  168. package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
  169. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  170. package/dist/components/VRadio/VRadio.d.ts +1 -5
  171. package/dist/components/VRadio/VRadio.js.map +1 -1
  172. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  173. package/dist/components/VSeparator/VSeparator.d.ts +2 -15
  174. package/dist/components/VSeparator/VSeparator.js.map +1 -1
  175. package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  176. package/dist/components/VSideNavigation/VSideNavigation.d.ts +1 -1
  177. package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
  178. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
  179. package/dist/components/VSideNavigation/VSideNavigationGroup.d.ts +1 -5
  180. package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
  181. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  182. package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +1 -5
  183. package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
  184. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +80 -75
  185. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  186. package/dist/components/VSideNavigation/VSideNavigationSeparator.js.map +1 -1
  187. package/dist/components/VSideNavigation/context.js.map +1 -1
  188. package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +6 -19
  189. package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
  190. package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  191. package/dist/components/VSlider/VSlider.d.ts +5 -13
  192. package/dist/components/VSlider/VSlider.js.map +1 -1
  193. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  194. package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
  195. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  196. package/dist/components/VSnackbar/state.d.ts +4 -17
  197. package/dist/components/VSnackbar/state.js.map +1 -1
  198. package/dist/components/VSpinner/VSpinner.d.ts +3 -5
  199. package/dist/components/VSpinner/VSpinner.js.map +1 -1
  200. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
  201. package/dist/components/VSwitch/VSwitch.js.map +1 -1
  202. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  203. package/dist/components/VTabs/VTab.d.ts +1 -5
  204. package/dist/components/VTabs/VTab.js.map +1 -1
  205. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
  206. package/dist/components/VTabs/VTabPanel.js.map +1 -1
  207. package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  208. package/dist/components/VTabs/VTabs.d.ts +5 -17
  209. package/dist/components/VTabs/VTabs.js.map +1 -1
  210. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  211. package/dist/components/VTabs/context.js.map +1 -1
  212. package/dist/components/VTextarea/VTextarea.d.ts +13 -20
  213. package/dist/components/VTextarea/VTextarea.js.map +1 -1
  214. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  215. package/dist/components/VTimeInput/VTimeInput.d.ts +8 -21
  216. package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
  217. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  218. package/dist/components/VTimeInput/search.d.ts +3 -34
  219. package/dist/components/VTimeInput/search.js.map +1 -1
  220. package/dist/components/VTimePicker/VTimePicker.d.ts +9 -31
  221. package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
  222. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  223. package/dist/components/VTimePicker/limits.d.ts +18 -45
  224. package/dist/components/VTimePicker/limits.js.map +1 -1
  225. package/dist/components/VToast/VToast.js.map +1 -1
  226. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  227. package/dist/components/VToast/VToaster.js.map +1 -1
  228. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
  229. package/dist/components/VToast/state.d.ts +2 -7
  230. package/dist/components/VToast/state.js.map +1 -1
  231. package/dist/components/VToggle/VToggle.d.ts +5 -9
  232. package/dist/components/VToggle/VToggle.js.map +1 -1
  233. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
  234. package/dist/components/VToggle/VToggleItem.d.ts +1 -9
  235. package/dist/components/VToggle/VToggleItem.js.map +1 -1
  236. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  237. package/dist/components/VToggle/context.js.map +1 -1
  238. package/dist/components/VTooltip/VTooltip.d.ts +3 -16
  239. package/dist/components/VTooltip/VTooltip.js.map +1 -1
  240. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +2 -2
  241. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  242. package/dist/components/VTypography/VTypography.d.ts +4 -8
  243. package/dist/components/VTypography/VTypography.js.map +1 -1
  244. package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
  245. package/dist/composables/useAriaLabel.js.map +1 -1
  246. package/dist/composables/useClearable.d.ts +2 -8
  247. package/dist/composables/useClearable.js.map +1 -1
  248. package/dist/composables/useControlShape.js.map +1 -1
  249. package/dist/composables/useDetailsOpen.d.ts +2 -10
  250. package/dist/composables/useDetailsOpen.js.map +1 -1
  251. package/dist/composables/useFieldIds.js.map +1 -1
  252. package/dist/composables/useFieldPanel.d.ts +4 -17
  253. package/dist/composables/useFieldPanel.js.map +1 -1
  254. package/dist/composables/useFileDrop.js.map +1 -1
  255. package/dist/composables/useFileField.d.ts +1 -5
  256. package/dist/composables/useFileField.js.map +1 -1
  257. package/dist/composables/useFocusoutDismiss.js.map +1 -1
  258. package/dist/composables/useIconClickHandlers.d.ts +4 -19
  259. package/dist/composables/useIconClickHandlers.js.map +1 -1
  260. package/dist/composables/useInertLink.d.ts +1 -1
  261. package/dist/composables/useInertLink.js.map +1 -1
  262. package/dist/composables/useLiveAnnouncer.js.map +1 -1
  263. package/dist/composables/useMaskedField.d.ts +12 -27
  264. package/dist/composables/useMaskedField.js.map +1 -1
  265. package/dist/composables/usePopover.d.ts +3 -11
  266. package/dist/composables/usePopover.js.map +1 -1
  267. package/dist/composables/useProgressValue.js.map +1 -1
  268. package/dist/composables/useRootAttrs.d.ts +0 -9
  269. package/dist/composables/useRootAttrs.js +11 -4
  270. package/dist/composables/useRootAttrs.js.map +1 -1
  271. package/dist/composables/useSlotNodes.d.ts +3 -14
  272. package/dist/composables/useSlotNodes.js.map +1 -1
  273. package/dist/composables/useTextLimit.d.ts +4 -16
  274. package/dist/composables/useTextLimit.js.map +1 -1
  275. package/dist/composables/useTimer.d.ts +2 -15
  276. package/dist/composables/useTimer.js.map +1 -1
  277. package/dist/i18n/en.js.map +1 -1
  278. package/dist/i18n/fr.js.map +1 -1
  279. package/dist/i18n/state.d.ts +9 -38
  280. package/dist/i18n/state.js.map +1 -1
  281. package/dist/i18n/types.d.ts +19 -81
  282. package/dist/icons.d.ts +2 -12
  283. package/dist/tokens/css.d.ts +3 -16
  284. package/dist/tokens/css.js.map +1 -1
  285. package/dist/tokens/index.js.map +1 -1
  286. package/dist/tokens/primitives.js.map +1 -1
  287. package/dist/tokens/semantic.d.ts +1 -23
  288. package/dist/tokens/semantic.js.map +1 -1
  289. package/dist/tokens/themes/dark.js.map +1 -1
  290. package/dist/tokens/types.d.ts +2 -12
  291. package/dist/tokens/types.js.map +1 -1
  292. package/dist/types.d.ts +3 -21
  293. package/dist/utils/array.d.ts +2 -4
  294. package/dist/utils/array.js.map +1 -1
  295. package/dist/utils/arrowNav.d.ts +2 -26
  296. package/dist/utils/arrowNav.js.map +1 -1
  297. package/dist/utils/chip.d.ts +1 -8
  298. package/dist/utils/chip.js.map +1 -1
  299. package/dist/utils/clock.d.ts +16 -39
  300. package/dist/utils/clock.js.map +1 -1
  301. package/dist/utils/css.d.ts +5 -16
  302. package/dist/utils/css.js.map +1 -1
  303. package/dist/utils/date.d.ts +17 -60
  304. package/dist/utils/date.js.map +1 -1
  305. package/dist/utils/direction.d.ts +2 -9
  306. package/dist/utils/direction.js.map +1 -1
  307. package/dist/utils/env.d.ts +2 -15
  308. package/dist/utils/env.js.map +1 -1
  309. package/dist/utils/file.d.ts +8 -46
  310. package/dist/utils/file.js.map +1 -1
  311. package/dist/utils/focus.d.ts +1 -14
  312. package/dist/utils/focus.js +1 -6
  313. package/dist/utils/focus.js.map +1 -1
  314. package/dist/utils/hostWarns.d.ts +3 -8
  315. package/dist/utils/hostWarns.js.map +1 -1
  316. package/dist/utils/ids.d.ts +2 -7
  317. package/dist/utils/ids.js.map +1 -1
  318. package/dist/utils/matcher.d.ts +1 -8
  319. package/dist/utils/matcher.js.map +1 -1
  320. package/dist/utils/memo.d.ts +1 -11
  321. package/dist/utils/memo.js.map +1 -1
  322. package/dist/utils/number.d.ts +1 -7
  323. package/dist/utils/number.js.map +1 -1
  324. package/dist/utils/text.d.ts +4 -23
  325. package/dist/utils/text.js.map +1 -1
  326. package/dist/utils/time.d.ts +4 -12
  327. package/dist/utils/time.js.map +1 -1
  328. package/dist/utils/vnode.d.ts +0 -10
  329. package/dist/utils/vnode.js.map +1 -1
  330. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"VTypography.js","names":[],"sources":["../../../src/components/VTypography/VTypography.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The design system's text element. Rather than setting a size here and a weight\n * there, the writer names the ROLE the text plays — a heading, a caption, a label —\n * and `variant` applies the complete typographic recipe that goes with it: family,\n * size, weight, line height and letter spacing, all read from the semantic tokens.\n * `tone` does the same for colour.\n *\n * It renders a single element and adds no behaviour, so the attributes it is given\n * (id, for, aria-*…) land straight on that element. This is what lets it serve as a\n * real field label, with `as=\"label\"` and a `for`.\n */\nimport { computed } from 'vue'\n\n/** The role a text plays, each with its complete typographic recipe. */\nexport type TypographyVariant =\n | 'display'\n | 'heading-1'\n | 'heading-2'\n | 'heading-3'\n | 'heading-4'\n | 'subtitle'\n | 'body-xl'\n | 'body-lg'\n | 'body-md'\n | 'body-sm'\n | 'label'\n | 'caption'\n | 'overline'\n | 'code'\n\n/** The colour of a text; `default` sets none and inherits. */\nexport type TypographyTone =\n 'default' | 'muted' | 'subtle' | 'accent' | 'danger' | 'success' | 'warning' | 'on-inverse'\n\ninterface TypographyProps {\n /**\n * The role this text plays, which selects a complete recipe of typographic\n * tokens: size, weight, line height and, where the role calls for it, letter\n * spacing and a monospaced family.\n */\n variant?: TypographyVariant\n /**\n * The HTML tag to render. Each variant already has a sensible default (h1 to h4,\n * p, span, code), so this is for the cases where the meaning and the look differ:\n * a subtitle that is really an `h2`, or a label attached to a field.\n */\n as?: string\n /**\n * The colour of the text. `default` sets none at all, so the text inherits from\n * whatever surrounds it, which is what lets the same component be used on an\n * inverted surface or inside a coloured toast.\n */\n tone?: TypographyTone\n /**\n * Cuts the text to one line and ends it with an ellipsis. The element needs a\n * width to be cut against (being a block, or a flex item), otherwise there is\n * nothing to overflow and the text simply stays whole.\n */\n truncate?: boolean\n}\n\nconst props = withDefaults(defineProps<TypographyProps>(), {\n variant: 'body-md',\n as: undefined,\n tone: 'default',\n truncate: false,\n})\n\ndefineSlots<{\n /** Text content. */\n default(): unknown\n}>()\n\n/**\n * The tag each variant renders when `as` says nothing — the one whose meaning\n * matches the role.\n */\nconst DEFAULT_TAGS: Record<TypographyVariant, string> = {\n display: 'p',\n 'heading-1': 'h1',\n 'heading-2': 'h2',\n 'heading-3': 'h3',\n 'heading-4': 'h4',\n subtitle: 'p',\n 'body-xl': 'p',\n 'body-lg': 'p',\n 'body-md': 'p',\n 'body-sm': 'p',\n label: 'span',\n caption: 'p',\n overline: 'span',\n code: 'code',\n}\n\nconst tag = computed(() => props.as ?? DEFAULT_TAGS[props.variant])\n</script>\n\n<template>\n <component\n :is=\"tag\"\n class=\"v-typography\"\n :data-variant=\"variant\"\n :data-tone=\"tone === 'default' ? undefined : tone\"\n :data-truncate=\"truncate ? '' : undefined\"\n >\n <slot />\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-typography {\n /* These two defaults are re-declared on every instance because custom properties\n INHERIT: nested inside a variant that sets letter spacing, or inside a `code`,\n a VTypography would otherwise pick up its ancestor's tracking and monospaced\n family. The [data-variant] blocks below, being more specific, still win.\n\n Size, weight and leading need no such reset, and that is an INVARIANT rather than\n an oversight: every variant block sets all three, so none can be inherited. A new\n variant that leaves one out takes it from whichever VTypography encloses it — the\n same bug these two lines exist to prevent, one step further out. */\n --typography-family: var(--vectis-text-family);\n --typography-tracking: normal;\n\n margin: 0; /* the space around a text block belongs to the layout holding it */\n font-family: var(--typography-family);\n font-size: var(--typography-size);\n font-weight: var(--typography-weight);\n line-height: var(--typography-leading);\n letter-spacing: var(--typography-tracking);\n /* With the `default` tone no colour variable is set, and the fallback makes the\n text inherit — which is what allows the component to be dropped inside an\n already coloured context, an inverted surface or a tinted toast. */\n color: var(--typography-color, inherit);\n overflow-wrap: break-word;\n }\n\n /* TRAP — the colour variable inherits like any other, so without this reset a `default`\n VTypography nested in a toned one took the OUTER tone's colour instead of the colour of\n whatever sits between them. `initial` makes it guaranteed-invalid, which is what lets\n the fallback above apply. At zero specificity, so the tone rules below and the sheets\n that route a colour through the variable (VDataTable's title, VFilePicker's subtitle)\n win over it whatever order the sheets land in. */\n :where(.v-typography) {\n --typography-color: initial;\n }\n\n .v-typography[data-variant='display'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-display-size);\n --typography-weight: var(--vectis-text-display-weight);\n --typography-leading: var(--vectis-text-display-leading);\n --typography-tracking: var(--vectis-text-display-tracking);\n }\n\n .v-typography[data-variant='heading-1'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-1-size);\n --typography-weight: var(--vectis-text-heading-1-weight);\n --typography-leading: var(--vectis-text-heading-1-leading);\n --typography-tracking: var(--vectis-text-heading-1-tracking);\n }\n\n .v-typography[data-variant='heading-2'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-2-size);\n --typography-weight: var(--vectis-text-heading-2-weight);\n --typography-leading: var(--vectis-text-heading-2-leading);\n --typography-tracking: var(--vectis-text-heading-2-tracking);\n }\n\n .v-typography[data-variant='heading-3'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-3-size);\n --typography-weight: var(--vectis-text-heading-3-weight);\n --typography-leading: var(--vectis-text-heading-3-leading);\n }\n\n .v-typography[data-variant='heading-4'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-4-size);\n --typography-weight: var(--vectis-text-heading-4-weight);\n --typography-leading: var(--vectis-text-heading-4-leading);\n }\n\n .v-typography[data-variant='subtitle'] {\n --typography-size: var(--vectis-text-subtitle-size);\n --typography-weight: var(--vectis-text-subtitle-weight);\n --typography-leading: var(--vectis-text-subtitle-leading);\n }\n\n .v-typography[data-variant='body-xl'] {\n --typography-size: var(--vectis-text-body-xl-size);\n --typography-weight: var(--vectis-text-body-xl-weight);\n --typography-leading: var(--vectis-text-body-xl-leading);\n }\n\n .v-typography[data-variant='body-lg'] {\n --typography-size: var(--vectis-text-body-lg-size);\n --typography-weight: var(--vectis-text-body-lg-weight);\n --typography-leading: var(--vectis-text-body-lg-leading);\n }\n\n .v-typography[data-variant='body-md'] {\n --typography-size: var(--vectis-text-body-md-size);\n --typography-weight: var(--vectis-text-body-md-weight);\n --typography-leading: var(--vectis-text-body-md-leading);\n }\n\n .v-typography[data-variant='body-sm'] {\n --typography-size: var(--vectis-text-body-sm-size);\n --typography-weight: var(--vectis-text-body-sm-weight);\n --typography-leading: var(--vectis-text-body-sm-leading);\n }\n\n .v-typography[data-variant='label'] {\n --typography-size: var(--vectis-text-label-size);\n --typography-weight: var(--vectis-text-label-weight);\n --typography-leading: var(--vectis-text-label-leading);\n }\n\n .v-typography[data-variant='caption'] {\n --typography-size: var(--vectis-text-caption-size);\n --typography-weight: var(--vectis-text-caption-weight);\n --typography-leading: var(--vectis-text-caption-leading);\n }\n\n .v-typography[data-variant='overline'] {\n --typography-size: var(--vectis-text-overline-size);\n --typography-weight: var(--vectis-text-overline-weight);\n --typography-leading: var(--vectis-text-overline-leading);\n --typography-tracking: var(--vectis-text-overline-tracking);\n\n text-transform: uppercase;\n }\n\n .v-typography[data-variant='code'] {\n --typography-family: var(--vectis-text-family-code);\n --typography-size: var(--vectis-text-code-size);\n --typography-weight: var(--vectis-text-code-weight);\n --typography-leading: var(--vectis-text-code-leading);\n }\n\n /* There is deliberately no block for the `default` tone: leaving the colour\n variable unset is precisely what makes the text inherit. */\n .v-typography[data-tone='muted'] {\n --typography-color: var(--vectis-color-text-muted);\n }\n\n .v-typography[data-tone='subtle'] {\n --typography-color: var(--vectis-color-text-subtle);\n }\n\n .v-typography[data-tone='accent'] {\n --typography-color: var(--vectis-color-accent-text);\n }\n\n .v-typography[data-tone='danger'] {\n --typography-color: var(--vectis-color-danger-text);\n }\n\n .v-typography[data-tone='success'] {\n --typography-color: var(--vectis-color-success-text);\n }\n\n .v-typography[data-tone='warning'] {\n --typography-color: var(--vectis-color-warning-text);\n }\n\n .v-typography[data-tone='on-inverse'] {\n --typography-color: var(--vectis-color-text-on-inverse);\n }\n\n .v-typography[data-truncate] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VTypography.js","names":[],"sources":["../../../src/components/VTypography/VTypography.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Apply complete semantic typography recipes by role; choose native heading levels unless as\n * explicitly overrides the element.\n */\nimport { computed } from 'vue'\n\n/** The role a text plays, each with its complete typographic recipe. */\nexport type TypographyVariant =\n | 'display'\n | 'heading-1'\n | 'heading-2'\n | 'heading-3'\n | 'heading-4'\n | 'subtitle'\n | 'body-xl'\n | 'body-lg'\n | 'body-md'\n | 'body-sm'\n | 'label'\n | 'caption'\n | 'overline'\n | 'code'\n\n/** The colour of a text; `default` sets none and inherits. */\nexport type TypographyTone =\n 'default' | 'muted' | 'subtle' | 'accent' | 'danger' | 'success' | 'warning' | 'on-inverse'\n\ninterface TypographyProps {\n /**\n * The role this text plays, which selects a complete recipe of typographic\n * tokens: size, weight, line height and, where the role calls for it, letter\n * spacing and a monospaced family.\n */\n variant?: TypographyVariant\n /**\n * The HTML tag to render. Each variant already has a sensible default (h1 to h4,\n * p, span, code), so this is for the cases where the meaning and the look differ:\n * a subtitle that is really an `h2`, or a label attached to a field.\n */\n as?: string\n /**\n * The colour of the text. `default` sets none at all, so the text inherits from whatever\n * surrounds it, which lets the same component be used on an inverted surface or inside a\n * coloured toast.\n */\n tone?: TypographyTone\n /** Cuts the text to one line and ends it with an ellipsis. */\n truncate?: boolean\n}\n\nconst props = withDefaults(defineProps<TypographyProps>(), {\n variant: 'body-md',\n as: undefined,\n tone: 'default',\n truncate: false,\n})\n\ndefineSlots<{\n /** Text content. */\n default(): unknown\n}>()\n\n/** The tag each variant renders when `as` says nothing; the one whose meaning matches the role. */\nconst DEFAULT_TAGS: Record<TypographyVariant, string> = {\n display: 'p',\n 'heading-1': 'h1',\n 'heading-2': 'h2',\n 'heading-3': 'h3',\n 'heading-4': 'h4',\n subtitle: 'p',\n 'body-xl': 'p',\n 'body-lg': 'p',\n 'body-md': 'p',\n 'body-sm': 'p',\n label: 'span',\n caption: 'p',\n overline: 'span',\n code: 'code',\n}\n\nconst tag = computed(() => props.as ?? DEFAULT_TAGS[props.variant])\n</script>\n\n<template>\n <component\n :is=\"tag\"\n class=\"v-typography\"\n :data-variant=\"variant\"\n :data-tone=\"tone === 'default' ? undefined : tone\"\n :data-truncate=\"truncate ? '' : undefined\"\n >\n <slot />\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-typography {\n /*\n * These two defaults are re-declared on every instance because custom properties INHERIT:\n * nested inside a variant that sets letter spacing, or inside a `code`, a VTypography would\n * otherwise pick up its ancestor's tracking and monospaced family. The [data-variant]\n * blocks below, being more specific, still win.\n */\n --typography-family: var(--vectis-text-family);\n --typography-tracking: normal;\n\n margin: 0;\n font-family: var(--typography-family);\n font-size: var(--typography-size);\n font-weight: var(--typography-weight);\n line-height: var(--typography-leading);\n letter-spacing: var(--typography-tracking);\n /*\n * With the `default` tone no colour variable is set, and the fallback makes the text\n * inherit; which allows the component to be dropped inside an already coloured context, an\n * inverted surface or a tinted toast.\n */\n color: var(--typography-color, inherit);\n overflow-wrap: break-word;\n }\n\n /*\n * The colour variable inherits like any other, so without this reset a `default` VTypography\n * nested in a toned one took the OUTER tone's colour instead of the colour of whatever sits\n * between them. `initial` makes it guaranteed-invalid, which lets the fallback above apply.\n */\n :where(.v-typography) {\n --typography-color: initial;\n }\n\n .v-typography[data-variant='display'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-display-size);\n --typography-weight: var(--vectis-text-display-weight);\n --typography-leading: var(--vectis-text-display-leading);\n --typography-tracking: var(--vectis-text-display-tracking);\n }\n\n .v-typography[data-variant='heading-1'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-1-size);\n --typography-weight: var(--vectis-text-heading-1-weight);\n --typography-leading: var(--vectis-text-heading-1-leading);\n --typography-tracking: var(--vectis-text-heading-1-tracking);\n }\n\n .v-typography[data-variant='heading-2'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-2-size);\n --typography-weight: var(--vectis-text-heading-2-weight);\n --typography-leading: var(--vectis-text-heading-2-leading);\n --typography-tracking: var(--vectis-text-heading-2-tracking);\n }\n\n .v-typography[data-variant='heading-3'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-3-size);\n --typography-weight: var(--vectis-text-heading-3-weight);\n --typography-leading: var(--vectis-text-heading-3-leading);\n }\n\n .v-typography[data-variant='heading-4'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-4-size);\n --typography-weight: var(--vectis-text-heading-4-weight);\n --typography-leading: var(--vectis-text-heading-4-leading);\n }\n\n .v-typography[data-variant='subtitle'] {\n --typography-size: var(--vectis-text-subtitle-size);\n --typography-weight: var(--vectis-text-subtitle-weight);\n --typography-leading: var(--vectis-text-subtitle-leading);\n }\n\n .v-typography[data-variant='body-xl'] {\n --typography-size: var(--vectis-text-body-xl-size);\n --typography-weight: var(--vectis-text-body-xl-weight);\n --typography-leading: var(--vectis-text-body-xl-leading);\n }\n\n .v-typography[data-variant='body-lg'] {\n --typography-size: var(--vectis-text-body-lg-size);\n --typography-weight: var(--vectis-text-body-lg-weight);\n --typography-leading: var(--vectis-text-body-lg-leading);\n }\n\n .v-typography[data-variant='body-md'] {\n --typography-size: var(--vectis-text-body-md-size);\n --typography-weight: var(--vectis-text-body-md-weight);\n --typography-leading: var(--vectis-text-body-md-leading);\n }\n\n .v-typography[data-variant='body-sm'] {\n --typography-size: var(--vectis-text-body-sm-size);\n --typography-weight: var(--vectis-text-body-sm-weight);\n --typography-leading: var(--vectis-text-body-sm-leading);\n }\n\n .v-typography[data-variant='label'] {\n --typography-size: var(--vectis-text-label-size);\n --typography-weight: var(--vectis-text-label-weight);\n --typography-leading: var(--vectis-text-label-leading);\n }\n\n .v-typography[data-variant='caption'] {\n --typography-size: var(--vectis-text-caption-size);\n --typography-weight: var(--vectis-text-caption-weight);\n --typography-leading: var(--vectis-text-caption-leading);\n }\n\n .v-typography[data-variant='overline'] {\n --typography-size: var(--vectis-text-overline-size);\n --typography-weight: var(--vectis-text-overline-weight);\n --typography-leading: var(--vectis-text-overline-leading);\n --typography-tracking: var(--vectis-text-overline-tracking);\n\n text-transform: uppercase;\n }\n\n .v-typography[data-variant='code'] {\n --typography-family: var(--vectis-text-family-code);\n --typography-size: var(--vectis-text-code-size);\n --typography-weight: var(--vectis-text-code-weight);\n --typography-leading: var(--vectis-text-code-leading);\n }\n\n /* There is deliberately no block for the `default` tone: leaving the colour\n variable unset is precisely what makes the text inherit. */\n .v-typography[data-tone='muted'] {\n --typography-color: var(--vectis-color-text-muted);\n }\n\n .v-typography[data-tone='subtle'] {\n --typography-color: var(--vectis-color-text-subtle);\n }\n\n .v-typography[data-tone='accent'] {\n --typography-color: var(--vectis-color-accent-text);\n }\n\n .v-typography[data-tone='danger'] {\n --typography-color: var(--vectis-color-danger-text);\n }\n\n .v-typography[data-tone='success'] {\n --typography-color: var(--vectis-color-success-text);\n }\n\n .v-typography[data-tone='warning'] {\n --typography-color: var(--vectis-color-warning-text);\n }\n\n .v-typography[data-tone='on-inverse'] {\n --typography-color: var(--vectis-color-text-on-inverse);\n }\n\n .v-typography[data-truncate] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"VTypography.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VTypography/VTypography.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * The design system's text element. Rather than setting a size here and a weight\n * there, the writer names the ROLE the text plays — a heading, a caption, a label —\n * and `variant` applies the complete typographic recipe that goes with it: family,\n * size, weight, line height and letter spacing, all read from the semantic tokens.\n * `tone` does the same for colour.\n *\n * It renders a single element and adds no behaviour, so the attributes it is given\n * (id, for, aria-*…) land straight on that element. This is what lets it serve as a\n * real field label, with `as=\"label\"` and a `for`.\n */\nimport { computed } from 'vue'\n\n/** The role a text plays, each with its complete typographic recipe. */\nexport type TypographyVariant =\n | 'display'\n | 'heading-1'\n | 'heading-2'\n | 'heading-3'\n | 'heading-4'\n | 'subtitle'\n | 'body-xl'\n | 'body-lg'\n | 'body-md'\n | 'body-sm'\n | 'label'\n | 'caption'\n | 'overline'\n | 'code'\n\n/** The colour of a text; `default` sets none and inherits. */\nexport type TypographyTone =\n 'default' | 'muted' | 'subtle' | 'accent' | 'danger' | 'success' | 'warning' | 'on-inverse'\n\ninterface TypographyProps {\n /**\n * The role this text plays, which selects a complete recipe of typographic\n * tokens: size, weight, line height and, where the role calls for it, letter\n * spacing and a monospaced family.\n */\n variant?: TypographyVariant\n /**\n * The HTML tag to render. Each variant already has a sensible default (h1 to h4,\n * p, span, code), so this is for the cases where the meaning and the look differ:\n * a subtitle that is really an `h2`, or a label attached to a field.\n */\n as?: string\n /**\n * The colour of the text. `default` sets none at all, so the text inherits from\n * whatever surrounds it, which is what lets the same component be used on an\n * inverted surface or inside a coloured toast.\n */\n tone?: TypographyTone\n /**\n * Cuts the text to one line and ends it with an ellipsis. The element needs a\n * width to be cut against (being a block, or a flex item), otherwise there is\n * nothing to overflow and the text simply stays whole.\n */\n truncate?: boolean\n}\n\nconst props = withDefaults(defineProps<TypographyProps>(), {\n variant: 'body-md',\n as: undefined,\n tone: 'default',\n truncate: false,\n})\n\ndefineSlots<{\n /** Text content. */\n default(): unknown\n}>()\n\n/**\n * The tag each variant renders when `as` says nothing — the one whose meaning\n * matches the role.\n */\nconst DEFAULT_TAGS: Record<TypographyVariant, string> = {\n display: 'p',\n 'heading-1': 'h1',\n 'heading-2': 'h2',\n 'heading-3': 'h3',\n 'heading-4': 'h4',\n subtitle: 'p',\n 'body-xl': 'p',\n 'body-lg': 'p',\n 'body-md': 'p',\n 'body-sm': 'p',\n label: 'span',\n caption: 'p',\n overline: 'span',\n code: 'code',\n}\n\nconst tag = computed(() => props.as ?? DEFAULT_TAGS[props.variant])\n</script>\n\n<template>\n <component\n :is=\"tag\"\n class=\"v-typography\"\n :data-variant=\"variant\"\n :data-tone=\"tone === 'default' ? undefined : tone\"\n :data-truncate=\"truncate ? '' : undefined\"\n >\n <slot />\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-typography {\n /* These two defaults are re-declared on every instance because custom properties\n INHERIT: nested inside a variant that sets letter spacing, or inside a `code`,\n a VTypography would otherwise pick up its ancestor's tracking and monospaced\n family. The [data-variant] blocks below, being more specific, still win.\n\n Size, weight and leading need no such reset, and that is an INVARIANT rather than\n an oversight: every variant block sets all three, so none can be inherited. A new\n variant that leaves one out takes it from whichever VTypography encloses it — the\n same bug these two lines exist to prevent, one step further out. */\n --typography-family: var(--vectis-text-family);\n --typography-tracking: normal;\n\n margin: 0; /* the space around a text block belongs to the layout holding it */\n font-family: var(--typography-family);\n font-size: var(--typography-size);\n font-weight: var(--typography-weight);\n line-height: var(--typography-leading);\n letter-spacing: var(--typography-tracking);\n /* With the `default` tone no colour variable is set, and the fallback makes the\n text inherit — which is what allows the component to be dropped inside an\n already coloured context, an inverted surface or a tinted toast. */\n color: var(--typography-color, inherit);\n overflow-wrap: break-word;\n }\n\n /* TRAP — the colour variable inherits like any other, so without this reset a `default`\n VTypography nested in a toned one took the OUTER tone's colour instead of the colour of\n whatever sits between them. `initial` makes it guaranteed-invalid, which is what lets\n the fallback above apply. At zero specificity, so the tone rules below and the sheets\n that route a colour through the variable (VDataTable's title, VFilePicker's subtitle)\n win over it whatever order the sheets land in. */\n :where(.v-typography) {\n --typography-color: initial;\n }\n\n .v-typography[data-variant='display'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-display-size);\n --typography-weight: var(--vectis-text-display-weight);\n --typography-leading: var(--vectis-text-display-leading);\n --typography-tracking: var(--vectis-text-display-tracking);\n }\n\n .v-typography[data-variant='heading-1'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-1-size);\n --typography-weight: var(--vectis-text-heading-1-weight);\n --typography-leading: var(--vectis-text-heading-1-leading);\n --typography-tracking: var(--vectis-text-heading-1-tracking);\n }\n\n .v-typography[data-variant='heading-2'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-2-size);\n --typography-weight: var(--vectis-text-heading-2-weight);\n --typography-leading: var(--vectis-text-heading-2-leading);\n --typography-tracking: var(--vectis-text-heading-2-tracking);\n }\n\n .v-typography[data-variant='heading-3'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-3-size);\n --typography-weight: var(--vectis-text-heading-3-weight);\n --typography-leading: var(--vectis-text-heading-3-leading);\n }\n\n .v-typography[data-variant='heading-4'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-4-size);\n --typography-weight: var(--vectis-text-heading-4-weight);\n --typography-leading: var(--vectis-text-heading-4-leading);\n }\n\n .v-typography[data-variant='subtitle'] {\n --typography-size: var(--vectis-text-subtitle-size);\n --typography-weight: var(--vectis-text-subtitle-weight);\n --typography-leading: var(--vectis-text-subtitle-leading);\n }\n\n .v-typography[data-variant='body-xl'] {\n --typography-size: var(--vectis-text-body-xl-size);\n --typography-weight: var(--vectis-text-body-xl-weight);\n --typography-leading: var(--vectis-text-body-xl-leading);\n }\n\n .v-typography[data-variant='body-lg'] {\n --typography-size: var(--vectis-text-body-lg-size);\n --typography-weight: var(--vectis-text-body-lg-weight);\n --typography-leading: var(--vectis-text-body-lg-leading);\n }\n\n .v-typography[data-variant='body-md'] {\n --typography-size: var(--vectis-text-body-md-size);\n --typography-weight: var(--vectis-text-body-md-weight);\n --typography-leading: var(--vectis-text-body-md-leading);\n }\n\n .v-typography[data-variant='body-sm'] {\n --typography-size: var(--vectis-text-body-sm-size);\n --typography-weight: var(--vectis-text-body-sm-weight);\n --typography-leading: var(--vectis-text-body-sm-leading);\n }\n\n .v-typography[data-variant='label'] {\n --typography-size: var(--vectis-text-label-size);\n --typography-weight: var(--vectis-text-label-weight);\n --typography-leading: var(--vectis-text-label-leading);\n }\n\n .v-typography[data-variant='caption'] {\n --typography-size: var(--vectis-text-caption-size);\n --typography-weight: var(--vectis-text-caption-weight);\n --typography-leading: var(--vectis-text-caption-leading);\n }\n\n .v-typography[data-variant='overline'] {\n --typography-size: var(--vectis-text-overline-size);\n --typography-weight: var(--vectis-text-overline-weight);\n --typography-leading: var(--vectis-text-overline-leading);\n --typography-tracking: var(--vectis-text-overline-tracking);\n\n text-transform: uppercase;\n }\n\n .v-typography[data-variant='code'] {\n --typography-family: var(--vectis-text-family-code);\n --typography-size: var(--vectis-text-code-size);\n --typography-weight: var(--vectis-text-code-weight);\n --typography-leading: var(--vectis-text-code-leading);\n }\n\n /* There is deliberately no block for the `default` tone: leaving the colour\n variable unset is precisely what makes the text inherit. */\n .v-typography[data-tone='muted'] {\n --typography-color: var(--vectis-color-text-muted);\n }\n\n .v-typography[data-tone='subtle'] {\n --typography-color: var(--vectis-color-text-subtle);\n }\n\n .v-typography[data-tone='accent'] {\n --typography-color: var(--vectis-color-accent-text);\n }\n\n .v-typography[data-tone='danger'] {\n --typography-color: var(--vectis-color-danger-text);\n }\n\n .v-typography[data-tone='success'] {\n --typography-color: var(--vectis-color-success-text);\n }\n\n .v-typography[data-tone='warning'] {\n --typography-color: var(--vectis-color-warning-text);\n }\n\n .v-typography[data-tone='on-inverse'] {\n --typography-color: var(--vectis-color-text-on-inverse);\n }\n\n .v-typography[data-truncate] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;EA8DA,IAAM,IAAQ,GAgBR,IAAkD;GACtD,SAAS;GACT,aAAa;GACb,aAAa;GACb,aAAa;GACb,aAAa;GACb,UAAU;GACV,WAAW;GACX,WAAW;GACX,WAAW;GACX,WAAW;GACX,OAAO;GACP,SAAS;GACT,UAAU;GACV,MAAM;EACR,GAEM,IAAM,QAAe,EAAM,MAAM,EAAa,EAAM,QAAQ;yBAIhE,EAQY,EAPL,EAAA,KAAG,GAAA;GACR,OAAM;GACL,gBAAc,EAAA;GACd,aAAW,EAAA,SAAI,YAAiB,KAAA,IAAY,EAAA;GAC5C,iBAAe,EAAA,WAAQ,KAAQ,KAAA;;oBAExB,CAAR,EAAQ,EAAA,QAAA,SAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"VTypography.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VTypography/VTypography.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * Apply complete semantic typography recipes by role; choose native heading levels unless as\n * explicitly overrides the element.\n */\nimport { computed } from 'vue'\n\n/** The role a text plays, each with its complete typographic recipe. */\nexport type TypographyVariant =\n | 'display'\n | 'heading-1'\n | 'heading-2'\n | 'heading-3'\n | 'heading-4'\n | 'subtitle'\n | 'body-xl'\n | 'body-lg'\n | 'body-md'\n | 'body-sm'\n | 'label'\n | 'caption'\n | 'overline'\n | 'code'\n\n/** The colour of a text; `default` sets none and inherits. */\nexport type TypographyTone =\n 'default' | 'muted' | 'subtle' | 'accent' | 'danger' | 'success' | 'warning' | 'on-inverse'\n\ninterface TypographyProps {\n /**\n * The role this text plays, which selects a complete recipe of typographic\n * tokens: size, weight, line height and, where the role calls for it, letter\n * spacing and a monospaced family.\n */\n variant?: TypographyVariant\n /**\n * The HTML tag to render. Each variant already has a sensible default (h1 to h4,\n * p, span, code), so this is for the cases where the meaning and the look differ:\n * a subtitle that is really an `h2`, or a label attached to a field.\n */\n as?: string\n /**\n * The colour of the text. `default` sets none at all, so the text inherits from whatever\n * surrounds it, which lets the same component be used on an inverted surface or inside a\n * coloured toast.\n */\n tone?: TypographyTone\n /** Cuts the text to one line and ends it with an ellipsis. */\n truncate?: boolean\n}\n\nconst props = withDefaults(defineProps<TypographyProps>(), {\n variant: 'body-md',\n as: undefined,\n tone: 'default',\n truncate: false,\n})\n\ndefineSlots<{\n /** Text content. */\n default(): unknown\n}>()\n\n/** The tag each variant renders when `as` says nothing; the one whose meaning matches the role. */\nconst DEFAULT_TAGS: Record<TypographyVariant, string> = {\n display: 'p',\n 'heading-1': 'h1',\n 'heading-2': 'h2',\n 'heading-3': 'h3',\n 'heading-4': 'h4',\n subtitle: 'p',\n 'body-xl': 'p',\n 'body-lg': 'p',\n 'body-md': 'p',\n 'body-sm': 'p',\n label: 'span',\n caption: 'p',\n overline: 'span',\n code: 'code',\n}\n\nconst tag = computed(() => props.as ?? DEFAULT_TAGS[props.variant])\n</script>\n\n<template>\n <component\n :is=\"tag\"\n class=\"v-typography\"\n :data-variant=\"variant\"\n :data-tone=\"tone === 'default' ? undefined : tone\"\n :data-truncate=\"truncate ? '' : undefined\"\n >\n <slot />\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-typography {\n /*\n * These two defaults are re-declared on every instance because custom properties INHERIT:\n * nested inside a variant that sets letter spacing, or inside a `code`, a VTypography would\n * otherwise pick up its ancestor's tracking and monospaced family. The [data-variant]\n * blocks below, being more specific, still win.\n */\n --typography-family: var(--vectis-text-family);\n --typography-tracking: normal;\n\n margin: 0;\n font-family: var(--typography-family);\n font-size: var(--typography-size);\n font-weight: var(--typography-weight);\n line-height: var(--typography-leading);\n letter-spacing: var(--typography-tracking);\n /*\n * With the `default` tone no colour variable is set, and the fallback makes the text\n * inherit; which allows the component to be dropped inside an already coloured context, an\n * inverted surface or a tinted toast.\n */\n color: var(--typography-color, inherit);\n overflow-wrap: break-word;\n }\n\n /*\n * The colour variable inherits like any other, so without this reset a `default` VTypography\n * nested in a toned one took the OUTER tone's colour instead of the colour of whatever sits\n * between them. `initial` makes it guaranteed-invalid, which lets the fallback above apply.\n */\n :where(.v-typography) {\n --typography-color: initial;\n }\n\n .v-typography[data-variant='display'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-display-size);\n --typography-weight: var(--vectis-text-display-weight);\n --typography-leading: var(--vectis-text-display-leading);\n --typography-tracking: var(--vectis-text-display-tracking);\n }\n\n .v-typography[data-variant='heading-1'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-1-size);\n --typography-weight: var(--vectis-text-heading-1-weight);\n --typography-leading: var(--vectis-text-heading-1-leading);\n --typography-tracking: var(--vectis-text-heading-1-tracking);\n }\n\n .v-typography[data-variant='heading-2'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-2-size);\n --typography-weight: var(--vectis-text-heading-2-weight);\n --typography-leading: var(--vectis-text-heading-2-leading);\n --typography-tracking: var(--vectis-text-heading-2-tracking);\n }\n\n .v-typography[data-variant='heading-3'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-3-size);\n --typography-weight: var(--vectis-text-heading-3-weight);\n --typography-leading: var(--vectis-text-heading-3-leading);\n }\n\n .v-typography[data-variant='heading-4'] {\n --typography-family: var(--vectis-text-family-heading);\n --typography-size: var(--vectis-text-heading-4-size);\n --typography-weight: var(--vectis-text-heading-4-weight);\n --typography-leading: var(--vectis-text-heading-4-leading);\n }\n\n .v-typography[data-variant='subtitle'] {\n --typography-size: var(--vectis-text-subtitle-size);\n --typography-weight: var(--vectis-text-subtitle-weight);\n --typography-leading: var(--vectis-text-subtitle-leading);\n }\n\n .v-typography[data-variant='body-xl'] {\n --typography-size: var(--vectis-text-body-xl-size);\n --typography-weight: var(--vectis-text-body-xl-weight);\n --typography-leading: var(--vectis-text-body-xl-leading);\n }\n\n .v-typography[data-variant='body-lg'] {\n --typography-size: var(--vectis-text-body-lg-size);\n --typography-weight: var(--vectis-text-body-lg-weight);\n --typography-leading: var(--vectis-text-body-lg-leading);\n }\n\n .v-typography[data-variant='body-md'] {\n --typography-size: var(--vectis-text-body-md-size);\n --typography-weight: var(--vectis-text-body-md-weight);\n --typography-leading: var(--vectis-text-body-md-leading);\n }\n\n .v-typography[data-variant='body-sm'] {\n --typography-size: var(--vectis-text-body-sm-size);\n --typography-weight: var(--vectis-text-body-sm-weight);\n --typography-leading: var(--vectis-text-body-sm-leading);\n }\n\n .v-typography[data-variant='label'] {\n --typography-size: var(--vectis-text-label-size);\n --typography-weight: var(--vectis-text-label-weight);\n --typography-leading: var(--vectis-text-label-leading);\n }\n\n .v-typography[data-variant='caption'] {\n --typography-size: var(--vectis-text-caption-size);\n --typography-weight: var(--vectis-text-caption-weight);\n --typography-leading: var(--vectis-text-caption-leading);\n }\n\n .v-typography[data-variant='overline'] {\n --typography-size: var(--vectis-text-overline-size);\n --typography-weight: var(--vectis-text-overline-weight);\n --typography-leading: var(--vectis-text-overline-leading);\n --typography-tracking: var(--vectis-text-overline-tracking);\n\n text-transform: uppercase;\n }\n\n .v-typography[data-variant='code'] {\n --typography-family: var(--vectis-text-family-code);\n --typography-size: var(--vectis-text-code-size);\n --typography-weight: var(--vectis-text-code-weight);\n --typography-leading: var(--vectis-text-code-leading);\n }\n\n /* There is deliberately no block for the `default` tone: leaving the colour\n variable unset is precisely what makes the text inherit. */\n .v-typography[data-tone='muted'] {\n --typography-color: var(--vectis-color-text-muted);\n }\n\n .v-typography[data-tone='subtle'] {\n --typography-color: var(--vectis-color-text-subtle);\n }\n\n .v-typography[data-tone='accent'] {\n --typography-color: var(--vectis-color-accent-text);\n }\n\n .v-typography[data-tone='danger'] {\n --typography-color: var(--vectis-color-danger-text);\n }\n\n .v-typography[data-tone='success'] {\n --typography-color: var(--vectis-color-success-text);\n }\n\n .v-typography[data-tone='warning'] {\n --typography-color: var(--vectis-color-warning-text);\n }\n\n .v-typography[data-tone='on-inverse'] {\n --typography-color: var(--vectis-color-text-on-inverse);\n }\n\n .v-typography[data-truncate] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;EAmDA,IAAM,IAAQ,GAaR,IAAkD;GACtD,SAAS;GACT,aAAa;GACb,aAAa;GACb,aAAa;GACb,aAAa;GACb,UAAU;GACV,WAAW;GACX,WAAW;GACX,WAAW;GACX,WAAW;GACX,OAAO;GACP,SAAS;GACT,UAAU;GACV,MAAM;EACR,GAEM,IAAM,QAAe,EAAM,MAAM,EAAa,EAAM,QAAQ;yBAIhE,EAQY,EAPL,EAAA,KAAG,GAAA;GACR,OAAM;GACL,gBAAc,EAAA;GACd,aAAW,EAAA,SAAI,YAAiB,KAAA,IAAY,EAAA;GAC5C,iBAAe,EAAA,WAAQ,KAAQ,KAAA;;oBAExB,CAAR,EAAQ,EAAA,QAAA,SAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAriaLabel.js","names":[],"sources":["../../src/composables/useAriaLabel.ts"],"sourcesContent":["// @a11y\n/**\n * The accessible name of a labelled container — a tablist, a group, a nav — resolved\n * `aria-labelledby` > consumer `aria-label` > the `label` prop.\n *\n * `label` is therefore only a default: `aria-label` replaces it, and `aria-labelledby`\n * removes it outright, or the element would carry two names at once.\n *\n * The arbitration is only NEEDED where a component both spreads `$attrs` and binds its own\n * `:aria-label` on the same element (VTabs, VToggle): nothing would choose, and both would\n * apply. Elsewhere fallthrough already lets the consumer win and this just drops a default\n * that has become redundant.\n */\n\nimport { computed, useAttrs, type ComputedRef } from 'vue'\n\nexport function useAriaLabel(label: () => string | undefined): ComputedRef<string | undefined> {\n const attrs = useAttrs()\n return computed(() =>\n attrs['aria-labelledby'] != null\n ? undefined\n : ((attrs['aria-label'] as string | undefined) ?? label()),\n )\n}\n"],"mappings":";;AAgBA,SAAgB,EAAa,GAAkE;CAC7F,IAAM,IAAQ,EAAS;CACvB,OAAO,QACL,EAAM,sBAAsB,OAEtB,EAAM,iBAAwC,EAAM,IADtD,KAAA,CAEN;AACF"}
1
+ {"version":3,"file":"useAriaLabel.js","names":[],"sources":["../../src/composables/useAriaLabel.ts"],"sourcesContent":["// @a11y\n/**\n * Resolve accessible names in order: aria-labelledby, consumer aria-label, label fallback.\n * Suppress aria-label when aria-labelledby names the element.\n */\n\nimport { computed, useAttrs, type ComputedRef } from 'vue'\n\nexport function useAriaLabel(label: () => string | undefined): ComputedRef<string | undefined> {\n const attrs = useAttrs()\n return computed(() =>\n attrs['aria-labelledby'] != null\n ? undefined\n : ((attrs['aria-label'] as string | undefined) ?? label()),\n )\n}\n"],"mappings":";;AAQA,SAAgB,EAAa,GAAkE;CAC7F,IAAM,IAAQ,EAAS;CACvB,OAAO,QACL,EAAM,sBAAsB,OAEtB,EAAM,iBAAwC,EAAM,IADtD,KAAA,CAEN;AACF"}
@@ -5,7 +5,7 @@ interface ClearableOptions {
5
5
  /**
6
6
  * The consumer's EXPLICIT answer to "is there anything to clear?", which holds even on a
7
7
  * read-only field: the value of a read-only date or time picker changes through its panel
8
- * rather than by typing. Left undefined, the field answers for itself.
8
+ * rather than by typing.
9
9
  */
10
10
  clearVisible: () => boolean | undefined;
11
11
  /** Whether the field is unusable, the row it sits in taken into account. */
@@ -23,13 +23,7 @@ interface ClearableOptions {
23
23
  }
24
24
  /**
25
25
  * Whether a field offers its cross: it asks for one, it can be used and changed, and it holds
26
- * something. The fields composed on top of VInput: VCombobox, VDateInput, VTimeInput,
27
- * VFileInput, hold their value somewhere other than the text, so they work out `filled`
28
- * themselves and hand VInput the result as `clearVisible`; the other terms are the same for
29
- * every field and are written here once.
30
- *
31
- * `disabled` is passed apart from the props because it is the value RESOLVED against the row
32
- * the field sits in, which the prop alone is not.
26
+ * something.
33
27
  */
34
28
  export declare function canClear(props: {
35
29
  clearable: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useClearable.js","names":[],"sources":["../../src/composables/useClearable.ts"],"sourcesContent":["/**\n * The cross that empties a field, shared by VInput and VTextarea — and, through\n * `canClear`, the answer the fields composed on top of VInput give it.\n *\n * The two halves of it are one decision: WHEN the cross is shown, and what pressing it does.\n * They were written out in both components and had already started to drift — one asked\n * `props.disabled` where the other asked the value resolved against the row it sits in — which\n * is the kind of divergence nothing catches, the cross simply staying visible in a disabled\n * VInputGroup.\n *\n * The focus is the accessibility half and is not optional: the cross VANISHES the instant it\n * empties the field, so without that call the focus would be left on a button that no longer\n * exists and fall back to the top of the document.\n */\nimport { computed } from 'vue'\nimport type { ComputedRef, Ref } from 'vue'\n\ninterface ClearableOptions {\n /** Whether the field offers a cross at all. */\n clearable: () => boolean\n /**\n * The consumer's EXPLICIT answer to \"is there anything to clear?\", which holds even on a\n * read-only field: the value of a read-only date or time picker changes through its panel\n * rather than by typing. Left undefined, the field answers for itself.\n */\n clearVisible: () => boolean | undefined\n /** Whether the field is unusable, the row it sits in taken into account. */\n disabled: () => boolean\n /** Whether the field refuses to be typed into. */\n readonly: () => boolean\n /** What the field currently holds, as text. */\n text: () => string\n /** The real control, which the focus goes back to. */\n controlEl: Ref<{ focus: () => void } | null>\n /** Empties the value and tells the consumer. */\n onCleared: () => void\n}\n\n/**\n * Whether a field offers its cross: it asks for one, it can be used and changed, and it holds\n * something. The fields composed on top of VInput: VCombobox, VDateInput, VTimeInput,\n * VFileInput, hold their value somewhere other than the text, so they work out `filled`\n * themselves and hand VInput the result as `clearVisible`; the other terms are the same for\n * every field and are written here once.\n *\n * `disabled` is passed apart from the props because it is the value RESOLVED against the row\n * the field sits in, which the prop alone is not.\n */\nexport function canClear(\n props: { clearable: boolean; readonly: boolean },\n disabled: boolean,\n filled: boolean,\n): boolean {\n return props.clearable && !disabled && !props.readonly && filled\n}\n\nexport function useClearable(options: ClearableOptions): {\n showClear: ComputedRef<boolean>\n onClear: () => void\n} {\n // An explicit `clearVisible` replaces the read-only and the content terms together: a\n // composed field has already asked both through `canClear`.\n const showClear = computed(() => {\n if (!options.clearable() || options.disabled()) return false\n return options.clearVisible() ?? (!options.readonly() && options.text().length > 0)\n })\n\n function onClear() {\n options.onCleared()\n options.controlEl.value?.focus()\n }\n\n return { showClear, onClear }\n}\n"],"mappings":";;AAgDA,SAAgB,EACd,GACA,GACA,GACS;CACT,OAAO,EAAM,aAAa,CAAC,KAAY,CAAC,EAAM,YAAY;AAC5D;AAEA,SAAgB,EAAa,GAG3B;CAGA,IAAM,IAAY,QACZ,CAAC,EAAQ,UAAU,KAAK,EAAQ,SAAS,IAAU,KAChD,EAAQ,aAAa,MAAM,CAAC,EAAQ,SAAS,KAAK,EAAQ,KAAK,CAAC,CAAC,SAAS,EAClF;CAED,SAAS,IAAU;EAEjB,AADA,EAAQ,UAAU,GAClB,EAAQ,UAAU,OAAO,MAAM;CACjC;CAEA,OAAO;EAAE;EAAW;CAAQ;AAC9B"}
1
+ {"version":3,"file":"useClearable.js","names":[],"sources":["../../src/composables/useClearable.ts"],"sourcesContent":["/**\n * Share clear-button visibility and activation. Restore field focus because clearing removes\n * the button currently holding it.\n */\nimport { computed } from 'vue'\nimport type { ComputedRef, Ref } from 'vue'\n\ninterface ClearableOptions {\n /** Whether the field offers a cross at all. */\n clearable: () => boolean\n /**\n * The consumer's EXPLICIT answer to \"is there anything to clear?\", which holds even on a\n * read-only field: the value of a read-only date or time picker changes through its panel\n * rather than by typing.\n */\n clearVisible: () => boolean | undefined\n /** Whether the field is unusable, the row it sits in taken into account. */\n disabled: () => boolean\n /** Whether the field refuses to be typed into. */\n readonly: () => boolean\n /** What the field currently holds, as text. */\n text: () => string\n /** The real control, which the focus goes back to. */\n controlEl: Ref<{ focus: () => void } | null>\n /** Empties the value and tells the consumer. */\n onCleared: () => void\n}\n\n/**\n * Whether a field offers its cross: it asks for one, it can be used and changed, and it holds\n * something.\n */\nexport function canClear(\n props: { clearable: boolean; readonly: boolean },\n disabled: boolean,\n filled: boolean,\n): boolean {\n return props.clearable && !disabled && !props.readonly && filled\n}\n\nexport function useClearable(options: ClearableOptions): {\n showClear: ComputedRef<boolean>\n onClear: () => void\n} {\n // An explicit `clearVisible` replaces the read-only and the content terms together: a\n // composed field has already asked both through `canClear`.\n const showClear = computed(() => {\n if (!options.clearable() || options.disabled()) return false\n return options.clearVisible() ?? (!options.readonly() && options.text().length > 0)\n })\n\n function onClear() {\n options.onCleared()\n options.controlEl.value?.focus()\n }\n\n return { showClear, onClear }\n}\n"],"mappings":";;AAgCA,SAAgB,EACd,GACA,GACA,GACS;CACT,OAAO,EAAM,aAAa,CAAC,KAAY,CAAC,EAAM,YAAY;AAC5D;AAEA,SAAgB,EAAa,GAG3B;CAGA,IAAM,IAAY,QACZ,CAAC,EAAQ,UAAU,KAAK,EAAQ,SAAS,IAAU,KAChD,EAAQ,aAAa,MAAM,CAAC,EAAQ,SAAS,KAAK,EAAQ,KAAK,CAAC,CAAC,SAAS,EAClF;CAED,SAAS,IAAU;EAEjB,AADA,EAAQ,UAAU,GAClB,EAAQ,UAAU,OAAO,MAAM;CACjC;CAEA,OAAO;EAAE;EAAW;CAAQ;AAC9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"useControlShape.js","names":[],"sources":["../../src/composables/useControlShape.ts"],"sourcesContent":["/**\n * The arbitration between a control and the row it sits in, resolved once.\n *\n * Seven components read a group context — VButton from a VButtonGroup, and the five fields\n * and VSlider's number fields from a VInputGroup — and each of them wrote the same three\n * computeds by hand. Two of the\n * three use `??` and the third uses `||`, and that asymmetry is the whole rule: `size` and\n * `compact` are the SHAPE of the control, which belongs to the row, while `disabled` is\n * CUMULATIVE, so a row switched off cannot have one of its segments opt back in. Written out\n * six times, one of the six was always going to end up with the wrong operator.\n *\n * It takes the injected context rather than the key so a component keeps its own typing: the\n * two contexts carry different sizes, and neither is `unknown` on the other side.\n */\nimport { computed } from 'vue'\nimport type { ComputedRef } from 'vue'\n\n/** The three members every group context shares, whatever else it carries. */\ninterface ShapeContext<S> {\n readonly size?: S\n readonly compact?: boolean\n readonly disabled?: boolean\n}\n\ninterface ShapeProps<S> {\n readonly size: S\n readonly compact: boolean\n readonly disabled: boolean\n}\n\nexport interface ResolvedShape<S> {\n /** The height, the row's if it named one. */\n size: ComputedRef<S>\n /** The reduced density, the row's if it named one. */\n compact: ComputedRef<boolean>\n /** Unusable as soon as EITHER the row or the control says so. */\n disabled: ComputedRef<boolean>\n}\n\nexport function useControlShape<S>(\n props: ShapeProps<S>,\n group: ShapeContext<S> | null,\n): ResolvedShape<S> {\n return {\n size: computed(() => group?.size ?? props.size),\n compact: computed(() => group?.compact ?? props.compact),\n disabled: computed(() => group?.disabled || props.disabled),\n }\n}\n"],"mappings":";;AAuCA,SAAgB,EACd,GACA,GACkB;CAClB,OAAO;EACL,MAAM,QAAe,GAAO,QAAQ,EAAM,IAAI;EAC9C,SAAS,QAAe,GAAO,WAAW,EAAM,OAAO;EACvD,UAAU,QAAe,GAAO,YAAY,EAAM,QAAQ;CAC5D;AACF"}
1
+ {"version":3,"file":"useControlShape.js","names":[],"sources":["../../src/composables/useControlShape.ts"],"sourcesContent":["/**\n * Row size and compact override control shape; disabled is cumulative so no child can\n * reactivate a disabled row.\n */\nimport { computed } from 'vue'\nimport type { ComputedRef } from 'vue'\n\n/** The three members every group context shares, whatever else it carries. */\ninterface ShapeContext<S> {\n readonly size?: S\n readonly compact?: boolean\n readonly disabled?: boolean\n}\n\ninterface ShapeProps<S> {\n readonly size: S\n readonly compact: boolean\n readonly disabled: boolean\n}\n\nexport interface ResolvedShape<S> {\n /** The height, the row's if it named one. */\n size: ComputedRef<S>\n /** The reduced density, the row's if it named one. */\n compact: ComputedRef<boolean>\n /** Unusable as soon as EITHER the row or the control says so. */\n disabled: ComputedRef<boolean>\n}\n\nexport function useControlShape<S>(\n props: ShapeProps<S>,\n group: ShapeContext<S> | null,\n): ResolvedShape<S> {\n return {\n size: computed(() => group?.size ?? props.size),\n compact: computed(() => group?.compact ?? props.compact),\n disabled: computed(() => group?.disabled || props.disabled),\n }\n}\n"],"mappings":";;AA6BA,SAAgB,EACd,GACA,GACkB;CAClB,OAAO;EACL,MAAM,QAAe,GAAO,QAAQ,EAAM,IAAI;EAC9C,SAAS,QAAe,GAAO,WAAW,EAAM,OAAO;EACvD,UAAU,QAAe,GAAO,YAAY,EAAM,QAAQ;CAC5D;AACF"}
@@ -1,16 +1,8 @@
1
1
  import { Ref } from 'vue';
2
2
  /**
3
3
  * The open state of a native `<details>`, shared by the design system's two consumers of one:
4
- * VAccordionItem and VSideNavigationItem. Bind `openAttr` to `:open`, `onToggle` to `@toggle`
5
- * and `onSummaryClick` to the `<summary>`'s `@click`.
6
- *
7
- * The element is the source of truth and the `v-model:open` is fed BY it. Left unbound, the
8
- * model follows the element, so Vue never has anything to patch back and the native toggling
9
- * stays sovereign.
10
- *
11
- * `defaultOpen` is read ONCE, here, and never again: it is an initial state, so a prop changed
12
- * later must not close a section the reader has opened. Read inside the computed instead, a
13
- * parent re-rendering with another value would silently fold it.
4
+ * VAccordionItem and VSideNavigationItem. Read inside the computed instead, a parent
5
+ * re-rendering with another value would silently fold it.
14
6
  */
15
7
  export declare function useDetailsOpen(open: Ref<boolean | null>, options: {
16
8
  defaultOpen: () => boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useDetailsOpen.js","names":[],"sources":["../../src/composables/useDetailsOpen.ts"],"sourcesContent":["import { computed, nextTick } from 'vue'\nimport type { Ref } from 'vue'\n\n/**\n * The open state of a native `<details>`, shared by the design system's two consumers of one:\n * VAccordionItem and VSideNavigationItem. Bind `openAttr` to `:open`, `onToggle` to `@toggle`\n * and `onSummaryClick` to the `<summary>`'s `@click`.\n *\n * The element is the source of truth and the `v-model:open` is fed BY it. Left unbound, the\n * model follows the element, so Vue never has anything to patch back and the native toggling\n * stays sovereign.\n *\n * `defaultOpen` is read ONCE, here, and never again: it is an initial state, so a prop changed\n * later must not close a section the reader has opened. Read inside the computed instead, a\n * parent re-rendering with another value would silently fold it.\n */\nexport function useDetailsOpen(\n open: Ref<boolean | null>,\n options: { defaultOpen: () => boolean; disabled: () => boolean },\n) {\n const initial = options.defaultOpen()\n const openAttr = computed(() => (open.value ?? initial) || undefined)\n\n // @core\n // The toggle event does not bubble, which is convenient: listening on the element itself\n // cannot pick up a nested `<details>` opening inside it.\n function onToggle(event: Event) {\n const element = event.target as HTMLDetailsElement\n const value = element.open\n if (open.value === value) return\n open.value = value\n /*\n * TRAP — a CONTROLLED model may refuse the change (`:open=\"false\"` and no handler, or one\n * that says no). Nothing then moves in the props, so Vue has nothing to patch, and the\n * element would stay open under a model that says closed. It is put back once the\n * parent has answered.\n */\n void nextTick(() => {\n if (open.value !== null && element.open !== open.value) element.open = open.value\n })\n }\n\n // @a11y @core\n /*\n * A `<summary>` has no `disabled` attribute, so cancelling the click is the only way to stop\n * a disabled section from folding. The keyboard needs nothing: the component takes the\n * summary out of the tab order. Deliberately not `pointer-events: none`, which would also\n * remove the forbidden cursor telling the reader why nothing happens.\n */\n function onSummaryClick(event: MouseEvent) {\n if (options.disabled()) event.preventDefault()\n }\n\n return { openAttr, onToggle, onSummaryClick }\n}\n"],"mappings":";;AAgBA,SAAgB,EACd,GACA,GACA;CACA,IAAM,IAAU,EAAQ,YAAY,GAC9B,IAAW,SAAgB,EAAK,SAAS,MAAY,KAAA,CAAS;CAKpE,SAAS,EAAS,GAAc;EAC9B,IAAM,IAAU,EAAM,QAChB,IAAQ,EAAQ;EAClB,EAAK,UAAU,MACnB,EAAK,QAAQ,GAOb,QAAoB;GAClB,AAAI,EAAK,UAAU,QAAQ,EAAQ,SAAS,EAAK,UAAO,EAAQ,OAAO,EAAK;EAC9E,CAAC;CACH;CASA,SAAS,EAAe,GAAmB;EACzC,AAAI,EAAQ,SAAS,KAAG,EAAM,eAAe;CAC/C;CAEA,OAAO;EAAE;EAAU;EAAU;CAAe;AAC9C"}
1
+ {"version":3,"file":"useDetailsOpen.js","names":[],"sources":["../../src/composables/useDetailsOpen.ts"],"sourcesContent":["import { computed, nextTick } from 'vue'\nimport type { Ref } from 'vue'\n\n/**\n * The open state of a native `<details>`, shared by the design system's two consumers of one:\n * VAccordionItem and VSideNavigationItem. Read inside the computed instead, a parent\n * re-rendering with another value would silently fold it.\n */\nexport function useDetailsOpen(\n open: Ref<boolean | null>,\n options: { defaultOpen: () => boolean; disabled: () => boolean },\n) {\n const initial = options.defaultOpen()\n const openAttr = computed(() => (open.value ?? initial) || undefined)\n\n // @core\n // The toggle event does not bubble, which is convenient: listening on the element itself\n // cannot pick up a nested `<details>` opening inside it.\n function onToggle(event: Event) {\n const element = event.target as HTMLDetailsElement\n const value = element.open\n if (open.value === value) return\n open.value = value\n /*\n * A controlled model may refuse the change (`:open=\"false\"` and no handler, or one that\n * says no). Nothing then moves in the props, so Vue has nothing to patch, and the element\n * would stay open under a model that says closed.\n */\n void nextTick(() => {\n if (open.value !== null && element.open !== open.value) element.open = open.value\n })\n }\n\n // @a11y @core\n /*\n * A `<summary>` has no `disabled` attribute, so cancelling the click is the only way to stop\n * a disabled section from folding. The keyboard needs nothing: the component takes the\n * summary out of the tab order.\n */\n function onSummaryClick(event: MouseEvent) {\n if (options.disabled()) event.preventDefault()\n }\n\n return { openAttr, onToggle, onSummaryClick }\n}\n"],"mappings":";;AAQA,SAAgB,EACd,GACA,GACA;CACA,IAAM,IAAU,EAAQ,YAAY,GAC9B,IAAW,SAAgB,EAAK,SAAS,MAAY,KAAA,CAAS;CAKpE,SAAS,EAAS,GAAc;EAC9B,IAAM,IAAU,EAAM,QAChB,IAAQ,EAAQ;EAClB,EAAK,UAAU,MACnB,EAAK,QAAQ,GAMb,QAAoB;GAClB,AAAI,EAAK,UAAU,QAAQ,EAAQ,SAAS,EAAK,UAAO,EAAQ,OAAO,EAAK;EAC9E,CAAC;CACH;CAQA,SAAS,EAAe,GAAmB;EACzC,AAAI,EAAQ,SAAS,KAAG,EAAM,eAAe;CAC/C;CAEA,OAAO;EAAE;EAAU;EAAU;CAAe;AAC9C"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFieldIds.js","names":[],"sources":["../../src/composables/useFieldIds.ts"],"sourcesContent":["// @a11y @ssr\n/**\n * The ids tying a wrapped field to its label, its hint and its counter — VInput and VTextarea, and the hint\n * of VCheckbox, VRadio and VSwitch.\n *\n * A consumer `id` WINS over the generated one, or a label they wrote outside the component\n * would point at nothing. `aria-describedby` is a space-separated LIST, so the component's\n * hint is APPENDED to whatever the consumer already pointed at rather than replacing it,\n * and the counter after it: a screen reader then reads \"12/80\" out on focus, where it would\n * otherwise only ever be seen.\n *\n * Ids come from `useId()` rather than a counter of our own, which is what keeps them\n * identical across the server render and hydration.\n */\n\nimport { computed, useId, type ComputedRef } from 'vue'\n\nimport { joinIds } from '../utils/ids'\n\nexport function useFieldIds(\n attrs: Record<string, unknown>,\n hasHint: () => boolean,\n hasCounter: () => boolean = () => false,\n): {\n fieldId: ComputedRef<string>\n hintId: string\n counterId: string\n describedBy: ComputedRef<string | undefined>\n} {\n const uid = useId()\n const hintId = useId()\n const counterId = useId()\n\n return {\n fieldId: computed(() => (attrs.id as string | undefined) ?? uid),\n hintId,\n counterId,\n describedBy: computed(() =>\n joinIds(\n attrs['aria-describedby'] as string | undefined,\n hasHint() && hintId,\n hasCounter() && counterId,\n ),\n ),\n }\n}\n"],"mappings":";;;AAmBA,SAAgB,EACd,GACA,GACA,UAAkC,IAMlC;CACA,IAAM,IAAM,EAAM,GACZ,IAAS,EAAM,GACf,IAAY,EAAM;CAExB,OAAO;EACL,SAAS,QAAgB,EAAM,MAA6B,CAAG;EAC/D;EACA;EACA,aAAa,QACX,EACE,EAAM,qBACN,EAAQ,KAAK,GACb,EAAW,KAAK,CAClB,CACF;CACF;AACF"}
1
+ {"version":3,"file":"useFieldIds.js","names":[],"sources":["../../src/composables/useFieldIds.ts"],"sourcesContent":["// @a11y @ssr\n/**\n * Use hydration-stable IDs and append hint/counter references to consumer aria-describedby\n * rather than replacing it.\n */\n\nimport { computed, useId, type ComputedRef } from 'vue'\n\nimport { joinIds } from '../utils/ids'\n\nexport function useFieldIds(\n attrs: Record<string, unknown>,\n hasHint: () => boolean,\n hasCounter: () => boolean = () => false,\n): {\n fieldId: ComputedRef<string>\n hintId: string\n counterId: string\n describedBy: ComputedRef<string | undefined>\n} {\n const uid = useId()\n const hintId = useId()\n const counterId = useId()\n\n return {\n fieldId: computed(() => (attrs.id as string | undefined) ?? uid),\n hintId,\n counterId,\n describedBy: computed(() =>\n joinIds(\n attrs['aria-describedby'] as string | undefined,\n hasHint() && hintId,\n hasCounter() && counterId,\n ),\n ),\n }\n}\n"],"mappings":";;;AAUA,SAAgB,EACd,GACA,GACA,UAAkC,IAMlC;CACA,IAAM,IAAM,EAAM,GACZ,IAAS,EAAM,GACf,IAAY,EAAM;CAExB,OAAO;EACL,SAAS,QAAgB,EAAM,MAA6B,CAAG;EAC/D;EACA;EACA,aAAa,QACX,EACE,EAAM,qBACN,EAAQ,KAAK,GACb,EAAW,KAAK,CAClB,CACF;CACF;AACF"}
@@ -26,26 +26,13 @@ export interface UseFieldPanelOptions {
26
26
  onOpen?: () => void;
27
27
  /** What it needs to do as the panel closes: clear an announcement, for instance. */
28
28
  onClose?: () => void;
29
- /**
30
- * Whether focusing the field opens the panel. It does not by default.
31
- *
32
- * A field one TYPES into says yes, and its panel then opens WITHOUT taking the caret, so
33
- * typing carries on and the down arrow remains the one explicit way in. A click on such a
34
- * field leaves the focus where it is for the same reason.
35
- */
29
+ /** Whether focusing the field opens the panel. It does not by default. */
36
30
  openOnFocus?: () => boolean;
37
31
  }
38
32
  /**
39
- * The "field + `VPopover` in `mode="manual"`" shell of VDateInput and VTimeInput.
40
- *
41
- * A `manual` popover does nothing on its own: no light dismiss, no focus move, no focus
42
- * return. This is exactly that minimum; what is particular to either component arrives
43
- * through `onOpen`/`onClose`.
44
- *
45
- * TRAP: the panel is WRITTEN imperatively and READ back by model. Bind `open` as
46
- * `v-model:open` so the DOM feeds it, but the write must stay synchronous: the `rAF` that
47
- * moves focus assumes the panel is already open when it is armed, and the model would put
48
- * a tick in between.
33
+ * The panel is written imperatively and read back by model. Bind `open` as `v-model:open` so
34
+ * the DOM feeds it, but the write must stay synchronous: the `rAF` that moves focus assumes the
35
+ * panel is already open when it is armed, and the model would put a tick in between.
49
36
  */
50
37
  export declare function useFieldPanel(options: UseFieldPanelOptions): {
51
38
  open: Ref<boolean, boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"useFieldPanel.js","names":[],"sources":["../../src/composables/useFieldPanel.ts"],"sourcesContent":["import { onBeforeUnmount, provide, ref, useId, watch } from 'vue'\nimport type { Ref } from 'vue'\n\nimport { NO_BUTTON_GROUP, buttonGroupKey } from '../components/VButton/context'\nimport { useFocusoutDismiss } from './useFocusoutDismiss'\n\n/** The least this needs of the field: something it can put the focus back on. */\ninterface FocusableField {\n focus: () => void\n}\n\n/** The least this needs of the panel: something it can open and close. */\ninterface PanelControl {\n show: () => void\n close: () => void\n}\n\nexport interface UseFieldPanelOptions {\n /** The component's root, against which \"the focus has left\" is judged. */\n rootEl: Ref<HTMLElement | null>\n /** The panel to open and close. */\n panelRef: Ref<PanelControl | null>\n /** The field the focus returns to. */\n field: Ref<FocusableField | null>\n /**\n * Whether opening is refused. It is the SINGLE cut-off point: every route in passes\n * through it, so a component never has to repeat the condition per handler.\n */\n disabled: () => boolean\n /** Moves the focus INTO the open panel: where in it is the component's business. */\n focusInPanel: () => void\n /** What the component needs to do as the panel opens: prepare a draft, reset a step. */\n onOpen?: () => void\n /** What it needs to do as the panel closes: clear an announcement, for instance. */\n onClose?: () => void\n /**\n * Whether focusing the field opens the panel. It does not by default.\n *\n * A field one TYPES into says yes, and its panel then opens WITHOUT taking the caret, so\n * typing carries on and the down arrow remains the one explicit way in. A click on such a\n * field leaves the focus where it is for the same reason.\n */\n openOnFocus?: () => boolean\n}\n\n// @a11y @keyboard @core\n/**\n * The \"field + `VPopover` in `mode=\"manual\"`\" shell of VDateInput and VTimeInput.\n *\n * A `manual` popover does nothing on its own: no light dismiss, no focus move, no focus\n * return. This is exactly that minimum; what is particular to either component arrives\n * through `onOpen`/`onClose`.\n *\n * TRAP: the panel is WRITTEN imperatively and READ back by model. Bind `open` as\n * `v-model:open` so the DOM feeds it, but the write must stay synchronous: the `rAF` that\n * moves focus assumes the panel is already open when it is armed, and the model would put\n * a tick in between.\n */\nexport function useFieldPanel(options: UseFieldPanelOptions) {\n const open = ref(false)\n const openOnFocus = () => options.openOnFocus?.() ?? false\n\n /** The id the panel carries, for the field's `aria-controls`. */\n const panelId = useId()\n\n // @core\n // TRAP — this stops a VButtonGroup's or a VInputGroup's row context at the field's\n // boundary. The picker in the panel sizes its own buttons — VDatePicker writes `size=\"sm\"`\n // on its navigation, VTimePicker `size=\"lg\"` on its hour and minute cells — and a group\n // WINS over a button's prop: without this line a VInputGroup would resize them to its own\n // height, which only shows once the panel is open and nothing in the sheet or the template\n // would say why. It is the JS counterpart of the `.v-overlay` guard every VButtonGroup\n // selector carries — a floating panel is not a segment of the row that opened it.\n provide(buttonGroupKey, NO_BUTTON_GROUP)\n\n /*\n * The frame the focus is moved in, kept so it can be called off. A panel closed inside that\n * frame (Escape at once, the field disabled, a consumer closing it) would otherwise have the\n * focus pulled into it once shut, taken from the field it had just been handed back to.\n */\n let focusFrame = 0\n\n function cancelFocusFrame() {\n if (focusFrame) cancelAnimationFrame(focusFrame)\n focusFrame = 0\n }\n\n function openPanel(moveFocus = !openOnFocus()) {\n if (options.disabled() || open.value) return\n options.onOpen?.()\n options.panelRef.value?.show()\n // @a11y\n // A `manual` popover moves focus nowhere, so it is moved by hand — a frame later,\n // the panel not being painted yet, and nothing invisible can take focus.\n if (moveFocus) {\n cancelFocusFrame()\n focusFrame = requestAnimationFrame(() => {\n focusFrame = 0\n options.focusInPanel()\n })\n }\n }\n\n /** Closes the panel and leaves the focus where it is: the focus has already gone elsewhere. */\n function closePanel() {\n cancelFocusFrame()\n if (!open.value) return\n options.panelRef.value?.close()\n options.onClose?.()\n }\n\n onBeforeUnmount(cancelFocusFrame)\n\n // @core\n // TRAP — a panel refused WHILE it is open (the field turned read-only or disabled, the\n // picker switched off) is unmounted by its `v-if`, and an unmounted popover sends no\n // `toggle` to bring `open` back down. The model then stays true, and the next time the\n // panel is mounted VPopover's mount replay shows it again with nobody having asked.\n watch(options.disabled, (refused) => {\n if (!refused || !open.value) return\n closePanel()\n open.value = false\n })\n\n // @a11y\n /*\n * TRAP — handing the focus back to the field is what makes a field that opens ON FOCUS\n * reopen the panel that was just closed: the two would chase each other and the panel\n * would never close. The lock covers the focus call, which is synchronous.\n *\n * EVERY focus this shell or a component gives the field must go through `focusField`,\n * `closeAndFocus` included. Focusing the field directly brings the loop straight back.\n */\n let refocusing = false\n\n /**\n * Puts the focus on the field without opening the panel.\n *\n * A component emptying its value calls it BEFORE the field focuses itself, and that order\n * is the point: focusing an element that already has the focus emits no event at all, so\n * the field's own call that follows is inert and the panel stays shut.\n */\n function focusField() {\n refocusing = true\n options.field.value?.focus()\n refocusing = false\n }\n\n /** Closes the panel and hands the focus back to the field, under the lock above. */\n function closeAndFocus() {\n if (!open.value) return\n closePanel()\n focusField()\n }\n\n /** The field's focus handler: opens the panel on a field that asks for it. */\n function onFieldFocus() {\n if (refocusing || !openOnFocus()) return\n openPanel(false)\n }\n\n /**\n * The icon at the end of the field. Clicking it is an explicit request for the panel, and\n * the focus has already left the field for the button — so carrying it into the panel is\n * right whatever `openOnFocus` says.\n */\n function toggleFromIcon() {\n if (open.value) closeAndFocus()\n else openPanel(true)\n }\n\n /**\n * Whether an event started on one of the controls the field holds: the clear cross, the\n * AM/PM word, a clickable icon, or a button of the consumer's own in a slot. What happens\n * there is that control's business, for the pointer and the keyboard alike.\n */\n const fromOwnControl = (event: Event) =>\n !!(event.target as HTMLElement | null)?.closest?.('button, a[href]')\n\n function onControlClick(event: MouseEvent) {\n // Reacting here too would open the panel the clear cross has just given a reason to close.\n if (fromOwnControl(event)) return\n openPanel()\n }\n\n // @a11y\n const onFocusout = useFocusoutDismiss(options.rootEl, closePanel)\n\n // @a11y\n /*\n * TRAP — clicking a non-focusable pixel of the panel (padding, the gutter between cells)\n * hands focus back to `<body>`. `onFocusout` above then fires with a null `relatedTarget`,\n * reads it as an exit — rightly — and closes a panel the reader has just clicked.\n *\n * The filter is essential: VCombobox's unconditional `preventDefault` is safe there\n * because focus never leaves its field, but here it would rob the days and the navigation\n * arrows of focus and desynchronize VDatePicker's roving.\n *\n * Invisible in jsdom, where clicking moves no focus — the `ClicDansLeVide` play functions\n * of both components cover it.\n */\n function onPanelMousedown(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (!target?.closest('button, a, input, select, textarea, [tabindex]')) event.preventDefault()\n }\n\n // @keyboard @a11y\n function onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n if (open.value) {\n event.preventDefault()\n closeAndFocus()\n }\n return\n }\n // TRAP — a key already consumed INSIDE the panel is ignored. An Enter that selected a\n // day has just CLOSED the panel, and without the guard would reopen it as it bubbles.\n // TRAP — the same goes for a key typed on one of the field's own buttons. Its Enter is\n // what becomes the click that clears the field or flips the half of the day: cancelled\n // here to open the panel, the button would never be pressed from the keyboard at all.\n if (\n (event.key === 'ArrowDown' || event.key === 'Enter') &&\n !open.value &&\n !event.defaultPrevented &&\n !fromOwnControl(event)\n ) {\n event.preventDefault()\n // Opened from the KEYBOARD the panel always takes focus, whatever `openOnFocus` says:\n // otherwise ArrowDown opens a panel the keyboard cannot reach.\n openPanel(true)\n }\n }\n\n return {\n open,\n panelId,\n openPanel,\n closePanel,\n closeAndFocus,\n focusField,\n onFieldFocus,\n toggleFromIcon,\n onControlClick,\n onFocusout,\n onKeydown,\n onPanelMousedown,\n }\n}\n"],"mappings":";;;;AA0DA,SAAgB,EAAc,GAA+B;CAC3D,IAAM,IAAO,EAAI,EAAK,GAChB,UAAoB,EAAQ,cAAc,KAAK,IAG/C,IAAU,EAAM;CAUtB,EAAQ,GAAgB,CAAe;CAOvC,IAAI,IAAa;CAEjB,SAAS,IAAmB;EAE1B,AADI,KAAY,qBAAqB,CAAU,GAC/C,IAAa;CACf;CAEA,SAAS,EAAU,IAAY,CAAC,EAAY,GAAG;EACzC,EAAQ,SAAS,KAAK,EAAK,UAC/B,EAAQ,SAAS,GACjB,EAAQ,SAAS,OAAO,KAAK,GAIzB,MACF,EAAiB,GACjB,IAAa,4BAA4B;GAEvC,AADA,IAAa,GACb,EAAQ,aAAa;EACvB,CAAC;CAEL;CAGA,SAAS,IAAa;EACpB,EAAiB,GACZ,EAAK,UACV,EAAQ,SAAS,OAAO,MAAM,GAC9B,EAAQ,UAAU;CACpB;CASA,AAPA,EAAgB,CAAgB,GAOhC,EAAM,EAAQ,WAAW,MAAY;EAC/B,CAAC,KAAW,CAAC,EAAK,UACtB,EAAW,GACX,EAAK,QAAQ;CACf,CAAC;CAWD,IAAI,IAAa;CASjB,SAAS,IAAa;EAGpB,AAFA,IAAa,IACb,EAAQ,MAAM,OAAO,MAAM,GAC3B,IAAa;CACf;CAGA,SAAS,IAAgB;EAClB,EAAK,UACV,EAAW,GACX,EAAW;CACb;CAGA,SAAS,IAAe;EAClB,KAAc,CAAC,EAAY,KAC/B,EAAU,EAAK;CACjB;CAOA,SAAS,IAAiB;EACxB,AAAI,EAAK,QAAO,EAAc,IACzB,EAAU,EAAI;CACrB;CAOA,IAAM,KAAkB,MACtB,CAAC,CAAE,EAAM,QAA+B,UAAU,iBAAiB;CAErE,SAAS,EAAe,GAAmB;EAErC,EAAe,CAAK,KACxB,EAAU;CACZ;CAGA,IAAM,IAAa,EAAmB,EAAQ,QAAQ,CAAU;CAehE,SAAS,EAAiB,GAAmB;EAE3C,AADe,EAAM,QACR,QAAQ,gDAAgD,KAAG,EAAM,eAAe;CAC/F;CAGA,SAAS,EAAU,GAAsB;EACvC,IAAI,EAAM,QAAQ,UAAU;GAC1B,AAAI,EAAK,UACP,EAAM,eAAe,GACrB,EAAc;GAEhB;EACF;EAMA,CACG,EAAM,QAAQ,eAAe,EAAM,QAAQ,YAC5C,CAAC,EAAK,SACN,CAAC,EAAM,oBACP,CAAC,EAAe,CAAK,MAErB,EAAM,eAAe,GAGrB,EAAU,EAAI;CAElB;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"useFieldPanel.js","names":[],"sources":["../../src/composables/useFieldPanel.ts"],"sourcesContent":["import { onBeforeUnmount, provide, ref, useId, watch } from 'vue'\nimport type { Ref } from 'vue'\n\nimport { NO_BUTTON_GROUP, buttonGroupKey } from '../components/VButton/context'\nimport { useFocusoutDismiss } from './useFocusoutDismiss'\n\n/** The least this needs of the field: something it can put the focus back on. */\ninterface FocusableField {\n focus: () => void\n}\n\n/** The least this needs of the panel: something it can open and close. */\ninterface PanelControl {\n show: () => void\n close: () => void\n}\n\nexport interface UseFieldPanelOptions {\n /** The component's root, against which \"the focus has left\" is judged. */\n rootEl: Ref<HTMLElement | null>\n /** The panel to open and close. */\n panelRef: Ref<PanelControl | null>\n /** The field the focus returns to. */\n field: Ref<FocusableField | null>\n /**\n * Whether opening is refused. It is the SINGLE cut-off point: every route in passes\n * through it, so a component never has to repeat the condition per handler.\n */\n disabled: () => boolean\n /** Moves the focus INTO the open panel: where in it is the component's business. */\n focusInPanel: () => void\n /** What the component needs to do as the panel opens: prepare a draft, reset a step. */\n onOpen?: () => void\n /** What it needs to do as the panel closes: clear an announcement, for instance. */\n onClose?: () => void\n /** Whether focusing the field opens the panel. It does not by default. */\n openOnFocus?: () => boolean\n}\n\n// @a11y @keyboard @core\n/**\n * The panel is written imperatively and read back by model. Bind `open` as `v-model:open` so\n * the DOM feeds it, but the write must stay synchronous: the `rAF` that moves focus assumes the\n * panel is already open when it is armed, and the model would put a tick in between.\n */\nexport function useFieldPanel(options: UseFieldPanelOptions) {\n const open = ref(false)\n const openOnFocus = () => options.openOnFocus?.() ?? false\n\n /** The id the panel carries, for the field's `aria-controls`. */\n const panelId = useId()\n\n // @core\n // Stop row contexts at the field boundary so the picker keeps its own button sizes instead of\n // inheriting the enclosing input group's shape.\n provide(buttonGroupKey, NO_BUTTON_GROUP)\n\n /*\n * The frame the focus is moved in, kept so it can be called off. A panel closed inside that\n * frame (Escape at once, the field disabled, a consumer closing it) would otherwise have the\n * focus pulled into it once shut, taken from the field it had just been handed back to.\n */\n let focusFrame = 0\n\n function cancelFocusFrame() {\n if (focusFrame) cancelAnimationFrame(focusFrame)\n focusFrame = 0\n }\n\n function openPanel(moveFocus = !openOnFocus()) {\n if (options.disabled() || open.value) return\n options.onOpen?.()\n options.panelRef.value?.show()\n // @a11y\n // A `manual` popover moves focus nowhere, so it is moved by hand; a frame later, the panel\n // not being painted yet, and nothing invisible can take focus.\n if (moveFocus) {\n cancelFocusFrame()\n focusFrame = requestAnimationFrame(() => {\n focusFrame = 0\n options.focusInPanel()\n })\n }\n }\n\n /** Closes the panel and leaves the focus where it is: the focus has already gone elsewhere. */\n function closePanel() {\n cancelFocusFrame()\n if (!open.value) return\n options.panelRef.value?.close()\n options.onClose?.()\n }\n\n onBeforeUnmount(cancelFocusFrame)\n\n // @core\n // A panel refused WHILE it is open (the field turned read-only or disabled, the picker\n // switched off) is unmounted by its `v-if`, and an unmounted popover sends no `toggle` to\n // bring `open` back down. The model then stays true, and the next time the panel is mounted\n // VPopover's mount replay shows it again with nobody having asked.\n watch(options.disabled, (refused) => {\n if (!refused || !open.value) return\n closePanel()\n open.value = false\n })\n\n // @a11y\n /*\n * Handing the focus back to the field is what makes a field that opens on FOCUS reopen the\n * panel that was just closed: the two would chase each other and the panel would never close.\n * The lock covers the focus call, which is synchronous.\n */\n let refocusing = false\n\n /** Puts the focus on the field without opening the panel. */\n function focusField() {\n refocusing = true\n options.field.value?.focus()\n refocusing = false\n }\n\n /** Closes the panel and hands the focus back to the field, under the lock above. */\n function closeAndFocus() {\n if (!open.value) return\n closePanel()\n focusField()\n }\n\n /** The field's focus handler: opens the panel on a field that asks for it. */\n function onFieldFocus() {\n if (refocusing || !openOnFocus()) return\n openPanel(false)\n }\n\n /** The icon at the end of the field. */\n function toggleFromIcon() {\n if (open.value) closeAndFocus()\n else openPanel(true)\n }\n\n /**\n * Whether an event started on one of the controls the field holds: the clear cross, the AM/PM\n * word, a clickable icon, or a button of the consumer's own in a slot.\n */\n const fromOwnControl = (event: Event) =>\n !!(event.target as HTMLElement | null)?.closest?.('button, a[href]')\n\n function onControlClick(event: MouseEvent) {\n if (fromOwnControl(event)) return\n openPanel()\n }\n\n // @a11y\n const onFocusout = useFocusoutDismiss(options.rootEl, closePanel)\n\n // @a11y\n /*\n * Clicking a non-focusable pixel of the panel (padding, the gutter between cells) hands focus\n * back to `<body>`. `onFocusout` above then fires with a null `relatedTarget`, reads it as an\n * exit; rightly; and closes a panel the reader has just clicked.\n */\n function onPanelMousedown(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (!target?.closest('button, a, input, select, textarea, [tabindex]')) event.preventDefault()\n }\n\n // @keyboard @a11y\n function onKeydown(event: KeyboardEvent) {\n if (event.key === 'Escape') {\n if (open.value) {\n event.preventDefault()\n closeAndFocus()\n }\n return\n }\n // A key already consumed INSIDE the panel is ignored. An Enter that selected a day has just\n // CLOSED the panel, and without the guard would reopen it as it bubbles.\n if (\n (event.key === 'ArrowDown' || event.key === 'Enter') &&\n !open.value &&\n !event.defaultPrevented &&\n !fromOwnControl(event)\n ) {\n event.preventDefault()\n // Opened from the KEYBOARD the panel always takes focus, whatever `openOnFocus` says:\n // otherwise ArrowDown opens a panel the keyboard cannot reach.\n openPanel(true)\n }\n }\n\n return {\n open,\n panelId,\n openPanel,\n closePanel,\n closeAndFocus,\n focusField,\n onFieldFocus,\n toggleFromIcon,\n onControlClick,\n onFocusout,\n onKeydown,\n onPanelMousedown,\n }\n}\n"],"mappings":";;;;AA6CA,SAAgB,EAAc,GAA+B;CAC3D,IAAM,IAAO,EAAI,EAAK,GAChB,UAAoB,EAAQ,cAAc,KAAK,IAG/C,IAAU,EAAM;CAKtB,EAAQ,GAAgB,CAAe;CAOvC,IAAI,IAAa;CAEjB,SAAS,IAAmB;EAE1B,AADI,KAAY,qBAAqB,CAAU,GAC/C,IAAa;CACf;CAEA,SAAS,EAAU,IAAY,CAAC,EAAY,GAAG;EACzC,EAAQ,SAAS,KAAK,EAAK,UAC/B,EAAQ,SAAS,GACjB,EAAQ,SAAS,OAAO,KAAK,GAIzB,MACF,EAAiB,GACjB,IAAa,4BAA4B;GAEvC,AADA,IAAa,GACb,EAAQ,aAAa;EACvB,CAAC;CAEL;CAGA,SAAS,IAAa;EACpB,EAAiB,GACZ,EAAK,UACV,EAAQ,SAAS,OAAO,MAAM,GAC9B,EAAQ,UAAU;CACpB;CASA,AAPA,EAAgB,CAAgB,GAOhC,EAAM,EAAQ,WAAW,MAAY;EAC/B,CAAC,KAAW,CAAC,EAAK,UACtB,EAAW,GACX,EAAK,QAAQ;CACf,CAAC;CAQD,IAAI,IAAa;CAGjB,SAAS,IAAa;EAGpB,AAFA,IAAa,IACb,EAAQ,MAAM,OAAO,MAAM,GAC3B,IAAa;CACf;CAGA,SAAS,IAAgB;EAClB,EAAK,UACV,EAAW,GACX,EAAW;CACb;CAGA,SAAS,IAAe;EAClB,KAAc,CAAC,EAAY,KAC/B,EAAU,EAAK;CACjB;CAGA,SAAS,IAAiB;EACxB,AAAI,EAAK,QAAO,EAAc,IACzB,EAAU,EAAI;CACrB;CAMA,IAAM,KAAkB,MACtB,CAAC,CAAE,EAAM,QAA+B,UAAU,iBAAiB;CAErE,SAAS,EAAe,GAAmB;EACrC,EAAe,CAAK,KACxB,EAAU;CACZ;CAGA,IAAM,IAAa,EAAmB,EAAQ,QAAQ,CAAU;CAQhE,SAAS,EAAiB,GAAmB;EAE3C,AADe,EAAM,QACR,QAAQ,gDAAgD,KAAG,EAAM,eAAe;CAC/F;CAGA,SAAS,EAAU,GAAsB;EACvC,IAAI,EAAM,QAAQ,UAAU;GAC1B,AAAI,EAAK,UACP,EAAM,eAAe,GACrB,EAAc;GAEhB;EACF;EAGA,CACG,EAAM,QAAQ,eAAe,EAAM,QAAQ,YAC5C,CAAC,EAAK,SACN,CAAC,EAAM,oBACP,CAAC,EAAe,CAAK,MAErB,EAAM,eAAe,GAGrB,EAAU,EAAI;CAElB;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFileDrop.js","names":[],"sources":["../../src/composables/useFileDrop.ts"],"sourcesContent":["// @core\n/**\n * The drop-target plumbing of VFileInput and VFilePicker. It exists once rather than twice\n * because of the two traps it carries.\n *\n * `dragover` MUST `preventDefault`, or the browser refuses the drop and then NAVIGATES to\n * the file, replacing the page.\n *\n * And the state is a depth COUNTER, never a boolean: `dragleave` fires every time the\n * pointer crosses into a child, so a flag would flicker off under the cursor. `drop` forces\n * the depth back to 0 — a drag leaving the window never sends the matching `dragleave`, and\n * `currentTarget.contains(relatedTarget)`, the other classic fix, is useless there since\n * `relatedTarget` is null in exactly that case.\n *\n * `enabled` is a getter, read at event time, so a component disabled mid-drag stops\n * accepting with nothing re-bound.\n */\n\nimport { computed, ref, type ComputedRef } from 'vue'\n\nexport function useFileDrop(\n enabled: () => boolean,\n onFiles: (files: File[]) => void,\n): {\n dragging: ComputedRef<boolean>\n onDragEnter: (event: DragEvent) => void\n onDragOver: (event: DragEvent) => void\n onDragLeave: () => void\n onDrop: (event: DragEvent) => void\n} {\n const depth = ref(0)\n\n function onDragEnter(event: DragEvent) {\n if (!enabled()) return\n event.preventDefault()\n depth.value += 1\n }\n\n function onDragOver(event: DragEvent) {\n if (!enabled()) return\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy'\n }\n\n // TRAP — the depth comes back down whether or not files are accepted: a component\n // disabled mid-drag that ignored the leave would keep counting one enter too many, and\n // show the highlight again, for good, the day it was switched back on.\n function onDragLeave() {\n depth.value = Math.max(0, depth.value - 1)\n }\n\n function onDrop(event: DragEvent) {\n depth.value = 0\n if (!enabled()) return\n event.preventDefault()\n onFiles([...(event.dataTransfer?.files ?? [])])\n }\n\n return {\n // Read against `enabled` too, so switching the component off drops the highlight at once.\n dragging: computed(() => depth.value > 0 && enabled()),\n onDragEnter,\n onDragOver,\n onDragLeave,\n onDrop,\n }\n}\n"],"mappings":";;AAoBA,SAAgB,EACd,GACA,GAOA;CACA,IAAM,IAAQ,EAAI,CAAC;CAEnB,SAAS,EAAY,GAAkB;EAChC,EAAQ,MACb,EAAM,eAAe,GACrB,EAAM,SAAS;CACjB;CAEA,SAAS,EAAW,GAAkB;EAC/B,EAAQ,MACb,EAAM,eAAe,GACjB,EAAM,iBAAc,EAAM,aAAa,aAAa;CAC1D;CAKA,SAAS,IAAc;EACrB,EAAM,QAAQ,KAAK,IAAI,GAAG,EAAM,QAAQ,CAAC;CAC3C;CAEA,SAAS,EAAO,GAAkB;EAChC,EAAM,QAAQ,GACT,EAAQ,MACb,EAAM,eAAe,GACrB,EAAQ,CAAC,GAAI,EAAM,cAAc,SAAS,CAAC,CAAE,CAAC;CAChD;CAEA,OAAO;EAEL,UAAU,QAAe,EAAM,QAAQ,KAAK,EAAQ,CAAC;EACrD;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"useFileDrop.js","names":[],"sources":["../../src/composables/useFileDrop.ts"],"sourcesContent":["// @core\n/**\n * Cancel dragover to permit drops and avoid browser navigation. Track nested drag depth so\n * entering a child does not clear the active drop state.\n */\n\nimport { computed, ref, type ComputedRef } from 'vue'\n\nexport function useFileDrop(\n enabled: () => boolean,\n onFiles: (files: File[]) => void,\n): {\n dragging: ComputedRef<boolean>\n onDragEnter: (event: DragEvent) => void\n onDragOver: (event: DragEvent) => void\n onDragLeave: () => void\n onDrop: (event: DragEvent) => void\n} {\n const depth = ref(0)\n\n function onDragEnter(event: DragEvent) {\n if (!enabled()) return\n event.preventDefault()\n depth.value += 1\n }\n\n function onDragOver(event: DragEvent) {\n if (!enabled()) return\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy'\n }\n\n // The depth comes back down whether or not files are accepted: a component disabled mid-drag\n // that ignored the leave would keep counting one enter too many, and show the highlight\n // again, for good, the day it was switched back on.\n function onDragLeave() {\n depth.value = Math.max(0, depth.value - 1)\n }\n\n function onDrop(event: DragEvent) {\n depth.value = 0\n if (!enabled()) return\n event.preventDefault()\n onFiles([...(event.dataTransfer?.files ?? [])])\n }\n\n return {\n dragging: computed(() => depth.value > 0 && enabled()),\n onDragEnter,\n onDragOver,\n onDragLeave,\n onDrop,\n }\n}\n"],"mappings":";;AAQA,SAAgB,EACd,GACA,GAOA;CACA,IAAM,IAAQ,EAAI,CAAC;CAEnB,SAAS,EAAY,GAAkB;EAChC,EAAQ,MACb,EAAM,eAAe,GACrB,EAAM,SAAS;CACjB;CAEA,SAAS,EAAW,GAAkB;EAC/B,EAAQ,MACb,EAAM,eAAe,GACjB,EAAM,iBAAc,EAAM,aAAa,aAAa;CAC1D;CAKA,SAAS,IAAc;EACrB,EAAM,QAAQ,KAAK,IAAI,GAAG,EAAM,QAAQ,CAAC;CAC3C;CAEA,SAAS,EAAO,GAAkB;EAChC,EAAM,QAAQ,GACT,EAAQ,MACb,EAAM,eAAe,GACrB,EAAQ,CAAC,GAAI,EAAM,cAAc,SAAS,CAAC,CAAE,CAAC;CAChD;CAEA,OAAO;EACL,UAAU,QAAe,EAAM,QAAQ,KAAK,EAAQ,CAAC;EACrD;EACA;EACA;EACA;CACF;AACF"}
@@ -28,11 +28,7 @@ export interface FileFieldOptions {
28
28
  * `disabled` prop answers alone.
29
29
  */
30
30
  disabled?: Ref<boolean>;
31
- /**
32
- * Attributes to withhold from the visible control too. VFileInput withholds
33
- * `aria-describedby`, which it re-aggregates itself: a binding written after the spread
34
- * would replace the consumer's rather than merge with it.
35
- */
31
+ /** Attributes to withhold from the visible control too. */
36
32
  excludeFromControl?: readonly string[];
37
33
  /** Called once the selection has been emptied by `clear`, before `change` is emitted. */
38
34
  onClear?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useFileField.js","names":[],"sources":["../../src/composables/useFileField.ts"],"sourcesContent":["// @core\n/**\n * The hidden `<input type=\"file\">` gate of VFileInput and VFilePicker.\n *\n * The JS here is imposed by the platform, not chosen: a dialog opens ONLY from a real click\n * on a file input, and a `FileList` cannot be written from a template. So what the reader\n * clicks can never BE the file input, and the file input can only ever be a SOURCE of files.\n *\n * Hence `.click()` and not `showPicker()` — both need a transient user activation, but\n * `showPicker()` THROWS without one where `.click()` is merely inert — and hence\n * `syncNative()` on every path, which writes the selection back into the input so the form\n * submits it, and so re-picking a file that left the selection is a change again.\n *\n * Everything the two components did identically lives here, so the two cannot drift: the\n * attributes of the hidden input, the screening of what comes in, the removal of one file and\n * the emptying of all of them, and the development warnings both of them owe the integrator.\n */\nimport {\n computed,\n getCurrentInstance,\n ref,\n watch,\n watchEffect,\n type ComputedRef,\n type Ref,\n} from 'vue'\n\nimport { isDev } from '../utils/env'\nimport { screenFiles, type FileRejection } from '../utils/file'\n\n// @a11y\n/**\n * The attributes belonging on the hidden file input rather than on the visible control.\n *\n * The wrapper-root pattern assumes ONE functional element and there are two here, so the\n * attrs split in three: `class`/`style` on the root, these four on the input because they\n * are what the FORM reads, everything else on the element the READER focuses. The buckets\n * must not overlap — a duplicated `id` breaks a consumer's `<label for>`, a duplicated\n * `name` gets the field announced twice.\n */\nconst NATIVE_ONLY = ['name', 'required', 'form', 'capture']\n\n/** The props both components declare under the same names, read at the moment they matter. */\nexport interface FileFieldProps {\n multiple: boolean\n accept?: string\n maxSize?: number\n maxFiles?: number\n maxTotalSize?: number\n disabled: boolean\n readonly: boolean\n}\n\n/** The events both components declare under the same names and with the same payloads. */\nexport interface FileFieldEmit {\n (event: 'change', files: File[]): void\n (event: 'reject', rejection: FileRejection): void\n (event: 'remove', file: File, index: number): void\n}\n\nexport interface FileFieldOptions {\n /** The component's value: always a list of files, whether one or several are allowed. */\n model: Ref<File[]>\n props: FileFieldProps\n emit: FileFieldEmit\n /** The attributes the consumer wrote, minus styling, still to be split in two. */\n forwardedAttrs: ComputedRef<Record<string, unknown>>\n /**\n * Whether the component is disabled, the row it sits in taken into account. Left out, the\n * `disabled` prop answers alone.\n */\n disabled?: Ref<boolean>\n /**\n * Attributes to withhold from the visible control too. VFileInput withholds\n * `aria-describedby`, which it re-aggregates itself: a binding written after the spread\n * would replace the consumer's rather than merge with it.\n */\n excludeFromControl?: readonly string[]\n /** Called once the selection has been emptied by `clear`, before `change` is emitted. */\n onClear?: () => void\n /**\n * The component's own development warnings, one message per condition that holds. They\n * follow the same rule as the shared ones: each is given once per instance.\n */\n warnings?: () => (string | false)[]\n}\n\nexport function useFileField(options: FileFieldOptions) {\n const { model, props, emit } = options\n\n const fileEl = ref<HTMLInputElement | null>(null)\n\n const disabled = () => options.disabled?.value ?? props.disabled\n\n /** Whether files may come in or go out — no dialog, no drop and no removal otherwise. */\n const enabled = computed(() => !disabled() && !props.readonly)\n\n const controlAttrs = computed(() => {\n const excluded = options.excludeFromControl ?? []\n return Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(\n ([key]) => !NATIVE_ONLY.includes(key) && !excluded.includes(key),\n ),\n )\n })\n\n // @core\n /**\n * Writes the selection back into the hidden input, which is what the FORM reads: `name`,\n * `form` and `required` land there, and an input left holding the last batch the dialog\n * produced (or emptied) would submit that rather than the selection, or nothing at all.\n * A `FileList` cannot be built, but a `DataTransfer` hands one out.\n *\n * TRAP — it also keeps every file that LEFT the selection pickable again. The input holds\n * exactly the selection, so choosing a file that was cleared, removed or refused differs\n * from what it holds and fires `change`; left holding it, the dialog would answer the\n * same file with no event at all.\n */\n function syncNative() {\n const el = fileEl.value\n if (!el) return\n // @fallback\n // An environment without `DataTransfer` (jsdom) can only empty the input,\n // which keeps a file pickable again and leaves the form without it.\n if (typeof DataTransfer === 'undefined') {\n el.value = ''\n return\n }\n const transfer = new DataTransfer()\n for (const file of model.value) transfer.items.add(file)\n el.files = transfer.files\n }\n\n // A value set from outside, a consumer emptying the list after an upload, reaches the\n // input the same way.\n watch(model, syncNative, { flush: 'post' })\n\n /**\n * The single entry into the model: the dialog and a drop both arrive here.\n *\n * A refused file never enters the model at all, and every refusal is reported so the\n * component can say why. The screening is a pure function in `utils/file`, which is what\n * makes the ORDER of the reasons testable without a mount.\n */\n function acceptFiles(incoming: File[]) {\n const current = props.multiple ? model.value : []\n const { accepted, rejected } = screenFiles(incoming, current, {\n accept: props.accept,\n maxSize: props.maxSize,\n // A single-file field is a list capped at one, so extra files are refused for the\n // same reason as anywhere else: too many.\n maxFiles: props.multiple ? props.maxFiles : 1,\n maxTotalSize: props.maxTotalSize,\n })\n\n for (const rejection of rejected) emit('reject', rejection)\n\n // The dialog has just written ITS batch into the input, whatever happens to it here.\n syncNative()\n if (accepted.length === 0) return\n\n // A single file replaced by another has left the selection as surely as one taken out\n // by its cross, and `remove` is how an upload already under way for it is cancelled.\n const replaced = props.multiple ? undefined : model.value[0]\n model.value = [...current, ...accepted]\n if (replaced && replaced !== model.value[0]) emit('remove', replaced, 0)\n emit('change', model.value)\n }\n\n function onNativeChange(event: Event) {\n acceptFiles([...((event.target as HTMLInputElement).files ?? [])])\n }\n\n /**\n * Everything the hidden input is bound with, the consumer's form attributes included. It\n * is taken out of the tab order and hidden from screen readers, which leaves the visible\n * control as the single stop and the single announcement; it stays disabled only when the\n * component is, since a read-only one still submits what it holds (`syncNative`).\n */\n const nativeInputAttrs = computed(() => ({\n ...Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(([key]) => NATIVE_ONLY.includes(key)),\n ),\n type: 'file',\n tabindex: -1,\n 'aria-hidden': 'true' as const,\n accept: props.accept,\n multiple: props.multiple || undefined,\n disabled: disabled() || undefined,\n onChange: onNativeChange,\n }))\n\n // @fallback\n /**\n * `.click()` and not `showPicker()`. Both need a transient user activation, but\n * `showPicker()` THROWS without one — and inside a cross-origin iframe — where a click is\n * simply inert. On a file input there is nothing to gain in exchange.\n */\n function openPicker() {\n if (!enabled.value) return\n fileEl.value?.click()\n }\n\n /**\n * Takes ONE file out of the selection, and says which: `remove` with the file and the\n * position it held, then `change` with what is left. It returns the file removed, or\n * `undefined` when nothing was — so the component knows whether it has a focus to catch.\n */\n function removeAt(index: number): File | undefined {\n const file = model.value[index]\n if (!file || !enabled.value) return undefined\n\n model.value = model.value.filter((_, i) => i !== index)\n syncNative()\n emit('remove', file, index)\n emit('change', model.value)\n return file\n }\n\n /**\n * Empties the selection, and the hidden input with it. Refused where `removeAt` is, and\n * silent when there is nothing to empty: no `change` for a selection that did not change.\n */\n function clear() {\n if (!enabled.value || model.value.length === 0) return\n model.value = []\n syncNative()\n options.onClear?.()\n emit('change', model.value)\n }\n\n // @devwarn\n /*\n * Both shared guards describe something that fails SILENTLY at runtime. Each message is given\n * once per instance: they sit in an effect, which re-runs on every change of what it reads,\n * and the same sentence repeated at every keystroke is how a warning stops being read.\n */\n if (isDev) {\n // The prefix is the component's own name, read off the instance rather than passed in:\n // this whole block is dropped from a production build, and the name with it.\n const name = getCurrentInstance()?.type.__name\n const warned = new Set<string>()\n const warn = (message: string) => {\n if (warned.has(message)) return\n warned.add(message)\n console.warn(`[${name}] ${message}`)\n }\n watchEffect(() => {\n if (props.maxFiles !== undefined && !props.multiple)\n warn('`maxFiles` ignored without `multiple`: single mode already caps at one file.')\n if (options.forwardedAttrs.value.required !== undefined)\n warn(\n '`required` lands on the hidden file input: the browser does block an empty submission, but its message points at an input nobody can see. Validate the v-model yourself and use the `invalid` prop.',\n )\n for (const message of options.warnings?.() ?? []) if (message) warn(message)\n })\n }\n\n return {\n fileEl,\n enabled,\n nativeInputAttrs,\n controlAttrs,\n acceptFiles,\n openPicker,\n removeAt,\n clear,\n }\n}\n"],"mappings":";;;;AAwCA,IAAM,IAAc;CAAC;CAAQ;CAAY;CAAQ;AAAS;AA+C1D,SAAgB,EAAa,GAA2B;CACtD,IAAM,EAAE,UAAO,UAAO,YAAS,GAEzB,IAAS,EAA6B,IAAI,GAE1C,UAAiB,EAAQ,UAAU,SAAS,EAAM,UAGlD,IAAU,QAAe,CAAC,EAAS,KAAK,CAAC,EAAM,QAAQ,GAEvD,IAAe,QAAe;EAClC,IAAM,IAAW,EAAQ,sBAAsB,CAAC;EAChD,OAAO,OAAO,YACZ,OAAO,QAAQ,EAAQ,eAAe,KAAK,CAAC,CAAC,QAC1C,CAAC,OAAS,CAAC,EAAY,SAAS,CAAG,KAAK,CAAC,EAAS,SAAS,CAAG,CACjE,CACF;CACF,CAAC;CAcD,SAAS,IAAa;EACpB,IAAM,IAAK,EAAO;EAClB,IAAI,CAAC,GAAI;EAIT,IAAI,OAAO,eAAiB,KAAa;GACvC,EAAG,QAAQ;GACX;EACF;EACA,IAAM,IAAW,IAAI,aAAa;EAClC,KAAK,IAAM,KAAQ,EAAM,OAAO,EAAS,MAAM,IAAI,CAAI;EACvD,EAAG,QAAQ,EAAS;CACtB;CAIA,EAAM,GAAO,GAAY,EAAE,OAAO,OAAO,CAAC;CAS1C,SAAS,EAAY,GAAkB;EACrC,IAAM,IAAU,EAAM,WAAW,EAAM,QAAQ,CAAC,GAC1C,EAAE,aAAU,gBAAa,EAAY,GAAU,GAAS;GAC5D,QAAQ,EAAM;GACd,SAAS,EAAM;GAGf,UAAU,EAAM,WAAW,EAAM,WAAW;GAC5C,cAAc,EAAM;EACtB,CAAC;EAED,KAAK,IAAM,KAAa,GAAU,EAAK,UAAU,CAAS;EAI1D,IADA,EAAW,GACP,EAAS,WAAW,GAAG;EAI3B,IAAM,IAAW,EAAM,WAAW,KAAA,IAAY,EAAM,MAAM;EAG1D,AAFA,EAAM,QAAQ,CAAC,GAAG,GAAS,GAAG,CAAQ,GAClC,KAAY,MAAa,EAAM,MAAM,MAAI,EAAK,UAAU,GAAU,CAAC,GACvE,EAAK,UAAU,EAAM,KAAK;CAC5B;CAEA,SAAS,EAAe,GAAc;EACpC,EAAY,CAAC,GAAK,EAAM,OAA4B,SAAS,CAAC,CAAE,CAAC;CACnE;CAQA,IAAM,IAAmB,SAAgB;EACvC,GAAG,OAAO,YACR,OAAO,QAAQ,EAAQ,eAAe,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAS,EAAY,SAAS,CAAG,CAAC,CAC1F;EACA,MAAM;EACN,UAAU;EACV,eAAe;EACf,QAAQ,EAAM;EACd,UAAU,EAAM,YAAY,KAAA;EAC5B,UAAU,EAAS,KAAK,KAAA;EACxB,UAAU;CACZ,EAAE;CAQF,SAAS,IAAa;EACf,EAAQ,SACb,EAAO,OAAO,MAAM;CACtB;CAOA,SAAS,EAAS,GAAiC;EACjD,IAAM,IAAO,EAAM,MAAM;EACrB,OAAC,KAAQ,CAAC,EAAQ,QAMtB,OAJA,EAAM,QAAQ,EAAM,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAK,GACtD,EAAW,GACX,EAAK,UAAU,GAAM,CAAK,GAC1B,EAAK,UAAU,EAAM,KAAK,GACnB;CACT;CAMA,SAAS,IAAQ;EACX,CAAC,EAAQ,SAAS,EAAM,MAAM,WAAW,MAC7C,EAAM,QAAQ,CAAC,GACf,EAAW,GACX,EAAQ,UAAU,GAClB,EAAK,UAAU,EAAM,KAAK;CAC5B;CAQA,IAAI,GAAO;EAGT,IAAM,IAAO,EAAmB,CAAC,EAAE,KAAK,QAClC,oBAAS,IAAI,IAAY,GACzB,KAAQ,MAAoB;GAC5B,EAAO,IAAI,CAAO,MACtB,EAAO,IAAI,CAAO,GAClB,QAAQ,KAAK,IAAI,EAAK,IAAI,GAAS;EACrC;EACA,QAAkB;GAGhB,AAFI,EAAM,aAAa,KAAA,KAAa,CAAC,EAAM,YACzC,EAAK,8EAA8E,GACjF,EAAQ,eAAe,MAAM,aAAa,KAAA,KAC5C,EACE,qMACF;GACF,KAAK,IAAM,KAAW,EAAQ,WAAW,KAAK,CAAC,GAAG,AAAI,KAAS,EAAK,CAAO;EAC7E,CAAC;CACH;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"useFileField.js","names":[],"sources":["../../src/composables/useFileField.ts"],"sourcesContent":["// @core\n/**\n * Bridge native file selection, screening and form state for both file components. Synchronize\n * the accepted File[] through DataTransfer where supported.\n */\nimport {\n computed,\n getCurrentInstance,\n ref,\n watch,\n watchEffect,\n type ComputedRef,\n type Ref,\n} from 'vue'\n\nimport { isDev } from '../utils/env'\nimport { screenFiles, type FileRejection } from '../utils/file'\n\n// @a11y\n/** The attributes belonging on the hidden file input rather than on the visible control. */\nconst NATIVE_ONLY = ['name', 'required', 'form', 'capture']\n\n/** The props both components declare under the same names, read at the moment they matter. */\nexport interface FileFieldProps {\n multiple: boolean\n accept?: string\n maxSize?: number\n maxFiles?: number\n maxTotalSize?: number\n disabled: boolean\n readonly: boolean\n}\n\n/** The events both components declare under the same names and with the same payloads. */\nexport interface FileFieldEmit {\n (event: 'change', files: File[]): void\n (event: 'reject', rejection: FileRejection): void\n (event: 'remove', file: File, index: number): void\n}\n\nexport interface FileFieldOptions {\n /** The component's value: always a list of files, whether one or several are allowed. */\n model: Ref<File[]>\n props: FileFieldProps\n emit: FileFieldEmit\n /** The attributes the consumer wrote, minus styling, still to be split in two. */\n forwardedAttrs: ComputedRef<Record<string, unknown>>\n /**\n * Whether the component is disabled, the row it sits in taken into account. Left out, the\n * `disabled` prop answers alone.\n */\n disabled?: Ref<boolean>\n /** Attributes to withhold from the visible control too. */\n excludeFromControl?: readonly string[]\n /** Called once the selection has been emptied by `clear`, before `change` is emitted. */\n onClear?: () => void\n /**\n * The component's own development warnings, one message per condition that holds. They\n * follow the same rule as the shared ones: each is given once per instance.\n */\n warnings?: () => (string | false)[]\n}\n\nexport function useFileField(options: FileFieldOptions) {\n const { model, props, emit } = options\n\n const fileEl = ref<HTMLInputElement | null>(null)\n\n const disabled = () => options.disabled?.value ?? props.disabled\n\n /** Whether files may come in or go out; no dialog, no drop and no removal otherwise. */\n const enabled = computed(() => !disabled() && !props.readonly)\n\n const controlAttrs = computed(() => {\n const excluded = options.excludeFromControl ?? []\n return Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(\n ([key]) => !NATIVE_ONLY.includes(key) && !excluded.includes(key),\n ),\n )\n })\n\n // @core\n /**\n * It also keeps every file that LEFT the selection pickable again. The input holds exactly\n * the selection, so choosing a file that was cleared, removed or refused differs from what it\n * holds and fires `change`; left holding it, the dialog would answer the same file with no\n * event at all.\n */\n function syncNative() {\n const el = fileEl.value\n if (!el) return\n // @fallback\n // An environment without `DataTransfer` (jsdom) can only empty the input,\n // which keeps a file pickable again and leaves the form without it.\n if (typeof DataTransfer === 'undefined') {\n el.value = ''\n return\n }\n const transfer = new DataTransfer()\n for (const file of model.value) transfer.items.add(file)\n el.files = transfer.files\n }\n\n // A value set from outside, a consumer emptying the list after an upload, reaches the\n // input the same way.\n watch(model, syncNative, { flush: 'post' })\n\n /** The single entry into the model: the dialog and a drop both arrive here. */\n function acceptFiles(incoming: File[]) {\n const current = props.multiple ? model.value : []\n const { accepted, rejected } = screenFiles(incoming, current, {\n accept: props.accept,\n maxSize: props.maxSize,\n // A single-file field is a list capped at one, so extra files are refused for the\n // same reason as anywhere else: too many.\n maxFiles: props.multiple ? props.maxFiles : 1,\n maxTotalSize: props.maxTotalSize,\n })\n\n for (const rejection of rejected) emit('reject', rejection)\n\n syncNative()\n if (accepted.length === 0) return\n\n // A single file replaced by another has left the selection as surely as one taken out\n // by its cross, and `remove` is how an upload already under way for it is cancelled.\n const replaced = props.multiple ? undefined : model.value[0]\n model.value = [...current, ...accepted]\n if (replaced && replaced !== model.value[0]) emit('remove', replaced, 0)\n emit('change', model.value)\n }\n\n function onNativeChange(event: Event) {\n acceptFiles([...((event.target as HTMLInputElement).files ?? [])])\n }\n\n /**\n * Everything the hidden input is bound with, the consumer's form attributes included. It\n * is taken out of the tab order and hidden from screen readers, which leaves the visible\n * control as the single stop and the single announcement; it stays disabled only when the\n * component is, since a read-only one still submits what it holds (`syncNative`).\n */\n const nativeInputAttrs = computed(() => ({\n ...Object.fromEntries(\n Object.entries(options.forwardedAttrs.value).filter(([key]) => NATIVE_ONLY.includes(key)),\n ),\n type: 'file',\n tabindex: -1,\n 'aria-hidden': 'true' as const,\n accept: props.accept,\n multiple: props.multiple || undefined,\n disabled: disabled() || undefined,\n onChange: onNativeChange,\n }))\n\n // @fallback\n /** `.click()` and not `showPicker()`. */\n function openPicker() {\n if (!enabled.value) return\n fileEl.value?.click()\n }\n\n /**\n * Takes ONE file out of the selection, and says which: `remove` with the file and the\n * position it held, then `change` with what is left. It returns the file removed, or\n * `undefined` when nothing was; so the component knows whether it has a focus to catch.\n */\n function removeAt(index: number): File | undefined {\n const file = model.value[index]\n if (!file || !enabled.value) return undefined\n\n model.value = model.value.filter((_, i) => i !== index)\n syncNative()\n emit('remove', file, index)\n emit('change', model.value)\n return file\n }\n\n /**\n * Empties the selection, and the hidden input with it. Refused where `removeAt` is, and\n * silent when there is nothing to empty: no `change` for a selection that did not change.\n */\n function clear() {\n if (!enabled.value || model.value.length === 0) return\n model.value = []\n syncNative()\n options.onClear?.()\n emit('change', model.value)\n }\n\n // @devwarn\n /*\n * Both shared guards describe something that fails SILENTLY at runtime. Each message is given\n * once per instance: they sit in an effect, which re-runs on every change of what it reads,\n * and the same sentence repeated at every keystroke is how a warning stops being read.\n */\n if (isDev) {\n // The prefix is the component's own name, read off the instance rather than passed in:\n // this whole block is dropped from a production build, and the name with it.\n const name = getCurrentInstance()?.type.__name\n const warned = new Set<string>()\n const warn = (message: string) => {\n if (warned.has(message)) return\n warned.add(message)\n console.warn(`[${name}] ${message}`)\n }\n watchEffect(() => {\n if (props.maxFiles !== undefined && !props.multiple)\n warn('`maxFiles` ignored without `multiple`: single mode already caps at one file.')\n if (options.forwardedAttrs.value.required !== undefined)\n warn(\n '`required` lands on the hidden file input: the browser does block an empty submission, but its message points at an input nobody can see. Validate the v-model yourself and use the `invalid` prop.',\n )\n for (const message of options.warnings?.() ?? []) if (message) warn(message)\n })\n }\n\n return {\n fileEl,\n enabled,\n nativeInputAttrs,\n controlAttrs,\n acceptFiles,\n openPicker,\n removeAt,\n clear,\n }\n}\n"],"mappings":";;;;AAoBA,IAAM,IAAc;CAAC;CAAQ;CAAY;CAAQ;AAAS;AA2C1D,SAAgB,EAAa,GAA2B;CACtD,IAAM,EAAE,UAAO,UAAO,YAAS,GAEzB,IAAS,EAA6B,IAAI,GAE1C,UAAiB,EAAQ,UAAU,SAAS,EAAM,UAGlD,IAAU,QAAe,CAAC,EAAS,KAAK,CAAC,EAAM,QAAQ,GAEvD,IAAe,QAAe;EAClC,IAAM,IAAW,EAAQ,sBAAsB,CAAC;EAChD,OAAO,OAAO,YACZ,OAAO,QAAQ,EAAQ,eAAe,KAAK,CAAC,CAAC,QAC1C,CAAC,OAAS,CAAC,EAAY,SAAS,CAAG,KAAK,CAAC,EAAS,SAAS,CAAG,CACjE,CACF;CACF,CAAC;CASD,SAAS,IAAa;EACpB,IAAM,IAAK,EAAO;EAClB,IAAI,CAAC,GAAI;EAIT,IAAI,OAAO,eAAiB,KAAa;GACvC,EAAG,QAAQ;GACX;EACF;EACA,IAAM,IAAW,IAAI,aAAa;EAClC,KAAK,IAAM,KAAQ,EAAM,OAAO,EAAS,MAAM,IAAI,CAAI;EACvD,EAAG,QAAQ,EAAS;CACtB;CAIA,EAAM,GAAO,GAAY,EAAE,OAAO,OAAO,CAAC;CAG1C,SAAS,EAAY,GAAkB;EACrC,IAAM,IAAU,EAAM,WAAW,EAAM,QAAQ,CAAC,GAC1C,EAAE,aAAU,gBAAa,EAAY,GAAU,GAAS;GAC5D,QAAQ,EAAM;GACd,SAAS,EAAM;GAGf,UAAU,EAAM,WAAW,EAAM,WAAW;GAC5C,cAAc,EAAM;EACtB,CAAC;EAED,KAAK,IAAM,KAAa,GAAU,EAAK,UAAU,CAAS;EAG1D,IADA,EAAW,GACP,EAAS,WAAW,GAAG;EAI3B,IAAM,IAAW,EAAM,WAAW,KAAA,IAAY,EAAM,MAAM;EAG1D,AAFA,EAAM,QAAQ,CAAC,GAAG,GAAS,GAAG,CAAQ,GAClC,KAAY,MAAa,EAAM,MAAM,MAAI,EAAK,UAAU,GAAU,CAAC,GACvE,EAAK,UAAU,EAAM,KAAK;CAC5B;CAEA,SAAS,EAAe,GAAc;EACpC,EAAY,CAAC,GAAK,EAAM,OAA4B,SAAS,CAAC,CAAE,CAAC;CACnE;CAQA,IAAM,IAAmB,SAAgB;EACvC,GAAG,OAAO,YACR,OAAO,QAAQ,EAAQ,eAAe,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAS,EAAY,SAAS,CAAG,CAAC,CAC1F;EACA,MAAM;EACN,UAAU;EACV,eAAe;EACf,QAAQ,EAAM;EACd,UAAU,EAAM,YAAY,KAAA;EAC5B,UAAU,EAAS,KAAK,KAAA;EACxB,UAAU;CACZ,EAAE;CAIF,SAAS,IAAa;EACf,EAAQ,SACb,EAAO,OAAO,MAAM;CACtB;CAOA,SAAS,EAAS,GAAiC;EACjD,IAAM,IAAO,EAAM,MAAM;EACrB,OAAC,KAAQ,CAAC,EAAQ,QAMtB,OAJA,EAAM,QAAQ,EAAM,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAK,GACtD,EAAW,GACX,EAAK,UAAU,GAAM,CAAK,GAC1B,EAAK,UAAU,EAAM,KAAK,GACnB;CACT;CAMA,SAAS,IAAQ;EACX,CAAC,EAAQ,SAAS,EAAM,MAAM,WAAW,MAC7C,EAAM,QAAQ,CAAC,GACf,EAAW,GACX,EAAQ,UAAU,GAClB,EAAK,UAAU,EAAM,KAAK;CAC5B;CAQA,IAAI,GAAO;EAGT,IAAM,IAAO,EAAmB,CAAC,EAAE,KAAK,QAClC,oBAAS,IAAI,IAAY,GACzB,KAAQ,MAAoB;GAC5B,EAAO,IAAI,CAAO,MACtB,EAAO,IAAI,CAAO,GAClB,QAAQ,KAAK,IAAI,EAAK,IAAI,GAAS;EACrC;EACA,QAAkB;GAGhB,AAFI,EAAM,aAAa,KAAA,KAAa,CAAC,EAAM,YACzC,EAAK,8EAA8E,GACjF,EAAQ,eAAe,MAAM,aAAa,KAAA,KAC5C,EACE,qMACF;GACF,KAAK,IAAM,KAAW,EAAQ,WAAW,KAAK,CAAC,GAAG,AAAI,KAAS,EAAK,CAAO;EAC7E,CAAC;CACH;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFocusoutDismiss.js","names":[],"sources":["../../src/composables/useFocusoutDismiss.ts"],"sourcesContent":["// @a11y\n/**\n * A `@focusout` handler closing the component when focus leaves it, panel included.\n *\n * A popover stays a DOM descendant of its owner even while painted in the top layer, so\n * `contains` is the whole test and nothing has to be watched at document level. A null\n * `relatedTarget` — focus gone to the body, or out of the window — counts as leaving.\n *\n * Only `manual` popovers need this. VMenu's `auto` panel is dismissed by the browser.\n */\n\nimport type { Ref } from 'vue'\n\nexport function useFocusoutDismiss(\n root: Ref<HTMLElement | null>,\n close: () => void,\n): (event: FocusEvent) => void {\n return (event: FocusEvent) => {\n const next = event.relatedTarget as Node | null\n if (!next || !root.value?.contains(next)) close()\n }\n}\n"],"mappings":";AAaA,SAAgB,EACd,GACA,GAC6B;CAC7B,QAAQ,MAAsB;EAC5B,IAAM,IAAO,EAAM;EACnB,CAAI,CAAC,KAAQ,CAAC,EAAK,OAAO,SAAS,CAAI,MAAG,EAAM;CAClD;AACF"}
1
+ {"version":3,"file":"useFocusoutDismiss.js","names":[],"sources":["../../src/composables/useFocusoutDismiss.ts"],"sourcesContent":["// @a11y\n/**\n * Close when focus leaves the owner and panel. Top-layer popovers remain DOM descendants, so\n * contains covers both without document listeners.\n */\n\nimport type { Ref } from 'vue'\n\nexport function useFocusoutDismiss(\n root: Ref<HTMLElement | null>,\n close: () => void,\n): (event: FocusEvent) => void {\n return (event: FocusEvent) => {\n const next = event.relatedTarget as Node | null\n if (!next || !root.value?.contains(next)) close()\n }\n}\n"],"mappings":";AAQA,SAAgB,EACd,GACA,GAC6B;CAC7B,QAAQ,MAAsB;EAC5B,IAAM,IAAO,EAAM;EACnB,CAAI,CAAC,KAAQ,CAAC,EAAK,OAAO,SAAS,CAAI,MAAG,EAAM;CAClD;AACF"}
@@ -1,14 +1,6 @@
1
1
  /**
2
- * Whether VInput/VTextarea's icons should render as real buttons: an icon is decoration
3
- * until someone listens for `@click:icon-*`, at which point it needs a tab stop and a name.
4
- *
5
- * Declared emits are removed from `$attrs`, so the only place left to look is
6
- * `vnode.props` — in BOTH spellings, a template writing them with dashes and a render
7
- * function in camel case. Read ONCE and never revised: a listener attached later is not
8
- * picked up, which is marginal enough to accept.
9
- *
10
- * The missing-name warning lives here rather than in each field: a button nothing can
11
- * announce is a flaw of the arrangement, not of the component using it.
2
+ * Detect declared icon-click listeners from vnode props because emits removes them from attrs.
3
+ * Require accessible names before turning decorative icons into buttons.
12
4
  */
13
5
  /**
14
6
  * Whether the component being set up was handed a `@click:icon-start` / `@click:icon-end`
@@ -19,15 +11,8 @@ export declare function iconClickHandlers(): {
19
11
  end: boolean;
20
12
  };
21
13
  /**
22
- * The start-icon listener a field COMPOSED on top of VInput: VCombobox, VDateInput,
23
- * VTimeInput, VFileInput, binds onto that inner VInput, ready to spread beside the
24
- * consumer's attributes. Call it during setup: it reads the component's own vnode.
25
- *
26
- * Each of those fields declares `click:icon-start` as its own event, which puts it in the API
27
- * tables and takes it out of `$attrs`, hence out of what is forwarded: the listener has to be
28
- * bound by hand. It has to follow what the consumer actually wrote: bound unconditionally,
29
- * VInput would read a handler on every instance and turn a decorative icon into a focusable
30
- * button nobody asked for. So it is `undefined` when there is nothing to relay.
14
+ * The start-icon listener a field COMPOSED on top of VInput: VCombobox, VDateInput, VTimeInput,
15
+ * VFileInput, binds onto that inner VInput, ready to spread beside the consumer's attributes.
31
16
  */
32
17
  export declare function iconStartListener(relay: (event: MouseEvent) => void): {
33
18
  'onClick:icon-start': (event: MouseEvent) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useIconClickHandlers.js","names":[],"sources":["../../src/composables/useIconClickHandlers.ts"],"sourcesContent":["// @core\n/**\n * Whether VInput/VTextarea's icons should render as real buttons: an icon is decoration\n * until someone listens for `@click:icon-*`, at which point it needs a tab stop and a name.\n *\n * Declared emits are removed from `$attrs`, so the only place left to look is\n * `vnode.props` — in BOTH spellings, a template writing them with dashes and a render\n * function in camel case. Read ONCE and never revised: a listener attached later is not\n * picked up, which is marginal enough to accept.\n *\n * The missing-name warning lives here rather than in each field: a button nothing can\n * announce is a flaw of the arrangement, not of the component using it.\n */\n\nimport { getCurrentInstance } from 'vue'\n\nimport { isDev } from '../utils/env'\n\n/**\n * Whether the component being set up was handed a `@click:icon-start` / `@click:icon-end`\n * listener, in either spelling.\n */\nexport function iconClickHandlers(): { start: boolean; end: boolean } {\n const vnodeProps = getCurrentInstance()?.vnode.props ?? {}\n return {\n start: 'onClick:iconStart' in vnodeProps || 'onClick:icon-start' in vnodeProps,\n end: 'onClick:iconEnd' in vnodeProps || 'onClick:icon-end' in vnodeProps,\n }\n}\n\n/**\n * The start-icon listener a field COMPOSED on top of VInput: VCombobox, VDateInput,\n * VTimeInput, VFileInput, binds onto that inner VInput, ready to spread beside the\n * consumer's attributes. Call it during setup: it reads the component's own vnode.\n *\n * Each of those fields declares `click:icon-start` as its own event, which puts it in the API\n * tables and takes it out of `$attrs`, hence out of what is forwarded: the listener has to be\n * bound by hand. It has to follow what the consumer actually wrote: bound unconditionally,\n * VInput would read a handler on every instance and turn a decorative icon into a focusable\n * button nobody asked for. So it is `undefined` when there is nothing to relay.\n */\nexport function iconStartListener(\n relay: (event: MouseEvent) => void,\n): { 'onClick:icon-start': (event: MouseEvent) => void } | undefined {\n return iconClickHandlers().start ? { 'onClick:icon-start': relay } : undefined\n}\n\nexport function useIconClickHandlers(options: {\n name: string\n iconStartLabel?: string\n iconEndLabel?: string\n}): { hasIconStartHandler: boolean; hasIconEndHandler: boolean } {\n const { start: hasIconStartHandler, end: hasIconEndHandler } = iconClickHandlers()\n\n // @a11y @devwarn\n if (isDev) {\n if (hasIconStartHandler && !options.iconStartLabel)\n console.warn(\n `[${options.name}] clickable start icon without iconStartLabel — provide an accessible label.`,\n )\n if (hasIconEndHandler && !options.iconEndLabel)\n console.warn(\n `[${options.name}] clickable end icon without iconEndLabel — provide an accessible label.`,\n )\n }\n\n return { hasIconStartHandler, hasIconEndHandler }\n}\n"],"mappings":";;;AAsBA,SAAgB,IAAsD;CACpE,IAAM,IAAa,EAAmB,CAAC,EAAE,MAAM,SAAS,CAAC;CACzD,OAAO;EACL,OAAO,uBAAuB,KAAc,wBAAwB;EACpE,KAAK,qBAAqB,KAAc,sBAAsB;CAChE;AACF;AAaA,SAAgB,EACd,GACmE;CACnE,OAAO,EAAkB,CAAC,CAAC,QAAQ,EAAE,sBAAsB,EAAM,IAAI,KAAA;AACvE;AAEA,SAAgB,EAAqB,GAI4B;CAC/D,IAAM,EAAE,OAAO,GAAqB,KAAK,MAAsB,EAAkB;CAcjF,OAXI,MACE,KAAuB,CAAC,EAAQ,kBAClC,QAAQ,KACN,IAAI,EAAQ,KAAK,6EACnB,GACE,KAAqB,CAAC,EAAQ,gBAChC,QAAQ,KACN,IAAI,EAAQ,KAAK,yEACnB,IAGG;EAAE;EAAqB;CAAkB;AAClD"}
1
+ {"version":3,"file":"useIconClickHandlers.js","names":[],"sources":["../../src/composables/useIconClickHandlers.ts"],"sourcesContent":["// @core\n/**\n * Detect declared icon-click listeners from vnode props because emits removes them from attrs.\n * Require accessible names before turning decorative icons into buttons.\n */\n\nimport { getCurrentInstance } from 'vue'\n\nimport { isDev } from '../utils/env'\n\n/**\n * Whether the component being set up was handed a `@click:icon-start` / `@click:icon-end`\n * listener, in either spelling.\n */\nexport function iconClickHandlers(): { start: boolean; end: boolean } {\n const vnodeProps = getCurrentInstance()?.vnode.props ?? {}\n return {\n start: 'onClick:iconStart' in vnodeProps || 'onClick:icon-start' in vnodeProps,\n end: 'onClick:iconEnd' in vnodeProps || 'onClick:icon-end' in vnodeProps,\n }\n}\n\n/**\n * The start-icon listener a field COMPOSED on top of VInput: VCombobox, VDateInput, VTimeInput,\n * VFileInput, binds onto that inner VInput, ready to spread beside the consumer's attributes.\n */\nexport function iconStartListener(\n relay: (event: MouseEvent) => void,\n): { 'onClick:icon-start': (event: MouseEvent) => void } | undefined {\n return iconClickHandlers().start ? { 'onClick:icon-start': relay } : undefined\n}\n\nexport function useIconClickHandlers(options: {\n name: string\n iconStartLabel?: string\n iconEndLabel?: string\n}): { hasIconStartHandler: boolean; hasIconEndHandler: boolean } {\n const { start: hasIconStartHandler, end: hasIconEndHandler } = iconClickHandlers()\n\n // @a11y @devwarn\n if (isDev) {\n if (hasIconStartHandler && !options.iconStartLabel)\n console.warn(\n `[${options.name}] clickable start icon without iconStartLabel — provide an accessible label.`,\n )\n if (hasIconEndHandler && !options.iconEndLabel)\n console.warn(\n `[${options.name}] clickable end icon without iconEndLabel — provide an accessible label.`,\n )\n }\n\n return { hasIconStartHandler, hasIconEndHandler }\n}\n"],"mappings":";;;AAcA,SAAgB,IAAsD;CACpE,IAAM,IAAa,EAAmB,CAAC,EAAE,MAAM,SAAS,CAAC;CACzD,OAAO;EACL,OAAO,uBAAuB,KAAc,wBAAwB;EACpE,KAAK,qBAAqB,KAAc,sBAAsB;CAChE;AACF;AAMA,SAAgB,EACd,GACmE;CACnE,OAAO,EAAkB,CAAC,CAAC,QAAQ,EAAE,sBAAsB,EAAM,IAAI,KAAA;AACvE;AAEA,SAAgB,EAAqB,GAI4B;CAC/D,IAAM,EAAE,OAAO,GAAqB,KAAK,MAAsB,EAAkB;CAcjF,OAXI,MACE,KAAuB,CAAC,EAAQ,kBAClC,QAAQ,KACN,IAAI,EAAQ,KAAK,6EACnB,GACE,KAAqB,CAAC,EAAQ,gBAChC,QAAQ,KACN,IAAI,EAAQ,KAAK,yEACnB,IAGG;EAAE;EAAqB;CAAkB;AAClD"}
@@ -10,7 +10,7 @@ export interface InertLinkOptions {
10
10
  export interface InertLink {
11
11
  /** Whether the component renders an `<a>`. */
12
12
  isLink: ComputedRef<boolean>;
13
- /** Whether that `<a>` is inert, which is what `aria-disabled` is bound to. */
13
+ /** Whether that `<a>` is inert, which `aria-disabled` is bound to. */
14
14
  isInertLink: ComputedRef<boolean>;
15
15
  /** The `href` to bind: the address, or nothing on an inert link. */
16
16
  linkHref: ComputedRef<string | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"useInertLink.js","names":[],"sources":["../../src/composables/useInertLink.ts"],"sourcesContent":["// @a11y\n/**\n * The inert link of every component that may render as an `<a>`: VButton, VChip and VAvatar.\n *\n * A link has no `disabled` attribute, so the complete inertness a `<button disabled>` gets for\n * free is rebuilt by hand, in parts that only work together. The address is dropped, so the\n * link can be neither focused nor followed; `aria-disabled` tells assistive technology why; and\n * the consumer's click listeners are filtered out of the attributes, since an `<a>` without an\n * `href` still dispatches clicks and a handler would otherwise run on a control announced as\n * disabled. TRAP — a listener written with a modifier is compiled to a key of its own\n * (`@click.capture` → `onClickCapture`, `.once` → `onClickOnce`), so the filter is a pattern\n * and not the single `onClick` key, which let those handlers run on a disabled link.\n *\n * TRAP — the attributes also gain `role=\"link\"`. An `<a>` without `href` is no longer a link\n * but a generic element, on which an `aria-label` is PROHIBITED (axe `aria-prohibited-attr`)\n * and `aria-disabled` describes nothing: an icon-only VIconButton turned inert would lose its\n * name. The role is spread BEFORE the consumer's attributes, so a role of their own still wins —\n * which is also why it travels in the attributes and is never bound after them, where an\n * `undefined` would erase the `role=\"tab\"` a VTab hands its button.\n *\n * Each of the three decides for itself WHEN it is a link and WHEN it is inert (VChip gives\n * `selectable` precedence over `href`, VButton counts `loading` as inert), which is why both\n * arrive as getters; the consequences are the same everywhere and live here.\n */\nimport { computed } from 'vue'\nimport type { ComputedRef } from 'vue'\n\nexport interface InertLinkOptions {\n /** The address the component would link to, or `undefined` when it is not a link. */\n href: () => string | undefined\n /** Whether the component is unusable right now. */\n inert: () => boolean\n /** The attributes the component forwards to the element that acts. */\n attrs: () => Record<string, unknown>\n}\n\nexport interface InertLink {\n /** Whether the component renders an `<a>`. */\n isLink: ComputedRef<boolean>\n /** Whether that `<a>` is inert, which is what `aria-disabled` is bound to. */\n isInertLink: ComputedRef<boolean>\n /** The `href` to bind: the address, or nothing on an inert link. */\n linkHref: ComputedRef<string | undefined>\n /** The attributes to bind, without the click handler on an inert link. */\n attrs: ComputedRef<Record<string, unknown>>\n}\n\n/** `onClick` and the keys Vue compiles its event modifiers to, in any combination. */\nconst CLICK_LISTENER = /^onClick(?:Once|Capture|Passive)*$/\n\nexport function useInertLink(options: InertLinkOptions): InertLink {\n const isLink = computed(() => options.href() !== undefined)\n const isInertLink = computed(() => isLink.value && options.inert())\n return {\n isLink,\n isInertLink,\n linkHref: computed(() => (isInertLink.value ? undefined : options.href())),\n attrs: computed(() => {\n const attrs = options.attrs()\n if (!isInertLink.value) return attrs\n const rest: Record<string, unknown> = { role: 'link', ...attrs }\n for (const key of Object.keys(rest)) if (CLICK_LISTENER.test(key)) delete rest[key]\n return rest\n }),\n }\n}\n"],"mappings":";;AAgDA,IAAM,IAAiB;AAEvB,SAAgB,EAAa,GAAsC;CACjE,IAAM,IAAS,QAAe,EAAQ,KAAK,MAAM,KAAA,CAAS,GACpD,IAAc,QAAe,EAAO,SAAS,EAAQ,MAAM,CAAC;CAClE,OAAO;EACL;EACA;EACA,UAAU,QAAgB,EAAY,QAAQ,KAAA,IAAY,EAAQ,KAAK,CAAE;EACzE,OAAO,QAAe;GACpB,IAAM,IAAQ,EAAQ,MAAM;GAC5B,IAAI,CAAC,EAAY,OAAO,OAAO;GAC/B,IAAM,IAAgC;IAAE,MAAM;IAAQ,GAAG;GAAM;GAC/D,KAAK,IAAM,KAAO,OAAO,KAAK,CAAI,GAAG,AAAI,EAAe,KAAK,CAAG,KAAG,OAAO,EAAK;GAC/E,OAAO;EACT,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"useInertLink.js","names":[],"sources":["../../src/composables/useInertLink.ts"],"sourcesContent":["// @a11y\n/**\n * Anchors have no disabled state: remove href, mark the link disabled and filter every\n * click-listener modifier. Keep role=link when href is absent.\n */\nimport { computed } from 'vue'\nimport type { ComputedRef } from 'vue'\n\nexport interface InertLinkOptions {\n /** The address the component would link to, or `undefined` when it is not a link. */\n href: () => string | undefined\n /** Whether the component is unusable right now. */\n inert: () => boolean\n /** The attributes the component forwards to the element that acts. */\n attrs: () => Record<string, unknown>\n}\n\nexport interface InertLink {\n /** Whether the component renders an `<a>`. */\n isLink: ComputedRef<boolean>\n /** Whether that `<a>` is inert, which `aria-disabled` is bound to. */\n isInertLink: ComputedRef<boolean>\n /** The `href` to bind: the address, or nothing on an inert link. */\n linkHref: ComputedRef<string | undefined>\n /** The attributes to bind, without the click handler on an inert link. */\n attrs: ComputedRef<Record<string, unknown>>\n}\n\n/** `onClick` and the keys Vue compiles its event modifiers to, in any combination. */\nconst CLICK_LISTENER = /^onClick(?:Once|Capture|Passive)*$/\n\nexport function useInertLink(options: InertLinkOptions): InertLink {\n const isLink = computed(() => options.href() !== undefined)\n const isInertLink = computed(() => isLink.value && options.inert())\n return {\n isLink,\n isInertLink,\n linkHref: computed(() => (isInertLink.value ? undefined : options.href())),\n attrs: computed(() => {\n const attrs = options.attrs()\n if (!isInertLink.value) return attrs\n const rest: Record<string, unknown> = { role: 'link', ...attrs }\n for (const key of Object.keys(rest)) if (CLICK_LISTENER.test(key)) delete rest[key]\n return rest\n }),\n }\n}\n"],"mappings":";;AA6BA,IAAM,IAAiB;AAEvB,SAAgB,EAAa,GAAsC;CACjE,IAAM,IAAS,QAAe,EAAQ,KAAK,MAAM,KAAA,CAAS,GACpD,IAAc,QAAe,EAAO,SAAS,EAAQ,MAAM,CAAC;CAClE,OAAO;EACL;EACA;EACA,UAAU,QAAgB,EAAY,QAAQ,KAAA,IAAY,EAAQ,KAAK,CAAE;EACzE,OAAO,QAAe;GACpB,IAAM,IAAQ,EAAQ,MAAM;GAC5B,IAAI,CAAC,EAAY,OAAO,OAAO;GAC/B,IAAM,IAAgC;IAAE,MAAM;IAAQ,GAAG;GAAM;GAC/D,KAAK,IAAM,KAAO,OAAO,KAAK,CAAI,GAAG,AAAI,EAAe,KAAK,CAAG,KAAG,OAAO,EAAK;GAC/E,OAAO;EACT,CAAC;CACH;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveAnnouncer.js","names":[],"sources":["../../src/composables/useLiveAnnouncer.ts"],"sourcesContent":["// @a11y\n/**\n * Two live regions that are ALREADY on the page, and a way to say something through them.\n *\n * A live region inserted at the same time as its text is not reliably announced: screen\n * readers watch regions for CHANGES, and a region that did not exist a moment ago has no\n * before to change from. A notification card or a snackbar bar is exactly that, created\n * with its message, so the message was announced sometimes and dropped at others. The\n * components render the two regions permanently (a polite `status`, an assertive `alert`),\n * visually hidden and outside their popover containers, and hand each message to them.\n *\n * The text is cleared first and written on the next tick, so the same message raised twice\n * in a row is a change the second time too.\n *\n * TRAP: messages said in the SAME tick are written together, one per line. Each used to\n * schedule its own write into the same region, and only the last ever reached the page: two\n * notifications raised by one handler were announced as one.\n *\n * Consumed by VToaster, VSnackbar and VCalendar.\n */\nimport { nextTick, ref } from 'vue'\n\nexport function useLiveAnnouncer() {\n const polite = ref('')\n const assertive = ref('')\n const pending = { polite: [] as string[], assertive: [] as string[] }\n\n /** Says `text`, interrupting what is being read when `urgent`, waiting for a pause otherwise. */\n function announce(text: string, urgent: boolean) {\n const region = urgent ? assertive : polite\n const queue = urgent ? pending.assertive : pending.polite\n queue.push(text)\n // A write is already on its way this tick: this message joins it.\n if (queue.length > 1) return\n region.value = ''\n void nextTick(() => {\n region.value = queue.join('\\n')\n queue.length = 0\n })\n }\n\n return { polite, assertive, announce }\n}\n"],"mappings":";;AAsBA,SAAgB,IAAmB;CACjC,IAAM,IAAS,EAAI,EAAE,GACf,IAAY,EAAI,EAAE,GAClB,IAAU;EAAE,QAAQ,CAAC;EAAe,WAAW,CAAC;CAAc;CAGpE,SAAS,EAAS,GAAc,GAAiB;EAC/C,IAAM,IAAS,IAAS,IAAY,GAC9B,IAAQ,IAAS,EAAQ,YAAY,EAAQ;EACnD,EAAM,KAAK,CAAI,GAEX,IAAM,SAAS,OACnB,EAAO,QAAQ,IACf,QAAoB;GAElB,AADA,EAAO,QAAQ,EAAM,KAAK,IAAI,GAC9B,EAAM,SAAS;EACjB,CAAC;CACH;CAEA,OAAO;EAAE;EAAQ;EAAW;CAAS;AACvC"}
1
+ {"version":3,"file":"useLiveAnnouncer.js","names":[],"sources":["../../src/composables/useLiveAnnouncer.ts"],"sourcesContent":["// @a11y\n/**\n * Mount live regions before writing messages. Clear before rewriting identical text and batch\n * same-tick announcements so none overwrite each other.\n */\nimport { nextTick, ref } from 'vue'\n\nexport function useLiveAnnouncer() {\n const polite = ref('')\n const assertive = ref('')\n const pending = { polite: [] as string[], assertive: [] as string[] }\n\n /** Says `text`, interrupting what is being read when `urgent`, waiting for a pause otherwise. */\n function announce(text: string, urgent: boolean) {\n const region = urgent ? assertive : polite\n const queue = urgent ? pending.assertive : pending.polite\n queue.push(text)\n if (queue.length > 1) return\n region.value = ''\n void nextTick(() => {\n region.value = queue.join('\\n')\n queue.length = 0\n })\n }\n\n return { polite, assertive, announce }\n}\n"],"mappings":";;AAOA,SAAgB,IAAmB;CACjC,IAAM,IAAS,EAAI,EAAE,GACf,IAAY,EAAI,EAAE,GAClB,IAAU;EAAE,QAAQ,CAAC;EAAe,WAAW,CAAC;CAAc;CAGpE,SAAS,EAAS,GAAc,GAAiB;EAC/C,IAAM,IAAS,IAAS,IAAY,GAC9B,IAAQ,IAAS,EAAQ,YAAY,EAAQ;EACnD,EAAM,KAAK,CAAI,GACX,IAAM,SAAS,OACnB,EAAO,QAAQ,IACf,QAAoB;GAElB,AADA,EAAO,QAAQ,EAAM,KAAK,IAAI,GAC9B,EAAM,SAAS;EACjB,CAAC;CACH;CAEA,OAAO;EAAE;EAAQ;EAAW;CAAS;AACvC"}
@@ -4,7 +4,7 @@ export interface MaskedFieldOptions<T extends string> {
4
4
  fieldEl: Ref<HTMLInputElement | null>;
5
5
  /** Whether the field can be typed into at all. Everything here is inert when it cannot. */
6
6
  typing: () => boolean;
7
- /** What the field shows when it is NOT being typed into: the value, written out in full. */
7
+ /** What the field shows when it is not being typed into: the value, written out in full. */
8
8
  displayText: () => string;
9
9
  /** The current value, or nothing. */
10
10
  readValue: () => T | null;
@@ -15,20 +15,13 @@ export interface MaskedFieldOptions<T extends string> {
15
15
  /** Lays a run of digits out as masked text. */
16
16
  format: (digits: string) => string;
17
17
  /**
18
- * Where the caret goes so as to sit after a given number of digits.
19
- *
20
- * The flag says whether something was INSERTED rather than deleted, and it matters: on
21
- * a deletion the caret must stay IN FRONT of the separator, or the next press of the key
22
- * would step over it instead of erasing, and the key would appear to do nothing.
18
+ * Where the caret goes so as to sit after a given number of digits. The flag says whether
19
+ * something was INSERTED rather than deleted, and it matters: on a deletion the caret must
20
+ * stay in FRONT of the separator, or the next press of the key would step over it instead of
21
+ * erasing, and the key would appear to do nothing.
23
22
  */
24
23
  caret: (text: string, digitsBefore: number, inserting: boolean) => number;
25
- /**
26
- * Reads masked text back into a value, or nothing when it is not one yet.
27
- *
28
- * The flag marks the FINAL reading, when the reader leaves the field. A date expands a
29
- * two-digit year only there: "26" must not be committed as 2026 while it may still be on
30
- * its way to becoming it.
31
- */
24
+ /** Reads masked text back into a value, or nothing when it is not one yet. */
32
25
  parse: (text: string, final: boolean) => T | null;
33
26
  /** Writes a value as masked text, and nothing at all for no value. */
34
27
  toMask: (value: T | null) => string;
@@ -40,11 +33,7 @@ export interface MaskedFieldOptions<T extends string> {
40
33
  }
41
34
  /** What a component adds to the mask's own keys. */
42
35
  export interface MaskedKeyHooks {
43
- /**
44
- * A key that is not a digit was typed: a separator, most often. Its character never
45
- * reaches the field; what it MEANS is the component's: a date pads the field being typed
46
- * and moves on to the next, a time pads the hour.
47
- */
36
+ /** A key that is not a digit was typed: a separator, most often. */
48
37
  onSeparator: (el: HTMLInputElement) => void;
49
38
  /**
50
39
  * The down arrow, which a component with a panel turns into the way into it. It answers
@@ -59,10 +48,10 @@ export interface MaskedField<T extends string> {
59
48
  /** The text currently in the field while it is being typed into. */
60
49
  draft: Ref<string>;
61
50
  /**
62
- * TRAP: bind with `v-model` and NEVER `:model-value`. Without an `onUpdate:modelValue`
63
- * listener, `useModel` keeps an internal copy of the RAW typed text and rewrites it on the
64
- * next patch, erasing the mask exactly when the masked text did NOT change: a rejected
65
- * character, or a digit past the last. Both components lock it with a test.
51
+ * Bind with `v-model` and never `:model-value`. Without an `onUpdate:modelValue` listener,
52
+ * `useModel` keeps an internal copy of the RAW typed text and rewrites it on the next patch,
53
+ * erasing the mask exactly when the masked text did not change: a rejected character, or a
54
+ * digit past the last.
66
55
  */
67
56
  fieldModel: WritableComputedRef<string | number>;
68
57
  /**
@@ -78,11 +67,7 @@ export interface MaskedField<T extends string> {
78
67
  onFieldInput: (event: Event) => void;
79
68
  /** The handler for the field's keydown event, with what the component adds to it. */
80
69
  onKeydown: (event: KeyboardEvent, hooks: MaskedKeyHooks) => void;
81
- /**
82
- * The handler for the field's paste event. `recognize` reads the pasted text as a WHOLE
83
- * value when it is one (a date in ISO form or a canonical time) and answers nothing
84
- * otherwise, in which case only the digits of what was pasted are taken.
85
- */
70
+ /** The handler for the field's paste event. */
86
71
  onPaste: (event: ClipboardEvent, recognize: (pasted: string) => T | null) => void;
87
72
  }
88
73
  export declare function useMaskedField<T extends string>(options: MaskedFieldOptions<T>): MaskedField<T>;