vectis-ui 0.1.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. package/README.md +40 -6
  2. package/dist/components/VAccordion/VAccordion.css +1 -1
  3. package/dist/components/VAccordion/VAccordion.d.ts +9 -16
  4. package/dist/components/VAccordion/VAccordion.js.map +1 -1
  5. package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js +3 -3
  6. package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  7. package/dist/components/VAccordion/VAccordionItem.css +1 -1
  8. package/dist/components/VAccordion/VAccordionItem.d.ts +33 -19
  9. package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
  10. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js +50 -38
  11. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  12. package/dist/components/VAccordion/context.d.ts +1 -8
  13. package/dist/components/VAccordion/context.js.map +1 -1
  14. package/dist/components/VAvatar/VAvatar.css +1 -1
  15. package/dist/components/VAvatar/VAvatar.d.ts +44 -29
  16. package/dist/components/VAvatar/VAvatar.js.map +1 -1
  17. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +38 -35
  18. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/VAvatar/VAvatarGroup.d.ts +5 -16
  20. package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
  21. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +7 -7
  22. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  23. package/dist/components/VAvatar/context.d.ts +6 -10
  24. package/dist/components/VAvatar/context.js.map +1 -1
  25. package/dist/components/VBadge/VBadge.css +1 -1
  26. package/dist/components/VBadge/VBadge.d.ts +31 -10
  27. package/dist/components/VBadge/VBadge.js.map +1 -1
  28. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +39 -24
  29. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  30. package/dist/components/VBreadcrumb/VBreadcrumb.css +1 -1
  31. package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +4 -17
  32. package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
  33. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +23 -25
  34. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  35. package/dist/components/VButton/VButton.css +1 -1
  36. package/dist/components/VButton/VButton.d.ts +30 -10
  37. package/dist/components/VButton/VButton.js.map +1 -1
  38. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +39 -30
  39. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  40. package/dist/components/VButton/VButtonGroup.css +1 -1
  41. package/dist/components/VButton/VButtonGroup.d.ts +76 -13
  42. package/dist/components/VButton/VButtonGroup.js.map +1 -1
  43. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js +74 -8
  44. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  45. package/dist/components/VButton/context.d.ts +30 -0
  46. package/dist/components/VButton/context.js +6 -0
  47. package/dist/components/VButton/context.js.map +1 -0
  48. package/dist/components/VCalendar/VCalendar.css +1 -1
  49. package/dist/components/VCalendar/VCalendar.d.ts +50 -36
  50. package/dist/components/VCalendar/VCalendar.js.map +1 -1
  51. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +99 -85
  52. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  53. package/dist/components/VCalendar/VCalendarEvent.css +1 -1
  54. package/dist/components/VCalendar/VCalendarEvent.d.ts +5 -11
  55. package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
  56. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js +7 -1
  57. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  58. package/dist/components/VCalendar/VCalendarMonth.css +1 -1
  59. package/dist/components/VCalendar/VCalendarMonth.d.ts +10 -13
  60. package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
  61. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +183 -244
  62. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
  63. package/dist/components/VCalendar/VCalendarTimeGrid.css +1 -1
  64. package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +15 -20
  65. package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
  66. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +385 -448
  67. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  68. package/dist/components/VCalendar/VCalendarYear.css +1 -1
  69. package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
  70. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js +53 -49
  71. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  72. package/dist/components/VCalendar/gesture.d.ts +158 -0
  73. package/dist/components/VCalendar/gesture.js +170 -0
  74. package/dist/components/VCalendar/gesture.js.map +1 -0
  75. package/dist/components/VCalendar/keyboard.d.ts +9 -3
  76. package/dist/components/VCalendar/keyboard.js +2 -6
  77. package/dist/components/VCalendar/keyboard.js.map +1 -1
  78. package/dist/components/VCalendar/layout.d.ts +67 -54
  79. package/dist/components/VCalendar/layout.js +77 -69
  80. package/dist/components/VCalendar/layout.js.map +1 -1
  81. package/dist/components/VCalendar/timeGrid.d.ts +55 -0
  82. package/dist/components/VCalendar/timeGrid.js +71 -0
  83. package/dist/components/VCalendar/timeGrid.js.map +1 -0
  84. package/dist/components/VCalendar/types.d.ts +41 -16
  85. package/dist/components/VCarousel/VCarousel.css +1 -1
  86. package/dist/components/VCarousel/VCarousel.d.ts +21 -41
  87. package/dist/components/VCarousel/VCarousel.js.map +1 -1
  88. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +157 -129
  89. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  90. package/dist/components/VCarousel/VCarouselItem.d.ts +0 -17
  91. package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
  92. package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  93. package/dist/components/VCarousel/context.d.ts +0 -12
  94. package/dist/components/VCarousel/context.js.map +1 -1
  95. package/dist/components/VCheckbox/VCheckbox.css +1 -1
  96. package/dist/components/VCheckbox/VCheckbox.d.ts +31 -16
  97. package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
  98. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +62 -29
  99. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  100. package/dist/components/VChip/VChip.css +1 -1
  101. package/dist/components/VChip/VChip.d.ts +36 -26
  102. package/dist/components/VChip/VChip.js.map +1 -1
  103. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +57 -48
  104. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  105. package/dist/components/VCombobox/VCombobox.css +1 -1
  106. package/dist/components/VCombobox/VCombobox.d.ts +127 -31
  107. package/dist/components/VCombobox/VCombobox.js.map +1 -1
  108. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +336 -247
  109. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  110. package/dist/components/VCombobox/VComboboxGroup.css +1 -1
  111. package/dist/components/VCombobox/VComboboxGroup.d.ts +0 -9
  112. package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
  113. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js +1 -1
  114. package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  115. package/dist/components/VCombobox/VComboboxOption.css +1 -1
  116. package/dist/components/VCombobox/VComboboxOption.d.ts +0 -15
  117. package/dist/components/VCombobox/VComboboxOption.js.map +1 -1
  118. package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js +4 -4
  119. package/dist/components/VCombobox/VComboboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
  120. package/dist/components/VCombobox/VComboboxSeparator.js.map +1 -1
  121. package/dist/components/VCombobox/VComboboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  122. package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
  123. package/dist/components/VDataTable/VDataTable.css +1 -1
  124. package/dist/components/VDataTable/VDataTable.d.ts +50 -28
  125. package/dist/components/VDataTable/VDataTable.js.map +1 -1
  126. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +183 -194
  127. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  128. package/dist/components/VDateInput/VDateInput.css +1 -1
  129. package/dist/components/VDateInput/VDateInput.d.ts +90 -42
  130. package/dist/components/VDateInput/VDateInput.js.map +1 -1
  131. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +159 -191
  132. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  133. package/dist/components/VDatePicker/VDatePicker.css +1 -1
  134. package/dist/components/VDatePicker/VDatePicker.d.ts +61 -30
  135. package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
  136. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +243 -221
  137. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  138. package/dist/components/VDialog/VDialog.css +1 -1
  139. package/dist/components/VDialog/VDialog.d.ts +45 -33
  140. package/dist/components/VDialog/VDialog.js.map +1 -1
  141. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +65 -64
  142. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  143. package/dist/components/VDialog/VDialogAlert.d.ts +24 -20
  144. package/dist/components/VDialog/VDialogAlert.js.map +1 -1
  145. package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js +36 -25
  146. package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  147. package/dist/components/VFileInput/VFileInput.css +1 -1
  148. package/dist/components/VFileInput/VFileInput.d.ts +78 -26
  149. package/dist/components/VFileInput/VFileInput.js.map +1 -1
  150. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +144 -132
  151. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  152. package/dist/components/VFilePicker/VFilePicker.css +1 -1
  153. package/dist/components/VFilePicker/VFilePicker.d.ts +47 -30
  154. package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
  155. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +166 -163
  156. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  157. package/dist/components/VFilePicker/fileKind.d.ts +2 -27
  158. package/dist/components/VFilePicker/fileKind.js +9 -8
  159. package/dist/components/VFilePicker/fileKind.js.map +1 -1
  160. package/dist/components/VHotkeys/VHotkeys.css +1 -1
  161. package/dist/components/VHotkeys/VHotkeys.d.ts +8 -30
  162. package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
  163. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +4 -4
  164. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
  165. package/dist/components/VHotkeys/platform.js.map +1 -1
  166. package/dist/components/VIcon/VIcon.css +1 -1
  167. package/dist/components/VIcon/VIcon.d.ts +15 -6
  168. package/dist/components/VIcon/VIcon.js.map +1 -1
  169. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js +45 -37
  170. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  171. package/dist/components/VIcon/iconProps.js.map +1 -1
  172. package/dist/components/VIcon/icons/index.d.ts +1 -1
  173. package/dist/components/VIcon/icons/index.js.map +1 -1
  174. package/dist/components/VIcon/icons/names.d.ts +1 -1
  175. package/dist/components/VIcon/icons/names.js.map +1 -1
  176. package/dist/components/VIcon/resolver.d.ts +3 -3
  177. package/dist/components/VIcon/resolver.js.map +1 -1
  178. package/dist/components/VIcon/types.d.ts +12 -12
  179. package/dist/components/VIconButton/VIconButton.css +1 -1
  180. package/dist/components/VIconButton/VIconButton.d.ts +28 -11
  181. package/dist/components/VIconButton/VIconButton.js.map +1 -1
  182. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js +26 -20
  183. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  184. package/dist/components/VInput/VInput.css +1 -1
  185. package/dist/components/VInput/VInput.d.ts +26 -7
  186. package/dist/components/VInput/VInput.js.map +1 -1
  187. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +86 -75
  188. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  189. package/dist/components/VInput/VInputGroup.css +2 -0
  190. package/dist/components/VInput/VInputGroup.d.ts +47 -0
  191. package/dist/components/VInput/VInputGroup.js +9 -0
  192. package/dist/components/VInput/VInputGroup.js.map +1 -0
  193. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +80 -0
  194. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -0
  195. package/dist/components/VInput/context.d.ts +11 -0
  196. package/dist/components/VInput/context.js +6 -0
  197. package/dist/components/VInput/context.js.map +1 -0
  198. package/dist/components/VInputOTP/VInputOTP.css +1 -1
  199. package/dist/components/VInputOTP/VInputOTP.d.ts +37 -22
  200. package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
  201. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +98 -61
  202. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
  203. package/dist/components/VMenu/VMenu.d.ts +23 -30
  204. package/dist/components/VMenu/VMenu.js.map +1 -1
  205. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js +31 -27
  206. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  207. package/dist/components/VMenu/VMenuGroup.css +1 -1
  208. package/dist/components/VMenu/VMenuGroup.d.ts +12 -8
  209. package/dist/components/VMenu/VMenuGroup.js.map +1 -1
  210. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js +11 -11
  211. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  212. package/dist/components/VMenu/VMenuItem.css +1 -1
  213. package/dist/components/VMenu/VMenuItem.d.ts +11 -19
  214. package/dist/components/VMenu/VMenuItem.js.map +1 -1
  215. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +65 -57
  216. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  217. package/dist/components/VMenu/VMenuPanel.d.ts +14 -30
  218. package/dist/components/VMenu/VMenuPanel.js.map +1 -1
  219. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +43 -44
  220. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  221. package/dist/components/VMenu/context.d.ts +2 -6
  222. package/dist/components/VMenu/context.js.map +1 -1
  223. package/dist/components/VPagination/VPagination.css +1 -1
  224. package/dist/components/VPagination/VPagination.d.ts +60 -22
  225. package/dist/components/VPagination/VPagination.js.map +1 -1
  226. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +113 -161
  227. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  228. package/dist/components/VPopover/VPopover.css +1 -1
  229. package/dist/components/VPopover/VPopover.d.ts +26 -7
  230. package/dist/components/VPopover/VPopover.js.map +1 -1
  231. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js +43 -37
  232. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  233. package/dist/components/VProgressCircular/VProgressCircular.css +1 -1
  234. package/dist/components/VProgressCircular/VProgressCircular.d.ts +15 -4
  235. package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
  236. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js +32 -30
  237. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
  238. package/dist/components/VProgressLinear/VProgressLinear.css +1 -1
  239. package/dist/components/VProgressLinear/VProgressLinear.d.ts +23 -8
  240. package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
  241. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js +34 -32
  242. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  243. package/dist/components/VRadio/VRadio.css +1 -1
  244. package/dist/components/VRadio/VRadio.d.ts +36 -18
  245. package/dist/components/VRadio/VRadio.js.map +1 -1
  246. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +55 -26
  247. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  248. package/dist/components/VSeparator/VSeparator.d.ts +4 -2
  249. package/dist/components/VSeparator/VSeparator.js.map +1 -1
  250. package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  251. package/dist/components/VSideNavigation/VSideNavigation.css +1 -1
  252. package/dist/components/VSideNavigation/VSideNavigation.d.ts +5 -19
  253. package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
  254. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +5 -5
  255. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
  256. package/dist/components/VSideNavigation/VSideNavigationGroup.css +1 -1
  257. package/dist/components/VSideNavigation/VSideNavigationGroup.d.ts +9 -17
  258. package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
  259. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js +2 -2
  260. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  261. package/dist/components/VSideNavigation/VSideNavigationItem.css +1 -1
  262. package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +22 -30
  263. package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
  264. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +77 -87
  265. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  266. package/dist/components/VSideNavigation/context.d.ts +0 -14
  267. package/dist/components/VSideNavigation/context.js.map +1 -1
  268. package/dist/components/VSkeletonLoader/VSkeletonLoader.css +1 -1
  269. package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +9 -9
  270. package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
  271. package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  272. package/dist/components/VSlider/VSlider.css +1 -1
  273. package/dist/components/VSlider/VSlider.d.ts +52 -27
  274. package/dist/components/VSlider/VSlider.js.map +1 -1
  275. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +202 -118
  276. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  277. package/dist/components/VSnackbar/VSnackbar.css +1 -1
  278. package/dist/components/VSnackbar/VSnackbar.d.ts +0 -23
  279. package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
  280. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +38 -38
  281. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  282. package/dist/components/VSnackbar/state.d.ts +0 -27
  283. package/dist/components/VSnackbar/state.js.map +1 -1
  284. package/dist/components/VSpinner/VSpinner.css +1 -1
  285. package/dist/components/VSpinner/VSpinner.d.ts +6 -6
  286. package/dist/components/VSpinner/VSpinner.js.map +1 -1
  287. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js +10 -9
  288. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
  289. package/dist/components/VSwitch/VSwitch.css +1 -1
  290. package/dist/components/VSwitch/VSwitch.d.ts +35 -13
  291. package/dist/components/VSwitch/VSwitch.js.map +1 -1
  292. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +62 -21
  293. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  294. package/dist/components/VTabs/VTab.css +1 -1
  295. package/dist/components/VTabs/VTab.d.ts +12 -12
  296. package/dist/components/VTabs/VTab.js.map +1 -1
  297. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +36 -24
  298. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
  299. package/dist/components/VTabs/VTabPanel.d.ts +2 -10
  300. package/dist/components/VTabs/VTabPanel.js.map +1 -1
  301. package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js +4 -4
  302. package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  303. package/dist/components/VTabs/VTabs.css +1 -1
  304. package/dist/components/VTabs/VTabs.d.ts +27 -18
  305. package/dist/components/VTabs/VTabs.js.map +1 -1
  306. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +92 -81
  307. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  308. package/dist/components/VTabs/context.d.ts +9 -19
  309. package/dist/components/VTabs/context.js +1 -1
  310. package/dist/components/VTabs/context.js.map +1 -1
  311. package/dist/components/VTextarea/VTextarea.css +1 -1
  312. package/dist/components/VTextarea/VTextarea.d.ts +34 -6
  313. package/dist/components/VTextarea/VTextarea.js.map +1 -1
  314. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +91 -74
  315. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  316. package/dist/components/VTimeInput/VTimeInput.css +1 -1
  317. package/dist/components/VTimeInput/VTimeInput.d.ts +123 -33
  318. package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
  319. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +294 -291
  320. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  321. package/dist/components/VTimeInput/search.d.ts +24 -0
  322. package/dist/components/VTimeInput/search.js +22 -0
  323. package/dist/components/VTimeInput/search.js.map +1 -0
  324. package/dist/components/VTimePicker/VTimePicker.css +1 -1
  325. package/dist/components/VTimePicker/VTimePicker.d.ts +51 -30
  326. package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
  327. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +216 -151
  328. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  329. package/dist/components/VTimePicker/limits.d.ts +88 -0
  330. package/dist/components/VTimePicker/limits.js +70 -0
  331. package/dist/components/VTimePicker/limits.js.map +1 -0
  332. package/dist/components/VToast/VToast.css +1 -1
  333. package/dist/components/VToast/VToast.d.ts +0 -10
  334. package/dist/components/VToast/VToast.js.map +1 -1
  335. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +10 -10
  336. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  337. package/dist/components/VToast/VToaster.css +1 -1
  338. package/dist/components/VToast/VToaster.d.ts +0 -17
  339. package/dist/components/VToast/VToaster.js.map +1 -1
  340. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +29 -28
  341. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
  342. package/dist/components/VToast/state.d.ts +10 -19
  343. package/dist/components/VToast/state.js +1 -1
  344. package/dist/components/VToast/state.js.map +1 -1
  345. package/dist/components/VToggle/VToggle.css +1 -1
  346. package/dist/components/VToggle/VToggle.d.ts +56 -11
  347. package/dist/components/VToggle/VToggle.js.map +1 -1
  348. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +80 -46
  349. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
  350. package/dist/components/VToggle/VToggleItem.d.ts +16 -2
  351. package/dist/components/VToggle/VToggleItem.js +0 -2
  352. package/dist/components/VToggle/VToggleItem.js.map +1 -1
  353. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js +23 -15
  354. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  355. package/dist/components/VToggle/context.d.ts +4 -24
  356. package/dist/components/VToggle/context.js.map +1 -1
  357. package/dist/components/VTooltip/VTooltip.css +1 -1
  358. package/dist/components/VTooltip/VTooltip.d.ts +9 -33
  359. package/dist/components/VTooltip/VTooltip.js.map +1 -1
  360. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +28 -24
  361. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  362. package/dist/components/VTypography/VTypography.js.map +1 -1
  363. package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
  364. package/dist/composables/useAriaLabel.d.ts +0 -17
  365. package/dist/composables/useAriaLabel.js.map +1 -1
  366. package/dist/composables/useClearable.d.ts +42 -0
  367. package/dist/composables/useClearable.js +19 -0
  368. package/dist/composables/useClearable.js.map +1 -0
  369. package/dist/composables/useControlShape.d.ts +22 -0
  370. package/dist/composables/useControlShape.js +13 -0
  371. package/dist/composables/useControlShape.js.map +1 -0
  372. package/dist/composables/useDetailsOpen.d.ts +22 -0
  373. package/dist/composables/useDetailsOpen.js +21 -0
  374. package/dist/composables/useDetailsOpen.js.map +1 -0
  375. package/dist/composables/useFieldIds.d.ts +0 -15
  376. package/dist/composables/useFieldIds.js +8 -7
  377. package/dist/composables/useFieldIds.js.map +1 -1
  378. package/dist/composables/useFieldPanel.d.ts +21 -18
  379. package/dist/composables/useFieldPanel.js +43 -23
  380. package/dist/composables/useFieldPanel.js.map +1 -1
  381. package/dist/composables/useFileDrop.d.ts +0 -22
  382. package/dist/composables/useFileDrop.js.map +1 -1
  383. package/dist/composables/useFileField.d.ts +45 -17
  384. package/dist/composables/useFileField.js +40 -20
  385. package/dist/composables/useFileField.js.map +1 -1
  386. package/dist/composables/useFocusoutDismiss.d.ts +0 -15
  387. package/dist/composables/useFocusoutDismiss.js.map +1 -1
  388. package/dist/composables/useIconClickHandlers.d.ts +29 -14
  389. package/dist/composables/useIconClickHandlers.js +15 -5
  390. package/dist/composables/useIconClickHandlers.js.map +1 -1
  391. package/dist/composables/useInertLink.d.ts +20 -0
  392. package/dist/composables/useInertLink.js +23 -0
  393. package/dist/composables/useInertLink.js.map +1 -0
  394. package/dist/composables/useMaskedField.d.ts +33 -10
  395. package/dist/composables/useMaskedField.js +45 -1
  396. package/dist/composables/useMaskedField.js.map +1 -1
  397. package/dist/composables/usePopover.d.ts +15 -1
  398. package/dist/composables/usePopover.js +23 -8
  399. package/dist/composables/usePopover.js.map +1 -1
  400. package/dist/composables/useProgressValue.d.ts +5 -8
  401. package/dist/composables/useProgressValue.js +6 -3
  402. package/dist/composables/useProgressValue.js.map +1 -1
  403. package/dist/composables/useRootAttrs.d.ts +12 -19
  404. package/dist/composables/useRootAttrs.js.map +1 -1
  405. package/dist/composables/useTimer.d.ts +8 -10
  406. package/dist/composables/useTimer.js.map +1 -1
  407. package/dist/i18n/en.d.ts +2 -10
  408. package/dist/i18n/en.js +18 -19
  409. package/dist/i18n/en.js.map +1 -1
  410. package/dist/i18n/fr.d.ts +2 -18
  411. package/dist/i18n/fr.js +18 -19
  412. package/dist/i18n/fr.js.map +1 -1
  413. package/dist/i18n/state.d.ts +44 -47
  414. package/dist/i18n/state.js +20 -17
  415. package/dist/i18n/state.js.map +1 -1
  416. package/dist/i18n/types.d.ts +53 -44
  417. package/dist/icons.d.ts +3 -3
  418. package/dist/index.d.ts +43 -17
  419. package/dist/index.js +45 -44
  420. package/dist/styles.css +1 -1
  421. package/dist/tokens/css.js.map +1 -1
  422. package/dist/tokens/index.d.ts +5 -1
  423. package/dist/tokens/primitives.js.map +1 -1
  424. package/dist/tokens/semantic.d.ts +5 -1
  425. package/dist/tokens/semantic.js +6 -2
  426. package/dist/tokens/semantic.js.map +1 -1
  427. package/dist/tokens/themes/dark.js.map +1 -1
  428. package/dist/tokens/types.d.ts +7 -15
  429. package/dist/tokens/types.js.map +1 -1
  430. package/dist/tokens.json +25 -5
  431. package/dist/types.d.ts +24 -0
  432. package/dist/utils/array.d.ts +3 -6
  433. package/dist/utils/array.js.map +1 -1
  434. package/dist/utils/arrowNav.d.ts +17 -17
  435. package/dist/utils/arrowNav.js +5 -3
  436. package/dist/utils/arrowNav.js.map +1 -1
  437. package/dist/utils/chip.d.ts +11 -15
  438. package/dist/utils/chip.js.map +1 -1
  439. package/dist/utils/css.d.ts +22 -11
  440. package/dist/utils/css.js +5 -2
  441. package/dist/utils/css.js.map +1 -1
  442. package/dist/utils/date.d.ts +42 -19
  443. package/dist/utils/date.js +97 -98
  444. package/dist/utils/date.js.map +1 -1
  445. package/dist/utils/direction.d.ts +6 -12
  446. package/dist/utils/direction.js.map +1 -1
  447. package/dist/utils/env.d.ts +5 -6
  448. package/dist/utils/file.d.ts +46 -58
  449. package/dist/utils/file.js +43 -36
  450. package/dist/utils/file.js.map +1 -1
  451. package/dist/utils/focus.d.ts +8 -17
  452. package/dist/utils/focus.js.map +1 -1
  453. package/dist/utils/hostWarns.d.ts +12 -0
  454. package/dist/utils/hostWarns.js +6 -0
  455. package/dist/utils/hostWarns.js.map +1 -0
  456. package/dist/utils/ids.d.ts +10 -0
  457. package/dist/utils/ids.js +8 -0
  458. package/dist/utils/ids.js.map +1 -0
  459. package/dist/utils/matcher.d.ts +5 -9
  460. package/dist/utils/matcher.js.map +1 -1
  461. package/dist/utils/memo.d.ts +12 -0
  462. package/dist/utils/memo.js +9 -0
  463. package/dist/utils/memo.js.map +1 -0
  464. package/dist/utils/number.d.ts +3 -4
  465. package/dist/utils/number.js.map +1 -1
  466. package/dist/utils/text.d.ts +20 -9
  467. package/dist/utils/text.js +17 -3
  468. package/dist/utils/text.js.map +1 -1
  469. package/dist/utils/time.d.ts +53 -61
  470. package/dist/utils/time.js +49 -45
  471. package/dist/utils/time.js.map +1 -1
  472. package/dist/utils/vnode.d.ts +8 -15
  473. package/dist/utils/vnode.js.map +1 -1
  474. package/package.json +6 -6
  475. package/dist/components/VToggle/VToggleItem.css +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"VTimeInput.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/VTimeInput/VTimeInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, useId, watchEffect } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport { expand_more as expandMoreIcon } from '../VIcon/icons/expand_more'\nimport { schedule as scheduleIcon } from '../VIcon/icons/schedule'\nimport type { IconSource } from '../VIcon/types'\nimport VInput from '../VInput/VInput.vue'\nimport VPopover from '../VPopover/VPopover.vue'\nimport VToggle from '../VToggle/VToggle.vue'\nimport type { ToggleModelValue } from '../VToggle/VToggle.vue'\nimport VToggleItem from '../VToggle/VToggleItem.vue'\nimport VTimePicker from '../VTimePicker/VTimePicker.vue'\nimport type { TimePickerFormat } from '../VTimePicker/VTimePicker.vue'\nimport {\n formatDisplay,\n formatTime,\n formatTimeMask,\n hourCycleFor,\n isValidTime,\n minutesOf,\n parseTime,\n parseTimeMask,\n snapMinute,\n timeCaret,\n timeList,\n timeToMask,\n to12h,\n to24h,\n} from '../../utils/time'\nimport type { Meridiem, TimeOption } from '../../utils/time'\nimport { arrowNavigate } from '../../utils/arrowNav'\nimport { clamp } from '../../utils/number'\nimport { isDev } from '../../utils/env'\nimport { digitsOf } from '../../utils/text'\n\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\nimport { useFieldPanel } from '../../composables/useFieldPanel'\nimport { useMaskedField } from '../../composables/useMaskedField'\nimport { useLocale, useMessages } from '../../i18n/state'\n\n// @a11y @core\n/**\n * A field for choosing a time, with an optional panel below it. The shell is the same one\n * VDateInput uses: the panel is opened from code, which is what lets the focus be moved\n * into it, and closed when the focus leaves the component or Escape is pressed.\n *\n * The field comes in three forms. It can be TYPED into, hours and minutes behind a mask,\n * optionally with a picker beside it. It can be read-only, the picker then being the only way\n * in. Or it can offer a LIST of times at a fixed interval, which suits booking a slot far\n * better than pointing at a clock face.\n *\n * The two panels commit differently, and deliberately so. The PICKER works on a draft that\n * only OK writes to the value — Cancel, Escape and the focus leaving all discard it —\n * because dragging a hand across a clock passes over dozens of times nobody meant. The\n * LIST writes immediately: choosing a row there is one deliberate gesture.\n *\n * Whatever is displayed, the value itself is always a 24-hour time.\n */\nexport type TimeInputMode = 'readonly' | 'input' | 'list'\n\nconst MODES: TimeInputMode[] = ['readonly', 'input', 'list']\n\ntype Placement = 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end'\n\ninterface TimeInputProps {\n /**\n * Whether times are shown on 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 * Which form the field takes: one that can be TYPED into, a read-only one where the\n * picker is the only way in — so the picker is forced on there — or a LIST of times at a\n * fixed interval, where a picker would make no sense.\n */\n mode?: TimeInputMode\n /**\n * Offers the picker beside a field one can type into: an icon at the end of the field,\n * and a panel it opens.\n *\n * It is left undefined by default rather than set to off, which is what distinguishes\n * \"not given\" from an explicit refusal — and therefore what allows warning only the\n * consumer who really asked to remove the picker from a read-only field, where it is the\n * only way in.\n */\n showPicker?: boolean\n /**\n * The interval between two times that can be chosen. It applies to the picker, to the\n * arrow keys and to the rows of the list.\n */\n minuteStep?: number\n /**\n * A BCP 47 locale, which decides the clock and how a time is written out. It TAKES\n * PRECEDENCE over the design system's global locale and falls back to it — which is why\n * it has no literal default: `undefined` has to stay recognizable for the global locale\n * to have its chance.\n */\n locale?: string\n // From here on: the field.\n /** The label above the field. */\n label?: string\n /** A line of help under the field. */\n hint?: string\n /** What the field says while empty. */\n placeholder?: string\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: 'sm' | 'md' | 'lg'\n /** Takes 4px off the height. */\n compact?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /** Marks the field as invalid — for a rule of your own. */\n invalid?: boolean\n /** Offers a cross that empties the value, shown before the end icon. */\n clearable?: boolean\n /**\n * The icon that opens the PICKER, at the end of the field. It has no effect on the list\n * form, whose chevron follows the combobox convention. The clear cross appears to its\n * left rather than in its place.\n */\n pickerIcon?: IconSource\n /** Where the panel opens relative to the field. */\n placement?: Placement\n}\n\nconst props = withDefaults(defineProps<TimeInputProps>(), {\n format: undefined,\n // Deliberately left undefined rather than defaulted to the typed mode: that is what\n // distinguishes \"the prop was not given\" from \"the prop was given this value\", and\n // therefore what allows warning ONLY the consumer who explicitly asked for something\n // that cannot work.\n mode: undefined,\n showPicker: undefined,\n minuteStep: 1,\n locale: undefined,\n label: undefined,\n hint: undefined,\n placeholder: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n invalid: false,\n clearable: true,\n pickerIcon: () => scheduleIcon,\n placement: 'bottom-start',\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 */\nconst model = defineModel<string | null>({ default: null })\n\n// `class` and `style` stay on the wrapper; everything else goes down to the text field,\n// which is what a consumer's label points at and what assistive technology deals with.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelRef = ref<InstanceType<typeof VPopover> | null>(null)\nconst inputRef = ref<InstanceType<typeof VInput> | null>(null)\nconst pickerRef = ref<InstanceType<typeof VTimePicker> | null>(null)\nconst panelId = useId()\n\n/** The real input inside the field, which the mask and the caret work on. */\nconst fieldEl = computed<HTMLInputElement | null>(() => inputRef.value?.el ?? null)\n\nconst resolvedMode = computed<TimeInputMode>(() => {\n // TRAP — the default is applied BEFORE the value is checked. A prop that was never\n // given is not an unknown value, and treating it as one would warn on a bare\n // `<VTimeInput />`.\n const mode = props.mode ?? 'input'\n return MODES.includes(mode) ? mode : 'input'\n})\nconst typing = computed(() => resolvedMode.value === 'input')\nconst isList = computed(() => resolvedMode.value === 'list')\n/**\n * Whether there is a picker. It is forced on a read-only field, where nothing else could\n * fill it; offered beside a field one types into; and meaningless in the list form, which\n * has a panel of its own.\n */\nconst hasPicker = computed(\n () => resolvedMode.value === 'readonly' || (typing.value && props.showPicker === true),\n)\nconst hasPanel = computed(() => hasPicker.value || isList.value)\n\nconst vectisLocale = useLocale()\n/* The prop wins, and the design system's global locale is what it falls back to. */\nconst resolvedLocale = computed(() => props.locale ?? vectisLocale.value)\n\nconst resolvedFormat = computed<TimePickerFormat>(\n () => props.format ?? hourCycleFor(resolvedLocale.value),\n)\n\n// @devwarn\nif (isDev) {\n watchEffect(() => {\n if (props.minuteStep < 1 || 60 % props.minuteStep !== 0)\n console.warn(`[VTimeInput] minuteStep ${props.minuteStep} — a divisor of 60 is expected.`)\n if (props.mode !== undefined && !MODES.includes(props.mode))\n console.warn(\n `[VTimeInput] unknown mode \"${props.mode}\": use \"input\" (the default), \"readonly\" or \"list\".`,\n )\n if (resolvedMode.value === 'readonly' && props.showPicker === false)\n console.warn(\n '[VTimeInput] showPicker is forced to true in \"readonly\" mode: with no picker, a read-only field would be impossible to fill.',\n )\n if (isList.value && props.showPicker === true)\n console.warn(\n '[VTimeInput] showPicker is ignored in \"list\" mode: the list of times is the only panel.',\n )\n if (isList.value && props.minuteStep < 5)\n console.warn(\n `[VTimeInput] minuteStep ${props.minuteStep} in \"list\" mode would render ${Math.ceil(1440 / props.minuteStep)} rows: a step of 15 or 30 minutes is expected.`,\n )\n })\n}\n\n/**\n * The time being built on the clock, in the same canonical form as the value itself.\n * Nothing here reaches the value until OK is pressed — which is the whole reason the\n * picker is bound to THIS rather than to the model.\n */\nconst draft = ref<string | null>(null)\n\nconst modelParts = computed(() => parseTime(model.value))\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.\n */\nconst pendingMeridiem = ref<Meridiem>('AM')\n\n/**\n * The AM/PM control beside the FIELD. It is what the mask needs — the half of the day has\n * to be known while the panel is shut, and in the list form there is no picker at all — so\n * it is rendered in every form but the read-only one, where the picker carries its own.\n */\nconst meridiemModel = computed<ToggleModelValue>({\n get: () => {\n // With the picker open it is the DRAFT that is on screen, and this control has to read\n // the same thing: two controls showing opposite halves of the day until OK is pressed\n // is the one state that must not happen.\n const parts = open.value && hasPicker.value ? parseTime(draft.value) : modelParts.value\n return parts ? to12h(parts.hour).meridiem : pendingMeridiem.value\n },\n set: (value) => {\n const meridiem: Meridiem = value === 'PM' ? 'PM' : 'AM'\n pendingMeridiem.value = meridiem\n const parts = modelParts.value\n // With no time set there is nothing to convert, so the choice is simply REMEMBERED\n // and applies to the first time typed or chosen.\n if (parts) model.value = formatTime(to24h(to12h(parts.hour).hour, meridiem), parts.minute)\n // With the picker open the draft has to follow as well, or OK would write back the\n // half of the day the reader has just changed.\n const pending = open.value && hasPicker.value ? parseTime(draft.value) : null\n if (pending) draft.value = formatTime(to24h(to12h(pending.hour).hour, meridiem), pending.minute)\n },\n})\n\nconst hasValue = computed(() => !!modelParts.value)\nconst displayText = computed(() =>\n model.value ? formatDisplay(model.value, resolvedLocale.value, resolvedFormat.value) : '',\n)\n\n// @a11y\n// Where the focus goes when the panel opens.\nfunction focusInPanel() {\n if (!isList.value) {\n // The picker publishes where its own focus goes, so nothing here has to know that the\n // target is a slider — the same arrangement VDateInput has with VDatePicker.\n pickerRef.value?.focus()\n return\n }\n const panel = panelRef.value?.el\n if (panel) focusListSelection(panel)\n}\n\n// The whole \"field plus panel\" shell, shared with VDateInput. What is specific to this\n// component is only what happens around it: preparing the draft as the panel opens, and\n// clearing the announcement as it closes.\nconst { open, openPanel, closePanel, onControlClick, onFocusout, onKeydown, onPanelMousedown } =\n useFieldPanel({\n rootEl,\n panelRef,\n fieldEl: inputRef,\n // With no panel there is nothing to open. This is the composable's SINGLE cut-off\n // point, and every way in passes through it — clicking the field, focusing it, the\n // down arrow, Enter, the icon.\n disabled: () => props.disabled || !hasPanel.value,\n focusInPanel,\n // Beside a field one types into, the panel opens WITHOUT taking the focus, so typing\n // carries on.\n focusOnOpen: () => !typing.value,\n onOpen: () => {\n // Only the picker works on a draft; the list writes its choice straight away.\n if (!hasPicker.value) return\n const parts = modelParts.value\n if (parts) draft.value = formatTime(parts.hour, parts.minute)\n else {\n // Opening on the current time when none is set. Reading the clock is safe here:\n // this runs from a handler, hence in a browser, never during a render — and it is\n // why the picker itself never reads it, so that it stays identical on both sides\n // of hydration. The instant is taken ONCE: two reads could straddle a minute.\n const now = new Date()\n draft.value = formatTime(now.getHours(), snapMinute(now.getMinutes(), props.minuteStep))\n }\n pickerRef.value?.reset()\n },\n onClose: () => {\n pickerRef.value?.reset()\n },\n })\n\n// @a11y\n/**\n * TRAP — closing the panel hands the focus back to the field, and beside a field one\n * types into the panel opens ON FOCUS: the two would chase each other and the panel would\n * never close.\n *\n * This lock covers the focus call, which is synchronous. EVERY close that returns the\n * focus must go through here; closing directly brings the loop straight back.\n */\nlet refocusing = false\nfunction closeAndFocus() {\n refocusing = true\n closePanel(true)\n refocusing = false\n}\n\n/** OK: the ONE route by which the picker's draft becomes the value. */\nfunction confirm() {\n if (draft.value) model.value = draft.value\n closeAndFocus()\n}\n\nfunction cancel() {\n closeAndFocus()\n}\n\n// @a11y\n/*\n * Emptying the value, called by the field as it emits its clear event.\n *\n * TRAP — the focus is taken here, under the lock, on purpose: the field focuses itself\n * immediately afterwards, and focusing an element that ALREADY has the focus emits no\n * event at all. That is what stops the panel reopening. Taking the lock away from this\n * function brings the reopening straight back.\n */\nfunction clearValue() {\n model.value = null\n // The typed text is emptied EXPLICITLY: when the value was already empty, nothing\n // changes, and the guard that keeps the field and the value from chasing each other\n // would leave the text where it was.\n if (typing.value) writeField('')\n refocusing = true\n inputRef.value?.focus()\n refocusing = false\n}\n\n// Escape and the focus leaving both close WITHOUT committing, which is the picker's Cancel\n// behaviour: closing never writes the value, and only OK does.\n\nconst m = useMessages()\n\n/* From here on: everything the typed field needs. */\n\nconst currentMeridiem = (): Meridiem => (meridiemModel.value === 'PM' ? 'PM' : 'AM')\n\n/*\n * The mask machinery — the text being typed, the bridge to the value, the reformatting\n * that preserves the caret, the commit and the silent revert — is shared with VDateInput\n * and lives in `useMaskedField`.\n *\n * The time vocabulary is the simpler of the two: four digits, and a separator that is the\n * SAME in every language. That is why the caret can be computed outright here, where a\n * date has to look for a separator it cannot predict; and why nothing distinguishes a\n * final commit from a live one, there being no equivalent of expanding a two-digit year.\n */\nconst {\n draft: maskDraft,\n fieldModel,\n writeField,\n commitLive,\n commitOrRevert,\n onFieldInput,\n} = useMaskedField({\n fieldEl,\n typing: () => typing.value,\n displayText: () => displayText.value,\n readValue: () => model.value,\n writeValue: (time) => {\n model.value = time\n },\n maxDigits: () => 4,\n format: formatTimeMask,\n caret: (_text, digitsBefore, inserting) => timeCaret(digitsBefore, inserting),\n parse: (text) => parseTimeMask(text, resolvedFormat.value, currentMeridiem()),\n toMask: (time) => timeToMask(time, resolvedFormat.value),\n})\n\n// @keyboard @core — the keys the mask itself needs, plus the down arrow, which is the one\n// explicit way from the field into the picker.\nfunction onFieldKeydown(event: KeyboardEvent) {\n if (!typing.value) return\n const el = fieldEl.value\n if (!el) return\n\n if (event.key === 'Enter') {\n // Cancelling the default does two things at once: it stops the surrounding form from\n // being submitted, and it stops the panel this keystroke has just closed from being\n // reopened as the event travels up to the root.\n event.preventDefault()\n commitOrRevert()\n if (open.value) closeAndFocus()\n return\n }\n if (event.key === 'ArrowDown' && open.value && hasPicker.value) {\n // The one explicit route from the field into the picker.\n event.preventDefault()\n focusInPanel()\n return\n }\n if (event.key === 'Backspace') {\n const start = el.selectionStart\n if (\n start !== null &&\n start === el.selectionEnd &&\n start > 0 &&\n !/\\d/.test(el.value[start - 1] ?? '')\n ) {\n // TRAP — the separator is PLACED by the mask and never typed, so erasing one has to\n // erase the DIGIT before it. Left alone, the mask would write it straight back and\n // the key would look dead.\n event.preventDefault()\n const n = digitsOf(el.value.slice(0, start)).length\n const digits = digitsOf(el.value)\n writeField(formatTimeMask(digits.slice(0, n - 1) + digits.slice(n)), timeCaret(n - 1))\n commitLive()\n }\n return\n }\n // Typing anything that is not a digit — the separator included, so that \"9:30\" can be\n // typed exactly as it reads — completes the hour with a leading zero and moves on to\n // the minutes.\n if (\n event.key.length === 1 &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey &&\n !/\\d/.test(event.key)\n ) {\n event.preventDefault()\n const digits = digitsOf(el.value)\n if (digits.length === 1) writeField(formatTimeMask(`0${digits}`), 3)\n }\n}\n\n/**\n * Pasting. A time recognizable as a whole is adopted as it stands; anything else\n * contributes its digits alone.\n */\nfunction onFieldPaste(event: ClipboardEvent) {\n if (!typing.value) return\n const el = fieldEl.value\n if (!el) return\n event.preventDefault()\n const pasted = (event.clipboardData?.getData('text') ?? '').trim()\n // A pasted 24-hour time is read as one whatever clock is on display: \"19:05\" means\n // seven in the evening even in a field showing a 12-hour clock.\n if (isValidTime(pasted)) {\n const text = timeToMask(pasted, resolvedFormat.value)\n writeField(text, text.length)\n if (pasted !== model.value) model.value = pasted\n return\n }\n const digits = digitsOf(el.value)\n const start = el.selectionStart ?? el.value.length\n const end = el.selectionEnd ?? start\n const from = digitsOf(el.value.slice(0, start)).length\n const to = digitsOf(el.value.slice(0, end)).length\n const inserted = digitsOf(pasted)\n const next = (digits.slice(0, from) + inserted + digits.slice(to)).slice(0, 4)\n writeField(formatTimeMask(next), timeCaret(Math.min(from + inserted.length, next.length), true))\n commitLive()\n}\n\nfunction onFieldFocus() {\n if (!typing.value || refocusing) return\n openPanel(false)\n}\n\nfunction onRootKeydown(event: KeyboardEvent) {\n if (typing.value && event.key === 'Escape' && open.value) {\n event.preventDefault()\n closeAndFocus()\n return\n }\n onKeydown(event)\n}\n\n/* From here on: everything the list form needs. */\n\nconst options = computed<TimeOption[]>(() =>\n isList.value ? timeList(props.minuteStep, resolvedLocale.value, resolvedFormat.value) : [],\n)\n\n// @keyboard\n/**\n * The rows the arrows may move to, gathered by hand rather than through the shared\n * helper.\n *\n * That helper asks the browser for the computed style of EVERY element it considers, to\n * skip the hidden ones. Across dozens of rows — a list at a five-minute step holds nearly\n * three hundred — that is a real cost, and none of these rows is ever hidden.\n */\nconst optionEls = (panel: HTMLElement) => [\n ...panel.querySelectorAll<HTMLElement>('[role=\"option\"]'),\n]\n\n// @a11y\n/**\n * On opening, the list is both scrolled and focused onto the current time — or onto the\n * NEAREST row, since a value need not be one of them: \"09:07\" against a list stepping\n * every fifteen minutes matches nothing at all.\n */\nfunction focusListSelection(panel: HTMLElement) {\n const items = optionEls(panel)\n if (!items.length) return\n const minutes = minutesOf(model.value)\n const index =\n minutes === null ? 0 : clamp(Math.round(minutes / props.minuteStep), 0, items.length - 1)\n const el = items[index]\n // Called optionally, the unit-test environment implementing no scrolling. No scrolling\n // behaviour is requested either: leaving it out lets the stylesheet decide, which is\n // what makes the movement respect a reader who has asked for less motion.\n el?.scrollIntoView?.({ block: 'center' })\n el?.focus({ preventScroll: true })\n}\n\n/**\n * Choosing a row writes the value straight away and closes — no draft and no footer, the\n * same way choosing a single date closes the calendar.\n */\nfunction selectTime(value: string) {\n model.value = value\n closeAndFocus()\n}\n\n// @keyboard @a11y\nfunction onPanelKeydown(event: KeyboardEvent) {\n if (!isList.value) return\n const panel = panelRef.value?.el\n if (!panel) return\n if (event.key === 'Enter' || event.key === ' ') {\n // TRAP — cancelling the default here is MANDATORY, and the commit consequently has to\n // be explicit. Left alone, Enter would fire the row's own click — which closes the\n // panel — and then travel up to the root WITHOUT being marked as handled, so the\n // guard that stops a consumed key reopening the panel would not apply, and it would\n // reopen immediately.\n const value = (document.activeElement as HTMLElement | null)?.closest<HTMLElement>(\n '[role=\"option\"]',\n )?.dataset.value\n if (!value) return\n event.preventDefault()\n selectTime(value)\n return\n }\n arrowNavigate(event, panel, optionEls(panel), { vertical: true })\n}\n\n/*\n * The clear cross and the icon at the end of the field.\n *\n * The cross is the field's own, which renders it BEFORE the end icon rather than in its\n * place — the convention every field in the design system follows. In the list form that\n * gives exactly the cross-and-chevron pairing of a combobox, which is the right thing:\n * the two behave the same way.\n *\n * Whether the cross is shown has to be answered explicitly here: outside the typed mode\n * the field is read-only and would hide it, while the value comes from the panel.\n */\nconst canClear = computed(\n () =>\n props.clearable && !props.disabled && (hasValue.value || (typing.value && !!maskDraft.value)),\n)\nconst endIcon = computed<IconSource | undefined>(() =>\n isList.value ? expandMoreIcon : hasPicker.value ? props.pickerIcon : undefined,\n)\n// @a11y @devwarn\n/*\n * TRAP — the LABEL is defined at all times, even when no icon is rendered at all.\n *\n * The helper detecting a click handler on an icon warns AT SETUP if one is attached\n * without a label, and it has no way of knowing whether an icon exists. Since the\n * listener here is attached permanently and that detection is static, making the label\n * conditional would produce a false warning every time a typed field without a picker is\n * mounted.\n */\nconst endIconLabel = computed(() =>\n isList.value ? m.value.timeInput.openList : m.value.timeInput.openPicker,\n)\n\nfunction onEndIcon() {\n if (open.value) closeAndFocus()\n else openPanel(true)\n}\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-time-input\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-open=\"open ? '' : undefined\"\n :data-mode=\"resolvedMode\"\n @focusout=\"onFocusout\"\n @keydown=\"onRootKeydown\"\n >\n <div class=\"v-time-input-row\">\n <div class=\"v-time-input-control\" @click=\"onControlClick\">\n <!-- The field is declared a combobox rather than left as the plain text box it\n implicitly is, because a text box may not carry the attribute saying whether\n something is expanded — the same reasoning as in VDateInput. Here it also\n agrees with the list form, where what the field opens really is a list. -->\n <VInput\n ref=\"inputRef\"\n v-model=\"fieldModel\"\n :inputmode=\"typing ? 'numeric' : undefined\"\n :autocomplete=\"typing ? 'off' : undefined\"\n v-bind=\"forwardedAttrs\"\n :readonly=\"!typing\"\n :label=\"label\"\n :hint=\"hint\"\n :placeholder=\"placeholder ?? (typing ? m.timeInput.maskPlaceholder : undefined)\"\n :size=\"size\"\n :compact=\"compact\"\n :disabled=\"disabled\"\n :invalid=\"invalid\"\n :clearable=\"clearable\"\n :clear-visible=\"canClear\"\n :clear-label=\"m.timeInput.clear\"\n :icon-end=\"endIcon\"\n :icon-end-label=\"endIconLabel\"\n :role=\"hasPanel ? 'combobox' : undefined\"\n :aria-haspopup=\"hasPanel ? (isList ? 'listbox' : 'dialog') : undefined\"\n :aria-expanded=\"hasPanel ? open : undefined\"\n :aria-controls=\"hasPanel ? panelId : undefined\"\n @click:icon-end=\"onEndIcon\"\n @clear=\"clearValue\"\n @focus=\"onFieldFocus\"\n @input=\"onFieldInput\"\n @change=\"commitOrRevert\"\n @blur=\"commitOrRevert\"\n @keydown=\"onFieldKeydown\"\n @paste=\"onFieldPaste\"\n />\n </div>\n\n <!-- The AM/PM control beside the field, which the two typed forms cannot do without:\n the mask needs the half of the day while the panel is shut, and the list form\n has no picker at all. It is dropped in the READ-ONLY form alone, where the\n picker is the only way in and carries its own.\n\n It stays INSIDE the component's root: clicking it must not count as the focus\n leaving, which would close the panel under the reader's hand. -->\n <VToggle\n v-if=\"resolvedFormat === '12h' && resolvedMode !== 'readonly'\"\n v-model=\"meridiemModel\"\n class=\"v-time-input-meridiem\"\n mandatory\n variant=\"outline\"\n :size=\"size\"\n :compact=\"compact\"\n :label=\"m.timePicker.meridiem\"\n >\n <VToggleItem value=\"AM\" :label=\"m.timePicker.am\" />\n <VToggleItem value=\"PM\" :label=\"m.timePicker.pm\" />\n </VToggle>\n </div>\n\n <!-- With no panel rendered there is nothing to hold a reference to, and the open state\n — which is fed by the panel's own events — can no longer become true. The absence\n of a panel is therefore self-enforcing. -->\n <VPopover\n v-if=\"hasPanel\"\n :id=\"panelId\"\n ref=\"panelRef\"\n v-model:open=\"open\"\n mode=\"manual\"\n anchor=\"--time-input-anchor\"\n :placement=\"placement\"\n surface\n :role=\"isList ? 'listbox' : 'dialog'\"\n :class=\"isList ? 'v-time-input-list v-control' : 'v-time-input-panel'\"\n :data-size=\"isList ? size : undefined\"\n :data-compact=\"isList && compact ? '' : undefined\"\n :aria-label=\"label ?? (isList ? m.timeInput.listLabel : m.timeInput.pickerLabel)\"\n @mousedown=\"onPanelMousedown\"\n @keydown=\"onPanelKeydown\"\n >\n <!-- The list: rows the focus really moves between, as in a menu or a calendar —\n unlike VCombobox, where the focus stays in the field — and a choice written\n straight away on a click or on Enter. -->\n <template v-if=\"isList\">\n <button\n v-for=\"option in options\"\n :key=\"option.value\"\n type=\"button\"\n role=\"option\"\n tabindex=\"-1\"\n class=\"v-time-input-option\"\n :data-value=\"option.value\"\n :aria-selected=\"option.value === model ? 'true' : 'false'\"\n @click=\"selectTime(option.value)\"\n >\n {{ option.label }}\n </button>\n </template>\n\n <!-- The picker works on the DRAFT and not on the value, which is what makes OK the\n only way in: Cancel, Escape and the focus leaving simply drop it. The two\n actions are handed to its own footer slot, since they belong to this panel\n rather than to a clock shown on its own. -->\n <VTimePicker\n v-else\n ref=\"pickerRef\"\n v-model=\"draft\"\n :format=\"resolvedFormat\"\n :locale=\"resolvedLocale\"\n :minute-step=\"minuteStep\"\n @confirm=\"confirm\"\n >\n <template #footer>\n <VButton variant=\"ghost\" tone=\"neutral\" @click=\"cancel\">{{ m.common.cancel }}</VButton>\n <VButton @click=\"confirm\">{{ m.common.confirm }}</VButton>\n </template>\n </VTimePicker>\n </VPopover>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-time-input {\n /* Confines the anchor name to this instance. It is declared on the root because that\n is the common ancestor of the field and the panel. */\n anchor-scope: --time-input-anchor;\n display: block;\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n /* The row holding the field: the field takes whatever room is left, and the AM/PM\n control keeps its natural width. */\n .v-time-input-row {\n display: flex;\n align-items: stretch;\n gap: var(--vectis-space-2);\n }\n\n .v-time-input-control {\n anchor-name: --time-input-anchor;\n flex: 1;\n /* Without this the field would refuse to shrink below its own content and would\n overflow the row. */\n min-inline-size: 0;\n cursor: pointer;\n }\n\n /* A field one types into shows the text cursor rather than the pointer a clickable\n control shows. Its figures are also given equal widths: with proportional ones the\n text shifts as digits are typed, and the caret appears to jitter. */\n .v-time-input[data-mode='input'] .v-time-input-control {\n cursor: text;\n }\n\n .v-time-input[data-mode='input'] .v-input-control {\n font-variant-numeric: tabular-nums;\n }\n\n /* The AM/PM control lines up with the FIELD and not with the whole block, which also\n stacks a label above and a hint below it. Those two are compensated with margins\n derived from their own type, so the control sits exactly beside the field whatever\n the field is given. */\n .v-time-input-meridiem {\n flex: none;\n align-self: center;\n }\n\n .v-time-input-row:has(.v-input-label) .v-time-input-meridiem {\n margin-block-start: calc(\n var(--vectis-text-label-size) * var(--vectis-text-label-leading) + var(--vectis-space-1)\n );\n }\n\n .v-time-input-row:has(.v-input-hint) .v-time-input-meridiem {\n margin-block-end: calc(\n var(--vectis-text-caption-size) * var(--vectis-text-caption-leading) + var(--vectis-space-1)\n );\n }\n\n /* The anchoring and the panel's surface both come from VPopover; what is left here is\n the padding around the picker. The picker brings its own layout — including the gap\n between its parts and the centring of the face — so nothing of that is declared here.\n\n TRAP — declaring a display here overrides the browser's own rule hiding a closed\n popover. What closes it back is the shared guard on the overlay class, which is more\n specific than anything a component can write.\n\n The selector compounds two classes VPopover puts on the same element, because the\n padding is also declared by the shared panel class: at equal specificity the winner\n would be whichever sheet the consumer's bundler put last. The size attribute cannot\n serve that purpose here — the picker's panel carries none. */\n .v-popover-panel.v-time-input-panel {\n display: flex;\n flex-direction: column;\n width: max-content;\n padding: var(--vectis-space-3);\n color: var(--vectis-color-text);\n }\n\n /* The list's surface comes from the shared panel class, and its rows read their\n dimensions from the size class set on the PANEL. What stays here are the panel's own\n dimensions, which the shared class deliberately declares none of. */\n .v-time-input-list {\n min-inline-size: anchor-size(width);\n max-block-size: var(--vectis-control-size-time-input-list-max-block);\n overflow: auto;\n }\n\n .v-time-input-option {\n display: flex;\n align-items: center;\n width: 100%;\n min-height: var(--control-height);\n padding: var(--vectis-space-1) var(--control-padding-inline);\n border: none;\n border-radius: var(--vectis-radius-sm);\n background: transparent;\n color: var(--vectis-color-text);\n font-family: inherit;\n font-size: var(--control-font-size);\n line-height: var(--vectis-text-body-md-leading);\n font-variant-numeric: tabular-nums;\n text-align: start;\n cursor: pointer;\n }\n\n .v-time-input-option:hover {\n background: var(--vectis-color-surface-muted);\n }\n\n /* The focus ring is drawn INSIDE the row: the panel scrolls, and a ring sitting outside\n it would be cropped on the first and last rows. */\n .v-time-input-option:focus-visible {\n outline: var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);\n outline-offset: calc(-1 * var(--vectis-focus-ring-width));\n }\n\n .v-time-input-option[aria-selected='true'] {\n background: var(--vectis-color-accent-surface);\n color: var(--vectis-color-accent-text);\n }\n\n .v-time-input-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-2);\n }\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"VTimeInput.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VTimeInput/VTimeInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\n// @a11y @core\n/**\n * A field for choosing a time. Three forms, and only two of them are built here.\n *\n * `input` types hours and minutes behind a mask, optionally with a clock beside it, and\n * `picker` makes that clock the only way in. What the field is FOR is a different question\n * from the `readonly` prop, which freezes whatever it holds by every route at once. Both use the VDateInput shell\n * (`useFieldPanel`): the panel is opened imperatively, so the focus can be moved into it,\n * and closes on `focusout` or Escape. The CLOCK works on a draft only OK writes — Cancel,\n * Escape and focusout all discard it — because dragging a hand passes over dozens of times\n * nobody meant.\n *\n * `list` offers the times at a fixed interval, which suits booking a slot far better than\n * pointing at a dial. It is a VCombobox outright, so a time is FOUND by typing as much as\n * scrolled to, and the whole listbox pattern comes from there rather than being written\n * again. Choosing a row commits at once: picking from a list is a single gesture.\n *\n * Whatever is displayed, the value is always a canonical 24-hour `'HH:mm'`.\n */\n\nimport { computed, inject, provide, ref, watchEffect } from 'vue'\n\nimport VButton from '../VButton/VButton.vue'\nimport VCombobox from '../VCombobox/VCombobox.vue'\nimport type { ComboboxOption } from '../VCombobox/VCombobox.vue'\nimport { inputGroupKey } from '../VInput/context'\nimport { schedule as scheduleIcon } from '../VIcon/icons/schedule'\nimport type { IconSource } from '../VIcon/types'\nimport VInput from '../VInput/VInput.vue'\nimport VPopover from '../VPopover/VPopover.vue'\nimport VSeparator from '../VSeparator/VSeparator.vue'\nimport VTimePicker from '../VTimePicker/VTimePicker.vue'\nimport type { TimePickerFormat } from '../VTimePicker/VTimePicker.vue'\nimport {\n isTimeAllowed,\n limitsProblem,\n nearestAllowedTime,\n resolveLimits,\n} from '../VTimePicker/limits'\nimport type { TimePickerAllowed } from '../VTimePicker/limits'\nimport {\n formatTime,\n formatTimeDisplay,\n formatTimeMask,\n hourCycleFor,\n isValidTime,\n parseTime,\n parseTimeMask,\n snapMinute,\n timeCaret,\n timeList,\n timeToMask,\n to12h,\n withMeridiem,\n} from '../../utils/time'\nimport type { Meridiem, TimeOption } from '../../utils/time'\nimport { timeMatches } from './search'\nimport { isDev } from '../../utils/env'\nimport { hostWarnsKey } from '../../utils/hostWarns'\nimport { digitsOf } from '../../utils/text'\n\nimport { useControlShape } from '../../composables/useControlShape'\nimport { useRootAttrs } from '../../composables/useRootAttrs'\n\nimport { useFieldPanel } from '../../composables/useFieldPanel'\nimport { canClear } from '../../composables/useClearable'\nimport { iconStartListener } from '../../composables/useIconClickHandlers'\nimport { useMaskedField } from '../../composables/useMaskedField'\nimport { useMessages, useResolvedLocale } from '../../i18n/state'\n\nexport type TimeInputMode = 'picker' | 'input' | 'list'\n\nconst MODES: TimeInputMode[] = ['picker', 'input', 'list']\n\n/** Where the panel opens relative to the field, whether it holds the clock or the list. */\nexport type TimeInputPlacement =\n 'bottom' | 'bottom-start' | 'bottom-end' | 'top' | 'top-start' | 'top-end'\n\n/** The height of the field: 32, 40 or 48 pixels. */\nexport type TimeInputSize = 'sm' | 'md' | 'lg'\n\ninterface TimeInputProps {\n /**\n * Whether times are shown on 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 * Which form the field takes: one that can be TYPED into, a `picker` one where the\n * clock is the only way in — so the picker is forced on there — or a searchable LIST of\n * times at a fixed interval, where a picker would make no sense.\n */\n mode?: TimeInputMode\n /**\n * Offers the picker beside a field one can type into: an icon at the end of the field,\n * and a panel it opens.\n *\n * It means nothing in `picker` mode, where the clock is already the only way to choose,\n * nor in `list` mode, which has a panel of its own — VDateInput reads it the same way.\n */\n showPicker?: boolean\n /**\n * The interval between two times that can be chosen. It applies to the picker, to the\n * arrow keys and to the rows of the list.\n */\n minuteStep?: number\n /**\n * The earliest time that can be chosen, inclusive, as a canonical 24-hour `'HH:mm'`.\n * The picker and the list both LEAVE OUT what it rules out, and a time typed past it\n * makes the field invalid.\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.\n */\n allowedHours?: TimePickerAllowed\n /** Which minutes can be chosen: the list of them, or a rule answering for one. */\n allowedMinutes?: TimePickerAllowed\n /**\n * A BCP 47 locale, which decides the clock and how a time is written out. It TAKES\n * PRECEDENCE over the design system's global locale and falls back to it — which is why\n * it has no literal default: `undefined` has to stay recognizable for the global locale\n * to have its chance.\n */\n locale?: string\n // From here on: the field.\n /** The label above the field. */\n label?: string\n /** A line of help under the field. */\n hint?: string\n /** What the field says while empty. */\n placeholder?: string\n /** The height of the field: 32, 40 or 48 pixels. */\n size?: TimeInputSize\n /** Takes 4px off the height. */\n compact?: boolean\n /** Makes the field unusable, greyed out through the colour tokens. */\n disabled?: boolean\n /**\n * Shows the time without letting it be changed: nothing can be typed, there is no\n * clock and no clear cross. The field stays focusable and can be copied from, which\n * is what separates it from `disabled`, and it is a different question from `mode`,\n * which says how a field that CAN be changed is filled in.\n */\n readonly?: boolean\n /** Marks the field as invalid — for a rule of your own. */\n invalid?: boolean\n /**\n * An icon inside the field, at the start. It is decorative by default and becomes a\n * real button as soon as a `@click:icon-start` listener is attached, in which case it\n * needs `iconStartLabel`.\n */\n iconStart?: IconSource\n /** What the start icon does, in words, once it is clickable. */\n iconStartLabel?: string\n /**\n * What the clock button does, in words. It names the button `pickerIcon` renders,\n * and falls back to the design system dictionary.\n */\n pickerIconLabel?: string\n /**\n * Shows a spinner at the end of the field, in place of the clock icon. It says that\n * something is being loaded and changes nothing else: the field can still be typed\n * into and the panel still opens.\n */\n loading?: boolean\n /**\n * What screen readers announce while the spinner turns. It falls back to the design\n * system dictionary.\n */\n loadingLabel?: string\n /** Offers a cross that empties the value, shown before the end icon. */\n clearable?: boolean\n /** What that cross does, in words. It falls back to the design system dictionary. */\n clearLabel?: string\n /**\n * The icon that opens the PICKER, at the end of the field. It has no effect on the list\n * form, whose chevron follows the combobox convention. The clear cross appears to its\n * left rather than in its place.\n */\n pickerIcon?: IconSource\n /** Where the panel opens relative to the field, whether it holds the clock or the list. */\n placement?: TimeInputPlacement\n}\n\nconst props = withDefaults(defineProps<TimeInputProps>(), {\n format: undefined,\n // Deliberately left undefined rather than defaulted to the typed mode: that is what\n // distinguishes \"the prop was not given\" from \"the prop was given this value\", and\n // therefore what allows warning ONLY the consumer who explicitly asked for something\n // that cannot work.\n mode: undefined,\n showPicker: false,\n minuteStep: 1,\n min: undefined,\n max: undefined,\n allowedHours: undefined,\n allowedMinutes: undefined,\n locale: undefined,\n label: undefined,\n hint: undefined,\n placeholder: undefined,\n size: 'md',\n compact: false,\n disabled: false,\n readonly: false,\n invalid: false,\n iconStart: undefined,\n iconStartLabel: undefined,\n pickerIconLabel: undefined,\n loading: false,\n loadingLabel: undefined,\n clearable: false,\n clearLabel: undefined,\n pickerIcon: () => scheduleIcon,\n placement: 'bottom-start',\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 */\nconst model = defineModel<string | null>({ default: null })\n\nconst emit = defineEmits<{\n /** The clear cross emptied the field. The value has already been reset. */\n clear: []\n /** The start icon was clicked. Attaching this listener is what makes it a button. */\n 'click:icon-start': [event: MouseEvent]\n}>()\n\ndefineSlots<{\n /**\n * Content at the start of the field, rendered after `iconStart` rather than in its place,\n * as in VInput.\n */\n start?(): unknown\n /**\n * Controls of your own inside the field, after the AM/PM button when there is one and\n * before the ones the field owns — the clear cross and the icon that opens the panel.\n * Those two are this component's own affordance, which is why there is no `#end` here.\n */\n 'value-end'?(): unknown\n /**\n * The strip at the foot of the clock, which REPLACES the Cancel and OK buttons rather\n * than joining them.\n *\n * It receives both actions, and they are what make the slot usable: the clock writes a\n * DRAFT that only `confirm` commits, so a footer of your own without it would leave the\n * value unchangeable through the panel. `cancel` drops the draft and closes, and `close`\n * is the same function under the name VDateInput's footer hands out.\n *\n * It is not rendered in `list` mode, which has no panel of this component's own.\n */\n footer?(props: { confirm: () => void; cancel: () => void; close: () => void }): unknown\n}>()\n\n// `class` and `style` stay on the wrapper; everything else goes down to the text field,\n// which is what a consumer's label points at and what assistive technology deals with.\ndefineOptions({ inheritAttrs: false })\nconst { rootClass, rootStyle, forwardedAttrs } = useRootAttrs()\n\n// Declared as this component's own event, `click:icon-start` is out of `$attrs`, so the\n// listener is relayed to the field by hand — and only when the consumer wrote one.\nconst iconStartClick = iconStartListener((event) => emit('click:icon-start', event))\n\n/** What reaches the field on screen — the masked VInput or the list's VCombobox. */\nconst fieldAttrs = computed(() => ({ ...forwardedAttrs.value, ...iconStartClick }))\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst panelRef = ref<InstanceType<typeof VPopover> | null>(null)\nconst inputRef = ref<InstanceType<typeof VInput> | null>(null)\nconst listRef = ref<InstanceType<typeof VCombobox> | null>(null)\nconst pickerRef = ref<InstanceType<typeof VTimePicker> | null>(null)\n\n/** The real input inside the field, which the mask and the caret work on. */\nconst fieldEl = computed<HTMLInputElement | null>(() => inputRef.value?.el ?? null)\n\nconst resolvedMode = computed<TimeInputMode>(() => {\n // TRAP — the default is applied BEFORE the value is checked. A prop that was never\n // given is not an unknown value, and treating it as one would warn on a bare\n // `<VTimeInput />`.\n const mode = props.mode ?? 'input'\n return MODES.includes(mode) ? mode : 'input'\n})\nconst typing = computed(() => resolvedMode.value === 'input')\nconst isList = computed(() => resolvedMode.value === 'list')\n/**\n * Whether this component builds a panel of its own, which is the clock's: the list form\n * hands that job to VCombobox. It is forced on a `picker` field, where nothing else could\n * fill it, and offered beside a field one types into. A frozen field has none: there is\n * nothing left for a panel to do.\n */\nconst hasPanel = computed(\n () => !props.readonly && (resolvedMode.value === 'picker' || (typing.value && props.showPicker)),\n)\n\nconst resolvedLocale = useResolvedLocale(() => props.locale)\n\nconst resolvedFormat = computed<TimePickerFormat>(\n () => props.format ?? hourCycleFor(resolvedLocale.value),\n)\n\n/**\n * The restrictions, resolved once. VTimePicker resolves the same props again for itself:\n * they are ITS contract, and this component only consults them for the two things the\n * picker knows nothing about — which rows the list offers, and whether a typed time is\n * one the field should be showing as invalid.\n */\nconst limits = computed(() => resolveLimits(props))\n\n/** Whether the value is one the restrictions allow. Nothing at all is not a breach. */\nconst valueAllowed = computed(() => {\n const parts = modelParts.value\n return !parts || isTimeAllowed(parts.hour, parts.minute, limits.value)\n})\n\n// @devwarn\nif (isDev) {\n // The clock receives `minuteStep` and the restrictions as they stand, so what it would say\n // about them is said here, under the name the consumer wrote (utils/hostWarns).\n provide(hostWarnsKey, true)\n watchEffect(() => {\n if (props.minuteStep < 1 || 60 % props.minuteStep !== 0)\n console.warn(`[VTimeInput] minuteStep ${props.minuteStep} — a divisor of 60 is expected.`)\n if (props.mode !== undefined && !MODES.includes(props.mode))\n console.warn(\n `[VTimeInput] unknown mode \"${props.mode}\": use \"input\" (the default), \"picker\" or \"list\".`,\n )\n if (isList.value && props.showPicker)\n console.warn(\n '[VTimeInput] showPicker is ignored in \"list\" mode: the list of times is the only panel.',\n )\n // The list form is a VCombobox, which draws the end of the field itself: its own\n // chevron, and a spinner that names nothing because the panel is what announces the\n // loading. The three props that describe THIS component's end icon therefore reach\n // nothing at all, and their absence is invisible on screen.\n const inert = ([] as string[]).concat(\n props.pickerIcon !== scheduleIcon ? 'pickerIcon' : [],\n props.pickerIconLabel ? 'pickerIconLabel' : [],\n props.loadingLabel ? 'loadingLabel' : [],\n )\n if (isList.value && inert.length > 0)\n console.warn(\n `[VTimeInput] ${inert.join(', ')} ${inert.length > 1 ? 'are' : 'is'} ignored in \"list\" mode: the list draws its own chevron and spinner.`,\n )\n // A field one types into without the picker has no end icon either, as in VDateInput.\n // A read-only field is left out: it is a state that comes and goes, not a configuration\n // to correct.\n const iconless = ([] as string[]).concat(\n props.pickerIcon !== scheduleIcon ? 'pickerIcon' : [],\n props.pickerIconLabel ? 'pickerIconLabel' : [],\n )\n if (typing.value && !props.showPicker && iconless.length > 0)\n console.warn(\n `[VTimeInput] ${iconless.join(', ')} ${iconless.length > 1 ? 'are' : 'is'} ignored without showPicker: a field one types into has no clock icon unless it offers the clock.`,\n )\n const problem = limitsProblem(limits.value)\n if (problem) console.warn(`[VTimeInput] ${problem}`)\n if (isList.value && props.minuteStep < 5)\n console.warn(\n `[VTimeInput] minuteStep ${props.minuteStep} in \"list\" mode would render ${Math.ceil(1440 / props.minuteStep)} rows: a step of 15 or 30 minutes is expected.`,\n )\n })\n}\n\n/**\n * The time being built on the clock, in the same canonical form as the value itself.\n * Nothing here reaches the value until OK is pressed — which is the whole reason the\n * picker is bound to THIS rather than to the model.\n */\nconst pickerDraft = ref<string | null>(null)\n\nconst modelParts = computed(() => parseTime(model.value))\n\n/** The half of the day chosen while no time is set: VTimePicker's own `pendingMeridiem`. */\nconst pendingMeridiem = ref<Meridiem>('AM')\n\n/**\n * The half of the day the FIELD is showing. It is what the mask needs: typing \"9:30\" says\n * nothing about the morning or the afternoon, so the answer has to be readable while the\n * panel is shut.\n */\nconst currentMeridiem = computed<Meridiem>(() => {\n // With the picker open it is the DRAFT that is on screen, and this control has to read\n // the same thing: two controls showing opposite halves of the day until OK is pressed\n // is the one state that must not happen.\n const parts = open.value && hasPanel.value ? parseTime(pickerDraft.value) : modelParts.value\n return parts ? to12h(parts.hour).meridiem : pendingMeridiem.value\n})\n\n/**\n * The button beside the value flips the half of the day. There is no third state to reach,\n * so it names the half it is showing and swaps to the other one on a click.\n */\nfunction toggleMeridiem() {\n const next: Meridiem = currentMeridiem.value === 'PM' ? 'AM' : 'PM'\n pendingMeridiem.value = next\n // With no time set there is nothing to convert, so the choice is simply REMEMBERED\n // and applies to the first time typed or chosen.\n const moved = withMeridiem(model.value, next)\n if (moved) model.value = moved\n // With the picker open the draft has to follow as well, or OK would write back the\n // half of the day the reader has just changed.\n const pending = open.value && hasPanel.value ? withMeridiem(pickerDraft.value, next) : null\n if (pending) pickerDraft.value = pending\n}\n\n/**\n * Whether the button is rendered at all. Only the typed form needs it: the `picker` form\n * reaches the clock, which carries its own AM/PM, and the list already spells the half of\n * the day out on every row. It writes the value, so a frozen field renders none.\n */\nconst hasMeridiem = computed(\n () => typing.value && !props.readonly && resolvedFormat.value === '12h',\n)\n\nconst hasValue = computed(() => !!modelParts.value)\nconst displayText = computed(() =>\n model.value ? formatTimeDisplay(model.value, resolvedLocale.value, resolvedFormat.value) : '',\n)\n\n// @a11y\n// Where the focus goes when the panel opens. The picker publishes where its own focus\n// belongs, so nothing here has to know that the target is a slider — the same arrangement\n// VDateInput has with VDatePicker.\nfunction focusInPanel() {\n pickerRef.value?.focus()\n}\n\n// A VInputGroup joins several controls into one object, so the shape of this field is the\n// row's decision rather than its own (VInput/context.ts). Nothing else is needed here: the\n// AM/PM button lives INSIDE the field, so a 12 hour field is one box like any other and\n// the row has nothing of its own to join.\nconst group = inject(inputGroupKey, null)\nconst {\n size: resolvedSize,\n compact: resolvedCompact,\n disabled: resolvedDisabled,\n} = useControlShape(props, group)\n\n// The whole \"field plus panel\" shell, shared with VDateInput. What is specific to this\n// component is only what happens around it: preparing the draft as the panel opens, and\n// clearing the announcement as it closes.\nconst {\n open,\n panelId,\n closeAndFocus,\n focusField,\n onFieldFocus,\n toggleFromIcon,\n onControlClick,\n onFocusout,\n onKeydown,\n onPanelMousedown,\n} = useFieldPanel({\n rootEl,\n panelRef,\n field: inputRef,\n // With no panel there is nothing to open. This is the composable's SINGLE cut-off\n // point, and every way in passes through it — clicking the field, focusing it, the\n // down arrow, Enter, the icon.\n disabled: () => resolvedDisabled.value || !hasPanel.value,\n focusInPanel,\n // Beside a field one types into, the panel opens on focus WITHOUT taking it, so typing\n // carries on.\n openOnFocus: () => typing.value,\n onOpen: () => {\n // Only the picker works on a draft; the list writes its choice straight away.\n if (!hasPanel.value) return\n const parts = modelParts.value\n if (parts) pickerDraft.value = formatTime(parts.hour, parts.minute)\n else {\n // Opening on the current time when none is set. Reading the clock is safe here:\n // this runs from a handler, hence in a browser, never during a render — and it is\n // why the picker itself never reads it, so that it stays identical on both sides\n // of hydration. The instant is taken ONCE: two reads could straddle a minute.\n const now = new Date()\n const wanted = snapMinute(now.getMinutes(), props.minuteStep)\n // Pulled to the nearest time the restrictions allow, so that the panel never opens\n // on one the reader would be refused. Null is a set of restrictions that allows\n // nothing at all, which the warning above has already reported.\n pickerDraft.value =\n nearestAllowedTime(now.getHours(), wanted, limits.value) ??\n formatTime(now.getHours(), wanted)\n }\n pickerRef.value?.reset()\n },\n onClose: () => {\n pickerRef.value?.reset()\n },\n})\n\n/** OK: the ONE route by which the picker's draft becomes the value. */\nfunction confirm() {\n if (pickerDraft.value) model.value = pickerDraft.value\n closeAndFocus()\n}\n\n/** Cancel: the draft is simply dropped with the panel. */\nconst cancel = closeAndFocus\n\n/*\n * Emptying the value, called by the field as it emits its clear event. The focus is taken\n * through `focusField`, whose note says why that stops the panel reopening.\n */\nfunction clearValue() {\n model.value = null\n // The typed text is emptied EXPLICITLY: when the value was already empty, nothing\n // changes, and the guard that keeps the field and the value from chasing each other\n // would leave the text where it was.\n if (typing.value) writeField('')\n focusField()\n emit('clear')\n}\n\n// Escape and the focus leaving both close WITHOUT committing, which is the picker's Cancel\n// behaviour: closing never writes the value, and only OK does.\n\nconst m = useMessages()\n\n// @core\n/**\n * Saying no to a time that is not one the restrictions allow.\n *\n * The value is committed all the same — the field shows what was typed, and a consumer\n * bound to it reads the same thing — and the refusal is carried by the control's OWN\n * validity, which is what turns the field red through `:user-invalid` and what stops a\n * form being submitted with it. That is the design system's route for a rule of its own\n * making (`useTextLimit`), never an event of its own invention.\n *\n * It is written unconditionally rather than only while typing: the browser BARS a\n * read-only control from constraint validation, so the read-only form settles it without\n * a branch here. The list form never reaches this at all, having no control of this\n * component's own to write on.\n */\nwatchEffect(\n () => {\n const el = fieldEl.value\n if (!el) return\n el.setCustomValidity(valueAllowed.value ? '' : m.value.timeInput.unavailable)\n },\n { flush: 'post' },\n)\n\n/* From here on: everything the typed field needs. */\n\n/*\n * The mask machinery — the text being typed, the bridge to the value, the reformatting\n * that preserves the caret, the commit and the silent revert — is shared with VDateInput\n * and lives in `useMaskedField`.\n *\n * The time vocabulary is the simpler of the two: four digits, and a separator that is the\n * SAME in every language. That is why the caret can be computed outright here, where a\n * date has to look for a separator it cannot predict; and why nothing distinguishes a\n * final commit from a live one, there being no equivalent of expanding a two-digit year.\n */\nconst {\n draft,\n fieldModel,\n writeField,\n commitOrRevert,\n onFieldInput,\n onKeydown: onMaskKeydown,\n onPaste,\n} = useMaskedField({\n fieldEl,\n typing: () => typing.value,\n displayText: () => displayText.value,\n readValue: () => model.value,\n writeValue: (time) => {\n model.value = time\n },\n maxDigits: () => 4,\n format: formatTimeMask,\n caret: (_text, digitsBefore, inserting) => timeCaret(digitsBefore, inserting),\n parse: (text) => parseTimeMask(text, resolvedFormat.value, currentMeridiem.value),\n toMask: (time) => timeToMask(time, resolvedFormat.value),\n})\n\n// @keyboard — what the mask's keys mean for a time: typing anything that is not a digit,\n// the separator included so that \"9:30\" can be typed exactly as it reads, completes the\n// hour with a leading zero and moves on to the minutes; the down arrow is the one explicit\n// way from the field into the picker.\nfunction onFieldKeydown(event: KeyboardEvent) {\n onMaskKeydown(event, {\n onSeparator: (el) => {\n const digits = digitsOf(el.value)\n if (digits.length === 1) writeField(formatTimeMask(`0${digits}`), 3)\n },\n onArrowDown: () => {\n if (!open.value) return false\n focusInPanel()\n return true\n },\n onEnter: closeAndFocus,\n })\n}\n\n/**\n * Pasting. A time recognizable as a whole is adopted as it stands — and a pasted 24-hour\n * time is read as one whatever clock is on display: \"19:05\" means seven in the evening\n * even in a field showing a 12-hour clock. Anything else contributes its digits alone.\n */\nfunction onFieldPaste(event: ClipboardEvent) {\n onPaste(event, (pasted) => (isValidTime(pasted) ? pasted : null))\n}\n\n/*\n * TRAP — the list form must not reach these two at all, and being panel-less is not\n * enough to keep it out. `useFieldPanel.onKeydown` cancels the default on Enter BEFORE\n * asking whether a panel can open, so an Enter travelling up from the VCombobox would\n * stop a surrounding form from being submitted with nothing to show for it.\n */\nfunction onRootKeydown(event: KeyboardEvent) {\n if (!isList.value) onKeydown(event)\n}\n\nfunction onRootFocusout(event: FocusEvent) {\n if (!isList.value) onFocusout(event)\n}\n\n/*\n * From here on: everything the list form needs.\n *\n * The list is a VCombobox, so the whole ARIA listbox pattern — the panel, the highlight,\n * the scroll onto the chosen row, the commit and the dismissal — comes from there and\n * NONE of it is written here. What is left is the three things a combobox cannot know\n * about times: which rows to offer, how a search matches one, and the fact that this\n * component's value is a nullable string where a combobox's is a plain one.\n */\n\n/**\n * The rows the step and the restrictions offer, which the value does not enter into: a\n * change of value must not format up to 1440 times again.\n */\nconst baseOptions = computed<TimeOption[]>(() => {\n if (!isList.value) return []\n return timeList(props.minuteStep, resolvedLocale.value, resolvedFormat.value).filter((row) => {\n // A list is READ before it is chosen from, so a time it may not take has no reason to be\n // in it, which is the picker's own rule for its numerals.\n const parts = parseTime(row.value)\n return !parts || isTimeAllowed(parts.hour, parts.minute, limits.value)\n })\n})\n\n/**\n * The rows on offer.\n *\n * A value that is not ON the step is added to them, in order: it is a real value, so the\n * field has to be able to write it out and the panel to open on it. Without it the field\n * would fall back to showing the raw canonical string — \"09:07\" in a field that spells\n * every other row \"9:07 AM\" — since a combobox labels a value through the option carrying\n * it.\n */\nconst options = computed<TimeOption[]>(() => {\n const rows = baseOptions.value\n const current = model.value\n if (!isValidTime(current) || rows.some((row) => row.value === current)) return rows\n const at = rows.findIndex((row) => row.value > current)\n const extra: TimeOption = {\n value: current,\n label: formatTimeDisplay(current, resolvedLocale.value, resolvedFormat.value),\n }\n return at < 0 ? [...rows, extra] : [...rows.slice(0, at), extra, ...rows.slice(at)]\n})\n\n/**\n * The search, which accepts the digit run as well as the words — \"930\" finds 9:30, the way\n * the typed form's mask reads what it is given. The rule itself is pure and lives in\n * `./search`, which says why it is not in `utils/time.ts`.\n */\nconst matchTime = (option: ComboboxOption, query: string) =>\n timeMatches(option.label, String(option.value), query)\n\n/**\n * The bridge to VCombobox, whose value is a plain string where this component's is\n * nullable: an emptied combobox says so with an empty string, which is not a time.\n */\nconst listModel = computed<string | string[]>({\n get: () => model.value ?? '',\n set: (value) => {\n model.value = typeof value === 'string' && value ? value : null\n },\n})\n\n/*\n * The clear cross and the icon at the end of the field, for the two forms this component\n * builds itself; the list form gets both from VCombobox.\n *\n * The cross is the field's own, which renders it BEFORE the end icon rather than in its\n * place — the convention every field in the design system follows.\n *\n * Whether the cross is shown has to be answered explicitly here: outside the typed mode\n * the field is read-only and would hide it, while the value comes from the panel. The\n * `readonly` PROP is the one case where that default answer was right: frozen, the field\n * offers no route to a new value, so it offers no route to none either.\n */\nconst clearVisible = computed(() =>\n canClear(props, resolvedDisabled.value, hasValue.value || (typing.value && !!draft.value)),\n)\nconst endIcon = computed<IconSource | undefined>(() =>\n hasPanel.value ? props.pickerIcon : undefined,\n)\n// @a11y @devwarn\n/*\n * TRAP — the LABEL is passed at all times, even when no icon is rendered at all.\n *\n * The helper detecting a click handler on an icon warns AT SETUP if one is attached\n * without a label, and it has no way of knowing whether an icon exists. Since the\n * listener here is attached permanently and that detection is static, making the label\n * conditional would produce a false warning every time a typed field without a picker is\n * mounted.\n */\nconst endIconLabel = computed(() => props.pickerIconLabel ?? m.value.timeInput.openPicker)\nconst resolvedClearLabel = computed(() => props.clearLabel ?? m.value.timeInput.clear)\n\n/*\n * The three of them go through whichever field is on screen: in the list form there is no\n * VInput of this component's own, the VCombobox owning the one the reader sees. Exposing\n * the same trio as the other fields is what keeps a consumer's `ref` working whatever the\n * mode, which is a runtime prop they may well be binding.\n */\nconst field = computed(() => (isList.value ? listRef.value : inputRef.value))\n\ndefineExpose({\n /** Moves the focus to the text field. */\n focus: (options?: FocusOptions) => field.value?.focus(options),\n /** Selects what the field is showing. */\n select: () => field.value?.select(),\n /** The real `<input>` behind the field, for what neither of the two above covers. */\n el: computed(() => field.value?.el ?? null),\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-time-input\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :data-open=\"open ? '' : undefined\"\n :data-mode=\"resolvedMode\"\n @focusout=\"onRootFocusout\"\n @keydown=\"onRootKeydown\"\n >\n <!-- The list form is a VCombobox and nothing else: a list one picks a value from and\n narrows by typing IS the combobox pattern, down to the chevron, the clear cross\n and the dismissal. All this component adds is the three things a combobox cannot\n know about times — the rows, the search, and a value that may be nothing. -->\n <VCombobox\n v-if=\"isList\"\n ref=\"listRef\"\n v-model=\"listModel\"\n v-bind=\"fieldAttrs\"\n :options=\"options\"\n :filter=\"matchTime\"\n :label=\"label\"\n :hint=\"hint\"\n :placeholder=\"placeholder\"\n :size=\"resolvedSize\"\n :compact=\"resolvedCompact\"\n :disabled=\"resolvedDisabled\"\n :readonly=\"readonly\"\n :invalid=\"invalid\"\n :icon-start=\"iconStart\"\n :icon-start-label=\"iconStartLabel\"\n :clearable=\"clearable\"\n :clear-label=\"clearLabel\"\n :loading=\"loading\"\n :placement=\"placement\"\n @clear=\"emit('clear')\"\n />\n\n <div v-else class=\"v-time-input-control\" @click=\"onControlClick\">\n <!-- The field is declared a combobox rather than left as the plain text box it\n implicitly is, because a text box may not carry the attribute saying whether\n something is expanded — the same reasoning as in VDateInput. -->\n <VInput\n ref=\"inputRef\"\n v-model=\"fieldModel\"\n :inputmode=\"typing ? 'numeric' : undefined\"\n :autocomplete=\"typing ? 'off' : undefined\"\n v-bind=\"fieldAttrs\"\n :readonly=\"!typing || readonly\"\n :label=\"label\"\n :hint=\"hint\"\n :placeholder=\"placeholder ?? (typing ? m.timeInput.maskPlaceholder : undefined)\"\n :size=\"resolvedSize\"\n :compact=\"resolvedCompact\"\n :disabled=\"resolvedDisabled\"\n :invalid=\"invalid\"\n :clearable=\"clearable\"\n :clear-visible=\"clearVisible\"\n :clear-label=\"resolvedClearLabel\"\n :icon-start=\"iconStart\"\n :icon-start-label=\"iconStartLabel\"\n :loading=\"loading\"\n :loading-label=\"loadingLabel\"\n :icon-end=\"endIcon\"\n :icon-end-label=\"endIconLabel\"\n :role=\"hasPanel ? 'combobox' : undefined\"\n :aria-haspopup=\"hasPanel ? 'dialog' : undefined\"\n :aria-expanded=\"hasPanel ? open : undefined\"\n :aria-controls=\"hasPanel ? panelId : undefined\"\n @click:icon-end=\"toggleFromIcon\"\n @clear=\"clearValue\"\n @focus=\"onFieldFocus\"\n @input=\"onFieldInput\"\n @change=\"commitOrRevert\"\n @blur=\"commitOrRevert\"\n @keydown=\"onFieldKeydown\"\n @paste=\"onFieldPaste\"\n >\n <!-- The AM/PM button sits beside the value it qualifies, before the controls that\n clear the field and open the panel. It carries `.v-input-action`, which is\n what the field's own buttons wear AND what tells the shell that a click here\n is that button's business rather than a reason to open the panel.\n\n The rule after it separates what acts on the VALUE from what acts on the\n FIELD. It is dropped when nothing follows it, or it would trail off the end\n of an otherwise bare field. -->\n <template v-if=\"$slots.start\" #start><slot name=\"start\" /></template>\n <template v-if=\"hasMeridiem || $slots['value-end']\" #value-end>\n <button\n v-if=\"hasMeridiem\"\n type=\"button\"\n class=\"v-input-action v-field-action v-time-input-meridiem\"\n :disabled=\"resolvedDisabled\"\n :aria-label=\"\n m.timeInput.meridiemValue(\n currentMeridiem === 'PM' ? m.timePicker.pm : m.timePicker.am,\n )\n \"\n @click=\"toggleMeridiem\"\n >\n {{ currentMeridiem === 'PM' ? m.timePicker.pm : m.timePicker.am }}\n </button>\n <VSeparator\n v-if=\"hasMeridiem && (clearVisible || endIcon)\"\n orientation=\"vertical\"\n class=\"v-time-input-divider\"\n />\n <slot name=\"value-end\" />\n </template>\n </VInput>\n </div>\n\n <!-- With no panel rendered there is nothing to hold a reference to, and the open state\n — which is fed by the panel's own events — can no longer become true. The absence\n of a panel is therefore self-enforcing. -->\n <VPopover\n v-if=\"hasPanel\"\n :id=\"panelId\"\n ref=\"panelRef\"\n v-model:open=\"open\"\n mode=\"manual\"\n anchor=\"--time-input-anchor\"\n :placement=\"placement\"\n role=\"dialog\"\n class=\"v-time-input-panel\"\n :aria-label=\"label ?? m.timeInput.pickerLabel\"\n @mousedown=\"onPanelMousedown\"\n >\n <!-- The picker works on the DRAFT and not on the value, which is what makes OK the\n only way in: Cancel, Escape and the focus leaving simply drop it. The two\n actions are handed to its own footer slot, since they belong to this panel\n rather than to a clock shown on its own. -->\n <VTimePicker\n ref=\"pickerRef\"\n v-model=\"pickerDraft\"\n :format=\"resolvedFormat\"\n :locale=\"resolvedLocale\"\n :minute-step=\"minuteStep\"\n :min=\"min\"\n :max=\"max\"\n :allowed-hours=\"allowedHours\"\n :allowed-minutes=\"allowedMinutes\"\n @confirm=\"confirm\"\n >\n <!-- The slot REPLACES the two buttons rather than sitting beside them, so it is\n handed both actions: the clock writes a draft, and without `confirm` a footer\n of one's own could never commit it — the value would become unchangeable\n through the panel. The `remove` of VCombobox's `#chip` slot exists for the\n same reason. -->\n <template #footer>\n <slot name=\"footer\" :confirm=\"confirm\" :cancel=\"cancel\" :close=\"cancel\">\n <VButton variant=\"ghost\" tone=\"neutral\" @click=\"cancel\">{{ m.common.cancel }}</VButton>\n <VButton @click=\"confirm\">{{ m.common.confirm }}</VButton>\n </slot>\n </template>\n </VTimePicker>\n </VPopover>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-time-input {\n /* Confines the anchor name to this instance. It is declared on the root because that\n is the common ancestor of the field and the panel. */\n anchor-scope: --time-input-anchor;\n display: block;\n width: 100%;\n font-family: var(--vectis-text-family);\n }\n\n .v-time-input-control {\n cursor: pointer;\n }\n\n /* The anchor is the FIELD's box and not this wrapper's, which also holds the label and\n the hint: anchored to the wrapper, the panel opens a hint's height below the field, and\n a label's height above it once there is no room below and `flip-block` turns it over.\n Against the field it covers whichever of the two it lands on, which is what a panel\n belonging to a control is supposed to do.\n\n TRAP — the selector has to start at the wrapper and never at the root. In `list` mode\n that wrapper is not rendered at all and the field on screen belongs to a VCombobox,\n which names it `--combobox-anchor` from its own sheet at the same (0,2,0): written\n `.v-time-input .v-input-field`, this rule would put a SECOND `anchor-name` on that very\n element, and which of the two survived would be decided by the order the consumer's\n bundler gave the two sheets. The list's panel would then lose its anchor and paint\n itself at the viewport origin, with nothing in the console to say why. */\n .v-time-input-control .v-input-field {\n anchor-name: --time-input-anchor;\n }\n\n /* A field one types into shows the text cursor rather than the pointer a clickable\n control shows. Its figures are also given equal widths: with proportional ones the\n text shifts as digits are typed, and the caret appears to jitter. */\n .v-time-input[data-mode='input'] .v-time-input-control {\n cursor: text;\n }\n\n .v-time-input[data-mode='input'] .v-input-control {\n font-variant-numeric: tabular-nums;\n }\n\n /* The AM/PM button wears the field's own action recipe — the box, the muted colour that\n lights up on hover, the focus ring and the disabled state all come from\n `.v-field-action` — and changes only what a WORD needs that a glyph does not: room to\n be read, and the field's type rather than the browser's default button font.\n\n The floor keeps the hit target at the size of its neighbours, so the two spellings do\n not resize the field between them.\n\n The padding is DERIVED and not picked from the spacing scale: `.v-field-action` carries\n a negative inline margin that cancels the slack around a glyph inside its box, so that\n the row's gap is measured from the INK. A word fills its own box, so it has to put that\n slack back or it would sit a couple of pixels off the rhythm its neighbours keep —\n which reads as an asymmetry around the rule, whose two sides are otherwise identical.\n\n The selector compounds the two classes because they are the same specificity in two\n different sheets, and which one the consumer's bundler puts last is not ours to\n decide. */\n .v-field-action.v-time-input-meridiem {\n inline-size: auto;\n min-inline-size: var(--control-action-size);\n padding-inline: calc((var(--control-action-size) - var(--vectis-icon-size)) / 2);\n font: inherit;\n }\n\n /* The rule between the AM/PM button and the field's own controls, which is what keeps a\n word from reading as a third icon in the row.\n\n A vertical VSeparator stretches to its flex line by contract, which here is the full\n height of the field: it would meet the border at both ends and read as a division of\n the box rather than a break in the row. It is brought back to the height of the\n buttons on either side of it.\n\n `align-self` and the height are declared against the ORIENTATION branch, which is the\n one that stretches, and beating it takes the extra class: the compound alone would tie\n with it. */\n .v-separator[data-orientation='vertical'].v-time-input-divider {\n align-self: center;\n block-size: var(--control-action-size);\n }\n\n /* The anchoring and the panel's surface both come from VPopover. The panel's OWN padding\n is cancelled: VTimePicker pads itself, as VDatePicker does, so both pickers keep the\n same room on a page and in a panel. The picker brings its own layout too — the gap\n between its parts and the centring of the face — so nothing of that is declared here.\n\n TRAP — NO `display` here, and that is not an omission. The column layout comes from\n `.v-panel`, which is (0,1,0) and therefore loses to `.v-overlay:not(:popover-open)`, the\n guard that hides a closed popover. This selector is (0,2,0): declaring a display on it\n would TIE with that guard, and a tie between two sheets is settled by whichever the\n consumer's bundler put last. The symptom when the component wins is silent and nasty —\n the closed panel keeps its box, invisible at `opacity: 0` and fixed over the page, and\n swallows every click that lands on it.\n\n The selector compounds two classes VPopover puts on the same element, because the\n padding is also declared by the shared panel class: at equal specificity the winner\n would be whichever sheet the consumer's bundler put last. The size attribute cannot\n serve that purpose here — the picker's panel carries none. */\n .v-popover-panel.v-time-input-panel {\n width: max-content;\n padding: 0;\n color: var(--vectis-color-text);\n }\n\n /* The list form is a VCombobox and takes everything from it — this is the one\n declaration that does not belong to a combobox in general.\n\n A column of times read down the panel is a column of FIGURES, and proportional ones\n slide the colon from row to row. The selector is (0,2,0) against the panel's own\n (0,1,0), so it wins on specificity and not on the order the consumer's bundler\n happens to give the two sheets. */\n .v-time-input[data-mode='list'] .v-combobox-panel {\n font-variant-numeric: tabular-nums;\n }\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyEA,IAAM,IAAyB;GAAC;GAAU;GAAS;EAAM,GAoHnD,IAAQ,GAqCR,IAAQ,GAA0B,GAAA,YAAkB,GAEpD,IAAO,GAoCP,EAAE,eAAW,eAAW,uBAAmB,GAAa,GAIxD,KAAiB,IAAmB,MAAU,EAAK,oBAAoB,CAAK,CAAC,GAG7E,IAAa,SAAgB;GAAE,GAAG,GAAe;GAAO,GAAG;EAAe,EAAE,GAE5E,IAAS,EAAwB,IAAI,GACrC,KAAW,EAA0C,IAAI,GACzD,IAAW,EAAwC,IAAI,GACvD,IAAU,EAA2C,IAAI,GACzD,IAAY,EAA6C,IAAI,GAG7D,KAAU,QAAwC,EAAS,OAAO,MAAM,IAAI,GAE5E,IAAe,QAA8B;GAIjD,IAAM,IAAO,EAAM,QAAQ;GAC3B,OAAO,EAAM,SAAS,CAAI,IAAI,IAAO;EACvC,CAAC,GACK,IAAS,QAAe,EAAa,UAAU,OAAO,GACtD,IAAS,QAAe,EAAa,UAAU,MAAM,GAOrD,IAAW,QACT,CAAC,EAAM,aAAa,EAAa,UAAU,YAAa,EAAO,SAAS,EAAM,WACtF,GAEM,IAAiB,QAAwB,EAAM,MAAM,GAErD,IAAiB,QACf,EAAM,UAAU,GAAa,EAAe,KAAK,CACzD,GAQM,IAAS,QAAe,GAAc,CAAK,CAAC,GAG5C,KAAe,QAAe;GAClC,IAAM,IAAQ,EAAW;GACzB,OAAO,CAAC,KAAS,GAAc,EAAM,MAAM,EAAM,QAAQ,EAAO,KAAK;EACvE,CAAC,GAwDK,IAAc,EAAmB,IAAI,GAErC,IAAa,QAAe,EAAU,EAAM,KAAK,CAAC,GAGlD,IAAkB,EAAc,IAAI,GAOpC,IAAkB,QAAyB;GAI/C,IAAM,IAAQ,EAAK,SAAS,EAAS,QAAQ,EAAU,EAAY,KAAK,IAAI,EAAW;GACvF,OAAO,IAAQ,GAAM,EAAM,IAAI,CAAC,CAAC,WAAW,EAAgB;EAC9D,CAAC;EAMD,SAAS,KAAiB;GACxB,IAAM,IAAiB,EAAgB,UAAU,OAAO,OAAO;GAC/D,EAAgB,QAAQ;GAGxB,IAAM,IAAQ,EAAa,EAAM,OAAO,CAAI;GAC5C,AAAI,MAAO,EAAM,QAAQ;GAGzB,IAAM,IAAU,EAAK,SAAS,EAAS,QAAQ,EAAa,EAAY,OAAO,CAAI,IAAI;GACvF,AAAI,MAAS,EAAY,QAAQ;EACnC;EAOA,IAAM,IAAc,QACZ,EAAO,SAAS,CAAC,EAAM,YAAY,EAAe,UAAU,KACpE,GAEM,KAAW,QAAe,CAAC,CAAC,EAAW,KAAK,GAC5C,KAAc,QAClB,EAAM,QAAQ,GAAkB,EAAM,OAAO,EAAe,OAAO,EAAe,KAAK,IAAI,EAC7F;EAMA,SAAS,IAAe;GACtB,EAAU,OAAO,MAAM;EACzB;EAOA,IAAM,EACJ,MAAM,GACN,SAAS,GACT,UAAU,MACR,GAAgB,GALN,GAAO,IAAe,IAKT,CAAK,GAK1B,EACJ,SACA,aACA,kBACA,gBACA,kBACA,oBACA,oBACA,gBACA,eACA,yBACE,GAAc;GAChB;GACA;GACA,OAAO;GAIP,gBAAgB,EAAiB,SAAS,CAAC,EAAS;GACpD;GAGA,mBAAmB,EAAO;GAC1B,cAAc;IAEZ,IAAI,CAAC,EAAS,OAAO;IACrB,IAAM,IAAQ,EAAW;IACzB,IAAI,GAAO,EAAY,QAAQ,GAAW,EAAM,MAAM,EAAM,MAAM;SAC7D;KAKH,IAAM,oBAAM,IAAI,KAAK,GACf,IAAS,GAAW,EAAI,WAAW,GAAG,EAAM,UAAU;KAI5D,EAAY,QACV,GAAmB,EAAI,SAAS,GAAG,GAAQ,EAAO,KAAK,KACvD,GAAW,EAAI,SAAS,GAAG,CAAM;IACrC;IACA,EAAU,OAAO,MAAM;GACzB;GACA,eAAe;IACb,EAAU,OAAO,MAAM;GACzB;EACF,CAAC;EAGD,SAAS,IAAU;GAEjB,AADI,EAAY,UAAO,EAAM,QAAQ,EAAY,QACjD,EAAc;EAChB;EAGA,IAAM,IAAS;EAMf,SAAS,KAAa;GAOpB,AANA,EAAM,QAAQ,MAIV,EAAO,SAAO,GAAW,EAAE,GAC/B,GAAW,GACX,EAAK,OAAO;EACd;EAKA,IAAM,IAAI,EAAY;EAiBtB,SACQ;GACJ,IAAM,IAAK,GAAQ;GACd,KACL,EAAG,kBAAkB,GAAa,QAAQ,KAAK,EAAE,MAAM,UAAU,WAAW;EAC9E,GACA,EAAE,OAAO,OAAO,CAClB;EAcA,IAAM,EACJ,WACA,eACA,gBACA,oBACA,kBACA,WAAW,IACX,gBACE,GAAe;GACjB;GACA,cAAc,EAAO;GACrB,mBAAmB,GAAY;GAC/B,iBAAiB,EAAM;GACvB,aAAa,MAAS;IACpB,EAAM,QAAQ;GAChB;GACA,iBAAiB;GACjB,QAAQ;GACR,QAAQ,GAAO,GAAc,MAAc,GAAU,GAAc,CAAS;GAC5E,QAAQ,MAAS,GAAc,GAAM,EAAe,OAAO,EAAgB,KAAK;GAChF,SAAS,MAAS,GAAW,GAAM,EAAe,KAAK;EACzD,CAAC;EAMD,SAAS,GAAe,GAAsB;GAC5C,GAAc,GAAO;IACnB,cAAc,MAAO;KACnB,IAAM,IAAS,GAAS,EAAG,KAAK;KAChC,AAAI,EAAO,WAAW,KAAG,GAAW,GAAe,IAAI,GAAQ,GAAG,CAAC;IACrE;IACA,mBACO,EAAK,SACV,EAAa,GACN,MAFiB;IAI1B,SAAS;GACX,CAAC;EACH;EAOA,SAAS,GAAa,GAAuB;GAC3C,GAAQ,IAAQ,MAAY,GAAY,CAAM,IAAI,IAAS,IAAK;EAClE;EAQA,SAAS,GAAc,GAAsB;GAC3C,AAAK,EAAO,SAAO,GAAU,CAAK;EACpC;EAEA,SAAS,GAAe,GAAmB;GACzC,AAAK,EAAO,SAAO,GAAW,CAAK;EACrC;EAgBA,IAAM,KAAc,QACb,EAAO,QACL,GAAS,EAAM,YAAY,EAAe,OAAO,EAAe,KAAK,CAAC,CAAC,QAAQ,MAAQ;GAG5F,IAAM,IAAQ,EAAU,EAAI,KAAK;GACjC,OAAO,CAAC,KAAS,GAAc,EAAM,MAAM,EAAM,QAAQ,EAAO,KAAK;EACvE,CAAC,IANyB,CAAC,CAO5B,GAWK,KAAU,QAA6B;GAC3C,IAAM,IAAO,GAAY,OACnB,IAAU,EAAM;GACtB,IAAI,CAAC,GAAY,CAAO,KAAK,EAAK,MAAM,MAAQ,EAAI,UAAU,CAAO,GAAG,OAAO;GAC/E,IAAM,IAAK,EAAK,WAAW,MAAQ,EAAI,QAAQ,CAAO,GAChD,IAAoB;IACxB,OAAO;IACP,OAAO,GAAkB,GAAS,EAAe,OAAO,EAAe,KAAK;GAC9E;GACA,OAAO,IAAK,IAAI,CAAC,GAAG,GAAM,CAAK,IAAI;IAAC,GAAG,EAAK,MAAM,GAAG,CAAE;IAAG;IAAO,GAAG,EAAK,MAAM,CAAE;GAAC;EACpF,CAAC,GAOK,MAAa,GAAwB,MACzC,GAAY,EAAO,OAAO,OAAO,EAAO,KAAK,GAAG,CAAK,GAMjD,KAAY,EAA4B;GAC5C,WAAW,EAAM,SAAS;GAC1B,MAAM,MAAU;IACd,EAAM,QAAQ,OAAO,KAAU,YAAY,IAAQ,IAAQ;GAC7D;EACF,CAAC,GAcK,KAAe,QACnB,GAAS,GAAO,EAAiB,OAAO,GAAS,SAAU,EAAO,SAAS,CAAC,CAAC,GAAM,KAAM,CAC3F,GACM,KAAU,QACd,EAAS,QAAQ,EAAM,aAAa,KAAA,CACtC,GAWM,KAAe,QAAe,EAAM,mBAAmB,EAAE,MAAM,UAAU,UAAU,GACnF,KAAqB,QAAe,EAAM,cAAc,EAAE,MAAM,UAAU,KAAK,GAQ/E,IAAQ,QAAgB,EAAO,QAAQ,EAAQ,QAAQ,EAAS,KAAM;SAE5E,EAAa;GAEX,QAAQ,MAA2B,EAAM,OAAO,MAAM,CAAO;GAE7D,cAAc,EAAM,OAAO,OAAO;GAElC,IAAI,QAAe,EAAM,OAAO,MAAM,IAAI;EAC5C,CAAC,mBAIC,EA6JM,OAAA;YA5JA;GAAJ,KAAI;GACJ,OAAK,GAAA,CAAC,gBACE,EAAA,EAAA,CAAS,CAAA;GAChB,OAAK,GAAE,EAAA,EAAA,CAAS;GAChB,aAAW,EAAA,CAAA,IAAI,KAAQ,KAAA;GACvB,aAAW,EAAA;GACX,YAAU;GACV,WAAS;MAOF,EAAA,SAAA,EAAA,GADR,EAsBE,IAtBF,GAsBE;;YApBI;GAAJ,KAAI;eACK,GAAA;6CAAS,QAAA;KACV,EAAA,OAAU;GACjB,SAAS,GAAA;GACT,QAAQ;GACR,OAAO,EAAA;GACP,MAAM,EAAA;GACN,aAAa,EAAA;GACb,MAAM,EAAA,CAAA;GACN,SAAS,EAAA,CAAA;GACT,UAAU,EAAA,CAAA;GACV,UAAU,EAAA;GACV,SAAS,EAAA;GACT,cAAY,EAAA;GACZ,oBAAkB,EAAA;GAClB,WAAW,EAAA;GACX,eAAa,EAAA;GACb,SAAS,EAAA;GACT,WAAW,EAAA;GACX,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,OAAA;;;;;;;;;;;;;;;;;;cAGd,EAwEM,OAAA;;GAxEM,OAAM;GAAwB,SAAK,AAAA,EAAA,QAAA,GAAA,MAAE,EAAA,EAAA,KAAA,EAAA,EAAA,CAAA,CAAA,GAAA,CAAA;MAI/C,EAmES,IAnET,GAmES;YAlEH;GAAJ,KAAI;eACK,EAAA,CAAA;mDAAU,QAAA,IAAA;GAClB,WAAW,EAAA,QAAM,YAAe,KAAA;GAChC,cAAc,EAAA,QAAM,QAAW,KAAA;KACxB,EAAA,OAAU;GACjB,UAAQ,CAAG,EAAA,SAAU,EAAA;GACrB,OAAO,EAAA;GACP,MAAM,EAAA;GACN,aAAa,EAAA,gBAAgB,EAAA,QAAS,EAAA,CAAA,CAAC,CAAC,UAAU,kBAAkB,KAAA;GACpE,MAAM,EAAA,CAAA;GACN,SAAS,EAAA,CAAA;GACT,UAAU,EAAA,CAAA;GACV,SAAS,EAAA;GACT,WAAW,EAAA;GACX,iBAAe,GAAA;GACf,eAAa,GAAA;GACb,cAAY,EAAA;GACZ,oBAAkB,EAAA;GAClB,SAAS,EAAA;GACT,iBAAe,EAAA;GACf,YAAU,GAAA;GACV,kBAAgB,GAAA;GAChB,MAAM,EAAA,QAAQ,aAAgB,KAAA;GAC9B,iBAAe,EAAA,QAAQ,WAAc,KAAA;GACrC,iBAAe,EAAA,QAAW,EAAA,CAAA,IAAO,KAAA;GACjC,iBAAe,EAAA,QAAW,EAAA,EAAA,IAAU,KAAA;GACpC,mBAAgB,EAAA,EAAA;GAChB,SAAO;GACP,SAAO,EAAA,EAAA;GACP,SAAO,EAAA,EAAA;GACP,UAAQ,EAAA,EAAA;GACR,QAAM,EAAA,EAAA;GACN,WAAS;GACT,SAAO;oBAUQA,EAAAA,OAAO,QAAA;SAAQ;eAA2B,CAArB,EAAqB,EAAA,QAAA,OAAA,CAAA,CAAA;;cAC1C,EAAA,SAAeA,EAAAA,OAAM,eAAA;SAAgB;eAwD2B;IAtDtE,EAAA,SAAA,EAAA,GADR,EAaS,UAAA;;KAXP,MAAK;KACL,OAAM;KACL,UAAU,EAAA,CAAA;KACV,cAA2B,EAAA,CAAA,CAAC,CAAC,UAAU,cAA+B,EAAA,UAAe,OAAY,EAAA,CAAA,CAAC,CAAC,WAAW,KAAK,EAAA,CAAA,CAAC,CAAC,WAAW,EAAA;KAKhI,SAAO;SAEL,EAAA,UAAe,OAAY,EAAA,CAAA,CAAC,CAAC,WAAW,KAAK,EAAA,CAAA,CAAC,CAAC,WAAW,EAAE,GAAA,GAAA,EAAA,KAAA,EAAA,IAAA,EAAA;IAGzD,EAAA,UAAgB,GAAA,SAAgB,GAAA,UAAA,EAAA,GADxC,EAIE,GAAA;;KAFA,aAAY;KACZ,OAAM;;IAER,EAAyB,EAAA,QAAA,WAAA;;;qWASvB,EAAA,SAAA,EAAA,GADR,EAyCW,GAAA;;GAvCR,IAAI,EAAA,EAAA;YACD;GAAJ,KAAI;GACI,MAAM,EAAA,CAAA;6CAAI,QAAA,IAAA;GAClB,MAAK;GACL,QAAO;GACN,WAAW,EAAA;GACZ,MAAK;GACL,OAAM;GACL,cAAY,EAAA,SAAS,EAAA,CAAA,CAAC,CAAC,UAAU;GACjC,aAAW,EAAA,EAAA;;oBA6BE,CAvBd,EAuBc,IAAA;aAtBR;IAAJ,KAAI;gBACK,EAAA;6CAAW,QAAA;IACnB,QAAQ,EAAA;IACR,QAAQ,EAAA,CAAA;IACR,eAAa,EAAA;IACb,KAAK,EAAA;IACL,KAAK,EAAA;IACL,iBAAe,EAAA;IACf,mBAAiB,EAAA;IACjB,WAAS;;IAOC,QAAM,QAIR,CAHP,EAGO,EAAA,QAAA,UAAA;KAHuB;KAAU,QAAQ,EAAA,CAAA;KAAS,OAAO,EAAA,CAAA;aAGzD,CAFL,EAAuF,IAAA;KAA9E,SAAQ;KAAQ,MAAK;KAAW,SAAO,EAAA,CAAA;;sBAA6B,CAAA,EAAA,EAAlB,EAAA,CAAA,CAAC,CAAC,OAAO,MAAM,GAAA,CAAA,CAAA,CAAA;;wBAC1E,EAA0D,IAAA,EAAhD,SAAO,EAAO,GAAA;sBAAwB,CAAA,EAAA,EAAnB,EAAA,CAAA,CAAC,CAAC,OAAO,OAAO,GAAA,CAAA,CAAA,CAAA"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Whether a row of times answers to what is being searched for.
