vectis-ui 0.10.1 → 0.10.3

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
@@ -7,10 +7,9 @@ export type BadgeTone = 'neutral' | 'accent' | 'danger' | 'success' | 'warning';
7
7
  export type BadgeOverlayPosition = 'top' | 'bottom';
8
8
  interface BadgeProps {
9
9
  /**
10
- * How strongly the badge is painted: filled with the full colour (`solid`, the
11
- * default), or a tinted background with the text in the colour (`soft`), for a
12
- * count that should not draw the eye. A `dot` is always solid: with no content to
13
- * tint, a soft dot would be a pale disc barely distinguishable from the page.
10
+ * How strongly the badge is painted: filled with the full colour (`solid`, the default), or a
11
+ * tinted background with the text in the colour (`soft`), for a count that should not draw
12
+ * the eye.
14
13
  */
15
14
  variant?: BadgeVariant;
16
15
  /**
@@ -20,11 +19,10 @@ interface BadgeProps {
20
19
  */
21
20
  tone?: BadgeTone;
22
21
  /**
23
- * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. On a
24
- * solid badge, browsers that support `contrast-color()` turn the text black or
25
- * white by themselves; everywhere else it falls back to white, so with a light
26
- * colour the contrast is yours to check. A soft badge derives its background and
27
- * text from that colour mixed with the theme's surface and text.
22
+ * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. On a solid
23
+ * badge, browsers that support `contrast-color()` turn the text black or white by themselves;
24
+ * everywhere else it falls back to white, so with a light colour the contrast is yours to
25
+ * check.
28
26
  */
29
27
  color?: string;
30
28
  /**
@@ -49,32 +47,20 @@ interface BadgeProps {
49
47
  */
50
48
  overlay?: boolean;
51
49
  /**
52
- * Which corner an `overlay` badge is pinned to: the top one by default, the
53
- * bottom one for a marker that belongs at the foot of its target, such as a presence dot
54
- * under an avatar. The horizontal side follows the reading
55
- * direction and is not configurable. It does nothing without `overlay`.
50
+ * Which corner an `overlay` badge is pinned to: the top one by default, the bottom one for a
51
+ * marker that belongs at the foot of its target, such as a presence dot under an avatar.
56
52
  */
57
53
  overlayPosition?: BadgeOverlayPosition;
58
54
  /**
59
- * Draws a 2px ring around the badge, in the colour of the surface behind it,
60
- * which detaches it from a busy target such as a picture. That colour defaults to
61
- * the page background and is named through `ringColor` on any other surface.
55
+ * Draws a 2px ring around the badge, in the colour of the surface behind it, which detaches
56
+ * it from a busy target such as a picture.
62
57
  */
63
58
  bordered?: boolean;
64
- /**
65
- * The colour of the ring drawn by `bordered`. It defaults to the page background,
66
- * which is what makes the ring read as a gap around the badge; on another surface
67
- * (a card, a coloured banner), pass that surface's colour. It does nothing without
68
- * `bordered`.
69
- */
59
+ /** The colour of the ring drawn by `bordered`. */
70
60
  ringColor?: string;
71
61
  }
72
62
  type __VLS_Slots = {
73
- /**
74
- * The element the badge belongs to. Without it the badge stands on its own; with
75
- * it, the badge is placed beside the element, or in its corner under `overlay`, and is
76
- * hidden from assistive technology: say the count in the element's own name.
77
- */
63
+ /** The element the badge belongs to. */
78
64
  default?(): unknown;
79
65
  };
