vectis-ui 0.14.0 → 0.15.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 (375) hide show
  1. package/dist/components/VAccordion/VAccordion.js.map +1 -1
  2. package/dist/components/VAccordion/VAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  3. package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
  4. package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  5. package/dist/components/VAccordion/context.js.map +1 -1
  6. package/dist/components/VAlert/VAlert.js.map +1 -1
  7. package/dist/components/VAlert/VAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  8. package/dist/components/VAvatar/VAvatar.js.map +1 -1
  9. package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  10. package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
  11. package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  12. package/dist/components/VAvatar/context.js.map +1 -1
  13. package/dist/components/VBadge/VBadge.js.map +1 -1
  14. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +1 -1
  15. package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
  16. package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
  17. package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
  18. package/dist/components/VButton/VButton.js.map +1 -1
  19. package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  20. package/dist/components/VButton/VButtonGroup.js.map +1 -1
  21. package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  22. package/dist/components/VButton/context.js.map +1 -1
  23. package/dist/components/VCalendar/VCalendar.js.map +1 -1
  24. package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  25. package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
  26. package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  27. package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
  28. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +5 -17
  29. package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
  30. package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
  31. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +19 -31
  32. package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
  33. package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
  34. package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  35. package/dist/components/VCalendar/color.js.map +1 -1
  36. package/dist/components/VCalendar/edgeStep.js +1 -5
  37. package/dist/components/VCalendar/edgeStep.js.map +1 -1
  38. package/dist/components/VCalendar/gesture.js +4 -12
  39. package/dist/components/VCalendar/gesture.js.map +1 -1
  40. package/dist/components/VCalendar/keyboard.js.map +1 -1
  41. package/dist/components/VCalendar/layout.js +8 -12
  42. package/dist/components/VCalendar/layout.js.map +1 -1
  43. package/dist/components/VCalendar/timeGrid.js +5 -5
  44. package/dist/components/VCalendar/timeGrid.js.map +1 -1
  45. package/dist/components/VCard/VCard.js.map +1 -1
  46. package/dist/components/VCard/VCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  47. package/dist/components/VCarousel/VCarousel.js.map +1 -1
  48. package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  49. package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
  50. package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  51. package/dist/components/VCarousel/context.js.map +1 -1
  52. package/dist/components/VCheckbox/VCheckMark.js.map +1 -1
  53. package/dist/components/VCheckbox/VCheckMark.vue_vue_type_script_setup_true_lang.js.map +1 -1
  54. package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
  55. package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  56. package/dist/components/VChip/VChip.js.map +1 -1
  57. package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  58. package/dist/components/VColorInput/VColorInput.js.map +1 -1
  59. package/dist/components/VColorInput/VColorInput.vue_vue_type_script_setup_true_lang.js +62 -62
  60. package/dist/components/VColorInput/VColorInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  61. package/dist/components/VColorPicker/VColorPicker.js.map +1 -1
  62. package/dist/components/VColorPicker/VColorPicker.vue_vue_type_script_setup_true_lang.js +1 -5
  63. package/dist/components/VColorPicker/VColorPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  64. package/dist/components/VColorPicker/color.js.map +1 -1
  65. package/dist/components/VCombobox/VCombobox.js.map +1 -1
  66. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +114 -122
  67. package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
  68. package/dist/components/VCommandPalette/VCommandPalette.js.map +1 -1
  69. package/dist/components/VCommandPalette/VCommandPalette.vue_vue_type_script_setup_true_lang.js +0 -1
  70. package/dist/components/VCommandPalette/VCommandPalette.vue_vue_type_script_setup_true_lang.js.map +1 -1
  71. package/dist/components/VCommandPalette/VCommandPaletteItem.js.map +1 -1
  72. package/dist/components/VCommandPalette/VCommandPaletteItem.vue_vue_type_script_setup_true_lang.js +1 -5
  73. package/dist/components/VCommandPalette/VCommandPaletteItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  74. package/dist/components/VContextMenu/VContextMenu.js.map +1 -1
  75. package/dist/components/VContextMenu/VContextMenu.vue_vue_type_script_setup_true_lang.js +3 -5
  76. package/dist/components/VContextMenu/VContextMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  77. package/dist/components/VDataTable/VDataTable.js.map +1 -1
  78. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +1 -5
  79. package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  80. package/dist/components/VDateInput/VDateInput.js.map +1 -1
  81. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +35 -35
  82. package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  83. package/dist/components/VDateInput/mask.js.map +1 -1
  84. package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
  85. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +2 -1
  86. package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  87. package/dist/components/VDatePicker/keyboard.js.map +1 -1
  88. package/dist/components/VDatePicker/names.js.map +1 -1
  89. package/dist/components/VDialog/VDialog.js.map +1 -1
  90. package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
  91. package/dist/components/VDialog/VDialogAlert.js.map +1 -1
  92. package/dist/components/VDialog/VDialogAlert.vue_vue_type_script_setup_true_lang.js.map +1 -1
  93. package/dist/components/VDrawer/VDrawer.js.map +1 -1
  94. package/dist/components/VDrawer/VDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  95. package/dist/components/VEmptyState/VEmptyState.js.map +1 -1
  96. package/dist/components/VEmptyState/VEmptyState.vue_vue_type_script_setup_true_lang.js.map +1 -1
  97. package/dist/components/VField/VField.js.map +1 -1
  98. package/dist/components/VField/VField.vue_vue_type_script_setup_true_lang.js.map +1 -1
  99. package/dist/components/VField/VFieldAnnouncer.js.map +1 -1
  100. package/dist/components/VField/VFieldAnnouncer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  101. package/dist/components/VField/VFieldMessage.js.map +1 -1
  102. package/dist/components/VField/VFieldMessage.vue_vue_type_script_setup_true_lang.js.map +1 -1
  103. package/dist/components/VFieldset/VFieldset.js.map +1 -1
  104. package/dist/components/VFieldset/VFieldset.vue_vue_type_script_setup_true_lang.js.map +1 -1
  105. package/dist/components/VFileInput/VFileInput.js.map +1 -1
  106. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +85 -85
  107. package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  108. package/dist/components/VFileInput/truncate.js.map +1 -1
  109. package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
  110. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  111. package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  112. package/dist/components/VFilePicker/fileKind.js.map +1 -1
  113. package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
  114. package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
  115. package/dist/components/VHotkeys/platform.js +1 -1
  116. package/dist/components/VHotkeys/platform.js.map +1 -1
  117. package/dist/components/VHoverCard/VHoverCard.js.map +1 -1
  118. package/dist/components/VHoverCard/VHoverCard.vue_vue_type_script_setup_true_lang.js +2 -12
  119. package/dist/components/VHoverCard/VHoverCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  120. package/dist/components/VIcon/VIcon.js.map +1 -1
  121. package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  122. package/dist/components/VIcon/iconProps.js.map +1 -1
  123. package/dist/components/VIcon/icons/add.js.map +1 -1
  124. package/dist/components/VIcon/icons/arrow_downward.js.map +1 -1
  125. package/dist/components/VIcon/icons/arrow_downward_alt.js.map +1 -1
  126. package/dist/components/VIcon/icons/arrow_drop_down.js.map +1 -1
  127. package/dist/components/VIcon/icons/arrow_drop_up.js.map +1 -1
  128. package/dist/components/VIcon/icons/arrow_left_alt.js.map +1 -1
  129. package/dist/components/VIcon/icons/arrow_right_alt.js.map +1 -1
  130. package/dist/components/VIcon/icons/arrow_upward.js.map +1 -1
  131. package/dist/components/VIcon/icons/arrow_upward_alt.js.map +1 -1
  132. package/dist/components/VIcon/icons/attach_file.js.map +1 -1
  133. package/dist/components/VIcon/icons/audio_file.js.map +1 -1
  134. package/dist/components/VIcon/icons/calendar_today.js.map +1 -1
  135. package/dist/components/VIcon/icons/check.js.map +1 -1
  136. package/dist/components/VIcon/icons/check_circle.js.map +1 -1
  137. package/dist/components/VIcon/icons/chevron_left.js.map +1 -1
  138. package/dist/components/VIcon/icons/chevron_right.js.map +1 -1
  139. package/dist/components/VIcon/icons/close.js.map +1 -1
  140. package/dist/components/VIcon/icons/cloud_upload.js.map +1 -1
  141. package/dist/components/VIcon/icons/code.js.map +1 -1
  142. package/dist/components/VIcon/icons/colorize.js.map +1 -1
  143. package/dist/components/VIcon/icons/description.js.map +1 -1
  144. package/dist/components/VIcon/icons/error.js.map +1 -1
  145. package/dist/components/VIcon/icons/expand_less.js.map +1 -1
  146. package/dist/components/VIcon/icons/expand_more.js.map +1 -1
  147. package/dist/components/VIcon/icons/first_page.js.map +1 -1
  148. package/dist/components/VIcon/icons/folder_zip.js.map +1 -1
  149. package/dist/components/VIcon/icons/image.js.map +1 -1
  150. package/dist/components/VIcon/icons/inbox.js.map +1 -1
  151. package/dist/components/VIcon/icons/index.js.map +1 -1
  152. package/dist/components/VIcon/icons/info.js.map +1 -1
  153. package/dist/components/VIcon/icons/last_page.js.map +1 -1
  154. package/dist/components/VIcon/icons/more_horiz.js.map +1 -1
  155. package/dist/components/VIcon/icons/names.js.map +1 -1
  156. package/dist/components/VIcon/icons/notifications.js.map +1 -1
  157. package/dist/components/VIcon/icons/open_in_new.js.map +1 -1
  158. package/dist/components/VIcon/icons/picture_as_pdf.js.map +1 -1
  159. package/dist/components/VIcon/icons/priority_high.js.map +1 -1
  160. package/dist/components/VIcon/icons/remove.js.map +1 -1
  161. package/dist/components/VIcon/icons/schedule.js.map +1 -1
  162. package/dist/components/VIcon/icons/search.js.map +1 -1
  163. package/dist/components/VIcon/icons/search_off.js.map +1 -1
  164. package/dist/components/VIcon/icons/star.js.map +1 -1
  165. package/dist/components/VIcon/icons/swap_vert.js.map +1 -1
  166. package/dist/components/VIcon/icons/table_chart.js.map +1 -1
  167. package/dist/components/VIcon/icons/video_file.js.map +1 -1
  168. package/dist/components/VIcon/icons/viewBox.js.map +1 -1
  169. package/dist/components/VIcon/icons/warning.js.map +1 -1
  170. package/dist/components/VIcon/resolver.js.map +1 -1
  171. package/dist/components/VIconButton/VIconButton.js.map +1 -1
  172. package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  173. package/dist/components/VInput/VInput.js.map +1 -1
  174. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +20 -20
  175. package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  176. package/dist/components/VInput/VInputGroup.js.map +1 -1
  177. package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  178. package/dist/components/VInput/context.js.map +1 -1
  179. package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
  180. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +2 -8
  181. package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
  182. package/dist/components/VLink/VLink.js.map +1 -1
  183. package/dist/components/VLink/VLink.vue_vue_type_script_setup_true_lang.js.map +1 -1
  184. package/dist/components/VListbox/VListboxChevron.js.map +1 -1
  185. package/dist/components/VListbox/VListboxChevron.vue_vue_type_script_setup_true_lang.js.map +1 -1
  186. package/dist/components/VListbox/VListboxGroup.js.map +1 -1
  187. package/dist/components/VListbox/VListboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  188. package/dist/components/VListbox/VListboxOption.js.map +1 -1
  189. package/dist/components/VListbox/VListboxOption.vue_vue_type_script_setup_true_lang.js.map +1 -1
  190. package/dist/components/VListbox/VListboxPanel.js.map +1 -1
  191. package/dist/components/VListbox/VListboxPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  192. package/dist/components/VListbox/VListboxSeparator.js.map +1 -1
  193. package/dist/components/VListbox/VListboxSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  194. package/dist/components/VListbox/VListboxValues.js.map +1 -1
  195. package/dist/components/VListbox/VListboxValues.vue_vue_type_script_setup_true_lang.js.map +1 -1
  196. package/dist/components/VMenu/VMenu.js.map +1 -1
  197. package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  198. package/dist/components/VMenu/VMenuGroup.js.map +1 -1
  199. package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  200. package/dist/components/VMenu/VMenuItem.js.map +1 -1
  201. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +6 -12
  202. package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  203. package/dist/components/VMenu/VMenuPanel.js.map +1 -1
  204. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +9 -10
  205. package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  206. package/dist/components/VMenu/VMenuSeparator.js.map +1 -1
  207. package/dist/components/VMenu/VMenuSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  208. package/dist/components/VMenu/context.js.map +1 -1
  209. package/dist/components/VMeter/VMeter.js.map +1 -1
  210. package/dist/components/VMeter/VMeter.vue_vue_type_script_setup_true_lang.js.map +1 -1
  211. package/dist/components/VNumberInput/VNumberInput.js.map +1 -1
  212. package/dist/components/VNumberInput/VNumberInput.vue_vue_type_script_setup_true_lang.js +86 -85
  213. package/dist/components/VNumberInput/VNumberInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  214. package/dist/components/VNumberInput/number.js.map +1 -1
  215. package/dist/components/VPagination/VPagination.js.map +1 -1
  216. package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
  217. package/dist/components/VPopover/VPopover.js.map +1 -1
  218. package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  219. package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
  220. package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
  221. package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
  222. package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
  223. package/dist/components/VRadio/VRadio.js.map +1 -1
  224. package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
  225. package/dist/components/VRating/VRating.js.map +1 -1
  226. package/dist/components/VRating/VRating.vue_vue_type_script_setup_true_lang.js.map +1 -1
  227. package/dist/components/VResizable/VResizable.js.map +1 -1
  228. package/dist/components/VResizable/VResizable.vue_vue_type_script_setup_true_lang.js +8 -6
  229. package/dist/components/VResizable/VResizable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  230. package/dist/components/VResizable/VResizablePanel.js.map +1 -1
  231. package/dist/components/VResizable/VResizablePanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  232. package/dist/components/VResizable/context.js.map +1 -1
  233. package/dist/components/VResizable/layout.js.map +1 -1
  234. package/dist/components/VSelect/VSelect.js.map +1 -1
  235. package/dist/components/VSelect/VSelect.vue_vue_type_script_setup_true_lang.js +124 -125
  236. package/dist/components/VSelect/VSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  237. package/dist/components/VSeparator/VSeparator.js.map +1 -1
  238. package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
  239. package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
  240. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +1 -1
  241. package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
  242. package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
  243. package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  244. package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
  245. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +1 -5
  246. package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  247. package/dist/components/VSideNavigation/VSideNavigationSeparator.js.map +1 -1
  248. package/dist/components/VSideNavigation/context.js.map +1 -1
  249. package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
  250. package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
  251. package/dist/components/VSlider/VSlider.js.map +1 -1
  252. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +22 -22
  253. package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
  254. package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
  255. package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  256. package/dist/components/VSnackbar/state.js.map +1 -1
  257. package/dist/components/VSpinner/VSpinner.js.map +1 -1
  258. package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
  259. package/dist/components/VSplitButton/VSplitButton.js.map +1 -1
  260. package/dist/components/VSplitButton/VSplitButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  261. package/dist/components/VStepper/VStepper.js.map +1 -1
  262. package/dist/components/VStepper/VStepper.vue_vue_type_script_setup_true_lang.js.map +1 -1
  263. package/dist/components/VSwitch/VSwitch.js.map +1 -1
  264. package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
  265. package/dist/components/VTabs/VTab.js.map +1 -1
  266. package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
  267. package/dist/components/VTabs/VTabPanel.js.map +1 -1
  268. package/dist/components/VTabs/VTabPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
  269. package/dist/components/VTabs/VTabs.js.map +1 -1
  270. package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
  271. package/dist/components/VTabs/context.js.map +1 -1
  272. package/dist/components/VTextarea/VTextarea.js.map +1 -1
  273. package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
  274. package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
  275. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +45 -45
  276. package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
  277. package/dist/components/VTimeInput/search.js.map +1 -1
  278. package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
  279. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +9 -9
  280. package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  281. package/dist/components/VTimePicker/limits.js.map +1 -1
  282. package/dist/components/VTimeline/VTimeline.js.map +1 -1
  283. package/dist/components/VTimeline/VTimeline.vue_vue_type_script_setup_true_lang.js.map +1 -1
  284. package/dist/components/VTimeline/VTimelineItem.js.map +1 -1
  285. package/dist/components/VTimeline/VTimelineItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  286. package/dist/components/VTimeline/context.js.map +1 -1
  287. package/dist/components/VTimeline/datetime.js.map +1 -1
  288. package/dist/components/VToast/VToast.js.map +1 -1
  289. package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  290. package/dist/components/VToast/VToaster.js.map +1 -1
  291. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +1 -1
  292. package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
  293. package/dist/components/VToast/state.js.map +1 -1
  294. package/dist/components/VToggle/VToggle.js.map +1 -1
  295. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +1 -7
  296. package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
  297. package/dist/components/VToggle/VToggleItem.js.map +1 -1
  298. package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  299. package/dist/components/VToggle/context.js.map +1 -1
  300. package/dist/components/VTooltip/VTooltip.js.map +1 -1
  301. package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
  302. package/dist/components/VTreeView/VTreeView.js.map +1 -1
  303. package/dist/components/VTreeView/VTreeView.vue_vue_type_script_setup_true_lang.js +1 -5
  304. package/dist/components/VTreeView/VTreeView.vue_vue_type_script_setup_true_lang.js.map +1 -1
  305. package/dist/components/VTypography/VTypography.js.map +1 -1
  306. package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
  307. package/dist/components/VVirtualList/VVirtualList.js.map +1 -1
  308. package/dist/components/VVirtualList/VVirtualList.vue_vue_type_script_setup_true_lang.js.map +1 -1
  309. package/dist/composables/useAriaLabel.js.map +1 -1
  310. package/dist/composables/useChoice.js.map +1 -1
  311. package/dist/composables/useClearable.js.map +1 -1
  312. package/dist/composables/useControlShape.js.map +1 -1
  313. package/dist/composables/useDebouncedSearch.js.map +1 -1
  314. package/dist/composables/useDetailsOpen.js.map +1 -1
  315. package/dist/composables/useEscapeDismiss.js.map +1 -1
  316. package/dist/composables/useFieldIds.js.map +1 -1
  317. package/dist/composables/useFieldPanel.js +3 -7
  318. package/dist/composables/useFieldPanel.js.map +1 -1
  319. package/dist/composables/useFileDrop.js.map +1 -1
  320. package/dist/composables/useFileField.js +2 -2
  321. package/dist/composables/useFileField.js.map +1 -1
  322. package/dist/composables/useFocusoutDismiss.js.map +1 -1
  323. package/dist/composables/useHotkeyListener.js.map +1 -1
  324. package/dist/composables/useIconClickHandlers.js.map +1 -1
  325. package/dist/composables/useInertLink.js.map +1 -1
  326. package/dist/composables/useInfiniteScroll.js +2 -2
  327. package/dist/composables/useInfiniteScroll.js.map +1 -1
  328. package/dist/composables/useListbox.js +3 -9
  329. package/dist/composables/useListbox.js.map +1 -1
  330. package/dist/composables/useLiveAnnouncer.js.map +1 -1
  331. package/dist/composables/useMaskedField.js +28 -48
  332. package/dist/composables/useMaskedField.js.map +1 -1
  333. package/dist/composables/useModalDialog.js +1 -1
  334. package/dist/composables/useModalDialog.js.map +1 -1
  335. package/dist/composables/usePopover.js +1 -1
  336. package/dist/composables/usePopover.js.map +1 -1
  337. package/dist/composables/useProgressValue.js.map +1 -1
  338. package/dist/composables/useRootAttrs.js.map +1 -1
  339. package/dist/composables/useSlotNodes.js.map +1 -1
  340. package/dist/composables/useTextLimit.js.map +1 -1
  341. package/dist/composables/useTimer.js.map +1 -1
  342. package/dist/composables/useTypeahead.js.map +1 -1
  343. package/dist/composables/useVirtualList.js.map +1 -1
  344. package/dist/i18n/en.js.map +1 -1
  345. package/dist/i18n/fr.js.map +1 -1
  346. package/dist/i18n/state.js.map +1 -1
  347. package/dist/tokens/css.js.map +1 -1
  348. package/dist/tokens/index.js.map +1 -1
  349. package/dist/tokens/primitives.js.map +1 -1
  350. package/dist/tokens/semantic.js.map +1 -1
  351. package/dist/tokens/themes/dark.js.map +1 -1
  352. package/dist/tokens/types.js.map +1 -1
  353. package/dist/utils/array.js.map +1 -1
  354. package/dist/utils/arrowNav.js.map +1 -1
  355. package/dist/utils/attrs.js.map +1 -1
  356. package/dist/utils/chip.js.map +1 -1
  357. package/dist/utils/clock.js +3 -1
  358. package/dist/utils/clock.js.map +1 -1
  359. package/dist/utils/css.js +1 -1
  360. package/dist/utils/css.js.map +1 -1
  361. package/dist/utils/date.js.map +1 -1
  362. package/dist/utils/direction.js.map +1 -1
  363. package/dist/utils/env.js.map +1 -1
  364. package/dist/utils/file.js +13 -31
  365. package/dist/utils/file.js.map +1 -1
  366. package/dist/utils/focus.js.map +1 -1
  367. package/dist/utils/hostWarns.js.map +1 -1
  368. package/dist/utils/ids.js.map +1 -1
  369. package/dist/utils/matcher.js.map +1 -1
  370. package/dist/utils/memo.js.map +1 -1
  371. package/dist/utils/number.js.map +1 -1
  372. package/dist/utils/text.js.map +1 -1
  373. package/dist/utils/time.js.map +1 -1
  374. package/dist/utils/vnode.js.map +1 -1
  375. package/package.json +19 -19