3
+ *
4
+ * Two ways in, because a time is typed in two ways. The plain one is a substring of the
5
+ * label, which is what finds "PM" or ":30". The other is the DIGIT RUN: "930" finds 9:30
6
+ * even though the label carries a colon that was never typed. That second route rests on
7
+ * the same premise the typed form is built on, where the whole mask only ever knows the
8
+ * digits and places the separator itself.
9
+ *
10
+ * The digit route is ADDITIVE: it only ever answers what the label route already refused,
11
+ * which is why a plain "30" still returns every half past the hour — that is the substring
12
+ * search doing its ordinary job, and it is the least surprising thing a search box can do.
13
+ *
14
+ * Three things keep the digit route precise. It matches a run from its START, never inside
15
+ * it, so "035" does not reach 10:35 through the middle of its digits. Every run it compares
16
+ * against is one the reader could have READ — the label as shown, its hour padded and
17
+ * unpadded (the reader pads or not whatever is on screen), and the canonical 24-hour form,
18
+ * so "2130" reaches a row a 12-hour clock writes "9:30 PM". Nothing is synthesized the
19
+ * other way round: reading a 24-hour label as a 12-hour one would make "930" return half
20
+ * past NINE and half past TWENTY-ONE from the same list. And whatever LETTERS were typed
21
+ * still have to be in the label, so "930pm" leaves the morning behind while "930" keeps
22
+ * both halves of the day.
23
+ */
24
+ export declare function timeMatches(label: string, value: string, query: string): boolean;
@@ -0,0 +1,22 @@
1
+ import { digitsOf as e, normalizeText as t, pad2 as n } from "../../utils/text.js";
2
+ import { parseTime as r } from "../../utils/time.js";
3
+ //#region src/components/VTimeInput/search.ts
4
+ var i = (e) => t(e).replace(/[^a-z]/g, "");
5
+ function a(a, o, s) {
6
+ let c = s.trim();
7
+ if (!c || t(a).includes(t(c))) return !0;
8
+ let l = e(c);
9
+ if (!l) return !1;
10
+ let u = e(a), d = u.slice(-2), f = u.slice(0, -2), p = [
11
+ u,
12
+ f.padStart(2, "0") + d,
13
+ String(Number(f)) + d
14
+ ], m = r(o);
15
+ if (m && p.push(n(m.hour) + n(m.minute)), !p.some((e) => e.startsWith(l))) return !1;
16
+ let h = i(c);
17
+ return !h || i(a).includes(h);
18
+ }
19
+ //#endregion
20
+ export { a as timeMatches };
21
+
22
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","names":[],"sources":["../../../src/components/VTimeInput/search.ts"],"sourcesContent":["// @core\n/**\n * How a search matches a row of VTimeInput's list.\n *\n * It sits in the component's folder rather than in `utils/time.ts` for two reasons. There\n * is one consumer, so it fails that folder's admission rule — the `VCombobox/infiniteScroll`\n * and `VHotkeys/platform` precedent. And `utils/time.ts` is in the import closure of every\n * VCalendar component, through `VCalendar/layout`, which reads `minutesOf` from it: a rule\n * only a time FIELD needs would be weighed on every calendar that never offers a list.\n *\n * Pure, so it is testable without a mount — which matters here, since what makes the rule\n * worth having is the handful of entries it must and must not accept.\n */\nimport { parseTime } from '../../utils/time'\nimport { digitsOf, normalizeText, pad2 } from '../../utils/text'\n\n/** Everything but the letters, so that \"9:30 PM\" and \"pm\" can be compared as words. */\nconst lettersOf = (s: string): string => normalizeText(s).replace(/[^a-z]/g, '')\n\n/**\n * Whether a row of times answers to what is being searched for.\n *\n * Two ways in, because a time is typed in two ways. The plain one is a substring of the\n * label, which is what finds \"PM\" or \":30\". The other is the DIGIT RUN: \"930\" finds 9:30\n * even though the label carries a colon that was never typed. That second route rests on\n * the same premise the typed form is built on, where the whole mask only ever knows the\n * digits and places the separator itself.\n *\n * The digit route is ADDITIVE: it only ever answers what the label route already refused,\n * which is why a plain \"30\" still returns every half past the hour — that is the substring\n * search doing its ordinary job, and it is the least surprising thing a search box can do.\n *\n * Three things keep the digit route precise. It matches a run from its START, never inside\n * it, so \"035\" does not reach 10:35 through the middle of its digits. Every run it compares\n * against is one the reader could have READ — the label as shown, its hour padded and\n * unpadded (the reader pads or not whatever is on screen), and the canonical 24-hour form,\n * so \"2130\" reaches a row a 12-hour clock writes \"9:30 PM\". Nothing is synthesized the\n * other way round: reading a 24-hour label as a 12-hour one would make \"930\" return half\n * past NINE and half past TWENTY-ONE from the same list. And whatever LETTERS were typed\n * still have to be in the label, so \"930pm\" leaves the morning behind while \"930\" keeps\n * both halves of the day.\n */\nexport function timeMatches(label: string, value: string, query: string): boolean {\n const q = query.trim()\n if (!q) return true\n if (normalizeText(label).includes(normalizeText(q))) return true\n\n const needle = digitsOf(q)\n if (!needle) return false\n\n // The minutes are always written on two digits, so what comes before them is the hour as\n // this label spells it — which is what the reader has in front of them.\n const shown = digitsOf(label)\n const minutes = shown.slice(-2)\n const hour = shown.slice(0, -2)\n const runs = [shown, hour.padStart(2, '0') + minutes, String(Number(hour)) + minutes]\n const parts = parseTime(value)\n if (parts) runs.push(pad2(parts.hour) + pad2(parts.minute))\n if (!runs.some((run) => run.startsWith(needle))) return false\n\n const letters = lettersOf(q)\n return !letters || lettersOf(label).includes(letters)\n}\n"],"mappings":";;;AAiBA,IAAM,KAAa,MAAsB,EAAc,CAAC,CAAC,CAAC,QAAQ,WAAW,EAAE;AAyB/E,SAAgB,EAAY,GAAe,GAAe,GAAwB;CAChF,IAAM,IAAI,EAAM,KAAK;CAErB,IADI,CAAC,KACD,EAAc,CAAK,CAAC,CAAC,SAAS,EAAc,CAAC,CAAC,GAAG,OAAO;CAE5D,IAAM,IAAS,EAAS,CAAC;CACzB,IAAI,CAAC,GAAQ,OAAO;CAIpB,IAAM,IAAQ,EAAS,CAAK,GACtB,IAAU,EAAM,MAAM,EAAE,GACxB,IAAO,EAAM,MAAM,GAAG,EAAE,GACxB,IAAO;EAAC;EAAO,EAAK,SAAS,GAAG,GAAG,IAAI;EAAS,OAAO,OAAO,CAAI,CAAC,IAAI;CAAO,GAC9E,IAAQ,EAAU,CAAK;CAE7B,IADI,KAAO,EAAK,KAAK,EAAK,EAAM,IAAI,IAAI,EAAK,EAAM,MAAM,CAAC,GACtD,CAAC,EAAK,MAAM,MAAQ,EAAI,WAAW,CAAM,CAAC,GAAG,OAAO;CAExD,IAAM,IAAU,EAAU,CAAC;CAC3B,OAAO,CAAC,KAAW,EAAU,CAAK,CAAC,CAAC,SAAS,CAAO;AACtD"}
@@ -1,2 +1,2 @@
1
1
  @layer vectis.reset, vectis.tokens, vectis.components, vectis.utilities;
