vectis-ui 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/dist/components/VAccordion/VAccordion.js.map +1 -1
  2. package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  3. package/dist/components/VAccordion/VAccordionItem.d.ts +6 -21
  4. package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
  5. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/VAccordion/context.js.map +1 -1
  7. package/dist/components/VAvatar/VAvatar.d.ts +2 -12
  8. package/dist/components/VAvatar/VAvatar.js.map +1 -1
  9. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  10. package/dist/components/VAvatar/VAvatarGroup.d.ts +2 -2
  11. package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
  12. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +6 -6
  13. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  14. package/dist/components/VAvatar/context.d.ts +1 -5
  15. package/dist/components/VAvatar/context.js.map +1 -1
  16. package/dist/components/VBadge/VBadge.d.ts +13 -27
  17. package/dist/components/VBadge/VBadge.js.map +1 -1
  18. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +3 -9
  20. package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
  21. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/VButton/VButton.d.ts +10 -17
  23. package/dist/components/VButton/VButton.js.map +1 -1
  24. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  25. package/dist/components/VButton/VButtonGroup.d.ts +6 -15
  26. package/dist/components/VButton/VButtonGroup.js.map +1 -1
  27. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  28. package/dist/components/VButton/context.d.ts +1 -9
  29. package/dist/components/VButton/context.js.map +1 -1
  30. package/dist/components/VCalendar/VCalendar.d.ts +9 -24
  31. package/dist/components/VCalendar/VCalendar.js.map +1 -1
  32. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  33. package/dist/components/VCalendar/VCalendarEvent.d.ts +4 -13
  34. package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
  35. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  36. package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
  37. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
  38. package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +1 -5
  39. package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
  40. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  41. package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
  42. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  43. package/dist/components/VCalendar/color.d.ts +1 -7
  44. package/dist/components/VCalendar/color.js.map +1 -1
  45. package/dist/components/VCalendar/edgeStep.d.ts +5 -15
  46. package/dist/components/VCalendar/edgeStep.js.map +1 -1
  47. package/dist/components/VCalendar/gesture.d.ts +12 -36
  48. package/dist/components/VCalendar/gesture.js.map +1 -1
  49. package/dist/components/VCalendar/keyboard.d.ts +5 -42
  50. package/dist/components/VCalendar/keyboard.js.map +1 -1
  51. package/dist/components/VCalendar/layout.d.ts +38 -199
  52. package/dist/components/VCalendar/layout.js.map +1 -1
  53. package/dist/components/VCalendar/timeGrid.d.ts +3 -25
  54. package/dist/components/VCalendar/timeGrid.js.map +1 -1
  55. package/dist/components/VCalendar/types.d.ts +6 -23
  56. package/dist/components/VCarousel/VCarousel.d.ts +25 -96
  57. package/dist/components/VCarousel/VCarousel.js.map +1 -1
  58. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +6 -6
  59. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  60. package/dist/components/VCarousel/VCarouselItem.d.ts +1 -6
  61. package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
  62. package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  63. package/dist/components/VCarousel/context.js.map +1 -1
  64. package/dist/components/VCheckbox/VCheckbox.d.ts +1 -5
  65. package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
  66. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  67. package/dist/components/VChip/VChip.d.ts +2 -8
  68. package/dist/components/VChip/VChip.js.map +1 -1
  69. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  70. package/dist/components/VCombobox/VCombobox.d.ts +21 -59
  71. package/dist/components/VCombobox/VCombobox.js.map +1 -1
  72. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +117 -119
  73. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  74. package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
  75. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  76. package/dist/components/VCombobox/VComboboxOption.js.map +1 -1
  77. package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
  78. package/dist/components/VCombobox/VComboboxSeparator.js.map +1 -1
  79. package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  80. package/dist/components/VCombobox/infiniteScroll.d.ts +4 -10
  81. package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
  82. package/dist/components/VDataTable/VDataTable.d.ts +11 -36
  83. package/dist/components/VDataTable/VDataTable.js.map +1 -1
  84. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +23 -20
  85. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  86. package/dist/components/VDateInput/VDateInput.d.ts +12 -21
  87. package/dist/components/VDateInput/VDateInput.js.map +1 -1
  88. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  89. package/dist/components/VDateInput/mask.d.ts +14 -59
  90. package/dist/components/VDateInput/mask.js.map +1 -1
  91. package/dist/components/VDatePicker/VDatePicker.d.ts +6 -12
  92. package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
  93. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  94. package/dist/components/VDatePicker/keyboard.d.ts +10 -42
  95. package/dist/components/VDatePicker/keyboard.js.map +1 -1
  96. package/dist/components/VDatePicker/names.d.ts +8 -22
  97. package/dist/components/VDatePicker/names.js.map +1 -1
  98. package/dist/components/VDialog/VDialog.d.ts +5 -10
  99. package/dist/components/VDialog/VDialog.js.map +1 -1
  100. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +51 -53
  101. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  102. package/dist/components/VDialog/VDialogAlert.d.ts +2 -3
  103. package/dist/components/VDialog/VDialogAlert.js.map +1 -1
  104. package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  105. package/dist/components/VFileInput/VFileInput.d.ts +11 -37
  106. package/dist/components/VFileInput/VFileInput.js.map +1 -1
  107. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  108. package/dist/components/VFileInput/truncate.d.ts +3 -23
  109. package/dist/components/VFileInput/truncate.js.map +1 -1
  110. package/dist/components/VFilePicker/VFilePicker.d.ts +15 -65
  111. package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
  112. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  113. package/dist/components/VFilePicker/fileKind.js.map +1 -1
  114. package/dist/components/VHotkeys/VHotkeys.d.ts +6 -15
  115. package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
  116. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
  117. package/dist/components/VHotkeys/platform.d.ts +10 -49
  118. package/dist/components/VHotkeys/platform.js.map +1 -1
  119. package/dist/components/VIcon/VIcon.d.ts +7 -19
  120. package/dist/components/VIcon/VIcon.js.map +1 -1
  121. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  122. package/dist/components/VIcon/iconProps.d.ts +5 -19
  123. package/dist/components/VIcon/iconProps.js.map +1 -1
  124. package/dist/components/VIcon/resolver.d.ts +15 -43
  125. package/dist/components/VIcon/resolver.js.map +1 -1
  126. package/dist/components/VIcon/types.d.ts +7 -34
  127. package/dist/components/VIconButton/VIconButton.d.ts +2 -3
  128. package/dist/components/VIconButton/VIconButton.js.map +1 -1
  129. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  130. package/dist/components/VInput/VInput.d.ts +14 -29
  131. package/dist/components/VInput/VInput.js.map +1 -1
  132. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  133. package/dist/components/VInput/VInputGroup.js.map +1 -1
  134. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +34 -50
  135. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  136. package/dist/components/VInput/context.js.map +1 -1
  137. package/dist/components/VInputOTP/VInputOTP.d.ts +8 -16
  138. package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
  139. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +55 -63
  140. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
  141. package/dist/components/VMenu/VMenu.js.map +1 -1
  142. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  143. package/dist/components/VMenu/VMenuGroup.d.ts +1 -6
  144. package/dist/components/VMenu/VMenuGroup.js.map +1 -1
  145. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  146. package/dist/components/VMenu/VMenuItem.d.ts +0 -5
  147. package/dist/components/VMenu/VMenuItem.js.map +1 -1
  148. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  149. package/dist/components/VMenu/VMenuPanel.d.ts +1 -6
  150. package/dist/components/VMenu/VMenuPanel.js.map +1 -1
  151. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +2 -2
  152. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  153. package/dist/components/VMenu/VMenuSeparator.js.map +1 -1
  154. package/dist/components/VMenu/VMenuSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  155. package/dist/components/VMenu/context.d.ts +3 -11
  156. package/dist/components/VMenu/context.js.map +1 -1
  157. package/dist/components/VPagination/VPagination.d.ts +9 -20
  158. package/dist/components/VPagination/VPagination.js.map +1 -1
  159. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  160. package/dist/components/VPopover/VPopover.d.ts +7 -18
  161. package/dist/components/VPopover/VPopover.js.map +1 -1
  162. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js +23 -27
  163. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  164. package/dist/components/VProgressCircular/VProgressCircular.d.ts +2 -6
  165. package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
  166. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
  167. package/dist/components/VProgressLinear/VProgressLinear.d.ts +4 -15
  168. package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
  169. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  170. package/dist/components/VRadio/VRadio.d.ts +1 -5
  171. package/dist/components/VRadio/VRadio.js.map +1 -1
  172. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  173. package/dist/components/VSeparator/VSeparator.d.ts +2 -15
  174. package/dist/components/VSeparator/VSeparator.js.map +1 -1
  175. package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  176. package/dist/components/VSideNavigation/VSideNavigation.d.ts +1 -1
  177. package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
  178. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
  179. package/dist/components/VSideNavigation/VSideNavigationGroup.d.ts +1 -5
  180. package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
  181. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  182. package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +1 -5
  183. package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
  184. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +80 -75
  185. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  186. package/dist/components/VSideNavigation/VSideNavigationSeparator.js.map +1 -1
  187. package/dist/components/VSideNavigation/context.js.map +1 -1
  188. package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +6 -19
  189. package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
  190. package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  191. package/dist/components/VSlider/VSlider.d.ts +5 -13
  192. package/dist/components/VSlider/VSlider.js.map +1 -1
  193. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  194. package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
  195. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  196. package/dist/components/VSnackbar/state.d.ts +4 -17
  197. package/dist/components/VSnackbar/state.js.map +1 -1
  198. package/dist/components/VSpinner/VSpinner.d.ts +3 -5
  199. package/dist/components/VSpinner/VSpinner.js.map +1 -1
  200. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
  201. package/dist/components/VSwitch/VSwitch.js.map +1 -1
  202. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  203. package/dist/components/VTabs/VTab.d.ts +1 -5
  204. package/dist/components/VTabs/VTab.js.map +1 -1
  205. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
  206. package/dist/components/VTabs/VTabPanel.js.map +1 -1
  207. package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  208. package/dist/components/VTabs/VTabs.d.ts +5 -17
  209. package/dist/components/VTabs/VTabs.js.map +1 -1
  210. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  211. package/dist/components/VTabs/context.js.map +1 -1
  212. package/dist/components/VTextarea/VTextarea.d.ts +13 -20
  213. package/dist/components/VTextarea/VTextarea.js.map +1 -1
  214. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  215. package/dist/components/VTimeInput/VTimeInput.d.ts +8 -21
  216. package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
  217. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  218. package/dist/components/VTimeInput/search.d.ts +3 -34
  219. package/dist/components/VTimeInput/search.js.map +1 -1
  220. package/dist/components/VTimePicker/VTimePicker.d.ts +9 -31
  221. package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
  222. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  223. package/dist/components/VTimePicker/limits.d.ts +18 -45
  224. package/dist/components/VTimePicker/limits.js.map +1 -1
  225. package/dist/components/VToast/VToast.js.map +1 -1
  226. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  227. package/dist/components/VToast/VToaster.js.map +1 -1
  228. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
  229. package/dist/components/VToast/state.d.ts +2 -7
  230. package/dist/components/VToast/state.js.map +1 -1
  231. package/dist/components/VToggle/VToggle.d.ts +5 -9
  232. package/dist/components/VToggle/VToggle.js.map +1 -1
  233. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
  234. package/dist/components/VToggle/VToggleItem.d.ts +1 -9
  235. package/dist/components/VToggle/VToggleItem.js.map +1 -1
  236. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  237. package/dist/components/VToggle/context.js.map +1 -1
  238. package/dist/components/VTooltip/VTooltip.d.ts +3 -16
  239. package/dist/components/VTooltip/VTooltip.js.map +1 -1
  240. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +2 -2
  241. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  242. package/dist/components/VTypography/VTypography.d.ts +4 -8
  243. package/dist/components/VTypography/VTypography.js.map +1 -1
  244. package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
  245. package/dist/composables/useAriaLabel.js.map +1 -1
  246. package/dist/composables/useClearable.d.ts +2 -8
  247. package/dist/composables/useClearable.js.map +1 -1
  248. package/dist/composables/useControlShape.js.map +1 -1
  249. package/dist/composables/useDetailsOpen.d.ts +2 -10
  250. package/dist/composables/useDetailsOpen.js.map +1 -1
  251. package/dist/composables/useFieldIds.js.map +1 -1
  252. package/dist/composables/useFieldPanel.d.ts +4 -17
  253. package/dist/composables/useFieldPanel.js.map +1 -1
  254. package/dist/composables/useFileDrop.js.map +1 -1
  255. package/dist/composables/useFileField.d.ts +1 -5
  256. package/dist/composables/useFileField.js.map +1 -1
  257. package/dist/composables/useFocusoutDismiss.js.map +1 -1
  258. package/dist/composables/useIconClickHandlers.d.ts +4 -19
  259. package/dist/composables/useIconClickHandlers.js.map +1 -1
  260. package/dist/composables/useInertLink.d.ts +1 -1
  261. package/dist/composables/useInertLink.js.map +1 -1
  262. package/dist/composables/useLiveAnnouncer.js.map +1 -1
  263. package/dist/composables/useMaskedField.d.ts +12 -27
  264. package/dist/composables/useMaskedField.js.map +1 -1
  265. package/dist/composables/usePopover.d.ts +3 -11
  266. package/dist/composables/usePopover.js.map +1 -1
  267. package/dist/composables/useProgressValue.js.map +1 -1
  268. package/dist/composables/useRootAttrs.d.ts +0 -9
  269. package/dist/composables/useRootAttrs.js +11 -4
  270. package/dist/composables/useRootAttrs.js.map +1 -1
  271. package/dist/composables/useSlotNodes.d.ts +3 -14
  272. package/dist/composables/useSlotNodes.js.map +1 -1
  273. package/dist/composables/useTextLimit.d.ts +4 -16
  274. package/dist/composables/useTextLimit.js.map +1 -1
  275. package/dist/composables/useTimer.d.ts +2 -15
  276. package/dist/composables/useTimer.js.map +1 -1
  277. package/dist/i18n/en.js.map +1 -1
  278. package/dist/i18n/fr.js.map +1 -1
  279. package/dist/i18n/state.d.ts +9 -38
  280. package/dist/i18n/state.js.map +1 -1
  281. package/dist/i18n/types.d.ts +19 -81
  282. package/dist/icons.d.ts +2 -12
  283. package/dist/tokens/css.d.ts +3 -16
  284. package/dist/tokens/css.js.map +1 -1
  285. package/dist/tokens/index.js.map +1 -1
  286. package/dist/tokens/primitives.js.map +1 -1
  287. package/dist/tokens/semantic.d.ts +1 -23
  288. package/dist/tokens/semantic.js.map +1 -1
  289. package/dist/tokens/themes/dark.js.map +1 -1
  290. package/dist/tokens/types.d.ts +2 -12
  291. package/dist/tokens/types.js.map +1 -1
  292. package/dist/types.d.ts +3 -21
  293. package/dist/utils/array.d.ts +2 -4
  294. package/dist/utils/array.js.map +1 -1
  295. package/dist/utils/arrowNav.d.ts +2 -26
  296. package/dist/utils/arrowNav.js.map +1 -1
  297. package/dist/utils/chip.d.ts +1 -8
  298. package/dist/utils/chip.js.map +1 -1
  299. package/dist/utils/clock.d.ts +16 -39
  300. package/dist/utils/clock.js.map +1 -1
  301. package/dist/utils/css.d.ts +5 -16
  302. package/dist/utils/css.js.map +1 -1
  303. package/dist/utils/date.d.ts +17 -60
  304. package/dist/utils/date.js.map +1 -1
  305. package/dist/utils/direction.d.ts +2 -9
  306. package/dist/utils/direction.js.map +1 -1
  307. package/dist/utils/env.d.ts +2 -15
  308. package/dist/utils/env.js.map +1 -1
  309. package/dist/utils/file.d.ts +8 -46
  310. package/dist/utils/file.js.map +1 -1
  311. package/dist/utils/focus.d.ts +1 -14
  312. package/dist/utils/focus.js +1 -6
  313. package/dist/utils/focus.js.map +1 -1
  314. package/dist/utils/hostWarns.d.ts +3 -8
  315. package/dist/utils/hostWarns.js.map +1 -1
  316. package/dist/utils/ids.d.ts +2 -7
  317. package/dist/utils/ids.js.map +1 -1
  318. package/dist/utils/matcher.d.ts +1 -8
  319. package/dist/utils/matcher.js.map +1 -1
  320. package/dist/utils/memo.d.ts +1 -11
  321. package/dist/utils/memo.js.map +1 -1
  322. package/dist/utils/number.d.ts +1 -7
  323. package/dist/utils/number.js.map +1 -1
  324. package/dist/utils/text.d.ts +4 -23
  325. package/dist/utils/text.js.map +1 -1
  326. package/dist/utils/time.d.ts +4 -12
  327. package/dist/utils/time.js.map +1 -1
  328. package/dist/utils/vnode.d.ts +0 -10
  329. package/dist/utils/vnode.js.map +1 -1
  330. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"VTimePicker.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VTimePicker/VTimePicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @keyboard @core\n/**\n * An inline clock for choosing a time, the hour-and-minute counterpart of VDatePicker.\n * VTimeInput does no more than dress it in a field and a popover; it is usable on its own.\n *\n * Three parts: two large numerals saying what has been chosen and switching between hours\n * and minutes, the AM/PM toggle beside them on a 12-hour clock, and the face below, where a\n * time is POINTED AT rather than stepped through.\n *\n * Nothing native covers choosing a value by angle, so the JS does two things — turning a\n * point on the face into a time (the face measured once, then pure trigonometry in\n * `utils/clock`) and implementing a slider's keyboard.\n *\n * Everything one SEES is CSS: the numerals are placed around the circle and the hand turned,\n * both from one unitless turn fraction set inline.\n *\n * Exactly ONE focusable element on the face, announced as a slider. The numerals are\n * `aria-hidden` markers reached by angle rather than one cell at a time, which is why that\n * single element has to carry the whole spoken value.\n */\n\nimport { computed, inject, ref, watchEffect } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VToggle from '../VToggle/VToggle.vue'\nimport type { ToggleModelValue } from '../VToggle/VToggle.vue'\nimport VToggleItem from '../VToggle/VToggleItem.vue'\nimport { formatTime, hourCycleFor, parseTime } from '../../utils/time'\nimport {\n DIAL_DEAD_ZONE,\n DIAL_INNER_THRESHOLD,\n angleToIndex,\n dialIndexToHour24,\n distanceFraction,\n hour24ToDial,\n hourWithMeridiem,\n snapMinute,\n to12h,\n to24h,\n} from '../../utils/clock'\nimport type { HourFormat, Meridiem } from '../../utils/time'\nimport {\n allowedMinutesFor,\n firstAllowed,\n isHourAllowed,\n isTimeAllowed,\n limitsProblem,\n minuteGrid,\n minuteInterval,\n nearestAllowedMinute,\n resolveLimits,\n} from './limits'\nimport type { TimePickerAllowed } from './limits'\nimport { pad2 } from '../../utils/text'\nimport { isDev } from '../../utils/env'\nimport { hostWarnsKey } from '../../utils/hostWarns'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages, useResolvedLocale } from '../../i18n/state'\n\n/** Whether the face shows a 12- or a 24-hour clock. */\nexport type TimePickerFormat = HourFormat\n\n/** Which of the two halves of a time is being adjusted. */\ntype TimePickerStep = 'hour' | 'minute'\n\ninterface TimePickerProps {\n /**\n * Whether the face shows a 12- or a 24-hour clock. Left out, the reader's language\n * decides, which is almost always what one wants.\n */\n format?: TimePickerFormat\n /**\n * A BCP 47 locale, which decides the clock. It TAKES PRECEDENCE over the design\n * system's global locale and falls back to it, which is why it has no literal default:\n * `undefined` has to stay recognizable for the global locale to have its chance.\n */\n locale?: string\n /**\n * The interval the minutes snap to, both when dragging and with the arrow keys. The face\n * prints only the minutes it can reach, so a step of a quarter of an hour marks four.\n */\n minuteStep?: number\n /**\n * The earliest time that can be chosen, inclusive, as a canonical 24-hour `'HH:mm'`.\n * What it rules out is LEFT OFF the face, the way `minuteStep` leaves off the minutes\n * it cannot reach: the clock prints what can be chosen and nothing else.\n */\n min?: string\n /** The latest time that can be chosen, inclusive, written like `min`. */\n max?: string\n /**\n * Which hours can be chosen: the list of them, or a rule answering for one. The hour\n * handed to a rule is always the 24-hour one, whichever clock is on display, so the\n * same rule holds on both faces. The hours it leaves out are not printed.\n */\n allowedHours?: TimePickerAllowed\n /**\n * Which minutes can be chosen: the list of them, or a rule answering for one. The\n * minutes it leaves out are not printed.\n */\n allowedMinutes?: TimePickerAllowed\n /**\n * Makes the whole clock unusable: the hand cannot be moved, the half-day cannot be\n * changed, and everything greys out through the colour tokens.\n */\n disabled?: boolean\n /**\n * Shows the time without letting it be changed. The face keeps its focus and the two\n * numerals still switch between the hour and the minutes, so the value can be read in\n * full. That is what separates it from `disabled`.\n */\n readonly?: boolean\n /**\n * The accessible name of the whole clock, its two numerals and its face together. It\n * falls back to the dictionary, and a consumer `aria-label` wins over it. The face\n * keeps its own name, which says whether the hand is on the hour or the minutes.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<TimePickerProps>(), {\n format: undefined,\n locale: undefined,\n minuteStep: 1,\n min: undefined,\n max: undefined,\n allowedHours: undefined,\n allowedMinutes: undefined,\n disabled: false,\n readonly: false,\n label: undefined,\n})\n\n/**\n * The time, always as a 24-hour \"HH:mm\" string whatever clock is displayed. A consumer\n * therefore never has to know which clock the reader's language uses.\n *\n * With no value at all the clock shows midnight, or noon once PM has been chosen. It is\n * deliberately NOT the current time: reading the clock while rendering would make a page\n * drawn on a server disagree with the same page in the browser. A component that wants to\n * open on the current time sets it from a handler, which is what VTimeInput does.\n */\nconst model = defineModel<string | null>({ default: null })\n\nconst emit = defineEmits<{\n /**\n * The reader has FINISHED, carrying the time as it stands: the minutes were settled from\n * the keyboard. Releasing the pointer does not count: on a clock face, letting go of the\n * hand is how one stops adjusting it, not how one confirms. VTimeInput listens to it to\n * commit its draft and close its panel.\n */\n confirm: [value: string | null]\n}>()\n\ndefineSlots<{\n /** A strip at the foot of the clock, the place for actions such as Cancel and OK. */\n footer?(): unknown\n}>()\n\nconst m = useMessages()\n// @a11y\n// A roleless box cannot carry an accessible name (axe: aria-prohibited-attr),\n// so the root is a named group: the VCarousel viewport arrangement.\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.timePicker.label)\nconst resolvedLocale = useResolvedLocale(() => props.locale)\nconst resolvedFormat = computed<TimePickerFormat>(\n () => props.format ?? hourCycleFor(resolvedLocale.value),\n)\n\n/** The restrictions, resolved once for the whole render. */\nconst limits = computed(() => resolveLimits(props))\n\n/**\n * The hours the restrictions still leave something in, worked out once per set of\n * restrictions. Every hour on the face, both AM/PM buttons and every key ask the question,\n * and each answer walks a whole hour of minutes: asked afresh, a drag re-walked all 24 of\n * them at pointer rate.\n */\nconst availableHours = computed(() => {\n const hours = new Set<number>()\n for (let candidate = 0; candidate < 24; candidate += 1)\n if (isHourAllowed(candidate, limits.value)) hours.add(candidate)\n return hours\n})\n\n/** Whether an hour is one the restrictions still leave something in. */\nconst isAvailableHour = (candidate: number) => availableHours.value.has(candidate)\n\n// @devwarn\n// Silent inside a host that forwards these props and reports on them itself (utils/hostWarns).\nif (isDev && !inject(hostWarnsKey, false)) {\n watchEffect(() => {\n if (!Number.isInteger(props.minuteStep) || props.minuteStep < 1 || 60 % props.minuteStep !== 0)\n console.warn(`[VTimePicker] minuteStep ${props.minuteStep} — a divisor of 60 is expected.`)\n const problem = limitsProblem(limits.value)\n if (problem) console.warn(`[VTimePicker] ${problem}`)\n })\n}\n\n/** Which of the two is being adjusted. It starts on the hour and moves on by itself. */\nconst step = ref<TimePickerStep>('hour')\n\n// With no value the clock shows the first hour of the half of the day chosen so far, which\n// is midnight until PM is picked. It is also the hour a time started from the MINUTES is\n// written with, so that choice is not lost when the reader begins there.\nconst parts = computed(\n () =>\n parseTime(model.value) ?? {\n hour: resolvedFormat.value === '12h' ? to24h(12, pendingMeridiem.value) : 0,\n minute: 0,\n },\n)\nconst hour = computed(() => parts.value.hour)\nconst minute = computed(() => parts.value.minute)\n\n/*\n * The two writers are the SINGLE cut-off point for the restrictions: the pointer, the six\n * keys and the AM/PM control all write through them and through nothing else, so not one\n * handler carries a guard of its own.\n */\n\n/**\n * Writing the hour. An hour with nothing left in it has no minute to fall back on, which\n * is what refuses it; one that is only PARTLY available pulls the minutes to what it does\n * allow, since `min: '09:30'` must not be left holding 09:00.\n *\n * The minute in force is kept whenever it is allowed, which is also what leaves an\n * unrestricted clock exactly as it was: a value off the step, 09:07 on a quarter-hour\n * face, belongs to the consumer and is not ours to round.\n */\nfunction setHour(value: number | null) {\n // The two writers below are the ONLY way the value ever changes — pointer, keys and the\n // half-day control all come through them — so the two states are refused here and\n // nowhere else. Changing which face is shown stays allowed under `readonly`: reading\n // the minutes is not changing them.\n if (props.disabled || props.readonly) return\n if (value === null) return\n const minutes = isTimeAllowed(value, minute.value, limits.value)\n ? minute.value\n : nearestAllowedMinute(value, minute.value, limits.value)\n if (minutes === null) return\n model.value = formatTime(value, minutes)\n}\n\n/** Writing the minutes. Null is \"there was nowhere to go\", which every key can produce. */\nfunction setMinute(value: number | null) {\n if (props.disabled || props.readonly) return\n if (value === null || !isTimeAllowed(hour.value, value, limits.value)) return\n model.value = formatTime(hour.value, value)\n}\n\n// @a11y\n/*\n * Which step the clock is on is otherwise carried by the face's own name alone, and a\n * change of name is not reliably announced. A politely announced region says it a second\n * time. The wording has no prop: the dictionary is where it is changed.\n *\n * It is written HERE, by the function the reader's own gestures go through, rather than by\n * a watcher on the step: putting the clock back on the hour between two openings — what\n * `reset` does — must stay silent, and a watcher could not tell the two apart.\n */\nconst liveMessage = ref('')\n\nfunction setStep(next: TimePickerStep) {\n step.value = next\n liveMessage.value =\n next === 'minute' ? m.value.timePicker.choosingMinutes : m.value.timePicker.choosingHour\n}\n\n/** One numeral on the face. */\ninterface DialCell {\n key: string\n /** What is printed. */\n label: string\n /** Where it sits, as a fraction of a full turn from twelve o'clock. */\n turn: number\n /** Which of the two circles it belongs to, a 24-hour face having an inner one. */\n ring: 'outer' | 'inner'\n /** Whether it is the value currently being pointed at. */\n selected: boolean\n}\n\n/**\n * The hour a position on the face stands for. On a 12-hour clock that is what the half of\n * the day in force turns it into, and it is the 24-hour hour either way: the one the\n * restrictions answer about, and the one written.\n */\nfunction hourAt(index: number, ring: 'outer' | 'inner'): number {\n return resolvedFormat.value === '24h'\n ? dialIndexToHour24(index, ring)\n : to24h(index === 0 ? 12 : index, currentMeridiem.value)\n}\n\n/**\n * The minutes the face prints. A numeral one can point at and not land on is a lie, which\n * the hand tells the moment it settles beside it rather than on it, so a marker is only\n * ever a minute `minuteStep` actually reaches AND the restrictions allow.\n *\n * How MANY of them is the other half of the question: sixty numerals would be unreadable,\n * so a step fine enough to offer more than twelve values keeps the five-minute grid a\n * clock is read on — minus, again, whatever it cannot reach, a step of two printing ten\n * past and not five past.\n */\nconst minuteMarks = computed(() => {\n const interval = minuteInterval(props.minuteStep)\n const spacing = 60 / interval <= 12 ? interval : 5\n const marks: number[] = []\n for (let minutes = 0; minutes < 60; minutes += spacing)\n if (minutes % interval === 0 && isTimeAllowed(hour.value, minutes, limits.value))\n marks.push(minutes)\n return marks\n})\n\n/*\n * The face prints what can be chosen and nothing else — the minute step's own rule, held\n * to for all four restrictions rather than for one of them. A numeral that is only there\n * to be refused says nothing the missing numeral does not say better, and it says it in\n * grey text a screen reader has no way to reach: the markers are `aria-hidden`, so what\n * the restrictions leave is spoken by the face's own value and by nothing else.\n */\nconst cells = computed<DialCell[]>(() => {\n if (step.value === 'minute') {\n return minuteMarks.value.map((minutes) => ({\n key: `m-${minutes}`,\n label: pad2(minutes),\n turn: minutes / 60,\n ring: 'outer' as const,\n selected: minute.value === minutes,\n }))\n }\n // Each position's hour is worked out ONCE, since on a 12-hour face that takes the half of\n // the day in force. The selection there still compares numerals and not hours: an empty\n // clock holding a PM choice sits on midnight, and its hand points at the 12 all the same.\n const twelve = resolvedFormat.value === '12h'\n const shown12 = to12h(hour.value).hour\n const rings: DialCell['ring'][] = twelve ? ['outer'] : ['outer', 'inner']\n const out: DialCell[] = []\n for (const ring of rings)\n for (let i = 0; i < 12; i += 1) {\n const target = hourAt(i, ring)\n if (!isAvailableHour(target)) continue\n const numeral = i === 0 ? 12 : i\n out.push({\n key: `${ring === 'outer' ? 'o' : 'i'}-${i}`,\n label: ring === 'inner' ? pad2(target) : String(numeral),\n turn: i / 12,\n ring,\n selected: twelve ? shown12 === numeral : hour.value === target,\n })\n }\n return out\n})\n\nconst handTurn = computed(() => {\n if (step.value === 'minute') return minute.value / 60\n const index =\n resolvedFormat.value === '24h' ? hour24ToDial(hour.value).index : to12h(hour.value).hour % 12\n return index / 12\n})\n\nconst handRing = computed(() =>\n step.value === 'hour' && resolvedFormat.value === '24h' ? hour24ToDial(hour.value).ring : 'outer',\n)\n\n/**\n * Whether the hand points at a minute that has no marker of its own. The tip is then\n * drawn small: at full size it would cover the two neighbouring markers and read as\n * pointing at neither.\n */\nconst handMinor = computed(\n () => step.value === 'minute' && !minuteMarks.value.includes(minute.value),\n)\n\n/** The hour as the two large numerals show it, on whichever clock is displayed. */\nconst displayHourText = computed(() =>\n pad2(resolvedFormat.value === '12h' ? to12h(hour.value).hour : hour.value),\n)\n\n/**\n * Which half of the day is chosen while NO time is set at all. The AM/PM control always\n * needs a value — it refuses to have none — and \"nothing\" is not one.\n *\n * It starts at AM rather than at whatever the current time happens to be, which keeps the\n * component's first render identical on a server and in a browser, and its tests free of\n * a clock. VTimeInput keeps one for its own AM/PM button, on the same terms.\n */\nconst pendingMeridiem = ref<Meridiem>('AM')\n\n/**\n * The half of the day in force: the value's own once there is one, the remembered choice\n * until then. Every hour written on a 12-hour face goes through it — without which a\n * reader who picks PM on an empty clock and then an hour would get the morning back, the\n * value having no half-day of its own to read.\n */\nconst currentMeridiem = computed<Meridiem>(() =>\n model.value ? to12h(hour.value).meridiem : pendingMeridiem.value,\n)\n\nconst meridiemModel = computed<ToggleModelValue>({\n get: () => currentMeridiem.value,\n set: (value) => {\n const meridiem: Meridiem = value === 'PM' ? 'PM' : 'AM'\n pendingMeridiem.value = meridiem\n // With no time set there is nothing to convert, so the choice is simply REMEMBERED\n // and applies to the first time chosen.\n if (!model.value) return\n // What is being chosen here is the HALF OF THE DAY, so the hour gives way to it: kept\n // when the new half allows it, and otherwise the first hour of that half that does.\n // Refusing the write instead would leave the control snapping straight back, since it\n // reads the half of the day off the value.\n const wanted = hourWithMeridiem(hour.value, meridiem)\n setHour(\n isAvailableHour(wanted)\n ? wanted\n : firstAllowed(12, (i) => to24h(i, meridiem), isAvailableHour),\n )\n },\n})\n\n/**\n * Whether each half of the day still holds an hour one could choose. An empty one takes its\n * button with it: a control that can only ever be refused is worse than no control.\n */\nconst meridiemAvailable = computed<Record<Meridiem, boolean>>(() => ({\n AM: firstAllowed(12, (i) => to24h(i, 'AM'), isAvailableHour) !== null,\n PM: firstAllowed(12, (i) => to24h(i, 'PM'), isAvailableHour) !== null,\n}))\n\n// @a11y\n// The entire spoken value of the face. The numerals are hidden from screen\n// readers, so these four attributes are the ONLY thing assistive technology has: what the\n// value is, what its bounds are, and how to say it.\nconst ariaValueNow = computed(() => {\n if (step.value === 'minute') return minute.value\n return resolvedFormat.value === '12h' ? to12h(hour.value).hour : hour.value\n})\nconst ariaValueMin = computed(() =>\n step.value === 'minute' ? 0 : resolvedFormat.value === '12h' ? 1 : 0,\n)\nconst ariaValueMax = computed(() =>\n step.value === 'minute' ? 59 : resolvedFormat.value === '12h' ? 12 : 23,\n)\n// On a 12-hour face the half of the day is spoken with the hour: the AM/PM control is not\n// on the slider's way, and \"9 o'clock\" alone is two different times.\nconst ariaValueText = computed(() => {\n const m_ = m.value.timePicker\n if (step.value === 'minute') return m_.minutesValue(minute.value)\n const spoken = m_.hourValue(ariaValueNow.value)\n if (resolvedFormat.value === '24h') return spoken\n return `${spoken} ${currentMeridiem.value === 'PM' ? m_.pm : m_.am}`\n})\n\n/**\n * A step has been settled: after the hour come the minutes, and after the minutes the\n * whole thing is confirmed — but by KEYBOARD only, for the reason given on the emit.\n */\nfunction settleStep(via: 'pointer' | 'keyboard') {\n if (step.value === 'hour') setStep('minute')\n else if (via === 'keyboard') emit('confirm', model.value)\n}\n\n// Pointing at the face, by click or by drag.\nconst faceEl = ref<HTMLElement | null>(null)\nconst dragging = ref(false)\n/**\n * Whether anything in the gesture under way landed on a numeral. It is the whole gesture\n * and not its last point: a drag that chose an hour and wandered off the ring before\n * being released has still chosen one, and the step follows the value rather than the\n * pixel let go of.\n */\nlet landed = false\n\n/**\n * Where a point on the face lands, and whether there was anything there at all. A\n * position the restrictions took a numeral off holds nothing to aim at, so the gesture\n * does nothing whatever: no value, and — through the answer returned here — no step moved\n * on either. The pointer catches nothing it was not aimed at.\n *\n * The question asked is the FACE's, `isAvailableHour`/`isTimeAllowed` being the pair\n * `cells` prints from, so \"there is a numeral here\" and \"this can be written\" cannot come\n * apart. The write itself still goes through `setHour`/`setMinute` and their own guards,\n * which is why `readonly` is refused THERE and not here: a frozen clock is still one\n * whose numerals are real, and a tap on one moves the step on as it always did.\n */\nfunction applyPoint(event: PointerEvent): boolean {\n const face = faceEl.value\n // A disabled clock aims at nothing, so no step is moved on either: its header buttons\n // are disabled for the same reason.\n if (!face || props.disabled) return false\n // Measuring the face is safe here: this runs from a handler, hence in a browser, never\n // during a render.\n const rect = face.getBoundingClientRect()\n const dx = event.clientX - (rect.left + rect.width / 2)\n const dy = event.clientY - (rect.top + rect.height / 2)\n // `<=` so that the exact centre is refused even on a face measured at zero.\n if (Math.hypot(dx, dy) <= DIAL_DEAD_ZONE * (rect.width / 2)) return false\n if (step.value === 'minute') {\n // The step's own snapping stands: a point between two markers has always been pulled\n // to the one it is nearest. What it is pulled to still has to BE on the face.\n const minutes = snapMinute(angleToIndex(dx, dy, 60), props.minuteStep)\n if (!isTimeAllowed(hour.value, minutes, limits.value)) return false\n setMinute(minutes)\n return true\n }\n const index = angleToIndex(dx, dy, 12)\n const ring =\n resolvedFormat.value === '24h' &&\n distanceFraction(dx, dy, rect.width / 2) < DIAL_INNER_THRESHOLD\n ? 'inner'\n : 'outer'\n const target = hourAt(index, ring)\n if (!isAvailableHour(target)) return false\n setHour(target)\n return true\n}\n\nfunction onPointerdown(event: PointerEvent) {\n // @fallback\n // Capturing the pointer is what keeps the drag alive when it wanders off the face. It\n // is wrapped because a pointer event fired by a TEST refers to no real pointer, and the\n // call then throws. Failing to capture merely means the drag stops at the edge, which\n // no test is checking.\n try {\n faceEl.value?.setPointerCapture(event.pointerId)\n } catch {\n /* a synthetic pointer: nothing to capture */\n }\n dragging.value = true\n landed = applyPoint(event)\n}\n\nfunction onPointermove(event: PointerEvent) {\n if (dragging.value && applyPoint(event)) landed = true\n}\n\nfunction onPointerup() {\n if (!dragging.value) return\n dragging.value = false\n // A gesture that never landed on a numeral has chosen nothing, so there is nothing to\n // move on FROM: the reader is left on the face they aimed at, looking at what they missed.\n if (landed) settleStep('pointer')\n}\n\nfunction onPointercancel() {\n dragging.value = false\n}\n\n// @keyboard @a11y\n// The keyboard a slider is expected to have.\n/*\n * Every key below walks until it finds something it MAY land on, rather than stopping at\n * the first thing it may not: with scattered hours a key that stopped would die at the\n * first hole and never reach what lies past it. Against a bound there is nothing past the\n * hole, so the same walk comes back empty and the key holds still, which is what a bound\n * means. Restrict nothing and the first candidate answers: the walk is then the plain step\n * it has always been.\n */\nfunction moveHour(delta: number) {\n if (resolvedFormat.value === '24h') {\n setHour(firstAllowed(23, (i) => (hour.value + delta * i + 24) % 24, isAvailableHour))\n return\n }\n // On a 12-hour face the hours cycle from 1 to 12 WITHIN the current half of the day:\n // passing midday is done on the AM/PM control, not by walking the hand past twelve.\n const hour12 = to12h(hour.value).hour\n setHour(\n firstAllowed(\n 11,\n (i) => to24h(((hour12 - 1 + delta * i + 12) % 12) + 1, currentMeridiem.value),\n isAvailableHour,\n ),\n )\n}\n\n/** The first or the last hour the face allows, on whichever clock it is showing. */\nfunction edgeHour(edge: 'first' | 'last'): number | null {\n if (resolvedFormat.value === '24h')\n return firstAllowed(24, (i) => (edge === 'first' ? i - 1 : 24 - i), isAvailableHour)\n return firstAllowed(\n 12,\n (i) => to24h(edge === 'first' ? i : 13 - i, currentMeridiem.value),\n isAvailableHour,\n )\n}\n\n/**\n * The minute a key lands on: the first step of the grid at least `distance` minutes away,\n * that way round, and then the next one the hour allows.\n *\n * It walks the GRID rather than adding a step and rounding: from a minute off the grid,\n * 09:07 on a quarter-hour face, rounding sent the down arrow to :45 and let PageUp round\n * straight back to where it started. Past the end of the hour it wraps, onto the grid's own\n * first step, which is also what keeps a step that does not divide sixty on its grid.\n */\nfunction minuteToward(direction: 1 | -1, distance: number): number | null {\n const grid = minuteGrid(props.minuteStep)\n const target = (((minute.value + direction * distance) % 60) + 60) % 60\n const n = grid.length\n // The first step at or past the target that way round, the grid's own end when none is.\n let start = direction > 0 ? 0 : n - 1\n for (let k = 0; k < n; k += 1) {\n const i = direction > 0 ? k : n - 1 - k\n if (direction > 0 ? grid[i]! >= target : grid[i]! <= target) {\n start = i\n break\n }\n }\n return firstAllowed(\n n,\n (i) => grid[(((start + direction * (i - 1)) % n) + n) % n]!,\n (candidate) => isTimeAllowed(hour.value, candidate, limits.value),\n )\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n // The face of a disabled clock is out of the tab order, but `focus()` can still put the\n // focus there: nothing it is sent may move the step on or confirm.\n if (props.disabled) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n settleStep('keyboard')\n return\n }\n // The arrows are NOT flipped in a right-to-left page: a clock face is never mirrored —\n // clockwise means the same thing everywhere — so forward stays forward.\n const delta =\n event.key === 'ArrowUp' || event.key === 'ArrowRight'\n ? 1\n : event.key === 'ArrowDown' || event.key === 'ArrowLeft'\n ? -1\n : 0\n if (step.value === 'hour') {\n if (delta) moveHour(delta)\n else if (event.key === 'Home') setHour(edgeHour('first'))\n else if (event.key === 'End') setHour(edgeHour('last'))\n else return\n event.preventDefault()\n return\n }\n if (delta) setMinute(minuteToward(delta, 1))\n else if (event.key === 'PageUp') setMinute(minuteToward(1, 5))\n else if (event.key === 'PageDown') setMinute(minuteToward(-1, 5))\n // The list is built in these two branches alone: the arrows walk to a neighbour and never\n // need the whole of it.\n else if (event.key === 'Home') setMinute(allowedMinutesFor(hour.value, limits.value)[0] ?? null)\n else if (event.key === 'End')\n setMinute(allowedMinutesFor(hour.value, limits.value).at(-1) ?? null)\n else return\n event.preventDefault()\n}\n\ndefineExpose({\n /** Moves the focus onto the clock face, which is what the arrow keys drive. */\n focus: (options?: FocusOptions) => faceEl.value?.focus(options),\n /**\n * Puts the clock back on the hour and clears the announcement, WITHOUT announcing\n * anything itself. A component holding this one in a panel calls it as the panel opens\n * and closes, so that every visit starts on the hour.\n */\n reset: () => {\n step.value = 'hour'\n liveMessage.value = ''\n },\n})\n</script>\n\n<template>\n <div\n class=\"v-time-picker\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-step=\"step\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <!-- The two large numerals switch between adjusting the hour and the minutes. The one\n being adjusted takes the accent tone, which on a quiet button shows as the colour\n of the numeral rather than as a filled background.\n\n The choice of half-day sits beside them, on the same row: it is read as part of\n the time itself rather than as a property of the face. On a 24-hour clock there\n is no such choice, and the row then holds the numerals alone. -->\n <div class=\"v-time-picker-header\">\n <div class=\"v-time-picker-time\">\n <VButton\n class=\"v-time-picker-cell\"\n variant=\"ghost\"\n size=\"lg\"\n :tone=\"step === 'hour' ? 'accent' : 'neutral'\"\n :aria-pressed=\"step === 'hour' ? 'true' : 'false'\"\n :aria-label=\"`${displayHourText}, ${m.timePicker.selectHour}`\"\n :disabled=\"disabled\"\n @click=\"setStep('hour')\"\n >\n {{ displayHourText }}\n </VButton>\n <span class=\"v-time-picker-sep\" aria-hidden=\"true\">:</span>\n <VButton\n class=\"v-time-picker-cell\"\n variant=\"ghost\"\n size=\"lg\"\n :tone=\"step === 'minute' ? 'accent' : 'neutral'\"\n :aria-pressed=\"step === 'minute' ? 'true' : 'false'\"\n :aria-label=\"`${pad2(minute)}, ${m.timePicker.selectMinutes}`\"\n :disabled=\"disabled\"\n @click=\"setStep('minute')\"\n >\n {{ pad2(minute) }}\n </VButton>\n </div>\n\n <VToggle\n v-if=\"resolvedFormat === '12h'\"\n v-model=\"meridiemModel\"\n class=\"v-time-picker-meridiem\"\n mandatory\n variant=\"outline\"\n selected-variant=\"soft\"\n orientation=\"vertical\"\n size=\"sm\"\n :label=\"m.timePicker.meridiem\"\n :disabled=\"disabled || readonly\"\n >\n <VToggleItem value=\"AM\" :label=\"m.timePicker.am\" :disabled=\"!meridiemAvailable.AM\" />\n <VToggleItem value=\"PM\" :label=\"m.timePicker.pm\" :disabled=\"!meridiemAvailable.PM\" />\n </VToggle>\n </div>\n\n <div\n ref=\"faceEl\"\n role=\"slider\"\n :tabindex=\"disabled ? -1 : 0\"\n class=\"v-time-picker-face\"\n :aria-label=\"step === 'hour' ? m.timePicker.hour : m.timePicker.minutes\"\n :aria-valuemin=\"ariaValueMin\"\n :aria-valuemax=\"ariaValueMax\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuetext=\"ariaValueText\"\n :data-dragging=\"dragging ? '' : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :aria-readonly=\"readonly ? 'true' : undefined\"\n @pointerdown=\"onPointerdown\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @keydown=\"onKeydown\"\n >\n <div\n class=\"v-time-picker-hand\"\n aria-hidden=\"true\"\n :data-ring=\"handRing\"\n :data-minor=\"handMinor ? '' : undefined\"\n :style=\"{ '--dial-turn': String(handTurn) }\"\n />\n <span class=\"v-time-picker-center\" aria-hidden=\"true\" />\n <span\n v-for=\"cell in cells\"\n :key=\"cell.key\"\n class=\"v-time-picker-number\"\n aria-hidden=\"true\"\n :data-ring=\"cell.ring\"\n :data-selected=\"cell.selected ? '' : undefined\"\n :style=\"{ '--dial-turn': String(cell.turn) }\"\n >{{ cell.label }}</span\n >\n </div>\n\n <div class=\"v-visually-hidden\" aria-live=\"polite\">{{ liveMessage }}</div>\n\n <div v-if=\"$slots.footer\" class=\"v-time-picker-footer\"><slot name=\"footer\" /></div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The same root as VDatePicker's: an inline column that sizes to its content and pads\n itself, so the clock reads the same on a page as in VTimeInput's panel, which adds no\n padding of its own. The gap is the clock's, its parts being larger. */\n .v-time-picker {\n display: inline-flex;\n flex-direction: column;\n gap: var(--vectis-space-4);\n padding: var(--vectis-space-3);\n font-family: var(--vectis-text-family);\n color: var(--vectis-color-text);\n }\n\n /* The numerals and the choice of half-day on one row, the second beside the first. The\n gap is logical, so the control follows the reading direction; the numerals inside it\n stay in a group of their own, which is what keeps the direction they force off it.\n\n The centring is what places the row whether or not that control is rendered — a\n 24-hour clock has none, and the numerals then come out centred on their own. */\n .v-time-picker-header {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--vectis-space-3);\n }\n\n /* A time written in figures always reads hours then minutes, in every language. Forcing\n the direction here is what stops bidirectional reordering from swapping the two in a\n right-to-left page. */\n .v-time-picker-time {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--vectis-space-2);\n direction: ltr;\n }\n\n /* The two large numerals are ordinary quiet buttons. What is overridden here is the\n \"large numeral\" look alone — the width and the type; the height, the states, the\n focus ring and the transitions all come from the button itself.\n\n The type is the `heading-1` recipe taken WHOLE, and the colon the `heading-2` one:\n read in halves, a consumer repointing `--vectis-text-heading-1-weight` would move every\n heading but these two numerals.\n\n The selector is qualified by an attribute that button always renders, which is what\n makes it win whatever order the two sheets end up in. */\n .v-time-picker-cell[data-size] {\n width: var(--control-height);\n font-size: var(--vectis-text-heading-1-size);\n font-weight: var(--vectis-text-heading-1-weight);\n line-height: var(--vectis-text-heading-1-leading);\n letter-spacing: var(--vectis-text-heading-1-tracking);\n /* Figures of equal width, so that going from \"11\" to \"00\" does not shift the cell. */\n font-variant-numeric: tabular-nums;\n }\n\n .v-time-picker-sep {\n font-size: var(--vectis-text-heading-2-size);\n font-weight: var(--vectis-text-heading-2-weight);\n line-height: var(--vectis-text-heading-2-leading);\n color: var(--vectis-color-text);\n user-select: none;\n }\n\n .v-time-picker-meridiem {\n flex: none;\n }\n\n .v-time-picker-face {\n position: relative;\n /* The row above is wider than the face as soon as the half-day control is rendered,\n and the column is only as wide as its widest child: without this the clock would\n hang at the start edge of that row instead of under the middle of it. The face\n itself stays physical — a clock is never mirrored. */\n align-self: center;\n inline-size: var(--vectis-control-size-time-picker-dial);\n block-size: var(--vectis-control-size-time-picker-dial);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-surface-muted);\n /* Dragging across the face IS how one chooses, so it must do nothing else: no\n scrolling under a finger, and no selecting the numerals as text. */\n touch-action: none;\n user-select: none;\n cursor: pointer;\n /* How far from the centre the numerals sit — the outer circle. The numerals and the\n hand both read it, and the inner circle redefines it for itself.\n\n TRAP — this is coupled to the threshold that decides which of the two circles a\n point belongs to, in `utils/clock`. Changing one without the other makes the face\n answer with the wrong ring, and no test can catch it: the unit tests measure\n nothing. */\n --dial-radius: calc(\n var(--vectis-control-size-time-picker-dial) / 2 -\n var(--vectis-control-size-time-picker-number) / 2\n );\n }\n\n .v-time-picker-face: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 /*\n * The numerals are placed around the circle by CSS trigonometry, from the fraction of a\n * turn each one was given — no coordinates are computed in code.\n *\n * The properties used are PHYSICAL on purpose, where the rest of the design system\n * prefers logical ones: a clock face is never mirrored, clockwise meaning the same thing\n * in every language.\n */\n .v-time-picker-number {\n position: absolute;\n left: calc(50% + var(--dial-radius) * sin(var(--dial-turn) * 1turn));\n top: calc(50% - var(--dial-radius) * cos(var(--dial-turn) * 1turn));\n translate: -50% -50%;\n inline-size: var(--vectis-control-size-time-picker-number);\n block-size: var(--vectis-control-size-time-picker-number);\n display: grid;\n place-items: center;\n border-radius: var(--vectis-radius-pill);\n /* The outer circle is set at the large body size: these numerals are read at arm's\n length on a phone, not scanned like a label. */\n font-size: var(--vectis-text-body-lg-size);\n color: var(--vectis-color-text);\n /* Above the hand, so that the numeral being pointed at reads ON its dot rather than\n being covered by it. */\n z-index: 1;\n pointer-events: none;\n }\n\n .v-time-picker-number[data-ring='inner'],\n .v-time-picker-hand[data-ring='inner'] {\n --dial-radius: calc(\n var(--vectis-control-size-time-picker-dial) / 2 -\n var(--vectis-control-size-time-picker-number) * 1.5\n );\n }\n\n .v-time-picker-number[data-ring='inner'] {\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text-muted);\n }\n\n .v-time-picker-number[data-selected] {\n color: var(--vectis-color-text-on-accent);\n }\n\n /*\n * The hand: a stroke anchored at the centre and turned by the same fraction of a turn.\n *\n * TRAP — the rotation is deliberately NOT animated. Going from 55 minutes to 0 takes the\n * angle from nearly a full turn back to none, and an interpolation would sweep the hand\n * all the way round anticlockwise. That is an accepted departure from the Material\n * design it follows otherwise.\n */\n .v-time-picker-hand {\n position: absolute;\n left: calc(50% - var(--vectis-control-size-time-picker-hand) / 2);\n bottom: 50%;\n inline-size: var(--vectis-control-size-time-picker-hand);\n block-size: var(--dial-radius);\n background: var(--vectis-color-accent);\n transform-origin: 50% 100%;\n rotate: calc(var(--dial-turn) * 1turn);\n }\n\n /* The dot at the tip of the hand. It is exactly the size of a numeral's cell, so it\n covers the one being pointed at — whose text turns to the colour that reads on it. */\n .v-time-picker-hand::before {\n content: '';\n position: absolute;\n top: 0;\n left: 50%;\n translate: -50% -50%;\n inline-size: var(--vectis-control-size-time-picker-number);\n block-size: var(--vectis-control-size-time-picker-number);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-accent);\n /* The ONE thing animated on the hand: the dot changing size between its two forms. It\n interpolates between two bounded values and can therefore never take a wrong path,\n unlike the rotation above. */\n transition:\n inline-size var(--vectis-duration-fast) var(--vectis-ease-default),\n block-size var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * On a minute with no marker of its own, the dot covers no numeral and at full size\n * would spill onto both neighbours, reading as pointing at neither. Shrunk, it becomes\n * the precise marker itself — which is also why the pale centre Material draws inside it\n * is dropped here: at this size it would turn the dot into a ring.\n *\n * TRAP — only the DOT's own size changes. Redefining the numeral size variable here\n * would also move the radius derived from it, and with it the hand's length and the\n * position of both circles.\n */\n .v-time-picker-hand[data-minor]::before {\n inline-size: var(--vectis-control-size-time-picker-hand-minor);\n block-size: var(--vectis-control-size-time-picker-hand-minor);\n }\n\n .v-time-picker-center {\n position: absolute;\n left: 50%;\n top: 50%;\n translate: -50% -50%;\n inline-size: var(--vectis-control-size-time-picker-center);\n block-size: var(--vectis-control-size-time-picker-center);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-accent);\n }\n\n /* Neither state is a pointer target. A read-only clock is still read, so it keeps the\n ordinary cursor; a disabled one refuses. */\n .v-time-picker[data-readonly] .v-time-picker-face {\n cursor: default;\n }\n\n .v-time-picker[data-disabled] .v-time-picker-face {\n cursor: not-allowed;\n }\n\n /*\n * A disabled clock greys out through the colour tokens and never through opacity: the\n * hand and its centre dot are the two accent-painted parts, and the numeral under the\n * dot is painted for an accent background it no longer has.\n *\n * The whole block sits AFTER the rules it overrides, every one of them at the same\n * specificity: this is one sheet, so its own order is what settles them.\n */\n .v-time-picker[data-disabled] .v-time-picker-hand,\n .v-time-picker[data-disabled] .v-time-picker-hand::before,\n .v-time-picker[data-disabled] .v-time-picker-center {\n background: var(--vectis-color-border-strong);\n }\n\n .v-time-picker[data-disabled] .v-time-picker-sep,\n .v-time-picker[data-disabled] .v-time-picker-number,\n .v-time-picker[data-disabled] .v-time-picker-number[data-selected] {\n color: var(--vectis-color-text-subtle);\n }\n\n /* The foot of the clock, separated from the face the way VDatePicker's is from its grid. */\n .v-time-picker-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-2);\n padding-block-start: var(--vectis-space-2);\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-time-picker-hand::before {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"VTimePicker.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VTimePicker/VTimePicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @keyboard @core\n/**\n * CSS places clock numerals and the hand. JavaScript maps pointer angles and keyboard steps to\n * a time because HTML has no interactive clock-face primitive.\n */\n\nimport { computed, inject, ref, watchEffect } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VToggle from '../VToggle/VToggle.vue'\nimport type { ToggleModelValue } from '../VToggle/VToggle.vue'\nimport VToggleItem from '../VToggle/VToggleItem.vue'\nimport { formatTime, hourCycleFor, parseTime } from '../../utils/time'\nimport {\n DIAL_DEAD_ZONE,\n DIAL_INNER_THRESHOLD,\n angleToIndex,\n dialIndexToHour24,\n distanceFraction,\n hour24ToDial,\n hourWithMeridiem,\n snapMinute,\n to12h,\n to24h,\n} from '../../utils/clock'\nimport type { HourFormat, Meridiem } from '../../utils/time'\nimport {\n allowedMinutesFor,\n firstAllowed,\n isHourAllowed,\n isTimeAllowed,\n limitsProblem,\n minuteGrid,\n minuteInterval,\n nearestAllowedMinute,\n resolveLimits,\n} from './limits'\nimport type { TimePickerAllowed } from './limits'\nimport { pad2 } from '../../utils/text'\nimport { isDev } from '../../utils/env'\nimport { hostWarnsKey } from '../../utils/hostWarns'\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useMessages, useResolvedLocale } from '../../i18n/state'\n\n/** Whether the face shows a 12- or a 24-hour clock. */\nexport type TimePickerFormat = HourFormat\n\n/** Which of the two halves of a time is being adjusted. */\ntype TimePickerStep = 'hour' | 'minute'\n\ninterface TimePickerProps {\n /**\n * Whether the face shows a 12- or a 24-hour clock. Left out, the reader's language\n * decides, which is almost always what one wants.\n */\n format?: TimePickerFormat\n /**\n * A BCP 47 locale, which decides the clock. It TAKES PRECEDENCE over the design\n * system's global locale and falls back to it, which is why it has no literal default:\n * `undefined` has to stay recognizable for the global locale to have its chance.\n */\n locale?: string\n /**\n * The interval the minutes snap to, both when dragging and with the arrow keys. The face\n * prints only the minutes it can reach, so a step of a quarter of an hour marks four.\n */\n minuteStep?: number\n /** The earliest time that can be chosen, inclusive, as a canonical 24-hour `'HH:mm'`. */\n min?: string\n /** The latest time that can be chosen, inclusive, written like `min`. */\n max?: string\n /** Which hours can be chosen: the list of them, or a rule answering for one. */\n allowedHours?: TimePickerAllowed\n /**\n * Which minutes can be chosen: the list of them, or a rule answering for one. The\n * minutes it leaves out are not printed.\n */\n allowedMinutes?: TimePickerAllowed\n /**\n * Makes the whole clock unusable: the hand cannot be moved, the half-day cannot be\n * changed, and everything greys out through the colour tokens.\n */\n disabled?: boolean\n /** Shows the time without letting it be changed. */\n readonly?: boolean\n /**\n * The accessible name of the whole clock, its two numerals and its face together. It falls\n * back to the dictionary, and a consumer `aria-label` wins over it.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<TimePickerProps>(), {\n format: undefined,\n locale: undefined,\n minuteStep: 1,\n min: undefined,\n max: undefined,\n allowedHours: undefined,\n allowedMinutes: undefined,\n disabled: false,\n readonly: false,\n label: undefined,\n})\n\n/** The time, always as a 24-hour \"HH:mm\" string whatever clock is displayed. */\nconst model = defineModel<string | null>({ default: null })\n\nconst emit = defineEmits<{\n /**\n * The reader has FINISHED, carrying the time as it stands: the minutes were settled from the\n * keyboard.\n */\n confirm: [value: string | null]\n}>()\n\ndefineSlots<{\n /** A strip at the foot of the clock, the place for actions such as Cancel and OK. */\n footer?(): unknown\n}>()\n\nconst m = useMessages()\n// @a11y\n// A roleless box cannot carry an accessible name (axe: aria-prohibited-attr),\n// so the root is a named group: the VCarousel viewport arrangement.\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.timePicker.label)\nconst resolvedLocale = useResolvedLocale(() => props.locale)\nconst resolvedFormat = computed<TimePickerFormat>(\n () => props.format ?? hourCycleFor(resolvedLocale.value),\n)\n\n/** The restrictions, resolved once for the whole render. */\nconst limits = computed(() => resolveLimits(props))\n\n/** The hours the restrictions still leave something in, worked out once per set of restrictions. */\nconst availableHours = computed(() => {\n const hours = new Set<number>()\n for (let candidate = 0; candidate < 24; candidate += 1)\n if (isHourAllowed(candidate, limits.value)) hours.add(candidate)\n return hours\n})\n\n/** Whether an hour is one the restrictions still leave something in. */\nconst isAvailableHour = (candidate: number) => availableHours.value.has(candidate)\n\n// @devwarn\nif (isDev && !inject(hostWarnsKey, false)) {\n watchEffect(() => {\n if (!Number.isInteger(props.minuteStep) || props.minuteStep < 1 || 60 % props.minuteStep !== 0)\n console.warn(`[VTimePicker] minuteStep ${props.minuteStep} — a divisor of 60 is expected.`)\n const problem = limitsProblem(limits.value)\n if (problem) console.warn(`[VTimePicker] ${problem}`)\n })\n}\n\n/** Which of the two is being adjusted. It starts on the hour and moves on by itself. */\nconst step = ref<TimePickerStep>('hour')\n\n// With no value the clock shows the first hour of the half of the day chosen so far, which\n// is midnight until PM is picked. It is also the hour a time started from the MINUTES is\n// written with, so that choice is not lost when the reader begins there.\nconst parts = computed(\n () =>\n parseTime(model.value) ?? {\n hour: resolvedFormat.value === '12h' ? to24h(12, pendingMeridiem.value) : 0,\n minute: 0,\n },\n)\nconst hour = computed(() => parts.value.hour)\nconst minute = computed(() => parts.value.minute)\n\n/*\n * The two writers are the SINGLE cut-off point for the restrictions: the pointer, the six\n * keys and the AM/PM control all write through them and through nothing else, so not one\n * handler carries a guard of its own.\n */\n\n/**\n * Writing the hour. An hour with nothing left in it has no minute to fall back on, which\n * refuses it; one that is only PARTLY available pulls the minutes to what it does allow, since\n * `min: '09:30'` must not be left holding 09:00.\n */\nfunction setHour(value: number | null) {\n // Changing which face is shown stays allowed under `readonly`: reading the minutes is not\n // changing them.\n if (props.disabled || props.readonly) return\n if (value === null) return\n const minutes = isTimeAllowed(value, minute.value, limits.value)\n ? minute.value\n : nearestAllowedMinute(value, minute.value, limits.value)\n if (minutes === null) return\n model.value = formatTime(value, minutes)\n}\n\n/** Writing the minutes. Null is \"there was nowhere to go\", which every key can produce. */\nfunction setMinute(value: number | null) {\n if (props.disabled || props.readonly) return\n if (value === null || !isTimeAllowed(hour.value, value, limits.value)) return\n model.value = formatTime(hour.value, value)\n}\n\n// @a11y\n/*\n * Which step the clock is on is otherwise carried by the face's own name alone, and a change of\n * name is not reliably announced. A politely announced region says it a second time.\n */\nconst liveMessage = ref('')\n\nfunction setStep(next: TimePickerStep) {\n step.value = next\n liveMessage.value =\n next === 'minute' ? m.value.timePicker.choosingMinutes : m.value.timePicker.choosingHour\n}\n\n/** One numeral on the face. */\ninterface DialCell {\n key: string\n /** What is printed. */\n label: string\n /** Where it sits, as a fraction of a full turn from twelve o'clock. */\n turn: number\n /** Which of the two circles it belongs to, a 24-hour face having an inner one. */\n ring: 'outer' | 'inner'\n /** Whether it is the value currently being pointed at. */\n selected: boolean\n}\n\n/** The hour a position on the face stands for. */\nfunction hourAt(index: number, ring: 'outer' | 'inner'): number {\n return resolvedFormat.value === '24h'\n ? dialIndexToHour24(index, ring)\n : to24h(index === 0 ? 12 : index, currentMeridiem.value)\n}\n\n/** The minutes the face prints. */\nconst minuteMarks = computed(() => {\n const interval = minuteInterval(props.minuteStep)\n const spacing = 60 / interval <= 12 ? interval : 5\n const marks: number[] = []\n for (let minutes = 0; minutes < 60; minutes += spacing)\n if (minutes % interval === 0 && isTimeAllowed(hour.value, minutes, limits.value))\n marks.push(minutes)\n return marks\n})\n\n/*\n * A numeral that is only there to be refused says nothing the missing numeral does not say\n * better, and it says it in grey text a screen reader has no way to reach: the markers are\n * `aria-hidden`, so what the restrictions leave is spoken by the face's own value and by\n * nothing else.\n */\nconst cells = computed<DialCell[]>(() => {\n if (step.value === 'minute') {\n return minuteMarks.value.map((minutes) => ({\n key: `m-${minutes}`,\n label: pad2(minutes),\n turn: minutes / 60,\n ring: 'outer' as const,\n selected: minute.value === minutes,\n }))\n }\n // Each position's hour is worked out once, since on a 12-hour face that takes the half of the\n // day in force. The selection there still compares numerals and not hours: an empty clock\n // holding a PM choice sits on midnight, and its hand points at the 12 all the same.\n const twelve = resolvedFormat.value === '12h'\n const shown12 = to12h(hour.value).hour\n const rings: DialCell['ring'][] = twelve ? ['outer'] : ['outer', 'inner']\n const out: DialCell[] = []\n for (const ring of rings)\n for (let i = 0; i < 12; i += 1) {\n const target = hourAt(i, ring)\n if (!isAvailableHour(target)) continue\n const numeral = i === 0 ? 12 : i\n out.push({\n key: `${ring === 'outer' ? 'o' : 'i'}-${i}`,\n label: ring === 'inner' ? pad2(target) : String(numeral),\n turn: i / 12,\n ring,\n selected: twelve ? shown12 === numeral : hour.value === target,\n })\n }\n return out\n})\n\nconst handTurn = computed(() => {\n if (step.value === 'minute') return minute.value / 60\n const index =\n resolvedFormat.value === '24h' ? hour24ToDial(hour.value).index : to12h(hour.value).hour % 12\n return index / 12\n})\n\nconst handRing = computed(() =>\n step.value === 'hour' && resolvedFormat.value === '24h' ? hour24ToDial(hour.value).ring : 'outer',\n)\n\n/**\n * Whether the hand points at a minute that has no marker of its own. The tip is then\n * drawn small: at full size it would cover the two neighbouring markers and read as\n * pointing at neither.\n */\nconst handMinor = computed(\n () => step.value === 'minute' && !minuteMarks.value.includes(minute.value),\n)\n\n/** The hour as the two large numerals show it, on whichever clock is displayed. */\nconst displayHourText = computed(() =>\n pad2(resolvedFormat.value === '12h' ? to12h(hour.value).hour : hour.value),\n)\n\n/** Which half of the day is chosen while no time is set at all. */\nconst pendingMeridiem = ref<Meridiem>('AM')\n\n/**\n * The half of the day in force: the value's own once there is one, the remembered choice until\n * then.\n */\nconst currentMeridiem = computed<Meridiem>(() =>\n model.value ? to12h(hour.value).meridiem : pendingMeridiem.value,\n)\n\nconst meridiemModel = computed<ToggleModelValue>({\n get: () => currentMeridiem.value,\n set: (value) => {\n const meridiem: Meridiem = value === 'PM' ? 'PM' : 'AM'\n pendingMeridiem.value = meridiem\n // With no time set there is nothing to convert, so the choice is simply REMEMBERED\n // and applies to the first time chosen.\n if (!model.value) return\n // What is being chosen here is the HALF OF THE DAY, so the hour gives way to it: kept\n // when the new half allows it, and otherwise the first hour of that half that does.\n // Refusing the write instead would leave the control snapping straight back, since it\n // reads the half of the day off the value.\n const wanted = hourWithMeridiem(hour.value, meridiem)\n setHour(\n isAvailableHour(wanted)\n ? wanted\n : firstAllowed(12, (i) => to24h(i, meridiem), isAvailableHour),\n )\n },\n})\n\n/**\n * Whether each half of the day still holds an hour one could choose. An empty one takes its\n * button with it: a control that can only ever be refused is worse than no control.\n */\nconst meridiemAvailable = computed<Record<Meridiem, boolean>>(() => ({\n AM: firstAllowed(12, (i) => to24h(i, 'AM'), isAvailableHour) !== null,\n PM: firstAllowed(12, (i) => to24h(i, 'PM'), isAvailableHour) !== null,\n}))\n\n// @a11y\n// The entire spoken value of the face. The numerals are hidden from screen readers, so these\n// four attributes are the only thing assistive technology has: what the value is, what its\n// bounds are, and how to say it.\nconst ariaValueNow = computed(() => {\n if (step.value === 'minute') return minute.value\n return resolvedFormat.value === '12h' ? to12h(hour.value).hour : hour.value\n})\nconst ariaValueMin = computed(() =>\n step.value === 'minute' ? 0 : resolvedFormat.value === '12h' ? 1 : 0,\n)\nconst ariaValueMax = computed(() =>\n step.value === 'minute' ? 59 : resolvedFormat.value === '12h' ? 12 : 23,\n)\n// On a 12-hour face the half of the day is spoken with the hour: the AM/PM control is not\n// on the slider's way, and \"9 o'clock\" alone is two different times.\nconst ariaValueText = computed(() => {\n const m_ = m.value.timePicker\n if (step.value === 'minute') return m_.minutesValue(minute.value)\n const spoken = m_.hourValue(ariaValueNow.value)\n if (resolvedFormat.value === '24h') return spoken\n return `${spoken} ${currentMeridiem.value === 'PM' ? m_.pm : m_.am}`\n})\n\n/**\n * A step has been settled: after the hour come the minutes, and after the minutes the whole\n * thing is confirmed; but by KEYBOARD only, for the reason given on the emit.\n */\nfunction settleStep(via: 'pointer' | 'keyboard') {\n if (step.value === 'hour') setStep('minute')\n else if (via === 'keyboard') emit('confirm', model.value)\n}\n\nconst faceEl = ref<HTMLElement | null>(null)\nconst dragging = ref(false)\n/** Whether anything in the gesture under way landed on a numeral. */\nlet landed = false\n\n/**\n * Where a point on the face lands, and whether there was anything there at all. The write\n * itself still goes through `setHour`/ `setMinute` and their own guards, which is why\n * `readonly` is refused THERE and not here: a frozen clock is still one whose numerals are\n * real, and a tap on one moves the step on as it always did.\n */\nfunction applyPoint(event: PointerEvent): boolean {\n const face = faceEl.value\n // A disabled clock aims at nothing, so no step is moved on either: its header buttons\n // are disabled for the same reason.\n if (!face || props.disabled) return false\n // Measuring the face is safe here: this runs from a handler, hence in a browser, never\n // during a render.\n const rect = face.getBoundingClientRect()\n const dx = event.clientX - (rect.left + rect.width / 2)\n const dy = event.clientY - (rect.top + rect.height / 2)\n if (Math.hypot(dx, dy) <= DIAL_DEAD_ZONE * (rect.width / 2)) return false\n if (step.value === 'minute') {\n // The step's own snapping stands: a point between two markers has always been pulled\n // to the one it is nearest. What it is pulled to still has to BE on the face.\n const minutes = snapMinute(angleToIndex(dx, dy, 60), props.minuteStep)\n if (!isTimeAllowed(hour.value, minutes, limits.value)) return false\n setMinute(minutes)\n return true\n }\n const index = angleToIndex(dx, dy, 12)\n const ring =\n resolvedFormat.value === '24h' &&\n distanceFraction(dx, dy, rect.width / 2) < DIAL_INNER_THRESHOLD\n ? 'inner'\n : 'outer'\n const target = hourAt(index, ring)\n if (!isAvailableHour(target)) return false\n setHour(target)\n return true\n}\n\nfunction onPointerdown(event: PointerEvent) {\n // @fallback\n // Capture keeps dragging active outside the clock face; synthetic events may not reference a\n // capturable pointer.\n try {\n faceEl.value?.setPointerCapture(event.pointerId)\n } catch {\n /* Synthetic pointers cannot be captured. */\n }\n dragging.value = true\n landed = applyPoint(event)\n}\n\nfunction onPointermove(event: PointerEvent) {\n if (dragging.value && applyPoint(event)) landed = true\n}\n\nfunction onPointerup() {\n if (!dragging.value) return\n dragging.value = false\n // A gesture that never landed on a numeral has chosen nothing, so there is nothing to\n // move on FROM: the reader is left on the face they aimed at, looking at what they missed.\n if (landed) settleStep('pointer')\n}\n\nfunction onPointercancel() {\n dragging.value = false\n}\n\n// @keyboard @a11y\n// The keyboard a slider is expected to have.\n/*\n * Every key below walks until it finds something it MAY land on, rather than stopping at the\n * first thing it may not: with scattered hours a key that stopped would die at the first hole\n * and never reach what lies past it. Against a bound there is nothing past the hole, so the\n * same walk comes back empty and the key holds still, which a bound means.\n */\nfunction moveHour(delta: number) {\n if (resolvedFormat.value === '24h') {\n setHour(firstAllowed(23, (i) => (hour.value + delta * i + 24) % 24, isAvailableHour))\n return\n }\n // On a 12-hour face the hours cycle from 1 to 12 WITHIN the current half of the day:\n // passing midday is done on the AM/PM control, not by walking the hand past twelve.\n const hour12 = to12h(hour.value).hour\n setHour(\n firstAllowed(\n 11,\n (i) => to24h(((hour12 - 1 + delta * i + 12) % 12) + 1, currentMeridiem.value),\n isAvailableHour,\n ),\n )\n}\n\n/** The first or the last hour the face allows, on whichever clock it is showing. */\nfunction edgeHour(edge: 'first' | 'last'): number | null {\n if (resolvedFormat.value === '24h')\n return firstAllowed(24, (i) => (edge === 'first' ? i - 1 : 24 - i), isAvailableHour)\n return firstAllowed(\n 12,\n (i) => to24h(edge === 'first' ? i : 13 - i, currentMeridiem.value),\n isAvailableHour,\n )\n}\n\n/**\n * The minute a key lands on: the first step of the grid at least `distance` minutes away, that\n * way round, and then the next one the hour allows.\n */\nfunction minuteToward(direction: 1 | -1, distance: number): number | null {\n const grid = minuteGrid(props.minuteStep)\n const target = (((minute.value + direction * distance) % 60) + 60) % 60\n const n = grid.length\n let start = direction > 0 ? 0 : n - 1\n for (let k = 0; k < n; k += 1) {\n const i = direction > 0 ? k : n - 1 - k\n if (direction > 0 ? grid[i]! >= target : grid[i]! <= target) {\n start = i\n break\n }\n }\n return firstAllowed(\n n,\n (i) => grid[(((start + direction * (i - 1)) % n) + n) % n]!,\n (candidate) => isTimeAllowed(hour.value, candidate, limits.value),\n )\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n // The face of a disabled clock is out of the tab order, but `focus()` can still put the\n // focus there: nothing it is sent may move the step on or confirm.\n if (props.disabled) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n settleStep('keyboard')\n return\n }\n // The arrows are not flipped in a right-to-left page: a clock face is never mirrored;\n // clockwise means the same thing everywhere; so forward stays forward.\n const delta =\n event.key === 'ArrowUp' || event.key === 'ArrowRight'\n ? 1\n : event.key === 'ArrowDown' || event.key === 'ArrowLeft'\n ? -1\n : 0\n if (step.value === 'hour') {\n if (delta) moveHour(delta)\n else if (event.key === 'Home') setHour(edgeHour('first'))\n else if (event.key === 'End') setHour(edgeHour('last'))\n else return\n event.preventDefault()\n return\n }\n if (delta) setMinute(minuteToward(delta, 1))\n else if (event.key === 'PageUp') setMinute(minuteToward(1, 5))\n else if (event.key === 'PageDown') setMinute(minuteToward(-1, 5))\n // The list is built in these two branches alone: the arrows walk to a neighbour and never\n // need the whole of it.\n else if (event.key === 'Home') setMinute(allowedMinutesFor(hour.value, limits.value)[0] ?? null)\n else if (event.key === 'End')\n setMinute(allowedMinutesFor(hour.value, limits.value).at(-1) ?? null)\n else return\n event.preventDefault()\n}\n\ndefineExpose({\n /** Moves the focus onto the clock face, which the arrow keys drive. */\n focus: (options?: FocusOptions) => faceEl.value?.focus(options),\n /**\n * Puts the clock back on the hour and clears the announcement, WITHOUT announcing anything\n * itself.\n */\n reset: () => {\n step.value = 'hour'\n liveMessage.value = ''\n },\n})\n</script>\n\n<template>\n <div\n class=\"v-time-picker\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :data-step=\"step\"\n :data-disabled=\"disabled ? '' : undefined\"\n :data-readonly=\"readonly ? '' : undefined\"\n >\n <!--\n The two large numerals switch between adjusting the hour and the minutes. The one being\n adjusted takes the accent tone, which on a quiet button shows as the colour of the numeral\n rather than as a filled background.\n -->\n <div class=\"v-time-picker-header\">\n <div class=\"v-time-picker-time\">\n <VButton\n class=\"v-time-picker-cell\"\n variant=\"ghost\"\n size=\"lg\"\n :tone=\"step === 'hour' ? 'accent' : 'neutral'\"\n :aria-pressed=\"step === 'hour' ? 'true' : 'false'\"\n :aria-label=\"`${displayHourText}, ${m.timePicker.selectHour}`\"\n :disabled=\"disabled\"\n @click=\"setStep('hour')\"\n >\n {{ displayHourText }}\n </VButton>\n <span class=\"v-time-picker-sep\" aria-hidden=\"true\">:</span>\n <VButton\n class=\"v-time-picker-cell\"\n variant=\"ghost\"\n size=\"lg\"\n :tone=\"step === 'minute' ? 'accent' : 'neutral'\"\n :aria-pressed=\"step === 'minute' ? 'true' : 'false'\"\n :aria-label=\"`${pad2(minute)}, ${m.timePicker.selectMinutes}`\"\n :disabled=\"disabled\"\n @click=\"setStep('minute')\"\n >\n {{ pad2(minute) }}\n </VButton>\n </div>\n\n <VToggle\n v-if=\"resolvedFormat === '12h'\"\n v-model=\"meridiemModel\"\n class=\"v-time-picker-meridiem\"\n mandatory\n variant=\"outline\"\n selected-variant=\"soft\"\n orientation=\"vertical\"\n size=\"sm\"\n :label=\"m.timePicker.meridiem\"\n :disabled=\"disabled || readonly\"\n >\n <VToggleItem value=\"AM\" :label=\"m.timePicker.am\" :disabled=\"!meridiemAvailable.AM\" />\n <VToggleItem value=\"PM\" :label=\"m.timePicker.pm\" :disabled=\"!meridiemAvailable.PM\" />\n </VToggle>\n </div>\n\n <div\n ref=\"faceEl\"\n role=\"slider\"\n :tabindex=\"disabled ? -1 : 0\"\n class=\"v-time-picker-face\"\n :aria-label=\"step === 'hour' ? m.timePicker.hour : m.timePicker.minutes\"\n :aria-valuemin=\"ariaValueMin\"\n :aria-valuemax=\"ariaValueMax\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuetext=\"ariaValueText\"\n :data-dragging=\"dragging ? '' : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :aria-readonly=\"readonly ? 'true' : undefined\"\n @pointerdown=\"onPointerdown\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @keydown=\"onKeydown\"\n >\n <div\n class=\"v-time-picker-hand\"\n aria-hidden=\"true\"\n :data-ring=\"handRing\"\n :data-minor=\"handMinor ? '' : undefined\"\n :style=\"{ '--dial-turn': String(handTurn) }\"\n />\n <span class=\"v-time-picker-center\" aria-hidden=\"true\" />\n <span\n v-for=\"cell in cells\"\n :key=\"cell.key\"\n class=\"v-time-picker-number\"\n aria-hidden=\"true\"\n :data-ring=\"cell.ring\"\n :data-selected=\"cell.selected ? '' : undefined\"\n :style=\"{ '--dial-turn': String(cell.turn) }\"\n >{{ cell.label }}</span\n >\n </div>\n\n <div class=\"v-visually-hidden\" aria-live=\"polite\">{{ liveMessage }}</div>\n\n <div v-if=\"$slots.footer\" class=\"v-time-picker-footer\"><slot name=\"footer\" /></div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The same root as VDatePicker's: an inline column that sizes to its content and pads\n itself, so the clock reads the same on a page as in VTimeInput's panel, which adds no\n padding of its own. The gap is the clock's, its parts being larger. */\n .v-time-picker {\n display: inline-flex;\n flex-direction: column;\n gap: var(--vectis-space-4);\n padding: var(--vectis-space-3);\n font-family: var(--vectis-text-family);\n color: var(--vectis-color-text);\n }\n\n /*\n * The numerals and the choice of half-day on one row, the second beside the first. The gap is\n * logical, so the control follows the reading direction; the numerals inside it stay in a\n * group of their own, which keeps the direction they force off it.\n */\n .v-time-picker-header {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--vectis-space-3);\n }\n\n /* A time written in figures always reads hours then minutes, in every language. Forcing\n the direction here is what stops bidirectional reordering from swapping the two in a\n right-to-left page. */\n .v-time-picker-time {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--vectis-space-2);\n direction: ltr;\n }\n\n /*\n * What is overridden here is the \"large numeral\" look alone; the width and the type; the\n * height, the states, the focus ring and the transitions all come from the button itself. The\n * type is the `heading-1` recipe taken whole, and the colon the `heading-2` one: read in\n * halves, a consumer repointing `--vectis-text-heading-1-weight` would move every heading but\n * these two numerals.\n */\n .v-time-picker-cell[data-size] {\n width: var(--control-height);\n font-size: var(--vectis-text-heading-1-size);\n font-weight: var(--vectis-text-heading-1-weight);\n line-height: var(--vectis-text-heading-1-leading);\n letter-spacing: var(--vectis-text-heading-1-tracking);\n /* Figures of equal width, so that going from \"11\" to \"00\" does not shift the cell. */\n font-variant-numeric: tabular-nums;\n }\n\n .v-time-picker-sep {\n font-size: var(--vectis-text-heading-2-size);\n font-weight: var(--vectis-text-heading-2-weight);\n line-height: var(--vectis-text-heading-2-leading);\n color: var(--vectis-color-text);\n user-select: none;\n }\n\n .v-time-picker-meridiem {\n flex: none;\n }\n\n .v-time-picker-face {\n position: relative;\n /*\n * The row above is wider than the face as soon as the half-day control is rendered, and the\n * column is only as wide as its widest child: without this the clock would hang at the\n * start edge of that row instead of under the middle of it. The face itself stays physical;\n * a clock is never mirrored.\n */\n align-self: center;\n inline-size: var(--vectis-control-size-time-picker-dial);\n block-size: var(--vectis-control-size-time-picker-dial);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-surface-muted);\n /* Dragging across the face IS how one chooses, so it must do nothing else: no\n scrolling under a finger, and no selecting the numerals as text. */\n touch-action: none;\n user-select: none;\n cursor: pointer;\n /*\n * This is coupled to the threshold that decides which of the two circles a point belongs\n * to, in `utils/clock`. Changing one without the other makes the face answer with the wrong\n * ring, and no test can catch it: the unit tests measure nothing.\n */\n --dial-radius: calc(\n var(--vectis-control-size-time-picker-dial) / 2 -\n var(--vectis-control-size-time-picker-number) / 2\n );\n }\n\n .v-time-picker-face: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 /*\n * The properties used are PHYSICAL on purpose, where the rest of the design system prefers\n * logical ones: a clock face is never mirrored, clockwise meaning the same thing in every\n * language.\n */\n .v-time-picker-number {\n position: absolute;\n left: calc(50% + var(--dial-radius) * sin(var(--dial-turn) * 1turn));\n top: calc(50% - var(--dial-radius) * cos(var(--dial-turn) * 1turn));\n translate: -50% -50%;\n inline-size: var(--vectis-control-size-time-picker-number);\n block-size: var(--vectis-control-size-time-picker-number);\n display: grid;\n place-items: center;\n border-radius: var(--vectis-radius-pill);\n /* The outer circle is set at the large body size: these numerals are read at arm's\n length on a phone, not scanned like a label. */\n font-size: var(--vectis-text-body-lg-size);\n color: var(--vectis-color-text);\n /*\n * Above the hand, so that the numeral being pointed at reads on its dot rather than being\n * covered by it.\n */\n z-index: 1;\n pointer-events: none;\n }\n\n .v-time-picker-number[data-ring='inner'],\n .v-time-picker-hand[data-ring='inner'] {\n --dial-radius: calc(\n var(--vectis-control-size-time-picker-dial) / 2 -\n var(--vectis-control-size-time-picker-number) * 1.5\n );\n }\n\n .v-time-picker-number[data-ring='inner'] {\n font-size: var(--vectis-text-body-md-size);\n color: var(--vectis-color-text-muted);\n }\n\n .v-time-picker-number[data-selected] {\n color: var(--vectis-color-text-on-accent);\n }\n\n /*\n * The rotation is deliberately not animated. Going from 55 minutes to 0 takes the angle from\n * nearly a full turn back to none, and an interpolation would sweep the hand all the way\n * round anticlockwise.\n */\n .v-time-picker-hand {\n position: absolute;\n left: calc(50% - var(--vectis-control-size-time-picker-hand) / 2);\n bottom: 50%;\n inline-size: var(--vectis-control-size-time-picker-hand);\n block-size: var(--dial-radius);\n background: var(--vectis-color-accent);\n transform-origin: 50% 100%;\n rotate: calc(var(--dial-turn) * 1turn);\n }\n\n /*\n * The dot at the tip of the hand. It is exactly the size of a numeral's cell, so it covers\n * the one being pointed at; whose text turns to the colour that reads on it.\n */\n .v-time-picker-hand::before {\n content: '';\n position: absolute;\n top: 0;\n left: 50%;\n translate: -50% -50%;\n inline-size: var(--vectis-control-size-time-picker-number);\n block-size: var(--vectis-control-size-time-picker-number);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-accent);\n /* The ONE thing animated on the hand: the dot changing size between its two forms. It\n interpolates between two bounded values and can therefore never take a wrong path,\n unlike the rotation above. */\n transition:\n inline-size var(--vectis-duration-fast) var(--vectis-ease-default),\n block-size var(--vectis-duration-fast) var(--vectis-ease-default);\n }\n\n /*\n * Only the DOT's own size changes. Redefining the numeral size variable here would also move\n * the radius derived from it, and with it the hand's length and the position of both circles.\n */\n .v-time-picker-hand[data-minor]::before {\n inline-size: var(--vectis-control-size-time-picker-hand-minor);\n block-size: var(--vectis-control-size-time-picker-hand-minor);\n }\n\n .v-time-picker-center {\n position: absolute;\n left: 50%;\n top: 50%;\n translate: -50% -50%;\n inline-size: var(--vectis-control-size-time-picker-center);\n block-size: var(--vectis-control-size-time-picker-center);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-accent);\n }\n\n /* Neither state is a pointer target. A read-only clock is still read, so it keeps the\n ordinary cursor; a disabled one refuses. */\n .v-time-picker[data-readonly] .v-time-picker-face {\n cursor: default;\n }\n\n .v-time-picker[data-disabled] .v-time-picker-face {\n cursor: not-allowed;\n }\n\n /*\n * The whole block sits after the rules it overrides, every one of them at the same\n * specificity: this is one sheet, so its own order is what settles them.\n */\n .v-time-picker[data-disabled] .v-time-picker-hand,\n .v-time-picker[data-disabled] .v-time-picker-hand::before,\n .v-time-picker[data-disabled] .v-time-picker-center {\n background: var(--vectis-color-border-strong);\n }\n\n .v-time-picker[data-disabled] .v-time-picker-sep,\n .v-time-picker[data-disabled] .v-time-picker-number,\n .v-time-picker[data-disabled] .v-time-picker-number[data-selected] {\n color: var(--vectis-color-text-subtle);\n }\n\n .v-time-picker-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-2);\n padding-block-start: var(--vectis-space-2);\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-time-picker-hand::before {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6FA,IAAM,IAAQ,GAcR,IAAQ,GAA0B,GAAA,YAAkB,GAEpD,KAAO,GAaP,IAAI,EAAY,GAIhB,KAAY,QAAmB,EAAM,SAAS,EAAE,MAAM,WAAW,KAAK,GACtE,KAAiB,QAAwB,EAAM,MAAM,GACrD,IAAiB,QACf,EAAM,UAAU,GAAa,GAAe,KAAK,CACzD,GAGM,IAAS,QAAe,GAAc,CAAK,CAAC,GAG5C,KAAiB,QAAe;GACpC,IAAM,oBAAQ,IAAI,IAAY;GAC9B,KAAK,IAAI,IAAY,GAAG,IAAY,IAAI,KAAa,GACnD,AAAI,GAAc,GAAW,EAAO,KAAK,KAAG,EAAM,IAAI,CAAS;GACjE,OAAO;EACT,CAAC,GAGK,KAAmB,MAAsB,GAAe,MAAM,IAAI,CAAS;EAGjF,AAAI,KAAS,CAAC,GAAO,IAAc,EAAK,KACtC,SAAkB;GAChB,CAAI,CAAC,OAAO,UAAU,EAAM,UAAU,KAAK,EAAM,aAAa,KAAK,KAAK,EAAM,cAAe,MAC3F,QAAQ,KAAK,4BAA4B,EAAM,WAAW,gCAAgC;GAC5F,IAAM,IAAU,GAAc,EAAO,KAAK;GAC1C,AAAI,KAAS,QAAQ,KAAK,iBAAiB,GAAS;EACtD,CAAC;EAIH,IAAM,IAAO,EAAoB,MAAM,GAKjC,IAAQ,QAEV,GAAU,EAAM,KAAK,KAAK;GACxB,MAAM,EAAe,UAAU,QAAQ,EAAM,IAAI,EAAgB,KAAK,IAAI;GAC1E,QAAQ;EACV,CACJ,GACM,IAAO,QAAe,EAAM,MAAM,IAAI,GACtC,IAAS,QAAe,EAAM,MAAM,MAAM;EAahD,SAAS,EAAQ,GAAsB;GAIrC,IADI,EAAM,YAAY,EAAM,YACxB,MAAU,MAAM;GACpB,IAAM,IAAU,EAAc,GAAO,EAAO,OAAO,EAAO,KAAK,IAC3D,EAAO,QACP,GAAqB,GAAO,EAAO,OAAO,EAAO,KAAK;GACtD,MAAY,SAChB,EAAM,QAAQ,EAAW,GAAO,CAAO;EACzC;EAGA,SAAS,EAAU,GAAsB;GACnC,EAAM,YAAY,EAAM,YACxB,MAAU,QAAQ,CAAC,EAAc,EAAK,OAAO,GAAO,EAAO,KAAK,MACpE,EAAM,QAAQ,EAAW,EAAK,OAAO,CAAK;EAC5C;EAOA,IAAM,IAAc,EAAI,EAAE;EAE1B,SAAS,EAAQ,GAAsB;GAErC,AADA,EAAK,QAAQ,GACb,EAAY,QACV,MAAS,WAAW,EAAE,MAAM,WAAW,kBAAkB,EAAE,MAAM,WAAW;EAChF;EAgBA,SAAS,EAAO,GAAe,GAAiC;GAC9D,OAAO,EAAe,UAAU,QAC5B,GAAkB,GAAO,CAAI,IAC7B,EAAM,MAAU,IAAI,KAAK,GAAO,EAAgB,KAAK;EAC3D;EAGA,IAAM,IAAc,QAAe;GACjC,IAAM,IAAW,GAAe,EAAM,UAAU,GAC1C,IAAU,KAAK,KAAY,KAAK,IAAW,GAC3C,IAAkB,CAAC;GACzB,KAAK,IAAI,IAAU,GAAG,IAAU,IAAI,KAAW,GAC7C,AAAI,IAAU,MAAa,KAAK,EAAc,EAAK,OAAO,GAAS,EAAO,KAAK,KAC7E,EAAM,KAAK,CAAO;GACtB,OAAO;EACT,CAAC,GAQK,KAAQ,QAA2B;GACvC,IAAI,EAAK,UAAU,UACjB,OAAO,EAAY,MAAM,KAAK,OAAa;IACzC,KAAK,KAAK;IACV,OAAO,EAAK,CAAO;IACnB,MAAM,IAAU;IAChB,MAAM;IACN,UAAU,EAAO,UAAU;GAC7B,EAAE;GAKJ,IAAM,IAAS,EAAe,UAAU,OAClC,IAAU,EAAM,EAAK,KAAK,CAAC,CAAC,MAC5B,IAA4B,IAAS,CAAC,OAAO,IAAI,CAAC,SAAS,OAAO,GAClE,IAAkB,CAAC;GACzB,KAAK,IAAM,KAAQ,GACjB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,GAAG;IAC9B,IAAM,IAAS,EAAO,GAAG,CAAI;IAC7B,IAAI,CAAC,EAAgB,CAAM,GAAG;IAC9B,IAAM,IAAU,MAAM,IAAI,KAAK;IAC/B,EAAI,KAAK;KACP,KAAK,GAAG,MAAS,UAAU,MAAM,IAAI,GAAG;KACxC,OAAO,MAAS,UAAU,EAAK,CAAM,IAAI,OAAO,CAAO;KACvD,MAAM,IAAI;KACV;KACA,UAAU,IAAS,MAAY,IAAU,EAAK,UAAU;IAC1D,CAAC;GACH;GACF,OAAO;EACT,CAAC,GAEK,KAAW,QACX,EAAK,UAAU,WAAiB,EAAO,QAAQ,MAEjD,EAAe,UAAU,QAAQ,GAAa,EAAK,KAAK,CAAC,CAAC,QAAQ,EAAM,EAAK,KAAK,CAAC,CAAC,OAAO,MAC9E,EAChB,GAEK,KAAW,QACf,EAAK,UAAU,UAAU,EAAe,UAAU,QAAQ,GAAa,EAAK,KAAK,CAAC,CAAC,OAAO,OAC5F,GAOM,KAAY,QACV,EAAK,UAAU,YAAY,CAAC,EAAY,MAAM,SAAS,EAAO,KAAK,CAC3E,GAGM,IAAkB,QACtB,EAAK,EAAe,UAAU,QAAQ,EAAM,EAAK,KAAK,CAAC,CAAC,OAAO,EAAK,KAAK,CAC3E,GAGM,IAAkB,EAAc,IAAI,GAMpC,IAAkB,QACtB,EAAM,QAAQ,EAAM,EAAK,KAAK,CAAC,CAAC,WAAW,EAAgB,KAC7D,GAEM,IAAgB,EAA2B;GAC/C,WAAW,EAAgB;GAC3B,MAAM,MAAU;IACd,IAAM,IAAqB,MAAU,OAAO,OAAO;IAInD,IAHA,EAAgB,QAAQ,GAGpB,CAAC,EAAM,OAAO;IAKlB,IAAM,IAAS,GAAiB,EAAK,OAAO,CAAQ;IACpD,EACE,EAAgB,CAAM,IAClB,IACA,EAAa,KAAK,MAAM,EAAM,GAAG,CAAQ,GAAG,CAAe,CACjE;GACF;EACF,CAAC,GAMK,IAAoB,SAA2C;GACnE,IAAI,EAAa,KAAK,MAAM,EAAM,GAAG,IAAI,GAAG,CAAe,MAAM;GACjE,IAAI,EAAa,KAAK,MAAM,EAAM,GAAG,IAAI,GAAG,CAAe,MAAM;EACnE,EAAE,GAMI,IAAe,QACf,EAAK,UAAU,WAAiB,EAAO,QACpC,EAAe,UAAU,QAAQ,EAAM,EAAK,KAAK,CAAC,CAAC,OAAO,EAAK,KACvE,GACK,KAAe,QACnB,EAAK,UAAU,WAAW,IAAI,IAAe,UAAU,MACzD,GACM,KAAe,QACnB,EAAK,UAAU,WAAW,KAAK,EAAe,UAAU,QAAQ,KAAK,EACvE,GAGM,KAAgB,QAAe;GACnC,IAAM,IAAK,EAAE,MAAM;GACnB,IAAI,EAAK,UAAU,UAAU,OAAO,EAAG,aAAa,EAAO,KAAK;GAChE,IAAM,IAAS,EAAG,UAAU,EAAa,KAAK;GAE9C,OADI,EAAe,UAAU,QAAc,IACpC,GAAG,EAAO,GAAG,EAAgB,UAAU,OAAO,EAAG,KAAK,EAAG;EAClE,CAAC;EAMD,SAAS,GAAW,GAA6B;GAC/C,AAAI,EAAK,UAAU,SAAQ,EAAQ,QAAQ,IAClC,MAAQ,cAAY,GAAK,WAAW,EAAM,KAAK;EAC1D;EAEA,IAAM,IAAS,EAAwB,IAAI,GACrC,IAAW,EAAI,EAAK,GAEtB,IAAS;EAQb,SAAS,GAAW,GAA8B;GAChD,IAAM,IAAO,EAAO;GAGpB,IAAI,CAAC,KAAQ,EAAM,UAAU,OAAO;GAGpC,IAAM,IAAO,EAAK,sBAAsB,GAClC,IAAK,EAAM,WAAW,EAAK,OAAO,EAAK,QAAQ,IAC/C,IAAK,EAAM,WAAW,EAAK,MAAM,EAAK,SAAS;GACrD,IAAI,KAAK,MAAM,GAAI,CAAE,KAAA,MAAuB,EAAK,QAAQ,IAAI,OAAO;GACpE,IAAI,EAAK,UAAU,UAAU;IAG3B,IAAM,IAAU,GAAW,EAAa,GAAI,GAAI,EAAE,GAAG,EAAM,UAAU;IAGrE,OAFK,EAAc,EAAK,OAAO,GAAS,EAAO,KAAK,KACpD,EAAU,CAAO,GACV,MAFuD;GAGhE;GAOA,IAAM,IAAS,EAND,EAAa,GAAI,GAAI,EAMb,GAJpB,EAAe,UAAU,SACzB,GAAiB,GAAI,GAAI,EAAK,QAAQ,CAAC,IAAA,OACnC,UACA,OAC2B;GAGjC,OAFK,EAAgB,CAAM,KAC3B,EAAQ,CAAM,GACP,MAF8B;EAGvC;EAEA,SAAS,GAAc,GAAqB;GAI1C,IAAI;IACF,EAAO,OAAO,kBAAkB,EAAM,SAAS;GACjD,QAAQ,CAER;GAEA,AADA,EAAS,QAAQ,IACjB,IAAS,GAAW,CAAK;EAC3B;EAEA,SAAS,GAAc,GAAqB;GAC1C,AAAI,EAAS,SAAS,GAAW,CAAK,MAAG,IAAS;EACpD;EAEA,SAAS,KAAc;GAChB,EAAS,UACd,EAAS,QAAQ,IAGb,KAAQ,GAAW,SAAS;EAClC;EAEA,SAAS,KAAkB;GACzB,EAAS,QAAQ;EACnB;EAUA,SAAS,GAAS,GAAe;GAC/B,IAAI,EAAe,UAAU,OAAO;IAClC,EAAQ,EAAa,KAAK,OAAO,EAAK,QAAQ,IAAQ,IAAI,MAAM,IAAI,CAAe,CAAC;IACpF;GACF;GAGA,IAAM,IAAS,EAAM,EAAK,KAAK,CAAC,CAAC;GACjC,EACE,EACE,KACC,MAAM,GAAQ,IAAS,IAAI,IAAQ,IAAI,MAAM,KAAM,GAAG,EAAgB,KAAK,GAC5E,CACF,CACF;EACF;EAGA,SAAS,GAAS,GAAuC;GAGvD,OAFI,EAAe,UAAU,QACpB,EAAa,KAAK,MAAO,MAAS,UAAU,IAAI,IAAI,KAAK,GAAI,CAAe,IAC9E,EACL,KACC,MAAM,EAAM,MAAS,UAAU,IAAI,KAAK,GAAG,EAAgB,KAAK,GACjE,CACF;EACF;EAMA,SAAS,EAAa,GAAmB,GAAiC;GACxE,IAAM,IAAO,GAAW,EAAM,UAAU,GAClC,MAAY,EAAO,QAAQ,IAAY,KAAY,KAAM,MAAM,IAC/D,IAAI,EAAK,QACX,IAAQ,IAAY,IAAI,IAAI,IAAI;GACpC,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG;IAC7B,IAAM,IAAI,IAAY,IAAI,IAAI,IAAI,IAAI;IACtC,IAAI,IAAY,IAAI,EAAK,MAAO,IAAS,EAAK,MAAO,GAAQ;KAC3D,IAAQ;KACR;IACF;GACF;GACA,OAAO,EACL,IACC,MAAM,IAAQ,IAAQ,KAAa,IAAI,MAAM,IAAK,KAAK,KACvD,MAAc,EAAc,EAAK,OAAO,GAAW,EAAO,KAAK,CAClE;EACF;EAEA,SAAS,GAAU,GAAsB;GAGvC,IAAI,EAAM,UAAU;GACpB,IAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,KAAK;IAE9C,AADA,EAAM,eAAe,GACrB,GAAW,UAAU;IACrB;GACF;GAGA,IAAM,IACJ,EAAM,QAAQ,aAAa,EAAM,QAAQ,eACrC,IACA,EAAM,QAAQ,eAAe,EAAM,QAAQ,cACzC,KACA;GACR,IAAI,EAAK,UAAU,QAAQ;IACzB,IAAI,GAAO,GAAS,CAAK;SACpB,IAAI,EAAM,QAAQ,QAAQ,EAAQ,GAAS,OAAO,CAAC;SACnD,IAAI,EAAM,QAAQ,OAAO,EAAQ,GAAS,MAAM,CAAC;SACjD;IACL,EAAM,eAAe;IACrB;GACF;GACA,IAAI,GAAO,EAAU,EAAa,GAAO,CAAC,CAAC;QACtC,IAAI,EAAM,QAAQ,UAAU,EAAU,EAAa,GAAG,CAAC,CAAC;QACxD,IAAI,EAAM,QAAQ,YAAY,EAAU,EAAa,IAAI,CAAC,CAAC;QAG3D,IAAI,EAAM,QAAQ,QAAQ,EAAU,GAAkB,EAAK,OAAO,EAAO,KAAK,CAAC,CAAC,MAAM,IAAI;QAC1F,IAAI,EAAM,QAAQ,OACrB,EAAU,GAAkB,EAAK,OAAO,EAAO,KAAK,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI;QACjE;GACL,EAAM,eAAe;EACvB;SAEA,EAAa;GAEX,QAAQ,MAA2B,EAAO,OAAO,MAAM,CAAO;GAK9D,aAAa;IAEX,AADA,EAAK,QAAQ,QACb,EAAY,QAAQ;GACtB;EACF,CAAC,mBAIC,EAqGM,OAAA;GApGJ,OAAM;GACN,MAAK;GACJ,cAAY,EAAA,EAAA;GACZ,aAAW,EAAA;GACX,iBAAe,EAAA,WAAQ,KAAQ,KAAA;GAC/B,iBAAe,EAAA,WAAQ,KAAQ,KAAA;;GAOhC,EA4CM,OA5CN,IA4CM,CA3CJ,EA0BM,OA1BN,IA0BM;IAzBJ,EAWU,GAAA;KAVR,OAAM;KACN,SAAQ;KACR,MAAK;KACJ,MAAM,EAAA,UAAI,SAAA,WAAA;KACV,gBAAc,EAAA,UAAI,SAAA,SAAA;KAClB,cAAU,GAAK,EAAA,MAAe,IAAK,EAAA,CAAA,CAAC,CAAC,WAAW;KAChD,UAAU,EAAA;KACV,SAAK,AAAA,EAAA,QAAA,MAAE,EAAO,MAAA;;sBAEM,CAAA,EAAA,EAAlB,EAAA,KAAe,GAAA,CAAA,CAAA,CAAA;;;;;;;;aAEpB,EAA2D,QAAA;KAArD,OAAM;KAAoB,eAAY;OAAO,KAAC,EAAA;IACpD,EAWU,GAAA;KAVR,OAAM;KACN,SAAQ;KACR,MAAK;KACJ,MAAM,EAAA,UAAI,WAAA,WAAA;KACV,gBAAc,EAAA,UAAI,WAAA,SAAA;KAClB,cAAU,GAAK,EAAA,CAAA,CAAI,CAAC,EAAA,KAAM,EAAA,IAAM,EAAA,CAAA,CAAC,CAAC,WAAW;KAC7C,UAAU,EAAA;KACV,SAAK,AAAA,EAAA,QAAA,MAAE,EAAO,QAAA;;sBAEG,CAAA,EAAA,EAAf,EAAA,CAAA,CAAI,CAAC,EAAA,KAAM,CAAA,GAAA,CAAA,CAAA,CAAA;;;;;;;;OAKV,EAAA,UAAc,SAAA,EAAA,GADtB,GAcU,GAAA;;gBAZC,EAAA;6CAAa,QAAA;IACtB,OAAM;IACN,WAAA;IACA,SAAQ;IACR,oBAAiB;IACjB,aAAY;IACZ,MAAK;IACJ,OAAO,EAAA,CAAA,CAAC,CAAC,WAAW;IACpB,UAAU,EAAA,YAAY,EAAA;;qBAE8D,CAArF,EAAqF,GAAA;KAAxE,OAAM;KAAM,OAAO,EAAA,CAAA,CAAC,CAAC,WAAW;KAAK,UAAQ,CAAG,EAAA,MAAkB;wCAC/E,EAAqF,GAAA;KAAxE,OAAM;KAAM,OAAO,EAAA,CAAA,CAAC,CAAC,WAAW;KAAK,UAAQ,CAAG,EAAA,MAAkB;;;;;;;;GAInF,EAqCM,OAAA;aApCA;IAAJ,KAAI;IACJ,MAAK;IACJ,UAAU,EAAA,WAAQ,KAAA;IACnB,OAAM;IACL,cAAY,EAAA,UAAI,SAAc,EAAA,CAAA,CAAC,CAAC,WAAW,OAAO,EAAA,CAAA,CAAC,CAAC,WAAW;IAC/D,iBAAe,GAAA;IACf,iBAAe,GAAA;IACf,iBAAe,EAAA;IACf,kBAAgB,GAAA;IAChB,iBAAe,EAAA,QAAQ,KAAQ,KAAA;IAC/B,iBAAe,EAAA,WAAQ,SAAY,KAAA;IACnC,iBAAe,EAAA,WAAQ,SAAY,KAAA;IACtB;IACA;IACF;IACI;IACN;;IAEV,EAME,OAAA;KALA,OAAM;KACN,eAAY;KACX,aAAW,GAAA;KACX,cAAY,GAAA,QAAS,KAAQ,KAAA;KAC7B,OAAK,EAAA,EAAA,eAAmB,OAAO,GAAA,KAAQ,EAAA,CAAA;;aAE1C,EAAwD,QAAA;KAAlD,OAAM;KAAuB,eAAY;;YAC/C,EASC,IAAA,MAAA,GARgB,GAAA,QAAR,YADT,EASC,QAAA;KAPE,KAAK,EAAK;KACX,OAAM;KACN,eAAY;KACX,aAAW,EAAK;KAChB,iBAAe,EAAK,WAAQ,KAAQ,KAAA;KACpC,OAAK,EAAA,EAAA,eAAmB,OAAO,EAAK,IAAI,EAAA,CAAA;SACrC,EAAK,KAAK,GAAA,IAAA,EAAA;;GAIlB,EAAyE,OAAzE,IAAyE,EAApB,EAAA,KAAW,GAAA,CAAA;GAErDA,EAAAA,OAAO,UAAA,EAAA,GAAlB,EAAmF,OAAnF,IAAmF,CAA5B,GAAsB,EAAA,QAAA,QAAA,CAAA,CAAA,KAAA,GAAA,IAAA,EAAA"}
@@ -1,13 +1,9 @@
1
- /**
2
- * Which values are ALLOWED: the list of them, or a rule answering for one. The polarity is
3
- * the opposite of `DatePickerMatcher`, which names what is excluded, hence a name that
4
- * says so rather than a shared suffix.
5
- */
1
+ /** Which values are ALLOWED: the list of them, or a rule answering for one. */
6
2
  export type TimePickerAllowed = number[] | ((value: number) => boolean);
7
3
  /**
8
- * The restrictions in force, resolved ONCE: the matchers already turned into functions,
9
- * and the interval the minutes are reachable on carried alongside them, since whether an
10
- * hour has anything left in it depends on that too.
4
+ * The restrictions in force, resolved once: the matchers already turned into functions, and the
5
+ * interval the minutes are reachable on carried alongside them, since whether an hour has
6
+ * anything left in it depends on that too.
11
7
  */
12
8
  export interface TimeLimits {
13
9
  /** The earliest time allowed, inclusive, as a canonical 24-hour `'HH:mm'`. */
@@ -18,7 +14,7 @@ export interface TimeLimits {
18
14
  hour: (hour: number) => boolean;
19
15
  /** Whether a minute is allowed. */
20
16
  minute: (minute: number) => boolean;
21
- /** The interval the minutes snap to, which is what makes one reachable at all. */
17
+ /** The interval the minutes snap to, which makes one reachable at all. */
22
18
  step: number;
23
19
  }
24
20
  /** The props both components declare, which is all this needs to resolve them. */
@@ -29,33 +25,22 @@ export interface TimeLimitProps {
29
25
  allowedMinutes?: TimePickerAllowed;
30
26
  minuteStep: number;
31
27
  }
32
- /**
33
- * The props as the resolved form. Call it inside a `computed`: the matchers become
34
- * functions here, an allowed LIST becoming a `Set` on the way, so no lookup rescans it.
35
- *
36
- * `resolveMatcher` answers "is this one of them", which is exactly what an allowed list
37
- * asks of it, but a prop that is ABSENT has to allow everything, where the helper's own
38
- * empty case answers no. That inversion is the one thing it cannot be left to do.
39
- */
28
+ /** The props as the resolved form. */
40
29
  export declare function resolveLimits(props: TimeLimitProps): TimeLimits;
41
30
  /**
42
- * The interval a `minuteStep` actually walks by. A step at or below one minute is every
43
- * minute, which is what `snapMinute` reads it as.
31
+ * The interval a `minuteStep` actually walks by. A step at or below one minute is every minute,
32
+ * which `snapMinute` reads it as.
44
33
  */
45
34
  export declare function minuteInterval(step: number): number;
46
35
  /**
47
- * The minutes an interval leaves reachable inside an hour, built once per step. Every
48
- * question about an hour walks this list, and the face asks it for each of its 24 hours on
49
- * every render, so rebuilding it per call allocated for nothing. The list is shared:
50
- * read-only.
36
+ * The minutes an interval leaves reachable inside an hour, built once per step. The list is
37
+ * shared: read-only.
51
38
  */
52
39
  export declare function minuteGrid(step: number): readonly number[];
53
40
  /**
54
- * Whether the restrictions allow a time.
55
- *
56
- * TRAP: the bounds are compared as STRINGS. That works only because a canonical time is
57
- * zero-padded and fixed-width, which makes '09:30' < '17:00' order the way the clock does;
58
- * hand it a value that never went through `formatTime` and the comparison is quietly wrong.
41
+ * The bounds are compared as STRINGS. That works only because a canonical time is zero-padded
42
+ * and fixed-width, which makes '09:30' < '17:00' order the way the clock does; hand it a value
43
+ * that never went through `formatTime` and the comparison is quietly wrong.
59
44
  */
60
45
  export declare function isTimeAllowed(hour: number, minute: number, limits: TimeLimits): boolean;
61
46
  /** The minutes of one hour that can be chosen, in order. */
@@ -66,30 +51,18 @@ export declare function allowedMinutesFor(hour: number, limits: TimeLimits): num
66
51
  * first half of it away from the minutes instead.
67
52
  */
68
53
  export declare function isHourAllowed(hour: number, limits: TimeLimits): boolean;
69
- /**
70
- * The minute nearest the one asked for that the hour allows, `null` when it allows none.
71
- * A tie goes to the earlier of the two: choosing an hour must not walk the clock past a
72
- * time the reader could have meant.
73
- */
54
+ /** The minute nearest the one asked for that the hour allows, `null` when it allows none. */
74
55
  export declare function nearestAllowedMinute(hour: number, minute: number, limits: TimeLimits): number | null;
75
56
  /**
76
57
  * The allowed time nearest the one asked for, as a canonical string, `null` when the
77
- * restrictions allow nothing at all. It is what a panel opens on when the field is empty:
78
- * the current time, pulled to something the reader is actually able to confirm.
58
+ * restrictions allow nothing at all.
79
59
  */
80
60
  export declare function nearestAllowedTime(hour: number, minute: number, limits: TimeLimits): string | null;
81
61
  /**
82
62
  * Walking a cycle until a value is allowed: `at` produces the candidate `i` steps away,
83
- * counting from one, and `allowed` answers for it. It gives up after a full turn instead
84
- * of looping for ever, which is what a set of restrictions allowing nothing would do.
85
- *
86
- * This is what makes a key SKIP: with nothing restricted the first candidate answers, so
87
- * the walk costs one call and lands exactly where a plain step would have.
63
+ * counting from one, and `allowed` answers for it. It gives up after a full turn instead of
64
+ * looping for ever, which a set of restrictions allowing nothing would do.
88
65
  */
89
66
  export declare function firstAllowed(turn: number, at: (i: number) => number, allowed: (value: number) => boolean): number | null;
90
- /**
91
- * What is wrong with a set of restrictions, in a sentence, or nothing at all. Both
92
- * components print it under their own name: the mistake is the consumer's either way, and
93
- * only they know which of the two they wrote.
94
- */
67
+ /** What is wrong with a set of restrictions, in a sentence, or nothing at all. */
95
68
  export declare function limitsProblem(limits: TimeLimits): string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"limits.js","names":[],"sources":["../../../src/components/VTimePicker/limits.ts"],"sourcesContent":["// @core\n/**\n * Which times a clock may offer: the two bounds and the two rules, plus everything a\n * component has to derive from them — whether an hour still holds anything, which minute\n * to fall back on, where a key lands next.\n *\n * It sits in this folder rather than in `utils/time.ts` for the reasons `../VTimeInput/search.ts`\n * gives for its own neighbour: that module is in the import closure of every VCalendar\n * component through `VCalendar/layout`, so a rule only a time picker consults would be\n * weighed on every calendar that restricts nothing. VTimePicker owns the contract and\n * VTimeInput reads it, the `VIcon/iconProps` arrangement.\n *\n * Pure, which is what makes any of it testable: jsdom lays no dial out, so what the face\n * decides has to be decidable from numbers alone.\n */\nimport { resolveMatcher } from '../../utils/matcher'\nimport { memo } from '../../utils/memo'\nimport { formatTime } from '../../utils/time'\n\n/**\n * Which values are ALLOWED: the list of them, or a rule answering for one. The polarity is\n * the opposite of `DatePickerMatcher`, which names what is excluded, hence a name that\n * says so rather than a shared suffix.\n */\nexport type TimePickerAllowed = number[] | ((value: number) => boolean)\n\n/**\n * The restrictions in force, resolved ONCE: the matchers already turned into functions,\n * and the interval the minutes are reachable on carried alongside them, since whether an\n * hour has anything left in it depends on that too.\n */\nexport interface TimeLimits {\n /** The earliest time allowed, inclusive, as a canonical 24-hour `'HH:mm'`. */\n min?: string\n /** The latest time allowed, inclusive, in the same form. */\n max?: string\n /** Whether an hour, always the 24-hour one, is allowed. */\n hour: (hour: number) => boolean\n /** Whether a minute is allowed. */\n minute: (minute: number) => boolean\n /** The interval the minutes snap to, which is what makes one reachable at all. */\n step: number\n}\n\n/** The props both components declare, which is all this needs to resolve them. */\nexport interface TimeLimitProps {\n min?: string\n max?: string\n allowedHours?: TimePickerAllowed\n allowedMinutes?: TimePickerAllowed\n minuteStep: number\n}\n\n/** Allowing everything, which is what a restriction nobody wrote has to mean. */\nconst allowEverything = () => true\n\n/**\n * The props as the resolved form. Call it inside a `computed`: the matchers become\n * functions here, an allowed LIST becoming a `Set` on the way, so no lookup rescans it.\n *\n * `resolveMatcher` answers \"is this one of them\", which is exactly what an allowed list\n * asks of it, but a prop that is ABSENT has to allow everything, where the helper's own\n * empty case answers no. That inversion is the one thing it cannot be left to do.\n */\nexport function resolveLimits(props: TimeLimitProps): TimeLimits {\n return {\n min: props.min,\n max: props.max,\n hour: props.allowedHours ? resolveMatcher(props.allowedHours) : allowEverything,\n minute: props.allowedMinutes ? resolveMatcher(props.allowedMinutes) : allowEverything,\n step: props.minuteStep,\n }\n}\n\n/**\n * The interval a `minuteStep` actually walks by. A step at or below one minute is every\n * minute, which is what `snapMinute` reads it as.\n */\nexport function minuteInterval(step: number): number {\n // Whole minutes, as `snapMinute` reads it: a fractional step would put 7.5 on the face.\n const size = Math.round(step)\n return size > 1 ? size : 1\n}\n\nconst minuteGrids = new Map<number, readonly number[]>()\n\n/**\n * The minutes an interval leaves reachable inside an hour, built once per step. Every\n * question about an hour walks this list, and the face asks it for each of its 24 hours on\n * every render, so rebuilding it per call allocated for nothing. The list is shared:\n * read-only.\n */\nexport function minuteGrid(step: number): readonly number[] {\n return memo(minuteGrids, step, () => {\n const interval = minuteInterval(step)\n const grid: number[] = []\n for (let minute = 0; minute < 60; minute += interval) grid.push(minute)\n return grid\n })\n}\n\n/**\n * Whether the restrictions allow a time.\n *\n * TRAP: the bounds are compared as STRINGS. That works only because a canonical time is\n * zero-padded and fixed-width, which makes '09:30' < '17:00' order the way the clock does;\n * hand it a value that never went through `formatTime` and the comparison is quietly wrong.\n */\nexport function isTimeAllowed(hour: number, minute: number, limits: TimeLimits): boolean {\n if (!limits.hour(hour) || !limits.minute(minute)) return false\n // Without a bound there is nothing to compare, and no string worth formatting.\n if (!limits.min && !limits.max) return true\n const time = formatTime(hour, minute)\n return (!limits.min || time >= limits.min) && (!limits.max || time <= limits.max)\n}\n\n/** The minutes of one hour that can be chosen, in order. */\nexport function allowedMinutesFor(hour: number, limits: TimeLimits): number[] {\n return minuteGrid(limits.step).filter((minute) => isTimeAllowed(hour, minute, limits))\n}\n\n/**\n * Whether an hour still holds a minute one could choose. An hour is closed only when\n * nothing at all is left in it: `min: '09:30'` leaves nine o'clock open and takes the\n * first half of it away from the minutes instead.\n */\nexport function isHourAllowed(hour: number, limits: TimeLimits): boolean {\n if (!limits.hour(hour)) return false\n return minuteGrid(limits.step).some((minute) => isTimeAllowed(hour, minute, limits))\n}\n\n/**\n * The minute nearest the one asked for that the hour allows, `null` when it allows none.\n * A tie goes to the earlier of the two: choosing an hour must not walk the clock past a\n * time the reader could have meant.\n */\nexport function nearestAllowedMinute(\n hour: number,\n minute: number,\n limits: TimeLimits,\n): number | null {\n let best: number | null = null\n let bestDistance = Number.POSITIVE_INFINITY\n for (const candidate of minuteGrid(limits.step)) {\n if (!isTimeAllowed(hour, candidate, limits)) continue\n const distance = Math.abs(candidate - minute)\n if (distance < bestDistance) {\n best = candidate\n bestDistance = distance\n }\n }\n return best\n}\n\n/**\n * The allowed time nearest the one asked for, as a canonical string, `null` when the\n * restrictions allow nothing at all. It is what a panel opens on when the field is empty:\n * the current time, pulled to something the reader is actually able to confirm.\n */\nexport function nearestAllowedTime(\n hour: number,\n minute: number,\n limits: TimeLimits,\n): string | null {\n const target = hour * 60 + minute\n let best: string | null = null\n let bestDistance = Number.POSITIVE_INFINITY\n for (let candidate = 0; candidate < 24; candidate += 1) {\n if (!limits.hour(candidate)) continue\n for (const each of allowedMinutesFor(candidate, limits)) {\n const distance = Math.abs(candidate * 60 + each - target)\n if (distance < bestDistance) {\n best = formatTime(candidate, each)\n bestDistance = distance\n }\n }\n }\n return best\n}\n\n/**\n * Walking a cycle until a value is allowed: `at` produces the candidate `i` steps away,\n * counting from one, and `allowed` answers for it. It gives up after a full turn instead\n * of looping for ever, which is what a set of restrictions allowing nothing would do.\n *\n * This is what makes a key SKIP: with nothing restricted the first candidate answers, so\n * the walk costs one call and lands exactly where a plain step would have.\n */\nexport function firstAllowed(\n turn: number,\n at: (i: number) => number,\n allowed: (value: number) => boolean,\n): number | null {\n for (let i = 1; i <= turn; i += 1) {\n const value = at(i)\n if (allowed(value)) return value\n }\n return null\n}\n\n/**\n * What is wrong with a set of restrictions, in a sentence, or nothing at all. Both\n * components print it under their own name: the mistake is the consumer's either way, and\n * only they know which of the two they wrote.\n */\nexport function limitsProblem(limits: TimeLimits): string | null {\n if (limits.min && limits.max && limits.min > limits.max)\n return `min \"${limits.min}\" falls after max \"${limits.max}\", so no time can be chosen.`\n for (let hour = 0; hour < 24; hour += 1) if (isHourAllowed(hour, limits)) return null\n return 'min, max, allowedHours and allowedMinutes together leave no time that can be chosen.'\n}\n"],"mappings":";;;;AAsDA,IAAM,UAAwB;AAU9B,SAAgB,EAAc,GAAmC;CAC/D,OAAO;EACL,KAAK,EAAM;EACX,KAAK,EAAM;EACX,MAAM,EAAM,eAAe,EAAe,EAAM,YAAY,IAAI;EAChE,QAAQ,EAAM,iBAAiB,EAAe,EAAM,cAAc,IAAI;EACtE,MAAM,EAAM;CACd;AACF;AAMA,SAAgB,EAAe,GAAsB;CAEnD,IAAM,IAAO,KAAK,MAAM,CAAI;CAC5B,OAAO,IAAO,IAAI,IAAO;AAC3B;AAEA,IAAM,oBAAc,IAAI,IAA+B;AAQvD,SAAgB,EAAW,GAAiC;CAC1D,OAAO,EAAK,GAAa,SAAY;EACnC,IAAM,IAAW,EAAe,CAAI,GAC9B,IAAiB,CAAC;EACxB,KAAK,IAAI,IAAS,GAAG,IAAS,IAAI,KAAU,GAAU,EAAK,KAAK,CAAM;EACtE,OAAO;CACT,CAAC;AACH;AASA,SAAgB,EAAc,GAAc,GAAgB,GAA6B;CACvF,IAAI,CAAC,EAAO,KAAK,CAAI,KAAK,CAAC,EAAO,OAAO,CAAM,GAAG,OAAO;CAEzD,IAAI,CAAC,EAAO,OAAO,CAAC,EAAO,KAAK,OAAO;CACvC,IAAM,IAAO,EAAW,GAAM,CAAM;CACpC,QAAQ,CAAC,EAAO,OAAO,KAAQ,EAAO,SAAS,CAAC,EAAO,OAAO,KAAQ,EAAO;AAC/E;AAGA,SAAgB,EAAkB,GAAc,GAA8B;CAC5E,OAAO,EAAW,EAAO,IAAI,CAAC,CAAC,QAAQ,MAAW,EAAc,GAAM,GAAQ,CAAM,CAAC;AACvF;AAOA,SAAgB,EAAc,GAAc,GAA6B;CAEvE,OADK,EAAO,KAAK,CAAI,IACd,EAAW,EAAO,IAAI,CAAC,CAAC,MAAM,MAAW,EAAc,GAAM,GAAQ,CAAM,CAAC,IADpD;AAEjC;AAOA,SAAgB,EACd,GACA,GACA,GACe;CACf,IAAI,IAAsB,MACtB,IAAe;CACnB,KAAK,IAAM,KAAa,EAAW,EAAO,IAAI,GAAG;EAC/C,IAAI,CAAC,EAAc,GAAM,GAAW,CAAM,GAAG;EAC7C,IAAM,IAAW,KAAK,IAAI,IAAY,CAAM;EAC5C,AAAI,IAAW,MACb,IAAO,GACP,IAAe;CAEnB;CACA,OAAO;AACT;AAOA,SAAgB,EACd,GACA,GACA,GACe;CACf,IAAM,IAAS,IAAO,KAAK,GACvB,IAAsB,MACtB,IAAe;CACnB,KAAK,IAAI,IAAY,GAAG,IAAY,IAAI,KAAa,GAC9C,MAAO,KAAK,CAAS,GAC1B,KAAK,IAAM,KAAQ,EAAkB,GAAW,CAAM,GAAG;EACvD,IAAM,IAAW,KAAK,IAAI,IAAY,KAAK,IAAO,CAAM;EACxD,AAAI,IAAW,MACb,IAAO,EAAW,GAAW,CAAI,GACjC,IAAe;CAEnB;CAEF,OAAO;AACT;AAUA,SAAgB,EACd,GACA,GACA,GACe;CACf,KAAK,IAAI,IAAI,GAAG,KAAK,GAAM,KAAK,GAAG;EACjC,IAAM,IAAQ,EAAG,CAAC;EAClB,IAAI,EAAQ,CAAK,GAAG,OAAO;CAC7B;CACA,OAAO;AACT;AAOA,SAAgB,EAAc,GAAmC;CAC/D,IAAI,EAAO,OAAO,EAAO,OAAO,EAAO,MAAM,EAAO,KAClD,OAAO,QAAQ,EAAO,IAAI,qBAAqB,EAAO,IAAI;CAC5D,KAAK,IAAI,IAAO,GAAG,IAAO,IAAI,KAAQ,GAAG,IAAI,EAAc,GAAM,CAAM,GAAG,OAAO;CACjF,OAAO;AACT"}
1
+ {"version":3,"file":"limits.js","names":[],"sources":["../../../src/components/VTimePicker/limits.ts"],"sourcesContent":["// @core\n/**\n * Pure, which makes any of it testable: jsdom lays no dial out, so what the face decides has to\n * be decidable from numbers alone.\n */\nimport { resolveMatcher } from '../../utils/matcher'\nimport { memo } from '../../utils/memo'\nimport { formatTime } from '../../utils/time'\n\n/** Which values are ALLOWED: the list of them, or a rule answering for one. */\nexport type TimePickerAllowed = number[] | ((value: number) => boolean)\n\n/**\n * The restrictions in force, resolved once: the matchers already turned into functions, and the\n * interval the minutes are reachable on carried alongside them, since whether an hour has\n * anything left in it depends on that too.\n */\nexport interface TimeLimits {\n /** The earliest time allowed, inclusive, as a canonical 24-hour `'HH:mm'`. */\n min?: string\n /** The latest time allowed, inclusive, in the same form. */\n max?: string\n /** Whether an hour, always the 24-hour one, is allowed. */\n hour: (hour: number) => boolean\n /** Whether a minute is allowed. */\n minute: (minute: number) => boolean\n /** The interval the minutes snap to, which makes one reachable at all. */\n step: number\n}\n\n/** The props both components declare, which is all this needs to resolve them. */\nexport interface TimeLimitProps {\n min?: string\n max?: string\n allowedHours?: TimePickerAllowed\n allowedMinutes?: TimePickerAllowed\n minuteStep: number\n}\n\n/** Allowing everything, which a restriction nobody wrote has to mean. */\nconst allowEverything = () => true\n\n/** The props as the resolved form. */\nexport function resolveLimits(props: TimeLimitProps): TimeLimits {\n return {\n min: props.min,\n max: props.max,\n hour: props.allowedHours ? resolveMatcher(props.allowedHours) : allowEverything,\n minute: props.allowedMinutes ? resolveMatcher(props.allowedMinutes) : allowEverything,\n step: props.minuteStep,\n }\n}\n\n/**\n * The interval a `minuteStep` actually walks by. A step at or below one minute is every minute,\n * which `snapMinute` reads it as.\n */\nexport function minuteInterval(step: number): number {\n const size = Math.round(step)\n return size > 1 ? size : 1\n}\n\nconst minuteGrids = new Map<number, readonly number[]>()\n\n/**\n * The minutes an interval leaves reachable inside an hour, built once per step. The list is\n * shared: read-only.\n */\nexport function minuteGrid(step: number): readonly number[] {\n return memo(minuteGrids, step, () => {\n const interval = minuteInterval(step)\n const grid: number[] = []\n for (let minute = 0; minute < 60; minute += interval) grid.push(minute)\n return grid\n })\n}\n\n/**\n * The bounds are compared as STRINGS. That works only because a canonical time is zero-padded\n * and fixed-width, which makes '09:30' < '17:00' order the way the clock does; hand it a value\n * that never went through `formatTime` and the comparison is quietly wrong.\n */\nexport function isTimeAllowed(hour: number, minute: number, limits: TimeLimits): boolean {\n if (!limits.hour(hour) || !limits.minute(minute)) return false\n // Without a bound there is nothing to compare, and no string worth formatting.\n if (!limits.min && !limits.max) return true\n const time = formatTime(hour, minute)\n return (!limits.min || time >= limits.min) && (!limits.max || time <= limits.max)\n}\n\n/** The minutes of one hour that can be chosen, in order. */\nexport function allowedMinutesFor(hour: number, limits: TimeLimits): number[] {\n return minuteGrid(limits.step).filter((minute) => isTimeAllowed(hour, minute, limits))\n}\n\n/**\n * Whether an hour still holds a minute one could choose. An hour is closed only when\n * nothing at all is left in it: `min: '09:30'` leaves nine o'clock open and takes the\n * first half of it away from the minutes instead.\n */\nexport function isHourAllowed(hour: number, limits: TimeLimits): boolean {\n if (!limits.hour(hour)) return false\n return minuteGrid(limits.step).some((minute) => isTimeAllowed(hour, minute, limits))\n}\n\n/** The minute nearest the one asked for that the hour allows, `null` when it allows none. */\nexport function nearestAllowedMinute(\n hour: number,\n minute: number,\n limits: TimeLimits,\n): number | null {\n let best: number | null = null\n let bestDistance = Number.POSITIVE_INFINITY\n for (const candidate of minuteGrid(limits.step)) {\n if (!isTimeAllowed(hour, candidate, limits)) continue\n const distance = Math.abs(candidate - minute)\n if (distance < bestDistance) {\n best = candidate\n bestDistance = distance\n }\n }\n return best\n}\n\n/**\n * The allowed time nearest the one asked for, as a canonical string, `null` when the\n * restrictions allow nothing at all.\n */\nexport function nearestAllowedTime(\n hour: number,\n minute: number,\n limits: TimeLimits,\n): string | null {\n const target = hour * 60 + minute\n let best: string | null = null\n let bestDistance = Number.POSITIVE_INFINITY\n for (let candidate = 0; candidate < 24; candidate += 1) {\n if (!limits.hour(candidate)) continue\n for (const each of allowedMinutesFor(candidate, limits)) {\n const distance = Math.abs(candidate * 60 + each - target)\n if (distance < bestDistance) {\n best = formatTime(candidate, each)\n bestDistance = distance\n }\n }\n }\n return best\n}\n\n/**\n * Walking a cycle until a value is allowed: `at` produces the candidate `i` steps away,\n * counting from one, and `allowed` answers for it. It gives up after a full turn instead of\n * looping for ever, which a set of restrictions allowing nothing would do.\n */\nexport function firstAllowed(\n turn: number,\n at: (i: number) => number,\n allowed: (value: number) => boolean,\n): number | null {\n for (let i = 1; i <= turn; i += 1) {\n const value = at(i)\n if (allowed(value)) return value\n }\n return null\n}\n\n/** What is wrong with a set of restrictions, in a sentence, or nothing at all. */\nexport function limitsProblem(limits: TimeLimits): string | null {\n if (limits.min && limits.max && limits.min > limits.max)\n return `min \"${limits.min}\" falls after max \"${limits.max}\", so no time can be chosen.`\n for (let hour = 0; hour < 24; hour += 1) if (isHourAllowed(hour, limits)) return null\n return 'min, max, allowedHours and allowedMinutes together leave no time that can be chosen.'\n}\n"],"mappings":";;;;AAwCA,IAAM,UAAwB;AAG9B,SAAgB,EAAc,GAAmC;CAC/D,OAAO;EACL,KAAK,EAAM;EACX,KAAK,EAAM;EACX,MAAM,EAAM,eAAe,EAAe,EAAM,YAAY,IAAI;EAChE,QAAQ,EAAM,iBAAiB,EAAe,EAAM,cAAc,IAAI;EACtE,MAAM,EAAM;CACd;AACF;AAMA,SAAgB,EAAe,GAAsB;CACnD,IAAM,IAAO,KAAK,MAAM,CAAI;CAC5B,OAAO,IAAO,IAAI,IAAO;AAC3B;AAEA,IAAM,oBAAc,IAAI,IAA+B;AAMvD,SAAgB,EAAW,GAAiC;CAC1D,OAAO,EAAK,GAAa,SAAY;EACnC,IAAM,IAAW,EAAe,CAAI,GAC9B,IAAiB,CAAC;EACxB,KAAK,IAAI,IAAS,GAAG,IAAS,IAAI,KAAU,GAAU,EAAK,KAAK,CAAM;EACtE,OAAO;CACT,CAAC;AACH;AAOA,SAAgB,EAAc,GAAc,GAAgB,GAA6B;CACvF,IAAI,CAAC,EAAO,KAAK,CAAI,KAAK,CAAC,EAAO,OAAO,CAAM,GAAG,OAAO;CAEzD,IAAI,CAAC,EAAO,OAAO,CAAC,EAAO,KAAK,OAAO;CACvC,IAAM,IAAO,EAAW,GAAM,CAAM;CACpC,QAAQ,CAAC,EAAO,OAAO,KAAQ,EAAO,SAAS,CAAC,EAAO,OAAO,KAAQ,EAAO;AAC/E;AAGA,SAAgB,EAAkB,GAAc,GAA8B;CAC5E,OAAO,EAAW,EAAO,IAAI,CAAC,CAAC,QAAQ,MAAW,EAAc,GAAM,GAAQ,CAAM,CAAC;AACvF;AAOA,SAAgB,EAAc,GAAc,GAA6B;CAEvE,OADK,EAAO,KAAK,CAAI,IACd,EAAW,EAAO,IAAI,CAAC,CAAC,MAAM,MAAW,EAAc,GAAM,GAAQ,CAAM,CAAC,IADpD;AAEjC;AAGA,SAAgB,EACd,GACA,GACA,GACe;CACf,IAAI,IAAsB,MACtB,IAAe;CACnB,KAAK,IAAM,KAAa,EAAW,EAAO,IAAI,GAAG;EAC/C,IAAI,CAAC,EAAc,GAAM,GAAW,CAAM,GAAG;EAC7C,IAAM,IAAW,KAAK,IAAI,IAAY,CAAM;EAC5C,AAAI,IAAW,MACb,IAAO,GACP,IAAe;CAEnB;CACA,OAAO;AACT;AAMA,SAAgB,EACd,GACA,GACA,GACe;CACf,IAAM,IAAS,IAAO,KAAK,GACvB,IAAsB,MACtB,IAAe;CACnB,KAAK,IAAI,IAAY,GAAG,IAAY,IAAI,KAAa,GAC9C,MAAO,KAAK,CAAS,GAC1B,KAAK,IAAM,KAAQ,EAAkB,GAAW,CAAM,GAAG;EACvD,IAAM,IAAW,KAAK,IAAI,IAAY,KAAK,IAAO,CAAM;EACxD,AAAI,IAAW,MACb,IAAO,EAAW,GAAW,CAAI,GACjC,IAAe;CAEnB;CAEF,OAAO;AACT;AAOA,SAAgB,EACd,GACA,GACA,GACe;CACf,KAAK,IAAI,IAAI,GAAG,KAAK,GAAM,KAAK,GAAG;EACjC,IAAM,IAAQ,EAAG,CAAC;EAClB,IAAI,EAAQ,CAAK,GAAG,OAAO;CAC7B;CACA,OAAO;AACT;AAGA,SAAgB,EAAc,GAAmC;CAC/D,IAAI,EAAO,OAAO,EAAO,OAAO,EAAO,MAAM,EAAO,KAClD,OAAO,QAAQ,EAAO,IAAI,qBAAqB,EAAO,IAAI;CAC5D,KAAK,IAAI,IAAO,GAAG,IAAO,IAAI,KAAQ,GAAG,IAAI,EAAc,GAAM,CAAM,GAAG,OAAO;CACjF,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"VToast.js","names":[],"sources":["../../../src/components/VToast/VToast.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * The card one notification is drawn on. It holds no state and decides nothing: the\n * VToaster renders it, and it is internal to the component — never exported.\n *\n * How insistently it is announced follows its tone. A failure or a warning interrupts\n * whatever a screen reader is saying; anything else waits for a pause. There is a\n * known trade-off in the polite case: some screen readers miss the FIRST notification\n * of a stack, because the region that announces it comes into existence already\n * holding its text. The interruptive ones are always announced.\n */\n\nimport { computed } from 'vue'\n\nimport { cssSize } from '../../utils/css'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { check_circle as checkCircleIcon } from '../VIcon/icons/check_circle'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport { error as errorIcon } from '../VIcon/icons/error'\nimport { info as infoIcon } from '../VIcon/icons/info'\nimport { notifications as notificationsIcon } from '../VIcon/icons/notifications'\nimport { warning as warningIcon } from '../VIcon/icons/warning'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport type { ToastItem, ToastTone } from './state'\n\nconst props = defineProps<{\n /** The notification to draw, with all its options already resolved. */\n item: ToastItem\n /** What the close cross does, in words. */\n closeLabel: string\n}>()\n\nconst emit = defineEmits<{\n /**\n * The reader asked for this notification to go. The card removes nothing itself;\n * the queue belongs to the VToaster.\n */\n close: [id: number]\n}>()\n\nconst DEFAULT_ICONS: Record<ToastTone, IconSource> = {\n neutral: notificationsIcon,\n accent: infoIcon,\n success: checkCircleIcon,\n danger: errorIcon,\n warning: warningIcon,\n}\n\n/**\n * The icon to draw: the one the notification asked for, or failing that the one its\n * tone brings. Asking for `false` means no icon at all.\n */\nconst icon = computed(() =>\n props.item.icon === false\n ? undefined\n : iconProps(props.item.icon ?? DEFAULT_ICONS[props.item.tone]),\n)\n</script>\n\n<template>\n <div\n class=\"v-banner v-toast v-tone\"\n :data-tone=\"item.tone\"\n :data-variant=\"item.variant\"\n :style=\"item.width !== undefined ? { '--toast-width': cssSize(item.width) } : undefined\"\n >\n <VIcon v-if=\"icon\" class=\"v-toast-icon\" v-bind=\"icon\" />\n <div class=\"v-banner-text v-toast-body\">\n <p v-if=\"item.title\" class=\"v-toast-title\">{{ item.title }}</p>\n <p class=\"v-toast-message\">{{ item.message }}</p>\n </div>\n <VIconButton\n v-if=\"!item.hideClose\"\n class=\"v-banner-control v-toast-close\"\n :label=\"closeLabel\"\n size=\"sm\"\n @click=\"emit('close', item.id)\"\n >\n <VIcon :name=\"closeIcon\" />\n </VIconButton>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The box, the decoration, the typography and the entry motion come from the shared\n `.v-banner` class set on this same element — the chassis this card has in common\n with the snackbar, in `styles/banner.css`, along with the message block\n (`.v-banner-text`) and the close cross's margins (`.v-banner-control`). What stays here\n is what the two genuinely differ on: the alignment, the padding, the width, and the\n whole of the tone painting.\n `--banner-line`, the one-line alignment unit both use, is defined there. */\n .v-toast {\n /* A notification hooks its icon and its cross to the FIRST line of the message: it\n may carry a title and several lines, and the eye reads the icon against the text it\n introduces, not against the middle of a paragraph. The `--banner-line` margins are\n what make that identical to centring on a single line. (VSnackbar centres instead —\n one short sentence, and a control that would sit in a corner otherwise.) */\n align-items: flex-start;\n padding: var(--vectis-space-3) var(--vectis-space-4);\n inline-size: var(--toast-width, var(--vectis-control-size-toast-width));\n /* On a narrow screen the card is never wider than the viewport, the stack's own\n margins deducted. */\n max-inline-size: calc(100dvi - 2 * var(--vectis-space-4));\n }\n\n /* The tone table itself lives in styles/tones.css, in a layer below the components,\n and is shared with VButton and VChip. The rules below read that shared contract\n directly rather than restating any of its colours.\n\n One local name survives, and for a precise reason: the close cross rebinds the\n tone's text colour inside its own subtree further down, so pointing this variable\n at that one would turn into a self-reference. It is resolved here, on the card, so\n that the cross inherits a value already computed rather than recomputing it in a\n context where it no longer means the same thing. */\n .v-toast {\n --toast-accent: var(--tone-text-tinted);\n }\n\n /* The neutral tone is overridden here, and for the soft variant only. A notification\n floats above the page rather than sitting in it, so it takes the overlay\n background and the plain border that goes with it, where the shared table assumes\n a surface within the page.\n\n The solid pair is deliberately left alone: its text and surface inversion is\n canonical across the design system. This block sits one layer above the table, so\n it wins whatever order the two sheets end up in. */\n .v-toast[data-tone='neutral'] {\n --tone-bg-soft: var(--vectis-color-surface-overlay);\n --tone-border-soft: var(--vectis-color-border);\n --toast-accent: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] {\n background: var(--tone-bg-soft);\n border: 1px solid var(--tone-border-soft);\n color: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] .v-toast-icon,\n .v-toast[data-variant='soft'] .v-toast-title {\n color: var(--toast-accent);\n }\n\n .v-toast[data-variant='soft'] .v-toast-message {\n color: var(--vectis-color-text-muted);\n }\n\n .v-toast[data-variant='solid'] {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /*\n * On a tinted card the close cross takes the tone's own accent. On a solid one it keeps\n * the surrounding text colour the shared `.v-banner-control` rule gives it — the only thing\n * guaranteed readable against that background.\n *\n * TRAP — (0,4,0) is load-bearing: the shared rebind is (0,3,0) in the same layer and ships\n * in another sheet, so at equal specificity the winner would be whichever sheet the\n * consumer's bundler put last.\n */\n .v-toast[data-variant='soft'] .v-toast-close[data-tone] {\n --tone-text-tinted: var(--toast-accent);\n --tone-bg-soft: color-mix(in oklab, var(--toast-accent), transparent 88%);\n }\n\n .v-toast-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n margin-block: calc((var(--banner-line) - var(--vectis-icon-size-md)) / 2);\n }\n\n .v-toast-title {\n margin-block-end: var(--vectis-space-1);\n /* The heavier weight marks the title against its own message, which is emphasis\n rather than a typographic role — hence a font token read directly. */\n font-weight: var(--vectis-font-weight-semibold);\n }\n\n /* Windows forced colors flattens the solid card's background to Canvas and drops its\n shadow, which were its only edge (the soft card carries a border): it would float over\n the page with no boundary at all. An outline draws one without moving the layout. */\n @media (forced-colors: active) {\n .v-toast[data-variant='solid'] {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VToast.js","names":[],"sources":["../../../src/components/VToast/VToast.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Presentation-only notification card; VToaster owns queue state and polite/assertive\n * announcements to avoid duplicate status messages.\n */\n\nimport { computed } from 'vue'\n\nimport { cssSize } from '../../utils/css'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { check_circle as checkCircleIcon } from '../VIcon/icons/check_circle'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport { error as errorIcon } from '../VIcon/icons/error'\nimport { info as infoIcon } from '../VIcon/icons/info'\nimport { notifications as notificationsIcon } from '../VIcon/icons/notifications'\nimport { warning as warningIcon } from '../VIcon/icons/warning'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport type { ToastItem, ToastTone } from './state'\n\nconst props = defineProps<{\n /** The notification to draw, with all its options already resolved. */\n item: ToastItem\n /** What the close cross does, in words. */\n closeLabel: string\n}>()\n\nconst emit = defineEmits<{\n /**\n * The reader asked for this notification to go. The card removes nothing itself;\n * the queue belongs to the VToaster.\n */\n close: [id: number]\n}>()\n\nconst DEFAULT_ICONS: Record<ToastTone, IconSource> = {\n neutral: notificationsIcon,\n accent: infoIcon,\n success: checkCircleIcon,\n danger: errorIcon,\n warning: warningIcon,\n}\n\n/**\n * The icon to draw: the one the notification asked for, or failing that the one its\n * tone brings. Asking for `false` means no icon at all.\n */\nconst icon = computed(() =>\n props.item.icon === false\n ? undefined\n : iconProps(props.item.icon ?? DEFAULT_ICONS[props.item.tone]),\n)\n</script>\n\n<template>\n <div\n class=\"v-banner v-toast v-tone\"\n :data-tone=\"item.tone\"\n :data-variant=\"item.variant\"\n :style=\"item.width !== undefined ? { '--toast-width': cssSize(item.width) } : undefined\"\n >\n <VIcon v-if=\"icon\" class=\"v-toast-icon\" v-bind=\"icon\" />\n <div class=\"v-banner-text v-toast-body\">\n <p v-if=\"item.title\" class=\"v-toast-title\">{{ item.title }}</p>\n <p class=\"v-toast-message\">{{ item.message }}</p>\n </div>\n <VIconButton\n v-if=\"!item.hideClose\"\n class=\"v-banner-control v-toast-close\"\n :label=\"closeLabel\"\n size=\"sm\"\n @click=\"emit('close', item.id)\"\n >\n <VIcon :name=\"closeIcon\" />\n </VIconButton>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * Shared banner chrome supplies decoration and motion; toast owns alignment, dimensions and\n * tone painting.\n */\n .v-toast {\n /*\n * The `--banner-line` margins are what make that identical to centring on a single line.\n * (VSnackbar centres instead; one short sentence, and a control that would sit in a corner\n * otherwise.)\n */\n align-items: flex-start;\n padding: var(--vectis-space-3) var(--vectis-space-4);\n inline-size: var(--toast-width, var(--vectis-control-size-toast-width));\n /* On a narrow screen the card is never wider than the viewport, the stack's own\n margins deducted. */\n max-inline-size: calc(100dvi - 2 * var(--vectis-space-4));\n }\n\n /*\n * The tone table itself lives in styles/tones.css, in a layer below the components, and is\n * shared with VButton and VChip. The rules below read that shared contract directly rather\n * than restating any of its colours.\n */\n .v-toast {\n --toast-accent: var(--tone-text-tinted);\n }\n\n /*\n * The neutral tone is overridden here, and for the soft variant only. A notification floats\n * above the page rather than sitting in it, so it takes the overlay background and the plain\n * border that goes with it, where the shared table assumes a surface within the page.\n */\n .v-toast[data-tone='neutral'] {\n --tone-bg-soft: var(--vectis-color-surface-overlay);\n --tone-border-soft: var(--vectis-color-border);\n --toast-accent: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] {\n background: var(--tone-bg-soft);\n border: 1px solid var(--tone-border-soft);\n color: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] .v-toast-icon,\n .v-toast[data-variant='soft'] .v-toast-title {\n color: var(--toast-accent);\n }\n\n .v-toast[data-variant='soft'] .v-toast-message {\n color: var(--vectis-color-text-muted);\n }\n\n .v-toast[data-variant='solid'] {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /*\n * (0,4,0) is load-bearing: the shared rebind is (0,3,0) in the same layer and ships in\n * another sheet, so at equal specificity the winner would be whichever sheet the consumer's\n * bundler put last.\n */\n .v-toast[data-variant='soft'] .v-toast-close[data-tone] {\n --tone-text-tinted: var(--toast-accent);\n --tone-bg-soft: color-mix(in oklab, var(--toast-accent), transparent 88%);\n }\n\n .v-toast-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n margin-block: calc((var(--banner-line) - var(--vectis-icon-size-md)) / 2);\n }\n\n .v-toast-title {\n margin-block-end: var(--vectis-space-1);\n /*\n * The heavier weight marks the title against its own message, which is emphasis rather than\n * a typographic role; hence a font token read directly.\n */\n font-weight: var(--vectis-font-weight-semibold);\n }\n\n /* An outline draws one without moving the layout. */\n @media (forced-colors: active) {\n .v-toast[data-variant='solid'] {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"VToast.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VToast/VToast.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * The card one notification is drawn on. It holds no state and decides nothing: the\n * VToaster renders it, and it is internal to the component — never exported.\n *\n * How insistently it is announced follows its tone. A failure or a warning interrupts\n * whatever a screen reader is saying; anything else waits for a pause. There is a\n * known trade-off in the polite case: some screen readers miss the FIRST notification\n * of a stack, because the region that announces it comes into existence already\n * holding its text. The interruptive ones are always announced.\n */\n\nimport { computed } from 'vue'\n\nimport { cssSize } from '../../utils/css'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { check_circle as checkCircleIcon } from '../VIcon/icons/check_circle'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport { error as errorIcon } from '../VIcon/icons/error'\nimport { info as infoIcon } from '../VIcon/icons/info'\nimport { notifications as notificationsIcon } from '../VIcon/icons/notifications'\nimport { warning as warningIcon } from '../VIcon/icons/warning'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport type { ToastItem, ToastTone } from './state'\n\nconst props = defineProps<{\n /** The notification to draw, with all its options already resolved. */\n item: ToastItem\n /** What the close cross does, in words. */\n closeLabel: string\n}>()\n\nconst emit = defineEmits<{\n /**\n * The reader asked for this notification to go. The card removes nothing itself;\n * the queue belongs to the VToaster.\n */\n close: [id: number]\n}>()\n\nconst DEFAULT_ICONS: Record<ToastTone, IconSource> = {\n neutral: notificationsIcon,\n accent: infoIcon,\n success: checkCircleIcon,\n danger: errorIcon,\n warning: warningIcon,\n}\n\n/**\n * The icon to draw: the one the notification asked for, or failing that the one its\n * tone brings. Asking for `false` means no icon at all.\n */\nconst icon = computed(() =>\n props.item.icon === false\n ? undefined\n : iconProps(props.item.icon ?? DEFAULT_ICONS[props.item.tone]),\n)\n</script>\n\n<template>\n <div\n class=\"v-banner v-toast v-tone\"\n :data-tone=\"item.tone\"\n :data-variant=\"item.variant\"\n :style=\"item.width !== undefined ? { '--toast-width': cssSize(item.width) } : undefined\"\n >\n <VIcon v-if=\"icon\" class=\"v-toast-icon\" v-bind=\"icon\" />\n <div class=\"v-banner-text v-toast-body\">\n <p v-if=\"item.title\" class=\"v-toast-title\">{{ item.title }}</p>\n <p class=\"v-toast-message\">{{ item.message }}</p>\n </div>\n <VIconButton\n v-if=\"!item.hideClose\"\n class=\"v-banner-control v-toast-close\"\n :label=\"closeLabel\"\n size=\"sm\"\n @click=\"emit('close', item.id)\"\n >\n <VIcon :name=\"closeIcon\" />\n </VIconButton>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /* The box, the decoration, the typography and the entry motion come from the shared\n `.v-banner` class set on this same element — the chassis this card has in common\n with the snackbar, in `styles/banner.css`, along with the message block\n (`.v-banner-text`) and the close cross's margins (`.v-banner-control`). What stays here\n is what the two genuinely differ on: the alignment, the padding, the width, and the\n whole of the tone painting.\n `--banner-line`, the one-line alignment unit both use, is defined there. */\n .v-toast {\n /* A notification hooks its icon and its cross to the FIRST line of the message: it\n may carry a title and several lines, and the eye reads the icon against the text it\n introduces, not against the middle of a paragraph. The `--banner-line` margins are\n what make that identical to centring on a single line. (VSnackbar centres instead —\n one short sentence, and a control that would sit in a corner otherwise.) */\n align-items: flex-start;\n padding: var(--vectis-space-3) var(--vectis-space-4);\n inline-size: var(--toast-width, var(--vectis-control-size-toast-width));\n /* On a narrow screen the card is never wider than the viewport, the stack's own\n margins deducted. */\n max-inline-size: calc(100dvi - 2 * var(--vectis-space-4));\n }\n\n /* The tone table itself lives in styles/tones.css, in a layer below the components,\n and is shared with VButton and VChip. The rules below read that shared contract\n directly rather than restating any of its colours.\n\n One local name survives, and for a precise reason: the close cross rebinds the\n tone's text colour inside its own subtree further down, so pointing this variable\n at that one would turn into a self-reference. It is resolved here, on the card, so\n that the cross inherits a value already computed rather than recomputing it in a\n context where it no longer means the same thing. */\n .v-toast {\n --toast-accent: var(--tone-text-tinted);\n }\n\n /* The neutral tone is overridden here, and for the soft variant only. A notification\n floats above the page rather than sitting in it, so it takes the overlay\n background and the plain border that goes with it, where the shared table assumes\n a surface within the page.\n\n The solid pair is deliberately left alone: its text and surface inversion is\n canonical across the design system. This block sits one layer above the table, so\n it wins whatever order the two sheets end up in. */\n .v-toast[data-tone='neutral'] {\n --tone-bg-soft: var(--vectis-color-surface-overlay);\n --tone-border-soft: var(--vectis-color-border);\n --toast-accent: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] {\n background: var(--tone-bg-soft);\n border: 1px solid var(--tone-border-soft);\n color: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] .v-toast-icon,\n .v-toast[data-variant='soft'] .v-toast-title {\n color: var(--toast-accent);\n }\n\n .v-toast[data-variant='soft'] .v-toast-message {\n color: var(--vectis-color-text-muted);\n }\n\n .v-toast[data-variant='solid'] {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /*\n * On a tinted card the close cross takes the tone's own accent. On a solid one it keeps\n * the surrounding text colour the shared `.v-banner-control` rule gives it — the only thing\n * guaranteed readable against that background.\n *\n * TRAP — (0,4,0) is load-bearing: the shared rebind is (0,3,0) in the same layer and ships\n * in another sheet, so at equal specificity the winner would be whichever sheet the\n * consumer's bundler put last.\n */\n .v-toast[data-variant='soft'] .v-toast-close[data-tone] {\n --tone-text-tinted: var(--toast-accent);\n --tone-bg-soft: color-mix(in oklab, var(--toast-accent), transparent 88%);\n }\n\n .v-toast-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n margin-block: calc((var(--banner-line) - var(--vectis-icon-size-md)) / 2);\n }\n\n .v-toast-title {\n margin-block-end: var(--vectis-space-1);\n /* The heavier weight marks the title against its own message, which is emphasis\n rather than a typographic role — hence a font token read directly. */\n font-weight: var(--vectis-font-weight-semibold);\n }\n\n /* Windows forced colors flattens the solid card's background to Canvas and drops its\n shadow, which were its only edge (the soft card carries a border): it would float over\n the page with no boundary at all. An outline draws one without moving the layout. */\n @media (forced-colors: active) {\n .v-toast[data-variant='solid'] {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;EA6BA,IAAM,IAAQ,GAOR,IAAO,GAQP,IAA+C;GACnD,SAAS;GACT,QAAQ;GACR,SAAS;GACT,QAAQ;GACC;EACX,GAMM,IAAO,QACX,EAAM,KAAK,SAAS,KAChB,KAAA,IACA,EAAU,EAAM,KAAK,QAAQ,EAAc,EAAM,KAAK,KAAK,CACjE;yBAIE,EAoBM,OAAA;GAnBJ,OAAM;GACL,aAAW,EAAA,KAAK;GAChB,gBAAc,EAAA,KAAK;GACnB,OAAK,EAAE,EAAA,KAAK,UAAU,KAAA,IAAuD,KAAA,IAA9C,EAAA,iBAAsB,EAAA,CAAA,CAAO,CAAC,EAAA,KAAK,KAAK,EAAA,CAAe;;GAE1E,EAAA,SAAA,EAAA,GAAb,EAAwD,GAAxD,EAAwD;;IAArC,OAAM;MAAuB,EAAA,KAAI,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA;GACpD,EAGM,OAHN,GAGM,CAFK,EAAA,KAAK,SAAA,EAAA,GAAd,EAA+D,KAA/D,GAA+D,EAAjB,EAAA,KAAK,KAAK,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GACxD,EAAiD,KAAjD,GAAiD,EAAnB,EAAA,KAAK,OAAO,GAAA,CAAA,CAAA,CAAA;GAGnC,EAAA,KAAK,yBAAA,EAAA,GADd,EAQc,GAAA;;IANZ,OAAM;IACL,OAAO,EAAA;IACR,MAAK;IACJ,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,SAAU,EAAA,KAAK,EAAE;;qBAEF,CAA3B,EAA2B,GAAA,EAAnB,MAAM,EAAA,CAAA,EAAS,GAAA,MAAA,GAAA,CAAA,MAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"VToast.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VToast/VToast.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y\n/**\n * Presentation-only notification card; VToaster owns queue state and polite/assertive\n * announcements to avoid duplicate status messages.\n */\n\nimport { computed } from 'vue'\n\nimport { cssSize } from '../../utils/css'\n\nimport VIcon from '../VIcon/VIcon.vue'\nimport { iconProps } from '../VIcon/iconProps'\nimport { check_circle as checkCircleIcon } from '../VIcon/icons/check_circle'\nimport { close as closeIcon } from '../VIcon/icons/close'\nimport { error as errorIcon } from '../VIcon/icons/error'\nimport { info as infoIcon } from '../VIcon/icons/info'\nimport { notifications as notificationsIcon } from '../VIcon/icons/notifications'\nimport { warning as warningIcon } from '../VIcon/icons/warning'\nimport type { IconSource } from '../VIcon/types'\nimport VIconButton from '../VIconButton/VIconButton.vue'\nimport type { ToastItem, ToastTone } from './state'\n\nconst props = defineProps<{\n /** The notification to draw, with all its options already resolved. */\n item: ToastItem\n /** What the close cross does, in words. */\n closeLabel: string\n}>()\n\nconst emit = defineEmits<{\n /**\n * The reader asked for this notification to go. The card removes nothing itself;\n * the queue belongs to the VToaster.\n */\n close: [id: number]\n}>()\n\nconst DEFAULT_ICONS: Record<ToastTone, IconSource> = {\n neutral: notificationsIcon,\n accent: infoIcon,\n success: checkCircleIcon,\n danger: errorIcon,\n warning: warningIcon,\n}\n\n/**\n * The icon to draw: the one the notification asked for, or failing that the one its\n * tone brings. Asking for `false` means no icon at all.\n */\nconst icon = computed(() =>\n props.item.icon === false\n ? undefined\n : iconProps(props.item.icon ?? DEFAULT_ICONS[props.item.tone]),\n)\n</script>\n\n<template>\n <div\n class=\"v-banner v-toast v-tone\"\n :data-tone=\"item.tone\"\n :data-variant=\"item.variant\"\n :style=\"item.width !== undefined ? { '--toast-width': cssSize(item.width) } : undefined\"\n >\n <VIcon v-if=\"icon\" class=\"v-toast-icon\" v-bind=\"icon\" />\n <div class=\"v-banner-text v-toast-body\">\n <p v-if=\"item.title\" class=\"v-toast-title\">{{ item.title }}</p>\n <p class=\"v-toast-message\">{{ item.message }}</p>\n </div>\n <VIconButton\n v-if=\"!item.hideClose\"\n class=\"v-banner-control v-toast-close\"\n :label=\"closeLabel\"\n size=\"sm\"\n @click=\"emit('close', item.id)\"\n >\n <VIcon :name=\"closeIcon\" />\n </VIconButton>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * Shared banner chrome supplies decoration and motion; toast owns alignment, dimensions and\n * tone painting.\n */\n .v-toast {\n /*\n * The `--banner-line` margins are what make that identical to centring on a single line.\n * (VSnackbar centres instead; one short sentence, and a control that would sit in a corner\n * otherwise.)\n */\n align-items: flex-start;\n padding: var(--vectis-space-3) var(--vectis-space-4);\n inline-size: var(--toast-width, var(--vectis-control-size-toast-width));\n /* On a narrow screen the card is never wider than the viewport, the stack's own\n margins deducted. */\n max-inline-size: calc(100dvi - 2 * var(--vectis-space-4));\n }\n\n /*\n * The tone table itself lives in styles/tones.css, in a layer below the components, and is\n * shared with VButton and VChip. The rules below read that shared contract directly rather\n * than restating any of its colours.\n */\n .v-toast {\n --toast-accent: var(--tone-text-tinted);\n }\n\n /*\n * The neutral tone is overridden here, and for the soft variant only. A notification floats\n * above the page rather than sitting in it, so it takes the overlay background and the plain\n * border that goes with it, where the shared table assumes a surface within the page.\n */\n .v-toast[data-tone='neutral'] {\n --tone-bg-soft: var(--vectis-color-surface-overlay);\n --tone-border-soft: var(--vectis-color-border);\n --toast-accent: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] {\n background: var(--tone-bg-soft);\n border: 1px solid var(--tone-border-soft);\n color: var(--vectis-color-text);\n }\n\n .v-toast[data-variant='soft'] .v-toast-icon,\n .v-toast[data-variant='soft'] .v-toast-title {\n color: var(--toast-accent);\n }\n\n .v-toast[data-variant='soft'] .v-toast-message {\n color: var(--vectis-color-text-muted);\n }\n\n .v-toast[data-variant='solid'] {\n background: var(--tone-bg-solid);\n color: var(--tone-text-solid);\n }\n\n /*\n * (0,4,0) is load-bearing: the shared rebind is (0,3,0) in the same layer and ships in\n * another sheet, so at equal specificity the winner would be whichever sheet the consumer's\n * bundler put last.\n */\n .v-toast[data-variant='soft'] .v-toast-close[data-tone] {\n --tone-text-tinted: var(--toast-accent);\n --tone-bg-soft: color-mix(in oklab, var(--toast-accent), transparent 88%);\n }\n\n .v-toast-icon {\n --vectis-icon-size: var(--vectis-icon-size-md);\n margin-block: calc((var(--banner-line) - var(--vectis-icon-size-md)) / 2);\n }\n\n .v-toast-title {\n margin-block-end: var(--vectis-space-1);\n /*\n * The heavier weight marks the title against its own message, which is emphasis rather than\n * a typographic role; hence a font token read directly.\n */\n font-weight: var(--vectis-font-weight-semibold);\n }\n\n /* An outline draws one without moving the layout. */\n @media (forced-colors: active) {\n .v-toast[data-variant='solid'] {\n outline: 1px solid CanvasText;\n }\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;EAuBA,IAAM,IAAQ,GAOR,IAAO,GAQP,IAA+C;GACnD,SAAS;GACT,QAAQ;GACR,SAAS;GACT,QAAQ;GACC;EACX,GAMM,IAAO,QACX,EAAM,KAAK,SAAS,KAChB,KAAA,IACA,EAAU,EAAM,KAAK,QAAQ,EAAc,EAAM,KAAK,KAAK,CACjE;yBAIE,EAoBM,OAAA;GAnBJ,OAAM;GACL,aAAW,EAAA,KAAK;GAChB,gBAAc,EAAA,KAAK;GACnB,OAAK,EAAE,EAAA,KAAK,UAAU,KAAA,IAAuD,KAAA,IAA9C,EAAA,iBAAsB,EAAA,CAAA,CAAO,CAAC,EAAA,KAAK,KAAK,EAAA,CAAe;;GAE1E,EAAA,SAAA,EAAA,GAAb,EAAwD,GAAxD,EAAwD;;IAArC,OAAM;MAAuB,EAAA,KAAI,GAAA,MAAA,EAAA,KAAA,EAAA,IAAA,EAAA;GACpD,EAGM,OAHN,GAGM,CAFK,EAAA,KAAK,SAAA,EAAA,GAAd,EAA+D,KAA/D,GAA+D,EAAjB,EAAA,KAAK,KAAK,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GACxD,EAAiD,KAAjD,GAAiD,EAAnB,EAAA,KAAK,OAAO,GAAA,CAAA,CAAA,CAAA;GAGnC,EAAA,KAAK,yBAAA,EAAA,GADd,EAQc,GAAA;;IANZ,OAAM;IACL,OAAO,EAAA;IACR,MAAK;IACJ,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,SAAU,EAAA,KAAK,EAAE;;qBAEF,CAA3B,EAA2B,GAAA,EAAnB,MAAM,EAAA,CAAA,EAAS,GAAA,MAAA,GAAA,CAAA,MAAA,CAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"VToaster.js","names":[],"sources":["../../../src/components/VToast/VToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Where notifications appear: mounted ONCE at the root, after which every `toast()` call\n * shows up here.\n *\n * One container per placement rather than one popover per notification, each stacking its\n * own with CSS. These boxes are drawn above the page at physical coordinates, anchored to\n * nothing, so individually they would all land on the same spot and keeping them apart would\n * mean measuring and offsetting each in code. The six exist at all times; empty, they cost\n * nothing and are not displayed.\n *\n * The JS covers three things the platform does not: keeping the queue and the containers in\n * step, the popover being imperative; the per-notification dismissal timers; and holding\n * them while the pointer rests on a stack OR the keyboard is inside it, so something that\n * disappears on a clock can be read and closed (WCAG 2.2.1).\n */\n\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport VToast from './VToast.vue'\nimport { dismissToast, toasts, type ToastItem, type ToastPlacement } from './state'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useMessages } from '../../i18n/state'\n\ninterface ToasterProps {\n /** Which corner notifications appear in, unless one of them asks for another. */\n placement?: ToastPlacement\n /**\n * How long a notification stays, in milliseconds, unless it asks for something else.\n * A notification given 0 stays until it is dismissed.\n */\n duration?: number\n /** What the close cross does, in words. It falls back to the design system dictionary. */\n closeLabel?: string\n /**\n * What screen readers announce for the notification areas themselves, which are\n * landmarks of the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToasterProps>(), {\n placement: 'bottom-right',\n duration: 5000,\n closeLabel: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.toaster.label)\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? m.value.common.close)\n\nconst PLACEMENTS: ToastPlacement[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n]\n\n/**\n * The queue sorted into the corner each notification actually belongs to: the one it\n * asked for, or failing that the one set on this component.\n */\nconst groups = computed(() => {\n const map = new Map<ToastPlacement, ToastItem[]>()\n for (const item of toasts) {\n const placement = effectivePlacement(item)\n const list = map.get(placement)\n if (list) list.push(item)\n else map.set(placement, [item])\n }\n return map\n})\n\n/* The six containers, collected by the `v-for` itself. A plain template ref rather than a\n function one: a function written inline in the template is a new function on every\n render, which Vue answers by calling the old one with null and the new one with the\n element — twelve calls each time a notification comes or goes. */\nconst stackEls = ref<HTMLElement[]>([])\n\n/*\n * One instance of the popover plumbing PER corner. Each carries its own element, its\n * own open state — fed by that element's events — and the guards that make opening and\n * closing safe to call twice: asking the browser to show an already-shown popover\n * throws.\n *\n * The element is looked up by its placement rather than by its index: Vue does not\n * promise that a `v-for` ref array follows the order of the list.\n *\n * The containers being permanent, this runs once and never again.\n */\nconst stacks = new Map(\n PLACEMENTS.map((placement) => {\n const el = computed(\n () => stackEls.value.find((stack) => stack.dataset.placement === placement) ?? null,\n )\n return [placement, { el, ...usePopover(el) }] as const\n }),\n)\n\nfunction syncStack(placement: ToastPlacement, event: Event) {\n stacks.get(placement)?.syncShown(event)\n}\n\n/* The running countdowns, one per notification. */\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\n/* The corners holding their countdowns, one set per reason. Two sets rather than one\n because the reasons overlap and end independently: a reader tabs to a close cross, then\n moves the pointer over the stack and away, and the corner must stay held while the cross\n still has the focus — VSnackbar's two flags, per corner. */\nconst hovered = new Set<ToastPlacement>()\nconst focused = new Set<ToastPlacement>()\n\nfunction effectivePlacement(item: ToastItem): ToastPlacement {\n return item.placement ?? props.placement\n}\n\nconst isHeld = (placement: ToastPlacement) => hovered.has(placement) || focused.has(placement)\n\nfunction stopTimer(id: number) {\n const timer = timers.get(id)\n if (timer === undefined) return\n clearTimeout(timer)\n timers.delete(id)\n}\n\nfunction startTimer(item: ToastItem) {\n const duration = item.duration ?? props.duration\n if (duration <= 0 || timers.has(item.id)) return\n timers.set(\n item.id,\n setTimeout(() => {\n timers.delete(item.id)\n dismissToast(item.id)\n }, duration),\n )\n}\n\n// @core\n/**\n * Brings the page into line with the queue: it throws away the countdowns of the\n * notifications that have gone, gives every other one a countdown exactly when its corner\n * is not held, and then shows the corners holding something while hiding the empty ones.\n *\n * TRAP — the countdown follows the corner a notification is in NOW, re-derived on every\n * pass rather than remembered per notification. The default corner is a prop, so a\n * notification can change corner while the pointer rests on the old one: a memory of\n * \"already armed\" then outlived the countdown the hold had cancelled, the release looked\n * in the old corner, and the notification never got a countdown again.\n *\n * It runs once on mount — which is what makes a notification raised before this\n * component existed appear all the same — and after that on every change to the queue.\n */\nfunction sync() {\n const alive = new Set(toasts.map((item) => item.id))\n for (const id of timers.keys()) if (!alive.has(id)) stopTimer(id)\n for (const id of announced) if (!alive.has(id)) announced.delete(id)\n for (const item of toasts) {\n if (announced.has(item.id)) continue\n announced.add(item.id)\n // A failure or a warning interrupts what is being read; anything else waits.\n announce(\n [item.title, item.message].filter(Boolean).join('. '),\n item.tone === 'danger' || item.tone === 'warning',\n )\n }\n\n for (const placement of PLACEMENTS) {\n const stackEl = stacks.get(placement)?.el.value\n // TRAP — a hold is dropped here as soon as it can no longer be true, rather than\n // trusted to its closing event. A close cross leaves the page while it has the focus,\n // and an engine that sends no `focusout` for a removed element would keep the corner\n // held for good; an emptied stack is hidden under the pointer, with no `pointerleave`.\n if (!stackEl?.contains(document.activeElement)) focused.delete(placement)\n if ((groups.value.get(placement)?.length ?? 0) === 0) hovered.delete(placement)\n }\n\n for (const item of toasts) {\n if (isHeld(effectivePlacement(item))) stopTimer(item.id)\n else startTimer(item)\n }\n\n // Showing and hiding are safe to call on a container already in that state, the\n // guards living in the popover plumbing — so there is no need to remember which\n // corners are currently open.\n for (const placement of PLACEMENTS) {\n const stack = stacks.get(placement)\n if (!stack?.el.value) continue\n if ((groups.value.get(placement)?.length ?? 0) > 0) stack.show()\n else stack.hide()\n }\n}\n\n/*\n * Watching the grouped queue covers a change to the notifications AND a change to the\n * default corner. The `post` timing is load-bearing: the notification must already be\n * in the page before its container is told to show itself.\n */\nwatch(groups, sync, { flush: 'post' })\n// @ssr\n// A watcher does not run during the server render, so a notification raised\n// before this component mounted would never be picked up. Running the same\n// synchronization on mount is what brings it in.\nonMounted(sync)\n\n// @a11y\n// WCAG 2.2.1: something that disappears on a clock has to be holdable, or a\n// slow reader simply never finishes it.\n/*\n * Resting the pointer on a corner suspends its countdowns, and so does moving the keyboard\n * into it: each notification carries a close cross, a real button a reader tabs to, which\n * would otherwise vanish from under the focus ring. The same verbs and the same two\n * reasons as VSnackbar.\n *\n * Leaving restarts them from the FULL duration rather than from what was left: simpler,\n * and more generous to the reader who has just interrupted themselves.\n */\nfunction hold(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.add(placement)\n for (const item of groups.value.get(placement) ?? []) stopTimer(item.id)\n}\n\nfunction release(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.delete(placement)\n if (isHeld(placement)) return\n for (const item of groups.value.get(placement) ?? []) startTimer(item)\n}\n\n// @a11y\n// Every notification is said through two live regions rendered once, outside the stacks\n// (composables/useLiveAnnouncer): a card is created WITH its message, and a live region\n// inserted along with its text is not reliably announced.\nconst { polite, assertive, announce } = useLiveAnnouncer()\nconst announced = new Set<number>()\n\n// @a11y\n/*\n * A notification closed from its own cross takes the focused button with it, which would\n * drop the focus on `<body>`: it goes to the next cross of the same corner, or back to\n * where it was before the reader entered the corner. `cameFrom` is that element, taken\n * from the `focusin` that entered it.\n */\nconst cameFrom = new Map<ToastPlacement, HTMLElement>()\n\nfunction onStackFocusIn(placement: ToastPlacement, event: FocusEvent) {\n const from = event.relatedTarget as HTMLElement | null\n const stackEl = stacks.get(placement)?.el.value\n if (from && !stackEl?.contains(from)) cameFrom.set(placement, from)\n hold(placement, 'focus')\n}\n\nfunction closeFromCross(id: number) {\n const item = toasts.find((entry) => entry.id === id)\n const placement = item ? effectivePlacement(item) : undefined\n const stackEl = placement ? stacks.get(placement)?.el.value : null\n const hadFocus = !!stackEl?.contains(document.activeElement)\n dismissToast(id)\n if (!hadFocus || !placement) return\n void nextTick(() => {\n const next = stackEl?.querySelector<HTMLElement>('.v-toast-close')\n const back = cameFrom.get(placement)\n if (next) next.focus()\n else if (back?.isConnected) back.focus()\n })\n}\n\n/*\n * Two corners open at once are two landmarks, and two landmarks under one name are two\n * areas a screen reader cannot tell apart (axe `landmark-unique`). The name is then\n * numbered, in the order the corners are listed; alone on the page a corner keeps it bare.\n */\nconst openPlacements = computed(() =>\n PLACEMENTS.filter((placement) => (groups.value.get(placement)?.length ?? 0) > 0),\n)\nfunction stackLabel(placement: ToastPlacement) {\n const open = openPlacements.value\n if (open.length < 2) return ariaLabel.value\n return `${ariaLabel.value} (${open.indexOf(placement) + 1})`\n}\n\n/* The queue lives outside this component and survives it being unmounted and mounted\n again; only the countdowns are cleared here, and they are started afresh next time. */\nonBeforeUnmount(() => {\n for (const timer of timers.values()) clearTimeout(timer)\n timers.clear()\n})\n</script>\n\n<template>\n <div\n v-for=\"p in PLACEMENTS\"\n :key=\"p\"\n ref=\"stackEls\"\n class=\"v-overlay v-toast-stack\"\n popover=\"manual\"\n :data-placement=\"p\"\n role=\"region\"\n :aria-label=\"stackLabel(p)\"\n @pointerenter=\"hold(p, 'pointer')\"\n @pointerleave=\"release(p, 'pointer')\"\n @focusin=\"onStackFocusIn(p, $event)\"\n @focusout=\"release(p, 'focus')\"\n @beforetoggle=\"syncStack(p, $event)\"\n @toggle=\"syncStack(p, $event)\"\n >\n <VToast\n v-for=\"item in groups.get(p) ?? []\"\n :key=\"item.id\"\n :item=\"item\"\n :close-label=\"resolvedCloseLabel\"\n @close=\"closeFromCross\"\n />\n </div>\n <span class=\"v-visually-hidden\" role=\"status\">{{ polite }}</span>\n <span class=\"v-visually-hidden\" role=\"alert\">{{ assertive }}</span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * The container. Its rules are an ALIGNED COPY of VSnackbar's host: the two containers are the\n * same object, a popover at a physical corner that fades when it empties, and the copy is\n * kept line for line, the one difference being the top edge, which a confirmation never takes.\n * They are not factored into styles/banner.css because that sheet is paid for by every\n * consumer, and these rules cost more than the size gate allows; change one, change the other.\n *\n * The fixed positioning and the guard hiding a closed container come from the shared\n * `.v-overlay` class, set on this same element. What is undone here is the browser's own\n * popover decoration: its border, its padding, its opaque background.\n */\n .v-toast-stack {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * PHYSICAL coordinates rather than logical ones: a message appears at a place on the\n * screen, and that place does not flip with the reading direction — the operating\n * system's own notifications behave the same way.\n *\n * `--banner-enter-y` is the direction each card slides in from, read by `.v-banner` in\n * styles/banner.css: the container is the only thing that knows which edge of the screen\n * it sits on. That sheet reads it with a `, 0` fallback, so dropping the declaration costs\n * the slide and nothing else, and nothing reports it.\n */\n .v-toast-stack[data-placement^='top-'] {\n top: var(--vectis-space-4);\n --banner-enter-y: calc(-1 * var(--vectis-space-4));\n }\n\n .v-toast-stack[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-toast-stack {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-toast-stack:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-toast-stack:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-toast-stack {\n transition: none;\n }\n }\n\n /* The stack's own layout, which the snackbar's single bar has no use for. The newest\n notification should sit nearest the screen edge; the queue only ever grows at the end,\n so a stack along the top is simply drawn in reverse. */\n .v-toast-stack {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n }\n\n .v-toast-stack[data-placement^='top-'] {\n flex-direction: column-reverse;\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VToaster.js","names":[],"sources":["../../../src/components/VToast/VToaster.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * Manual popovers host notification stacks. JavaScript maintains queues, announcements and\n * per-item timers paused during hover or keyboard focus.\n */\n\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport { usePopover } from '../../composables/usePopover'\nimport VToast from './VToast.vue'\nimport { dismissToast, toasts, type ToastItem, type ToastPlacement } from './state'\n\nimport { useAriaLabel } from '../../composables/useAriaLabel'\nimport { useLiveAnnouncer } from '../../composables/useLiveAnnouncer'\nimport { useMessages } from '../../i18n/state'\n\ninterface ToasterProps {\n /** Which corner notifications appear in, unless one of them asks for another. */\n placement?: ToastPlacement\n /**\n * How long a notification stays, in milliseconds, unless it asks for something else.\n * A notification given 0 stays until it is dismissed.\n */\n duration?: number\n /** What the close cross does, in words. It falls back to the design system dictionary. */\n closeLabel?: string\n /**\n * What screen readers announce for the notification areas themselves, which are\n * landmarks of the page. It falls back to the design system dictionary.\n */\n label?: string\n}\n\nconst props = withDefaults(defineProps<ToasterProps>(), {\n placement: 'bottom-right',\n duration: 5000,\n closeLabel: undefined,\n label: undefined,\n})\n\nconst m = useMessages()\nconst ariaLabel = useAriaLabel(() => props.label ?? m.value.toaster.label)\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? m.value.common.close)\n\nconst PLACEMENTS: ToastPlacement[] = [\n 'top-left',\n 'top-center',\n 'top-right',\n 'bottom-left',\n 'bottom-center',\n 'bottom-right',\n]\n\n/**\n * The queue sorted into the corner each notification actually belongs to: the one it\n * asked for, or failing that the one set on this component.\n */\nconst groups = computed(() => {\n const map = new Map<ToastPlacement, ToastItem[]>()\n for (const item of toasts) {\n const placement = effectivePlacement(item)\n const list = map.get(placement)\n if (list) list.push(item)\n else map.set(placement, [item])\n }\n return map\n})\n\n/*\n * The six containers, collected by the `v-for` itself. A plain template ref rather than a\n * function one: a function written inline in the template is a new function on every render,\n * which Vue answers by calling the old one with null and the new one with the element; twelve\n * calls each time a notification comes or goes.\n */\nconst stackEls = ref<HTMLElement[]>([])\n\n/*\n * The element is looked up by its placement rather than by its index: Vue does not promise that\n * a `v-for` ref array follows the order of the list. The containers being permanent, this runs\n * once and never again.\n */\nconst stacks = new Map(\n PLACEMENTS.map((placement) => {\n const el = computed(\n () => stackEls.value.find((stack) => stack.dataset.placement === placement) ?? null,\n )\n return [placement, { el, ...usePopover(el) }] as const\n }),\n)\n\nfunction syncStack(placement: ToastPlacement, event: Event) {\n stacks.get(placement)?.syncShown(event)\n}\n\nconst timers = new Map<number, ReturnType<typeof setTimeout>>()\n/*\n * Two sets rather than one because the reasons overlap and end independently: a reader tabs to\n * a close cross, then moves the pointer over the stack and away, and the corner must stay held\n * while the cross still has the focus; VSnackbar's two flags, per corner.\n */\nconst hovered = new Set<ToastPlacement>()\nconst focused = new Set<ToastPlacement>()\n\nfunction effectivePlacement(item: ToastItem): ToastPlacement {\n return item.placement ?? props.placement\n}\n\nconst isHeld = (placement: ToastPlacement) => hovered.has(placement) || focused.has(placement)\n\nfunction stopTimer(id: number) {\n const timer = timers.get(id)\n if (timer === undefined) return\n clearTimeout(timer)\n timers.delete(id)\n}\n\nfunction startTimer(item: ToastItem) {\n const duration = item.duration ?? props.duration\n if (duration <= 0 || timers.has(item.id)) return\n timers.set(\n item.id,\n setTimeout(() => {\n timers.delete(item.id)\n dismissToast(item.id)\n }, duration),\n )\n}\n\n// @core\n/**\n * Recompute each toast's corner before arming timers: moving a held toast must not leave it\n * without a countdown after release.\n */\nfunction sync() {\n const alive = new Set(toasts.map((item) => item.id))\n for (const id of timers.keys()) if (!alive.has(id)) stopTimer(id)\n for (const id of announced) if (!alive.has(id)) announced.delete(id)\n for (const item of toasts) {\n if (announced.has(item.id)) continue\n announced.add(item.id)\n announce(\n [item.title, item.message].filter(Boolean).join('. '),\n item.tone === 'danger' || item.tone === 'warning',\n )\n }\n\n for (const placement of PLACEMENTS) {\n const stackEl = stacks.get(placement)?.el.value\n // A close cross leaves the page while it has the focus, and an engine that sends no\n // `focusout` for a removed element would keep the corner held for good; an emptied stack is\n // hidden under the pointer, with no `pointerleave`.\n if (!stackEl?.contains(document.activeElement)) focused.delete(placement)\n if ((groups.value.get(placement)?.length ?? 0) === 0) hovered.delete(placement)\n }\n\n for (const item of toasts) {\n if (isHeld(effectivePlacement(item))) stopTimer(item.id)\n else startTimer(item)\n }\n\n // Showing and hiding are safe to call on a container already in that state, the guards living\n // in the popover plumbing; so there is no need to remember which corners are currently open.\n for (const placement of PLACEMENTS) {\n const stack = stacks.get(placement)\n if (!stack?.el.value) continue\n if ((groups.value.get(placement)?.length ?? 0) > 0) stack.show()\n else stack.hide()\n }\n}\n\n/*\n * The `post` timing is load-bearing: the notification must already be in the page before its\n * container is told to show itself.\n */\nwatch(groups, sync, { flush: 'post' })\n// @ssr\n// A watcher does not run during the server render, so a notification raised\n// before this component mounted would never be picked up. Running the same\n// synchronization on mount is what brings it in.\nonMounted(sync)\n\n// @a11y\n// WCAG 2.2.1: something that disappears on a clock has to be holdable, or a\n// slow reader simply never finishes it.\n/*\n * Resting the pointer on a corner suspends its countdowns, and so does moving the keyboard into\n * it: each notification carries a close cross, a real button a reader tabs to, which would\n * otherwise vanish from under the focus ring. The same verbs and the same two reasons as\n * VSnackbar.\n */\nfunction hold(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.add(placement)\n for (const item of groups.value.get(placement) ?? []) stopTimer(item.id)\n}\n\nfunction release(placement: ToastPlacement, which: 'pointer' | 'focus') {\n const reason = which === 'pointer' ? hovered : focused\n reason.delete(placement)\n if (isHeld(placement)) return\n for (const item of groups.value.get(placement) ?? []) startTimer(item)\n}\n\n// @a11y\n// Every notification is said through two live regions rendered once, outside the stacks\n// (composables/useLiveAnnouncer): a card is created WITH its message, and a live region\n// inserted along with its text is not reliably announced.\nconst { polite, assertive, announce } = useLiveAnnouncer()\nconst announced = new Set<number>()\n\n// @a11y\n/*\n * A notification closed from its own cross takes the focused button with it, which would\n * drop the focus on `<body>`: it goes to the next cross of the same corner, or back to\n * where it was before the reader entered the corner. `cameFrom` is that element, taken\n * from the `focusin` that entered it.\n */\nconst cameFrom = new Map<ToastPlacement, HTMLElement>()\n\nfunction onStackFocusIn(placement: ToastPlacement, event: FocusEvent) {\n const from = event.relatedTarget as HTMLElement | null\n const stackEl = stacks.get(placement)?.el.value\n if (from && !stackEl?.contains(from)) cameFrom.set(placement, from)\n hold(placement, 'focus')\n}\n\nfunction closeFromCross(id: number) {\n const item = toasts.find((entry) => entry.id === id)\n const placement = item ? effectivePlacement(item) : undefined\n const stackEl = placement ? stacks.get(placement)?.el.value : null\n const hadFocus = !!stackEl?.contains(document.activeElement)\n dismissToast(id)\n if (!hadFocus || !placement) return\n void nextTick(() => {\n const next = stackEl?.querySelector<HTMLElement>('.v-toast-close')\n const back = cameFrom.get(placement)\n if (next) next.focus()\n else if (back?.isConnected) back.focus()\n })\n}\n\n/*\n * Two corners open at once are two landmarks, and two landmarks under one name are two\n * areas a screen reader cannot tell apart (axe `landmark-unique`). The name is then\n * numbered, in the order the corners are listed; alone on the page a corner keeps it bare.\n */\nconst openPlacements = computed(() =>\n PLACEMENTS.filter((placement) => (groups.value.get(placement)?.length ?? 0) > 0),\n)\nfunction stackLabel(placement: ToastPlacement) {\n const open = openPlacements.value\n if (open.length < 2) return ariaLabel.value\n return `${ariaLabel.value} (${open.indexOf(placement) + 1})`\n}\n\n/* The queue lives outside this component and survives it being unmounted and mounted\n again; only the countdowns are cleared here, and they are started afresh next time. */\nonBeforeUnmount(() => {\n for (const timer of timers.values()) clearTimeout(timer)\n timers.clear()\n})\n</script>\n\n<template>\n <div\n v-for=\"p in PLACEMENTS\"\n :key=\"p\"\n ref=\"stackEls\"\n class=\"v-overlay v-toast-stack\"\n popover=\"manual\"\n :data-placement=\"p\"\n role=\"region\"\n :aria-label=\"stackLabel(p)\"\n @pointerenter=\"hold(p, 'pointer')\"\n @pointerleave=\"release(p, 'pointer')\"\n @focusin=\"onStackFocusIn(p, $event)\"\n @focusout=\"release(p, 'focus')\"\n @beforetoggle=\"syncStack(p, $event)\"\n @toggle=\"syncStack(p, $event)\"\n >\n <VToast\n v-for=\"item in groups.get(p) ?? []\"\n :key=\"item.id\"\n :item=\"item\"\n :close-label=\"resolvedCloseLabel\"\n @close=\"closeFromCross\"\n />\n </div>\n <span class=\"v-visually-hidden\" role=\"status\">{{ polite }}</span>\n <span class=\"v-visually-hidden\" role=\"alert\">{{ assertive }}</span>\n</template>\n\n<style>\n@layer vectis.components {\n /*\n * Keep host rules aligned with VSnackbar; toaster also supports top placements. Sharing this\n * layout in core CSS would increase every consumer's size cost.\n */\n .v-toast-stack {\n margin: 0;\n border: none;\n padding: 0;\n background: transparent;\n overflow: visible;\n width: fit-content;\n }\n\n /*\n * Keep screen placement physical in RTL. The host supplies each card's entry direction from\n * its screen edge.\n */\n .v-toast-stack[data-placement^='top-'] {\n top: var(--vectis-space-4);\n --banner-enter-y: calc(-1 * var(--vectis-space-4));\n }\n\n .v-toast-stack[data-placement^='bottom-'] {\n bottom: var(--vectis-space-4);\n --banner-enter-y: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-left'] {\n left: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-right'] {\n right: var(--vectis-space-4);\n }\n\n .v-toast-stack[data-placement$='-center'] {\n left: 0;\n right: 0;\n margin-inline: auto;\n }\n\n /* The container fades in and out. Animating an element that is being added to or removed\n from the page needs the two `allow-discrete` declarations and the starting values\n below; a browser missing either simply shows and hides it at once. */\n .v-toast-stack {\n opacity: 1;\n transition:\n opacity var(--vectis-duration-base) var(--vectis-ease-default),\n overlay var(--vectis-duration-base) allow-discrete,\n display var(--vectis-duration-base) allow-discrete;\n }\n\n .v-toast-stack:not(:popover-open) {\n opacity: 0;\n }\n\n @starting-style {\n .v-toast-stack:popover-open {\n opacity: 0;\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-toast-stack {\n transition: none;\n }\n }\n\n /* The stack's own layout, which the snackbar's single bar has no use for. The newest\n notification should sit nearest the screen edge; the queue only ever grows at the end,\n so a stack along the top is simply drawn in reverse. */\n .v-toast-stack {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-3);\n }\n\n .v-toast-stack[data-placement^='top-'] {\n flex-direction: column-reverse;\n }\n}\n</style>\n"],"mappings":""}