@@ -1 +1 @@
1
- {"version":3,"file":"VCalendarMonth.js","names":[],"sources":["../../../src/components/VCalendar/VCalendarMonth.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @keyboard @a11y @core\n/**\n * Month cells summarize events and overflow counts. Shared gesture logic supplies dragging and\n * keyboard grabs; grid focus remains in this view.\n */\nimport { computed, ref, useId } from 'vue'\n\nimport { formatDateDisplay } from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { formatTimeDisplay } from '../../utils/time'\n\nimport { useMessages } from '../../i18n/state'\n\nimport VCalendarEvent from './VCalendarEvent.vue'\nimport { gridGeometryOf, useCalendarGesture, type GestureBase, type GrabStep } from './gesture'\nimport { calendarIntent } from './keyboard'\nimport {\n eventsByDay,\n isAllDayEvent,\n moveEventToDay,\n pointToMonthCell,\n sameTimes,\n timesOf,\n type MonthCell,\n} from './layout'\nimport type {\n ActivatedCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n} from './types'\n\nexport interface CalendarMonthProps<T> {\n /** The weeks, already padded to a fixed height and filtered to the visible weekdays. */\n weeks: MonthCell[][]\n events: T[]\n locale: string\n format: CalendarFormat\n today: string | null\n /** How many events a day shows before it starts counting the rest. */\n monthEventLimit: number\n /** Whether events can be moved from one day to another. */\n editable: boolean\n /** Whether the whole calendar is frozen, which also takes its cards out of the tab order. */\n disabled: boolean\n /** The node telling a reader how a chip can be moved, shared by every one of them. */\n hintId: string\n /** How long a drag rests against an edge before the month turns. Zero turns paging off. */\n edgeStepDelay: number\n label: string\n}\n\nconst props = defineProps<CalendarMonthProps<E>>()\n\nconst emit = defineEmits<{\n /** A day was asked to be opened on its own. */\n 'day-activate': [iso: string]\n /** An empty part of a day was activated. A month has no hours, so the time is left to the calendar. */\n 'cell-activate': [cell: ActivatedCell]\n 'event-activate': [event: E]\n /** An event was dropped on another day. A month has no hours, so it is only ever a move. */\n 'event-drop': [id: CalendarEventId, times: CalendarEventTimes, kind: 'move']\n /** Something happened that a reader who cannot see the grid needs told. */\n announce: [message: string]\n step: [delta: -1 | 1]\n}>()\n\n/** Which day holds the tab stop. Only the date matters here; the hour is the grid's affair. */\nconst focused = defineModel<string>('focused', { required: true })\n\ndefineSlots<{\n event?(props: CalendarEventSlotProps<E>): unknown\n}>()\n\nconst m = useMessages()\nconst uid = useId()\n\nconst cellId = (iso: string) => `${uid}-m-${iso}`\n\n/** The flattened list of days: what the arrows travel along, and what the events are filed by. */\nconst flat = computed(() => props.weeks.flat().map((cell) => cell.iso))\nconst columnCount = computed(() => props.weeks[0]?.length ?? 1)\n\n/** The column headings, taken from the first week so they always match the columns drawn. */\nconst weekdayNames = computed(() =>\n (props.weeks[0] ?? []).map((cell) =>\n formatDateDisplay(cell.iso, props.locale, { weekday: 'short' }),\n ),\n)\n\n/*\n * `longDay` is asked for twice in the template; the square's own `aria-label`, then the name of\n * the button that opens the day; so the 42 cells cost 126 `Intl` formats a render, and a drag\n * re-renders the whole grid each time it carries a chip onto another day. The map depends on\n * the weeks and the locale alone, so it survives every frame of a drag.\n */\nconst dayLabels = computed(() => {\n const map = new Map<string, { number: string; long: string }>()\n for (const iso of flat.value) {\n map.set(iso, {\n number: formatDateDisplay(iso, props.locale, { day: 'numeric' }),\n long: formatDateDisplay(iso, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n }),\n })\n }\n return map\n})\n\nconst dayNumber = (iso: string) => dayLabels.value.get(iso)?.number ?? ''\n\n/*\n * A day outside the grid can reach this; the announcement after a move names the day the event\n * landed on, which may be the next month; so it falls back to formatting rather than assuming\n * the map holds it.\n */\nconst longDay = (iso: string) =>\n dayLabels.value.get(iso)?.long ??\n formatDateDisplay(iso, props.locale, { weekday: 'long', day: 'numeric', month: 'long' })\n\n/** A chip being carried from one day to another, by pointer or by keyboard. */\ninterface Gesture extends GestureBase {\n /** Which square the pointer took hold in, so a long bar does not jump under it. */\n grabIndex: number\n}\n\nconst gridEl = ref<HTMLElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\nconst {\n gesture,\n withPreview,\n edgeCue,\n isRtl,\n start,\n endLastGesture,\n consumeDrag,\n onPointermove,\n onPointerup,\n onPointercancel,\n onPointerleave,\n onFocusout,\n onCardKeydown,\n focusCell,\n idOfCard,\n cardState,\n} = useCalendarGesture<E, Gesture>({\n events: () => props.events,\n onRelease: () => {\n lastApplied = null\n },\n eventOf: (id): E | undefined => eventsById.value.get(id),\n rootEl,\n // Captured on the GRID rather than on the chip, because a chip is redrawn on another day the\n // moment the drag starts and a captured element that moves takes the pointer with it.\n captureEl: gridEl,\n measureEl: gridEl,\n columns: () => columnCount.value,\n editable: () => props.editable,\n hintId: () => props.hintId,\n // The step is irrelevant to a month, which has no hours: 1 keeps the table honest without\n // asking the calendar for a number this view never uses.\n slotMinutes: () => 1,\n edgeStepDelay: () => props.edgeStepDelay,\n layout: () => props.weeks,\n applyPoint,\n onDrop: (state) => emit('event-drop', state.id, state.preview, 'move'),\n grab: (item) =>\n props.editable\n ? {\n id: item.id,\n origin: timesOf(item),\n pointerId: null,\n originX: 0,\n originY: 0,\n grabIndex: 0,\n }\n : null,\n onGrabStep,\n step: (delta) => emit('step', delta),\n announce: (message) => emit('announce', message),\n})\n\n/**\n * The events as the month should currently DRAW them; the model, with the one being carried put\n * wherever it now is.\n */\nconst drawnEvents = computed<E[]>(() => (gesture.value ? withPreview(props.events) : props.events))\n\n/** Every day's events, worked out once rather than once per chip. */\nconst byDay = computed(() => eventsByDay(drawnEvents.value, flat.value))\n\nconst eventsById = computed(\n () => new Map<CalendarEventId, E>(drawnEvents.value.map((item) => [item.id, item])),\n)\n\n/**\n * A chip says when it happens only when that is not obvious: an all-day event has no time to\n * give, and the day it sits in already says which day it is.\n */\nconst timeTextOf = computed(() => {\n const { locale, format } = props\n const cache = new Map<string, string>()\n return (event: E): string => {\n if (isAllDayEvent(event)) return ''\n let text = cache.get(event.startTime)\n if (text === undefined) {\n text = formatTimeDisplay(event.startTime, locale, format)\n cache.set(event.startTime, text)\n }\n return text\n }\n})\n\n/** One chip as the template draws it, its time already written out. */\ninterface Chip {\n event: E\n timeText: string\n}\n\n/** What each square draws, and how many it had to leave out; one Map, built in a single pass. */\nconst dayEvents = computed(() => {\n const map = new Map<string, { shown: Chip[]; hidden: number }>()\n const textOf = timeTextOf.value\n const limit = Math.max(0, props.monthEventLimit)\n for (const [iso, all] of byDay.value) {\n const shown = all.length > limit ? all.slice(0, limit) : all\n map.set(iso, {\n shown: shown.map((event) => ({ event, timeText: textOf(event) })),\n hidden: Math.max(0, all.length - limit),\n })\n }\n return map\n})\n\n/** A square with nothing on it, shared rather than allocated per empty cell. */\nconst NO_EVENTS: { shown: Chip[]; hidden: number } = Object.freeze({ shown: [], hidden: 0 })\nconst dayEventsOf = (iso: string) => dayEvents.value.get(iso) ?? NO_EVENTS\n\nfunction moveFocusTo(iso: string) {\n focused.value = iso\n // @a11y\n focusCell(cellId(iso), true)\n}\n\nconst tabbable = computed(() =>\n flat.value.includes(focused.value) ? focused.value : (flat.value[0] ?? null),\n)\n\n/**\n * The same table serves both views, so its two axes are read differently here: a sideways step\n * is one day, and a vertical one is a whole week rather than an hour.\n */\nconst dayStep = (days: number, minutes: number) =>\n days !== 0 ? days : Math.sign(minutes) * columnCount.value\n\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement | null\n\n // A chip answers its own table, delegated here on the same element as the pointer so the\n // two input paths cannot drift apart.\n const card = target?.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n onCardKeydown(event, card)\n return\n }\n\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell) return\n\n const intent = calendarIntent(event, 'cell', 1, isRtl())\n if (!intent) return\n\n const iso = cell.dataset.iso!\n const index = flat.value.indexOf(iso)\n const columns = columnCount.value\n\n if (intent.kind === 'moveFocus') {\n const next =\n flat.value[clamp(index + dayStep(intent.days, intent.minutes), 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'rowEdge') {\n const row = Math.floor(index / columns)\n const edge = intent.edge === 'start' ? row * columns : row * columns + columns - 1\n const next = flat.value[clamp(edge, 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'period') {\n event.preventDefault()\n emit('step', intent.delta)\n return\n }\n\n if (intent.kind === 'openDay') {\n event.preventDefault()\n emit('day-activate', iso)\n return\n }\n\n if (intent.kind === 'activate') {\n event.preventDefault()\n emit('cell-activate', { date: iso, minutes: null })\n }\n}\n\n/** Which of the flattened squares a point falls on. */\nfunction indexAt(x: number, y: number, rect: DOMRect, rtl: boolean): number {\n const cell = pointToMonthCell(\n { x, y },\n { ...gridGeometryOf(rect, columnCount.value, rtl), rows: props.weeks.length || 1 },\n rtl,\n )\n return cell.rowIndex * columnCount.value + cell.columnIndex\n}\n\nfunction onGridPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || !props.editable || event.button !== 0) return\n\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n const item = card ? eventsById.value.get(idOfCard(card)) : undefined\n const rect = gridEl.value?.getBoundingClientRect()\n if (!item || !rect) return\n const rtl = isRtl()\n\n start(\n {\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabIndex: indexAt(event.clientX, event.clientY, rect, rtl),\n },\n rtl,\n )\n}\n\n/** Works out which day the chip now belongs to, from wherever the pointer last was. */\nfunction applyPoint(state: Gesture, rect: DOMRect) {\n const index = indexAt(state.lastX, state.lastY, rect, state.rtl)\n const last = lastApplied\n if (last?.state === state && last.weeks === props.weeks && last.index === index) return\n lastApplied = { state, weeks: props.weeks, index }\n\n /*\n * The target is the event's START shifted by however many squares the pointer has crossed,\n * not the square it is over; so a bar grabbed by its third day keeps its third day under the\n * finger. The days come out of the flattened grid, which is already filtered to the weekdays\n * on show, so a hidden weekend is not counted.\n */\n const days = flat.value\n const from = days.indexOf(state.origin.start)\n const target =\n days[\n clamp((from === -1 ? state.grabIndex : from) + index - state.grabIndex, 0, days.length - 1)\n ]\n if (!target) return\n /*\n * Written only when the day changes. The gesture is a deep ref, so an equal but new preview\n * would still re-bucket and re-sort every event and re-render all 42 squares.\n */\n const next = moveEventToDay(state.origin, target)\n if (!sameTimes(state.preview, next)) state.preview = next\n}\n\n/**\n * The square `applyPoint` last placed the chip from, so the many `pointermove`s that fire\n * inside one square return before any date arithmetic.\n */\nlet lastApplied: { state: Gesture; weeks: MonthCell[][]; index: number } | null = null\n\n/** One arrow press on a chip held by the keyboard: a day sideways, a week vertically. */\nfunction onGrabStep(state: Gesture, step: GrabStep, item: E): boolean {\n if (step.kind !== 'grabMove') return false\n const days = flat.value\n const from = days.indexOf(state.preview.start)\n const target = days[clamp(from + dayStep(step.days, step.minutes), 0, days.length - 1)]\n if (!target || target === state.preview.start) return false\n state.preview = moveEventToDay(state.preview, target)\n emit('announce', m.value.calendar.movedTo(item.title, longDay(state.preview.start)))\n return true\n}\n\nfunction onCardClick(item: E) {\n // `pointerup` fires before `click`, so letting go at the end of a drag would ALSO open the\n // event; the consumer's editor over every chip the reader had just moved.\n if (consumeDrag()) return\n emit('event-activate', item)\n}\n\n/**\n * A click on the empty part of a day: the pointer's side of the Enter that activates a cell, so\n * a square drawn with a pointer cursor answers a click as it answers the key.\n */\nfunction onGridClick(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (target?.closest('.v-calendar-event, button')) return\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell || consumeDrag()) return\n emit('cell-activate', { date: cell.dataset.iso!, minutes: null })\n}\n\n// The same two members VCalendarTimeGrid exposes, so VCalendar drives either view without\n// asking which is on screen. A month has no hours, hence the empty `scrollToMinutes`.\ndefineExpose({\n /** Brings the focus onto the day the grid is currently pointing at. */\n focus: (options?: FocusOptions) => {\n if (tabbable.value) focusCell(cellId(tabbable.value), false, options)\n },\n /** Nothing to scroll to in a month view. */\n scrollToMinutes: () => {},\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-calendar-month\"\n :data-outside=\"gesture?.outside ? '' : undefined\"\n :style=\"{ '--calendar-columns': String(weekdayNames.length) }\"\n @focusout=\"onFocusout\"\n >\n <div class=\"v-calendar-month-head\" aria-hidden=\"true\">\n <span\n v-for=\"(name, index) in weekdayNames\"\n :key=\"index\"\n class=\"v-calendar-weekday v-calendar-month-weekday\"\n >\n {{ name }}\n </span>\n </div>\n\n <div\n ref=\"gridEl\"\n class=\"v-calendar-month-grid v-calendar-edge-cue\"\n role=\"grid\"\n :aria-label=\"label\"\n :data-edge=\"edgeCue\"\n @keydown=\"onKeydown\"\n @pointerdown=\"onGridPointerdown\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @pointerleave=\"onPointerleave\"\n @click=\"onGridClick\"\n >\n <div\n v-for=\"(week, row) in weeks\"\n :key=\"week[0]?.iso ?? row\"\n role=\"row\"\n class=\"v-calendar-month-week\"\n >\n <div\n v-for=\"cell in week\"\n :id=\"cellId(cell.iso)\"\n :key=\"cell.iso\"\n role=\"gridcell\"\n class=\"v-calendar-cell v-calendar-month-cell\"\n :data-iso=\"cell.iso\"\n :data-adjacent=\"cell.adjacent ?? undefined\"\n :tabindex=\"tabbable === cell.iso ? 0 : -1\"\n :aria-label=\"longDay(cell.iso)\"\n aria-keyshortcuts=\"Shift+Enter\"\n >\n <button\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-day\"\n :class=\"{ 'v-calendar-today': cell.iso === today }\"\n tabindex=\"-1\"\n :aria-label=\"m.calendar.openDay(longDay(cell.iso))\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ dayNumber(cell.iso) }}\n </button>\n\n <VCalendarEvent\n v-for=\"chip in dayEventsOf(cell.iso).shown\"\n :key=\"chip.event.id\"\n class=\"v-calendar-month-chip\"\n :event=\"chip.event\"\n layout=\"chip\"\n :disabled=\"disabled\"\n :time-text=\"chip.timeText\"\n v-bind=\"cardState(chip.event.id)\"\n @click=\"onCardClick(chip.event)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n\n <button\n v-if=\"dayEventsOf(cell.iso).hidden > 0\"\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-more\"\n tabindex=\"-1\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ m.calendar.moreEvents(dayEventsOf(cell.iso).hidden) }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar-month {\n display: flex;\n flex-direction: column;\n }\n\n .v-calendar-month-head,\n .v-calendar-month-week {\n display: grid;\n grid-template-columns: repeat(var(--calendar-columns), minmax(0, 1fr));\n }\n\n /* 4, the time grid's header layer: a dragged chip (3) and the edge strip (2) pass under the\n sticky names as the month scrolls, rather than over them. */\n .v-calendar-month-head {\n position: sticky;\n inset-block-start: 0;\n z-index: 4;\n background: var(--calendar-surface);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-month-weekday {\n padding-block: var(--vectis-space-2);\n text-align: center;\n }\n\n .v-calendar-month-grid {\n position: relative;\n display: flex;\n /* The rows share what height there is, so a month fills the box it was given instead of\n leaving a band of nothing under the last week. The floor keeps a day readable when\n that box is short. */\n flex: 1;\n flex-direction: column;\n min-block-size: 0;\n }\n\n .v-calendar-month-week {\n flex: 1;\n min-block-size: var(--vectis-control-size-calendar-month-cell);\n }\n\n .v-calendar-month-cell {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-control-size-calendar-gap);\n overflow: hidden;\n padding: var(--vectis-space-1);\n }\n\n /*\n * The days of the neighbouring months are kept; the grid is a fixed six rows, so the page\n * never jumps; but pushed back, since they are context rather than content.\n */\n .v-calendar-month-cell[data-adjacent] {\n background: var(--vectis-color-surface-sunken);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-calendar-month-day {\n align-self: center;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n inline-size: var(--vectis-control-height-sm);\n block-size: var(--vectis-control-height-sm);\n border-radius: var(--vectis-radius-pill);\n color: inherit;\n font-size: var(--vectis-text-body-md-size);\n }\n\n /* Disjoint from today rather than less specific: the two live in different sheets, where a\n tie would be settled by whichever one the consumer's bundler emitted last. */\n .v-calendar-month-day:hover:not(.v-calendar-today) {\n background: var(--calendar-hover-bg);\n }\n\n /*\n * The chip rule in VCalendarEvent's own sheet is (0,2,0) against this (0,1,0), so declaring\n * the size directly would lose; and matching its specificity would only hand the decision to\n * whichever sheet the consumer's bundler emitted last, which is the failure the library's\n * one-sheet-per-component rule exists to prevent.\n */\n .v-calendar-month-chip {\n --calendar-chip-height: var(--vectis-control-size-calendar-allday-lane);\n\n flex: none;\n }\n\n .v-calendar-month-more {\n flex: none;\n padding-inline: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n text-align: start;\n }\n\n .v-calendar-month-more:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n /* The neighbouring months' days are told apart by a tint forced colors removes: grey text\n keeps them apart. */\n @media (forced-colors: active) {\n .v-calendar-month-cell[data-adjacent] {\n forced-color-adjust: none;\n background: Canvas;\n color: GrayText;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VCalendarMonth.js","names":["_sfc_main","VCalendarMonth_vue_vue_type_script_setup_true_lang_default","VCalendarMonth_default"],"sources":["../../../src/components/VCalendar/VCalendarMonth.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @keyboard @a11y @core\n/**\n * Month cells summarize events and overflow counts. Shared gesture logic supplies dragging and\n * keyboard grabs; grid focus remains in this view.\n */\nimport { computed, ref, useId } from 'vue'\n\nimport { formatDateDisplay } from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { formatTimeDisplay } from '../../utils/time'\n\nimport { useMessages } from '../../i18n/state'\n\nimport VCalendarEvent from './VCalendarEvent.vue'\nimport { gridGeometryOf, useCalendarGesture, type GestureBase, type GrabStep } from './gesture'\nimport { calendarIntent } from './keyboard'\nimport {\n eventsByDay,\n isAllDayEvent,\n moveEventToDay,\n pointToMonthCell,\n sameTimes,\n timesOf,\n type MonthCell,\n} from './layout'\nimport type {\n ActivatedCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n} from './types'\n\nexport interface CalendarMonthProps<T> {\n /** The weeks, already padded to a fixed height and filtered to the visible weekdays. */\n weeks: MonthCell[][]\n events: T[]\n locale: string\n format: CalendarFormat\n today: string | null\n /** How many events a day shows before it starts counting the rest. */\n monthEventLimit: number\n /** Whether events can be moved from one day to another. */\n editable: boolean\n /** Whether the whole calendar is frozen, which also takes its cards out of the tab order. */\n disabled: boolean\n /** The node telling a reader how a chip can be moved, shared by every one of them. */\n hintId: string\n /** How long a drag rests against an edge before the month turns. Zero turns paging off. */\n edgeStepDelay: number\n label: string\n}\n\nconst props = defineProps<CalendarMonthProps<E>>()\n\nconst emit = defineEmits<{\n /** A day was asked to be opened on its own. */\n 'day-activate': [iso: string]\n /** An empty part of a day was activated. A month has no hours, so the time is left to the calendar. */\n 'cell-activate': [cell: ActivatedCell]\n 'event-activate': [event: E]\n /** An event was dropped on another day. A month has no hours, so it is only ever a move. */\n 'event-drop': [id: CalendarEventId, times: CalendarEventTimes, kind: 'move']\n /** Something happened that a reader who cannot see the grid needs told. */\n announce: [message: string]\n step: [delta: -1 | 1]\n}>()\n\n/** Which day holds the tab stop. Only the date matters here; the hour is the grid's affair. */\nconst focused = defineModel<string>('focused', { required: true })\n\ndefineSlots<{\n event?(props: CalendarEventSlotProps<E>): unknown\n}>()\n\nconst m = useMessages()\nconst uid = useId()\n\nconst cellId = (iso: string) => `${uid}-m-${iso}`\n\n/** The flattened list of days: what the arrows travel along, and what the events are filed by. */\nconst flat = computed(() => props.weeks.flat().map((cell) => cell.iso))\nconst columnCount = computed(() => props.weeks[0]?.length ?? 1)\n\n/** The column headings, taken from the first week so they always match the columns drawn. */\nconst weekdayNames = computed(() =>\n (props.weeks[0] ?? []).map((cell) =>\n formatDateDisplay(cell.iso, props.locale, { weekday: 'short' }),\n ),\n)\n\n/*\n * `longDay` is asked for twice in the template; the square's own `aria-label`, then the name of\n * the button that opens the day; so the 42 cells cost 126 `Intl` formats a render, and a drag\n * re-renders the whole grid each time it carries a chip onto another day. The map depends on\n * the weeks and the locale alone, so it survives every frame of a drag.\n */\nconst dayLabels = computed(() => {\n const map = new Map<string, { number: string; long: string }>()\n for (const iso of flat.value) {\n map.set(iso, {\n number: formatDateDisplay(iso, props.locale, { day: 'numeric' }),\n long: formatDateDisplay(iso, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n }),\n })\n }\n return map\n})\n\nconst dayNumber = (iso: string) => dayLabels.value.get(iso)?.number ?? ''\n\n/*\n * A day outside the grid can reach this; the announcement after a move names the day the event\n * landed on, which may be the next month; so it falls back to formatting rather than assuming\n * the map holds it.\n */\nconst longDay = (iso: string) =>\n dayLabels.value.get(iso)?.long ??\n formatDateDisplay(iso, props.locale, { weekday: 'long', day: 'numeric', month: 'long' })\n\n/** A chip being carried from one day to another, by pointer or by keyboard. */\ninterface Gesture extends GestureBase {\n /** Which square the pointer took hold in, so a long bar does not jump under it. */\n grabIndex: number\n}\n\nconst gridEl = ref<HTMLElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\nconst {\n gesture,\n withPreview,\n edgeCue,\n isRtl,\n start,\n endLastGesture,\n consumeDrag,\n onPointermove,\n onPointerup,\n onPointercancel,\n onPointerleave,\n onFocusout,\n onCardKeydown,\n focusCell,\n idOfCard,\n cardState,\n} = useCalendarGesture<E, Gesture>({\n events: () => props.events,\n onRelease: () => {\n lastApplied = null\n },\n eventOf: (id): E | undefined => eventsById.value.get(id),\n rootEl,\n // Captured on the GRID rather than on the chip, because a chip is redrawn on another day the\n // moment the drag starts and a captured element that moves takes the pointer with it.\n captureEl: gridEl,\n measureEl: gridEl,\n columns: () => columnCount.value,\n editable: () => props.editable,\n hintId: () => props.hintId,\n // The step is irrelevant to a month, which has no hours: 1 keeps the table honest without\n // asking the calendar for a number this view never uses.\n slotMinutes: () => 1,\n edgeStepDelay: () => props.edgeStepDelay,\n layout: () => props.weeks,\n applyPoint,\n onDrop: (state) => emit('event-drop', state.id, state.preview, 'move'),\n grab: (item) =>\n props.editable\n ? {\n id: item.id,\n origin: timesOf(item),\n pointerId: null,\n originX: 0,\n originY: 0,\n grabIndex: 0,\n }\n : null,\n onGrabStep,\n step: (delta) => emit('step', delta),\n announce: (message) => emit('announce', message),\n})\n\n/**\n * The events as the month should currently DRAW them; the model, with the one being carried put\n * wherever it now is.\n */\nconst drawnEvents = computed<E[]>(() => (gesture.value ? withPreview(props.events) : props.events))\n\n/** Every day's events, worked out once rather than once per chip. */\nconst byDay = computed(() => eventsByDay(drawnEvents.value, flat.value))\n\nconst eventsById = computed(\n () => new Map<CalendarEventId, E>(drawnEvents.value.map((item) => [item.id, item])),\n)\n\n/**\n * A chip says when it happens only when that is not obvious: an all-day event has no time to\n * give, and the day it sits in already says which day it is.\n */\nconst timeTextOf = computed(() => {\n const { locale, format } = props\n const cache = new Map<string, string>()\n return (event: E): string => {\n if (isAllDayEvent(event)) return ''\n let text = cache.get(event.startTime)\n if (text === undefined) {\n text = formatTimeDisplay(event.startTime, locale, format)\n cache.set(event.startTime, text)\n }\n return text\n }\n})\n\n/** One chip as the template draws it, its time already written out. */\ninterface Chip {\n event: E\n timeText: string\n}\n\n/** What each square draws, and how many it had to leave out; one Map, built in a single pass. */\nconst dayEvents = computed(() => {\n const map = new Map<string, { shown: Chip[]; hidden: number }>()\n const textOf = timeTextOf.value\n const limit = Math.max(0, props.monthEventLimit)\n for (const [iso, all] of byDay.value) {\n const shown = all.length > limit ? all.slice(0, limit) : all\n map.set(iso, {\n shown: shown.map((event) => ({ event, timeText: textOf(event) })),\n hidden: Math.max(0, all.length - limit),\n })\n }\n return map\n})\n\n/** A square with nothing on it, shared rather than allocated per empty cell. */\nconst NO_EVENTS: { shown: Chip[]; hidden: number } = Object.freeze({ shown: [], hidden: 0 })\nconst dayEventsOf = (iso: string) => dayEvents.value.get(iso) ?? NO_EVENTS\n\nfunction moveFocusTo(iso: string) {\n focused.value = iso\n // @a11y\n focusCell(cellId(iso), true)\n}\n\nconst tabbable = computed(() =>\n flat.value.includes(focused.value) ? focused.value : (flat.value[0] ?? null),\n)\n\n/**\n * The same table serves both views, so its two axes are read differently here: a sideways step\n * is one day, and a vertical one is a whole week rather than an hour.\n */\nconst dayStep = (days: number, minutes: number) =>\n days !== 0 ? days : Math.sign(minutes) * columnCount.value\n\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement | null\n\n // A chip answers its own table, delegated here on the same element as the pointer so the\n // two input paths cannot drift apart.\n const card = target?.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n onCardKeydown(event, card)\n return\n }\n\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell) return\n\n const intent = calendarIntent(event, 'cell', 1, isRtl())\n if (!intent) return\n\n const iso = cell.dataset.iso!\n const index = flat.value.indexOf(iso)\n const columns = columnCount.value\n\n if (intent.kind === 'moveFocus') {\n const next =\n flat.value[clamp(index + dayStep(intent.days, intent.minutes), 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'rowEdge') {\n const row = Math.floor(index / columns)\n const edge = intent.edge === 'start' ? row * columns : row * columns + columns - 1\n const next = flat.value[clamp(edge, 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'period') {\n event.preventDefault()\n emit('step', intent.delta)\n return\n }\n\n if (intent.kind === 'openDay') {\n event.preventDefault()\n emit('day-activate', iso)\n return\n }\n\n if (intent.kind === 'activate') {\n event.preventDefault()\n emit('cell-activate', { date: iso, minutes: null })\n }\n}\n\n/** Which of the flattened squares a point falls on. */\nfunction indexAt(x: number, y: number, rect: DOMRect, rtl: boolean): number {\n const cell = pointToMonthCell(\n { x, y },\n { ...gridGeometryOf(rect, columnCount.value, rtl), rows: props.weeks.length || 1 },\n rtl,\n )\n return cell.rowIndex * columnCount.value + cell.columnIndex\n}\n\nfunction onGridPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || !props.editable || event.button !== 0) return\n\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n const item = card ? eventsById.value.get(idOfCard(card)) : undefined\n const rect = gridEl.value?.getBoundingClientRect()\n if (!item || !rect) return\n const rtl = isRtl()\n\n start(\n {\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabIndex: indexAt(event.clientX, event.clientY, rect, rtl),\n },\n rtl,\n )\n}\n\n/** Works out which day the chip now belongs to, from wherever the pointer last was. */\nfunction applyPoint(state: Gesture, rect: DOMRect) {\n const index = indexAt(state.lastX, state.lastY, rect, state.rtl)\n const last = lastApplied\n if (last?.state === state && last.weeks === props.weeks && last.index === index) return\n lastApplied = { state, weeks: props.weeks, index }\n\n /*\n * The target is the event's START shifted by however many squares the pointer has crossed,\n * not the square it is over; so a bar grabbed by its third day keeps its third day under the\n * finger. The days come out of the flattened grid, which is already filtered to the weekdays\n * on show, so a hidden weekend is not counted.\n */\n const days = flat.value\n const from = days.indexOf(state.origin.start)\n const target =\n days[\n clamp((from === -1 ? state.grabIndex : from) + index - state.grabIndex, 0, days.length - 1)\n ]\n if (!target) return\n /*\n * Written only when the day changes. The gesture is a deep ref, so an equal but new preview\n * would still re-bucket and re-sort every event and re-render all 42 squares.\n */\n const next = moveEventToDay(state.origin, target)\n if (!sameTimes(state.preview, next)) state.preview = next\n}\n\n/**\n * The square `applyPoint` last placed the chip from, so the many `pointermove`s that fire\n * inside one square return before any date arithmetic.\n */\nlet lastApplied: { state: Gesture; weeks: MonthCell[][]; index: number } | null = null\n\n/** One arrow press on a chip held by the keyboard: a day sideways, a week vertically. */\nfunction onGrabStep(state: Gesture, step: GrabStep, item: E): boolean {\n if (step.kind !== 'grabMove') return false\n const days = flat.value\n const from = days.indexOf(state.preview.start)\n const target = days[clamp(from + dayStep(step.days, step.minutes), 0, days.length - 1)]\n if (!target || target === state.preview.start) return false\n state.preview = moveEventToDay(state.preview, target)\n emit('announce', m.value.calendar.movedTo(item.title, longDay(state.preview.start)))\n return true\n}\n\nfunction onCardClick(item: E) {\n // `pointerup` fires before `click`, so letting go at the end of a drag would ALSO open the\n // event; the consumer's editor over every chip the reader had just moved.\n if (consumeDrag()) return\n emit('event-activate', item)\n}\n\n/**\n * A click on the empty part of a day: the pointer's side of the Enter that activates a cell, so\n * a square drawn with a pointer cursor answers a click as it answers the key.\n */\nfunction onGridClick(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (target?.closest('.v-calendar-event, button')) return\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell || consumeDrag()) return\n emit('cell-activate', { date: cell.dataset.iso!, minutes: null })\n}\n\n// The same two members VCalendarTimeGrid exposes, so VCalendar drives either view without\n// asking which is on screen. A month has no hours, hence the empty `scrollToMinutes`.\ndefineExpose({\n /** Brings the focus onto the day the grid is currently pointing at. */\n focus: (options?: FocusOptions) => {\n if (tabbable.value) focusCell(cellId(tabbable.value), false, options)\n },\n /** Nothing to scroll to in a month view. */\n scrollToMinutes: () => {},\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-calendar-month\"\n :data-outside=\"gesture?.outside ? '' : undefined\"\n :style=\"{ '--calendar-columns': String(weekdayNames.length) }\"\n @focusout=\"onFocusout\"\n >\n <div class=\"v-calendar-month-head\" aria-hidden=\"true\">\n <span\n v-for=\"(name, index) in weekdayNames\"\n :key=\"index\"\n class=\"v-calendar-weekday v-calendar-month-weekday\"\n >\n {{ name }}\n </span>\n </div>\n\n <div\n ref=\"gridEl\"\n class=\"v-calendar-month-grid v-calendar-edge-cue\"\n role=\"grid\"\n :aria-label=\"label\"\n :data-edge=\"edgeCue\"\n @keydown=\"onKeydown\"\n @pointerdown=\"onGridPointerdown\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @pointerleave=\"onPointerleave\"\n @click=\"onGridClick\"\n >\n <div\n v-for=\"(week, row) in weeks\"\n :key=\"week[0]?.iso ?? row\"\n role=\"row\"\n class=\"v-calendar-month-week\"\n >\n <div\n v-for=\"cell in week\"\n :id=\"cellId(cell.iso)\"\n :key=\"cell.iso\"\n role=\"gridcell\"\n class=\"v-calendar-cell v-calendar-month-cell\"\n :data-iso=\"cell.iso\"\n :data-adjacent=\"cell.adjacent ?? undefined\"\n :tabindex=\"tabbable === cell.iso ? 0 : -1\"\n :aria-label=\"longDay(cell.iso)\"\n aria-keyshortcuts=\"Shift+Enter\"\n >\n <button\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-day\"\n :class=\"{ 'v-calendar-today': cell.iso === today }\"\n tabindex=\"-1\"\n :aria-label=\"m.calendar.openDay(longDay(cell.iso))\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ dayNumber(cell.iso) }}\n </button>\n\n <VCalendarEvent\n v-for=\"chip in dayEventsOf(cell.iso).shown\"\n :key=\"chip.event.id\"\n class=\"v-calendar-month-chip\"\n :event=\"chip.event\"\n layout=\"chip\"\n :disabled=\"disabled\"\n :time-text=\"chip.timeText\"\n v-bind=\"cardState(chip.event.id)\"\n @click=\"onCardClick(chip.event)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n\n <button\n v-if=\"dayEventsOf(cell.iso).hidden > 0\"\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-more\"\n tabindex=\"-1\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ m.calendar.moreEvents(dayEventsOf(cell.iso).hidden) }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar-month {\n display: flex;\n flex-direction: column;\n }\n\n .v-calendar-month-head,\n .v-calendar-month-week {\n display: grid;\n grid-template-columns: repeat(var(--calendar-columns), minmax(0, 1fr));\n }\n\n /* 4, the time grid's header layer: a dragged chip (3) and the edge strip (2) pass under the\n sticky names as the month scrolls, rather than over them. */\n .v-calendar-month-head {\n position: sticky;\n inset-block-start: 0;\n z-index: 4;\n background: var(--calendar-surface);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-month-weekday {\n padding-block: var(--vectis-space-2);\n text-align: center;\n }\n\n .v-calendar-month-grid {\n position: relative;\n display: flex;\n /* The rows share what height there is, so a month fills the box it was given instead of\n leaving a band of nothing under the last week. The floor keeps a day readable when\n that box is short. */\n flex: 1;\n flex-direction: column;\n min-block-size: 0;\n }\n\n .v-calendar-month-week {\n flex: 1;\n min-block-size: var(--vectis-control-size-calendar-month-cell);\n }\n\n .v-calendar-month-cell {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-control-size-calendar-gap);\n overflow: hidden;\n padding: var(--vectis-space-1);\n }\n\n /*\n * The days of the neighbouring months are kept; the grid is a fixed six rows, so the page\n * never jumps; but pushed back, since they are context rather than content.\n */\n .v-calendar-month-cell[data-adjacent] {\n background: var(--vectis-color-surface-sunken);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-calendar-month-day {\n align-self: center;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n inline-size: var(--vectis-control-height-sm);\n block-size: var(--vectis-control-height-sm);\n border-radius: var(--vectis-radius-pill);\n color: inherit;\n font-size: var(--vectis-text-body-md-size);\n }\n\n /* Disjoint from today rather than less specific: the two live in different sheets, where a\n tie would be settled by whichever one the consumer's bundler emitted last. */\n .v-calendar-month-day:hover:not(.v-calendar-today) {\n background: var(--calendar-hover-bg);\n }\n\n /*\n * The chip rule in VCalendarEvent's own sheet is (0,2,0) against this (0,1,0), so declaring\n * the size directly would lose; and matching its specificity would only hand the decision to\n * whichever sheet the consumer's bundler emitted last, which is the failure the library's\n * one-sheet-per-component rule exists to prevent.\n */\n .v-calendar-month-chip {\n --calendar-chip-height: var(--vectis-control-size-calendar-allday-lane);\n\n flex: none;\n }\n\n .v-calendar-month-more {\n flex: none;\n padding-inline: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n text-align: start;\n }\n\n .v-calendar-month-more:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n /* The neighbouring months' days are told apart by a tint forced colors removes: grey text\n keeps them apart. */\n @media (forced-colors: active) {\n .v-calendar-month-cell[data-adjacent] {\n forced-color-adjust: none;\n background: Canvas;\n color: GrayText;\n }\n }\n}\n</style>\n"],"mappings":""}
@@ -130,23 +130,11 @@ var fe = ["data-outside"], pe = {
130
130
  let n = k.value[t(s + Z(a.days, a.minutes), 0, k.value.length - 1)];
131
131
  if (!n) return;
132
132
  e.preventDefault(), Y(n);
133
- return;
134
- }
135
- if (a.kind === "rowEdge") {
133
+ } else if (a.kind === "rowEdge") {
136
134
  let n = Math.floor(s / c), r = a.edge === "start" ? n * c : n * c + c - 1, i = k.value[t(r, 0, k.value.length - 1)];
137
135
  if (!i) return;
138
136
  e.preventDefault(), Y(i);
139
- return;
140
- }
141
- if (a.kind === "period") {
142
- e.preventDefault(), T("step", a.delta);
143
- return;
144
- }
145
- if (a.kind === "openDay") {
146
- e.preventDefault(), T("day-activate", o);
147
- return;
148
- }
149
- a.kind === "activate" && (e.preventDefault(), T("cell-activate", {
137
+ } else a.kind === "period" ? (e.preventDefault(), T("step", a.delta)) : a.kind === "openDay" ? (e.preventDefault(), T("day-activate", o)) : a.kind === "activate" && (e.preventDefault(), T("cell-activate", {
150
138
  date: o,
151
139
  minutes: null
152
140
  }));
@@ -191,8 +179,8 @@ var fe = ["data-outside"], pe = {
191
179
  let $ = null;
192
180
  function Ne(e, n, r) {
193
181
  if (n.kind !== "grabMove") return !1;
194
- let i = k.value, a = i[t(i.indexOf(e.preview.start) + Z(n.days, n.minutes), 0, i.length - 1)];
195
- return !a || a === e.preview.start ? !1 : (e.preview = s(e.preview, a), T("announce", D.value.calendar.movedTo(r.title, N(e.preview.start))), !0);
182
+ let i = k.value, a = i.indexOf(e.preview.start), o = i[t(a + Z(n.days, n.minutes), 0, i.length - 1)];
183
+ return !o || o === e.preview.start ? !1 : (e.preview = s(e.preview, o), T("announce", D.value.calendar.movedTo(r.title, N(e.preview.start))), !0);
196
184
  }
197
185
  function Pe(e) {
198
186
  R() || T("event-activate", e);
@@ -201,7 +189,7 @@ var fe = ["data-outside"], pe = {
201
189
  let t = e.target;
202
190
  if (t?.closest(".v-calendar-event, button")) return;
203
191
  let n = t?.closest(".v-calendar-month-cell");
204
- !n || R() || T("cell-activate", {
192
+ n && !R() && T("cell-activate", {
205
193
  date: n.dataset.iso,
206
194
  minutes: null
207
195
  });
@@ -1 +1 @@
1
- {"version":3,"file":"VCalendarMonth.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/VCalendar/VCalendarMonth.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @keyboard @a11y @core\n/**\n * Month cells summarize events and overflow counts. Shared gesture logic supplies dragging and\n * keyboard grabs; grid focus remains in this view.\n */\nimport { computed, ref, useId } from 'vue'\n\nimport { formatDateDisplay } from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { formatTimeDisplay } from '../../utils/time'\n\nimport { useMessages } from '../../i18n/state'\n\nimport VCalendarEvent from './VCalendarEvent.vue'\nimport { gridGeometryOf, useCalendarGesture, type GestureBase, type GrabStep } from './gesture'\nimport { calendarIntent } from './keyboard'\nimport {\n eventsByDay,\n isAllDayEvent,\n moveEventToDay,\n pointToMonthCell,\n sameTimes,\n timesOf,\n type MonthCell,\n} from './layout'\nimport type {\n ActivatedCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n} from './types'\n\nexport interface CalendarMonthProps<T> {\n /** The weeks, already padded to a fixed height and filtered to the visible weekdays. */\n weeks: MonthCell[][]\n events: T[]\n locale: string\n format: CalendarFormat\n today: string | null\n /** How many events a day shows before it starts counting the rest. */\n monthEventLimit: number\n /** Whether events can be moved from one day to another. */\n editable: boolean\n /** Whether the whole calendar is frozen, which also takes its cards out of the tab order. */\n disabled: boolean\n /** The node telling a reader how a chip can be moved, shared by every one of them. */\n hintId: string\n /** How long a drag rests against an edge before the month turns. Zero turns paging off. */\n edgeStepDelay: number\n label: string\n}\n\nconst props = defineProps<CalendarMonthProps<E>>()\n\nconst emit = defineEmits<{\n /** A day was asked to be opened on its own. */\n 'day-activate': [iso: string]\n /** An empty part of a day was activated. A month has no hours, so the time is left to the calendar. */\n 'cell-activate': [cell: ActivatedCell]\n 'event-activate': [event: E]\n /** An event was dropped on another day. A month has no hours, so it is only ever a move. */\n 'event-drop': [id: CalendarEventId, times: CalendarEventTimes, kind: 'move']\n /** Something happened that a reader who cannot see the grid needs told. */\n announce: [message: string]\n step: [delta: -1 | 1]\n}>()\n\n/** Which day holds the tab stop. Only the date matters here; the hour is the grid's affair. */\nconst focused = defineModel<string>('focused', { required: true })\n\ndefineSlots<{\n event?(props: CalendarEventSlotProps<E>): unknown\n}>()\n\nconst m = useMessages()\nconst uid = useId()\n\nconst cellId = (iso: string) => `${uid}-m-${iso}`\n\n/** The flattened list of days: what the arrows travel along, and what the events are filed by. */\nconst flat = computed(() => props.weeks.flat().map((cell) => cell.iso))\nconst columnCount = computed(() => props.weeks[0]?.length ?? 1)\n\n/** The column headings, taken from the first week so they always match the columns drawn. */\nconst weekdayNames = computed(() =>\n (props.weeks[0] ?? []).map((cell) =>\n formatDateDisplay(cell.iso, props.locale, { weekday: 'short' }),\n ),\n)\n\n/*\n * `longDay` is asked for twice in the template; the square's own `aria-label`, then the name of\n * the button that opens the day; so the 42 cells cost 126 `Intl` formats a render, and a drag\n * re-renders the whole grid each time it carries a chip onto another day. The map depends on\n * the weeks and the locale alone, so it survives every frame of a drag.\n */\nconst dayLabels = computed(() => {\n const map = new Map<string, { number: string; long: string }>()\n for (const iso of flat.value) {\n map.set(iso, {\n number: formatDateDisplay(iso, props.locale, { day: 'numeric' }),\n long: formatDateDisplay(iso, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n }),\n })\n }\n return map\n})\n\nconst dayNumber = (iso: string) => dayLabels.value.get(iso)?.number ?? ''\n\n/*\n * A day outside the grid can reach this; the announcement after a move names the day the event\n * landed on, which may be the next month; so it falls back to formatting rather than assuming\n * the map holds it.\n */\nconst longDay = (iso: string) =>\n dayLabels.value.get(iso)?.long ??\n formatDateDisplay(iso, props.locale, { weekday: 'long', day: 'numeric', month: 'long' })\n\n/** A chip being carried from one day to another, by pointer or by keyboard. */\ninterface Gesture extends GestureBase {\n /** Which square the pointer took hold in, so a long bar does not jump under it. */\n grabIndex: number\n}\n\nconst gridEl = ref<HTMLElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\nconst {\n gesture,\n withPreview,\n edgeCue,\n isRtl,\n start,\n endLastGesture,\n consumeDrag,\n onPointermove,\n onPointerup,\n onPointercancel,\n onPointerleave,\n onFocusout,\n onCardKeydown,\n focusCell,\n idOfCard,\n cardState,\n} = useCalendarGesture<E, Gesture>({\n events: () => props.events,\n onRelease: () => {\n lastApplied = null\n },\n eventOf: (id): E | undefined => eventsById.value.get(id),\n rootEl,\n // Captured on the GRID rather than on the chip, because a chip is redrawn on another day the\n // moment the drag starts and a captured element that moves takes the pointer with it.\n captureEl: gridEl,\n measureEl: gridEl,\n columns: () => columnCount.value,\n editable: () => props.editable,\n hintId: () => props.hintId,\n // The step is irrelevant to a month, which has no hours: 1 keeps the table honest without\n // asking the calendar for a number this view never uses.\n slotMinutes: () => 1,\n edgeStepDelay: () => props.edgeStepDelay,\n layout: () => props.weeks,\n applyPoint,\n onDrop: (state) => emit('event-drop', state.id, state.preview, 'move'),\n grab: (item) =>\n props.editable\n ? {\n id: item.id,\n origin: timesOf(item),\n pointerId: null,\n originX: 0,\n originY: 0,\n grabIndex: 0,\n }\n : null,\n onGrabStep,\n step: (delta) => emit('step', delta),\n announce: (message) => emit('announce', message),\n})\n\n/**\n * The events as the month should currently DRAW them; the model, with the one being carried put\n * wherever it now is.\n */\nconst drawnEvents = computed<E[]>(() => (gesture.value ? withPreview(props.events) : props.events))\n\n/** Every day's events, worked out once rather than once per chip. */\nconst byDay = computed(() => eventsByDay(drawnEvents.value, flat.value))\n\nconst eventsById = computed(\n () => new Map<CalendarEventId, E>(drawnEvents.value.map((item) => [item.id, item])),\n)\n\n/**\n * A chip says when it happens only when that is not obvious: an all-day event has no time to\n * give, and the day it sits in already says which day it is.\n */\nconst timeTextOf = computed(() => {\n const { locale, format } = props\n const cache = new Map<string, string>()\n return (event: E): string => {\n if (isAllDayEvent(event)) return ''\n let text = cache.get(event.startTime)\n if (text === undefined) {\n text = formatTimeDisplay(event.startTime, locale, format)\n cache.set(event.startTime, text)\n }\n return text\n }\n})\n\n/** One chip as the template draws it, its time already written out. */\ninterface Chip {\n event: E\n timeText: string\n}\n\n/** What each square draws, and how many it had to leave out; one Map, built in a single pass. */\nconst dayEvents = computed(() => {\n const map = new Map<string, { shown: Chip[]; hidden: number }>()\n const textOf = timeTextOf.value\n const limit = Math.max(0, props.monthEventLimit)\n for (const [iso, all] of byDay.value) {\n const shown = all.length > limit ? all.slice(0, limit) : all\n map.set(iso, {\n shown: shown.map((event) => ({ event, timeText: textOf(event) })),\n hidden: Math.max(0, all.length - limit),\n })\n }\n return map\n})\n\n/** A square with nothing on it, shared rather than allocated per empty cell. */\nconst NO_EVENTS: { shown: Chip[]; hidden: number } = Object.freeze({ shown: [], hidden: 0 })\nconst dayEventsOf = (iso: string) => dayEvents.value.get(iso) ?? NO_EVENTS\n\nfunction moveFocusTo(iso: string) {\n focused.value = iso\n // @a11y\n focusCell(cellId(iso), true)\n}\n\nconst tabbable = computed(() =>\n flat.value.includes(focused.value) ? focused.value : (flat.value[0] ?? null),\n)\n\n/**\n * The same table serves both views, so its two axes are read differently here: a sideways step\n * is one day, and a vertical one is a whole week rather than an hour.\n */\nconst dayStep = (days: number, minutes: number) =>\n days !== 0 ? days : Math.sign(minutes) * columnCount.value\n\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement | null\n\n // A chip answers its own table, delegated here on the same element as the pointer so the\n // two input paths cannot drift apart.\n const card = target?.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n onCardKeydown(event, card)\n return\n }\n\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell) return\n\n const intent = calendarIntent(event, 'cell', 1, isRtl())\n if (!intent) return\n\n const iso = cell.dataset.iso!\n const index = flat.value.indexOf(iso)\n const columns = columnCount.value\n\n if (intent.kind === 'moveFocus') {\n const next =\n flat.value[clamp(index + dayStep(intent.days, intent.minutes), 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'rowEdge') {\n const row = Math.floor(index / columns)\n const edge = intent.edge === 'start' ? row * columns : row * columns + columns - 1\n const next = flat.value[clamp(edge, 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'period') {\n event.preventDefault()\n emit('step', intent.delta)\n return\n }\n\n if (intent.kind === 'openDay') {\n event.preventDefault()\n emit('day-activate', iso)\n return\n }\n\n if (intent.kind === 'activate') {\n event.preventDefault()\n emit('cell-activate', { date: iso, minutes: null })\n }\n}\n\n/** Which of the flattened squares a point falls on. */\nfunction indexAt(x: number, y: number, rect: DOMRect, rtl: boolean): number {\n const cell = pointToMonthCell(\n { x, y },\n { ...gridGeometryOf(rect, columnCount.value, rtl), rows: props.weeks.length || 1 },\n rtl,\n )\n return cell.rowIndex * columnCount.value + cell.columnIndex\n}\n\nfunction onGridPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || !props.editable || event.button !== 0) return\n\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n const item = card ? eventsById.value.get(idOfCard(card)) : undefined\n const rect = gridEl.value?.getBoundingClientRect()\n if (!item || !rect) return\n const rtl = isRtl()\n\n start(\n {\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabIndex: indexAt(event.clientX, event.clientY, rect, rtl),\n },\n rtl,\n )\n}\n\n/** Works out which day the chip now belongs to, from wherever the pointer last was. */\nfunction applyPoint(state: Gesture, rect: DOMRect) {\n const index = indexAt(state.lastX, state.lastY, rect, state.rtl)\n const last = lastApplied\n if (last?.state === state && last.weeks === props.weeks && last.index === index) return\n lastApplied = { state, weeks: props.weeks, index }\n\n /*\n * The target is the event's START shifted by however many squares the pointer has crossed,\n * not the square it is over; so a bar grabbed by its third day keeps its third day under the\n * finger. The days come out of the flattened grid, which is already filtered to the weekdays\n * on show, so a hidden weekend is not counted.\n */\n const days = flat.value\n const from = days.indexOf(state.origin.start)\n const target =\n days[\n clamp((from === -1 ? state.grabIndex : from) + index - state.grabIndex, 0, days.length - 1)\n ]\n if (!target) return\n /*\n * Written only when the day changes. The gesture is a deep ref, so an equal but new preview\n * would still re-bucket and re-sort every event and re-render all 42 squares.\n */\n const next = moveEventToDay(state.origin, target)\n if (!sameTimes(state.preview, next)) state.preview = next\n}\n\n/**\n * The square `applyPoint` last placed the chip from, so the many `pointermove`s that fire\n * inside one square return before any date arithmetic.\n */\nlet lastApplied: { state: Gesture; weeks: MonthCell[][]; index: number } | null = null\n\n/** One arrow press on a chip held by the keyboard: a day sideways, a week vertically. */\nfunction onGrabStep(state: Gesture, step: GrabStep, item: E): boolean {\n if (step.kind !== 'grabMove') return false\n const days = flat.value\n const from = days.indexOf(state.preview.start)\n const target = days[clamp(from + dayStep(step.days, step.minutes), 0, days.length - 1)]\n if (!target || target === state.preview.start) return false\n state.preview = moveEventToDay(state.preview, target)\n emit('announce', m.value.calendar.movedTo(item.title, longDay(state.preview.start)))\n return true\n}\n\nfunction onCardClick(item: E) {\n // `pointerup` fires before `click`, so letting go at the end of a drag would ALSO open the\n // event; the consumer's editor over every chip the reader had just moved.\n if (consumeDrag()) return\n emit('event-activate', item)\n}\n\n/**\n * A click on the empty part of a day: the pointer's side of the Enter that activates a cell, so\n * a square drawn with a pointer cursor answers a click as it answers the key.\n */\nfunction onGridClick(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (target?.closest('.v-calendar-event, button')) return\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell || consumeDrag()) return\n emit('cell-activate', { date: cell.dataset.iso!, minutes: null })\n}\n\n// The same two members VCalendarTimeGrid exposes, so VCalendar drives either view without\n// asking which is on screen. A month has no hours, hence the empty `scrollToMinutes`.\ndefineExpose({\n /** Brings the focus onto the day the grid is currently pointing at. */\n focus: (options?: FocusOptions) => {\n if (tabbable.value) focusCell(cellId(tabbable.value), false, options)\n },\n /** Nothing to scroll to in a month view. */\n scrollToMinutes: () => {},\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-calendar-month\"\n :data-outside=\"gesture?.outside ? '' : undefined\"\n :style=\"{ '--calendar-columns': String(weekdayNames.length) }\"\n @focusout=\"onFocusout\"\n >\n <div class=\"v-calendar-month-head\" aria-hidden=\"true\">\n <span\n v-for=\"(name, index) in weekdayNames\"\n :key=\"index\"\n class=\"v-calendar-weekday v-calendar-month-weekday\"\n >\n {{ name }}\n </span>\n </div>\n\n <div\n ref=\"gridEl\"\n class=\"v-calendar-month-grid v-calendar-edge-cue\"\n role=\"grid\"\n :aria-label=\"label\"\n :data-edge=\"edgeCue\"\n @keydown=\"onKeydown\"\n @pointerdown=\"onGridPointerdown\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @pointerleave=\"onPointerleave\"\n @click=\"onGridClick\"\n >\n <div\n v-for=\"(week, row) in weeks\"\n :key=\"week[0]?.iso ?? row\"\n role=\"row\"\n class=\"v-calendar-month-week\"\n >\n <div\n v-for=\"cell in week\"\n :id=\"cellId(cell.iso)\"\n :key=\"cell.iso\"\n role=\"gridcell\"\n class=\"v-calendar-cell v-calendar-month-cell\"\n :data-iso=\"cell.iso\"\n :data-adjacent=\"cell.adjacent ?? undefined\"\n :tabindex=\"tabbable === cell.iso ? 0 : -1\"\n :aria-label=\"longDay(cell.iso)\"\n aria-keyshortcuts=\"Shift+Enter\"\n >\n <button\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-day\"\n :class=\"{ 'v-calendar-today': cell.iso === today }\"\n tabindex=\"-1\"\n :aria-label=\"m.calendar.openDay(longDay(cell.iso))\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ dayNumber(cell.iso) }}\n </button>\n\n <VCalendarEvent\n v-for=\"chip in dayEventsOf(cell.iso).shown\"\n :key=\"chip.event.id\"\n class=\"v-calendar-month-chip\"\n :event=\"chip.event\"\n layout=\"chip\"\n :disabled=\"disabled\"\n :time-text=\"chip.timeText\"\n v-bind=\"cardState(chip.event.id)\"\n @click=\"onCardClick(chip.event)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n\n <button\n v-if=\"dayEventsOf(cell.iso).hidden > 0\"\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-more\"\n tabindex=\"-1\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ m.calendar.moreEvents(dayEventsOf(cell.iso).hidden) }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar-month {\n display: flex;\n flex-direction: column;\n }\n\n .v-calendar-month-head,\n .v-calendar-month-week {\n display: grid;\n grid-template-columns: repeat(var(--calendar-columns), minmax(0, 1fr));\n }\n\n /* 4, the time grid's header layer: a dragged chip (3) and the edge strip (2) pass under the\n sticky names as the month scrolls, rather than over them. */\n .v-calendar-month-head {\n position: sticky;\n inset-block-start: 0;\n z-index: 4;\n background: var(--calendar-surface);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-month-weekday {\n padding-block: var(--vectis-space-2);\n text-align: center;\n }\n\n .v-calendar-month-grid {\n position: relative;\n display: flex;\n /* The rows share what height there is, so a month fills the box it was given instead of\n leaving a band of nothing under the last week. The floor keeps a day readable when\n that box is short. */\n flex: 1;\n flex-direction: column;\n min-block-size: 0;\n }\n\n .v-calendar-month-week {\n flex: 1;\n min-block-size: var(--vectis-control-size-calendar-month-cell);\n }\n\n .v-calendar-month-cell {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-control-size-calendar-gap);\n overflow: hidden;\n padding: var(--vectis-space-1);\n }\n\n /*\n * The days of the neighbouring months are kept; the grid is a fixed six rows, so the page\n * never jumps; but pushed back, since they are context rather than content.\n */\n .v-calendar-month-cell[data-adjacent] {\n background: var(--vectis-color-surface-sunken);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-calendar-month-day {\n align-self: center;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n inline-size: var(--vectis-control-height-sm);\n block-size: var(--vectis-control-height-sm);\n border-radius: var(--vectis-radius-pill);\n color: inherit;\n font-size: var(--vectis-text-body-md-size);\n }\n\n /* Disjoint from today rather than less specific: the two live in different sheets, where a\n tie would be settled by whichever one the consumer's bundler emitted last. */\n .v-calendar-month-day:hover:not(.v-calendar-today) {\n background: var(--calendar-hover-bg);\n }\n\n /*\n * The chip rule in VCalendarEvent's own sheet is (0,2,0) against this (0,1,0), so declaring\n * the size directly would lose; and matching its specificity would only hand the decision to\n * whichever sheet the consumer's bundler emitted last, which is the failure the library's\n * one-sheet-per-component rule exists to prevent.\n */\n .v-calendar-month-chip {\n --calendar-chip-height: var(--vectis-control-size-calendar-allday-lane);\n\n flex: none;\n }\n\n .v-calendar-month-more {\n flex: none;\n padding-inline: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n text-align: start;\n }\n\n .v-calendar-month-more:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n /* The neighbouring months' days are told apart by a tint forced colors removes: grey text\n keeps them apart. */\n @media (forced-colors: active) {\n .v-calendar-month-cell[data-adjacent] {\n forced-color-adjust: none;\n background: Canvas;\n color: GrayText;\n }\n }\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"VCalendarMonth.vue_vue_type_script_setup_true_lang.js","names":["$slots","_useModel","_openBlock","_createElementBlock","_unref","_normalizeStyle","_createElementVNode","_Fragment","_renderList","_toDisplayString","_normalizeClass","_createBlock","VCalendarEvent","_mergeProps","_createSlots","_withCtx","_renderSlot","_createCommentVNode","VCalendarEvent_default","_hoisted_1","_hoisted_2","_hoisted_3","_hoisted_4","_hoisted_5","_hoisted_6","VCalendarMonth_vue_vue_type_script_setup_true_lang_default","\"focused\"","\"focusedModifiers\"","__expose","__emit","props","__props","emit","focused","useModel","m","useMessages","uid","useId","cellId","iso","flat","computed","cell","columnCount","weekdayNames","formatDateDisplay","dayLabels","map","dayNumber","longDay","gridEl","ref","rootEl","useCalendarGesture","eventsById","id","state","item","timesOf","delta","message","drawnEvents","gesture","withPreview","byDay","eventsByDay","timeTextOf","cache","isAllDayEvent","event","text","formatTimeDisplay","locale","format","dayEvents","textOf","limit","all","shown","NO_EVENTS","dayEventsOf","moveFocusTo","focusCell","tabbable","dayStep","days","minutes","onKeydown","target","card","intent","calendarIntent","isRtl","index","columns","next","clamp","row","edge","indexAt","pointToMonthCell","gridGeometryOf","rect","rtl","onGridPointerdown","endLastGesture","idOfCard","applyPoint","last","lastApplied","from","moveEventToDay","sameTimes","onGrabStep","step","onCardClick","consumeDrag","onGridClick","options","openBlock","createElementBlock","unref","normalizeStyle","_cache","args","onFocusout","createElementVNode","Fragment","renderList","toDisplayString","name","edgeCue","onPointermove","onPointerup","onPointercancel","onPointerleave","week","normalizeClass","createBlock","mergeProps","chip","cardState","createSlots","_ctx","withCtx","renderSlot","slotProps","createCommentVNode"],"sources":["../../../src/components/VCalendar/VCalendarMonth.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @keyboard @a11y @core\n/**\n * Month cells summarize events and overflow counts. Shared gesture logic supplies dragging and\n * keyboard grabs; grid focus remains in this view.\n */\nimport { computed, ref, useId } from 'vue'\n\nimport { formatDateDisplay } from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { formatTimeDisplay } from '../../utils/time'\n\nimport { useMessages } from '../../i18n/state'\n\nimport VCalendarEvent from './VCalendarEvent.vue'\nimport { gridGeometryOf, useCalendarGesture, type GestureBase, type GrabStep } from './gesture'\nimport { calendarIntent } from './keyboard'\nimport {\n eventsByDay,\n isAllDayEvent,\n moveEventToDay,\n pointToMonthCell,\n sameTimes,\n timesOf,\n type MonthCell,\n} from './layout'\nimport type {\n ActivatedCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n} from './types'\n\nexport interface CalendarMonthProps<T> {\n /** The weeks, already padded to a fixed height and filtered to the visible weekdays. */\n weeks: MonthCell[][]\n events: T[]\n locale: string\n format: CalendarFormat\n today: string | null\n /** How many events a day shows before it starts counting the rest. */\n monthEventLimit: number\n /** Whether events can be moved from one day to another. */\n editable: boolean\n /** Whether the whole calendar is frozen, which also takes its cards out of the tab order. */\n disabled: boolean\n /** The node telling a reader how a chip can be moved, shared by every one of them. */\n hintId: string\n /** How long a drag rests against an edge before the month turns. Zero turns paging off. */\n edgeStepDelay: number\n label: string\n}\n\nconst props = defineProps<CalendarMonthProps<E>>()\n\nconst emit = defineEmits<{\n /** A day was asked to be opened on its own. */\n 'day-activate': [iso: string]\n /** An empty part of a day was activated. A month has no hours, so the time is left to the calendar. */\n 'cell-activate': [cell: ActivatedCell]\n 'event-activate': [event: E]\n /** An event was dropped on another day. A month has no hours, so it is only ever a move. */\n 'event-drop': [id: CalendarEventId, times: CalendarEventTimes, kind: 'move']\n /** Something happened that a reader who cannot see the grid needs told. */\n announce: [message: string]\n step: [delta: -1 | 1]\n}>()\n\n/** Which day holds the tab stop. Only the date matters here; the hour is the grid's affair. */\nconst focused = defineModel<string>('focused', { required: true })\n\ndefineSlots<{\n event?(props: CalendarEventSlotProps<E>): unknown\n}>()\n\nconst m = useMessages()\nconst uid = useId()\n\nconst cellId = (iso: string) => `${uid}-m-${iso}`\n\n/** The flattened list of days: what the arrows travel along, and what the events are filed by. */\nconst flat = computed(() => props.weeks.flat().map((cell) => cell.iso))\nconst columnCount = computed(() => props.weeks[0]?.length ?? 1)\n\n/** The column headings, taken from the first week so they always match the columns drawn. */\nconst weekdayNames = computed(() =>\n (props.weeks[0] ?? []).map((cell) =>\n formatDateDisplay(cell.iso, props.locale, { weekday: 'short' }),\n ),\n)\n\n/*\n * `longDay` is asked for twice in the template; the square's own `aria-label`, then the name of\n * the button that opens the day; so the 42 cells cost 126 `Intl` formats a render, and a drag\n * re-renders the whole grid each time it carries a chip onto another day. The map depends on\n * the weeks and the locale alone, so it survives every frame of a drag.\n */\nconst dayLabels = computed(() => {\n const map = new Map<string, { number: string; long: string }>()\n for (const iso of flat.value) {\n map.set(iso, {\n number: formatDateDisplay(iso, props.locale, { day: 'numeric' }),\n long: formatDateDisplay(iso, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n }),\n })\n }\n return map\n})\n\nconst dayNumber = (iso: string) => dayLabels.value.get(iso)?.number ?? ''\n\n/*\n * A day outside the grid can reach this; the announcement after a move names the day the event\n * landed on, which may be the next month; so it falls back to formatting rather than assuming\n * the map holds it.\n */\nconst longDay = (iso: string) =>\n dayLabels.value.get(iso)?.long ??\n formatDateDisplay(iso, props.locale, { weekday: 'long', day: 'numeric', month: 'long' })\n\n/** A chip being carried from one day to another, by pointer or by keyboard. */\ninterface Gesture extends GestureBase {\n /** Which square the pointer took hold in, so a long bar does not jump under it. */\n grabIndex: number\n}\n\nconst gridEl = ref<HTMLElement | null>(null)\nconst rootEl = ref<HTMLElement | null>(null)\n\nconst {\n gesture,\n withPreview,\n edgeCue,\n isRtl,\n start,\n endLastGesture,\n consumeDrag,\n onPointermove,\n onPointerup,\n onPointercancel,\n onPointerleave,\n onFocusout,\n onCardKeydown,\n focusCell,\n idOfCard,\n cardState,\n} = useCalendarGesture<E, Gesture>({\n events: () => props.events,\n onRelease: () => {\n lastApplied = null\n },\n eventOf: (id): E | undefined => eventsById.value.get(id),\n rootEl,\n // Captured on the GRID rather than on the chip, because a chip is redrawn on another day the\n // moment the drag starts and a captured element that moves takes the pointer with it.\n captureEl: gridEl,\n measureEl: gridEl,\n columns: () => columnCount.value,\n editable: () => props.editable,\n hintId: () => props.hintId,\n // The step is irrelevant to a month, which has no hours: 1 keeps the table honest without\n // asking the calendar for a number this view never uses.\n slotMinutes: () => 1,\n edgeStepDelay: () => props.edgeStepDelay,\n layout: () => props.weeks,\n applyPoint,\n onDrop: (state) => emit('event-drop', state.id, state.preview, 'move'),\n grab: (item) =>\n props.editable\n ? {\n id: item.id,\n origin: timesOf(item),\n pointerId: null,\n originX: 0,\n originY: 0,\n grabIndex: 0,\n }\n : null,\n onGrabStep,\n step: (delta) => emit('step', delta),\n announce: (message) => emit('announce', message),\n})\n\n/**\n * The events as the month should currently DRAW them; the model, with the one being carried put\n * wherever it now is.\n */\nconst drawnEvents = computed<E[]>(() => (gesture.value ? withPreview(props.events) : props.events))\n\n/** Every day's events, worked out once rather than once per chip. */\nconst byDay = computed(() => eventsByDay(drawnEvents.value, flat.value))\n\nconst eventsById = computed(\n () => new Map<CalendarEventId, E>(drawnEvents.value.map((item) => [item.id, item])),\n)\n\n/**\n * A chip says when it happens only when that is not obvious: an all-day event has no time to\n * give, and the day it sits in already says which day it is.\n */\nconst timeTextOf = computed(() => {\n const { locale, format } = props\n const cache = new Map<string, string>()\n return (event: E): string => {\n if (isAllDayEvent(event)) return ''\n let text = cache.get(event.startTime)\n if (text === undefined) {\n text = formatTimeDisplay(event.startTime, locale, format)\n cache.set(event.startTime, text)\n }\n return text\n }\n})\n\n/** One chip as the template draws it, its time already written out. */\ninterface Chip {\n event: E\n timeText: string\n}\n\n/** What each square draws, and how many it had to leave out; one Map, built in a single pass. */\nconst dayEvents = computed(() => {\n const map = new Map<string, { shown: Chip[]; hidden: number }>()\n const textOf = timeTextOf.value\n const limit = Math.max(0, props.monthEventLimit)\n for (const [iso, all] of byDay.value) {\n const shown = all.length > limit ? all.slice(0, limit) : all\n map.set(iso, {\n shown: shown.map((event) => ({ event, timeText: textOf(event) })),\n hidden: Math.max(0, all.length - limit),\n })\n }\n return map\n})\n\n/** A square with nothing on it, shared rather than allocated per empty cell. */\nconst NO_EVENTS: { shown: Chip[]; hidden: number } = Object.freeze({ shown: [], hidden: 0 })\nconst dayEventsOf = (iso: string) => dayEvents.value.get(iso) ?? NO_EVENTS\n\nfunction moveFocusTo(iso: string) {\n focused.value = iso\n // @a11y\n focusCell(cellId(iso), true)\n}\n\nconst tabbable = computed(() =>\n flat.value.includes(focused.value) ? focused.value : (flat.value[0] ?? null),\n)\n\n/**\n * The same table serves both views, so its two axes are read differently here: a sideways step\n * is one day, and a vertical one is a whole week rather than an hour.\n */\nconst dayStep = (days: number, minutes: number) =>\n days !== 0 ? days : Math.sign(minutes) * columnCount.value\n\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement | null\n\n // A chip answers its own table, delegated here on the same element as the pointer so the\n // two input paths cannot drift apart.\n const card = target?.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n onCardKeydown(event, card)\n return\n }\n\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell) return\n\n const intent = calendarIntent(event, 'cell', 1, isRtl())\n if (!intent) return\n\n const iso = cell.dataset.iso!\n const index = flat.value.indexOf(iso)\n const columns = columnCount.value\n\n if (intent.kind === 'moveFocus') {\n const next =\n flat.value[clamp(index + dayStep(intent.days, intent.minutes), 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'rowEdge') {\n const row = Math.floor(index / columns)\n const edge = intent.edge === 'start' ? row * columns : row * columns + columns - 1\n const next = flat.value[clamp(edge, 0, flat.value.length - 1)]\n if (!next) return\n event.preventDefault()\n moveFocusTo(next)\n return\n }\n\n if (intent.kind === 'period') {\n event.preventDefault()\n emit('step', intent.delta)\n return\n }\n\n if (intent.kind === 'openDay') {\n event.preventDefault()\n emit('day-activate', iso)\n return\n }\n\n if (intent.kind === 'activate') {\n event.preventDefault()\n emit('cell-activate', { date: iso, minutes: null })\n }\n}\n\n/** Which of the flattened squares a point falls on. */\nfunction indexAt(x: number, y: number, rect: DOMRect, rtl: boolean): number {\n const cell = pointToMonthCell(\n { x, y },\n { ...gridGeometryOf(rect, columnCount.value, rtl), rows: props.weeks.length || 1 },\n rtl,\n )\n return cell.rowIndex * columnCount.value + cell.columnIndex\n}\n\nfunction onGridPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || !props.editable || event.button !== 0) return\n\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n const item = card ? eventsById.value.get(idOfCard(card)) : undefined\n const rect = gridEl.value?.getBoundingClientRect()\n if (!item || !rect) return\n const rtl = isRtl()\n\n start(\n {\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabIndex: indexAt(event.clientX, event.clientY, rect, rtl),\n },\n rtl,\n )\n}\n\n/** Works out which day the chip now belongs to, from wherever the pointer last was. */\nfunction applyPoint(state: Gesture, rect: DOMRect) {\n const index = indexAt(state.lastX, state.lastY, rect, state.rtl)\n const last = lastApplied\n if (last?.state === state && last.weeks === props.weeks && last.index === index) return\n lastApplied = { state, weeks: props.weeks, index }\n\n /*\n * The target is the event's START shifted by however many squares the pointer has crossed,\n * not the square it is over; so a bar grabbed by its third day keeps its third day under the\n * finger. The days come out of the flattened grid, which is already filtered to the weekdays\n * on show, so a hidden weekend is not counted.\n */\n const days = flat.value\n const from = days.indexOf(state.origin.start)\n const target =\n days[\n clamp((from === -1 ? state.grabIndex : from) + index - state.grabIndex, 0, days.length - 1)\n ]\n if (!target) return\n /*\n * Written only when the day changes. The gesture is a deep ref, so an equal but new preview\n * would still re-bucket and re-sort every event and re-render all 42 squares.\n */\n const next = moveEventToDay(state.origin, target)\n if (!sameTimes(state.preview, next)) state.preview = next\n}\n\n/**\n * The square `applyPoint` last placed the chip from, so the many `pointermove`s that fire\n * inside one square return before any date arithmetic.\n */\nlet lastApplied: { state: Gesture; weeks: MonthCell[][]; index: number } | null = null\n\n/** One arrow press on a chip held by the keyboard: a day sideways, a week vertically. */\nfunction onGrabStep(state: Gesture, step: GrabStep, item: E): boolean {\n if (step.kind !== 'grabMove') return false\n const days = flat.value\n const from = days.indexOf(state.preview.start)\n const target = days[clamp(from + dayStep(step.days, step.minutes), 0, days.length - 1)]\n if (!target || target === state.preview.start) return false\n state.preview = moveEventToDay(state.preview, target)\n emit('announce', m.value.calendar.movedTo(item.title, longDay(state.preview.start)))\n return true\n}\n\nfunction onCardClick(item: E) {\n // `pointerup` fires before `click`, so letting go at the end of a drag would ALSO open the\n // event; the consumer's editor over every chip the reader had just moved.\n if (consumeDrag()) return\n emit('event-activate', item)\n}\n\n/**\n * A click on the empty part of a day: the pointer's side of the Enter that activates a cell, so\n * a square drawn with a pointer cursor answers a click as it answers the key.\n */\nfunction onGridClick(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (target?.closest('.v-calendar-event, button')) return\n const cell = target?.closest<HTMLElement>('.v-calendar-month-cell')\n if (!cell || consumeDrag()) return\n emit('cell-activate', { date: cell.dataset.iso!, minutes: null })\n}\n\n// The same two members VCalendarTimeGrid exposes, so VCalendar drives either view without\n// asking which is on screen. A month has no hours, hence the empty `scrollToMinutes`.\ndefineExpose({\n /** Brings the focus onto the day the grid is currently pointing at. */\n focus: (options?: FocusOptions) => {\n if (tabbable.value) focusCell(cellId(tabbable.value), false, options)\n },\n /** Nothing to scroll to in a month view. */\n scrollToMinutes: () => {},\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-calendar-month\"\n :data-outside=\"gesture?.outside ? '' : undefined\"\n :style=\"{ '--calendar-columns': String(weekdayNames.length) }\"\n @focusout=\"onFocusout\"\n >\n <div class=\"v-calendar-month-head\" aria-hidden=\"true\">\n <span\n v-for=\"(name, index) in weekdayNames\"\n :key=\"index\"\n class=\"v-calendar-weekday v-calendar-month-weekday\"\n >\n {{ name }}\n </span>\n </div>\n\n <div\n ref=\"gridEl\"\n class=\"v-calendar-month-grid v-calendar-edge-cue\"\n role=\"grid\"\n :aria-label=\"label\"\n :data-edge=\"edgeCue\"\n @keydown=\"onKeydown\"\n @pointerdown=\"onGridPointerdown\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @pointerleave=\"onPointerleave\"\n @click=\"onGridClick\"\n >\n <div\n v-for=\"(week, row) in weeks\"\n :key=\"week[0]?.iso ?? row\"\n role=\"row\"\n class=\"v-calendar-month-week\"\n >\n <div\n v-for=\"cell in week\"\n :id=\"cellId(cell.iso)\"\n :key=\"cell.iso\"\n role=\"gridcell\"\n class=\"v-calendar-cell v-calendar-month-cell\"\n :data-iso=\"cell.iso\"\n :data-adjacent=\"cell.adjacent ?? undefined\"\n :tabindex=\"tabbable === cell.iso ? 0 : -1\"\n :aria-label=\"longDay(cell.iso)\"\n aria-keyshortcuts=\"Shift+Enter\"\n >\n <button\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-day\"\n :class=\"{ 'v-calendar-today': cell.iso === today }\"\n tabindex=\"-1\"\n :aria-label=\"m.calendar.openDay(longDay(cell.iso))\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ dayNumber(cell.iso) }}\n </button>\n\n <VCalendarEvent\n v-for=\"chip in dayEventsOf(cell.iso).shown\"\n :key=\"chip.event.id\"\n class=\"v-calendar-month-chip\"\n :event=\"chip.event\"\n layout=\"chip\"\n :disabled=\"disabled\"\n :time-text=\"chip.timeText\"\n v-bind=\"cardState(chip.event.id)\"\n @click=\"onCardClick(chip.event)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n\n <button\n v-if=\"dayEventsOf(cell.iso).hidden > 0\"\n type=\"button\"\n class=\"v-calendar-button v-calendar-month-more\"\n tabindex=\"-1\"\n @click=\"emit('day-activate', cell.iso)\"\n >\n {{ m.calendar.moreEvents(dayEventsOf(cell.iso).hidden) }}\n </button>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar-month {\n display: flex;\n flex-direction: column;\n }\n\n .v-calendar-month-head,\n .v-calendar-month-week {\n display: grid;\n grid-template-columns: repeat(var(--calendar-columns), minmax(0, 1fr));\n }\n\n /* 4, the time grid's header layer: a dragged chip (3) and the edge strip (2) pass under the\n sticky names as the month scrolls, rather than over them. */\n .v-calendar-month-head {\n position: sticky;\n inset-block-start: 0;\n z-index: 4;\n background: var(--calendar-surface);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-month-weekday {\n padding-block: var(--vectis-space-2);\n text-align: center;\n }\n\n .v-calendar-month-grid {\n position: relative;\n display: flex;\n /* The rows share what height there is, so a month fills the box it was given instead of\n leaving a band of nothing under the last week. The floor keeps a day readable when\n that box is short. */\n flex: 1;\n flex-direction: column;\n min-block-size: 0;\n }\n\n .v-calendar-month-week {\n flex: 1;\n min-block-size: var(--vectis-control-size-calendar-month-cell);\n }\n\n .v-calendar-month-cell {\n display: flex;\n flex-direction: column;\n gap: var(--vectis-control-size-calendar-gap);\n overflow: hidden;\n padding: var(--vectis-space-1);\n }\n\n /*\n * The days of the neighbouring months are kept; the grid is a fixed six rows, so the page\n * never jumps; but pushed back, since they are context rather than content.\n */\n .v-calendar-month-cell[data-adjacent] {\n background: var(--vectis-color-surface-sunken);\n color: var(--vectis-color-text-subtle);\n }\n\n .v-calendar-month-day {\n align-self: center;\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n inline-size: var(--vectis-control-height-sm);\n block-size: var(--vectis-control-height-sm);\n border-radius: var(--vectis-radius-pill);\n color: inherit;\n font-size: var(--vectis-text-body-md-size);\n }\n\n /* Disjoint from today rather than less specific: the two live in different sheets, where a\n tie would be settled by whichever one the consumer's bundler emitted last. */\n .v-calendar-month-day:hover:not(.v-calendar-today) {\n background: var(--calendar-hover-bg);\n }\n\n /*\n * The chip rule in VCalendarEvent's own sheet is (0,2,0) against this (0,1,0), so declaring\n * the size directly would lose; and matching its specificity would only hand the decision to\n * whichever sheet the consumer's bundler emitted last, which is the failure the library's\n * one-sheet-per-component rule exists to prevent.\n */\n .v-calendar-month-chip {\n --calendar-chip-height: var(--vectis-control-size-calendar-allday-lane);\n\n flex: none;\n }\n\n .v-calendar-month-more {\n flex: none;\n padding-inline: var(--vectis-space-1);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n text-align: start;\n }\n\n .v-calendar-month-more:hover {\n color: var(--vectis-color-text);\n text-decoration: underline;\n }\n\n /* The neighbouring months' days are told apart by a tint forced colors removes: grey text\n keeps them apart. */\n @media (forced-colors: active) {\n .v-calendar-month-cell[data-adjacent] {\n forced-color-adjust: none;\n background: Canvas;\n color: GrayText;\n }\n }\n}\n</style>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"VCalendarTimeGrid.js","names":[],"sources":["../../../src/components/VCalendar/VCalendarTimeGrid.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @keyboard @a11y @core\n/**\n * Render event buttons inside their owning grid cells for ARIA and keyboard navigation. Borders\n * preserve time rulings in forced colours; shared gestures manage editing.\n */\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\n\nimport { formatDateDisplay } from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { formatTimeDisplay } from '../../utils/time'\n\nimport { useMessages } from '../../i18n/state'\n\nimport VCalendarEvent from './VCalendarEvent.vue'\nimport { EDGE_BAND } from './edgeStep'\nimport {\n gridGeometryOf,\n useCalendarGesture,\n type GestureBase,\n type GestureInit,\n type GrabStep,\n} from './gesture'\nimport { calendarIntent } from './keyboard'\nimport {\n MINUTES_PER_DAY,\n MINUTES_PER_HOUR,\n blockEdgeAt,\n floorToSlot,\n fractionOf,\n isAllDayEvent,\n minutesAt,\n moveEventToDay,\n packAllDay,\n packDayColumn,\n pointToCell,\n sameTimes,\n snapToSlot,\n spansMidnight,\n timeOf,\n timedSegments,\n timesOf,\n type AllDaySpan,\n type PlacedSegment,\n type SegmentPart,\n type TimeWindow,\n} from './layout'\nimport { drawnSlot, moveTimedEvent, resizeTimedEvent, roundToSlot } from './timeGrid'\nimport type {\n ActivatedCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n} from './types'\n\n/** Where the keyboard focus is in the grid, as a day and a moment in it. */\nexport interface FocusedCell {\n iso: string\n minutes: number\n}\n\nexport interface CalendarTimeGridProps<T> {\n days: string[]\n events: T[]\n timeWindow: TimeWindow\n slotDuration: number\n locale: string\n format: CalendarFormat\n /** Today's date, or nothing on the server, where it cannot be known. */\n today: string | null\n /** The time it is now, in minutes since midnight: again nothing on the server. */\n now: number | null\n /** Whether events can be moved and stretched. */\n editable: boolean\n /** Whether the whole calendar is frozen, which also takes its cards out of the tab order. */\n disabled: boolean\n /** Whether an empty stretch of a day can be drawn out, and reported as a new event's times. */\n creatable: boolean\n /** The node telling a reader how a card can be moved, shared by every card. */\n hintId: string\n /** How long a drag rests against an edge before the view pages. Zero turns paging off. */\n edgeStepDelay: number\n /** Whether dragging near the top or bottom scrolls the grid. */\n autoScroll: boolean\n /** What the grid as a whole is called: the range it is showing. */\n label: string\n}\n\nconst props = defineProps<CalendarTimeGridProps<E>>()\n\nconst emit = defineEmits<{\n /** An empty part of the grid was activated, at this day and this moment. */\n 'cell-activate': [cell: ActivatedCell]\n /** A card was clicked or activated. */\n 'event-activate': [event: E]\n /** An event was dropped somewhere else, or stretched to a new end. */\n 'event-drop': [id: CalendarEventId, times: CalendarEventTimes, kind: 'move' | 'resize']\n /** An empty stretch of a day was drawn out, and is offered as a new event's times. */\n 'slot-create': [times: CalendarEventTimes]\n /** Something happened that a reader who cannot see the grid needs told. */\n announce: [message: string]\n /** A page key asked for the previous or next period, which only the calendar can do. */\n step: [delta: -1 | 1]\n}>()\n\n/** Which cell holds the tab stop. The calendar owns it, so the toolbar can move it too. */\nconst focused = defineModel<FocusedCell>('focused', { required: true })\n\ndefineSlots<{\n event?(props: CalendarEventSlotProps<E>): unknown\n 'day-header'?(props: { iso: string; weekday: string; dayText: string; today: boolean }): unknown\n 'all-day-label'?(): unknown\n}>()\n\nconst m = useMessages()\nconst uid = useId()\n\n/** The hour each row stands for, as minutes since midnight. */\nconst hours = computed(() => {\n const rows: number[] = []\n const { start, end } = props.timeWindow\n for (let at = start; at < end; at += MINUTES_PER_HOUR) rows.push(at)\n return rows\n})\n\n/**\n * The id a stretch of empty grid carries while it is being drawn out. It never leaves this\n * component: on release the calendar is handed the times alone, and the card is gone.\n */\nconst DRAFT_ID = '__vectis-calendar-draft__'\n\n/** A gesture under way, whether it came from a pointer or from the keyboard. */\ninterface Gesture extends GestureBase {\n kind: 'move' | 'move-days' | 'resize' | 'create'\n /**\n * How far the arrows have taken a KEYBOARD grab from its origin: days along the days on show,\n * and minutes moved (the start) and stretched (the end).\n */\n keyDays?: number\n keyStart?: number\n keyEnd?: number\n /**\n * How far after the event's start the pointer took hold, in minutes, so the card does not\n * jump under it.\n */\n grabOffset: number\n grabColumn: number\n /**\n * Store the dragged event's column shares by event piece, not day index: paging changes which\n * dates those indices represent.\n */\n ghostColumns: Partial<\n Record<SegmentPart, Pick<PlacedSegment, 'column' | 'span' | 'columns'>>\n > | null\n ghostLane: number | null\n}\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst canvasEl = ref<HTMLElement | null>(null)\nconst columnsEl = ref<HTMLElement | null>(null)\n\nconst {\n gesture,\n ghostEvent,\n withPreview,\n edgeCue,\n isRtl,\n start,\n reapply,\n endLastGesture,\n consumeDrag,\n onPointermove,\n onPointerup,\n onPointercancel,\n onPointerleave,\n onFocusout,\n onCardKeydown,\n focusCell,\n idOfCard,\n cardState,\n} = useCalendarGesture<E, Gesture>({\n events: () => props.events,\n eventOf: (id): E | undefined => eventOf(id),\n rootEl,\n // The SCROLLER, not the columns box: the all-day band sits in the sticky header, and this is\n // the only element that contains both it and the canvas.\n captureEl: rootEl,\n measureEl: columnsEl,\n columns: () => props.days.length,\n editable: () => props.editable,\n hintId: () => props.hintId,\n slotMinutes: () => props.slotDuration,\n edgeStepDelay: () => props.edgeStepDelay,\n layout: () => props.days,\n applyPoint,\n onEdges: (state, scroller) =>\n setScrollSpeed(\n props.autoScroll && scroller ? blockEdgeAt(state.lastY, scroller, EDGE_BAND) : 0,\n ),\n onRelease: stopScrolling,\n onLetGo: (state) => {\n if (state.kind !== 'create') return false\n // A press that never travelled drew nothing: its click reports the cell instead.\n if (state.moved) emit('slot-create', state.preview)\n return true\n },\n onDrop: (state) =>\n emit('event-drop', state.id, state.preview, state.kind === 'resize' ? 'resize' : 'move'),\n grab: (item) =>\n props.editable\n ? initFor({\n kind: isAllDayEvent(item) ? 'move-days' : 'move',\n id: item.id,\n origin: timesOf(item),\n pointerId: null,\n originX: 0,\n originY: 0,\n grabOffset: 0,\n grabColumn: 0,\n })\n : null,\n onGrabStep,\n step: (delta) => emit('step', delta),\n announce: (message) => emit('announce', message),\n})\n\n/** Where the echo goes, worked out afresh on every render rather than remembered. */\nconst NO_GHOSTS: readonly PlacedSegment[] = Object.freeze([])\n\n/*\n * The \"no echo\" answer is ONE shared array. A bar dragged along the band recomputes this on\n * every slot; a fresh `[]` each time is a new value, which would wake `placed` and re-pack\n * every column for a gesture that never touches them.\n */\nconst ghostSegments = computed<readonly PlacedSegment[]>(() => {\n const state = gesture.value\n const event = ghostEvent.value\n const packing = state?.ghostColumns\n if (!event || !packing) return NO_GHOSTS\n return timedSegments([event], props.days, props.timeWindow, props.slotDuration).flatMap(\n (segment) => {\n const columns = packing[segment.part]\n return columns ? [{ ...segment, ...columns }] : []\n },\n )\n})\n\nconst ghostSpan = computed<AllDaySpan | null>(() => {\n const state = gesture.value\n const event = ghostEvent.value\n if (!event || state?.ghostLane == null) return null\n const [span] = packAllDay([event], props.days)\n return span ? { ...span, lane: state.ghostLane } : null\n})\n\n/*\n * The model split in two, once per change to it: the timed events the columns draw, and the\n * all-day ones the band above them draws. The split is what lets a gesture rebuild only its own\n * half; see `drawnTimed`.\n */\nconst timedEvents = computed(() => props.events.filter((event) => !isAllDayEvent(event)))\nconst allDayEvents = computed(() => props.events.filter((event) => isAllDayEvent(event)))\n\n/**\n * The half a gesture belongs to is decided by its KIND and never by looking at the preview.\n * `move-days` is the only gesture that starts on the band, and the three others only start on a\n * timed card or an empty cell (both press handlers and the keyboard grab refuse an all-day\n * event), so the two lists below can each return their untouched half as the same array.\n */\nconst drawnTimed = computed<E[]>(() => {\n const state = gesture.value\n if (!state || state.kind === 'move-days') return timedEvents.value\n\n if (state.kind === 'create') {\n if (!state.moved) return timedEvents.value\n const draft = {\n id: DRAFT_ID,\n title: m.value.calendar.untitled,\n ...state.preview,\n // It exists while the pointer is down and never reaches the model, which makes the cast\n // safe.\n } as unknown as E\n return [...timedEvents.value, draft]\n }\n\n return withPreview(timedEvents.value)\n})\n\n/** The all-day bars as the band should currently draw them; see `drawnTimed`. */\nconst drawnAllDay = computed<E[]>(() =>\n gesture.value?.kind === 'move-days' ? withPreview(allDayEvents.value) : allDayEvents.value,\n)\n\nconst timedById = computed(\n () => new Map<CalendarEventId, E>(drawnTimed.value.map((event) => [event.id, event])),\n)\nconst allDayById = computed(\n () => new Map<CalendarEventId, E>(drawnAllDay.value.map((event) => [event.id, event])),\n)\n\n/** Any event on show, as it is currently drawn. For the handlers; the template reads a half. */\nconst eventOf = (id: CalendarEventId): E | undefined =>\n timedById.value.get(id) ?? allDayById.value.get(id)\n\n/** The bars above the grid, and how many rows they need between them. */\nconst allDay = computed(() => {\n const ghost = ghostSpan.value\n const spans = packAllDay(\n ghost ? drawnAllDay.value.filter((event) => event.id !== ghost.id) : drawnAllDay.value,\n props.days,\n )\n return ghost ? [...spans, ghost] : spans\n})\nconst allDayLanes = computed(() =>\n allDay.value.reduce((most, span) => Math.max(most, span.lane + 1), 0),\n)\n\n/** The row a moment of the window is drawn in, held inside the grid. */\nconst rowOf = (minutes: number) =>\n clamp(\n Math.floor((minutes - props.timeWindow.start) / MINUTES_PER_HOUR),\n 0,\n hours.value.length - 1,\n )\n\n/**\n * Where the current-time line goes, or nothing when it has no business being drawn: on the\n * server, on a range that does not include today, or at an hour the window has cropped\n * away.\n */\nconst nowMark = computed(() => {\n if (props.now === null || props.today === null) return null\n const dayIndex = props.days.indexOf(props.today)\n if (dayIndex === -1) return null\n if (props.now < props.timeWindow.start || props.now > props.timeWindow.end) return null\n return {\n day: dayIndex,\n row: rowOf(props.now),\n fraction: fractionOf(props.now, props.timeWindow),\n }\n})\n\n/** The placed boxes, day by day. */\nconst placed = computed(() => {\n const segments = timedSegments(drawnTimed.value, props.days, props.timeWindow, props.slotDuration)\n const ghosts = ghostSegments.value\n // Read off the echo's own boxes, never off `ghostEvent`, which changes with every slot of a\n // bar's drag and would wake this for the band's gesture too.\n const ghostId = ghosts[0]?.id\n\n const byDay = new Map<number, typeof segments>()\n for (const segment of segments) {\n /*\n * The echo is held OUT of the packing and put back below with the width and column it had\n * when the drag began. Packed with the rest, it would share its column with the card being\n * dragged the moment the two overlapped; so the card under the pointer would halve in width\n * and then grow back as it moved away, which reads as the drag going wrong.\n */\n if (ghostId !== undefined && segment.id === ghostId) continue\n const list = byDay.get(segment.dayIndex)\n if (list) list.push(segment)\n else byDay.set(segment.dayIndex, [segment])\n }\n\n const real = [...byDay.values()].flatMap((list) => packDayColumn(list))\n return ghosts.length > 0 ? [...real, ...ghosts] : real\n})\n\nconst cellId = (iso: string, minutes: number) => `${uid}-c-${iso}-${minutes}`\n\n/*\n * These two maps, of 7 and 24 entries, recompute when the locale, the days or the window change\n * and never because a card moved. Both are keyed by exactly what the template walks, `days` and\n * `hours`, so every lookup is a hit and none of them needs a formatting fallback.\n */\nconst dayLabels = computed(() => {\n const map = new Map<string, { short: string; number: string; full: string }>()\n for (const iso of props.days) {\n map.set(iso, {\n short: formatDateDisplay(iso, props.locale, { weekday: 'short' }),\n number: formatDateDisplay(iso, props.locale, { day: 'numeric' }),\n full: formatDateDisplay(iso, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n }),\n })\n }\n return map\n})\n\nconst hourLabels = computed(() => {\n const map = new Map<number, string>()\n for (const minutes of hours.value)\n map.set(minutes, formatTimeDisplay(timeOf(minutes), props.locale, props.format))\n return map\n})\n\nconst hourLabel = (minutes: number) => hourLabels.value.get(minutes) ?? ''\nconst dayName = (iso: string) => dayLabels.value.get(iso)?.short ?? ''\nconst dayNumber = (iso: string) => dayLabels.value.get(iso)?.number ?? ''\n\n/**\n * The cells, row by row, with everything about them that no gesture can change: the id the\n * focus is moved by, the name a reader hears, and the number `byCell` files cards under.\n */\nconst gridRows = computed(() => {\n const columns = props.days.length\n return hours.value.map((minutes, row) => ({\n minutes,\n cells: props.days.map((iso, day) => ({\n iso,\n day,\n row,\n key: row * columns + day,\n id: cellId(iso, minutes),\n label: `${dayLabels.value.get(iso)?.full ?? iso}, ${hourLabel(minutes)}`,\n })),\n }))\n})\n\n/**\n * What a dependant reads has to be this computed, never a cache held beside it. A hit reads no\n * prop, so a computed that only ever hit would track neither the locale nor the hour format,\n * and would go on printing the old ones after either changed.\n */\nconst timeTextOf = computed(() => {\n const { locale, format } = props\n const cache = new Map<string, string>()\n return (event: CalendarEvent): string => {\n const key = `${event.startTime}|${event.endTime}|${event.timezone ?? ''}`\n let text = cache.get(key)\n if (text === undefined) {\n const start = formatTimeDisplay(event.startTime, locale, format)\n const end = formatTimeDisplay(event.endTime, locale, format)\n const range = `${start} – ${end}`\n text = event.timezone ? `${range} (${event.timezone})` : range\n cache.set(key, text)\n }\n return text\n }\n})\n\n/** One card as the template draws it, with its event and its box already worked out. */\ninterface Card {\n segment: PlacedSegment\n event: E\n timeText: string\n style: Record<string, string>\n}\n\n/** The cards, filed under the cell their start falls in. */\nconst byCell = computed(() => {\n const map = new Map<number, Card[]>()\n const columns = props.days.length\n const events = timedById.value\n const textOf = timeTextOf.value\n for (const segment of placed.value) {\n // A placement always has an event behind it. Skipping rather than asserting turns a\n // mismatch between the two into one missing card instead of a grid that fails to render.\n const event = events.get(segment.id)\n if (!event) continue\n const key = rowOf(segment.start) * columns + segment.dayIndex\n const card: Card = {\n segment,\n event,\n timeText: textOf(event),\n style: {\n '--calendar-day-index': String(segment.dayIndex),\n '--calendar-block-start-fraction': String(fractionOf(segment.start, props.timeWindow)),\n '--calendar-block-end-fraction': String(fractionOf(segment.end, props.timeWindow)),\n '--calendar-block-column': String(segment.column),\n '--calendar-block-span': String(segment.span),\n '--calendar-block-columns': String(segment.columns),\n },\n }\n const list = map.get(key)\n if (list) list.push(card)\n else map.set(key, [card])\n }\n return map\n})\n\n/** An hour with nothing in it, shared rather than allocated per empty cell. */\nconst NO_CARDS: readonly Card[] = Object.freeze([])\nconst cardsAt = (key: number) => byCell.value.get(key) ?? NO_CARDS\n\nconst isFocused = (iso: string, minutes: number) =>\n focused.value.iso === iso && focused.value.minutes === minutes\n\n/**\n * Which cell holds the tab stop: the focused one, rounded to its row; or, when that cell is not\n * on screen (after a view change, or before anything has been focused at all), the first cell.\n */\nconst tabbable = computed<FocusedCell | null>(() => {\n const current = focused.value\n const { start, end } = props.timeWindow\n if (props.days.includes(current.iso)) {\n const rounded =\n Math.floor((current.minutes - start) / MINUTES_PER_HOUR) * MINUTES_PER_HOUR + start\n if (rounded >= start && rounded < end) return { iso: current.iso, minutes: rounded }\n }\n const iso = props.days[0]\n return iso ? { iso, minutes: start } : null\n})\n\nconst isTabStop = (iso: string, minutes: number) =>\n tabbable.value?.iso === iso && tabbable.value.minutes === minutes\n\nfunction moveFocusTo(iso: string, minutes: number) {\n focused.value = { iso, minutes }\n // @a11y\n focusCell(cellId(iso, minutes), true)\n}\n\n/** A day, then a start and an end: where a reader is told an event now is. */\nfunction timesText(times: CalendarEventTimes) {\n const start = formatTimeDisplay(times.startTime, props.locale, props.format)\n const end = formatTimeDisplay(times.endTime, props.locale, props.format)\n const day = formatDateDisplay(times.start, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n })\n return `${day}, ${start} – ${end}`\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement | null\n\n // A card answers a different table from the grid it sits in; that is what a grab mode IS.\n // Delegated here, on the same element as the pointer, so the two never drift apart.\n const card = target?.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n onCardKeydown(event, card)\n return\n }\n\n const cell = target?.closest<HTMLElement>('.v-calendar-cell')\n if (!cell) return\n\n const intent = calendarIntent(event, 'cell', props.slotDuration, isRtl())\n if (!intent) return\n\n const iso = cell.dataset.iso!\n const minutes = Number(cell.dataset.minutes)\n const index = props.days.indexOf(iso)\n\n if (intent.kind === 'moveFocus') {\n const day = props.days[clamp(index + intent.days, 0, props.days.length - 1)]!\n const at = clamp(\n minutes + intent.minutes,\n props.timeWindow.start,\n props.timeWindow.end - MINUTES_PER_HOUR,\n )\n event.preventDefault()\n moveFocusTo(day, at)\n return\n }\n\n if (intent.kind === 'rowEdge') {\n const day = intent.edge === 'start' ? props.days[0] : props.days.at(-1)\n if (!day) return\n event.preventDefault()\n moveFocusTo(day, minutes)\n return\n }\n\n if (intent.kind === 'period') {\n event.preventDefault()\n emit('step', intent.delta)\n return\n }\n\n /*\n * Enter or Space on an empty cell reports it through `cell-activate`, the keyboard's route to\n * a new event (WCAG 2.1.1): the cell carries a day and an hour, and how long the event lasts\n * is a question for the consumer's own form, where the pointer answers it by how far it was\n * drawn.\n */\n if (intent.kind === 'activate' || intent.kind === 'openDay') {\n event.preventDefault()\n emit('cell-activate', { date: iso, minutes })\n }\n}\n\n/**\n * Read before the gesture is set. Both are derived from the drawn events, which the gesture\n * immediately starts rewriting; a line later and they would already describe the preview rather\n * than the place it came from.\n */\nfunction initFor(\n init: Omit<GestureInit<Gesture>, 'ghostColumns' | 'ghostLane'>,\n): GestureInit<Gesture> {\n const segments = placed.value.filter((item) => item.id === init.id)\n const span = allDay.value.find((item) => item.id === init.id)\n return {\n ...init,\n ghostColumns:\n segments.length > 0\n ? Object.fromEntries(\n segments.map((segment) => [\n segment.part,\n { column: segment.column, span: segment.span, columns: segment.columns },\n ]),\n )\n : null,\n ghostLane: span ? span.lane : null,\n }\n}\n\nconst pointAt = (x: number, y: number, rect: DOMRect, rtl: boolean) =>\n pointToCell({ x, y }, gridGeometryOf(rect, props.days.length, rtl), props.timeWindow, rtl)\n\n/** Every press on the grid, dispatched by what it landed on. */\nfunction onGridPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || event.button !== 0) return\n\n const target = event.target as HTMLElement | null\n const rect = columnsEl.value?.getBoundingClientRect()\n if (!target || !rect) return\n const rtl = isRtl()\n const point = pointAt(event.clientX, event.clientY, rect, rtl)\n\n const card = target.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n if (!props.editable) return\n const item = eventOf(idOfCard(card))\n if (!item || isAllDayEvent(item)) return\n\n const resizing = target.closest('[data-calendar-handle]') !== null\n // The morning card of an overnight event is held from the evening before, so the offset\n // carries the day between the two.\n const pressedMorning = spansMidnight(item) && props.days[point.columnIndex] === item.end\n start(\n initFor({\n kind: resizing ? 'resize' : 'move',\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabOffset: resizing\n ? 0\n : (pressedMorning ? MINUTES_PER_DAY : 0) +\n point.minutes -\n minutesAt(item.startTime, props.timeWindow.start),\n grabColumn: point.columnIndex,\n }),\n rtl,\n )\n return\n }\n\n if (!props.creatable || !target.closest('.v-calendar-cell')) return\n const iso = props.days[point.columnIndex]\n if (!iso) return\n\n /*\n * The pressed slot is where the drawing is anchored; rounded DOWN, never to the nearest,\n * because a press at 09:07 means \"the nine o'clock slot\" and not the quarter past. Nothing is\n * drawn and nothing is reported until the press travels: a press that stays still is a click,\n * and reports its cell like any other.\n */\n const from = floorToSlot(point.minutes, props.slotDuration)\n start(\n initFor({\n kind: 'create',\n id: DRAFT_ID,\n origin: {\n start: iso,\n end: iso,\n startTime: timeOf(from),\n endTime: timeOf(Math.min(from + props.slotDuration, props.timeWindow.end)),\n },\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabOffset: 0,\n grabColumn: point.columnIndex,\n }),\n rtl,\n )\n}\n\n/** A press on the band of all-day bars above the grid. */\n/**\n * The band's bars answer the same card table as the grid's cards. They sit in the sticky\n * header, outside the grid whose keydown the cards reach, so the band delegates on its own.\n */\nfunction onBandKeydown(event: KeyboardEvent) {\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n if (card) onCardKeydown(event, card)\n}\n\nfunction onBandPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || !props.editable || event.button !== 0) return\n\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n const item = card ? eventOf(idOfCard(card)) : undefined\n const rect = columnsEl.value?.getBoundingClientRect()\n if (!item || !rect) return\n const rtl = isRtl()\n\n start(\n initFor({\n kind: 'move-days',\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabOffset: 0,\n grabColumn: pointAt(event.clientX, event.clientY, rect, rtl).columnIndex,\n }),\n rtl,\n )\n}\n\n/**\n * One arrow press on an all-day bar held by the keyboard: a whole day sideways along the days\n * on show, nothing else, since a bar has no hours to move through or to stretch.\n */\nfunction onBarStep(state: Gesture, step: GrabStep, item: E): boolean {\n if (step.kind !== 'grabMove' || step.days === 0) return false\n const keyDays = (state.keyDays ?? 0) + step.days\n const base = props.days.indexOf(state.origin.start)\n const column = clamp(base + keyDays, Math.min(base, 0), props.days.length - 1)\n const next = column < 0 ? state.origin : moveEventToDay(state.origin, props.days[column]!)\n if (sameTimes(next, state.preview)) return false\n state.keyDays = keyDays\n state.preview = next\n emit('announce', m.value.calendar.movedTo(item.title, timesText(state.preview)))\n return true\n}\n\n/** Works out where the dragged event now belongs, from wherever the pointer last was. */\nfunction applyPoint(state: Gesture, rect: DOMRect) {\n const point = pointAt(state.lastX, state.lastY, rect, state.rtl)\n const crossMidnight = spansMidnight(state.origin)\n\n /*\n * A move's start is rounded without being held inside the day, since an overnight event's may\n * leave it.\n */\n const column =\n state.kind === 'create' || (state.kind === 'resize' && !crossMidnight) ? 0 : point.columnIndex\n const minutes =\n state.kind === 'move-days'\n ? 0\n : state.kind === 'move'\n ? roundToSlot(point.minutes - state.grabOffset, props.slotDuration)\n : state.kind === 'create'\n ? floorToSlot(point.minutes, props.slotDuration)\n : snapToSlot(point.minutes, props.slotDuration)\n if (isLastApplied(state, column, minutes)) return\n\n if (state.kind === 'create') {\n const anchor = minutesAt(state.origin.startTime, props.timeWindow.start)\n setPreview(\n state,\n drawnSlot(state.origin.start, anchor, minutes, props.slotDuration, props.timeWindow),\n )\n return\n }\n\n if (state.kind === 'resize') {\n const endDay = props.days[column] ?? state.origin.end\n setPreview(\n state,\n resizeTimedEvent(\n state.origin,\n endDay,\n minutes,\n props.slotDuration,\n props.timeWindow,\n crossMidnight,\n ),\n )\n return\n }\n\n if (state.kind === 'move-days') {\n /*\n * The day is read out of the list of days on show; never from date arithmetic on how many\n * columns were crossed, which would count the days the calendar is hiding and land the\n * event on a Saturday nobody can see. A bar keeps its length in days.\n */\n const target = clamp(\n originColumn(state) + (column - state.grabColumn),\n 0,\n props.days.length - 1,\n )\n const iso = props.days[target]\n if (iso) setPreview(state, moveEventToDay(state.origin, iso))\n return\n }\n\n /*\n * A timed card is placed from the column the pointer is over, and its start from the pointer\n * less the offset it was taken hold at. For an overnight event taken by its morning that start\n * lands on the day before the pointer's column, which `moveTimedEvent` works out.\n */\n const iso = props.days[column]\n if (!iso) return\n setPreview(state, moveTimedEvent(state.origin, iso, minutes, props.timeWindow, crossMidnight))\n}\n\n/** The slot `applyPoint` last worked a preview out for. */\nlet lastApplied: {\n state: Gesture\n days: readonly string[]\n timeWindow: TimeWindow\n step: number\n column: number\n minutes: number\n} | null = null\n\nfunction isLastApplied(state: Gesture, column: number, minutes: number): boolean {\n const last = lastApplied\n if (\n last?.state === state &&\n last.days === props.days &&\n last.timeWindow === props.timeWindow &&\n last.step === props.slotDuration &&\n last.column === column &&\n last.minutes === minutes\n ) {\n return true\n }\n lastApplied = {\n state,\n days: props.days,\n timeWindow: props.timeWindow,\n step: props.slotDuration,\n column,\n minutes,\n }\n return false\n}\n\n/**\n * The comparison is the whole point, not a nicety. The gesture is a deep ref, so any assignment\n * to `preview`, an equal object included, wakes the drawn events, the packing of every column\n * and a render of all 168 cells.\n */\nfunction setPreview(state: Gesture, next: CalendarEventTimes) {\n if (!sameTimes(state.preview, next)) state.preview = next\n}\n\n/**\n * Which column the bar being moved started in. And when the origin day is not on screen at all;\n * a bar that began before this range, or one whose view has been paged out from under the drag;\n * falling back to the grab column makes the offset zero, so the bar lands under the pointer\n * instead of nowhere.\n */\nfunction originColumn(state: Gesture): number {\n const index = props.days.indexOf(state.origin.start)\n return index === -1 ? state.grabColumn : index\n}\n\n/**\n * One arrow press on a card held by the keyboard. Applied to the preview instead, a step held\n * against the end of the day built on its 23:59 and the event came back shorter and off the\n * slot grid.\n */\nfunction onGrabStep(state: Gesture, step: GrabStep, item: E): boolean {\n const { origin } = state\n if (state.kind === 'move-days') return onBarStep(state, step, item)\n const crossMidnight = spansMidnight(origin)\n let keyDays = state.keyDays ?? 0\n let keyStart = state.keyStart ?? 0\n let keyEnd = state.keyEnd ?? 0\n if (step.kind === 'grabResize') keyEnd += step.minutes\n else {\n keyDays += step.days\n keyStart += step.minutes\n }\n\n /*\n * An overnight event may have been taken by its morning card with its start on a day off show\n * (index -1): it can then go right, never further left than where it already is.\n */\n const base = props.days.indexOf(origin.start)\n const column = clamp(base + keyDays, Math.min(base, 0), props.days.length - 1)\n const day = column < 0 || keyDays === 0 ? origin.start : props.days[column]!\n const moved = moveTimedEvent(\n origin,\n day,\n minutesAt(origin.startTime, props.timeWindow.start) + keyStart,\n props.timeWindow,\n crossMidnight,\n )\n const next =\n keyEnd === 0\n ? moved\n : resizeTimedEvent(\n moved,\n moved.end,\n minutesAt(moved.endTime, props.timeWindow.end) + keyEnd,\n props.slotDuration,\n props.timeWindow,\n crossMidnight,\n )\n\n // Held against an edge, the press goes nowhere: nothing to announce, nothing to drop, and\n // nothing kept either, so the next press the other way moves at once.\n if (sameTimes(next, state.preview)) return false\n state.keyDays = keyDays\n state.keyStart = keyStart\n state.keyEnd = keyEnd\n state.preview = next\n emit('announce', m.value.calendar.movedTo(item.title, timesText(state.preview)))\n return true\n}\n\n/** How many pixels a frame the grid scrolls at full tilt. */\nconst AUTO_SCROLL_SPEED = 14\n\n/*\n * The design system's only `requestAnimationFrame` loop, and it earns that: a pointer held\n * still fires no events at all, so nothing else can drive continuous motion. Everything else\n * that moves here is either a transition or a timer.\n */\nlet scrollFrame: number | null = null\nlet scrollSpeed = 0\n\nfunction setScrollSpeed(speed: number) {\n scrollSpeed = speed\n if (speed === 0) {\n stopScrolling()\n return\n }\n scrollFrame ??= requestAnimationFrame(scrollStep)\n}\n\nfunction scrollStep() {\n scrollFrame = null\n const root = rootEl.value\n const state = gesture.value\n if (!root || !state || scrollSpeed === 0) return\n\n root.scrollTop += scrollSpeed * AUTO_SCROLL_SPEED\n reapply(state)\n scrollFrame = requestAnimationFrame(scrollStep)\n}\n\nfunction stopScrolling() {\n if (scrollFrame !== null) cancelAnimationFrame(scrollFrame)\n scrollFrame = null\n scrollSpeed = 0\n}\n\nonBeforeUnmount(stopScrolling)\n\nfunction onCardClick(id: CalendarEventId) {\n // `pointerup` fires before `click`, so letting go at the end of a drag would ALSO open the\n // event; the consumer's editor over every card the reader had just moved.\n if (consumeDrag()) return\n const item = eventOf(id)\n if (item && id !== DRAFT_ID) emit('event-activate', item)\n}\n\n/** A click on an empty part of a day, reported as the cell it landed in. */\nfunction onGridClick(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (target?.closest('.v-calendar-event')) return\n if (consumeDrag()) return\n const cell = target?.closest<HTMLElement>('.v-calendar-cell')\n if (!cell) return\n emit('cell-activate', { date: cell.dataset.iso!, minutes: Number(cell.dataset.minutes) })\n}\n\n/** The cursor a gesture holds for as long as it lasts, whatever the pointer passes over. */\nconst gestureCursor = computed(() => {\n const state = gesture.value\n if (!state || state.pointerId === null) return undefined\n if (state.kind === 'resize') return 'resize'\n if (state.kind === 'create' && state.moved) return 'create'\n return undefined\n})\n\n/** Brings a moment of the day to the top of the visible area. */\nfunction scrollToMinutes(minutes: number) {\n const root = rootEl.value\n const canvas = canvasEl.value\n if (!root || !canvas || hours.value.length === 0) return\n const rowHeight = canvas.offsetHeight / hours.value.length\n if (rowHeight <= 0) return\n root.scrollTop = ((minutes - props.timeWindow.start) / MINUTES_PER_HOUR) * rowHeight\n}\n\n// The contract VCalendar drives every view through, so its toolbar need not know which one\n// is on screen. VCalendarMonth answers the same two, its `scrollToMinutes` being a no-op.\ndefineExpose({\n /** Brings the focus onto the cell the grid is currently pointing at. */\n focus: (options?: FocusOptions) => {\n const cell = tabbable.value\n if (cell) focusCell(cellId(cell.iso, cell.minutes), false, options)\n },\n /** Scrolls the grid so a given moment of the day sits at the top of the visible area. */\n scrollToMinutes,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-calendar-time-grid\"\n :data-outside=\"gesture?.outside ? '' : undefined\"\n :data-gesture=\"gestureCursor\"\n :style=\"{ '--calendar-columns': String(days.length) }\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @pointerleave=\"onPointerleave\"\n @focusout=\"onFocusout\"\n >\n <div class=\"v-calendar-head\">\n <div class=\"v-calendar-head-gutter\" />\n <div v-for=\"iso in days\" :key=\"iso\" class=\"v-calendar-head-day\">\n <slot\n name=\"day-header\"\n :iso=\"iso\"\n :weekday=\"dayName(iso)\"\n :day-text=\"dayNumber(iso)\"\n :today=\"iso === today\"\n >\n <span class=\"v-calendar-weekday\">{{ dayName(iso) }}</span>\n <span class=\"v-calendar-head-number\" :class=\"{ 'v-calendar-today': iso === today }\">\n {{ dayNumber(iso) }}\n </span>\n </slot>\n </div>\n\n <!-- The band of all-day bars, kept INSIDE the header so that the whole of it is one\n sticky box: two boxes would each need to know how tall the other was. -->\n <template v-if=\"allDay.length > 0\">\n <div class=\"v-calendar-allday-label\">\n <slot name=\"all-day-label\">{{ m.calendar.allDay }}</slot>\n </div>\n <div\n class=\"v-calendar-allday\"\n role=\"group\"\n :aria-label=\"m.calendar.allDay\"\n :style=\"{ '--calendar-lanes': String(allDayLanes) }\"\n @pointerdown=\"onBandPointerdown\"\n @keydown=\"onBandKeydown\"\n >\n <VCalendarEvent\n v-for=\"span in allDay\"\n :key=\"span.id\"\n class=\"v-calendar-bar\"\n :event=\"allDayById.get(span.id)!\"\n layout=\"chip\"\n :disabled=\"disabled\"\n :continues-before=\"span.continuesBefore\"\n :continues-after=\"span.continuesAfter\"\n v-bind=\"cardState(span.id)\"\n :style=\"{\n '--calendar-day-index': String(span.startIndex),\n '--calendar-bar-span': String(span.span),\n '--calendar-bar-lane': String(span.lane),\n }\"\n @click=\"onCardClick(span.id)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n </div>\n </template>\n </div>\n\n <div ref=\"canvasEl\" class=\"v-calendar-canvas\">\n <div class=\"v-calendar-hours\" aria-hidden=\"true\">\n <span v-for=\"minutes in hours\" :key=\"minutes\" class=\"v-calendar-hour\">\n {{ hourLabel(minutes) }}\n </span>\n </div>\n\n <div\n ref=\"columnsEl\"\n class=\"v-calendar-columns v-calendar-edge-cue\"\n role=\"grid\"\n :aria-label=\"label\"\n :data-edge=\"edgeCue\"\n @keydown=\"onKeydown\"\n @click=\"onGridClick\"\n @pointerdown=\"onGridPointerdown\"\n >\n <div v-for=\"gridRow in gridRows\" :key=\"gridRow.minutes\" role=\"row\" class=\"v-calendar-row\">\n <div\n v-for=\"cell in gridRow.cells\"\n :id=\"cell.id\"\n :key=\"cell.iso\"\n role=\"gridcell\"\n class=\"v-calendar-cell\"\n :data-iso=\"cell.iso\"\n :data-minutes=\"gridRow.minutes\"\n :data-today=\"cell.iso === today ? '' : undefined\"\n :tabindex=\"isTabStop(cell.iso, gridRow.minutes) ? 0 : -1\"\n :aria-label=\"cell.label\"\n :aria-selected=\"isFocused(cell.iso, gridRow.minutes) ? true : undefined\"\n >\n <VCalendarEvent\n v-for=\"card in cardsAt(cell.key)\"\n :key=\"card.segment.id\"\n class=\"v-calendar-block\"\n :event=\"card.event\"\n layout=\"block\"\n :disabled=\"disabled\"\n :time-text=\"card.timeText\"\n :continues-before=\"card.segment.clippedStart\"\n :continues-after=\"card.segment.clippedEnd\"\n :resizable=\"editable && card.segment.id !== DRAFT_ID && card.segment.part !== 'head'\"\n v-bind=\"cardState(card.segment.id)\"\n :style=\"card.style\"\n @click=\"onCardClick(card.segment.id)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n\n <!-- The line is drawn INSIDE the cell holding the current hour, never as a\n direct child of the grid or of a row: a `grid` owns only rows and a row only\n cells, so an extra box at either level fails `aria-required-children`. -->\n <div\n v-if=\"nowMark && nowMark.day === cell.day && nowMark.row === cell.row\"\n class=\"v-calendar-now\"\n aria-hidden=\"true\"\n :style=\"{\n '--calendar-day-index': String(nowMark.day),\n '--calendar-now-fraction': String(nowMark.fraction),\n }\"\n />\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar-time-grid {\n /*\n * The one scrolling box; its scrolling itself is `.v-calendar-view`'s, in VCalendar's\n * sheet. The day names are sticky INSIDE it rather than in a container of their own, which\n * keeps them aligned with the columns for free: a separate header would have to be told how\n * far the body had scrolled sideways, and told again every time the column widths changed.\n */\n --calendar-hour: var(--vectis-control-size-calendar-hour);\n --calendar-gutter: var(--vectis-control-size-calendar-gutter);\n /*\n * The day columns, written once for the three boxes that lay them out: the header and the\n * canvas behind the hour gutter, the all-day band and the columns box without it. The three\n * must resolve to identical tracks; a bar is dropped by reading the pointer against the\n * COLUMNS' geometry; so one definition is what keeps them from drifting apart.\n */\n --calendar-tracks: repeat(\n var(--calendar-columns),\n minmax(var(--vectis-control-size-calendar-day-min), 1fr)\n );\n /*\n * One number, so the two cannot drift apart; two literals would misalign the moment the\n * caption's leading changed, with nothing to point at it.\n */\n --calendar-hour-lift: calc(\n var(--vectis-text-caption-size) * var(--vectis-text-caption-leading) / 2\n );\n\n position: relative;\n }\n\n /*\n * It is set on every descendant as well as on the scroller, because an engine that resolves\n * the cursor from what is under the pointer rather than from the capture target would\n * otherwise show the `pointer` of whichever card the gesture passes over.\n * `:not([data-outside])` keeps it off a gesture held outside the calendar, whose\n * `not-allowed` lives in VCalendar's sheet at (0,2,0): at equal weight the two would be\n * arbitrated by the order the consumer's bundler put the sheets in.\n */\n .v-calendar-time-grid[data-gesture='resize']:not([data-outside]),\n .v-calendar-time-grid[data-gesture='resize']:not([data-outside]) * {\n cursor: ns-resize;\n }\n\n .v-calendar-time-grid[data-gesture='create']:not([data-outside]),\n .v-calendar-time-grid[data-gesture='create']:not([data-outside]) * {\n cursor: move;\n }\n\n .v-calendar-head,\n .v-calendar-canvas {\n display: grid;\n grid-template-columns: var(--calendar-gutter) var(--calendar-tracks);\n /*\n * Without this the BOX would stay the width of the scroller while the tracks spilled out of\n * it: the sticky day names would paint their background only as far as the original width,\n * and the columns would run out from under them halfway across. `min-content` on a grid is\n * the sum of its track minimums, which is exactly the width wanted here.\n */\n min-inline-size: min-content;\n }\n\n /*\n * Shared grid stacking order: cards 1, current-time line and edge cues 2, dragged cards 3,\n * sticky headings 4. Intermediate containers must not create stacking contexts.\n */\n .v-calendar-head {\n position: sticky;\n inset-block-start: 0;\n z-index: 4;\n background: var(--calendar-surface);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-head-gutter {\n /* The corner above the hour labels. It is empty, and it is here so the day names line\n up with their columns rather than starting at the edge of the box. */\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-head-day {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--vectis-space-1);\n padding-block: var(--vectis-space-2);\n padding-inline: var(--vectis-space-1);\n }\n\n /*\n * Today is marked on the number alone, the way a calendar marks a date rather than a column;\n * the `.v-calendar-today` paint, in VCalendar's sheet.\n */\n .v-calendar-head-number {\n display: flex;\n align-items: center;\n justify-content: center;\n inline-size: var(--vectis-control-height-sm);\n block-size: var(--vectis-control-height-sm);\n border-radius: var(--vectis-radius-pill);\n font-size: var(--vectis-text-body-lg-size);\n }\n\n .v-calendar-allday-label {\n display: flex;\n align-items: start;\n justify-content: end;\n padding-block-start: var(--vectis-space-1);\n padding-inline-end: var(--vectis-space-2);\n border-block-start: 1px solid var(--vectis-color-border);\n border-inline-end: 1px solid var(--vectis-color-border);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n line-height: var(--vectis-text-caption-leading);\n }\n\n .v-calendar-allday {\n /*\n * The band's two axes are DISCRETE; a day, and a row; where the time grid's vertical axis\n * is continuous. That is why the bars are placed with real grid lines here and with\n * fractions there: forcing one mechanism onto both would cost a translation in each\n * direction and buy nothing.\n */\n display: grid;\n grid-column: 2 / -1;\n grid-template-columns: var(--calendar-tracks);\n grid-template-rows: repeat(\n var(--calendar-lanes),\n var(--vectis-control-size-calendar-allday-lane)\n );\n gap: 1px;\n overflow-y: auto;\n max-block-size: var(--vectis-control-size-calendar-allday-max);\n padding-block: var(--vectis-space-1);\n /*\n * The band's tracks have to line up with the canvas's exactly, because dragging a bar reads\n * the pointer against the CANVAS geometry; the two boxes span the same grid columns, so one\n * measurement serves both. A few pixels of padding here would put the last column out by\n * that much, and a bar dropped on the right edge would land a day early.\n */\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-bar {\n grid-column: calc(var(--calendar-day-index) + 1) / span var(--calendar-bar-span);\n grid-row: calc(var(--calendar-bar-lane) + 1);\n min-inline-size: 0;\n }\n\n .v-calendar-hours {\n display: grid;\n grid-auto-rows: var(--calendar-hour);\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n /*\n * The label names the hour its row BEGINS at, and it is drawn on that rule rather than under\n * it: `align-self: start` shrinks the span from its 4rem row down to its one line, and the\n * negative margin then straddles it across the rule above.\n */\n .v-calendar-hour {\n position: relative;\n align-self: start;\n margin-block-start: calc(-1 * var(--calendar-hour-lift));\n padding-inline-end: var(--vectis-space-2);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n line-height: var(--vectis-text-caption-leading);\n text-align: end;\n }\n\n /*\n * The rule carried a little way back into the gutter, so the eye joins the label to the line\n * it names. A border and not a background: Windows forced-colors flattens a background to\n * Canvas and the tick would vanish, where a border keeps a colour; the argument VSeparator\n * and the icon registry both make.\n */\n .v-calendar-hour::before {\n content: '';\n position: absolute;\n inset-block-start: var(--calendar-hour-lift);\n inset-inline-end: 0;\n inline-size: var(--vectis-control-size-calendar-tick);\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n /*\n * Hidden and not removed. The span has to go on occupying its grid row: `display: none` would\n * take the row with it and shift every hour below up by one.\n */\n .v-calendar-hour:first-child {\n visibility: hidden;\n }\n\n .v-calendar-row:first-child .v-calendar-cell {\n border-block-start: none;\n }\n\n .v-calendar-columns {\n /* The containing block every card and the edge strip are placed against. The rows below\n are `display: contents`, so the cells themselves are this grid's items. */\n position: relative;\n display: grid;\n grid-column: 2 / -1;\n /*\n * Repeat the canvas track formula over the same width so grid and overlay columns align\n * without another layout mechanism.\n */\n grid-template-columns: var(--calendar-tracks);\n grid-auto-rows: var(--calendar-hour);\n }\n\n .v-calendar-row {\n /* The row exists for the accessibility tree and must not exist for the layout: a real\n box here would make the cells its children instead of the grid's, and every column\n would collapse. */\n display: contents;\n }\n\n .v-calendar-cell[data-today] {\n background: var(--vectis-color-accent-surface);\n }\n\n /*\n * The height is the event's own length and NOTHING ELSE. No `max()` against a floor token\n * here: the minimum is already applied where it belongs, `timedSegments` stretching every\n * segment to at least `slotDuration` before the overlap packing sees it, so what is drawn and\n * what the packing believes are the same box.\n */\n .v-calendar-event.v-calendar-block {\n position: absolute;\n inset-block-start: calc(var(--calendar-block-start-fraction) * 100%);\n block-size: calc(\n (var(--calendar-block-end-fraction) - var(--calendar-block-start-fraction)) * 100%\n );\n inset-inline-start: calc(\n (var(--calendar-day-index) + var(--calendar-block-column) / var(--calendar-block-columns)) /\n var(--calendar-columns) * 100%\n );\n inline-size: calc(\n var(--calendar-block-span) / var(--calendar-block-columns) / var(--calendar-columns) * 100% -\n var(--vectis-control-size-calendar-gap)\n );\n z-index: 1;\n }\n\n /*\n * It is `aria-hidden`: it is a second rendering of something the cell labels and the day\n * marked as today already say, and announcing \"current time\" from the middle of a grid would\n * interrupt without informing.\n */\n .v-calendar-now {\n position: absolute;\n inset-block-start: calc(var(--calendar-now-fraction) * 100%);\n inset-inline-start: calc(var(--calendar-day-index) / var(--calendar-columns) * 100%);\n inline-size: calc(100% / var(--calendar-columns));\n border-block-start: 1px solid var(--vectis-color-danger);\n /* Above the cards: it is the one thing that must stay readable over a full day. */\n z-index: 2;\n pointer-events: none;\n }\n\n .v-calendar-now::before {\n content: '';\n position: absolute;\n inset-inline-start: 0;\n inline-size: var(--vectis-control-size-calendar-now-dot);\n block-size: var(--vectis-control-size-calendar-now-dot);\n /*\n * Half its own size upwards and backwards, so the dot is centred on the line's start rather\n * than hanging below it.\n */\n margin-block-start: calc(var(--vectis-control-size-calendar-now-dot) / -2);\n margin-inline-start: calc(var(--vectis-control-size-calendar-now-dot) / -2);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-danger);\n }\n\n /* The line is a border, which forced colors keeps; the dot at its start is a background,\n which it would flatten to Canvas. */\n @media (forced-colors: active) {\n .v-calendar-now::before {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"VCalendarTimeGrid.js","names":["_sfc_main","VCalendarTimeGrid_vue_vue_type_script_setup_true_lang_default","VCalendarTimeGrid_default"],"sources":["../../../src/components/VCalendar/VCalendarTimeGrid.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"E extends CalendarEvent\">\n// @keyboard @a11y @core\n/**\n * Render event buttons inside their owning grid cells for ARIA and keyboard navigation. Borders\n * preserve time rulings in forced colours; shared gestures manage editing.\n */\nimport { computed, onBeforeUnmount, ref, useId } from 'vue'\n\nimport { formatDateDisplay } from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { formatTimeDisplay } from '../../utils/time'\n\nimport { useMessages } from '../../i18n/state'\n\nimport VCalendarEvent from './VCalendarEvent.vue'\nimport { EDGE_BAND } from './edgeStep'\nimport {\n gridGeometryOf,\n useCalendarGesture,\n type GestureBase,\n type GestureInit,\n type GrabStep,\n} from './gesture'\nimport { calendarIntent } from './keyboard'\nimport {\n MINUTES_PER_DAY,\n MINUTES_PER_HOUR,\n blockEdgeAt,\n floorToSlot,\n fractionOf,\n isAllDayEvent,\n minutesAt,\n moveEventToDay,\n packAllDay,\n packDayColumn,\n pointToCell,\n sameTimes,\n snapToSlot,\n spansMidnight,\n timeOf,\n timedSegments,\n timesOf,\n type AllDaySpan,\n type PlacedSegment,\n type SegmentPart,\n type TimeWindow,\n} from './layout'\nimport { drawnSlot, moveTimedEvent, resizeTimedEvent, roundToSlot } from './timeGrid'\nimport type {\n ActivatedCell,\n CalendarEvent,\n CalendarEventId,\n CalendarEventSlotProps,\n CalendarEventTimes,\n CalendarFormat,\n} from './types'\n\n/** Where the keyboard focus is in the grid, as a day and a moment in it. */\nexport interface FocusedCell {\n iso: string\n minutes: number\n}\n\nexport interface CalendarTimeGridProps<T> {\n days: string[]\n events: T[]\n timeWindow: TimeWindow\n slotDuration: number\n locale: string\n format: CalendarFormat\n /** Today's date, or nothing on the server, where it cannot be known. */\n today: string | null\n /** The time it is now, in minutes since midnight: again nothing on the server. */\n now: number | null\n /** Whether events can be moved and stretched. */\n editable: boolean\n /** Whether the whole calendar is frozen, which also takes its cards out of the tab order. */\n disabled: boolean\n /** Whether an empty stretch of a day can be drawn out, and reported as a new event's times. */\n creatable: boolean\n /** The node telling a reader how a card can be moved, shared by every card. */\n hintId: string\n /** How long a drag rests against an edge before the view pages. Zero turns paging off. */\n edgeStepDelay: number\n /** Whether dragging near the top or bottom scrolls the grid. */\n autoScroll: boolean\n /** What the grid as a whole is called: the range it is showing. */\n label: string\n}\n\nconst props = defineProps<CalendarTimeGridProps<E>>()\n\nconst emit = defineEmits<{\n /** An empty part of the grid was activated, at this day and this moment. */\n 'cell-activate': [cell: ActivatedCell]\n /** A card was clicked or activated. */\n 'event-activate': [event: E]\n /** An event was dropped somewhere else, or stretched to a new end. */\n 'event-drop': [id: CalendarEventId, times: CalendarEventTimes, kind: 'move' | 'resize']\n /** An empty stretch of a day was drawn out, and is offered as a new event's times. */\n 'slot-create': [times: CalendarEventTimes]\n /** Something happened that a reader who cannot see the grid needs told. */\n announce: [message: string]\n /** A page key asked for the previous or next period, which only the calendar can do. */\n step: [delta: -1 | 1]\n}>()\n\n/** Which cell holds the tab stop. The calendar owns it, so the toolbar can move it too. */\nconst focused = defineModel<FocusedCell>('focused', { required: true })\n\ndefineSlots<{\n event?(props: CalendarEventSlotProps<E>): unknown\n 'day-header'?(props: { iso: string; weekday: string; dayText: string; today: boolean }): unknown\n 'all-day-label'?(): unknown\n}>()\n\nconst m = useMessages()\nconst uid = useId()\n\n/** The hour each row stands for, as minutes since midnight. */\nconst hours = computed(() => {\n const rows: number[] = []\n const { start, end } = props.timeWindow\n for (let at = start; at < end; at += MINUTES_PER_HOUR) rows.push(at)\n return rows\n})\n\n/**\n * The id a stretch of empty grid carries while it is being drawn out. It never leaves this\n * component: on release the calendar is handed the times alone, and the card is gone.\n */\nconst DRAFT_ID = '__vectis-calendar-draft__'\n\n/** A gesture under way, whether it came from a pointer or from the keyboard. */\ninterface Gesture extends GestureBase {\n kind: 'move' | 'move-days' | 'resize' | 'create'\n /**\n * How far the arrows have taken a KEYBOARD grab from its origin: days along the days on show,\n * and minutes moved (the start) and stretched (the end).\n */\n keyDays?: number\n keyStart?: number\n keyEnd?: number\n /**\n * How far after the event's start the pointer took hold, in minutes, so the card does not\n * jump under it.\n */\n grabOffset: number\n grabColumn: number\n /**\n * Store the dragged event's column shares by event piece, not day index: paging changes which\n * dates those indices represent.\n */\n ghostColumns: Partial<\n Record<SegmentPart, Pick<PlacedSegment, 'column' | 'span' | 'columns'>>\n > | null\n ghostLane: number | null\n}\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst canvasEl = ref<HTMLElement | null>(null)\nconst columnsEl = ref<HTMLElement | null>(null)\n\nconst {\n gesture,\n ghostEvent,\n withPreview,\n edgeCue,\n isRtl,\n start,\n reapply,\n endLastGesture,\n consumeDrag,\n onPointermove,\n onPointerup,\n onPointercancel,\n onPointerleave,\n onFocusout,\n onCardKeydown,\n focusCell,\n idOfCard,\n cardState,\n} = useCalendarGesture<E, Gesture>({\n events: () => props.events,\n eventOf: (id): E | undefined => eventOf(id),\n rootEl,\n // The SCROLLER, not the columns box: the all-day band sits in the sticky header, and this is\n // the only element that contains both it and the canvas.\n captureEl: rootEl,\n measureEl: columnsEl,\n columns: () => props.days.length,\n editable: () => props.editable,\n hintId: () => props.hintId,\n slotMinutes: () => props.slotDuration,\n edgeStepDelay: () => props.edgeStepDelay,\n layout: () => props.days,\n applyPoint,\n onEdges: (state, scroller) =>\n setScrollSpeed(\n props.autoScroll && scroller ? blockEdgeAt(state.lastY, scroller, EDGE_BAND) : 0,\n ),\n onRelease: stopScrolling,\n onLetGo: (state) => {\n if (state.kind !== 'create') return false\n // A press that never travelled drew nothing: its click reports the cell instead.\n if (state.moved) emit('slot-create', state.preview)\n return true\n },\n onDrop: (state) =>\n emit('event-drop', state.id, state.preview, state.kind === 'resize' ? 'resize' : 'move'),\n grab: (item) =>\n props.editable\n ? initFor({\n kind: isAllDayEvent(item) ? 'move-days' : 'move',\n id: item.id,\n origin: timesOf(item),\n pointerId: null,\n originX: 0,\n originY: 0,\n grabOffset: 0,\n grabColumn: 0,\n })\n : null,\n onGrabStep,\n step: (delta) => emit('step', delta),\n announce: (message) => emit('announce', message),\n})\n\n/** Where the echo goes, worked out afresh on every render rather than remembered. */\nconst NO_GHOSTS: readonly PlacedSegment[] = Object.freeze([])\n\n/*\n * The \"no echo\" answer is ONE shared array. A bar dragged along the band recomputes this on\n * every slot; a fresh `[]` each time is a new value, which would wake `placed` and re-pack\n * every column for a gesture that never touches them.\n */\nconst ghostSegments = computed<readonly PlacedSegment[]>(() => {\n const state = gesture.value\n const event = ghostEvent.value\n const packing = state?.ghostColumns\n if (!event || !packing) return NO_GHOSTS\n return timedSegments([event], props.days, props.timeWindow, props.slotDuration).flatMap(\n (segment) => {\n const columns = packing[segment.part]\n return columns ? [{ ...segment, ...columns }] : []\n },\n )\n})\n\nconst ghostSpan = computed<AllDaySpan | null>(() => {\n const state = gesture.value\n const event = ghostEvent.value\n if (!event || state?.ghostLane == null) return null\n const [span] = packAllDay([event], props.days)\n return span ? { ...span, lane: state.ghostLane } : null\n})\n\n/*\n * The model split in two, once per change to it: the timed events the columns draw, and the\n * all-day ones the band above them draws. The split is what lets a gesture rebuild only its own\n * half; see `drawnTimed`.\n */\nconst timedEvents = computed(() => props.events.filter((event) => !isAllDayEvent(event)))\nconst allDayEvents = computed(() => props.events.filter((event) => isAllDayEvent(event)))\n\n/**\n * The half a gesture belongs to is decided by its KIND and never by looking at the preview.\n * `move-days` is the only gesture that starts on the band, and the three others only start on a\n * timed card or an empty cell (both press handlers and the keyboard grab refuse an all-day\n * event), so the two lists below can each return their untouched half as the same array.\n */\nconst drawnTimed = computed<E[]>(() => {\n const state = gesture.value\n if (!state || state.kind === 'move-days') return timedEvents.value\n\n if (state.kind === 'create') {\n if (!state.moved) return timedEvents.value\n const draft = {\n id: DRAFT_ID,\n title: m.value.calendar.untitled,\n ...state.preview,\n // It exists while the pointer is down and never reaches the model, which makes the cast\n // safe.\n } as unknown as E\n return [...timedEvents.value, draft]\n }\n\n return withPreview(timedEvents.value)\n})\n\n/** The all-day bars as the band should currently draw them; see `drawnTimed`. */\nconst drawnAllDay = computed<E[]>(() =>\n gesture.value?.kind === 'move-days' ? withPreview(allDayEvents.value) : allDayEvents.value,\n)\n\nconst timedById = computed(\n () => new Map<CalendarEventId, E>(drawnTimed.value.map((event) => [event.id, event])),\n)\nconst allDayById = computed(\n () => new Map<CalendarEventId, E>(drawnAllDay.value.map((event) => [event.id, event])),\n)\n\n/** Any event on show, as it is currently drawn. For the handlers; the template reads a half. */\nconst eventOf = (id: CalendarEventId): E | undefined =>\n timedById.value.get(id) ?? allDayById.value.get(id)\n\n/** The bars above the grid, and how many rows they need between them. */\nconst allDay = computed(() => {\n const ghost = ghostSpan.value\n const spans = packAllDay(\n ghost ? drawnAllDay.value.filter((event) => event.id !== ghost.id) : drawnAllDay.value,\n props.days,\n )\n return ghost ? [...spans, ghost] : spans\n})\nconst allDayLanes = computed(() =>\n allDay.value.reduce((most, span) => Math.max(most, span.lane + 1), 0),\n)\n\n/** The row a moment of the window is drawn in, held inside the grid. */\nconst rowOf = (minutes: number) =>\n clamp(\n Math.floor((minutes - props.timeWindow.start) / MINUTES_PER_HOUR),\n 0,\n hours.value.length - 1,\n )\n\n/**\n * Where the current-time line goes, or nothing when it has no business being drawn: on the\n * server, on a range that does not include today, or at an hour the window has cropped\n * away.\n */\nconst nowMark = computed(() => {\n if (props.now === null || props.today === null) return null\n const dayIndex = props.days.indexOf(props.today)\n if (dayIndex === -1) return null\n if (props.now < props.timeWindow.start || props.now > props.timeWindow.end) return null\n return {\n day: dayIndex,\n row: rowOf(props.now),\n fraction: fractionOf(props.now, props.timeWindow),\n }\n})\n\n/** The placed boxes, day by day. */\nconst placed = computed(() => {\n const segments = timedSegments(drawnTimed.value, props.days, props.timeWindow, props.slotDuration)\n const ghosts = ghostSegments.value\n // Read off the echo's own boxes, never off `ghostEvent`, which changes with every slot of a\n // bar's drag and would wake this for the band's gesture too.\n const ghostId = ghosts[0]?.id\n\n const byDay = new Map<number, typeof segments>()\n for (const segment of segments) {\n /*\n * The echo is held OUT of the packing and put back below with the width and column it had\n * when the drag began. Packed with the rest, it would share its column with the card being\n * dragged the moment the two overlapped; so the card under the pointer would halve in width\n * and then grow back as it moved away, which reads as the drag going wrong.\n */\n if (ghostId !== undefined && segment.id === ghostId) continue\n const list = byDay.get(segment.dayIndex)\n if (list) list.push(segment)\n else byDay.set(segment.dayIndex, [segment])\n }\n\n const real = [...byDay.values()].flatMap((list) => packDayColumn(list))\n return ghosts.length > 0 ? [...real, ...ghosts] : real\n})\n\nconst cellId = (iso: string, minutes: number) => `${uid}-c-${iso}-${minutes}`\n\n/*\n * These two maps, of 7 and 24 entries, recompute when the locale, the days or the window change\n * and never because a card moved. Both are keyed by exactly what the template walks, `days` and\n * `hours`, so every lookup is a hit and none of them needs a formatting fallback.\n */\nconst dayLabels = computed(() => {\n const map = new Map<string, { short: string; number: string; full: string }>()\n for (const iso of props.days) {\n map.set(iso, {\n short: formatDateDisplay(iso, props.locale, { weekday: 'short' }),\n number: formatDateDisplay(iso, props.locale, { day: 'numeric' }),\n full: formatDateDisplay(iso, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n }),\n })\n }\n return map\n})\n\nconst hourLabels = computed(() => {\n const map = new Map<number, string>()\n for (const minutes of hours.value)\n map.set(minutes, formatTimeDisplay(timeOf(minutes), props.locale, props.format))\n return map\n})\n\nconst hourLabel = (minutes: number) => hourLabels.value.get(minutes) ?? ''\nconst dayName = (iso: string) => dayLabels.value.get(iso)?.short ?? ''\nconst dayNumber = (iso: string) => dayLabels.value.get(iso)?.number ?? ''\n\n/**\n * The cells, row by row, with everything about them that no gesture can change: the id the\n * focus is moved by, the name a reader hears, and the number `byCell` files cards under.\n */\nconst gridRows = computed(() => {\n const columns = props.days.length\n return hours.value.map((minutes, row) => ({\n minutes,\n cells: props.days.map((iso, day) => ({\n iso,\n day,\n row,\n key: row * columns + day,\n id: cellId(iso, minutes),\n label: `${dayLabels.value.get(iso)?.full ?? iso}, ${hourLabel(minutes)}`,\n })),\n }))\n})\n\n/**\n * What a dependant reads has to be this computed, never a cache held beside it. A hit reads no\n * prop, so a computed that only ever hit would track neither the locale nor the hour format,\n * and would go on printing the old ones after either changed.\n */\nconst timeTextOf = computed(() => {\n const { locale, format } = props\n const cache = new Map<string, string>()\n return (event: CalendarEvent): string => {\n const key = `${event.startTime}|${event.endTime}|${event.timezone ?? ''}`\n let text = cache.get(key)\n if (text === undefined) {\n const start = formatTimeDisplay(event.startTime, locale, format)\n const end = formatTimeDisplay(event.endTime, locale, format)\n const range = `${start} – ${end}`\n text = event.timezone ? `${range} (${event.timezone})` : range\n cache.set(key, text)\n }\n return text\n }\n})\n\n/** One card as the template draws it, with its event and its box already worked out. */\ninterface Card {\n segment: PlacedSegment\n event: E\n timeText: string\n style: Record<string, string>\n}\n\n/** The cards, filed under the cell their start falls in. */\nconst byCell = computed(() => {\n const map = new Map<number, Card[]>()\n const columns = props.days.length\n const events = timedById.value\n const textOf = timeTextOf.value\n for (const segment of placed.value) {\n // A placement always has an event behind it. Skipping rather than asserting turns a\n // mismatch between the two into one missing card instead of a grid that fails to render.\n const event = events.get(segment.id)\n if (!event) continue\n const key = rowOf(segment.start) * columns + segment.dayIndex\n const card: Card = {\n segment,\n event,\n timeText: textOf(event),\n style: {\n '--calendar-day-index': String(segment.dayIndex),\n '--calendar-block-start-fraction': String(fractionOf(segment.start, props.timeWindow)),\n '--calendar-block-end-fraction': String(fractionOf(segment.end, props.timeWindow)),\n '--calendar-block-column': String(segment.column),\n '--calendar-block-span': String(segment.span),\n '--calendar-block-columns': String(segment.columns),\n },\n }\n const list = map.get(key)\n if (list) list.push(card)\n else map.set(key, [card])\n }\n return map\n})\n\n/** An hour with nothing in it, shared rather than allocated per empty cell. */\nconst NO_CARDS: readonly Card[] = Object.freeze([])\nconst cardsAt = (key: number) => byCell.value.get(key) ?? NO_CARDS\n\nconst isFocused = (iso: string, minutes: number) =>\n focused.value.iso === iso && focused.value.minutes === minutes\n\n/**\n * Which cell holds the tab stop: the focused one, rounded to its row; or, when that cell is not\n * on screen (after a view change, or before anything has been focused at all), the first cell.\n */\nconst tabbable = computed<FocusedCell | null>(() => {\n const current = focused.value\n const { start, end } = props.timeWindow\n if (props.days.includes(current.iso)) {\n const rounded =\n Math.floor((current.minutes - start) / MINUTES_PER_HOUR) * MINUTES_PER_HOUR + start\n if (rounded >= start && rounded < end) return { iso: current.iso, minutes: rounded }\n }\n const iso = props.days[0]\n return iso ? { iso, minutes: start } : null\n})\n\nconst isTabStop = (iso: string, minutes: number) =>\n tabbable.value?.iso === iso && tabbable.value.minutes === minutes\n\nfunction moveFocusTo(iso: string, minutes: number) {\n focused.value = { iso, minutes }\n // @a11y\n focusCell(cellId(iso, minutes), true)\n}\n\n/** A day, then a start and an end: where a reader is told an event now is. */\nfunction timesText(times: CalendarEventTimes) {\n const start = formatTimeDisplay(times.startTime, props.locale, props.format)\n const end = formatTimeDisplay(times.endTime, props.locale, props.format)\n const day = formatDateDisplay(times.start, props.locale, {\n weekday: 'long',\n day: 'numeric',\n month: 'long',\n })\n return `${day}, ${start} – ${end}`\n}\n\nfunction onKeydown(event: KeyboardEvent) {\n const target = event.target as HTMLElement | null\n\n // A card answers a different table from the grid it sits in; that is what a grab mode IS.\n // Delegated here, on the same element as the pointer, so the two never drift apart.\n const card = target?.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n onCardKeydown(event, card)\n return\n }\n\n const cell = target?.closest<HTMLElement>('.v-calendar-cell')\n if (!cell) return\n\n const intent = calendarIntent(event, 'cell', props.slotDuration, isRtl())\n if (!intent) return\n\n const iso = cell.dataset.iso!\n const minutes = Number(cell.dataset.minutes)\n const index = props.days.indexOf(iso)\n\n if (intent.kind === 'moveFocus') {\n const day = props.days[clamp(index + intent.days, 0, props.days.length - 1)]!\n const at = clamp(\n minutes + intent.minutes,\n props.timeWindow.start,\n props.timeWindow.end - MINUTES_PER_HOUR,\n )\n event.preventDefault()\n moveFocusTo(day, at)\n return\n }\n\n if (intent.kind === 'rowEdge') {\n const day = intent.edge === 'start' ? props.days[0] : props.days.at(-1)\n if (!day) return\n event.preventDefault()\n moveFocusTo(day, minutes)\n return\n }\n\n if (intent.kind === 'period') {\n event.preventDefault()\n emit('step', intent.delta)\n return\n }\n\n /*\n * Enter or Space on an empty cell reports it through `cell-activate`, the keyboard's route to\n * a new event (WCAG 2.1.1): the cell carries a day and an hour, and how long the event lasts\n * is a question for the consumer's own form, where the pointer answers it by how far it was\n * drawn.\n */\n if (intent.kind === 'activate' || intent.kind === 'openDay') {\n event.preventDefault()\n emit('cell-activate', { date: iso, minutes })\n }\n}\n\n/**\n * Read before the gesture is set. Both are derived from the drawn events, which the gesture\n * immediately starts rewriting; a line later and they would already describe the preview rather\n * than the place it came from.\n */\nfunction initFor(\n init: Omit<GestureInit<Gesture>, 'ghostColumns' | 'ghostLane'>,\n): GestureInit<Gesture> {\n const segments = placed.value.filter((item) => item.id === init.id)\n const span = allDay.value.find((item) => item.id === init.id)\n return {\n ...init,\n ghostColumns:\n segments.length > 0\n ? Object.fromEntries(\n segments.map((segment) => [\n segment.part,\n { column: segment.column, span: segment.span, columns: segment.columns },\n ]),\n )\n : null,\n ghostLane: span ? span.lane : null,\n }\n}\n\nconst pointAt = (x: number, y: number, rect: DOMRect, rtl: boolean) =>\n pointToCell({ x, y }, gridGeometryOf(rect, props.days.length, rtl), props.timeWindow, rtl)\n\n/** Every press on the grid, dispatched by what it landed on. */\nfunction onGridPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || event.button !== 0) return\n\n const target = event.target as HTMLElement | null\n const rect = columnsEl.value?.getBoundingClientRect()\n if (!target || !rect) return\n const rtl = isRtl()\n const point = pointAt(event.clientX, event.clientY, rect, rtl)\n\n const card = target.closest<HTMLElement>('.v-calendar-event')\n if (card) {\n if (!props.editable) return\n const item = eventOf(idOfCard(card))\n if (!item || isAllDayEvent(item)) return\n\n const resizing = target.closest('[data-calendar-handle]') !== null\n // The morning card of an overnight event is held from the evening before, so the offset\n // carries the day between the two.\n const pressedMorning = spansMidnight(item) && props.days[point.columnIndex] === item.end\n start(\n initFor({\n kind: resizing ? 'resize' : 'move',\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabOffset: resizing\n ? 0\n : (pressedMorning ? MINUTES_PER_DAY : 0) +\n point.minutes -\n minutesAt(item.startTime, props.timeWindow.start),\n grabColumn: point.columnIndex,\n }),\n rtl,\n )\n return\n }\n\n if (!props.creatable || !target.closest('.v-calendar-cell')) return\n const iso = props.days[point.columnIndex]\n if (!iso) return\n\n /*\n * The pressed slot is where the drawing is anchored; rounded DOWN, never to the nearest,\n * because a press at 09:07 means \"the nine o'clock slot\" and not the quarter past. Nothing is\n * drawn and nothing is reported until the press travels: a press that stays still is a click,\n * and reports its cell like any other.\n */\n const from = floorToSlot(point.minutes, props.slotDuration)\n start(\n initFor({\n kind: 'create',\n id: DRAFT_ID,\n origin: {\n start: iso,\n end: iso,\n startTime: timeOf(from),\n endTime: timeOf(Math.min(from + props.slotDuration, props.timeWindow.end)),\n },\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabOffset: 0,\n grabColumn: point.columnIndex,\n }),\n rtl,\n )\n}\n\n/** A press on the band of all-day bars above the grid. */\n/**\n * The band's bars answer the same card table as the grid's cards. They sit in the sticky\n * header, outside the grid whose keydown the cards reach, so the band delegates on its own.\n */\nfunction onBandKeydown(event: KeyboardEvent) {\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n if (card) onCardKeydown(event, card)\n}\n\nfunction onBandPointerdown(event: PointerEvent) {\n endLastGesture()\n if (gesture.value || !props.editable || event.button !== 0) return\n\n const card = (event.target as HTMLElement | null)?.closest<HTMLElement>('.v-calendar-event')\n const item = card ? eventOf(idOfCard(card)) : undefined\n const rect = columnsEl.value?.getBoundingClientRect()\n if (!item || !rect) return\n const rtl = isRtl()\n\n start(\n initFor({\n kind: 'move-days',\n id: item.id,\n origin: timesOf(item),\n pointerId: event.pointerId,\n originX: event.clientX,\n originY: event.clientY,\n grabOffset: 0,\n grabColumn: pointAt(event.clientX, event.clientY, rect, rtl).columnIndex,\n }),\n rtl,\n )\n}\n\n/**\n * One arrow press on an all-day bar held by the keyboard: a whole day sideways along the days\n * on show, nothing else, since a bar has no hours to move through or to stretch.\n */\nfunction onBarStep(state: Gesture, step: GrabStep, item: E): boolean {\n if (step.kind !== 'grabMove' || step.days === 0) return false\n const keyDays = (state.keyDays ?? 0) + step.days\n const base = props.days.indexOf(state.origin.start)\n const column = clamp(base + keyDays, Math.min(base, 0), props.days.length - 1)\n const next = column < 0 ? state.origin : moveEventToDay(state.origin, props.days[column]!)\n if (sameTimes(next, state.preview)) return false\n state.keyDays = keyDays\n state.preview = next\n emit('announce', m.value.calendar.movedTo(item.title, timesText(state.preview)))\n return true\n}\n\n/** Works out where the dragged event now belongs, from wherever the pointer last was. */\nfunction applyPoint(state: Gesture, rect: DOMRect) {\n const point = pointAt(state.lastX, state.lastY, rect, state.rtl)\n const crossMidnight = spansMidnight(state.origin)\n\n /*\n * A move's start is rounded without being held inside the day, since an overnight event's may\n * leave it.\n */\n const column =\n state.kind === 'create' || (state.kind === 'resize' && !crossMidnight) ? 0 : point.columnIndex\n const minutes =\n state.kind === 'move-days'\n ? 0\n : state.kind === 'move'\n ? roundToSlot(point.minutes - state.grabOffset, props.slotDuration)\n : state.kind === 'create'\n ? floorToSlot(point.minutes, props.slotDuration)\n : snapToSlot(point.minutes, props.slotDuration)\n if (isLastApplied(state, column, minutes)) return\n\n if (state.kind === 'create') {\n const anchor = minutesAt(state.origin.startTime, props.timeWindow.start)\n setPreview(\n state,\n drawnSlot(state.origin.start, anchor, minutes, props.slotDuration, props.timeWindow),\n )\n return\n }\n\n if (state.kind === 'resize') {\n const endDay = props.days[column] ?? state.origin.end\n setPreview(\n state,\n resizeTimedEvent(\n state.origin,\n endDay,\n minutes,\n props.slotDuration,\n props.timeWindow,\n crossMidnight,\n ),\n )\n return\n }\n\n if (state.kind === 'move-days') {\n /*\n * The day is read out of the list of days on show; never from date arithmetic on how many\n * columns were crossed, which would count the days the calendar is hiding and land the\n * event on a Saturday nobody can see. A bar keeps its length in days.\n */\n const target = clamp(\n originColumn(state) + (column - state.grabColumn),\n 0,\n props.days.length - 1,\n )\n const iso = props.days[target]\n if (iso) setPreview(state, moveEventToDay(state.origin, iso))\n return\n }\n\n /*\n * A timed card is placed from the column the pointer is over, and its start from the pointer\n * less the offset it was taken hold at. For an overnight event taken by its morning that start\n * lands on the day before the pointer's column, which `moveTimedEvent` works out.\n */\n const iso = props.days[column]\n if (!iso) return\n setPreview(state, moveTimedEvent(state.origin, iso, minutes, props.timeWindow, crossMidnight))\n}\n\n/** The slot `applyPoint` last worked a preview out for. */\nlet lastApplied: {\n state: Gesture\n days: readonly string[]\n timeWindow: TimeWindow\n step: number\n column: number\n minutes: number\n} | null = null\n\nfunction isLastApplied(state: Gesture, column: number, minutes: number): boolean {\n const last = lastApplied\n if (\n last?.state === state &&\n last.days === props.days &&\n last.timeWindow === props.timeWindow &&\n last.step === props.slotDuration &&\n last.column === column &&\n last.minutes === minutes\n ) {\n return true\n }\n lastApplied = {\n state,\n days: props.days,\n timeWindow: props.timeWindow,\n step: props.slotDuration,\n column,\n minutes,\n }\n return false\n}\n\n/**\n * The comparison is the whole point, not a nicety. The gesture is a deep ref, so any assignment\n * to `preview`, an equal object included, wakes the drawn events, the packing of every column\n * and a render of all 168 cells.\n */\nfunction setPreview(state: Gesture, next: CalendarEventTimes) {\n if (!sameTimes(state.preview, next)) state.preview = next\n}\n\n/**\n * Which column the bar being moved started in. And when the origin day is not on screen at all;\n * a bar that began before this range, or one whose view has been paged out from under the drag;\n * falling back to the grab column makes the offset zero, so the bar lands under the pointer\n * instead of nowhere.\n */\nfunction originColumn(state: Gesture): number {\n const index = props.days.indexOf(state.origin.start)\n return index === -1 ? state.grabColumn : index\n}\n\n/**\n * One arrow press on a card held by the keyboard. Applied to the preview instead, a step held\n * against the end of the day built on its 23:59 and the event came back shorter and off the\n * slot grid.\n */\nfunction onGrabStep(state: Gesture, step: GrabStep, item: E): boolean {\n const { origin } = state\n if (state.kind === 'move-days') return onBarStep(state, step, item)\n const crossMidnight = spansMidnight(origin)\n let keyDays = state.keyDays ?? 0\n let keyStart = state.keyStart ?? 0\n let keyEnd = state.keyEnd ?? 0\n if (step.kind === 'grabResize') keyEnd += step.minutes\n else {\n keyDays += step.days\n keyStart += step.minutes\n }\n\n /*\n * An overnight event may have been taken by its morning card with its start on a day off show\n * (index -1): it can then go right, never further left than where it already is.\n */\n const base = props.days.indexOf(origin.start)\n const column = clamp(base + keyDays, Math.min(base, 0), props.days.length - 1)\n const day = column < 0 || keyDays === 0 ? origin.start : props.days[column]!\n const moved = moveTimedEvent(\n origin,\n day,\n minutesAt(origin.startTime, props.timeWindow.start) + keyStart,\n props.timeWindow,\n crossMidnight,\n )\n const next =\n keyEnd === 0\n ? moved\n : resizeTimedEvent(\n moved,\n moved.end,\n minutesAt(moved.endTime, props.timeWindow.end) + keyEnd,\n props.slotDuration,\n props.timeWindow,\n crossMidnight,\n )\n\n // Held against an edge, the press goes nowhere: nothing to announce, nothing to drop, and\n // nothing kept either, so the next press the other way moves at once.\n if (sameTimes(next, state.preview)) return false\n state.keyDays = keyDays\n state.keyStart = keyStart\n state.keyEnd = keyEnd\n state.preview = next\n emit('announce', m.value.calendar.movedTo(item.title, timesText(state.preview)))\n return true\n}\n\n/** How many pixels a frame the grid scrolls at full tilt. */\nconst AUTO_SCROLL_SPEED = 14\n\n/*\n * The design system's only `requestAnimationFrame` loop, and it earns that: a pointer held\n * still fires no events at all, so nothing else can drive continuous motion. Everything else\n * that moves here is either a transition or a timer.\n */\nlet scrollFrame: number | null = null\nlet scrollSpeed = 0\n\nfunction setScrollSpeed(speed: number) {\n scrollSpeed = speed\n if (speed === 0) {\n stopScrolling()\n return\n }\n scrollFrame ??= requestAnimationFrame(scrollStep)\n}\n\nfunction scrollStep() {\n scrollFrame = null\n const root = rootEl.value\n const state = gesture.value\n if (!root || !state || scrollSpeed === 0) return\n\n root.scrollTop += scrollSpeed * AUTO_SCROLL_SPEED\n reapply(state)\n scrollFrame = requestAnimationFrame(scrollStep)\n}\n\nfunction stopScrolling() {\n if (scrollFrame !== null) cancelAnimationFrame(scrollFrame)\n scrollFrame = null\n scrollSpeed = 0\n}\n\nonBeforeUnmount(stopScrolling)\n\nfunction onCardClick(id: CalendarEventId) {\n // `pointerup` fires before `click`, so letting go at the end of a drag would ALSO open the\n // event; the consumer's editor over every card the reader had just moved.\n if (consumeDrag()) return\n const item = eventOf(id)\n if (item && id !== DRAFT_ID) emit('event-activate', item)\n}\n\n/** A click on an empty part of a day, reported as the cell it landed in. */\nfunction onGridClick(event: MouseEvent) {\n const target = event.target as HTMLElement | null\n if (target?.closest('.v-calendar-event')) return\n if (consumeDrag()) return\n const cell = target?.closest<HTMLElement>('.v-calendar-cell')\n if (!cell) return\n emit('cell-activate', { date: cell.dataset.iso!, minutes: Number(cell.dataset.minutes) })\n}\n\n/** The cursor a gesture holds for as long as it lasts, whatever the pointer passes over. */\nconst gestureCursor = computed(() => {\n const state = gesture.value\n if (!state || state.pointerId === null) return undefined\n if (state.kind === 'resize') return 'resize'\n if (state.kind === 'create' && state.moved) return 'create'\n return undefined\n})\n\n/** Brings a moment of the day to the top of the visible area. */\nfunction scrollToMinutes(minutes: number) {\n const root = rootEl.value\n const canvas = canvasEl.value\n if (!root || !canvas || hours.value.length === 0) return\n const rowHeight = canvas.offsetHeight / hours.value.length\n if (rowHeight <= 0) return\n root.scrollTop = ((minutes - props.timeWindow.start) / MINUTES_PER_HOUR) * rowHeight\n}\n\n// The contract VCalendar drives every view through, so its toolbar need not know which one\n// is on screen. VCalendarMonth answers the same two, its `scrollToMinutes` being a no-op.\ndefineExpose({\n /** Brings the focus onto the cell the grid is currently pointing at. */\n focus: (options?: FocusOptions) => {\n const cell = tabbable.value\n if (cell) focusCell(cellId(cell.iso, cell.minutes), false, options)\n },\n /** Scrolls the grid so a given moment of the day sits at the top of the visible area. */\n scrollToMinutes,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n class=\"v-calendar-time-grid\"\n :data-outside=\"gesture?.outside ? '' : undefined\"\n :data-gesture=\"gestureCursor\"\n :style=\"{ '--calendar-columns': String(days.length) }\"\n @pointermove=\"onPointermove\"\n @pointerup=\"onPointerup\"\n @pointercancel=\"onPointercancel\"\n @pointerleave=\"onPointerleave\"\n @focusout=\"onFocusout\"\n >\n <div class=\"v-calendar-head\">\n <div class=\"v-calendar-head-gutter\" />\n <div v-for=\"iso in days\" :key=\"iso\" class=\"v-calendar-head-day\">\n <slot\n name=\"day-header\"\n :iso=\"iso\"\n :weekday=\"dayName(iso)\"\n :day-text=\"dayNumber(iso)\"\n :today=\"iso === today\"\n >\n <span class=\"v-calendar-weekday\">{{ dayName(iso) }}</span>\n <span class=\"v-calendar-head-number\" :class=\"{ 'v-calendar-today': iso === today }\">\n {{ dayNumber(iso) }}\n </span>\n </slot>\n </div>\n\n <!-- The band of all-day bars, kept INSIDE the header so that the whole of it is one\n sticky box: two boxes would each need to know how tall the other was. -->\n <template v-if=\"allDay.length > 0\">\n <div class=\"v-calendar-allday-label\">\n <slot name=\"all-day-label\">{{ m.calendar.allDay }}</slot>\n </div>\n <div\n class=\"v-calendar-allday\"\n role=\"group\"\n :aria-label=\"m.calendar.allDay\"\n :style=\"{ '--calendar-lanes': String(allDayLanes) }\"\n @pointerdown=\"onBandPointerdown\"\n @keydown=\"onBandKeydown\"\n >\n <VCalendarEvent\n v-for=\"span in allDay\"\n :key=\"span.id\"\n class=\"v-calendar-bar\"\n :event=\"allDayById.get(span.id)!\"\n layout=\"chip\"\n :disabled=\"disabled\"\n :continues-before=\"span.continuesBefore\"\n :continues-after=\"span.continuesAfter\"\n v-bind=\"cardState(span.id)\"\n :style=\"{\n '--calendar-day-index': String(span.startIndex),\n '--calendar-bar-span': String(span.span),\n '--calendar-bar-lane': String(span.lane),\n }\"\n @click=\"onCardClick(span.id)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n </div>\n </template>\n </div>\n\n <div ref=\"canvasEl\" class=\"v-calendar-canvas\">\n <div class=\"v-calendar-hours\" aria-hidden=\"true\">\n <span v-for=\"minutes in hours\" :key=\"minutes\" class=\"v-calendar-hour\">\n {{ hourLabel(minutes) }}\n </span>\n </div>\n\n <div\n ref=\"columnsEl\"\n class=\"v-calendar-columns v-calendar-edge-cue\"\n role=\"grid\"\n :aria-label=\"label\"\n :data-edge=\"edgeCue\"\n @keydown=\"onKeydown\"\n @click=\"onGridClick\"\n @pointerdown=\"onGridPointerdown\"\n >\n <div v-for=\"gridRow in gridRows\" :key=\"gridRow.minutes\" role=\"row\" class=\"v-calendar-row\">\n <div\n v-for=\"cell in gridRow.cells\"\n :id=\"cell.id\"\n :key=\"cell.iso\"\n role=\"gridcell\"\n class=\"v-calendar-cell\"\n :data-iso=\"cell.iso\"\n :data-minutes=\"gridRow.minutes\"\n :data-today=\"cell.iso === today ? '' : undefined\"\n :tabindex=\"isTabStop(cell.iso, gridRow.minutes) ? 0 : -1\"\n :aria-label=\"cell.label\"\n :aria-selected=\"isFocused(cell.iso, gridRow.minutes) ? true : undefined\"\n >\n <VCalendarEvent\n v-for=\"card in cardsAt(cell.key)\"\n :key=\"card.segment.id\"\n class=\"v-calendar-block\"\n :event=\"card.event\"\n layout=\"block\"\n :disabled=\"disabled\"\n :time-text=\"card.timeText\"\n :continues-before=\"card.segment.clippedStart\"\n :continues-after=\"card.segment.clippedEnd\"\n :resizable=\"editable && card.segment.id !== DRAFT_ID && card.segment.part !== 'head'\"\n v-bind=\"cardState(card.segment.id)\"\n :style=\"card.style\"\n @click=\"onCardClick(card.segment.id)\"\n >\n <template v-if=\"$slots.event\" #default=\"slotProps\">\n <slot name=\"event\" v-bind=\"slotProps\" />\n </template>\n </VCalendarEvent>\n\n <!-- The line is drawn INSIDE the cell holding the current hour, never as a\n direct child of the grid or of a row: a `grid` owns only rows and a row only\n cells, so an extra box at either level fails `aria-required-children`. -->\n <div\n v-if=\"nowMark && nowMark.day === cell.day && nowMark.row === cell.row\"\n class=\"v-calendar-now\"\n aria-hidden=\"true\"\n :style=\"{\n '--calendar-day-index': String(nowMark.day),\n '--calendar-now-fraction': String(nowMark.fraction),\n }\"\n />\n </div>\n </div>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@layer vectis.components {\n .v-calendar-time-grid {\n /*\n * The one scrolling box; its scrolling itself is `.v-calendar-view`'s, in VCalendar's\n * sheet. The day names are sticky INSIDE it rather than in a container of their own, which\n * keeps them aligned with the columns for free: a separate header would have to be told how\n * far the body had scrolled sideways, and told again every time the column widths changed.\n */\n --calendar-hour: var(--vectis-control-size-calendar-hour);\n --calendar-gutter: var(--vectis-control-size-calendar-gutter);\n /*\n * The day columns, written once for the three boxes that lay them out: the header and the\n * canvas behind the hour gutter, the all-day band and the columns box without it. The three\n * must resolve to identical tracks; a bar is dropped by reading the pointer against the\n * COLUMNS' geometry; so one definition is what keeps them from drifting apart.\n */\n --calendar-tracks: repeat(\n var(--calendar-columns),\n minmax(var(--vectis-control-size-calendar-day-min), 1fr)\n );\n /*\n * One number, so the two cannot drift apart; two literals would misalign the moment the\n * caption's leading changed, with nothing to point at it.\n */\n --calendar-hour-lift: calc(\n var(--vectis-text-caption-size) * var(--vectis-text-caption-leading) / 2\n );\n\n position: relative;\n }\n\n /*\n * It is set on every descendant as well as on the scroller, because an engine that resolves\n * the cursor from what is under the pointer rather than from the capture target would\n * otherwise show the `pointer` of whichever card the gesture passes over.\n * `:not([data-outside])` keeps it off a gesture held outside the calendar, whose\n * `not-allowed` lives in VCalendar's sheet at (0,2,0): at equal weight the two would be\n * arbitrated by the order the consumer's bundler put the sheets in.\n */\n .v-calendar-time-grid[data-gesture='resize']:not([data-outside]),\n .v-calendar-time-grid[data-gesture='resize']:not([data-outside]) * {\n cursor: ns-resize;\n }\n\n .v-calendar-time-grid[data-gesture='create']:not([data-outside]),\n .v-calendar-time-grid[data-gesture='create']:not([data-outside]) * {\n cursor: move;\n }\n\n .v-calendar-head,\n .v-calendar-canvas {\n display: grid;\n grid-template-columns: var(--calendar-gutter) var(--calendar-tracks);\n /*\n * Without this the BOX would stay the width of the scroller while the tracks spilled out of\n * it: the sticky day names would paint their background only as far as the original width,\n * and the columns would run out from under them halfway across. `min-content` on a grid is\n * the sum of its track minimums, which is exactly the width wanted here.\n */\n min-inline-size: min-content;\n }\n\n /*\n * Shared grid stacking order: cards 1, current-time line and edge cues 2, dragged cards 3,\n * sticky headings 4. Intermediate containers must not create stacking contexts.\n */\n .v-calendar-head {\n position: sticky;\n inset-block-start: 0;\n z-index: 4;\n background: var(--calendar-surface);\n border-block-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-head-gutter {\n /* The corner above the hour labels. It is empty, and it is here so the day names line\n up with their columns rather than starting at the edge of the box. */\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-head-day {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--vectis-space-1);\n padding-block: var(--vectis-space-2);\n padding-inline: var(--vectis-space-1);\n }\n\n /*\n * Today is marked on the number alone, the way a calendar marks a date rather than a column;\n * the `.v-calendar-today` paint, in VCalendar's sheet.\n */\n .v-calendar-head-number {\n display: flex;\n align-items: center;\n justify-content: center;\n inline-size: var(--vectis-control-height-sm);\n block-size: var(--vectis-control-height-sm);\n border-radius: var(--vectis-radius-pill);\n font-size: var(--vectis-text-body-lg-size);\n }\n\n .v-calendar-allday-label {\n display: flex;\n align-items: start;\n justify-content: end;\n padding-block-start: var(--vectis-space-1);\n padding-inline-end: var(--vectis-space-2);\n border-block-start: 1px solid var(--vectis-color-border);\n border-inline-end: 1px solid var(--vectis-color-border);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n line-height: var(--vectis-text-caption-leading);\n }\n\n .v-calendar-allday {\n /*\n * The band's two axes are DISCRETE; a day, and a row; where the time grid's vertical axis\n * is continuous. That is why the bars are placed with real grid lines here and with\n * fractions there: forcing one mechanism onto both would cost a translation in each\n * direction and buy nothing.\n */\n display: grid;\n grid-column: 2 / -1;\n grid-template-columns: var(--calendar-tracks);\n grid-template-rows: repeat(\n var(--calendar-lanes),\n var(--vectis-control-size-calendar-allday-lane)\n );\n gap: 1px;\n overflow-y: auto;\n max-block-size: var(--vectis-control-size-calendar-allday-max);\n padding-block: var(--vectis-space-1);\n /*\n * The band's tracks have to line up with the canvas's exactly, because dragging a bar reads\n * the pointer against the CANVAS geometry; the two boxes span the same grid columns, so one\n * measurement serves both. A few pixels of padding here would put the last column out by\n * that much, and a bar dropped on the right edge would land a day early.\n */\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n .v-calendar-bar {\n grid-column: calc(var(--calendar-day-index) + 1) / span var(--calendar-bar-span);\n grid-row: calc(var(--calendar-bar-lane) + 1);\n min-inline-size: 0;\n }\n\n .v-calendar-hours {\n display: grid;\n grid-auto-rows: var(--calendar-hour);\n border-inline-end: 1px solid var(--vectis-color-border);\n }\n\n /*\n * The label names the hour its row BEGINS at, and it is drawn on that rule rather than under\n * it: `align-self: start` shrinks the span from its 4rem row down to its one line, and the\n * negative margin then straddles it across the rule above.\n */\n .v-calendar-hour {\n position: relative;\n align-self: start;\n margin-block-start: calc(-1 * var(--calendar-hour-lift));\n padding-inline-end: var(--vectis-space-2);\n color: var(--vectis-color-text-muted);\n font-size: var(--vectis-text-caption-size);\n line-height: var(--vectis-text-caption-leading);\n text-align: end;\n }\n\n /*\n * The rule carried a little way back into the gutter, so the eye joins the label to the line\n * it names. A border and not a background: Windows forced-colors flattens a background to\n * Canvas and the tick would vanish, where a border keeps a colour; the argument VSeparator\n * and the icon registry both make.\n */\n .v-calendar-hour::before {\n content: '';\n position: absolute;\n inset-block-start: var(--calendar-hour-lift);\n inset-inline-end: 0;\n inline-size: var(--vectis-control-size-calendar-tick);\n border-block-start: 1px solid var(--vectis-color-border);\n }\n\n /*\n * Hidden and not removed. The span has to go on occupying its grid row: `display: none` would\n * take the row with it and shift every hour below up by one.\n */\n .v-calendar-hour:first-child {\n visibility: hidden;\n }\n\n .v-calendar-row:first-child .v-calendar-cell {\n border-block-start: none;\n }\n\n .v-calendar-columns {\n /* The containing block every card and the edge strip are placed against. The rows below\n are `display: contents`, so the cells themselves are this grid's items. */\n position: relative;\n display: grid;\n grid-column: 2 / -1;\n /*\n * Repeat the canvas track formula over the same width so grid and overlay columns align\n * without another layout mechanism.\n */\n grid-template-columns: var(--calendar-tracks);\n grid-auto-rows: var(--calendar-hour);\n }\n\n .v-calendar-row {\n /* The row exists for the accessibility tree and must not exist for the layout: a real\n box here would make the cells its children instead of the grid's, and every column\n would collapse. */\n display: contents;\n }\n\n .v-calendar-cell[data-today] {\n background: var(--vectis-color-accent-surface);\n }\n\n /*\n * The height is the event's own length and NOTHING ELSE. No `max()` against a floor token\n * here: the minimum is already applied where it belongs, `timedSegments` stretching every\n * segment to at least `slotDuration` before the overlap packing sees it, so what is drawn and\n * what the packing believes are the same box.\n */\n .v-calendar-event.v-calendar-block {\n position: absolute;\n inset-block-start: calc(var(--calendar-block-start-fraction) * 100%);\n block-size: calc(\n (var(--calendar-block-end-fraction) - var(--calendar-block-start-fraction)) * 100%\n );\n inset-inline-start: calc(\n (var(--calendar-day-index) + var(--calendar-block-column) / var(--calendar-block-columns)) /\n var(--calendar-columns) * 100%\n );\n inline-size: calc(\n var(--calendar-block-span) / var(--calendar-block-columns) / var(--calendar-columns) * 100% -\n var(--vectis-control-size-calendar-gap)\n );\n z-index: 1;\n }\n\n /*\n * It is `aria-hidden`: it is a second rendering of something the cell labels and the day\n * marked as today already say, and announcing \"current time\" from the middle of a grid would\n * interrupt without informing.\n */\n .v-calendar-now {\n position: absolute;\n inset-block-start: calc(var(--calendar-now-fraction) * 100%);\n inset-inline-start: calc(var(--calendar-day-index) / var(--calendar-columns) * 100%);\n inline-size: calc(100% / var(--calendar-columns));\n border-block-start: 1px solid var(--vectis-color-danger);\n /* Above the cards: it is the one thing that must stay readable over a full day. */\n z-index: 2;\n pointer-events: none;\n }\n\n .v-calendar-now::before {\n content: '';\n position: absolute;\n inset-inline-start: 0;\n inline-size: var(--vectis-control-size-calendar-now-dot);\n block-size: var(--vectis-control-size-calendar-now-dot);\n /*\n * Half its own size upwards and backwards, so the dot is centred on the line's start rather\n * than hanging below it.\n */\n margin-block-start: calc(var(--vectis-control-size-calendar-now-dot) / -2);\n margin-inline-start: calc(var(--vectis-control-size-calendar-now-dot) / -2);\n border-radius: var(--vectis-radius-pill);\n background: var(--vectis-color-danger);\n }\n\n /* The line is a border, which forced colors keeps; the dot at its start is a background,\n which it would flatten to Canvas. */\n @media (forced-colors: active) {\n .v-calendar-now::before {\n forced-color-adjust: none;\n background: CanvasText;\n }\n }\n}\n</style>\n"],"mappings":""}