2
- @layer vectis.components{.v-time-picker{gap:var(--vectis-space-4);width:max-content;font-family:var(--vectis-text-family);color:var(--vectis-color-text);flex-direction:column;display:flex}.v-time-picker-header{justify-content:center;align-items:center;gap:var(--vectis-space-3);display:flex}.v-time-picker-time{justify-content:center;align-items:center;gap:var(--vectis-space-2);direction:ltr;display:flex}.v-time-picker-cell[data-size]{width:var(--control-height);font-size:var(--vectis-text-heading-1-size);font-weight:var(--vectis-text-heading-2-weight);font-variant-numeric:tabular-nums}.v-time-picker-sep{font-size:var(--vectis-text-heading-2-size);color:var(--vectis-color-text);-webkit-user-select:none;user-select:none}.v-time-picker-meridiem{flex:none}.v-time-picker-face{inline-size:var(--vectis-control-size-time-picker-dial);block-size:var(--vectis-control-size-time-picker-dial);border-radius:var(--vectis-radius-pill);background:var(--vectis-color-surface-muted);touch-action:none;-webkit-user-select:none;user-select:none;cursor:pointer;--dial-radius:calc(var(--vectis-control-size-time-picker-dial) / 2 - var(--vectis-control-size-time-picker-number) / 2);align-self:center;position:relative}.v-time-picker-face:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:var(--vectis-focus-ring-offset)}.v-time-picker-number{left:calc(50% + var(--dial-radius) * sin(var(--dial-turn) * 1turn));top:calc(50% - var(--dial-radius) * cos(var(--dial-turn) * 1turn));inline-size:var(--vectis-control-size-time-picker-number);block-size:var(--vectis-control-size-time-picker-number);border-radius:var(--vectis-radius-pill);font-size:var(--vectis-text-body-lg-size);color:var(--vectis-color-text);z-index:1;pointer-events:none;place-items:center;display:grid;position:absolute;translate:-50% -50%}.v-time-picker-number[data-ring=inner],.v-time-picker-hand[data-ring=inner]{--dial-radius:calc(var(--vectis-control-size-time-picker-dial) / 2 - var(--vectis-control-size-time-picker-number) * 1.5)}.v-time-picker-number[data-ring=inner]{font-size:var(--vectis-text-body-md-size);color:var(--vectis-color-text-muted)}.v-time-picker-number[data-selected]{color:var(--vectis-color-text-on-accent)}.v-time-picker-hand{left:calc(50% - var(--vectis-control-size-time-picker-hand) / 2);inline-size:var(--vectis-control-size-time-picker-hand);block-size:var(--dial-radius);background:var(--vectis-color-accent);transform-origin:50% 100%;rotate:calc(var(--dial-turn) * 1turn);position:absolute;bottom:50%}.v-time-picker-hand:before{content:"";inline-size:var(--vectis-control-size-time-picker-number);block-size:var(--vectis-control-size-time-picker-number);border-radius:var(--vectis-radius-pill);background:var(--vectis-color-accent);transition:inline-size var(--vectis-duration-fast) var(--vectis-ease-default), block-size var(--vectis-duration-fast) var(--vectis-ease-default);position:absolute;top:0;left:50%;translate:-50% -50%}.v-time-picker-hand[data-minor]:before{inline-size:var(--vectis-control-size-time-picker-hand-minor);block-size:var(--vectis-control-size-time-picker-hand-minor)}.v-time-picker-center{inline-size:var(--vectis-control-size-time-picker-center);block-size:var(--vectis-control-size-time-picker-center);border-radius:var(--vectis-radius-pill);background:var(--vectis-color-accent);position:absolute;top:50%;left:50%;translate:-50% -50%}.v-time-picker-footer{justify-content:flex-end;align-items:center;gap:var(--vectis-space-2);display:flex}@media (prefers-reduced-motion:reduce){.v-time-picker-hand:before{transition:none}}}
2
+ @layer vectis.components{.v-time-picker{gap:var(--vectis-space-4);padding:var(--vectis-space-3);font-family:var(--vectis-text-family);color:var(--vectis-color-text);flex-direction:column;display:inline-flex}.v-time-picker-header{justify-content:center;align-items:center;gap:var(--vectis-space-3);display:flex}.v-time-picker-time{justify-content:center;align-items:center;gap:var(--vectis-space-2);direction:ltr;display:flex}.v-time-picker-cell[data-size]{width:var(--control-height);font-size:var(--vectis-text-heading-1-size);font-weight:var(--vectis-text-heading-2-weight);font-variant-numeric:tabular-nums}.v-time-picker-sep{font-size:var(--vectis-text-heading-2-size);color:var(--vectis-color-text);-webkit-user-select:none;user-select:none}.v-time-picker-meridiem{flex:none}.v-time-picker-face{inline-size:var(--vectis-control-size-time-picker-dial);block-size:var(--vectis-control-size-time-picker-dial);border-radius:var(--vectis-radius-pill);background:var(--vectis-color-surface-muted);touch-action:none;-webkit-user-select:none;user-select:none;cursor:pointer;--dial-radius:calc(var(--vectis-control-size-time-picker-dial) / 2 - var(--vectis-control-size-time-picker-number) / 2);align-self:center;position:relative}.v-time-picker-face:focus-visible{outline:var(--vectis-focus-ring-width) solid var(--vectis-focus-ring-color);outline-offset:var(--vectis-focus-ring-offset)}.v-time-picker-number{left:calc(50% + var(--dial-radius) * sin(var(--dial-turn) * 1turn));top:calc(50% - var(--dial-radius) * cos(var(--dial-turn) * 1turn));inline-size:var(--vectis-control-size-time-picker-number);block-size:var(--vectis-control-size-time-picker-number);border-radius:var(--vectis-radius-pill);font-size:var(--vectis-text-body-lg-size);color:var(--vectis-color-text);z-index:1;pointer-events:none;place-items:center;display:grid;position:absolute;translate:-50% -50%}.v-time-picker-number[data-ring=inner],.v-time-picker-hand[data-ring=inner]{--dial-radius:calc(var(--vectis-control-size-time-picker-dial) / 2 - var(--vectis-control-size-time-picker-number) * 1.5)}.v-time-picker-number[data-ring=inner]{font-size:var(--vectis-text-body-md-size);color:var(--vectis-color-text-muted)}.v-time-picker-number[data-selected]{color:var(--vectis-color-text-on-accent)}.v-time-picker-hand{left:calc(50% - var(--vectis-control-size-time-picker-hand) / 2);inline-size:var(--vectis-control-size-time-picker-hand);block-size:var(--dial-radius);background:var(--vectis-color-accent);transform-origin:50% 100%;rotate:calc(var(--dial-turn) * 1turn);position:absolute;bottom:50%}.v-time-picker-hand:before{content:"";inline-size:var(--vectis-control-size-time-picker-number);block-size:var(--vectis-control-size-time-picker-number);border-radius:var(--vectis-radius-pill);background:var(--vectis-color-accent);transition:inline-size var(--vectis-duration-fast) var(--vectis-ease-default), block-size var(--vectis-duration-fast) var(--vectis-ease-default);position:absolute;top:0;left:50%;translate:-50% -50%}.v-time-picker-hand[data-minor]:before{inline-size:var(--vectis-control-size-time-picker-hand-minor);block-size:var(--vectis-control-size-time-picker-hand-minor)}.v-time-picker-center{inline-size:var(--vectis-control-size-time-picker-center);block-size:var(--vectis-control-size-time-picker-center);border-radius:var(--vectis-radius-pill);background:var(--vectis-color-accent);position:absolute;top:50%;left:50%;translate:-50% -50%}.v-time-picker[data-readonly] .v-time-picker-face{cursor:default}.v-time-picker[data-disabled] .v-time-picker-face{cursor:not-allowed}.v-time-picker[data-disabled] .v-time-picker-hand,.v-time-picker[data-disabled] .v-time-picker-hand:before,.v-time-picker[data-disabled] .v-time-picker-center{background:var(--vectis-color-border-strong)}.v-time-picker[data-disabled] .v-time-picker-sep,.v-time-picker[data-disabled] .v-time-picker-number,.v-time-picker[data-disabled] .v-time-picker-number[data-selected]{color:var(--vectis-color-text-subtle)}.v-time-picker-footer{justify-content:flex-end;align-items:center;gap:var(--vectis-space-2);border-block-start:1px solid var(--vectis-color-border);padding-block-start:var(--vectis-space-2);display:flex}@media (prefers-reduced-motion:reduce){.v-time-picker-hand:before{transition:none}}}
@@ -1,31 +1,6 @@
1
1
  import { HourFormat } from '../../utils/time.js';