80
66
  declare const __VLS_base: import('vue').DefineComponent<BadgeProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<BadgeProps> & Readonly<{}>, {
@@ -1 +1 @@
1
- {"version":3,"file":"VBadge.js","names":[],"sources":["../../../src/components/VBadge/VBadge.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * A small pill carrying one piece of information at a glance: a count of unread\n * items, or a single icon. It is never interactive — there is nothing to click and\n * nothing to focus — and it either stands on its own, sits next to an element, or\n * is pinned to one of that element's corners.\n *\n * It is made of HTML and CSS alone. The only JavaScript decides what to render:\n * whether a target element was given at all, capping the counter at \"99+\", and\n * handing the two colours a consumer may choose to the stylesheet as\n * `--custom-color` and `--badge-ring-color`.\n *\n * Beside a target the pill is `aria-hidden`: read on its own, \"3\" means nothing, and the\n * target is what a reader reaches, so the count belongs in the target's own name\n * (\"Notifications, 3 unread\") where it would otherwise be announced twice.\n */\nimport { computed, h } from 'vue'\nimport type { FunctionalComponent } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { customColorStyle } from '../../utils/css'\n\n/** How strongly the badge is painted: the full colour, or a tint of it. */\nexport type BadgeVariant = 'solid' | 'soft'\n/** What the badge means, in colour. */\nexport type BadgeTone = 'neutral' | 'accent' | 'danger' | 'success' | 'warning'\n/** The corner an overlaid badge is pinned to. */\nexport type BadgeOverlayPosition = 'top' | 'bottom'\n\ninterface BadgeProps {\n /**\n * How strongly the badge is painted: filled with the full colour (`solid`, the\n * default), or a tinted background with the text in the colour (`soft`), for a\n * count that should not draw the eye. A `dot` is always solid: with no content to\n * tint, a soft dot would be a pale disc barely distinguishable from the page.\n */\n variant?: BadgeVariant\n /**\n * The meaning the badge carries, expressed as a colour. A solid badge is filled\n * with it and its text adapts to stay readable; a soft one is tinted with it and\n * writes its text in it.\n */\n tone?: BadgeTone\n /**\n * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. On a\n * solid badge, browsers that support `contrast-color()` turn the text black or\n * white by themselves; everywhere else it falls back to white, so with a light\n * colour the contrast is yours to check. A soft badge derives its background and\n * text from that colour mixed with the theme's surface and text.\n */\n color?: string\n /**\n * The number to display. Anything above 99 is shown as \"99+\", so that a busy\n * counter cannot stretch the pill indefinitely.\n */\n count?: number\n /**\n * A single icon shown instead of a number: an icon name, or an explicit render\n * (the VIcon contract). It takes precedence over `count`, and is ignored when the\n * badge is a `dot`.\n */\n icon?: IconSource\n /**\n * Reduces the badge to a 10px dot with no content, the discreet way to signal\n * that something is new without saying how much.\n */\n dot?: boolean\n /**\n * Pins the badge to a corner of the target element instead of placing it beside\n * it. It does nothing when the badge has no target.\n */\n overlay?: boolean\n /**\n * Which corner an `overlay` badge is pinned to: the top one by default, the\n * bottom one for a marker that belongs at the foot of its target, such as a presence dot\n * under an avatar. The horizontal side follows the reading\n * direction and is not configurable. It does nothing without `overlay`.\n */\n overlayPosition?: BadgeOverlayPosition\n /**\n * Draws a 2px ring around the badge, in the colour of the surface behind it,\n * which detaches it from a busy target such as a picture. That colour defaults to\n * the page background and is named through `ringColor` on any other surface.\n */\n bordered?: boolean\n /**\n * The colour of the ring drawn by `bordered`. It defaults to the page background,\n * which is what makes the ring read as a gap around the badge; on another surface\n * (a card, a coloured banner), pass that surface's colour. It does nothing without\n * `bordered`.\n */\n ringColor?: string\n}\n\nconst props = withDefaults(defineProps<BadgeProps>(), {\n variant: 'solid',\n tone: 'accent',\n color: undefined,\n count: undefined,\n icon: undefined,\n overlayPosition: 'top',\n ringColor: undefined,\n // Written out although Vue casts an absent boolean to `false` on its own: left out,\n // `vue-component-meta` reports no default at all and the docs table's cell goes blank.\n dot: false,\n overlay: false,\n bordered: false,\n})\n\nconst slots = defineSlots<{\n /**\n * The element the badge belongs to. Without it the badge stands on its own; with\n * it, the badge is placed beside the element, or in its corner under `overlay`, and is\n * hidden from assistive technology: say the count in the element's own name.\n */\n default?(): unknown\n}>()\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasTarget() {\n return slots.default !== undefined\n}\n\n/**\n * The counter as it is actually displayed: a whole number, zero or more, and anything past 99\n * becomes \"99+\". A count that is not a number (a division by a total not yet known) draws\n * nothing rather than the word \"NaN\".\n */\nconst displayCount = computed(() => {\n const count = props.count\n if (count === undefined || !Number.isFinite(count)) return undefined\n return count > 99 ? '99+' : Math.max(0, Math.trunc(count))\n})\n\n/*\n * The pill itself, written once. The template has two roots — one wrapping a target, one\n * standing alone — and both render the same badge; a functional component is the one way\n * to put the same markup in two places of a template without writing it twice.\n */\nconst Pill: FunctionalComponent = () =>\n h(\n 'span',\n {\n class: 'v-badge v-tone v-variant',\n 'data-variant': props.variant,\n 'data-tone': props.tone,\n 'data-custom': props.color !== undefined ? '' : undefined,\n 'data-dot': props.dot ? '' : undefined,\n 'data-icon-only': !props.dot && props.icon ? '' : undefined,\n 'data-bordered': props.bordered ? '' : undefined,\n style: [\n customColorStyle(props.color),\n props.ringColor !== undefined ? { '--badge-ring-color': props.ringColor } : undefined,\n ],\n },\n props.dot\n ? undefined\n : props.icon\n ? [h(VIcon, iconProps(props.icon))]\n : displayCount.value === undefined\n ? undefined\n : String(displayCount.value),\n )\n// TRAP — the empty list is not decoration. A functional component that declares no props lets\n// only `class`, `style` and listeners fall through, so a standalone badge, whose root this is,\n// would silently drop every other attribute a consumer set on VBadge (an `id`, a `data-*`).\nPill.props = []\n</script>\n\n<template>\n <span\n v-if=\"hasTarget()\"\n class=\"v-badge-host\"\n :data-overlay=\"overlay ? '' : undefined\"\n :data-overlay-position=\"overlayPosition\"\n >\n <slot />\n <Pill aria-hidden=\"true\" />\n </span>\n <Pill v-else />\n</template>\n\n<style>\n@layer vectis.components {\n .v-badge-host {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2); /* the space between the target and the badge, in inline mode */\n }\n\n /* The pill is painted by the shared `.v-variant` rules (styles/variants.css) over the\n tone table (`.v-tone`), both set in the template: `solid` is the tone's full colour,\n `soft` its tint with the text in the tone's own colour. A custom colour is the table's\n `[data-custom]` row, in styles/tones.css. */\n .v-badge {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n /* The colour of the `bordered` ring: a bet on the page background, the only one a\n component can make since CSS cannot read what its parent paints. The `ringColor`\n prop overrides it inline for a badge sitting on anything else. */\n --badge-ring-color: var(--vectis-color-surface);\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: var(--vectis-control-size-badge-h);\n /* A minimum width equal to the height, with a small padding on top, is what\n makes a one- or two-digit counter come out round rather than oval. */\n min-width: var(--vectis-control-size-badge-h);\n padding-inline: var(--vectis-space-1);\n border-radius: var(--vectis-radius-pill);\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-caption-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n }\n\n /* On a solid pill the text picks black or white against whatever the fill ended up\n with, where the function exists; elsewhere it keeps the table's `--tone-text-solid`.\n TRAP — the class is compounded to (0,3,0): `.v-variant[data-variant='solid']` sets\n `color` at (0,2,0) from another sheet, and a tie would be settled by sheet order. The\n soft pill keeps the tone's own text colour, which says the tone where black or white\n would not. */\n @supports (color: contrast-color(red)) {\n .v-badge.v-variant[data-variant='solid'] {\n color: contrast-color(var(--tone-bg-solid));\n }\n }\n\n /* A dot is always solid: with no content, a soft dot is a pale disc on the page, which a\n presence marker cannot afford. (0,3,0), above the soft variant's background. */\n .v-badge.v-variant[data-dot] {\n background: var(--tone-bg-solid);\n }\n\n .v-badge[data-icon-only] {\n padding: 0;\n }\n\n .v-badge[data-dot] {\n width: var(--vectis-control-size-badge-dot);\n height: var(--vectis-control-size-badge-dot);\n min-width: 0;\n padding: 0;\n }\n\n .v-badge[data-bordered] {\n box-shadow: 0 0 0 var(--vectis-control-size-badge-ring) var(--badge-ring-color);\n }\n\n /* The tuck-in offset is a ratio of the badge's own size, so it stays proportionate\n across the pill, the dot and the icon. Each half of it is held in its own\n variable: the corner and the reading direction each flip ONE axis, and writing\n the whole `translate` again in each branch would make the two multiply — four\n rules to keep in step by hand for two independent decisions. */\n .v-badge-host[data-overlay] > .v-badge {\n --badge-overlay-x: 25%;\n --badge-overlay-y: -25%;\n\n position: absolute;\n inset-block: 0 auto;\n inset-inline-end: 0;\n translate: var(--badge-overlay-x) var(--badge-overlay-y);\n /* TRAP — an overlaid badge deliberately leaves its target's box, so it lands on\n whatever sits beside it, and the host opens no stacking context of its own\n (`position: relative` with `z-index: auto`). Left at `auto` the badge is painted\n with the other positioned boxes in TREE ORDER, which means anything positioned\n after it covers it: inside a VButtonGroup, where every segment is positioned, the\n badge on one segment disappears under the borders of the next. The lift takes it\n past that step, and no further: the segment it leans into comes later in tree\n order, so that segment's focus ring, raised to this same level, is still drawn\n over the badge rather than under it. */\n z-index: 1;\n }\n\n .v-badge-host[data-overlay][data-overlay-position='bottom'] > .v-badge {\n --badge-overlay-y: 25%;\n\n inset-block: auto 0;\n }\n\n /* In a right-to-left direction the logical corner moves to the left, so the\n horizontal half of the translation has to flip with it — `translate` is a\n physical property and knows nothing about direction. */\n .v-badge-host[data-overlay]:dir(rtl) > .v-badge {\n --badge-overlay-x: -25%;\n }\n\n /* Forced colours repaint the background as Canvas and drop the ring, a shadow: the pill\n loses its shape, and a dot, which has nothing else, disappears altogether. The pill\n gets an edge, and the dot is filled in the text colour. */\n @media (forced-colors: active) {\n .v-badge {\n outline: var(--vectis-control-border-width) solid CanvasText;\n outline-offset: calc(-1 * var(--vectis-control-border-width));\n }\n\n .v-badge.v-variant[data-dot] {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VBadge.js","names":[],"sources":["../../../src/components/VBadge/VBadge.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * Render the default slot once: inspect its VNodes to decide whether a noninteractive badge has\n * content.\n */\nimport { computed, h } from 'vue'\nimport type { FunctionalComponent } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { customColorStyle } from '../../utils/css'\n\n/** How strongly the badge is painted: the full colour, or a tint of it. */\nexport type BadgeVariant = 'solid' | 'soft'\n/** What the badge means, in colour. */\nexport type BadgeTone = 'neutral' | 'accent' | 'danger' | 'success' | 'warning'\n/** The corner an overlaid badge is pinned to. */\nexport type BadgeOverlayPosition = 'top' | 'bottom'\n\ninterface BadgeProps {\n /**\n * How strongly the badge is painted: filled with the full colour (`solid`, the default), or a\n * tinted background with the text in the colour (`soft`), for a count that should not draw\n * the eye.\n */\n variant?: BadgeVariant\n /**\n * The meaning the badge carries, expressed as a colour. A solid badge is filled\n * with it and its text adapts to stay readable; a soft one is tinted with it and\n * writes its text in it.\n */\n tone?: BadgeTone\n /**\n * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. On a solid\n * badge, browsers that support `contrast-color()` turn the text black or white by themselves;\n * everywhere else it falls back to white, so with a light colour the contrast is yours to\n * check.\n */\n color?: string\n /**\n * The number to display. Anything above 99 is shown as \"99+\", so that a busy\n * counter cannot stretch the pill indefinitely.\n */\n count?: number\n /**\n * A single icon shown instead of a number: an icon name, or an explicit render\n * (the VIcon contract). It takes precedence over `count`, and is ignored when the\n * badge is a `dot`.\n */\n icon?: IconSource\n /**\n * Reduces the badge to a 10px dot with no content, the discreet way to signal\n * that something is new without saying how much.\n */\n dot?: boolean\n /**\n * Pins the badge to a corner of the target element instead of placing it beside\n * it. It does nothing when the badge has no target.\n */\n overlay?: boolean\n /**\n * Which corner an `overlay` badge is pinned to: the top one by default, the bottom one for a\n * marker that belongs at the foot of its target, such as a presence dot under an avatar.\n */\n overlayPosition?: BadgeOverlayPosition\n /**\n * Draws a 2px ring around the badge, in the colour of the surface behind it, which detaches\n * it from a busy target such as a picture.\n */\n bordered?: boolean\n /** The colour of the ring drawn by `bordered`. */\n ringColor?: string\n}\n\nconst props = withDefaults(defineProps<BadgeProps>(), {\n variant: 'solid',\n tone: 'accent',\n color: undefined,\n count: undefined,\n icon: undefined,\n overlayPosition: 'top',\n ringColor: undefined,\n // Written out although Vue casts an absent boolean to `false` on its own: left out,\n // `vue-component-meta` reports no default at all and the docs table's cell goes blank.\n dot: false,\n overlay: false,\n bordered: false,\n})\n\nconst slots = defineSlots<{\n /** The element the badge belongs to. */\n default?(): unknown\n}>()\n\n// A function read by the template, never a `computed`: `slots` is not reactive, so a computed\n// would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasTarget() {\n return slots.default !== undefined\n}\n\n/**\n * The counter as it is actually displayed: a whole number, zero or more, and anything past 99\n * becomes \"99+\".\n */\nconst displayCount = computed(() => {\n const count = props.count\n if (count === undefined || !Number.isFinite(count)) return undefined\n return count > 99 ? '99+' : Math.max(0, Math.trunc(count))\n})\n\n/*\n * The template has two roots; one wrapping a target, one standing alone; and both render the\n * same badge; a functional component is the one way to put the same markup in two places of a\n * template without writing it twice.\n */\nconst Pill: FunctionalComponent = () =>\n h(\n 'span',\n {\n class: 'v-badge v-tone v-variant',\n 'data-variant': props.variant,\n 'data-tone': props.tone,\n 'data-custom': props.color !== undefined ? '' : undefined,\n 'data-dot': props.dot ? '' : undefined,\n 'data-icon-only': !props.dot && props.icon ? '' : undefined,\n 'data-bordered': props.bordered ? '' : undefined,\n style: [\n customColorStyle(props.color),\n props.ringColor !== undefined ? { '--badge-ring-color': props.ringColor } : undefined,\n ],\n },\n props.dot\n ? undefined\n : props.icon\n ? [h(VIcon, iconProps(props.icon))]\n : displayCount.value === undefined\n ? undefined\n : String(displayCount.value),\n )\n// The empty list is not decoration. A functional component that declares no props lets only\n// `class`, `style` and listeners fall through, so a standalone badge, whose root this is, would\n// silently drop every other attribute a consumer set on VBadge (an `id`, a `data-*`).\nPill.props = []\n</script>\n\n<template>\n <span\n v-if=\"hasTarget()\"\n class=\"v-badge-host\"\n :data-overlay=\"overlay ? '' : undefined\"\n :data-overlay-position=\"overlayPosition\"\n >\n <slot />\n <Pill aria-hidden=\"true\" />\n </span>\n <Pill v-else />\n</template>\n\n<style>\n@layer vectis.components {\n .v-badge-host {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n }\n\n /* The pill is painted by the shared `.v-variant` rules (styles/variants.css) over the\n tone table (`.v-tone`), both set in the template: `solid` is the tone's full colour,\n `soft` its tint with the text in the tone's own colour. A custom colour is the table's\n `[data-custom]` row, in styles/tones.css. */\n .v-badge {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n /* The colour of the `bordered` ring: a bet on the page background, the only one a\n component can make since CSS cannot read what its parent paints. The `ringColor`\n prop overrides it inline for a badge sitting on anything else. */\n --badge-ring-color: var(--vectis-color-surface);\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: var(--vectis-control-size-badge-h);\n /* A minimum width equal to the height, with a small padding on top, is what\n makes a one- or two-digit counter come out round rather than oval. */\n min-width: var(--vectis-control-size-badge-h);\n padding-inline: var(--vectis-space-1);\n border-radius: var(--vectis-radius-pill);\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-caption-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n }\n\n /*\n * The class is compounded to (0,3,0): `.v-variant[data-variant='solid']` sets `color` at\n * (0,2,0) from another sheet, and a tie would be settled by sheet order. The soft pill keeps\n * the tone's own text colour, which says the tone where black or white would not.\n */\n @supports (color: contrast-color(red)) {\n .v-badge.v-variant[data-variant='solid'] {\n color: contrast-color(var(--tone-bg-solid));\n }\n }\n\n /* A dot is always solid: with no content, a soft dot is a pale disc on the page, which a\n presence marker cannot afford. (0,3,0), above the soft variant's background. */\n .v-badge.v-variant[data-dot] {\n background: var(--tone-bg-solid);\n }\n\n .v-badge[data-icon-only] {\n padding: 0;\n }\n\n .v-badge[data-dot] {\n width: var(--vectis-control-size-badge-dot);\n height: var(--vectis-control-size-badge-dot);\n min-width: 0;\n padding: 0;\n }\n\n .v-badge[data-bordered] {\n box-shadow: 0 0 0 var(--vectis-control-size-badge-ring) var(--badge-ring-color);\n }\n\n /*\n * The tuck-in offset is a ratio of the badge's own size, so it stays proportionate across the\n * pill, the dot and the icon. Each half of it is held in its own variable: the corner and the\n * reading direction each flip ONE axis, and writing the whole `translate` again in each\n * branch would make the two multiply; four rules to keep in step by hand for two independent\n * decisions.\n */\n .v-badge-host[data-overlay] > .v-badge {\n --badge-overlay-x: 25%;\n --badge-overlay-y: -25%;\n\n position: absolute;\n inset-block: 0 auto;\n inset-inline-end: 0;\n translate: var(--badge-overlay-x) var(--badge-overlay-y);\n /*\n * Raise overlaid badges above later positioned siblings; tree order alone lets neighbouring\n * segments cover them.\n */\n z-index: 1;\n }\n\n .v-badge-host[data-overlay][data-overlay-position='bottom'] > .v-badge {\n --badge-overlay-y: 25%;\n\n inset-block: auto 0;\n }\n\n /*\n * In a right-to-left direction the logical corner moves to the left, so the horizontal half\n * of the translation has to flip with it; `translate` is a physical property and knows\n * nothing about direction.\n */\n .v-badge-host[data-overlay]:dir(rtl) > .v-badge {\n --badge-overlay-x: -25%;\n }\n\n /* Forced colours repaint the background as Canvas and drop the ring, a shadow: the pill\n loses its shape, and a dot, which has nothing else, disappears altogether. The pill\n gets an edge, and the dot is filled in the text colour. */\n @media (forced-colors: active) {\n .v-badge {\n outline: var(--vectis-control-border-width) solid CanvasText;\n outline-offset: calc(-1 * var(--vectis-control-border-width));\n }\n\n .v-badge.v-variant[data-dot] {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"VBadge.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VBadge/VBadge.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * A small pill carrying one piece of information at a glance: a count of unread\n * items, or a single icon. It is never interactive — there is nothing to click and\n * nothing to focus — and it either stands on its own, sits next to an element, or\n * is pinned to one of that element's corners.\n *\n * It is made of HTML and CSS alone. The only JavaScript decides what to render:\n * whether a target element was given at all, capping the counter at \"99+\", and\n * handing the two colours a consumer may choose to the stylesheet as\n * `--custom-color` and `--badge-ring-color`.\n *\n * Beside a target the pill is `aria-hidden`: read on its own, \"3\" means nothing, and the\n * target is what a reader reaches, so the count belongs in the target's own name\n * (\"Notifications, 3 unread\") where it would otherwise be announced twice.\n */\nimport { computed, h } from 'vue'\nimport type { FunctionalComponent } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { customColorStyle } from '../../utils/css'\n\n/** How strongly the badge is painted: the full colour, or a tint of it. */\nexport type BadgeVariant = 'solid' | 'soft'\n/** What the badge means, in colour. */\nexport type BadgeTone = 'neutral' | 'accent' | 'danger' | 'success' | 'warning'\n/** The corner an overlaid badge is pinned to. */\nexport type BadgeOverlayPosition = 'top' | 'bottom'\n\ninterface BadgeProps {\n /**\n * How strongly the badge is painted: filled with the full colour (`solid`, the\n * default), or a tinted background with the text in the colour (`soft`), for a\n * count that should not draw the eye. A `dot` is always solid: with no content to\n * tint, a soft dot would be a pale disc barely distinguishable from the page.\n */\n variant?: BadgeVariant\n /**\n * The meaning the badge carries, expressed as a colour. A solid badge is filled\n * with it and its text adapts to stay readable; a soft one is tinted with it and\n * writes its text in it.\n */\n tone?: BadgeTone\n /**\n * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. On a\n * solid badge, browsers that support `contrast-color()` turn the text black or\n * white by themselves; everywhere else it falls back to white, so with a light\n * colour the contrast is yours to check. A soft badge derives its background and\n * text from that colour mixed with the theme's surface and text.\n */\n color?: string\n /**\n * The number to display. Anything above 99 is shown as \"99+\", so that a busy\n * counter cannot stretch the pill indefinitely.\n */\n count?: number\n /**\n * A single icon shown instead of a number: an icon name, or an explicit render\n * (the VIcon contract). It takes precedence over `count`, and is ignored when the\n * badge is a `dot`.\n */\n icon?: IconSource\n /**\n * Reduces the badge to a 10px dot with no content, the discreet way to signal\n * that something is new without saying how much.\n */\n dot?: boolean\n /**\n * Pins the badge to a corner of the target element instead of placing it beside\n * it. It does nothing when the badge has no target.\n */\n overlay?: boolean\n /**\n * Which corner an `overlay` badge is pinned to: the top one by default, the\n * bottom one for a marker that belongs at the foot of its target, such as a presence dot\n * under an avatar. The horizontal side follows the reading\n * direction and is not configurable. It does nothing without `overlay`.\n */\n overlayPosition?: BadgeOverlayPosition\n /**\n * Draws a 2px ring around the badge, in the colour of the surface behind it,\n * which detaches it from a busy target such as a picture. That colour defaults to\n * the page background and is named through `ringColor` on any other surface.\n */\n bordered?: boolean\n /**\n * The colour of the ring drawn by `bordered`. It defaults to the page background,\n * which is what makes the ring read as a gap around the badge; on another surface\n * (a card, a coloured banner), pass that surface's colour. It does nothing without\n * `bordered`.\n */\n ringColor?: string\n}\n\nconst props = withDefaults(defineProps<BadgeProps>(), {\n variant: 'solid',\n tone: 'accent',\n color: undefined,\n count: undefined,\n icon: undefined,\n overlayPosition: 'top',\n ringColor: undefined,\n // Written out although Vue casts an absent boolean to `false` on its own: left out,\n // `vue-component-meta` reports no default at all and the docs table's cell goes blank.\n dot: false,\n overlay: false,\n bordered: false,\n})\n\nconst slots = defineSlots<{\n /**\n * The element the badge belongs to. Without it the badge stands on its own; with\n * it, the badge is placed beside the element, or in its corner under `overlay`, and is\n * hidden from assistive technology: say the count in the element's own name.\n */\n default?(): unknown\n}>()\n\n// TRAP — a function read by the template, never a `computed`: `slots` is not reactive, so a\n// computed would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasTarget() {\n return slots.default !== undefined\n}\n\n/**\n * The counter as it is actually displayed: a whole number, zero or more, and anything past 99\n * becomes \"99+\". A count that is not a number (a division by a total not yet known) draws\n * nothing rather than the word \"NaN\".\n */\nconst displayCount = computed(() => {\n const count = props.count\n if (count === undefined || !Number.isFinite(count)) return undefined\n return count > 99 ? '99+' : Math.max(0, Math.trunc(count))\n})\n\n/*\n * The pill itself, written once. The template has two roots — one wrapping a target, one\n * standing alone — and both render the same badge; a functional component is the one way\n * to put the same markup in two places of a template without writing it twice.\n */\nconst Pill: FunctionalComponent = () =>\n h(\n 'span',\n {\n class: 'v-badge v-tone v-variant',\n 'data-variant': props.variant,\n 'data-tone': props.tone,\n 'data-custom': props.color !== undefined ? '' : undefined,\n 'data-dot': props.dot ? '' : undefined,\n 'data-icon-only': !props.dot && props.icon ? '' : undefined,\n 'data-bordered': props.bordered ? '' : undefined,\n style: [\n customColorStyle(props.color),\n props.ringColor !== undefined ? { '--badge-ring-color': props.ringColor } : undefined,\n ],\n },\n props.dot\n ? undefined\n : props.icon\n ? [h(VIcon, iconProps(props.icon))]\n : displayCount.value === undefined\n ? undefined\n : String(displayCount.value),\n )\n// TRAP — the empty list is not decoration. A functional component that declares no props lets\n// only `class`, `style` and listeners fall through, so a standalone badge, whose root this is,\n// would silently drop every other attribute a consumer set on VBadge (an `id`, a `data-*`).\nPill.props = []\n</script>\n\n<template>\n <span\n v-if=\"hasTarget()\"\n class=\"v-badge-host\"\n :data-overlay=\"overlay ? '' : undefined\"\n :data-overlay-position=\"overlayPosition\"\n >\n <slot />\n <Pill aria-hidden=\"true\" />\n </span>\n <Pill v-else />\n</template>\n\n<style>\n@layer vectis.components {\n .v-badge-host {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2); /* the space between the target and the badge, in inline mode */\n }\n\n /* The pill is painted by the shared `.v-variant` rules (styles/variants.css) over the\n tone table (`.v-tone`), both set in the template: `solid` is the tone's full colour,\n `soft` its tint with the text in the tone's own colour. A custom colour is the table's\n `[data-custom]` row, in styles/tones.css. */\n .v-badge {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n /* The colour of the `bordered` ring: a bet on the page background, the only one a\n component can make since CSS cannot read what its parent paints. The `ringColor`\n prop overrides it inline for a badge sitting on anything else. */\n --badge-ring-color: var(--vectis-color-surface);\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: var(--vectis-control-size-badge-h);\n /* A minimum width equal to the height, with a small padding on top, is what\n makes a one- or two-digit counter come out round rather than oval. */\n min-width: var(--vectis-control-size-badge-h);\n padding-inline: var(--vectis-space-1);\n border-radius: var(--vectis-radius-pill);\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-caption-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n }\n\n /* On a solid pill the text picks black or white against whatever the fill ended up\n with, where the function exists; elsewhere it keeps the table's `--tone-text-solid`.\n TRAP — the class is compounded to (0,3,0): `.v-variant[data-variant='solid']` sets\n `color` at (0,2,0) from another sheet, and a tie would be settled by sheet order. The\n soft pill keeps the tone's own text colour, which says the tone where black or white\n would not. */\n @supports (color: contrast-color(red)) {\n .v-badge.v-variant[data-variant='solid'] {\n color: contrast-color(var(--tone-bg-solid));\n }\n }\n\n /* A dot is always solid: with no content, a soft dot is a pale disc on the page, which a\n presence marker cannot afford. (0,3,0), above the soft variant's background. */\n .v-badge.v-variant[data-dot] {\n background: var(--tone-bg-solid);\n }\n\n .v-badge[data-icon-only] {\n padding: 0;\n }\n\n .v-badge[data-dot] {\n width: var(--vectis-control-size-badge-dot);\n height: var(--vectis-control-size-badge-dot);\n min-width: 0;\n padding: 0;\n }\n\n .v-badge[data-bordered] {\n box-shadow: 0 0 0 var(--vectis-control-size-badge-ring) var(--badge-ring-color);\n }\n\n /* The tuck-in offset is a ratio of the badge's own size, so it stays proportionate\n across the pill, the dot and the icon. Each half of it is held in its own\n variable: the corner and the reading direction each flip ONE axis, and writing\n the whole `translate` again in each branch would make the two multiply — four\n rules to keep in step by hand for two independent decisions. */\n .v-badge-host[data-overlay] > .v-badge {\n --badge-overlay-x: 25%;\n --badge-overlay-y: -25%;\n\n position: absolute;\n inset-block: 0 auto;\n inset-inline-end: 0;\n translate: var(--badge-overlay-x) var(--badge-overlay-y);\n /* TRAP — an overlaid badge deliberately leaves its target's box, so it lands on\n whatever sits beside it, and the host opens no stacking context of its own\n (`position: relative` with `z-index: auto`). Left at `auto` the badge is painted\n with the other positioned boxes in TREE ORDER, which means anything positioned\n after it covers it: inside a VButtonGroup, where every segment is positioned, the\n badge on one segment disappears under the borders of the next. The lift takes it\n past that step, and no further: the segment it leans into comes later in tree\n order, so that segment's focus ring, raised to this same level, is still drawn\n over the badge rather than under it. */\n z-index: 1;\n }\n\n .v-badge-host[data-overlay][data-overlay-position='bottom'] > .v-badge {\n --badge-overlay-y: 25%;\n\n inset-block: auto 0;\n }\n\n /* In a right-to-left direction the logical corner moves to the left, so the\n horizontal half of the translation has to flip with it — `translate` is a\n physical property and knows nothing about direction. */\n .v-badge-host[data-overlay]:dir(rtl) > .v-badge {\n --badge-overlay-x: -25%;\n }\n\n /* Forced colours repaint the background as Canvas and drop the ring, a shadow: the pill\n loses its shape, and a dot, which has nothing else, disappears altogether. The pill\n gets an edge, and the dot is filled in the text colour. */\n @media (forced-colors: active) {\n .v-badge {\n outline: var(--vectis-control-border-width) solid CanvasText;\n outline-offset: calc(-1 * var(--vectis-control-border-width));\n }\n\n .v-badge.v-variant[data-dot] {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkGA,IAAM,IAAQ,GAeR,IAAQ,EAAA;EAWd,SAAS,IAAY;GACnB,OAAO,EAAM,YAAY,KAAA;EAC3B;EAOA,IAAM,IAAe,QAAe;GAClC,IAAM,IAAQ,EAAM;GAChB,YAAU,KAAA,KAAa,CAAC,OAAO,SAAS,CAAK,IACjD,OAAO,IAAQ,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,CAAK,CAAC;EAC3D,CAAC,GAOK,UACJ,EACE,QACA;GACE,OAAO;GACP,gBAAgB,EAAM;GACtB,aAAa,EAAM;GACnB,eAAe,EAAM,UAAU,KAAA,IAAiB,KAAA,IAAL;GAC3C,YAAY,EAAM,MAAM,KAAK,KAAA;GAC7B,kBAAkB,CAAC,EAAM,OAAO,EAAM,OAAO,KAAK,KAAA;GAClD,iBAAiB,EAAM,WAAW,KAAK,KAAA;GACvC,OAAO,CACL,EAAiB,EAAM,KAAK,GAC5B,EAAM,cAAc,KAAA,IAAwD,KAAA,IAA5C,EAAE,sBAAsB,EAAM,UAAU,CAC1E;EACF,GACA,EAAM,MACF,KAAA,IACA,EAAM,OACJ,CAAC,EAAE,GAAO,EAAU,EAAM,IAAI,CAAC,CAAC,IAChC,EAAa,UAAU,KAAA,IACrB,KAAA,IACA,OAAO,EAAa,KAAK,CACnC;SAIF,EAAK,QAAQ,CAAC,aAKJ,EAAS,KAAA,EAAA,GADjB,EAQO,QAAA;;GANL,OAAM;GACL,gBAAc,EAAA,UAAO,KAAQ,KAAA;GAC7B,yBAAuB,EAAA;MAExB,EAAQ,EAAA,QAAA,SAAA,GACR,EAA2B,GAAA,EAArB,eAAY,OAAM,CAAA,CAAA,GAAA,GAAA,CAAA,MAAA,EAAA,GAE1B,EAAe,GAAA,EAAA,KAAA,EAAA,CAAA"}
1
+ {"version":3,"file":"VBadge.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VBadge/VBadge.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @core\n/**\n * Render the default slot once: inspect its VNodes to decide whether a noninteractive badge has\n * content.\n */\nimport { computed, h } from 'vue'\nimport type { FunctionalComponent } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\n\nimport { customColorStyle } from '../../utils/css'\n\n/** How strongly the badge is painted: the full colour, or a tint of it. */\nexport type BadgeVariant = 'solid' | 'soft'\n/** What the badge means, in colour. */\nexport type BadgeTone = 'neutral' | 'accent' | 'danger' | 'success' | 'warning'\n/** The corner an overlaid badge is pinned to. */\nexport type BadgeOverlayPosition = 'top' | 'bottom'\n\ninterface BadgeProps {\n /**\n * How strongly the badge is painted: filled with the full colour (`solid`, the default), or a\n * tinted background with the text in the colour (`soft`), for a count that should not draw\n * the eye.\n */\n variant?: BadgeVariant\n /**\n * The meaning the badge carries, expressed as a colour. A solid badge is filled\n * with it and its text adapts to stay readable; a soft one is tinted with it and\n * writes its text in it.\n */\n tone?: BadgeTone\n /**\n * A colour of your own (hex, CSS name or `oklch()`), which replaces the tone. On a solid\n * badge, browsers that support `contrast-color()` turn the text black or white by themselves;\n * everywhere else it falls back to white, so with a light colour the contrast is yours to\n * check.\n */\n color?: string\n /**\n * The number to display. Anything above 99 is shown as \"99+\", so that a busy\n * counter cannot stretch the pill indefinitely.\n */\n count?: number\n /**\n * A single icon shown instead of a number: an icon name, or an explicit render\n * (the VIcon contract). It takes precedence over `count`, and is ignored when the\n * badge is a `dot`.\n */\n icon?: IconSource\n /**\n * Reduces the badge to a 10px dot with no content, the discreet way to signal\n * that something is new without saying how much.\n */\n dot?: boolean\n /**\n * Pins the badge to a corner of the target element instead of placing it beside\n * it. It does nothing when the badge has no target.\n */\n overlay?: boolean\n /**\n * Which corner an `overlay` badge is pinned to: the top one by default, the bottom one for a\n * marker that belongs at the foot of its target, such as a presence dot under an avatar.\n */\n overlayPosition?: BadgeOverlayPosition\n /**\n * Draws a 2px ring around the badge, in the colour of the surface behind it, which detaches\n * it from a busy target such as a picture.\n */\n bordered?: boolean\n /** The colour of the ring drawn by `bordered`. */\n ringColor?: string\n}\n\nconst props = withDefaults(defineProps<BadgeProps>(), {\n variant: 'solid',\n tone: 'accent',\n color: undefined,\n count: undefined,\n icon: undefined,\n overlayPosition: 'top',\n ringColor: undefined,\n // Written out although Vue casts an absent boolean to `false` on its own: left out,\n // `vue-component-meta` reports no default at all and the docs table's cell goes blank.\n dot: false,\n overlay: false,\n bordered: false,\n})\n\nconst slots = defineSlots<{\n /** The element the badge belongs to. */\n default?(): unknown\n}>()\n\n// A function read by the template, never a `computed`: `slots` is not reactive, so a computed\n// would keep its first answer while a slot behind a `v-if` comes and goes.\nfunction hasTarget() {\n return slots.default !== undefined\n}\n\n/**\n * The counter as it is actually displayed: a whole number, zero or more, and anything past 99\n * becomes \"99+\".\n */\nconst displayCount = computed(() => {\n const count = props.count\n if (count === undefined || !Number.isFinite(count)) return undefined\n return count > 99 ? '99+' : Math.max(0, Math.trunc(count))\n})\n\n/*\n * The template has two roots; one wrapping a target, one standing alone; and both render the\n * same badge; a functional component is the one way to put the same markup in two places of a\n * template without writing it twice.\n */\nconst Pill: FunctionalComponent = () =>\n h(\n 'span',\n {\n class: 'v-badge v-tone v-variant',\n 'data-variant': props.variant,\n 'data-tone': props.tone,\n 'data-custom': props.color !== undefined ? '' : undefined,\n 'data-dot': props.dot ? '' : undefined,\n 'data-icon-only': !props.dot && props.icon ? '' : undefined,\n 'data-bordered': props.bordered ? '' : undefined,\n style: [\n customColorStyle(props.color),\n props.ringColor !== undefined ? { '--badge-ring-color': props.ringColor } : undefined,\n ],\n },\n props.dot\n ? undefined\n : props.icon\n ? [h(VIcon, iconProps(props.icon))]\n : displayCount.value === undefined\n ? undefined\n : String(displayCount.value),\n )\n// The empty list is not decoration. A functional component that declares no props lets only\n// `class`, `style` and listeners fall through, so a standalone badge, whose root this is, would\n// silently drop every other attribute a consumer set on VBadge (an `id`, a `data-*`).\nPill.props = []\n</script>\n\n<template>\n <span\n v-if=\"hasTarget()\"\n class=\"v-badge-host\"\n :data-overlay=\"overlay ? '' : undefined\"\n :data-overlay-position=\"overlayPosition\"\n >\n <slot />\n <Pill aria-hidden=\"true\" />\n </span>\n <Pill v-else />\n</template>\n\n<style>\n@layer vectis.components {\n .v-badge-host {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n }\n\n /* The pill is painted by the shared `.v-variant` rules (styles/variants.css) over the\n tone table (`.v-tone`), both set in the template: `solid` is the tone's full colour,\n `soft` its tint with the text in the tone's own colour. A custom colour is the table's\n `[data-custom]` row, in styles/tones.css. */\n .v-badge {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n /* The colour of the `bordered` ring: a bet on the page background, the only one a\n component can make since CSS cannot read what its parent paints. The `ringColor`\n prop overrides it inline for a badge sitting on anything else. */\n --badge-ring-color: var(--vectis-color-surface);\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: var(--vectis-control-size-badge-h);\n /* A minimum width equal to the height, with a small padding on top, is what\n makes a one- or two-digit counter come out round rather than oval. */\n min-width: var(--vectis-control-size-badge-h);\n padding-inline: var(--vectis-space-1);\n border-radius: var(--vectis-radius-pill);\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-caption-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n }\n\n /*\n * The class is compounded to (0,3,0): `.v-variant[data-variant='solid']` sets `color` at\n * (0,2,0) from another sheet, and a tie would be settled by sheet order. The soft pill keeps\n * the tone's own text colour, which says the tone where black or white would not.\n */\n @supports (color: contrast-color(red)) {\n .v-badge.v-variant[data-variant='solid'] {\n color: contrast-color(var(--tone-bg-solid));\n }\n }\n\n /* A dot is always solid: with no content, a soft dot is a pale disc on the page, which a\n presence marker cannot afford. (0,3,0), above the soft variant's background. */\n .v-badge.v-variant[data-dot] {\n background: var(--tone-bg-solid);\n }\n\n .v-badge[data-icon-only] {\n padding: 0;\n }\n\n .v-badge[data-dot] {\n width: var(--vectis-control-size-badge-dot);\n height: var(--vectis-control-size-badge-dot);\n min-width: 0;\n padding: 0;\n }\n\n .v-badge[data-bordered] {\n box-shadow: 0 0 0 var(--vectis-control-size-badge-ring) var(--badge-ring-color);\n }\n\n /*\n * The tuck-in offset is a ratio of the badge's own size, so it stays proportionate across the\n * pill, the dot and the icon. Each half of it is held in its own variable: the corner and the\n * reading direction each flip ONE axis, and writing the whole `translate` again in each\n * branch would make the two multiply; four rules to keep in step by hand for two independent\n * decisions.\n */\n .v-badge-host[data-overlay] > .v-badge {\n --badge-overlay-x: 25%;\n --badge-overlay-y: -25%;\n\n position: absolute;\n inset-block: 0 auto;\n inset-inline-end: 0;\n translate: var(--badge-overlay-x) var(--badge-overlay-y);\n /*\n * Raise overlaid badges above later positioned siblings; tree order alone lets neighbouring\n * segments cover them.\n */\n z-index: 1;\n }\n\n .v-badge-host[data-overlay][data-overlay-position='bottom'] > .v-badge {\n --badge-overlay-y: 25%;\n\n inset-block: auto 0;\n }\n\n /*\n * In a right-to-left direction the logical corner moves to the left, so the horizontal half\n * of the translation has to flip with it; `translate` is a physical property and knows\n * nothing about direction.\n */\n .v-badge-host[data-overlay]:dir(rtl) > .v-badge {\n --badge-overlay-x: -25%;\n }\n\n /* Forced colours repaint the background as Canvas and drop the ring, a shadow: the pill\n loses its shape, and a dot, which has nothing else, disappears altogether. The pill\n gets an edge, and the dot is filled in the text colour. */\n @media (forced-colors: active) {\n .v-badge {\n outline: var(--vectis-control-border-width) solid CanvasText;\n outline-offset: calc(-1 * var(--vectis-control-border-width));\n }\n\n .v-badge.v-variant[data-dot] {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6EA,IAAM,IAAQ,GAeR,IAAQ,EAAA;EAOd,SAAS,IAAY;GACnB,OAAO,EAAM,YAAY,KAAA;EAC3B;EAMA,IAAM,IAAe,QAAe;GAClC,IAAM,IAAQ,EAAM;GAChB,YAAU,KAAA,KAAa,CAAC,OAAO,SAAS,CAAK,IACjD,OAAO,IAAQ,KAAK,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,CAAK,CAAC;EAC3D,CAAC,GAOK,UACJ,EACE,QACA;GACE,OAAO;GACP,gBAAgB,EAAM;GACtB,aAAa,EAAM;GACnB,eAAe,EAAM,UAAU,KAAA,IAAiB,KAAA,IAAL;GAC3C,YAAY,EAAM,MAAM,KAAK,KAAA;GAC7B,kBAAkB,CAAC,EAAM,OAAO,EAAM,OAAO,KAAK,KAAA;GAClD,iBAAiB,EAAM,WAAW,KAAK,KAAA;GACvC,OAAO,CACL,EAAiB,EAAM,KAAK,GAC5B,EAAM,cAAc,KAAA,IAAwD,KAAA,IAA5C,EAAE,sBAAsB,EAAM,UAAU,CAC1E;EACF,GACA,EAAM,MACF,KAAA,IACA,EAAM,OACJ,CAAC,EAAE,GAAO,EAAU,EAAM,IAAI,CAAC,CAAC,IAChC,EAAa,UAAU,KAAA,IACrB,KAAA,IACA,OAAO,EAAa,KAAK,CACnC;SAIF,EAAK,QAAQ,CAAC,aAKJ,EAAS,KAAA,EAAA,GADjB,EAQO,QAAA;;GANL,OAAM;GACL,gBAAc,EAAA,UAAO,KAAQ,KAAA;GAC7B,yBAAuB,EAAA;MAExB,EAAQ,EAAA,QAAA,SAAA,GACR,EAA2B,GAAA,EAArB,eAAY,OAAM,CAAA,CAAA,GAAA,GAAA,CAAA,MAAA,EAAA,GAE1B,EAAe,GAAA,EAAA,KAAA,EAAA,CAAA"}
@@ -21,11 +21,7 @@ interface BreadcrumbProps {
21
21
  * wording of the DS dictionary, in the current language.
22
22
  */
23
23
  label?: string;
24
- /**
25
- * The address of the page being displayed. The segment whose `href` matches it is
26
- * the current one, even when it is folded into the menu. A trailing slash, a query and
27
- * a hash on either side make no difference, so a router's full path can be passed as is.
28
- */
24
+ /** The address of the page being displayed. */
29
25
  currentPath?: string;
30
26
  /**
31
27
  * The icon drawn between two segments: an icon name, or an explicit render,
@@ -33,10 +29,8 @@ interface BreadcrumbProps {
33
29
  */
34
30
  separatorIcon?: IconSource;
35
31
  /**
36
- * The length past which the trail folds: only the first segment, an "…" button and
37
- * the last two remain, the button opening a menu that lists the hidden segments
38
- * alone. Below 3 there would be nothing left to fold, so 3 is the effective
39
- * minimum.
32
+ * The length past which the trail folds: only the first segment, an "…" button and the last
33
+ * two remain, the button opening a menu that lists the hidden segments alone.
40
34
  */
41
35
  maxItems?: number;
42
36
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"VBreadcrumb.js","names":[],"sources":["../../../src/components/VBreadcrumb/VBreadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @ssr @core\n/**\n * The path leading to the page being read, from the broadest section down to the current\n * one: a `<nav>` holding an `<ol>`, built entirely from the `items` prop. Nothing is read\n * from the browser, so the server and the client render the same thing.\n *\n * The separators are decorative `aria-hidden` icons, and the first is removed in CSS rather\n * than by a condition in the markup. The current page is found by comparing each address\n * with `currentPath`, carries `aria-current=\"page\"` and stays a working link, as the ARIA\n * practices recommend. Past `maxItems`, the middle segments fold into a VMenu behind an \"…\".\n */\n\nimport { computed } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { more_horiz as moreHorizIcon } from '../VIcon/icons/more_horiz'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\nexport interface BreadcrumbItem {\n /** The text shown for this segment. */\n label: string\n /**\n * Where the segment leads. It is also the value compared with `currentPath` to\n * decide which segment is the current page.\n */\n href: string\n /**\n * An icon placed before the label: an icon name, or an explicit render\n * (`{ src: '/logo.svg' }`, `{ component }`…). A string is always a name.\n */\n icon?: IconSource\n}\n\ninterface BreadcrumbProps {\n /** The segments of the trail, ordered from the most general down to the deepest. */\n items: BreadcrumbItem[]\n /**\n * The name screen readers announce for this navigation. It falls back to the\n * wording of the DS dictionary, in the current language.\n */\n label?: string\n /**\n * The address of the page being displayed. The segment whose `href` matches it is\n * the current one, even when it is folded into the menu. A trailing slash, a query and\n * a hash on either side make no difference, so a router's full path can be passed as is.\n */\n currentPath?: string\n /**\n * The icon drawn between two segments: an icon name, or an explicit render,\n * exactly like an item's `icon`. It is mirrored in a right-to-left page.\n */\n separatorIcon?: IconSource\n /**\n * The length past which the trail folds: only the first segment, an \"…\" button and\n * the last two remain, the button opening a menu that lists the hidden segments\n * alone. Below 3 there would be nothing left to fold, so 3 is the effective\n * minimum.\n */\n maxItems?: number\n /**\n * The name screen readers announce for the \"…\" button. It falls back to the\n * wording of the DS dictionary.\n */\n ellipsisLabel?: string\n}\n\nconst props = withDefaults(defineProps<BreadcrumbProps>(), {\n label: undefined,\n currentPath: undefined,\n separatorIcon: () => chevronRightIcon,\n maxItems: undefined,\n ellipsisLabel: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.breadcrumb.label)\nconst resolvedEllipsisLabel = computed(() => props.ellipsisLabel ?? m.value.breadcrumb.ellipsis)\n\n/**\n * Drops the query, the hash and a trailing slash, so that `/docs`, `/docs/` and a\n * router's full path `/docs?tab=2#intro` compare as the same page; the root `/` is left\n * alone. Pure string handling, hence safe to run on the server.\n */\nfunction normalize(path: string): string {\n const bare = path.replace(/[?#].*$/, '')\n return bare.length > 1 && bare.endsWith('/') ? bare.slice(0, -1) : bare\n}\n\n// Normalized once per change of the prop rather than once per segment on every render.\nconst normalizedCurrent = computed(() =>\n props.currentPath === undefined ? undefined : normalize(props.currentPath),\n)\n\n// @a11y\n// This is what feeds `aria-current=\"page\"`, the only marker telling assistive technology\n// where the trail ends, the folded segments of the menu included.\nfunction isCurrent(item: BreadcrumbItem): boolean {\n return normalizedCurrent.value !== undefined && normalize(item.href) === normalizedCurrent.value\n}\n\nconst truncated = computed(\n () => props.maxItems !== undefined && props.items.length > Math.max(props.maxItems, 3),\n)\n/** The segments folded into the menu: everything between the first and the last two. */\nconst hiddenItems = computed(() => (truncated.value ? props.items.slice(1, -2) : []))\n/**\n * The segments actually rendered in the list — when the trail is folded, the first\n * one followed by the last two.\n */\nconst visibleItems = computed(() =>\n truncated.value ? [props.items[0] as BreadcrumbItem, ...props.items.slice(-2)] : props.items,\n)\n</script>\n\n<template>\n <nav class=\"v-breadcrumb\" :aria-label=\"ariaLabel\">\n <ol class=\"v-breadcrumb-list\">\n <template v-for=\"(item, index) in visibleItems\" :key=\"item.href\">\n <li v-if=\"truncated && index === 1\" class=\"v-breadcrumb-item v-breadcrumb-ellipsis\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <VMenu>\n <template #trigger=\"{ triggerProps }\">\n <VIconButton\n size=\"sm\"\n :label=\"resolvedEllipsisLabel\"\n :icon=\"moreHorizIcon\"\n v-bind=\"triggerProps\"\n />\n </template>\n <VMenuItem\n v-for=\"hidden in hiddenItems\"\n :key=\"hidden.href\"\n :href=\"hidden.href\"\n :label=\"hidden.label\"\n :icon-start=\"hidden.icon\"\n :selected=\"isCurrent(hidden)\"\n :aria-current=\"isCurrent(hidden) ? 'page' : undefined\"\n />\n </VMenu>\n </li>\n <li class=\"v-breadcrumb-item\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <a\n class=\"v-breadcrumb-link\"\n :href=\"item.href\"\n :aria-current=\"isCurrent(item) ? 'page' : undefined\"\n >\n <VIcon v-if=\"item.icon\" v-bind=\"iconProps(item.icon)\" />\n {{ item.label }}\n </a>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n\n<style>\n@layer vectis.components {\n /* The icon context is set here rather than through `.v-control`, which is the usual\n route: a trail has no `size` prop and no height to speak of, so the size scale would\n bring a table of five steps to pick one value from. */\n .v-breadcrumb {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n }\n\n .v-breadcrumb-list {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--vectis-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-body-md-size);\n }\n\n .v-breadcrumb-item {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n color: var(--vectis-color-text-muted);\n }\n\n /* TRAP — every item renders its own leading separator and the first one is hidden HERE,\n which is what keeps the template free of an index test. Delete this rule and the trail\n opens on a stray chevron; both child combinators are load-bearing too, so wrapping an\n item or the list in anything puts that chevron straight back. */\n .v-breadcrumb-list > .v-breadcrumb-item:first-child > .v-breadcrumb-separator {\n display: none;\n }\n\n .v-breadcrumb-separator {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The corner is only ever seen on the focus ring, which follows it. It is the control\n radius capped at half a line, the row recipe of VSideNavigationItem with the line\n standing in for a control height the link does not have: a label that wraps keeps\n the corner of a one-line one under a pill override. */\n .v-breadcrumb-link {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n text-decoration: none;\n border-radius: min(var(--vectis-radius-interactive), 0.5lh);\n transition: color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-breadcrumb-link:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n .v-breadcrumb-link:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n .v-breadcrumb-link[aria-current='page'] {\n color: var(--vectis-color-text);\n font-weight: var(--vectis-text-label-weight);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-breadcrumb-link {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VBreadcrumb.js","names":[],"sources":["../../../src/components/VBreadcrumb/VBreadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @ssr @core\n/**\n * Mark the matching link aria-current without disabling it. CSS hides the first separator so\n * SSR needs no child registry.\n */\n\nimport { computed } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { more_horiz as moreHorizIcon } from '../VIcon/icons/more_horiz'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\nexport interface BreadcrumbItem {\n /** The text shown for this segment. */\n label: string\n /**\n * Where the segment leads. It is also the value compared with `currentPath` to\n * decide which segment is the current page.\n */\n href: string\n /**\n * An icon placed before the label: an icon name, or an explicit render\n * (`{ src: '/logo.svg' }`, `{ component }`…). A string is always a name.\n */\n icon?: IconSource\n}\n\ninterface BreadcrumbProps {\n /** The segments of the trail, ordered from the most general down to the deepest. */\n items: BreadcrumbItem[]\n /**\n * The name screen readers announce for this navigation. It falls back to the\n * wording of the DS dictionary, in the current language.\n */\n label?: string\n /** The address of the page being displayed. */\n currentPath?: string\n /**\n * The icon drawn between two segments: an icon name, or an explicit render,\n * exactly like an item's `icon`. It is mirrored in a right-to-left page.\n */\n separatorIcon?: IconSource\n /**\n * The length past which the trail folds: only the first segment, an \"…\" button and the last\n * two remain, the button opening a menu that lists the hidden segments alone.\n */\n maxItems?: number\n /**\n * The name screen readers announce for the \"…\" button. It falls back to the\n * wording of the DS dictionary.\n */\n ellipsisLabel?: string\n}\n\nconst props = withDefaults(defineProps<BreadcrumbProps>(), {\n label: undefined,\n currentPath: undefined,\n separatorIcon: () => chevronRightIcon,\n maxItems: undefined,\n ellipsisLabel: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.breadcrumb.label)\nconst resolvedEllipsisLabel = computed(() => props.ellipsisLabel ?? m.value.breadcrumb.ellipsis)\n\n/**\n * Drops the query, the hash and a trailing slash, so that `/docs`, `/docs/` and a router's full\n * path `/docs?tab=2#intro` compare as the same page; the root `/` is left alone.\n */\nfunction normalize(path: string): string {\n const bare = path.replace(/[?#].*$/, '')\n return bare.length > 1 && bare.endsWith('/') ? bare.slice(0, -1) : bare\n}\n\nconst normalizedCurrent = computed(() =>\n props.currentPath === undefined ? undefined : normalize(props.currentPath),\n)\n\n// @a11y\n// This is what feeds `aria-current=\"page\"`, the only marker telling assistive technology\n// where the trail ends, the folded segments of the menu included.\nfunction isCurrent(item: BreadcrumbItem): boolean {\n return normalizedCurrent.value !== undefined && normalize(item.href) === normalizedCurrent.value\n}\n\nconst truncated = computed(\n () => props.maxItems !== undefined && props.items.length > Math.max(props.maxItems, 3),\n)\n/** The segments folded into the menu: everything between the first and the last two. */\nconst hiddenItems = computed(() => (truncated.value ? props.items.slice(1, -2) : []))\n/**\n * The segments actually rendered in the list; when the trail is folded, the first one followed\n * by the last two.\n */\nconst visibleItems = computed(() =>\n truncated.value ? [props.items[0] as BreadcrumbItem, ...props.items.slice(-2)] : props.items,\n)\n</script>\n\n<template>\n <nav class=\"v-breadcrumb\" :aria-label=\"ariaLabel\">\n <ol class=\"v-breadcrumb-list\">\n <template v-for=\"(item, index) in visibleItems\" :key=\"item.href\">\n <li v-if=\"truncated && index === 1\" class=\"v-breadcrumb-item v-breadcrumb-ellipsis\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <VMenu>\n <template #trigger=\"{ triggerProps }\">\n <VIconButton\n size=\"sm\"\n :label=\"resolvedEllipsisLabel\"\n :icon=\"moreHorizIcon\"\n v-bind=\"triggerProps\"\n />\n </template>\n <VMenuItem\n v-for=\"hidden in hiddenItems\"\n :key=\"hidden.href\"\n :href=\"hidden.href\"\n :label=\"hidden.label\"\n :icon-start=\"hidden.icon\"\n :selected=\"isCurrent(hidden)\"\n :aria-current=\"isCurrent(hidden) ? 'page' : undefined\"\n />\n </VMenu>\n </li>\n <li class=\"v-breadcrumb-item\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <a\n class=\"v-breadcrumb-link\"\n :href=\"item.href\"\n :aria-current=\"isCurrent(item) ? 'page' : undefined\"\n >\n <VIcon v-if=\"item.icon\" v-bind=\"iconProps(item.icon)\" />\n {{ item.label }}\n </a>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n\n<style>\n@layer vectis.components {\n /* The icon context is set here rather than through `.v-control`, which is the usual\n route: a trail has no `size` prop and no height to speak of, so the size scale would\n bring a table of five steps to pick one value from. */\n .v-breadcrumb {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n }\n\n .v-breadcrumb-list {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--vectis-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-body-md-size);\n }\n\n .v-breadcrumb-item {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n color: var(--vectis-color-text-muted);\n }\n\n /*\n * Every item renders its own leading separator and the first one is hidden here, which keeps\n * the template free of an index test. Delete this rule and the trail opens on a stray\n * chevron; both child combinators are load-bearing too, so wrapping an item or the list in\n * anything puts that chevron straight back.\n */\n .v-breadcrumb-list > .v-breadcrumb-item:first-child > .v-breadcrumb-separator {\n display: none;\n }\n\n .v-breadcrumb-separator {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The corner is only ever seen on the focus ring, which follows it. It is the control\n radius capped at half a line, the row recipe of VSideNavigationItem with the line\n standing in for a control height the link does not have: a label that wraps keeps\n the corner of a one-line one under a pill override. */\n .v-breadcrumb-link {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n text-decoration: none;\n border-radius: min(var(--vectis-radius-interactive), 0.5lh);\n transition: color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-breadcrumb-link:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n .v-breadcrumb-link:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n .v-breadcrumb-link[aria-current='page'] {\n color: var(--vectis-color-text);\n font-weight: var(--vectis-text-label-weight);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-breadcrumb-link {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"VBreadcrumb.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VBreadcrumb/VBreadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @ssr @core\n/**\n * The path leading to the page being read, from the broadest section down to the current\n * one: a `<nav>` holding an `<ol>`, built entirely from the `items` prop. Nothing is read\n * from the browser, so the server and the client render the same thing.\n *\n * The separators are decorative `aria-hidden` icons, and the first is removed in CSS rather\n * than by a condition in the markup. The current page is found by comparing each address\n * with `currentPath`, carries `aria-current=\"page\"` and stays a working link, as the ARIA\n * practices recommend. Past `maxItems`, the middle segments fold into a VMenu behind an \"…\".\n */\n\nimport { computed } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { more_horiz as moreHorizIcon } from '../VIcon/icons/more_horiz'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\nexport interface BreadcrumbItem {\n /** The text shown for this segment. */\n label: string\n /**\n * Where the segment leads. It is also the value compared with `currentPath` to\n * decide which segment is the current page.\n */\n href: string\n /**\n * An icon placed before the label: an icon name, or an explicit render\n * (`{ src: '/logo.svg' }`, `{ component }`…). A string is always a name.\n */\n icon?: IconSource\n}\n\ninterface BreadcrumbProps {\n /** The segments of the trail, ordered from the most general down to the deepest. */\n items: BreadcrumbItem[]\n /**\n * The name screen readers announce for this navigation. It falls back to the\n * wording of the DS dictionary, in the current language.\n */\n label?: string\n /**\n * The address of the page being displayed. The segment whose `href` matches it is\n * the current one, even when it is folded into the menu. A trailing slash, a query and\n * a hash on either side make no difference, so a router's full path can be passed as is.\n */\n currentPath?: string\n /**\n * The icon drawn between two segments: an icon name, or an explicit render,\n * exactly like an item's `icon`. It is mirrored in a right-to-left page.\n */\n separatorIcon?: IconSource\n /**\n * The length past which the trail folds: only the first segment, an \"…\" button and\n * the last two remain, the button opening a menu that lists the hidden segments\n * alone. Below 3 there would be nothing left to fold, so 3 is the effective\n * minimum.\n */\n maxItems?: number\n /**\n * The name screen readers announce for the \"…\" button. It falls back to the\n * wording of the DS dictionary.\n */\n ellipsisLabel?: string\n}\n\nconst props = withDefaults(defineProps<BreadcrumbProps>(), {\n label: undefined,\n currentPath: undefined,\n separatorIcon: () => chevronRightIcon,\n maxItems: undefined,\n ellipsisLabel: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.breadcrumb.label)\nconst resolvedEllipsisLabel = computed(() => props.ellipsisLabel ?? m.value.breadcrumb.ellipsis)\n\n/**\n * Drops the query, the hash and a trailing slash, so that `/docs`, `/docs/` and a\n * router's full path `/docs?tab=2#intro` compare as the same page; the root `/` is left\n * alone. Pure string handling, hence safe to run on the server.\n */\nfunction normalize(path: string): string {\n const bare = path.replace(/[?#].*$/, '')\n return bare.length > 1 && bare.endsWith('/') ? bare.slice(0, -1) : bare\n}\n\n// Normalized once per change of the prop rather than once per segment on every render.\nconst normalizedCurrent = computed(() =>\n props.currentPath === undefined ? undefined : normalize(props.currentPath),\n)\n\n// @a11y\n// This is what feeds `aria-current=\"page\"`, the only marker telling assistive technology\n// where the trail ends, the folded segments of the menu included.\nfunction isCurrent(item: BreadcrumbItem): boolean {\n return normalizedCurrent.value !== undefined && normalize(item.href) === normalizedCurrent.value\n}\n\nconst truncated = computed(\n () => props.maxItems !== undefined && props.items.length > Math.max(props.maxItems, 3),\n)\n/** The segments folded into the menu: everything between the first and the last two. */\nconst hiddenItems = computed(() => (truncated.value ? props.items.slice(1, -2) : []))\n/**\n * The segments actually rendered in the list — when the trail is folded, the first\n * one followed by the last two.\n */\nconst visibleItems = computed(() =>\n truncated.value ? [props.items[0] as BreadcrumbItem, ...props.items.slice(-2)] : props.items,\n)\n</script>\n\n<template>\n <nav class=\"v-breadcrumb\" :aria-label=\"ariaLabel\">\n <ol class=\"v-breadcrumb-list\">\n <template v-for=\"(item, index) in visibleItems\" :key=\"item.href\">\n <li v-if=\"truncated && index === 1\" class=\"v-breadcrumb-item v-breadcrumb-ellipsis\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <VMenu>\n <template #trigger=\"{ triggerProps }\">\n <VIconButton\n size=\"sm\"\n :label=\"resolvedEllipsisLabel\"\n :icon=\"moreHorizIcon\"\n v-bind=\"triggerProps\"\n />\n </template>\n <VMenuItem\n v-for=\"hidden in hiddenItems\"\n :key=\"hidden.href\"\n :href=\"hidden.href\"\n :label=\"hidden.label\"\n :icon-start=\"hidden.icon\"\n :selected=\"isCurrent(hidden)\"\n :aria-current=\"isCurrent(hidden) ? 'page' : undefined\"\n />\n </VMenu>\n </li>\n <li class=\"v-breadcrumb-item\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <a\n class=\"v-breadcrumb-link\"\n :href=\"item.href\"\n :aria-current=\"isCurrent(item) ? 'page' : undefined\"\n >\n <VIcon v-if=\"item.icon\" v-bind=\"iconProps(item.icon)\" />\n {{ item.label }}\n </a>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n\n<style>\n@layer vectis.components {\n /* The icon context is set here rather than through `.v-control`, which is the usual\n route: a trail has no `size` prop and no height to speak of, so the size scale would\n bring a table of five steps to pick one value from. */\n .v-breadcrumb {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n }\n\n .v-breadcrumb-list {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--vectis-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-body-md-size);\n }\n\n .v-breadcrumb-item {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n color: var(--vectis-color-text-muted);\n }\n\n /* TRAP — every item renders its own leading separator and the first one is hidden HERE,\n which is what keeps the template free of an index test. Delete this rule and the trail\n opens on a stray chevron; both child combinators are load-bearing too, so wrapping an\n item or the list in anything puts that chevron straight back. */\n .v-breadcrumb-list > .v-breadcrumb-item:first-child > .v-breadcrumb-separator {\n display: none;\n }\n\n .v-breadcrumb-separator {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The corner is only ever seen on the focus ring, which follows it. It is the control\n radius capped at half a line, the row recipe of VSideNavigationItem with the line\n standing in for a control height the link does not have: a label that wraps keeps\n the corner of a one-line one under a pill override. */\n .v-breadcrumb-link {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n text-decoration: none;\n border-radius: min(var(--vectis-radius-interactive), 0.5lh);\n transition: color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-breadcrumb-link:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n .v-breadcrumb-link:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n .v-breadcrumb-link[aria-current='page'] {\n color: var(--vectis-color-text);\n font-weight: var(--vectis-text-label-weight);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-breadcrumb-link {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EA2EA,IAAM,IAAQ,GAQR,IAAI,EAAY,GAChB,IAAY,QAAmB,EAAM,SAAS,EAAE,MAAM,WAAW,KAAK,GACtE,IAAwB,QAAe,EAAM,iBAAiB,EAAE,MAAM,WAAW,QAAQ;EAO/F,SAAS,EAAU,GAAsB;GACvC,IAAM,IAAO,EAAK,QAAQ,WAAW,EAAE;GACvC,OAAO,EAAK,SAAS,KAAK,EAAK,SAAS,GAAG,IAAI,EAAK,MAAM,GAAG,EAAE,IAAI;EACrE;EAGA,IAAM,IAAoB,QACxB,EAAM,gBAAgB,KAAA,IAAY,KAAA,IAAY,EAAU,EAAM,WAAW,CAC3E;EAKA,SAAS,EAAU,GAA+B;GAChD,OAAO,EAAkB,UAAU,KAAA,KAAa,EAAU,EAAK,IAAI,MAAM,EAAkB;EAC7F;EAEA,IAAM,IAAY,QACV,EAAM,aAAa,KAAA,KAAa,EAAM,MAAM,SAAS,KAAK,IAAI,EAAM,UAAU,CAAC,CACvF,GAEM,IAAc,QAAgB,EAAU,QAAQ,EAAM,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,CAAE,GAK9E,IAAe,QACnB,EAAU,QAAQ,CAAC,EAAM,MAAM,IAAsB,GAAG,EAAM,MAAM,MAAM,EAAE,CAAC,IAAI,EAAM,KACzF;yBAIE,EAsCM,OAAA;GAtCD,OAAM;GAAgB,cAAY,EAAA,CAAA;MACrC,EAoCK,MApCL,GAoCK,EAAA,EAAA,EAAA,GAnCH,EAkCW,GAAA,MAAA,EAlCuB,EAAA,QAAhB,GAAM,wBAA8B,EAAK,KAAA,GAAA,CAC/C,EAAA,SAAa,MAAK,KAAA,EAAA,GAA5B,EAqBK,MArBL,GAqBK,CApBH,EAAmF,GAAnF,EAAmF,EAA5E,OAAM,yBAAwB,GAAA,EAAA,SAAA,GAAA,GAAS,EAAA,CAAA,CAAS,CAAC,EAAA,aAAa,GAAA,EAAG,UAAA,GAAQ,CAAA,GAAA,MAAA,EAAA,GAChF,EAkBQ,GAAA,MAAA;GAjBK,SAAO,GAMd,EANkB,sBAAY,CAChC,EAKE,GALF,EAKE;IAJA,MAAK;IACJ,OAAO,EAAA;IACP,MAAM,EAAA,CAAA;uBACC,CAAY,GAAA,MAAA,IAAA,CAAA,SAAA,MAAA,CAAA,CAAA,CAAA;oBAIO,EAAA,EAAA,EAAA,GAD/B,EAQE,GAAA,MAAA,EAPiB,EAAA,QAAV,YADT,EAQE,GAAA;IANC,KAAK,EAAO;IACZ,MAAM,EAAO;IACb,OAAO,EAAO;IACd,cAAY,EAAO;IACnB,UAAU,EAAU,CAAM;IAC1B,gBAAc,EAAU,CAAM,IAAA,SAAa,KAAA;;;;;;;;;qBAIlD,EAUK,MAVL,GAUK,CATH,EAAmF,GAAnF,EAAmF,EAA5E,OAAM,yBAAwB,GAAA,EAAA,SAAA,GAAA,GAAS,EAAA,CAAA,CAAS,CAAC,EAAA,aAAa,GAAA,EAAG,UAAA,GAAQ,CAAA,GAAA,MAAA,EAAA,GAChF,EAOI,KAAA;GANF,OAAM;GACL,MAAM,EAAK;GACX,gBAAc,EAAU,CAAI,IAAA,SAAa,KAAA;MAE7B,EAAK,QAAA,EAAA,GAAlB,EAAwD,GAAxD,EAAwD;;;KAAxB,EAAA,CAAA,CAAS,CAAC,EAAK,IAAI,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,GAAA,EAAK,MACxD,EAAG,EAAK,KAAK,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA"}
1
+ {"version":3,"file":"VBreadcrumb.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VBreadcrumb/VBreadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @ssr @core\n/**\n * Mark the matching link aria-current without disabling it. CSS hides the first separator so\n * SSR needs no child registry.\n */\n\nimport { computed } from 'vue'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { chevron_right as chevronRightIcon } from '../VIcon/icons/chevron_right'\nimport { more_horiz as moreHorizIcon } from '../VIcon/icons/more_horiz'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport VMenu from '../VMenu/VMenu.vue'\nimport VMenuItem from '../VMenu/VMenuItem.vue'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages } from '../../i18n/state'\n\nexport interface BreadcrumbItem {\n /** The text shown for this segment. */\n label: string\n /**\n * Where the segment leads. It is also the value compared with `currentPath` to\n * decide which segment is the current page.\n */\n href: string\n /**\n * An icon placed before the label: an icon name, or an explicit render\n * (`{ src: '/logo.svg' }`, `{ component }`…). A string is always a name.\n */\n icon?: IconSource\n}\n\ninterface BreadcrumbProps {\n /** The segments of the trail, ordered from the most general down to the deepest. */\n items: BreadcrumbItem[]\n /**\n * The name screen readers announce for this navigation. It falls back to the\n * wording of the DS dictionary, in the current language.\n */\n label?: string\n /** The address of the page being displayed. */\n currentPath?: string\n /**\n * The icon drawn between two segments: an icon name, or an explicit render,\n * exactly like an item's `icon`. It is mirrored in a right-to-left page.\n */\n separatorIcon?: IconSource\n /**\n * The length past which the trail folds: only the first segment, an \"…\" button and the last\n * two remain, the button opening a menu that lists the hidden segments alone.\n */\n maxItems?: number\n /**\n * The name screen readers announce for the \"…\" button. It falls back to the\n * wording of the DS dictionary.\n */\n ellipsisLabel?: string\n}\n\nconst props = withDefaults(defineProps<BreadcrumbProps>(), {\n label: undefined,\n currentPath: undefined,\n separatorIcon: () => chevronRightIcon,\n maxItems: undefined,\n ellipsisLabel: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.breadcrumb.label)\nconst resolvedEllipsisLabel = computed(() => props.ellipsisLabel ?? m.value.breadcrumb.ellipsis)\n\n/**\n * Drops the query, the hash and a trailing slash, so that `/docs`, `/docs/` and a router's full\n * path `/docs?tab=2#intro` compare as the same page; the root `/` is left alone.\n */\nfunction normalize(path: string): string {\n const bare = path.replace(/[?#].*$/, '')\n return bare.length > 1 && bare.endsWith('/') ? bare.slice(0, -1) : bare\n}\n\nconst normalizedCurrent = computed(() =>\n props.currentPath === undefined ? undefined : normalize(props.currentPath),\n)\n\n// @a11y\n// This is what feeds `aria-current=\"page\"`, the only marker telling assistive technology\n// where the trail ends, the folded segments of the menu included.\nfunction isCurrent(item: BreadcrumbItem): boolean {\n return normalizedCurrent.value !== undefined && normalize(item.href) === normalizedCurrent.value\n}\n\nconst truncated = computed(\n () => props.maxItems !== undefined && props.items.length > Math.max(props.maxItems, 3),\n)\n/** The segments folded into the menu: everything between the first and the last two. */\nconst hiddenItems = computed(() => (truncated.value ? props.items.slice(1, -2) : []))\n/**\n * The segments actually rendered in the list; when the trail is folded, the first one followed\n * by the last two.\n */\nconst visibleItems = computed(() =>\n truncated.value ? [props.items[0] as BreadcrumbItem, ...props.items.slice(-2)] : props.items,\n)\n</script>\n\n<template>\n <nav class=\"v-breadcrumb\" :aria-label=\"ariaLabel\">\n <ol class=\"v-breadcrumb-list\">\n <template v-for=\"(item, index) in visibleItems\" :key=\"item.href\">\n <li v-if=\"truncated && index === 1\" class=\"v-breadcrumb-item v-breadcrumb-ellipsis\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <VMenu>\n <template #trigger=\"{ triggerProps }\">\n <VIconButton\n size=\"sm\"\n :label=\"resolvedEllipsisLabel\"\n :icon=\"moreHorizIcon\"\n v-bind=\"triggerProps\"\n />\n </template>\n <VMenuItem\n v-for=\"hidden in hiddenItems\"\n :key=\"hidden.href\"\n :href=\"hidden.href\"\n :label=\"hidden.label\"\n :icon-start=\"hidden.icon\"\n :selected=\"isCurrent(hidden)\"\n :aria-current=\"isCurrent(hidden) ? 'page' : undefined\"\n />\n </VMenu>\n </li>\n <li class=\"v-breadcrumb-item\">\n <VIcon class=\"v-breadcrumb-separator\" v-bind=\"iconProps(separatorIcon)\" mirrored />\n <a\n class=\"v-breadcrumb-link\"\n :href=\"item.href\"\n :aria-current=\"isCurrent(item) ? 'page' : undefined\"\n >\n <VIcon v-if=\"item.icon\" v-bind=\"iconProps(item.icon)\" />\n {{ item.label }}\n </a>\n </li>\n </template>\n </ol>\n </nav>\n</template>\n\n<style>\n@layer vectis.components {\n /* The icon context is set here rather than through `.v-control`, which is the usual\n route: a trail has no `size` prop and no height to speak of, so the size scale would\n bring a table of five steps to pick one value from. */\n .v-breadcrumb {\n --vectis-icon-size: var(--vectis-icon-size-sm);\n --vectis-icon-opsz: 20;\n }\n\n .v-breadcrumb-list {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--vectis-space-2);\n margin: 0;\n padding: 0;\n list-style: none;\n font-family: var(--vectis-text-family);\n font-size: var(--vectis-text-body-md-size);\n }\n\n .v-breadcrumb-item {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-2);\n color: var(--vectis-color-text-muted);\n }\n\n /*\n * Every item renders its own leading separator and the first one is hidden here, which keeps\n * the template free of an index test. Delete this rule and the trail opens on a stray\n * chevron; both child combinators are load-bearing too, so wrapping an item or the list in\n * anything puts that chevron straight back.\n */\n .v-breadcrumb-list > .v-breadcrumb-item:first-child > .v-breadcrumb-separator {\n display: none;\n }\n\n .v-breadcrumb-separator {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The corner is only ever seen on the focus ring, which follows it. It is the control\n radius capped at half a line, the row recipe of VSideNavigationItem with the line\n standing in for a control height the link does not have: a label that wraps keeps\n the corner of a one-line one under a pill override. */\n .v-breadcrumb-link {\n display: inline-flex;\n align-items: center;\n gap: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n text-decoration: none;\n border-radius: min(var(--vectis-radius-interactive), 0.5lh);\n transition: color var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n .v-breadcrumb-link:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n .v-breadcrumb-link:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n .v-breadcrumb-link[aria-current='page'] {\n color: var(--vectis-color-text);\n font-weight: var(--vectis-text-label-weight);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-breadcrumb-link {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EA+DA,IAAM,IAAQ,GAQR,IAAI,EAAY,GAChB,IAAY,QAAmB,EAAM,SAAS,EAAE,MAAM,WAAW,KAAK,GACtE,IAAwB,QAAe,EAAM,iBAAiB,EAAE,MAAM,WAAW,QAAQ;EAM/F,SAAS,EAAU,GAAsB;GACvC,IAAM,IAAO,EAAK,QAAQ,WAAW,EAAE;GACvC,OAAO,EAAK,SAAS,KAAK,EAAK,SAAS,GAAG,IAAI,EAAK,MAAM,GAAG,EAAE,IAAI;EACrE;EAEA,IAAM,IAAoB,QACxB,EAAM,gBAAgB,KAAA,IAAY,KAAA,IAAY,EAAU,EAAM,WAAW,CAC3E;EAKA,SAAS,EAAU,GAA+B;GAChD,OAAO,EAAkB,UAAU,KAAA,KAAa,EAAU,EAAK,IAAI,MAAM,EAAkB;EAC7F;EAEA,IAAM,IAAY,QACV,EAAM,aAAa,KAAA,KAAa,EAAM,MAAM,SAAS,KAAK,IAAI,EAAM,UAAU,CAAC,CACvF,GAEM,IAAc,QAAgB,EAAU,QAAQ,EAAM,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,CAAE,GAK9E,IAAe,QACnB,EAAU,QAAQ,CAAC,EAAM,MAAM,IAAsB,GAAG,EAAM,MAAM,MAAM,EAAE,CAAC,IAAI,EAAM,KACzF;yBAIE,EAsCM,OAAA;GAtCD,OAAM;GAAgB,cAAY,EAAA,CAAA;MACrC,EAoCK,MApCL,GAoCK,EAAA,EAAA,EAAA,GAnCH,EAkCW,GAAA,MAAA,EAlCuB,EAAA,QAAhB,GAAM,wBAA8B,EAAK,KAAA,GAAA,CAC/C,EAAA,SAAa,MAAK,KAAA,EAAA,GAA5B,EAqBK,MArBL,GAqBK,CApBH,EAAmF,GAAnF,EAAmF,EAA5E,OAAM,yBAAwB,GAAA,EAAA,SAAA,GAAA,GAAS,EAAA,CAAA,CAAS,CAAC,EAAA,aAAa,GAAA,EAAG,UAAA,GAAQ,CAAA,GAAA,MAAA,EAAA,GAChF,EAkBQ,GAAA,MAAA;GAjBK,SAAO,GAMd,EANkB,sBAAY,CAChC,EAKE,GALF,EAKE;IAJA,MAAK;IACJ,OAAO,EAAA;IACP,MAAM,EAAA,CAAA;uBACC,CAAY,GAAA,MAAA,IAAA,CAAA,SAAA,MAAA,CAAA,CAAA,CAAA;oBAIO,EAAA,EAAA,EAAA,GAD/B,EAQE,GAAA,MAAA,EAPiB,EAAA,QAAV,YADT,EAQE,GAAA;IANC,KAAK,EAAO;IACZ,MAAM,EAAO;IACb,OAAO,EAAO;IACd,cAAY,EAAO;IACnB,UAAU,EAAU,CAAM;IAC1B,gBAAc,EAAU,CAAM,IAAA,SAAa,KAAA;;;;;;;;;qBAIlD,EAUK,MAVL,GAUK,CATH,EAAmF,GAAnF,EAAmF,EAA5E,OAAM,yBAAwB,GAAA,EAAA,SAAA,GAAA,GAAS,EAAA,CAAA,CAAS,CAAC,EAAA,aAAa,GAAA,EAAG,UAAA,GAAQ,CAAA,GAAA,MAAA,EAAA,GAChF,EAOI,KAAA;GANF,OAAM;GACL,MAAM,EAAK;GACX,gBAAc,EAAU,CAAI,IAAA,SAAa,KAAA;MAE7B,EAAK,QAAA,EAAA,GAAlB,EAAwD,GAAxD,EAAwD;;;KAAxB,EAAA,CAAA,CAAS,CAAC,EAAK,IAAI,CAAA,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA,GAAA,EAAK,MACxD,EAAG,EAAK,KAAK,GAAA,CAAA,CAAA,GAAA,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA"}
@@ -8,26 +8,20 @@ export type ButtonTone = 'accent' | 'neutral' | 'danger';
8
8
  export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
9
9
  interface ButtonProps {
10
10
  /**
11
- * How much visual weight the button carries: `solid` is filled with the tone,
12
- * `soft` uses a tinted background, `outline` keeps only a border, and `ghost`
13
- * shows nothing until it is hovered. Inside a VButtonGroup the group's own variant
14
- * wins over this one.
11
+ * How much visual weight the button carries: `solid` is filled with the tone, `soft` uses a
12
+ * tinted background, `outline` keeps only a border, and `ghost` shows nothing until it is
13
+ * hovered.
15
14
  */
16
15
  variant?: ButtonVariant;
17
16
  /**
18
- * What the action means: `accent` for the ordinary one, `neutral` for a secondary
19
- * one, `danger` for one that destroys something. On a button a tone is an
20
- * intention, which is why states such as success or warning are not offered here.
21
- * Left out inside a VButtonGroup it takes the group's tone, which is the point of
22
- * not defaulting it here; on its own it is `accent`.
17
+ * What the action means: `accent` for the ordinary one, `neutral` for a secondary one,
18
+ * `danger` for one that destroys something. Left out inside a VButtonGroup it takes the
19
+ * group's tone, which is the point of not defaulting it here; on its own it is `accent`.
23
20
  */
24
21
  tone?: ButtonTone;
25
22
  /**
26
- * Raises the button off the page: a shadow that grows on hover and settles back
27
- * when pressed, whatever the variant. On `ghost` and `outline`, which have no
28
- * background of their own, it also paints the raised surface: in the dark theme
29
- * a shadow lying straight on the page background has nothing casting it. Inside a
30
- * VButtonGroup the group's own value wins over this one.
23
+ * Raises the button off the page: a shadow that grows on hover and settles back when pressed,
24
+ * whatever the variant. Inside a VButtonGroup the group's own value wins over this one.
31
25
  */
32
26
  elevated?: boolean;
33
27
  /**
@@ -41,9 +35,8 @@ interface ButtonProps {
41
35
  */
42
36
  compact?: boolean;
43
37
  /**
44
- * Stretches the button to the whole inline size of its parent, instead of leaving
45
- * it only as wide as its content. The label stays centred, and the button becomes
46
- * block-level, so it no longer sits on a line of text.
38
+ * Stretches the button to the whole inline size of its parent, instead of leaving it only as
39
+ * wide as its content.
47
40
  */
48
41
  fullWidth?: boolean;
49
42
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"VButton.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n// The component's ONLY behavioural JS, and it exists entirely for\n// accessibility: an <a> has no native `disabled`, so the inert-link bridge has to\n// reproduce it by hand.\n/**\n * The button that triggers an action, and the reference from which the tone and\n * variant tables of every other coloured component are taken.\n *\n * It renders a native <button>, or an <a> as soon as it is given an `href`, so\n * focus, keyboard activation and disabling all come from the browser. Its one piece\n * of behavioural code is the inert link: a link has no `disabled` attribute of its\n * own, so a disabled or loading <a> has its address removed — it can then be\n * neither focused nor followed — receives `aria-disabled` for assistive technology,\n * and has the click handlers it was given filtered out. Together these reproduce\n * the complete inertness that a <button disabled> gets for free.\n *\n * Inside a VButtonGroup it reads what the row decided: the group wins on the four\n * props that give the control its shape, the button keeps its own tone. The rule and\n * its reasons live in `context.ts`.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/*\n * The three unions a VButtonGroup has to name in order to hand them down, declared\n * BEFORE the interface and referenced from it. `build-api.ts` prints a type as the\n * source spells it, so this is what makes the documentation site name the type on the\n * Button page as it already does on the Chip, Tabs and Toggle ones — and what stops\n * the IconButton page from naming a type its own page leaves unnamed.\n */\n/** How much visual weight the button carries. */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\n/** What the action means, in colour. */\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\n/** The height of the button, from the scale every control shares. */\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone,\n * `soft` uses a tinted background, `outline` keeps only a border, and `ghost`\n * shows nothing until it is hovered. Inside a VButtonGroup the group's own variant\n * wins over this one.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary\n * one, `danger` for one that destroys something. On a button a tone is an\n * intention, which is why states such as success or warning are not offered here.\n * Left out inside a VButtonGroup it takes the group's tone, which is the point of\n * not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back\n * when pressed, whatever the variant. On `ghost` and `outline`, which have no\n * background of their own, it also paints the raised surface: in the dark theme\n * a shadow lying straight on the page background has nothing casting it. Inside a\n * VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving\n * it only as wide as its content. The label stays centred, and the button becomes\n * block-level, so it no longer sits on a line of text.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // TRAP — the only one of the five with no default. `tone` is the prop a button keeps\n // against its group, so `undefined` has to stay distinguishable from an explicit\n // `accent`, which would otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n block-size: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* An icon and no label: the button becomes a square, as wide as it is tall. The attribute\n is set by whatever renders the button that way — VIconButton always, a VTab or a\n VToggleItem given an icon and no label — and the rule lives here because this sheet\n owns the padding it cancels.\n\n A FLOOR rather than a width, so a box that is stretched (a column of segments, a\n full-width row) still fills its track, and an icon wider than the box still fits.\n\n TRAP — at (0,2,0) it beats this sheet's own padding by weight, and it is the only rule\n that may touch that padding on an icon-only button. A consumer of VButton restyling the\n padding of its own buttons at (0,2,0) from another sheet would tie with it, and the\n bundler would decide: such a rule has to exclude `[data-icon-only]`. */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The tone table lives in styles/tones.css (`v-tone`, in the vectis.tokens layer) and the\n four ways of painting it in styles/variants.css (`v-variant`, in vectis.components).\n VButton overrides not a single one of their values: it is the reference they were\n written for, and what stays here is the STATES, which each component answers for\n itself. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /* The `:not([data-elevated])` is what makes these rules DISJOINT from the\n elevation rules below, rather than merely less specific than them. Nothing then\n arbitrates the background of a raised ghost by a one-step specificity margin,\n so a declaration added to either set later cannot silently leak into the other\n case. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /* Elevation is ORTHOGONAL to the variant: the shadow scale applies to all four of\n them, while the raised surface is only painted on the two that have no\n background of their own. The `:is()` here is not decoration — it is what gives\n these rules a specificity of (0,3,0), beating the (0,2,0) of the ghost and\n outline bases without depending on the order the rules end up in. */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /* The shadow needs a rule of its OWN. No variant declares a shadow, so none of the\n three rules above cancels one, and a disabled raised ghost would otherwise go on\n casting its shadow-sm. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /* A box exactly the size of an icon, since the spinner takes the place of\n iconStart: a button with a start icon keeps its width when it starts loading\n (one with a label alone gains the spinner and the gap beside it). The\n font-size is set to that same size so that VSpinner's own box, which measures\n 1em, coincides with this one — VSpinner then draws its ring inside it at the\n proportion Material Symbols gives `progress_activity`, which is what makes the\n spinner and the icon it replaces read at the same size. */\n font-size: var(--vectis-icon-size);\n /* 1em of the line above rather than the variable again: a relative icon size would\n otherwise be resolved twice, once against the button's text and once against this\n box's own enlarged font-size. */\n inline-size: 1em;\n block-size: 1em;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VButton.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Native buttons own activation and disabling. Links use useInertLink because anchors have no\n * disabled state; group shape overrides preserve individual tones.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/** How much visual weight the button carries. */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\n/** What the action means, in colour. */\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\n/** The height of the button, from the scale every control shares. */\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone, `soft` uses a\n * tinted background, `outline` keeps only a border, and `ghost` shows nothing until it is\n * hovered.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary one,\n * `danger` for one that destroys something. Left out inside a VButtonGroup it takes the\n * group's tone, which is the point of not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back when pressed,\n * whatever the variant. Inside a VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving it only as\n * wide as its content.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // The only one of the five with no default. `tone` is the prop a button keeps against its\n // group, so `undefined` has to stay distinguishable from an explicit `accent`, which would\n // otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n block-size: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /*\n * At (0,2,0) it beats this sheet's own padding by weight, and it is the only rule that may\n * touch that padding on an icon-only button. A consumer of VButton restyling the padding of\n * its own buttons at (0,2,0) from another sheet would tie with it, and the bundler would\n * decide: such a rule has to exclude `[data-icon-only]`.\n */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* Tone and variant recipes are shared; component-specific interaction states remain here. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /*\n * Nothing then arbitrates the background of a raised ghost by a one-step specificity margin,\n * so a declaration added to either set later cannot silently leak into the other case.\n */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /*\n * The `:is()` here is not decoration; it is what gives these rules a specificity of (0,3,0),\n * beating the (0,2,0) of the ghost and outline bases without depending on the order the rules\n * end up in.\n */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n * The shadow needs a rule of its own. No variant declares a shadow, so none of the three\n * rules above cancels one, and a disabled raised ghost would otherwise go on casting its\n * shadow-sm.\n */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /*\n * Match the spinner box and font size to the icon it replaces so loading preserves button\n * width and visual scale.\n */\n font-size: var(--vectis-icon-size);\n /* 1em of the line above rather than the variable again: a relative icon size would\n otherwise be resolved twice, once against the button's text and once against this\n box's own enlarged font-size. */\n inline-size: 1em;\n block-size: 1em;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"VButton.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n// The component's ONLY behavioural JS, and it exists entirely for\n// accessibility: an <a> has no native `disabled`, so the inert-link bridge has to\n// reproduce it by hand.\n/**\n * The button that triggers an action, and the reference from which the tone and\n * variant tables of every other coloured component are taken.\n *\n * It renders a native <button>, or an <a> as soon as it is given an `href`, so\n * focus, keyboard activation and disabling all come from the browser. Its one piece\n * of behavioural code is the inert link: a link has no `disabled` attribute of its\n * own, so a disabled or loading <a> has its address removed — it can then be\n * neither focused nor followed — receives `aria-disabled` for assistive technology,\n * and has the click handlers it was given filtered out. Together these reproduce\n * the complete inertness that a <button disabled> gets for free.\n *\n * Inside a VButtonGroup it reads what the row decided: the group wins on the four\n * props that give the control its shape, the button keeps its own tone. The rule and\n * its reasons live in `context.ts`.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/*\n * The three unions a VButtonGroup has to name in order to hand them down, declared\n * BEFORE the interface and referenced from it. `build-api.ts` prints a type as the\n * source spells it, so this is what makes the documentation site name the type on the\n * Button page as it already does on the Chip, Tabs and Toggle ones — and what stops\n * the IconButton page from naming a type its own page leaves unnamed.\n */\n/** How much visual weight the button carries. */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\n/** What the action means, in colour. */\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\n/** The height of the button, from the scale every control shares. */\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone,\n * `soft` uses a tinted background, `outline` keeps only a border, and `ghost`\n * shows nothing until it is hovered. Inside a VButtonGroup the group's own variant\n * wins over this one.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary\n * one, `danger` for one that destroys something. On a button a tone is an\n * intention, which is why states such as success or warning are not offered here.\n * Left out inside a VButtonGroup it takes the group's tone, which is the point of\n * not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back\n * when pressed, whatever the variant. On `ghost` and `outline`, which have no\n * background of their own, it also paints the raised surface: in the dark theme\n * a shadow lying straight on the page background has nothing casting it. Inside a\n * VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving\n * it only as wide as its content. The label stays centred, and the button becomes\n * block-level, so it no longer sits on a line of text.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // TRAP — the only one of the five with no default. `tone` is the prop a button keeps\n // against its group, so `undefined` has to stay distinguishable from an explicit\n // `accent`, which would otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n block-size: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /* An icon and no label: the button becomes a square, as wide as it is tall. The attribute\n is set by whatever renders the button that way — VIconButton always, a VTab or a\n VToggleItem given an icon and no label — and the rule lives here because this sheet\n owns the padding it cancels.\n\n A FLOOR rather than a width, so a box that is stretched (a column of segments, a\n full-width row) still fills its track, and an icon wider than the box still fits.\n\n TRAP — at (0,2,0) it beats this sheet's own padding by weight, and it is the only rule\n that may touch that padding on an icon-only button. A consumer of VButton restyling the\n padding of its own buttons at (0,2,0) from another sheet would tie with it, and the\n bundler would decide: such a rule has to exclude `[data-icon-only]`. */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* The tone table lives in styles/tones.css (`v-tone`, in the vectis.tokens layer) and the\n four ways of painting it in styles/variants.css (`v-variant`, in vectis.components).\n VButton overrides not a single one of their values: it is the reference they were\n written for, and what stays here is the STATES, which each component answers for\n itself. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /* The `:not([data-elevated])` is what makes these rules DISJOINT from the\n elevation rules below, rather than merely less specific than them. Nothing then\n arbitrates the background of a raised ghost by a one-step specificity margin,\n so a declaration added to either set later cannot silently leak into the other\n case. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /* Elevation is ORTHOGONAL to the variant: the shadow scale applies to all four of\n them, while the raised surface is only painted on the two that have no\n background of their own. The `:is()` here is not decoration — it is what gives\n these rules a specificity of (0,3,0), beating the (0,2,0) of the ghost and\n outline bases without depending on the order the rules end up in. */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /* The shadow needs a rule of its OWN. No variant declares a shadow, so none of the\n three rules above cancels one, and a disabled raised ghost would otherwise go on\n casting its shadow-sm. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /* A box exactly the size of an icon, since the spinner takes the place of\n iconStart: a button with a start icon keeps its width when it starts loading\n (one with a label alone gains the spinner and the gap beside it). The\n font-size is set to that same size so that VSpinner's own box, which measures\n 1em, coincides with this one — VSpinner then draws its ring inside it at the\n proportion Material Symbols gives `progress_activity`, which is what makes the\n spinner and the icon it replaces read at the same size. */\n font-size: var(--vectis-icon-size);\n /* 1em of the line above rather than the variable again: a relative icon size would\n otherwise be resolved twice, once against the button's text and once against this\n box's own enlarged font-size. */\n inline-size: 1em;\n block-size: 1em;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsHA,IAAM,IAAQ,GAiCR,IAAQ,EAAS,GACjB,IAAQ,EAAO,GAAgB,IAAI,GAKnC,EACJ,MAAM,GACN,SAAS,GACT,UAAU,MACR,EAA4B,GAAO,CAAK,GACtC,IAAkB,QAA8B,GAAO,WAAW,EAAM,OAAO,GAC/E,IAAmB,QAAe,GAAO,YAAY,EAAM,QAAQ,GAGnE,IAAe,QAA2B,EAAM,QAAQ,GAAO,QAAQ,QAAQ,GAE/E,IAAU,QAAe,EAAiB,SAAS,EAAM,OAAO,GAChE,EACJ,WACA,gBACA,aACA,OAAO,MACL,EAAa;GACf,YAAY,EAAM;GAClB,aAAa,EAAQ;GACrB,aAAa;EACf,CAAC;yBAIC,EA8BY,EA7BL,EAAA,CAAA,IAAM,MAAA,QAAA,GADb,EA8BY;GA5BT,iBAAe,EAAA,CAAA,IAAW,SAAY,KAAA;GACtC,aAAW,EAAA,WAAW,KAAA;KACf,EAAA,CAAA,GAAW;GACnB,OAAM;GACL,MAAM,EAAA,CAAA;GACN,MAAM,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC3B,UAAU,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC/B,gBAAc,EAAA;GACd,aAAW,EAAA;GACX,iBAAe,EAAA,QAAgB,KAAQ,KAAA;GACvC,aAAW,EAAA,CAAA;GACX,gBAAc,EAAA,CAAA,IAAe,KAAQ,KAAA;GACrC,mBAAiB,EAAA,YAAS,KAAQ,KAAA;GAClC,gBAAc,EAAA,UAAO,KAAQ,KAAA;;oBAOvB;IAFK,EAAA,WAAA,EAAA,GAAZ,EAEO,QAFP,GAEO,CADL,EAAY,CAAA,CAAA,CAAA,KAEd,EAEO,EAAA,QAAA,SAAA,CAAA,SAAA,CADQ,EAAA,aAAA,EAAA,GAAb,EAA6E,GAA7E,EAA6E,EAAA,KAAA,EAAA,GAA7C,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;IAE3E,EAAQ,EAAA,QAAA,SAAA;IACR,EAEO,EAAA,QAAA,OAAA,CAAA,SAAA,CADQ,EAAA,WAAA,EAAA,GAAb,EAAyE,GAAzE,EAAyE,EAAA,KAAA,EAAA,GAA3C,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"VButton.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VButton/VButton.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Native buttons own activation and disabling. Links use useInertLink because anchors have no\n * disabled state; group shape overrides preserve individual tones.\n */\nimport { computed, inject, useAttrs } from 'vue'\nimport type { ButtonHTMLAttributes } from 'vue'\n\nimport { buttonGroupKey } from './context'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useInertLink } from '../../composables/useInertLink'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport type { IconSource } from '../VIcon/types'\nimport VSpinner from '../VSpinner/VSpinner.vue'\n\n/** How much visual weight the button carries. */\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'soft'\n/** What the action means, in colour. */\nexport type ButtonTone = 'accent' | 'neutral' | 'danger'\n/** The height of the button, from the scale every control shares. */\nexport type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'\n\ninterface ButtonProps {\n /**\n * How much visual weight the button carries: `solid` is filled with the tone, `soft` uses a\n * tinted background, `outline` keeps only a border, and `ghost` shows nothing until it is\n * hovered.\n */\n variant?: ButtonVariant\n /**\n * What the action means: `accent` for the ordinary one, `neutral` for a secondary one,\n * `danger` for one that destroys something. Left out inside a VButtonGroup it takes the\n * group's tone, which is the point of not defaulting it here; on its own it is `accent`.\n */\n tone?: ButtonTone\n /**\n * Raises the button off the page: a shadow that grows on hover and settles back when pressed,\n * whatever the variant. Inside a VButtonGroup the group's own value wins over this one.\n */\n elevated?: boolean\n /**\n * The height of the button, taken from the size scale shared by every control.\n * Inside a VButtonGroup the group's own size wins over this one.\n */\n size?: ButtonSize\n /**\n * Takes 4px off the height, leaving the padding, the text and the icons as they\n * are. Inside a VButtonGroup the group's own value wins over this one.\n */\n compact?: boolean\n /**\n * Stretches the button to the whole inline size of its parent, instead of leaving it only as\n * wide as its content.\n */\n fullWidth?: boolean\n /**\n * Turns the button into an `<a>` pointing at this address. A disabled or loading\n * link becomes inert: the address is dropped, so it can be neither focused nor\n * followed.\n */\n href?: string\n /** The native type of the button. It is ignored as soon as `href` makes it a link. */\n type?: ButtonHTMLAttributes['type']\n /**\n * Makes the button unusable: it stops responding, leaves the tab order and greys\n * out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows a spinner, disables the button and announces it as busy. The spinner takes\n * the place of the start slot (`iconStart` / `#start`), so an icon and a spinner\n * are never displayed side by side.\n */\n loading?: boolean\n /** An icon before the label. The `#start` slot replaces it when both are given. */\n iconStart?: IconSource\n /** An icon after the label. The `#end` slot replaces it when both are given. */\n iconEnd?: IconSource\n /**\n * Renders `iconStart` and `iconEnd` in their filled form (the font's `FILL` axis).\n * It has no effect on the `#start`/`#end` slots, whose icons the consumer builds.\n */\n iconFilled?: boolean\n}\n\nconst props = withDefaults(defineProps<ButtonProps>(), {\n variant: 'solid',\n // The only one of the five with no default. `tone` is the prop a button keeps against its\n // group, so `undefined` has to stay distinguishable from an explicit `accent`, which would\n // otherwise silence the group for every button in the row.\n tone: undefined,\n elevated: false,\n size: 'md',\n compact: false,\n fullWidth: false,\n href: undefined,\n type: 'button',\n disabled: false,\n loading: false,\n iconStart: undefined,\n iconEnd: undefined,\n iconFilled: false,\n})\n\ndefineSlots<{\n /** The label of the button. */\n default(): unknown\n /**\n * Content placed before the label, usually an icon. Mark it `aria-hidden` when it\n * only repeats what the label already says.\n */\n start?(): unknown\n /** Content placed after the label. */\n end?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\n\nconst attrs = useAttrs()\nconst group = inject(buttonGroupKey, null)\n\n// The shape of the segmented control belongs to the row: the group is read first, and a\n// group that named nothing hands the prop straight back. The tone goes the other way,\n// the button's own answer first (see context.ts).\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape<ButtonSize>(props, group)\nconst resolvedVariant = computed<ButtonVariant>(() => group?.variant ?? props.variant)\nconst resolvedElevated = computed(() => group?.elevated ?? props.elevated)\n// The tone goes the OTHER way, the button's own answer first: one action in a row can be\n// the destructive one, and the row has to be able to say so (see context.ts).\nconst resolvedTone = computed<ButtonTone>(() => props.tone ?? group?.tone ?? 'accent')\n\nconst isInert = computed(() => resolvedDisabled.value || props.loading)\nconst {\n isLink,\n isInertLink,\n linkHref,\n attrs: passedAttrs,\n} = useInertLink({\n href: () => props.href,\n inert: () => isInert.value,\n attrs: () => attrs,\n})\n</script>\n\n<template>\n <component\n :is=\"isLink ? 'a' : 'button'\"\n :aria-disabled=\"isInertLink ? 'true' : undefined\"\n :aria-busy=\"loading || undefined\"\n v-bind=\"passedAttrs\"\n class=\"v-button v-control v-tone v-variant\"\n :href=\"linkHref\"\n :type=\"isLink ? undefined : type\"\n :disabled=\"isLink ? undefined : isInert\"\n :data-variant=\"resolvedVariant\"\n :data-tone=\"resolvedTone\"\n :data-elevated=\"resolvedElevated ? '' : undefined\"\n :data-size=\"resolvedSize\"\n :data-compact=\"resolvedCompact ? '' : undefined\"\n :data-full-width=\"fullWidth ? '' : undefined\"\n :data-loading=\"loading ? '' : undefined\"\n >\n <!-- The button already carries aria-busy, so the spinner is hidden from\n assistive technology: its own role=\"status\" would announce the same state a\n second time -->\n <span v-if=\"loading\" class=\"v-button-spinner\" aria-hidden=\"true\">\n <VSpinner />\n </span>\n <slot v-else name=\"start\">\n <VIcon v-if=\"iconStart\" v-bind=\"iconProps(iconStart)\" :filled=\"iconFilled\" />\n </slot>\n <slot />\n <slot name=\"end\">\n <VIcon v-if=\"iconEnd\" v-bind=\"iconProps(iconEnd)\" :filled=\"iconFilled\" />\n </slot>\n </component>\n</template>\n\n<style>\n@layer vectis.components {\n .v-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--control-gap);\n block-size: var(--control-height);\n padding-inline: var(--control-padding-inline);\n border: 1px solid transparent;\n border-radius: var(--vectis-radius-interactive);\n font-family: var(--vectis-text-family);\n font-size: var(--control-font-size);\n font-weight: var(--vectis-text-control-weight);\n line-height: var(--vectis-text-control-leading);\n text-decoration: none;\n cursor: pointer;\n transition:\n background-color var(--vectis-duration-fast) var(--vectis-ease-default),\n border-color var(--vectis-duration-fast) var(--vectis-ease-default),\n color var(--vectis-duration-fast) var(--vectis-ease-default),\n box-shadow var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /* An explicit display, rather than a width alone: an inline-level box sits on a line\n box, which would add the strut's descender under a button the consumer asked to\n fill its parent. Block-level, it measures the parent's content box exactly. */\n .v-button[data-full-width] {\n display: flex;\n inline-size: 100%;\n }\n\n /*\n * At (0,2,0) it beats this sheet's own padding by weight, and it is the only rule that may\n * touch that padding on an icon-only button. A consumer of VButton restyling the padding of\n * its own buttons at (0,2,0) from another sheet would tie with it, and the bundler would\n * decide: such a rule has to exclude `[data-icon-only]`.\n */\n .v-button[data-icon-only] {\n min-inline-size: var(--control-height);\n padding-inline: 0;\n }\n\n .v-button:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: var(--vectis-focus-ring-offset);\n }\n\n /* Tone and variant recipes are shared; component-specific interaction states remain here. */\n\n .v-button[data-variant='solid']:hover:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-hover);\n }\n\n .v-button[data-variant='solid']:active:not(:disabled, [aria-disabled='true']) {\n background: var(--tone-bg-solid-active);\n }\n\n /*\n * Nothing then arbitrates the background of a raised ghost by a one-step specificity margin,\n * so a declaration added to either set later cannot silently leak into the other case.\n */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: var(--tone-bg-soft);\n }\n\n /* One step of tint, reached two ways: pressing a ghost or an outline, which already sit on\n the soft surface while hovered, and hovering a soft one. A selector list keeps each\n member's own weight, so merging them moves no arbitration: the outline/ghost half is\n still (0,5,0) after its hover, the soft half still (0,4,0) before its active. */\n .v-button:is([data-variant='outline'], [data-variant='ghost']):not([data-elevated]):active:not(\n :disabled,\n [aria-disabled='true']\n ),\n .v-button[data-variant='soft']:hover:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-variant='soft']:active:not(:disabled, [aria-disabled='true']) {\n background: color-mix(in oklab, var(--tone-bg-soft), var(--tone-text-tinted) 14%);\n }\n\n /*\n * The `:is()` here is not decoration; it is what gives these rules a specificity of (0,3,0),\n * beating the (0,2,0) of the ghost and outline bases without depending on the order the rules\n * end up in.\n */\n .v-button[data-elevated] {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']) {\n background: var(--vectis-color-surface-raised);\n }\n\n .v-button[data-elevated]:hover:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-md);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):hover:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(in oklab, var(--vectis-color-surface-raised), var(--tone-text-tinted) 8%);\n }\n\n .v-button[data-elevated]:active:not(:disabled, [aria-disabled='true']) {\n box-shadow: var(--vectis-shadow-sm);\n }\n\n .v-button[data-elevated]:is([data-variant='ghost'], [data-variant='outline']):active:not(\n :disabled,\n [aria-disabled='true']\n ) {\n background: color-mix(\n in oklab,\n var(--vectis-color-surface-raised),\n var(--tone-text-tinted) 12%\n );\n }\n\n .v-button:is(:disabled, [aria-disabled='true']) {\n cursor: not-allowed;\n }\n\n .v-button[data-loading] {\n opacity: 0.5;\n }\n\n /* A disabled button that is not loading greys out through the colour tokens and\n never through opacity. These three rules are (0,4,0), so they already beat the\n (0,3,0) of the raised background whatever the order they are read in. */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading]):is(\n [data-variant='solid'],\n [data-variant='soft']\n ) {\n background: var(--vectis-color-surface-muted);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='outline'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n border-color: var(--vectis-color-border);\n }\n\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-variant='ghost'] {\n background: transparent;\n color: var(--vectis-color-text-subtle);\n }\n\n /*\n * The shadow needs a rule of its own. No variant declares a shadow, so none of the three\n * rules above cancels one, and a disabled raised ghost would otherwise go on casting its\n * shadow-sm.\n */\n .v-button:is(:disabled, [aria-disabled='true']):not([data-loading])[data-elevated] {\n box-shadow: none;\n }\n\n .v-button-spinner {\n /*\n * Match the spinner box and font size to the icon it replaces so loading preserves button\n * width and visual scale.\n */\n font-size: var(--vectis-icon-size);\n /* 1em of the line above rather than the variable again: a relative icon size would\n otherwise be resolved twice, once against the button's text and once against this\n box's own enlarged font-size. */\n inline-size: 1em;\n block-size: 1em;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-button {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyFA,IAAM,IAAQ,GAiCR,IAAQ,EAAS,GACjB,IAAQ,EAAO,GAAgB,IAAI,GAKnC,EACJ,MAAM,GACN,SAAS,GACT,UAAU,MACR,EAA4B,GAAO,CAAK,GACtC,IAAkB,QAA8B,GAAO,WAAW,EAAM,OAAO,GAC/E,IAAmB,QAAe,GAAO,YAAY,EAAM,QAAQ,GAGnE,IAAe,QAA2B,EAAM,QAAQ,GAAO,QAAQ,QAAQ,GAE/E,IAAU,QAAe,EAAiB,SAAS,EAAM,OAAO,GAChE,EACJ,WACA,gBACA,aACA,OAAO,MACL,EAAa;GACf,YAAY,EAAM;GAClB,aAAa,EAAQ;GACrB,aAAa;EACf,CAAC;yBAIC,EA8BY,EA7BL,EAAA,CAAA,IAAM,MAAA,QAAA,GADb,EA8BY;GA5BT,iBAAe,EAAA,CAAA,IAAW,SAAY,KAAA;GACtC,aAAW,EAAA,WAAW,KAAA;KACf,EAAA,CAAA,GAAW;GACnB,OAAM;GACL,MAAM,EAAA,CAAA;GACN,MAAM,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC3B,UAAU,EAAA,CAAA,IAAS,KAAA,IAAY,EAAA;GAC/B,gBAAc,EAAA;GACd,aAAW,EAAA;GACX,iBAAe,EAAA,QAAgB,KAAQ,KAAA;GACvC,aAAW,EAAA,CAAA;GACX,gBAAc,EAAA,CAAA,IAAe,KAAQ,KAAA;GACrC,mBAAiB,EAAA,YAAS,KAAQ,KAAA;GAClC,gBAAc,EAAA,UAAO,KAAQ,KAAA;;oBAOvB;IAFK,EAAA,WAAA,EAAA,GAAZ,EAEO,QAFP,GAEO,CADL,EAAY,CAAA,CAAA,CAAA,KAEd,EAEO,EAAA,QAAA,SAAA,CAAA,SAAA,CADQ,EAAA,aAAA,EAAA,GAAb,EAA6E,GAA7E,EAA6E,EAAA,KAAA,EAAA,GAA7C,EAAA,CAAA,CAAS,CAAC,EAAA,SAAS,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,GAAA,KAAA,GAAA,CAAA;IAE3E,EAAQ,EAAA,QAAA,SAAA;IACR,EAEO,EAAA,QAAA,OAAA,CAAA,SAAA,CADQ,EAAA,WAAA,EAAA,GAAb,EAAyE,GAAzE,EAAyE,EAAA,KAAA,EAAA,GAA3C,EAAA,CAAA,CAAS,CAAC,EAAA,OAAO,GAAA,EAAI,QAAQ,EAAA,WAAU,CAAA,GAAA,MAAA,IAAA,CAAA,QAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA"}
@@ -13,16 +13,13 @@ interface ButtonGroupProps {
13
13
  */
14
14
  detached?: boolean;
15
15
  /**
16
- * Takes the lines out from between the joined buttons, so the row reads as one frame
17
- * rather than as segments. It has no effect under `detached`, where the buttons are
18
- * apart already and there is no shared edge to draw on.
16
+ * Takes the lines out from between the joined buttons, so the row reads as one frame rather
17
+ * than as segments.
19
18
  */
20
19
  seamless?: boolean;
21
20
  /**
22
- * Stretches the row across the whole inline size of its parent, every segment taking
23
- * an equal share of that width. A segment never shrinks below its own label, so a row
24
- * of labels too long for the parent overflows it rather than being crushed. Under
25
- * `vertical` it is the width alone: the segments already stretch across the column.
21
+ * Stretches the row across the whole inline size of its parent, every segment taking an equal
22
+ * share of that width.
26
23
  */
27
24
  fullWidth?: boolean;
28
25
  /**
@@ -31,11 +28,7 @@ interface ButtonGroupProps {
31
28
  * button inside was given.
32
29
  */
33
30
  variant?: ButtonVariant;
34
- /**
35
- * The colour the segments take, among `accent`, `neutral` and `danger`. This one is
36
- * a fallback rather than an order: a button that names a tone of its own keeps it,
37
- * which is what lets a single destructive action stand out in the row.
38
- */
31
+ /** The colour the segments take, among `accent`, `neutral` and `danger`. */
39
32
  tone?: ButtonTone;
40
33
  /**
41
34
  * The height of the segments, from the size scale shared by every control:
@@ -59,9 +52,7 @@ interface ButtonGroupProps {
59
52
  disabled?: boolean;
60
53
  /**
61
54
  * What screen readers announce for the row, which is a `role="group"`: "Text formatting",
62
- * "View". A group with no name is announced as a bare group, so give one whenever the
63
- * buttons do not say on their own what they are for. A consumer `aria-label` or
64
- * `aria-labelledby` wins over it.
55
+ * "View". A consumer `aria-label` or `aria-labelledby` wins over it.
65
56
  */
66
57
  label?: string;
67
58
  }