2
- /**
3
- * A clock shown directly in the page, for choosing a time. It is the hour-and-minute
4
- * counterpart of VDatePicker: VTimeInput does no more than dress it in a text field and a
5
- * popover, and it is perfectly usable on its own.
6
- *
7
- * It is read in three parts. Two large numerals at the top say what has been chosen so
8
- * far, and switch between adjusting the hour and adjusting the minutes; on a 12-hour clock
9
- * the choice between morning and afternoon sits beside them, since it is read as part of
10
- * that same time. Below them is the face itself, where one points at a time rather than
11
- * stepping through it.
12
- *
13
- * Nothing native covers choosing a value by pointing at an angle, so the JavaScript does
14
- * two things: it turns a point on the face into a time — measuring the face once, then
15
- * pure trigonometry that lives in `utils/time` — and it implements the keyboard of a
16
- * slider, the arrows and the Home, End and Page keys.
17
- *
18
- * Everything one SEES is CSS: the numerals are placed around the circle, and the hand is
19
- * turned, from a single unitless fraction of a turn written inline.
20
- *
21
- * There is exactly ONE focusable element on the face, and it is announced as a slider. The
22
- * numerals are visual markers hidden from screen readers, reached by the pointer's angle
23
- * rather than one cell at a time — which is why the whole spoken value has to be carried
24
- * by that one element.
25
- */
2
+ import { TimePickerAllowed } from './limits.js';
26
3
  export type TimePickerFormat = HourFormat;
27
- /** Which of the two halves of a time is being adjusted. */
28
- export type TimePickerStep = 'hour' | 'minute';
29
4
  interface TimePickerProps {
30
5
  /**
31
6
  * Whether the face shows a 12- or a 24-hour clock. Left out, the reader's language
@@ -38,8 +13,47 @@ interface TimePickerProps {
38
13
  * `undefined` has to stay recognizable for the global locale to have its chance.
39
14
  */
40
15
  locale?: string;
41
- /** The interval the minutes snap to, both when dragging and with the arrow keys. */
16
+ /**
17
+ * The interval the minutes snap to, both when dragging and with the arrow keys. The face
18
+ * prints only the minutes it can reach, so a step of a quarter of an hour marks four.
19
+ */
42
20
  minuteStep?: number;
21
+ /**
22
+ * The earliest time that can be chosen, inclusive, as a canonical 24-hour `'HH:mm'`.
23
+ * What it rules out is LEFT OFF the face, the way `minuteStep` leaves off the minutes
24
+ * it cannot reach: the clock prints what can be chosen and nothing else.
25
+ */
26
+ min?: string;
27
+ /** The latest time that can be chosen, inclusive, written like `min`. */
28
+ max?: string;
29
+ /**
30
+ * Which hours can be chosen: the list of them, or a rule answering for one. The hour
31
+ * handed to a rule is always the 24-hour one, whichever clock is on display, so the
32
+ * same rule holds on both faces. The hours it leaves out are not printed.
33
+ */
34
+ allowedHours?: TimePickerAllowed;
35
+ /**
36
+ * Which minutes can be chosen: the list of them, or a rule answering for one. The
37
+ * minutes it leaves out are not printed.
38
+ */
39
+ allowedMinutes?: TimePickerAllowed;
40
+ /**
41
+ * Makes the whole clock unusable: the hand cannot be moved, the half-day cannot be
42
+ * changed, and everything greys out through the colour tokens.
43
+ */
44
+ disabled?: boolean;
45
+ /**
46
+ * Shows the time without letting it be changed. The face keeps its focus and the two
47
+ * numerals still switch between the hour and the minutes, so the value can be read in
48
+ * full — which is what separates it from `disabled`.
49
+ */
50
+ readonly?: boolean;
51
+ /**
52
+ * The accessible name of the whole clock, its two numerals and its face together. It
53
+ * falls back to the dictionary, and a consumer `aria-label` wins over it. The face
54
+ * keeps its own name, which says whether the hand is on the hour or the minutes.
55
+ */
56
+ label?: string;
43
57
  }
44
58
  type __VLS_Props = TimePickerProps;
45
59
  type __VLS_Slots = {
@@ -69,15 +83,22 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_PublicProps, {
69
83
  */
70
84
  reset: () => void;
71
85
  }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
72
- confirm: () => any;
86
+ confirm: (value: string | null) => any;
73
87
  "update:modelValue": (value: string | null) => any;
74
88
  }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
75
- onConfirm?: (() => any) | undefined;
89
+ onConfirm?: ((value: string | null) => any) | undefined;
76
90
  "onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
77
91
  }>, {
78
- minuteStep: number;
92
+ label: string;
93
+ disabled: boolean;
94
+ readonly: boolean;
95
+ max: string;
79
96
  format: TimePickerFormat;
97
+ min: string;
80
98
  locale: string;
99
+ minuteStep: number;
100
+ allowedHours: TimePickerAllowed;
101
+ allowedMinutes: TimePickerAllowed;
81
102
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
82
103
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
83
104
  declare const _default: typeof __VLS_export;
@@ -1 +1 @@
1
- {"version":3,"file":"VTimePicker.js","names":[],"sources":["../../../src/components/VTimePicker/VTimePicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } 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 {\n DIAL_INNER_THRESHOLD,\n angleToIndex,\n dialIndexToHour24,\n distanceFraction,\n formatTime,\n hour24ToDial,\n hourCycleFor,\n parseTime,\n snapMinute,\n to12h,\n to24h,\n} from '../../utils/time'\nimport type { HourFormat, Meridiem } from '../../utils/time'\nimport { pad2 } from '../../utils/text'\nimport { useLocale, useMessages } from '../../i18n/state'\n\n// @a11y @keyboard @core\n/**\n * A clock shown directly in the page, for choosing a time. It is the hour-and-minute\n * counterpart of VDatePicker: VTimeInput does no more than dress it in a text field and a\n * popover, and it is perfectly usable on its own.\n *\n * It is read in three parts. Two large numerals at the top say what has been chosen so\n * far, and switch between adjusting the hour and adjusting the minutes; on a 12-hour clock\n * the choice between morning and afternoon sits beside them, since it is read as part of\n * that same time. Below them is the face itself, where one points at a time rather than\n * stepping through it.\n *\n * Nothing native covers choosing a value by pointing at an angle, so the JavaScript does\n * two things: it turns a point on the face into a time — measuring the face once, then\n * pure trigonometry that lives in `utils/time` — and it implements the keyboard of a\n * slider, the arrows and the Home, End and Page keys.\n *\n * Everything one SEES is CSS: the numerals are placed around the circle, and the hand is\n * turned, from a single unitless fraction of a turn written inline.\n *\n * There is exactly ONE focusable element on the face, and it is announced as a slider. The\n * numerals are visual markers hidden from screen readers, reached by the pointer's angle\n * rather than one cell at a time — which is why the whole spoken value has to be carried\n * by that one element.\n */\nexport type TimePickerFormat = HourFormat\n\n/** Which of the two halves of a time is being adjusted. */\nexport type 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 /** The interval the minutes snap to, both when dragging and with the arrow keys. */\n minuteStep?: number\n}\n\nconst props = withDefaults(defineProps<TimePickerProps>(), {\n format: undefined,\n locale: undefined,\n minuteStep: 1,\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. It is deliberately NOT the current time:\n * reading the clock while rendering would make a page drawn on a server disagree with the\n * same page in the browser. A component that wants to open on the current time sets it\n * from a handler — which is exactly what VTimeInput does.\n */\nconst model = defineModel<string | null>({ default: null })\n\nconst emit = defineEmits<{\n /**\n * The reader has finished: the minutes were settled from the KEYBOARD. Releasing the\n * pointer does not count — on a clock face, letting go of the hand is how one stops\n * adjusting it, not how one confirms.\n */\n confirm: []\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()\nconst vectisLocale = useLocale()\n/* The prop wins, and the design system's global locale is what it falls back to. */\nconst resolvedLocale = computed(() => props.locale ?? vectisLocale.value)\nconst resolvedFormat = computed<TimePickerFormat>(\n () => props.format ?? hourCycleFor(resolvedLocale.value),\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\nconst parts = computed(() => parseTime(model.value) ?? { hour: 0, minute: 0 })\nconst hour = computed(() => parts.value.hour)\nconst minute = computed(() => parts.value.minute)\n\nfunction setHour(value: number) {\n model.value = formatTime(value, minute.value)\n}\n\nfunction setMinute(value: number) {\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.minuteStep : m.value.timePicker.hourStep\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\nconst cells = computed<DialCell[]>(() => {\n if (step.value === 'minute') {\n // Only twelve markers are printed, one every five minutes — sixty numerals would be\n // unreadable — while dragging remains accurate to the minute.\n return Array.from({ length: 12 }, (_, i) => ({\n key: `m-${i}`,\n label: pad2(i * 5),\n turn: i / 12,\n ring: 'outer' as const,\n selected: minute.value === i * 5,\n }))\n }\n const outer: DialCell[] = Array.from({ length: 12 }, (_, i) => {\n const hour24 = dialIndexToHour24(i, 'outer')\n return {\n key: `o-${i}`,\n label: String(i === 0 ? 12 : i),\n turn: i / 12,\n ring: 'outer' as const,\n selected:\n resolvedFormat.value === '24h'\n ? hour.value === hour24\n : to12h(hour.value).hour === (i === 0 ? 12 : i),\n }\n })\n if (resolvedFormat.value === '12h') return outer\n const inner: DialCell[] = Array.from({ length: 12 }, (_, i) => {\n const hour24 = dialIndexToHour24(i, 'inner')\n return {\n key: `i-${i}`,\n label: pad2(hour24),\n turn: i / 12,\n ring: 'inner' as const,\n selected: hour.value === hour24,\n }\n })\n return [...outer, ...inner]\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(() => step.value === 'minute' && minute.value % 5 !== 0)\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.\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) setHour(to24h(to12h(hour.value).hour, meridiem))\n },\n})\n\n// @a11y — 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)\nconst ariaValueText = computed(() =>\n step.value === 'minute'\n ? m.value.timePicker.minutesValue(minute.value)\n : m.value.timePicker.hoursValue(ariaValueNow.value),\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')\n}\n\n// Pointing at the face, by click or by drag.\nconst faceEl = ref<HTMLElement | null>(null)\nconst dragging = ref(false)\n\nfunction applyPoint(event: PointerEvent) {\n const face = faceEl.value\n if (!face) return\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 (step.value === 'minute') {\n setMinute(snapMinute(angleToIndex(dx, dy, 60), props.minuteStep))\n return\n }\n const index = angleToIndex(dx, dy, 12)\n if (resolvedFormat.value === '24h') {\n const ring = distanceFraction(dx, dy, rect.width / 2) < DIAL_INNER_THRESHOLD ? 'inner' : 'outer'\n setHour(dialIndexToHour24(index, ring))\n } else {\n setHour(to24h(index === 0 ? 12 : index, currentMeridiem.value))\n }\n}\n\nfunction onPointerdown(event: PointerEvent) {\n // Capturing the pointer is what keeps the drag alive when it wanders off the face.\n //\n // @fallback — it is wrapped because a pointer event fired by a TEST refers to no real\n // pointer, and the call then throws. Failing to capture merely means the drag stops at\n // the edge, which 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 applyPoint(event)\n}\n\nfunction onPointermove(event: PointerEvent) {\n if (dragging.value) applyPoint(event)\n}\n\nfunction onPointerup() {\n if (!dragging.value) return\n dragging.value = false\n settleStep('pointer')\n}\n\nfunction onPointercancel() {\n dragging.value = false\n}\n\n// @keyboard @a11y — the keyboard a slider is expected to have.\nfunction moveHour(delta: number) {\n if (resolvedFormat.value === '24h') {\n setHour((hour.value + delta + 24) % 24)\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(to24h(((hour12 - 1 + delta + 12) % 12) + 1, currentMeridiem.value))\n}\n\nfunction onKeydown(event: KeyboardEvent) {\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')\n setHour(resolvedFormat.value === '12h' ? to24h(1, currentMeridiem.value) : 0)\n else if (event.key === 'End')\n setHour(resolvedFormat.value === '12h' ? to24h(12, currentMeridiem.value) : 23)\n else return\n event.preventDefault()\n return\n }\n if (delta) setMinute(snapMinute(minute.value + delta * props.minuteStep, props.minuteStep))\n else if (event.key === 'PageUp') setMinute(snapMinute(minute.value + 5, props.minuteStep))\n else if (event.key === 'PageDown') setMinute(snapMinute(minute.value - 5, props.minuteStep))\n else if (event.key === 'Home') setMinute(0)\n else if (event.key === 'End') setMinute(snapMinute(60 - props.minuteStep, props.minuteStep))\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 class=\"v-time-picker\" :data-step=\"step\">\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=\"m.timePicker.selectHour\"\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=\"m.timePicker.selectMinute\"\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 orientation=\"vertical\"\n size=\"sm\"\n :label=\"m.timePicker.meridiem\"\n >\n <VToggleItem value=\"AM\" :label=\"m.timePicker.am\" />\n <VToggleItem value=\"PM\" :label=\"m.timePicker.pm\" />\n </VToggle>\n </div>\n\n <div\n ref=\"faceEl\"\n role=\"slider\"\n tabindex=\"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 @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 .v-time-picker {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-space-4);\n width: max-content;\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 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-2-weight);\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 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/time`. 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 .v-time-picker-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--vectis-space-2);\n }\n\n @media (prefers-reduced-motion: reduce) {\n .v-time-picker-hand::before {\n transition: none;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VTimePicker.js","names":[],"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/time`) 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 {\n DIAL_INNER_THRESHOLD,\n angleToIndex,\n dialIndexToHour24,\n distanceFraction,\n formatTime,\n hour24ToDial,\n hourCycleFor,\n hourWithMeridiem,\n parseTime,\n snapMinute,\n to12h,\n to24h,\n} from '../../utils/time'\nimport type { HourFormat, Meridiem } from '../../utils/time'\nimport {\n allowedMinutesFor,\n firstAllowed,\n isHourAllowed,\n isTimeAllowed,\n limitsProblem,\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\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 — which 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. It is deliberately NOT the current time:\n * reading the clock while rendering would make a page drawn on a server disagree with the\n * same page in the browser. A component that wants to open on the current time sets it\n * from a handler — which is exactly 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 — 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 (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\nconst parts = computed(() => parseTime(model.value) ?? { hour: 0, minute: 0 })\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 — 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)\nconst ariaValueText = computed(() =>\n step.value === 'minute'\n ? m.value.timePicker.minutesValue(minute.value)\n : m.value.timePicker.hourValue(ariaValueNow.value),\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 if (!face) 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 (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 // Capturing the pointer is what keeps the drag alive when it wanders off the face.\n //\n // @fallback — it is wrapped because a pointer event fired by a TEST refers to no real\n // pointer, and the call then throws. Failing to capture merely means the drag stops at\n // the edge, which 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 — 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 one it asked for when the hour allows it, and otherwise\n * the next one the same way round that it does.\n */\nfunction minuteFrom(start: number, direction: number): number | null {\n const interval = minuteInterval(props.minuteStep)\n const allowed = (candidate: number) => isTimeAllowed(hour.value, candidate, limits.value)\n if (allowed(start)) return start\n return firstAllowed(\n Math.ceil(60 / interval),\n (i) => snapMinute(start + direction * i * interval, interval),\n allowed,\n )\n}\n\nfunction onKeydown(event: KeyboardEvent) {\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)\n setMinute(\n minuteFrom(snapMinute(minute.value + delta * props.minuteStep, props.minuteStep), delta),\n )\n else if (event.key === 'PageUp')\n setMinute(minuteFrom(snapMinute(minute.value + 5, props.minuteStep), 1))\n else if (event.key === 'PageDown')\n setMinute(minuteFrom(snapMinute(minute.value - 5, props.minuteStep), -1))\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=\"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=\"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 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-2-weight);\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 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/time`. 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":""}