vectis-ui 0.8.0 → 0.9.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.
- package/README.md +68 -334
- package/dist/components/VAccordion/VAccordionItem.d.ts +4 -6
- package/dist/components/VAccordion/VAccordionItem.js.map +1 -1
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js +21 -19
- package/dist/components/VAccordion/VAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAccordion/context.d.ts +6 -1
- package/dist/components/VAccordion/context.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.css +1 -1
- package/dist/components/VAvatar/VAvatar.d.ts +5 -3
- package/dist/components/VAvatar/VAvatar.js.map +1 -1
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js +53 -39
- package/dist/components/VAvatar/VAvatar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.d.ts +12 -3
- package/dist/components/VAvatar/VAvatarGroup.js.map +1 -1
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js +27 -20
- package/dist/components/VAvatar/VAvatarGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VAvatar/context.d.ts +2 -2
- package/dist/components/VAvatar/context.js.map +1 -1
- package/dist/components/VBadge/VBadge.css +1 -1
- package/dist/components/VBadge/VBadge.d.ts +8 -4
- package/dist/components/VBadge/VBadge.js.map +1 -1
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/components/VBadge/VBadge.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.d.ts +5 -4
- package/dist/components/VBreadcrumb/VBreadcrumb.js.map +1 -1
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js +8 -3
- package/dist/components/VBreadcrumb/VBreadcrumb.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButton.css +1 -1
- package/dist/components/VButton/VButton.d.ts +4 -1
- package/dist/components/VButton/VButton.js.map +1 -1
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js +8 -7
- package/dist/components/VButton/VButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.js.map +1 -1
- package/dist/components/VButton/VButtonGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.css +1 -1
- package/dist/components/VCalendar/VCalendar.d.ts +15 -6
- package/dist/components/VCalendar/VCalendar.js.map +1 -1
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js +144 -126
- package/dist/components/VCalendar/VCalendar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.css +1 -1
- package/dist/components/VCalendar/VCalendarEvent.d.ts +1 -1
- package/dist/components/VCalendar/VCalendarEvent.js.map +1 -1
- package/dist/components/VCalendar/VCalendarEvent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.css +1 -1
- package/dist/components/VCalendar/VCalendarMonth.d.ts +1 -1
- package/dist/components/VCalendar/VCalendarMonth.js.map +1 -1
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js +99 -90
- package/dist/components/VCalendar/VCalendarMonth.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.css +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.d.ts +3 -3
- package/dist/components/VCalendar/VCalendarTimeGrid.js.map +1 -1
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js +256 -246
- package/dist/components/VCalendar/VCalendarTimeGrid.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.css +1 -1
- package/dist/components/VCalendar/VCalendarYear.d.ts +8 -1
- package/dist/components/VCalendar/VCalendarYear.js.map +1 -1
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js +39 -32
- package/dist/components/VCalendar/VCalendarYear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCalendar/color.d.ts +1 -1
- package/dist/components/VCalendar/color.js.map +1 -1
- package/dist/components/VCalendar/edgeStep.d.ts +3 -3
- package/dist/components/VCalendar/edgeStep.js +5 -0
- package/dist/components/VCalendar/edgeStep.js.map +1 -1
- package/dist/components/VCalendar/gesture.d.ts +10 -9
- package/dist/components/VCalendar/gesture.js +60 -45
- package/dist/components/VCalendar/gesture.js.map +1 -1
- package/dist/components/VCalendar/keyboard.d.ts +22 -5
- package/dist/components/VCalendar/keyboard.js +17 -16
- package/dist/components/VCalendar/keyboard.js.map +1 -1
- package/dist/components/VCalendar/layout.d.ts +47 -37
- package/dist/components/VCalendar/layout.js +47 -37
- package/dist/components/VCalendar/layout.js.map +1 -1
- package/dist/components/VCalendar/timeGrid.d.ts +4 -4
- package/dist/components/VCalendar/timeGrid.js +6 -6
- package/dist/components/VCalendar/timeGrid.js.map +1 -1
- package/dist/components/VCalendar/types.d.ts +5 -5
- package/dist/components/VCarousel/VCarousel.css +1 -1
- package/dist/components/VCarousel/VCarousel.d.ts +59 -38
- package/dist/components/VCarousel/VCarousel.js.map +1 -1
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js +161 -132
- package/dist/components/VCarousel/VCarousel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.d.ts +1 -1
- package/dist/components/VCarousel/VCarouselItem.js.map +1 -1
- package/dist/components/VCarousel/VCarouselItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCarousel/context.d.ts +1 -1
- package/dist/components/VCarousel/context.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.css +1 -1
- package/dist/components/VCheckbox/VCheckbox.d.ts +2 -2
- package/dist/components/VCheckbox/VCheckbox.js.map +1 -1
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js +10 -8
- package/dist/components/VCheckbox/VCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VChip/VChip.css +1 -1
- package/dist/components/VChip/VChip.d.ts +7 -2
- package/dist/components/VChip/VChip.js.map +1 -1
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js +69 -55
- package/dist/components/VChip/VChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.css +1 -1
- package/dist/components/VCombobox/VCombobox.d.ts +50 -36
- package/dist/components/VCombobox/VCombobox.js.map +1 -1
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js +167 -161
- package/dist/components/VCombobox/VCombobox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.css +1 -1
- package/dist/components/VCombobox/VComboboxGroup.js.map +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VCombobox/VComboboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VCombobox/infiniteScroll.d.ts +12 -3
- package/dist/components/VCombobox/infiniteScroll.js +18 -12
- package/dist/components/VCombobox/infiniteScroll.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.css +1 -1
- package/dist/components/VDataTable/VDataTable.d.ts +55 -24
- package/dist/components/VDataTable/VDataTable.js.map +1 -1
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js +192 -155
- package/dist/components/VDataTable/VDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.css +1 -1
- package/dist/components/VDateInput/VDateInput.d.ts +15 -12
- package/dist/components/VDateInput/VDateInput.js.map +1 -1
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js +180 -160
- package/dist/components/VDateInput/VDateInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDateInput/mask.d.ts +95 -0
- package/dist/components/VDateInput/mask.js +118 -0
- package/dist/components/VDateInput/mask.js.map +1 -0
- package/dist/components/VDatePicker/VDatePicker.css +1 -1
- package/dist/components/VDatePicker/VDatePicker.d.ts +6 -5
- package/dist/components/VDatePicker/VDatePicker.js.map +1 -1
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js +204 -173
- package/dist/components/VDatePicker/VDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VDatePicker/keyboard.d.ts +3 -3
- package/dist/components/VDatePicker/keyboard.js.map +1 -1
- package/dist/components/VDatePicker/names.d.ts +34 -0
- package/dist/components/VDatePicker/names.js +42 -0
- package/dist/components/VDatePicker/names.js.map +1 -0
- package/dist/components/VDialog/VDialog.css +1 -1
- package/dist/components/VDialog/VDialog.d.ts +2 -2
- package/dist/components/VDialog/VDialog.js.map +1 -1
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js +63 -41
- package/dist/components/VDialog/VDialog.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.d.ts +38 -25
- package/dist/components/VFileInput/VFileInput.js.map +1 -1
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js +74 -71
- package/dist/components/VFileInput/VFileInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFileInput/truncate.d.ts +4 -4
- package/dist/components/VFileInput/truncate.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.css +1 -1
- package/dist/components/VFilePicker/VFilePicker.d.ts +54 -37
- package/dist/components/VFilePicker/VFilePicker.js.map +1 -1
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js +152 -135
- package/dist/components/VFilePicker/VFilePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VFilePicker/fileKind.d.ts +2 -2
- package/dist/components/VFilePicker/fileKind.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.css +1 -1
- package/dist/components/VHotkeys/VHotkeys.d.ts +10 -8
- package/dist/components/VHotkeys/VHotkeys.js.map +1 -1
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js +22 -20
- package/dist/components/VHotkeys/VHotkeys.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VHotkeys/platform.d.ts +9 -7
- package/dist/components/VHotkeys/platform.js +41 -30
- package/dist/components/VHotkeys/platform.js.map +1 -1
- package/dist/components/VIcon/VIcon.css +1 -1
- package/dist/components/VIcon/VIcon.d.ts +6 -6
- package/dist/components/VIcon/VIcon.js.map +1 -1
- package/dist/components/VIcon/VIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VIcon/iconProps.d.ts +4 -4
- package/dist/components/VIcon/iconProps.js.map +1 -1
- package/dist/components/VIcon/icons/index.d.ts +1 -1
- package/dist/components/VIcon/icons/index.js.map +1 -1
- package/dist/components/VIcon/icons/names.d.ts +1 -1
- package/dist/components/VIcon/icons/names.js.map +1 -1
- package/dist/components/VIcon/resolver.d.ts +3 -3
- package/dist/components/VIcon/resolver.js +23 -20
- package/dist/components/VIcon/resolver.js.map +1 -1
- package/dist/components/VIcon/types.d.ts +4 -4
- package/dist/components/VIconButton/VIconButton.d.ts +4 -4
- package/dist/components/VIconButton/VIconButton.js.map +1 -1
- package/dist/components/VIconButton/VIconButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInput.d.ts +16 -8
- package/dist/components/VInput/VInput.js.map +1 -1
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js +22 -16
- package/dist/components/VInput/VInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInput/VInputGroup.css +1 -1
- package/dist/components/VInput/VInputGroup.js.map +1 -1
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js +43 -28
- package/dist/components/VInput/VInputGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.css +1 -1
- package/dist/components/VInputOTP/VInputOTP.d.ts +3 -3
- package/dist/components/VInputOTP/VInputOTP.js.map +1 -1
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js +152 -85
- package/dist/components/VInputOTP/VInputOTP.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenu.d.ts +4 -4
- package/dist/components/VMenu/VMenu.js.map +1 -1
- package/dist/components/VMenu/VMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.css +1 -1
- package/dist/components/VMenu/VMenuGroup.js.map +1 -1
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js +18 -11
- package/dist/components/VMenu/VMenuGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.css +1 -1
- package/dist/components/VMenu/VMenuItem.d.ts +6 -6
- package/dist/components/VMenu/VMenuItem.js.map +1 -1
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js +83 -69
- package/dist/components/VMenu/VMenuItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.css +1 -1
- package/dist/components/VMenu/VMenuPanel.d.ts +2 -2
- package/dist/components/VMenu/VMenuPanel.js.map +1 -1
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js +56 -55
- package/dist/components/VMenu/VMenuPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VMenu/context.d.ts +2 -2
- package/dist/components/VMenu/context.js.map +1 -1
- package/dist/components/VPagination/VPagination.css +1 -1
- package/dist/components/VPagination/VPagination.d.ts +22 -9
- package/dist/components/VPagination/VPagination.js.map +1 -1
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js +67 -57
- package/dist/components/VPagination/VPagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VPopover/VPopover.css +1 -1
- package/dist/components/VPopover/VPopover.d.ts +1 -1
- package/dist/components/VPopover/VPopover.js.map +1 -1
- package/dist/components/VPopover/VPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.css +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.d.ts +12 -7
- package/dist/components/VProgressCircular/VProgressCircular.js.map +1 -1
- package/dist/components/VProgressCircular/VProgressCircular.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.css +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.d.ts +15 -10
- package/dist/components/VProgressLinear/VProgressLinear.js.map +1 -1
- package/dist/components/VProgressLinear/VProgressLinear.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VRadio/VRadio.css +1 -1
- package/dist/components/VRadio/VRadio.js.map +1 -1
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/VRadio/VRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.d.ts +1 -1
- package/dist/components/VSeparator/VSeparator.js.map +1 -1
- package/dist/components/VSeparator/VSeparator.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.d.ts +2 -2
- package/dist/components/VSideNavigation/VSideNavigation.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VSideNavigation/VSideNavigation.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/VSideNavigation/VSideNavigationGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.css +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.d.ts +12 -13
- package/dist/components/VSideNavigation/VSideNavigationItem.js.map +1 -1
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js +87 -68
- package/dist/components/VSideNavigation/VSideNavigationItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.css +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.d.ts +10 -7
- package/dist/components/VSkeletonLoader/VSkeletonLoader.js.map +1 -1
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/VSkeletonLoader/VSkeletonLoader.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSlider/VSlider.css +1 -1
- package/dist/components/VSlider/VSlider.d.ts +21 -7
- package/dist/components/VSlider/VSlider.js.map +1 -1
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js +159 -140
- package/dist/components/VSlider/VSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.css +1 -1
- package/dist/components/VSnackbar/VSnackbar.d.ts +4 -4
- package/dist/components/VSnackbar/VSnackbar.js.map +1 -1
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js +85 -59
- package/dist/components/VSnackbar/VSnackbar.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSnackbar/state.d.ts +7 -7
- package/dist/components/VSnackbar/state.js +13 -11
- package/dist/components/VSnackbar/state.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.css +1 -1
- package/dist/components/VSpinner/VSpinner.js.map +1 -1
- package/dist/components/VSpinner/VSpinner.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.css +1 -1
- package/dist/components/VSwitch/VSwitch.d.ts +1 -1
- package/dist/components/VSwitch/VSwitch.js.map +1 -1
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/VSwitch/VSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTab.css +1 -1
- package/dist/components/VTabs/VTab.d.ts +4 -0
- package/dist/components/VTabs/VTab.js.map +1 -1
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js +9 -8
- package/dist/components/VTabs/VTab.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTabs/VTabs.css +1 -1
- package/dist/components/VTabs/VTabs.d.ts +17 -7
- package/dist/components/VTabs/VTabs.js.map +1 -1
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js +84 -77
- package/dist/components/VTabs/VTabs.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.d.ts +8 -8
- package/dist/components/VTextarea/VTextarea.js.map +1 -1
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js +71 -69
- package/dist/components/VTextarea/VTextarea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.css +1 -1
- package/dist/components/VTimeInput/VTimeInput.d.ts +21 -12
- package/dist/components/VTimeInput/VTimeInput.js.map +1 -1
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js +230 -206
- package/dist/components/VTimeInput/VTimeInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimeInput/search.d.ts +17 -2
- package/dist/components/VTimeInput/search.js +19 -9
- package/dist/components/VTimeInput/search.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.css +1 -1
- package/dist/components/VTimePicker/VTimePicker.d.ts +8 -7
- package/dist/components/VTimePicker/VTimePicker.js.map +1 -1
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js +128 -106
- package/dist/components/VTimePicker/VTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTimePicker/limits.d.ts +11 -4
- package/dist/components/VTimePicker/limits.js +3 -2
- package/dist/components/VTimePicker/limits.js.map +1 -1
- package/dist/components/VToast/VToast.css +1 -1
- package/dist/components/VToast/VToast.js.map +1 -1
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js +33 -37
- package/dist/components/VToast/VToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/VToaster.js.map +1 -1
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js +91 -61
- package/dist/components/VToast/VToaster.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToast/state.d.ts +8 -6
- package/dist/components/VToast/state.js +15 -13
- package/dist/components/VToast/state.js.map +1 -1
- package/dist/components/VToggle/VToggle.css +1 -1
- package/dist/components/VToggle/VToggle.d.ts +9 -4
- package/dist/components/VToggle/VToggle.js.map +1 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/components/VToggle/VToggle.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.d.ts +4 -0
- package/dist/components/VToggle/VToggleItem.js.map +1 -1
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js +5 -5
- package/dist/components/VToggle/VToggleItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.css +1 -1
- package/dist/components/VTooltip/VTooltip.d.ts +17 -10
- package/dist/components/VTooltip/VTooltip.js.map +1 -1
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js +46 -26
- package/dist/components/VTooltip/VTooltip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/VTypography/VTypography.css +1 -1
- package/dist/components/VTypography/VTypography.d.ts +6 -4
- package/dist/components/VTypography/VTypography.js.map +1 -1
- package/dist/components/VTypography/VTypography.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/useAriaLabel.js +1 -1
- package/dist/composables/useAriaLabel.js.map +1 -1
- package/dist/composables/useClearable.d.ts +2 -2
- package/dist/composables/useClearable.js.map +1 -1
- package/dist/composables/useDetailsOpen.d.ts +3 -3
- package/dist/composables/useDetailsOpen.js +14 -12
- package/dist/composables/useDetailsOpen.js.map +1 -1
- package/dist/composables/useFieldIds.d.ts +2 -1
- package/dist/composables/useFieldIds.js +6 -5
- package/dist/composables/useFieldIds.js.map +1 -1
- package/dist/composables/useFieldPanel.d.ts +2 -2
- package/dist/composables/useFieldPanel.js +47 -37
- package/dist/composables/useFieldPanel.js.map +1 -1
- package/dist/composables/useFileDrop.js +3 -3
- package/dist/composables/useFileDrop.js.map +1 -1
- package/dist/composables/useFileField.d.ts +1 -1
- package/dist/composables/useFileField.js +62 -41
- package/dist/composables/useFileField.js.map +1 -1
- package/dist/composables/useIconClickHandlers.d.ts +3 -3
- package/dist/composables/useIconClickHandlers.js +14 -13
- package/dist/composables/useIconClickHandlers.js.map +1 -1
- package/dist/composables/useInertLink.js +12 -10
- package/dist/composables/useInertLink.js.map +1 -1
- package/dist/composables/useLiveAnnouncer.d.ts +5 -0
- package/dist/composables/useLiveAnnouncer.js +23 -0
- package/dist/composables/useLiveAnnouncer.js.map +1 -0
- package/dist/composables/useMaskedField.d.ts +5 -5
- package/dist/composables/useMaskedField.js.map +1 -1
- package/dist/composables/usePopover.d.ts +3 -3
- package/dist/composables/usePopover.js +6 -4
- package/dist/composables/usePopover.js.map +1 -1
- package/dist/composables/useProgressValue.js +11 -2
- package/dist/composables/useProgressValue.js.map +1 -1
- package/dist/composables/useRootAttrs.d.ts +1 -1
- package/dist/composables/useRootAttrs.js.map +1 -1
- package/dist/composables/useSlotNodes.d.ts +18 -0
- package/dist/composables/useSlotNodes.js +13 -0
- package/dist/composables/useSlotNodes.js.map +1 -0
- package/dist/composables/useTextLimit.d.ts +2 -1
- package/dist/composables/useTextLimit.js +2 -2
- package/dist/composables/useTextLimit.js.map +1 -1
- package/dist/composables/useTimer.d.ts +5 -0
- package/dist/composables/useTimer.js +22 -11
- package/dist/composables/useTimer.js.map +1 -1
- package/dist/i18n/en.js +1 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/fr.js +14 -14
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/state.d.ts +7 -7
- package/dist/i18n/state.js +37 -28
- package/dist/i18n/state.js.map +1 -1
- package/dist/i18n/types.d.ts +12 -12
- package/dist/icons.d.ts +2 -2
- package/dist/index.d.ts +12 -11
- package/dist/styles.css +1 -1
- package/dist/tokens/css.d.ts +1 -1
- package/dist/tokens/css.js.map +1 -1
- package/dist/tokens/index.d.ts +6 -0
- package/dist/tokens/semantic.d.ts +6 -0
- package/dist/tokens/semantic.js +66 -60
- package/dist/tokens/semantic.js.map +1 -1
- package/dist/tokens/themes/dark.js +3 -3
- package/dist/tokens/themes/dark.js.map +1 -1
- package/dist/tokens.json +130 -66
- package/dist/types.d.ts +1 -1
- package/dist/utils/arrowNav.d.ts +4 -1
- package/dist/utils/arrowNav.js +7 -7
- package/dist/utils/arrowNav.js.map +1 -1
- package/dist/utils/chip.d.ts +1 -1
- package/dist/utils/chip.js.map +1 -1
- package/dist/utils/clock.d.ts +113 -0
- package/dist/utils/clock.js +84 -0
- package/dist/utils/clock.js.map +1 -0
- package/dist/utils/css.d.ts +6 -4
- package/dist/utils/css.js +2 -2
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/date.d.ts +7 -102
- package/dist/utils/date.js +33 -160
- package/dist/utils/date.js.map +1 -1
- package/dist/utils/direction.d.ts +1 -1
- package/dist/utils/direction.js.map +1 -1
- package/dist/utils/env.d.ts +12 -3
- package/dist/utils/env.js +6 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/file.d.ts +3 -3
- package/dist/utils/file.js +1 -1
- package/dist/utils/file.js.map +1 -1
- package/dist/utils/focus.d.ts +1 -1
- package/dist/utils/focus.js.map +1 -1
- package/dist/utils/hostWarns.d.ts +1 -1
- package/dist/utils/hostWarns.js.map +1 -1
- package/dist/utils/ids.d.ts +1 -1
- package/dist/utils/ids.js.map +1 -1
- package/dist/utils/matcher.d.ts +2 -2
- package/dist/utils/matcher.js.map +1 -1
- package/dist/utils/memo.d.ts +1 -1
- package/dist/utils/memo.js.map +1 -1
- package/dist/utils/text.d.ts +3 -3
- package/dist/utils/text.js.map +1 -1
- package/dist/utils/time.d.ts +2 -101
- package/dist/utils/time.js +22 -98
- package/dist/utils/time.js.map +1 -1
- package/dist/utils/vnode.d.ts +5 -3
- package/dist/utils/vnode.js.map +1 -1
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@ function m(e, t) {
|
|
|
8
8
|
return n < r ? -1 : +(n > r);
|
|
9
9
|
}
|
|
10
10
|
function h(t, n) {
|
|
11
|
-
let r = e(Math.round(t * 60), 0, d);
|
|
11
|
+
let r = e(Math.round(t * 60), 0, d - 60);
|
|
12
12
|
return {
|
|
13
13
|
start: r,
|
|
14
14
|
end: e(Math.round(n * 60), r + 60, d)
|
|
@@ -28,7 +28,7 @@ function v(e, t) {
|
|
|
28
28
|
e >= 0 && e <= 6 && !n.has(e) && (n.add(e), r.push(e));
|
|
29
29
|
}
|
|
30
30
|
if (r.length > 0) return r;
|
|
31
|
-
let i = (Math.trunc(t) % 7 + 7) % 7;
|
|
31
|
+
let i = Number.isFinite(t) ? (Math.trunc(t) % 7 + 7) % 7 : 1;
|
|
32
32
|
return Array.from({ length: f }, (e, t) => (i + t) % 7);
|
|
33
33
|
}
|
|
34
34
|
function y(e, t) {
|
|
@@ -80,10 +80,13 @@ function w(e, t, i, a, o) {
|
|
|
80
80
|
}
|
|
81
81
|
function T(e, t, n, r) {
|
|
82
82
|
if (t === "year") {
|
|
83
|
-
let t = l(e)
|
|
84
|
-
return {
|
|
85
|
-
start: c(t, 0, 1),
|
|
86
|
-
end: c(t, 11, 31)
|
|
83
|
+
let t = l(e);
|
|
84
|
+
return t ? {
|
|
85
|
+
start: c(t.getFullYear(), 0, 1),
|
|
86
|
+
end: c(t.getFullYear(), 11, 31)
|
|
87
|
+
} : {
|
|
88
|
+
start: e,
|
|
89
|
+
end: e
|
|
87
90
|
};
|
|
88
91
|
}
|
|
89
92
|
let i = C(e, t, n, r);
|
|
@@ -96,7 +99,7 @@ function T(e, t, n, r) {
|
|
|
96
99
|
};
|
|
97
100
|
}
|
|
98
101
|
function E(e) {
|
|
99
|
-
return
|
|
102
|
+
return H(e) * d + _(e.endTime, d) - _(e.startTime, 0);
|
|
100
103
|
}
|
|
101
104
|
function D(e) {
|
|
102
105
|
return a(e.start, e.end) < 0 && E(e) < 1440;
|
|
@@ -176,44 +179,51 @@ function M(e) {
|
|
|
176
179
|
let n = t.getFullYear();
|
|
177
180
|
return Array.from({ length: 12 }, (e, t) => c(n, t, 1));
|
|
178
181
|
}
|
|
179
|
-
function N(e
|
|
180
|
-
return a(
|
|
182
|
+
function N(e) {
|
|
183
|
+
return e.allDay !== !0 && a(e.start, e.end) < 0 && u(e.endTime) === 0 ? n(e.end, -1) : e.end;
|
|
181
184
|
}
|
|
182
185
|
function P(e, t) {
|
|
186
|
+
return a(t, e.start) >= 0 && a(t, N(e)) <= 0;
|
|
187
|
+
}
|
|
188
|
+
function F(e, t) {
|
|
183
189
|
let r = /* @__PURE__ */ new Map();
|
|
184
190
|
for (let e of t) r.set(e, []);
|
|
185
191
|
if (t.length === 0) return /* @__PURE__ */ new Map();
|
|
186
192
|
let i = t[0], o = t[t.length - 1];
|
|
187
193
|
for (let t of e) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
194
|
+
if (!l(t.start) || !l(t.end)) continue;
|
|
195
|
+
let e = N(t), s = a(t.start, i) > 0 ? t.start : i, c = a(e, o) < 0 ? e : o;
|
|
196
|
+
if (a(s, c) > 0) continue;
|
|
197
|
+
let u = {
|
|
191
198
|
event: t,
|
|
192
199
|
allDay: +!O(t),
|
|
193
200
|
minutes: _(t.startTime, 0),
|
|
194
201
|
id: String(t.id)
|
|
195
|
-
}
|
|
196
|
-
|
|
202
|
+
}, d = u.allDay === 1 && a(t.start, t.end) < 0 ? {
|
|
203
|
+
...u,
|
|
204
|
+
minutes: 0
|
|
205
|
+
} : u;
|
|
206
|
+
for (let e = s; a(e, c) <= 0; e = n(e, 1)) r.get(e)?.push(e === t.start ? u : d);
|
|
197
207
|
}
|
|
198
208
|
let s = /* @__PURE__ */ new Map();
|
|
199
|
-
for (let [e, t] of r) t.sort(
|
|
209
|
+
for (let [e, t] of r) t.sort(I), s.set(e, t.map((e) => e.event));
|
|
200
210
|
return s;
|
|
201
211
|
}
|
|
202
|
-
function
|
|
212
|
+
function I(e, t) {
|
|
203
213
|
return e.allDay - t.allDay || e.minutes - t.minutes || m(e.id, t.id);
|
|
204
214
|
}
|
|
205
|
-
function
|
|
215
|
+
function L(e, t) {
|
|
206
216
|
let n = [];
|
|
207
217
|
for (let r of e) {
|
|
208
218
|
if (!O(r)) continue;
|
|
209
219
|
let e = -1, i = -1;
|
|
210
|
-
for (let n = 0; n < t.length; n++)
|
|
220
|
+
for (let n = 0; n < t.length; n++) P(r, t[n]) && (e === -1 && (e = n), i = n);
|
|
211
221
|
e !== -1 && n.push({
|
|
212
222
|
id: r.id,
|
|
213
223
|
startIndex: e,
|
|
214
224
|
span: i - e + 1,
|
|
215
225
|
continuesBefore: a(r.start, t[e]) < 0,
|
|
216
|
-
continuesAfter: a(r
|
|
226
|
+
continuesAfter: a(N(r), t[i]) > 0,
|
|
217
227
|
lane: 0
|
|
218
228
|
});
|
|
219
229
|
}
|
|
@@ -225,62 +235,62 @@ function I(e, t) {
|
|
|
225
235
|
}
|
|
226
236
|
return n;
|
|
227
237
|
}
|
|
228
|
-
function
|
|
238
|
+
function R(t, n) {
|
|
229
239
|
let r = n.end - n.start;
|
|
230
240
|
return r > 0 ? e((t - n.start) / r, 0, 1) : 0;
|
|
231
241
|
}
|
|
232
|
-
function
|
|
233
|
-
let a =
|
|
242
|
+
function z(t, n, r, i) {
|
|
243
|
+
let a = W(t.x, n, i), o = n.height > 0 ? (t.y - n.top) / n.height : 0;
|
|
234
244
|
return {
|
|
235
245
|
columnIndex: a,
|
|
236
246
|
minutes: e(r.start + o * (r.end - r.start), r.start, r.end)
|
|
237
247
|
};
|
|
238
248
|
}
|
|
239
|
-
function
|
|
249
|
+
function B(t, n) {
|
|
240
250
|
let r = n > 0 ? n : 1;
|
|
241
251
|
return e(Math.round(t / r) * r, 0, d);
|
|
242
252
|
}
|
|
243
|
-
function
|
|
253
|
+
function V(t, n) {
|
|
244
254
|
let r = n > 0 ? n : 1;
|
|
245
255
|
return e(Math.floor(t / r) * r, 0, d);
|
|
246
256
|
}
|
|
247
|
-
function
|
|
257
|
+
function H(e) {
|
|
248
258
|
let t = l(e.start), n = l(e.end);
|
|
249
259
|
return !t || !n ? 0 : Math.max(0, Math.round((n.getTime() - t.getTime()) / p));
|
|
250
260
|
}
|
|
251
|
-
function
|
|
261
|
+
function U(e, t) {
|
|
252
262
|
return l(t) === null ? e : {
|
|
253
263
|
start: t,
|
|
254
|
-
end: n(t,
|
|
264
|
+
end: n(t, H(e)),
|
|
255
265
|
startTime: e.startTime,
|
|
256
266
|
endTime: e.endTime
|
|
257
267
|
};
|
|
258
268
|
}
|
|
259
|
-
function
|
|
269
|
+
function W(t, n, r) {
|
|
260
270
|
let i = Math.max(1, n.columns), a = n.inlineSize / i, o = r ? n.inlineStart - t : t - n.inlineStart;
|
|
261
271
|
return e(a > 0 ? Math.floor(o / a) : 0, 0, i - 1);
|
|
262
272
|
}
|
|
263
|
-
function
|
|
264
|
-
let i = Math.max(1, n.rows), a =
|
|
273
|
+
function G(t, n, r) {
|
|
274
|
+
let i = Math.max(1, n.rows), a = W(t.x, n, r), o = n.height / i;
|
|
265
275
|
return {
|
|
266
276
|
columnIndex: a,
|
|
267
277
|
rowIndex: e(o > 0 ? Math.floor((t.y - n.top) / o) : 0, 0, i - 1)
|
|
268
278
|
};
|
|
269
279
|
}
|
|
270
|
-
function
|
|
280
|
+
function K(e, t, n, r) {
|
|
271
281
|
if (n <= 0 || t.inlineSize <= 0) return null;
|
|
272
282
|
let i = r ? t.inlineStart - e : e - t.inlineStart;
|
|
273
283
|
return i < n ? -1 : i > t.inlineSize - n ? 1 : null;
|
|
274
284
|
}
|
|
275
|
-
function
|
|
285
|
+
function q(t, n, r) {
|
|
276
286
|
if (r <= 0 || n.height <= 0) return 0;
|
|
277
287
|
let i = t - n.top, a = n.top + n.height - t;
|
|
278
288
|
return i < r ? -e((r - i) / r, 0, 1) : a < r ? e((r - a) / r, 0, 1) : 0;
|
|
279
289
|
}
|
|
280
|
-
function
|
|
290
|
+
function J(e, t) {
|
|
281
291
|
return t.width <= 0 || t.height <= 0 || e.x >= t.left && e.x <= t.left + t.width && e.y >= t.top && e.y <= t.top + t.height;
|
|
282
292
|
}
|
|
283
|
-
function
|
|
293
|
+
function Y(e) {
|
|
284
294
|
return {
|
|
285
295
|
start: e.start,
|
|
286
296
|
end: e.end,
|
|
@@ -288,13 +298,13 @@ function J(e) {
|
|
|
288
298
|
endTime: e.endTime
|
|
289
299
|
};
|
|
290
300
|
}
|
|
291
|
-
function
|
|
301
|
+
function X(e, t) {
|
|
292
302
|
return e.start === t.start && e.end === t.end && e.startTime === t.startTime && e.endTime === t.endTime;
|
|
293
303
|
}
|
|
294
|
-
function
|
|
304
|
+
function Z() {
|
|
295
305
|
return s(/* @__PURE__ */ new Date());
|
|
296
306
|
}
|
|
297
307
|
//#endregion
|
|
298
|
-
export { d as MINUTES_PER_DAY, x as advanceVisibleDays,
|
|
308
|
+
export { d as MINUTES_PER_DAY, x as advanceVisibleDays, q as blockEdgeAt, S as columnCount, P as coversDay, H as daySpan, E as durationOf, F as eventsByDay, V as floorToSlot, R as fractionOf, K as inlineEdgeAt, O as isAllDayEvent, y as isVisibleDay, N as lastDayOf, _ as minutesAt, j as monthWeeks, M as monthsOfYear, U as moveEventToDay, v as normalizeWeekdays, L as packAllDay, A as packDayColumn, z as pointToCell, G as pointToMonthCell, J as pointWithin, X as sameTimes, B as snapToSlot, b as snapToVisibleDay, D as spansMidnight, w as stepAnchor, g as timeOf, k as timedSegments, Y as timesOf, Z as todayISO, C as visibleDays, T as visibleRange, h as windowOf };
|
|
299
309
|
|
|
300
310
|
//# sourceMappingURL=layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","names":[],"sources":["../../../src/components/VCalendar/layout.ts"],"sourcesContent":["// @core\n/**\n * Everything VCalendar has to work out rather than render: which days a view shows, where\n * an event's box goes, how a day's width is shared between events happening at once, and\n * what point on the screen corresponds to what moment in the day.\n *\n * It sits in VCalendar's own folder rather than in `utils/` because it has exactly one\n * consumer, which fails the admission rule for shared code, and because VCalendar owns\n * the contract (the same reasoning as `VDatePicker/keyboard.ts` and\n * `VHotkeys/platform.ts`).\n *\n * Being pure is not tidiness here, it is the only way any of this is testable. The test\n * environment lays nothing out — every element it renders measures zero — so a function\n * that took an Element could never be checked. NOTHING below receives one: the single\n * `getBoundingClientRect()` in the whole component builds the plain `GridGeometry` struct\n * that `pointToCell` takes, and every consequence of that measurement is an ordinary unit\n * test over numbers.\n *\n * Times are handled throughout as MINUTES SINCE MIDNIGHT, converted at the edges from the\n * `HH:mm` strings the public API speaks. That is what lets the arithmetic be plain\n * addition instead of a string dance at every step.\n */\nimport {\n addDays,\n addMonths,\n buildMonthGrid,\n compareISO,\n daysInMonth,\n formatISO,\n isoOf,\n parseISO,\n} from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { pad2 } from '../../utils/text'\nimport { minutesOf } from '../../utils/time'\n\nimport type { MonthCell } from '../../utils/date'\nimport type { CalendarEvent, CalendarEventId, CalendarEventTimes, CalendarView } from './types'\n\n/** An hour, in the minutes every time here is counted in: one row of a time grid. */\nexport const MINUTES_PER_HOUR = 60\n\n/** How many minutes a day holds. The upper bound of every time computed here. */\nexport const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR\n\nconst DAYS_PER_WEEK = 7\n\n/**\n * A day in milliseconds, for counting the days between two dates.\n *\n * It is safe here because both ends come from `parseISO`, which builds LOCAL midnights: the\n * difference between two of them is a whole number of days everywhere except across a\n * daylight-saving boundary, and `Math.round` absorbs the hour that gains or loses there.\n */\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * How far the pointer must travel before a press becomes a drag rather than a click.\n *\n * It is what keeps a click a click, on a card as on an empty cell: a press that never moves\n * this far writes nothing on release and is handled as a plain activation. Without it, the\n * hand's natural tremor during a click would register as a one-pixel drag and every click\n * would silently move its event.\n */\nexport const DRAG_THRESHOLD = 3\n\n/**\n * The last tie-break of every ordering below: a stable total order over event ids.\n *\n * TRAP — this must NOT be `localeCompare`. An id is an opaque key, never user-facing text,\n * so there is nothing here to collate; and `localeCompare` with no locale resolves against\n * the RUNTIME's default, which differs between Node and the browser. The three orderings it\n * settles all decide rendered markup — `packDayColumn` assigns columns, `packAllDay`\n * assigns lanes, `eventsByDay` sets the order chips are listed in — so a divergent order is\n * a hydration mismatch, silent in dev and visible only as a card in the wrong place. It is\n * the same hazard `VDataTable` avoids by passing its locale explicitly.\n *\n * Comparing by code point is also allocation-free, which matters because all three run again\n * every time a drag carries its event into another slot.\n */\nfunction compareId(a: CalendarEventId, b: CalendarEventId): number {\n const left = String(a)\n const right = String(b)\n return left < right ? -1 : left > right ? 1 : 0\n}\n\n/** The slice of the day a calendar is showing, as minutes since midnight. */\nexport interface TimeWindow {\n start: number\n end: number\n}\n\n/** Turns the `dayStart`/`dayEnd` props, given in hours, into the window the rest works in. */\nexport function windowOf(startHour: number, endHour: number): TimeWindow {\n const start = clamp(Math.round(startHour * MINUTES_PER_HOUR), 0, MINUTES_PER_DAY)\n const end = clamp(\n Math.round(endHour * MINUTES_PER_HOUR),\n start + MINUTES_PER_HOUR,\n MINUTES_PER_DAY,\n )\n return { start, end }\n}\n\n/**\n * Minutes since midnight back into an `HH:mm` string.\n *\n * TRAP — the value is held one minute short of midnight rather than allowed to reach it.\n * A day's last moment is 1440 in this arithmetic, but `24:00` is not a time any of the\n * design system's helpers accept (`isValidTime` rejects it), and the alternative — writing\n * `00:00` on the following day — would turn an ordinary evening appointment into one that\n * runs past midnight, which every gesture then treats differently. One minute is invisible at\n * every zoom this component offers; an event that changed kind under the pointer is not.\n */\nexport function timeOf(minutes: number): string {\n const held = clamp(Math.round(minutes), 0, MINUTES_PER_DAY - 1)\n return `${pad2(Math.floor(held / MINUTES_PER_HOUR))}:${pad2(held % MINUTES_PER_HOUR)}`\n}\n\n/** An `HH:mm` string into minutes since midnight; `fallback` when it is not one. */\nexport function minutesAt(time: string | undefined, fallback: number): number {\n return minutesOf(time) ?? fallback\n}\n\n/**\n * The weekdays a calendar shows, in reading order.\n *\n * The array does double duty: it says which days are VISIBLE — `[1,2,3,4,5]` hides the\n * weekend everywhere — and its first entry is the day a week starts on. Given nothing, the\n * seven days rotated to `firstDayOfWeek`, which the calendar takes from its prop of that\n * name or from the locale.\n */\nexport function normalizeWeekdays(\n weekdays: readonly number[] | undefined,\n firstDayOfWeek: number,\n): number[] {\n const seen = new Set<number>()\n const kept: number[] = []\n for (const day of weekdays ?? []) {\n const value = Math.trunc(day)\n if (value >= 0 && value <= 6 && !seen.has(value)) {\n seen.add(value)\n kept.push(value)\n }\n }\n if (kept.length > 0) return kept\n const first = ((Math.trunc(firstDayOfWeek) % 7) + 7) % 7\n return Array.from({ length: DAYS_PER_WEEK }, (_, i) => (first + i) % 7)\n}\n\n/** Whether a date falls on one of the visible weekdays. */\nexport function isVisibleDay(iso: string, weekdays: readonly number[]): boolean {\n const date = parseISO(iso)\n return date !== null && weekdays.includes(date.getDay())\n}\n\n/**\n * The first visible day at or after `iso` (or at or before it, going backwards).\n *\n * The loop is bounded at seven because a non-empty weekday list always contains one of any\n * seven consecutive days — the bound is a guard against an empty list, never a real limit.\n */\nexport function snapToVisibleDay(\n iso: string,\n weekdays: readonly number[],\n direction: -1 | 1 = 1,\n): string {\n if (weekdays.length === 0) return iso\n let current = iso\n for (let i = 0; i < DAYS_PER_WEEK; i++) {\n if (isVisibleDay(current, weekdays)) return current\n current = addDays(current, direction)\n }\n return iso\n}\n\n/** Moves `count` VISIBLE days from `iso`, skipping over the hidden ones. */\nexport function advanceVisibleDays(\n iso: string,\n count: number,\n weekdays: readonly number[],\n): string {\n if (count === 0 || weekdays.length === 0) return iso\n const direction = count > 0 ? 1 : -1\n let current = iso\n let remaining = Math.abs(count)\n // Bounded so a malformed weekday list can never spin: at worst one visible day costs\n // seven calendar days, so this is the same walk with its worst case written down.\n let guard = Math.abs(count) * DAYS_PER_WEEK + DAYS_PER_WEEK\n while (remaining > 0 && guard-- > 0) {\n current = addDays(current, direction)\n if (isVisibleDay(current, weekdays)) remaining--\n }\n return current\n}\n\n/** How many day columns a view shows, before the visible-weekday filter applies. */\nexport function columnCount(view: CalendarView, customDays: number): number {\n if (view === 'day') return 1\n if (view === '4days') return 4\n if (view === 'custom') return clamp(Math.trunc(customDays), 1, 31)\n return DAYS_PER_WEEK\n}\n\n/**\n * The days a view shows, in reading order.\n *\n * `day`, `4days` and `custom` run forward from the anchor over the visible days alone, so\n * a four-day view of a Monday-to-Friday calendar shows four WORKING days rather than four\n * calendar ones. `week` shows the week the anchor falls in, starting on `weekdays[0]`.\n * `month` returns every day of the anchor's month that is visible — the grid that renders\n * them cuts them back into weeks itself.\n *\n * `year` returns an empty list on purpose: that view is twelve month names, not a row of\n * days, and giving it a 365-entry list nothing reads would be an invitation to read it.\n */\nexport function visibleDays(\n anchor: string,\n view: CalendarView,\n weekdays: readonly number[],\n customDays: number,\n): string[] {\n if (weekdays.length === 0 || parseISO(anchor) === null) return []\n if (view === 'year') return []\n\n if (view === 'month') {\n const date = parseISO(anchor)!\n const year = date.getFullYear()\n const month0 = date.getMonth()\n const days: string[] = []\n for (let day = 1; day <= daysInMonth(year, month0); day++) {\n const iso = isoOf(year, month0, day)\n if (isVisibleDay(iso, weekdays)) days.push(iso)\n }\n return days\n }\n\n if (view === 'week') {\n const date = parseISO(anchor)!\n const offset = (date.getDay() - weekdays[0]! + DAYS_PER_WEEK) % DAYS_PER_WEEK\n const weekStart = addDays(anchor, -offset)\n const days: string[] = []\n for (let i = 0; i < DAYS_PER_WEEK; i++) {\n const iso = addDays(weekStart, i)\n if (isVisibleDay(iso, weekdays)) days.push(iso)\n }\n return days\n }\n\n const total = columnCount(view, customDays)\n const days: string[] = [snapToVisibleDay(anchor, weekdays)]\n for (let i = 1; i < total; i++) days.push(advanceVisibleDays(days[i - 1]!, 1, weekdays))\n return days\n}\n\n/**\n * Where Previous and Next land.\n *\n * TRAP — the day-shaped views step over VISIBLE days, not calendar ones. Stepping by one\n * calendar day from a Friday in a Monday-to-Friday calendar would land on a Saturday the\n * grid does not draw; the view would then show the same week it already showed, and the\n * button would look broken with nothing in the console to say why.\n */\nexport function stepAnchor(\n anchor: string,\n view: CalendarView,\n direction: -1 | 1,\n weekdays: readonly number[],\n customDays: number,\n): string {\n if (parseISO(anchor) === null) return anchor\n if (view === 'month') return addMonths(anchor, direction)\n if (view === 'year') return addMonths(anchor, direction * 12)\n if (view === 'week') return snapToVisibleDay(addDays(anchor, direction * DAYS_PER_WEEK), weekdays)\n return advanceVisibleDays(anchor, direction * columnCount(view, customDays), weekdays)\n}\n\n/** The first and last day a view covers, which is what names it in the toolbar. */\nexport function visibleRange(\n anchor: string,\n view: CalendarView,\n weekdays: readonly number[],\n customDays: number,\n): { start: string; end: string } {\n if (view === 'year') {\n const date = parseISO(anchor)\n const year = date?.getFullYear() ?? new Date().getFullYear()\n return { start: isoOf(year, 0, 1), end: isoOf(year, 11, 31) }\n }\n const days = visibleDays(anchor, view, weekdays, customDays)\n if (days.length === 0) return { start: anchor, end: anchor }\n return { start: days[0]!, end: days[days.length - 1]! }\n}\n\n/**\n * How long an event lasts, in minutes, counting the days between its dates.\n *\n * A time that is not one reads as the far end of its day — midnight for a start, the next\n * midnight for an end — so a malformed event errs towards being long, and towards the band.\n */\nexport function durationOf(times: CalendarEventTimes): number {\n return (\n daySpan(times) * MINUTES_PER_DAY +\n minutesAt(times.endTime, MINUTES_PER_DAY) -\n minutesAt(times.startTime, 0)\n )\n}\n\n/** Whether an event runs from one day into the next without lasting a whole day. */\nexport function spansMidnight(times: CalendarEventTimes): boolean {\n return compareISO(times.start, times.end) < 0 && durationOf(times) < MINUTES_PER_DAY\n}\n\n/**\n * Whether an event belongs in the band above the grid rather than in a day's column.\n *\n * A day's column holds up to twenty-four hours, so an event running past midnight for less\n * than that is drawn as two cards — the evening in one column, the early morning in the next —\n * and only one lasting a whole day or more becomes a bar. Exactly twenty-four hours is a bar:\n * two cards covering the same hours of two columns would read as two events.\n *\n * An event whose end comes before its start has no column to go in either, so it goes to the\n * band, where `packAllDay` finds no day it covers and draws nothing.\n */\nexport function isAllDayEvent(event: CalendarEvent): boolean {\n if (event.allDay === true) return true\n const order = compareISO(event.start, event.end)\n if (order === 0) return false\n return order > 0 || !spansMidnight(event)\n}\n\n/**\n * Which piece of its event a box is: the whole of it, or one side of midnight for an event\n * that runs from one day into the next.\n */\nexport type SegmentPart = 'whole' | 'head' | 'tail'\n\n/** One event's box in one day column, in minutes since midnight. */\nexport interface EventSegment {\n id: CalendarEventId\n /** Which of the visible columns it belongs to. */\n dayIndex: number\n start: number\n end: number\n /**\n * Whether the card carries on past its top or bottom edge: the window cut it off, or\n * midnight did. Both are drawn the same way.\n */\n clippedStart: boolean\n clippedEnd: boolean\n /**\n * `head` is the evening of an event running past midnight and `tail` its morning. Only a\n * `whole` or a `tail` holds the event's real end, which is what its resize strip drags.\n */\n part: SegmentPart\n}\n\n/**\n * Cuts the timed events into the boxes the day columns draw. All-day events produce none —\n * they are the band's business — and so does an event whose day is not on show or whose\n * hours fall entirely outside the window.\n *\n * `minDuration` is applied HERE, by stretching the end, so that `packDayColumn` below sees\n * ordinary intervals and can stay a plain interval algorithm. Were the minimum applied\n * during packing instead, two events fifteen minutes apart would be laid out as though\n * they did not overlap and then drawn as though they did.\n */\nexport function timedSegments(\n events: readonly CalendarEvent[],\n days: readonly string[],\n timeWindow: TimeWindow,\n minDuration: number,\n): EventSegment[] {\n const index = new Map(days.map((iso, i) => [iso, i]))\n const segments: EventSegment[] = []\n\n const push = (\n id: CalendarEventId,\n dayIndex: number | undefined,\n rawStart: number,\n rawEnd: number,\n part: SegmentPart,\n ) => {\n if (dayIndex === undefined) return\n if (rawEnd <= timeWindow.start || rawStart >= timeWindow.end) return\n const start = clamp(rawStart, timeWindow.start, timeWindow.end - 1)\n const end = clamp(Math.max(rawEnd, start + minDuration), start + 1, timeWindow.end)\n segments.push({\n id,\n dayIndex,\n start,\n end,\n clippedStart: part === 'tail' || rawStart < timeWindow.start,\n clippedEnd: part === 'head' || rawEnd > timeWindow.end,\n part,\n })\n }\n\n for (const event of events) {\n if (isAllDayEvent(event)) continue\n const rawStart = minutesAt(event.startTime, timeWindow.start)\n\n if (compareISO(event.start, event.end) === 0) {\n const rawEnd = Math.max(minutesAt(event.endTime, rawStart + minDuration), rawStart + 1)\n push(event.id, index.get(event.start), rawStart, rawEnd, 'whole')\n continue\n }\n\n /*\n * Running past midnight: cut at midnight into two boxes, each in its own day's column. An\n * event ending at midnight exactly has nothing on its second day, so it is drawn whole — a\n * tail of no length would otherwise be stretched to a slot and show as a stub at 00:00.\n */\n const rawEnd = minutesAt(event.endTime, 0)\n if (rawEnd === 0) {\n push(event.id, index.get(event.start), rawStart, MINUTES_PER_DAY, 'whole')\n continue\n }\n push(event.id, index.get(event.start), rawStart, MINUTES_PER_DAY, 'head')\n push(event.id, index.get(event.end), 0, rawEnd, 'tail')\n }\n\n return segments\n}\n\n/** A segment once it knows how much of its column's width it gets. */\nexport interface PlacedSegment extends EventSegment {\n /** Which column of its cluster it sits in, counted from zero. */\n column: number\n /** How many columns it takes up — more than one when the room beside it is free. */\n span: number\n /** How many columns its cluster has. It is what both of the above are read against. */\n columns: number\n}\n\n/**\n * Shares one day's width out between the events happening at the same time.\n *\n * Three passes. First the segments are cut into CLUSTERS — runs joined by overlap, where an\n * event starting after everything before it has finished begins a new one. That is what\n * stops a crowded morning from squeezing a lone afternoon meeting into a sliver. Then,\n * inside a cluster, each event takes the first column whose last occupant has already\n * ended, which is the ordinary greedy colouring of an interval graph and uses as few\n * columns as the cluster can be drawn in. Finally each event grows sideways for as long as\n * nothing in the next column overlaps it, so a single event never leaves a gap it could\n * have filled.\n *\n * The order is fixed — by start, then longest first, then by id — so the same list always\n * produces the same picture. Without the id to break the last tie, two events at the same\n * time for the same length would swap places whenever the array was re-sorted, and the\n * whole day would appear to shuffle for no reason.\n *\n * Overlap is strictly `a.start < b.end && b.start < a.end`, so events that merely touch —\n * one ending exactly as the next begins — share a column instead of splitting the width.\n */\nexport function packDayColumn(segments: readonly EventSegment[]): PlacedSegment[] {\n const sorted = [...segments].sort(\n (a, b) => a.start - b.start || b.end - b.start - (a.end - a.start) || compareId(a.id, b.id),\n )\n\n const placed: PlacedSegment[] = []\n let cluster: PlacedSegment[] = []\n let columnEnds: number[] = []\n let clusterEnd = -Infinity\n\n const flush = () => {\n const columns = columnEnds.length\n /*\n * The cluster filed by column first, so asking \"is the room beside me free?\" reads that one\n * column's occupants rather than the whole cluster: a crowded morning of k events across c\n * columns otherwise costs k squared per column crossed.\n */\n const byColumn: PlacedSegment[][] = Array.from({ length: columns }, () => [])\n for (const item of cluster) byColumn[item.column]!.push(item)\n for (const item of cluster) {\n let span = 1\n while (\n item.column + span < columns &&\n !byColumn[item.column + span]!.some(\n (other) => other.start < item.end && item.start < other.end,\n )\n ) {\n span++\n }\n item.span = span\n item.columns = columns\n }\n placed.push(...cluster)\n cluster = []\n columnEnds = []\n clusterEnd = -Infinity\n }\n\n for (const segment of sorted) {\n if (segment.start >= clusterEnd && cluster.length > 0) flush()\n\n let column = columnEnds.findIndex((end) => end <= segment.start)\n if (column === -1) {\n column = columnEnds.length\n columnEnds.push(segment.end)\n } else {\n columnEnds[column] = segment.end\n }\n\n cluster.push({ ...segment, column, span: 1, columns: 1 })\n clusterEnd = Math.max(clusterEnd, segment.end)\n }\n if (cluster.length > 0) flush()\n\n return placed\n}\n\n/**\n * One square of the month view: a day, and whether it belongs to the month on show.\n *\n * It is `utils/date`'s own type, re-exported rather than restated: `monthWeeks` below builds\n * its cells by calling `buildMonthGrid` from that module, so a second declaration here could\n * only ever drift away from the shape it is actually handed. The re-export keeps\n * `import { type MonthCell } from './layout'` working for the two views that read it.\n */\nexport type { MonthCell }\n\n/**\n * The month view, cut into weeks.\n *\n * It keeps the fixed six rows `buildMonthGrid` produces — padded with the neighbouring\n * months — so the grid is the same height whichever month is on show and the page does not\n * jump as one steps through the year. Each row is then filtered down to the visible\n * weekdays, which is what makes a Monday-to-Friday month five columns wide rather than\n * seven with two blanks.\n */\nexport function monthWeeks(anchor: string, weekdays: readonly number[]): MonthCell[][] {\n const date = parseISO(anchor)\n if (!date || weekdays.length === 0) return []\n\n const cells = buildMonthGrid(date.getFullYear(), date.getMonth(), weekdays[0]!)\n const weeks: MonthCell[][] = []\n for (let i = 0; i < cells.length; i += DAYS_PER_WEEK) {\n const week = cells\n .slice(i, i + DAYS_PER_WEEK)\n .filter((cell) => isVisibleDay(cell.iso, weekdays))\n if (week.length > 0) weeks.push(week)\n }\n return weeks\n}\n\n/** The first day of each month of the anchor's year, which is what the year view lays out. */\nexport function monthsOfYear(anchor: string): string[] {\n const date = parseISO(anchor)\n if (!date) return []\n const year = date.getFullYear()\n return Array.from({ length: 12 }, (_, month0) => isoOf(year, month0, 1))\n}\n\n/** Whether an event covers a given day at all, all-day or timed. */\nexport function coversDay(event: CalendarEvent, iso: string): boolean {\n return compareISO(iso, event.start) >= 0 && compareISO(iso, event.end) <= 0\n}\n\n/**\n * Every visible day's events, in the order a summary lists them: the all-day ones first, since\n * they frame the day rather than sit inside it, then the rest by when they start, the last tie\n * broken by id so the list cannot reshuffle for no reason.\n *\n * WHY ONE PASS RATHER THAN A FILTER PER DAY. The month view needs all 42 squares filled, and\n * filtering and sorting the list once per square walks the whole event list 42 times and sorts\n * it 42 times — `cells × events`, with a sort each. Measured on the bench: 7.8 ms per render at\n * 2000 events, and the month view rebuilds it every time a drag carries a chip onto another\n * day, so that is roughly half a frame spent rebuilding lists the gesture left alone.\n *\n * Here each event is placed once, into the days it actually covers, and each day is sorted\n * once — `events × span + cells × k log k`. The walk is bounded to the grid on both ends, so\n * an event running from last year costs its visible part and nothing more.\n */\nexport function eventsByDay<T extends CalendarEvent>(\n events: readonly T[],\n days: readonly string[],\n): Map<string, T[]> {\n const buckets = new Map<string, Ranked<T>[]>()\n for (const iso of days) buckets.set(iso, [])\n if (days.length === 0) return new Map()\n\n // The grid's own bounds. `days` is in order, so its ends bound every walk below — which\n // is what keeps a long-running event from being walked outside the month on show.\n const first = days[0]!\n const last = days[days.length - 1]!\n\n for (const event of events) {\n const from = compareISO(event.start, first) > 0 ? event.start : first\n const to = compareISO(event.end, last) < 0 ? event.end : last\n if (compareISO(from, to) > 0) continue\n\n /*\n * Decorate-sort-undecorate, for the classic reason: the sort key is derived ONCE per\n * event rather than once per comparison, which is where the time in this function goes.\n * `minutesAt` re-parses an `HH:mm` string on every ask, and a 42-square month holding\n * 2000 events sorts some 17 000 pairs. Measured at 9.97 ms\n * against 4.19 ms with the key hoisted.\n */\n const ranked: Ranked<T> = {\n event,\n allDay: isAllDayEvent(event) ? 0 : 1,\n minutes: minutesAt(event.startTime, 0),\n id: String(event.id),\n }\n\n for (let iso = from; compareISO(iso, to) <= 0; iso = addDays(iso, 1)) {\n // A day the weekday filter hides has no bucket, so it is simply skipped: that is what\n // makes a Monday-to-Friday month cost nothing for the weekends it does not show.\n buckets.get(iso)?.push(ranked)\n }\n }\n\n const byDay = new Map<string, T[]>()\n for (const [iso, list] of buckets) {\n list.sort(compareRanked)\n byDay.set(\n iso,\n list.map((item) => item.event),\n )\n }\n return byDay\n}\n\n/** An event with its sort key already worked out — see `eventsByDay`. */\ninterface Ranked<T> {\n event: T\n allDay: number\n minutes: number\n id: string\n}\n\n/** The order `eventsByDay` lists a day in, reading keys that are already computed. */\nfunction compareRanked<T>(a: Ranked<T>, b: Ranked<T>): number {\n return a.allDay - b.allDay || a.minutes - b.minutes || compareId(a.id, b.id)\n}\n\n/** One bar in the all-day band: the columns it covers, and the row it sits on. */\nexport interface AllDaySpan {\n id: CalendarEventId\n /** The first visible column it covers. */\n startIndex: number\n /** How many visible columns it covers, at least one. */\n span: number\n /** Whether it reaches beyond the days on show, on either side. */\n continuesBefore: boolean\n continuesAfter: boolean\n /** Which row of the band it sits on. */\n lane: number\n}\n\n/**\n * Stacks the all-day bars into as few rows as they fit in.\n *\n * A bar covers the run from the FIRST to the LAST visible day it touches, which is what\n * makes a hidden weekday in the middle of a range simply not there rather than a gap to\n * account for: an event running Friday to Monday in a Monday-to-Friday calendar shows as\n * one day on the Friday and one on the following Monday, because those are the two days it\n * actually covers on screen.\n *\n * The rows are the same greedy pass the day columns use — each bar takes the first row\n * whose last bar has already ended — and the ordering is fixed, longest first at equal\n * starts, so the band cannot reshuffle when the array is re-sorted.\n */\nexport function packAllDay(\n events: readonly CalendarEvent[],\n days: readonly string[],\n): AllDaySpan[] {\n const spans: AllDaySpan[] = []\n\n for (const event of events) {\n if (!isAllDayEvent(event)) continue\n\n let first = -1\n let last = -1\n for (let i = 0; i < days.length; i++) {\n if (coversDay(event, days[i]!)) {\n if (first === -1) first = i\n last = i\n }\n }\n if (first === -1) continue\n\n spans.push({\n id: event.id,\n startIndex: first,\n span: last - first + 1,\n continuesBefore: compareISO(event.start, days[first]!) < 0,\n continuesAfter: compareISO(event.end, days[last]!) > 0,\n lane: 0,\n })\n }\n\n spans.sort((a, b) => a.startIndex - b.startIndex || b.span - a.span || compareId(a.id, b.id))\n\n const laneEnds: number[] = []\n for (const span of spans) {\n let lane = laneEnds.findIndex((end) => end < span.startIndex)\n if (lane === -1) {\n lane = laneEnds.length\n laneEnds.push(0)\n }\n laneEnds[lane] = span.startIndex + span.span - 1\n span.lane = lane\n }\n\n return spans\n}\n\n/** Where a moment sits in the visible window, as a unitless fraction the stylesheet scales. */\nexport function fractionOf(minutes: number, timeWindow: TimeWindow): number {\n const span = timeWindow.end - timeWindow.start\n return span > 0 ? clamp((minutes - timeWindow.start) / span, 0, 1) : 0\n}\n\n/** The measurements of the grid a pointer is being read against, in viewport coordinates. */\nexport interface GridGeometry {\n top: number\n height: number\n /** The edge the first column starts at: the left one normally, the right one in RTL. */\n inlineStart: number\n inlineSize: number\n columns: number\n}\n\n/**\n * Turns a point on the screen into a point on the calendar: which day column it fell in,\n * and what moment that height stands for.\n *\n * Both answers are brought back inside the grid, so a pointer dragged past an edge keeps\n * producing the nearest sensible value rather than none — which is what makes a drag that\n * wanders off the component behave as the user expects instead of stopping dead.\n */\nexport function pointToCell(\n point: { x: number; y: number },\n geometry: GridGeometry,\n timeWindow: TimeWindow,\n rtl: boolean,\n): { columnIndex: number; minutes: number } {\n const columnIndex = columnAt(point.x, geometry, rtl)\n\n const fraction = geometry.height > 0 ? (point.y - geometry.top) / geometry.height : 0\n const minutes = clamp(\n timeWindow.start + fraction * (timeWindow.end - timeWindow.start),\n timeWindow.start,\n timeWindow.end,\n )\n\n return { columnIndex, minutes }\n}\n\n/** Rounds a moment to the nearest step the calendar allows. */\nexport function snapToSlot(minutes: number, step: number): number {\n const size = step > 0 ? step : 1\n return clamp(Math.round(minutes / size) * size, 0, MINUTES_PER_DAY)\n}\n\n/** Rounds a moment DOWN to the step containing it — where a drawn slot begins. */\nexport function floorToSlot(minutes: number, step: number): number {\n const size = step > 0 ? step : 1\n return clamp(Math.floor(minutes / size) * size, 0, MINUTES_PER_DAY)\n}\n\n/**\n * How many whole days an event covers beyond its first — zero for one that starts and ends\n * on the same day, two for a trip from Monday to Wednesday.\n */\nexport function daySpan(times: CalendarEventTimes): number {\n const start = parseISO(times.start)\n const end = parseISO(times.end)\n if (!start || !end) return 0\n return Math.max(0, Math.round((end.getTime() - start.getTime()) / MS_PER_DAY))\n}\n\n/**\n * Moves an event to another day, KEEPING its length in days as well as its times.\n *\n * This is the counterpart of `timeGrid.ts`'s `moveEvent`, and the two must not be confused: that one is for\n * a timed move INSIDE a single day and deliberately collapses `end` onto `start`, so putting\n * a three-day trip through it would silently squash it into one Tuesday. This one is for the\n * whole-day gestures — an all-day bar dragged along the band, a chip dragged across a month —\n * where the times are carried along untouched and only the dates move.\n */\nexport function moveEventToDay(origin: CalendarEventTimes, target: string): CalendarEventTimes {\n if (parseISO(target) === null) return origin\n return {\n start: target,\n end: addDays(target, daySpan(origin)),\n startTime: origin.startTime,\n endTime: origin.endTime,\n }\n}\n\n/**\n * Which column a horizontal position falls in, counted in reading order and held inside the\n * grid. The inline half both grids share: the time grid's block axis is a moment, the month's\n * a row.\n */\nfunction columnAt(x: number, geometry: GridGeometry, rtl: boolean): number {\n const columns = Math.max(1, geometry.columns)\n const columnSize = geometry.inlineSize / columns\n const offset = rtl ? geometry.inlineStart - x : x - geometry.inlineStart\n return clamp(columnSize > 0 ? Math.floor(offset / columnSize) : 0, 0, columns - 1)\n}\n\n/** The measurements of a month grid — the same struct, with rows where a time window was. */\nexport interface MonthGeometry extends GridGeometry {\n rows: number\n}\n\n/**\n * Turns a point on the screen into a square of the month.\n *\n * The block axis is a ROW here rather than a moment, which is the whole difference from\n * `pointToCell` — the inline half is the same arithmetic, RTL flip included. It is sound\n * because every week of a month is drawn the same height: the rows share what space there is\n * and a day too full for its box clips rather than growing.\n */\nexport function pointToMonthCell(\n point: { x: number; y: number },\n geometry: MonthGeometry,\n rtl: boolean,\n): { columnIndex: number; rowIndex: number } {\n const rows = Math.max(1, geometry.rows)\n const columnIndex = columnAt(point.x, geometry, rtl)\n\n const rowSize = geometry.height / rows\n const rowIndex = clamp(\n rowSize > 0 ? Math.floor((point.y - geometry.top) / rowSize) : 0,\n 0,\n rows - 1,\n )\n\n return { columnIndex, rowIndex }\n}\n\n/**\n * Which inline edge a point is resting against, as a direction to page in: -1 for the start\n * edge, 1 for the end one, nothing in between.\n *\n * It answers in READING order, so the start edge is the left one normally and the right one\n * in a right-to-left page — which is what makes \"hold at the edge to go back\" mean the same\n * thing in both.\n */\nexport function inlineEdgeAt(\n x: number,\n geometry: { inlineStart: number; inlineSize: number },\n band: number,\n rtl: boolean,\n): -1 | 1 | null {\n if (band <= 0 || geometry.inlineSize <= 0) return null\n const offset = rtl ? geometry.inlineStart - x : x - geometry.inlineStart\n // Past the far side of the box counts as being against that edge: a pointer dragged clean\n // off the calendar is asking to keep going, not to stop.\n if (offset < band) return -1\n if (offset > geometry.inlineSize - band) return 1\n return null\n}\n\n/**\n * How hard a point near the top or bottom of a box is asking it to scroll: -1 at the very\n * top, 1 at the very bottom, 0 anywhere in the middle.\n *\n * A FRACTION and not a direction, so the speed can rise as the pointer nears the edge. At a\n * constant speed there is no setting that works: fast enough to be useful at the boundary is\n * uncontrollable a few pixels in, and gentle enough to be controllable never gets anywhere.\n */\nexport function blockEdgeAt(\n y: number,\n geometry: { top: number; height: number },\n band: number,\n): number {\n if (band <= 0 || geometry.height <= 0) return 0\n const fromTop = y - geometry.top\n const fromBottom = geometry.top + geometry.height - y\n if (fromTop < band) return -clamp((band - fromTop) / band, 0, 1)\n if (fromBottom < band) return clamp((band - fromBottom) / band, 0, 1)\n return 0\n}\n\n/**\n * Whether a point falls inside a box — how a drag decides, on release, whether it is over the\n * calendar at all or over the toolbar, or over the page beside it.\n *\n * It takes the box as the browser MEASURED it, and never the `GridGeometry` the rest of this file\n * works in. Containment is a physical question with no reading direction, where `inlineStart` is\n * the box's RIGHT edge in a right-to-left page: bracketing with it would walk off the far side of\n * the screen and make every right-to-left drop read as outside. `columns` says nothing about\n * whether a point is in a box either, so asking for a `GridGeometry` would ask every caller to\n * build a thing this needs no part of.\n *\n * The edges COUNT as inside: a rect is a pair of floats on a screen that may be scaled by a\n * fraction, and an exclusive boundary would refuse a drop that looked, and was, on target.\n *\n * TRAP — a box that measures nothing contains EVERYTHING. That is the same answer `inlineEdgeAt`\n * and `blockEdgeAt` give a box of no size, and for the same reason: with no measurement there is\n * no outside to be on. Take a zero-size box literally instead and the calendar refuses every drop\n * wherever nothing has been laid out — in a hidden tab, before the first frame, and in jsdom,\n * which measures nothing at all and where all but a handful of the drag tests would go red at\n * once.\n */\nexport function pointWithin(\n point: { x: number; y: number },\n box: { left: number; top: number; width: number; height: number },\n): boolean {\n if (box.width <= 0 || box.height <= 0) return true\n return (\n point.x >= box.left &&\n point.x <= box.left + box.width &&\n point.y >= box.top &&\n point.y <= box.top + box.height\n )\n}\n\n/** The times of an event, as the shape the move and resize events carry. */\nexport function timesOf(event: CalendarEvent): CalendarEventTimes {\n return {\n start: event.start,\n end: event.end,\n startTime: event.startTime,\n endTime: event.endTime,\n }\n}\n\n/**\n * Whether two placements are the same, field for field.\n *\n * It is what a drag asks before it writes its preview. A slot is a quarter of an hour, some\n * sixteen pixels at the default hour height, so most pointer moves land on the times already\n * shown — and the gesture state is deeply reactive, so an EQUAL but new object would still\n * re-run the whole layout and re-render every cell of the grid for a picture that did not\n * change.\n */\nexport function sameTimes(a: CalendarEventTimes, b: CalendarEventTimes): boolean {\n return (\n a.start === b.start && a.end === b.end && a.startTime === b.startTime && a.endTime === b.endTime\n )\n}\n\n/** Today, as the calendar reads it. Kept here so the component has one place to call. */\nexport function todayISO(): string {\n return formatISO(new Date())\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"layout.js","names":[],"sources":["../../../src/components/VCalendar/layout.ts"],"sourcesContent":["// @core\n/**\n * Everything VCalendar has to work out rather than render: which days a view shows, where\n * an event's box goes, how a day's width is shared between events happening at once, and\n * what point on the screen corresponds to what moment in the day.\n *\n * It sits in VCalendar's own folder rather than in `utils/` because it has exactly one\n * consumer, which fails the admission rule for shared code, and because VCalendar owns\n * the contract (the same reasoning as `VDatePicker/keyboard.ts` and\n * `VHotkeys/platform.ts`).\n *\n * Being pure is not tidiness here, it is the only way any of this is testable. The test\n * environment lays nothing out — every element it renders measures zero — so a function\n * that took an Element could never be checked. NOTHING below receives one: the single\n * `getBoundingClientRect()` in the whole component builds the plain `GridGeometry` struct\n * that `pointToCell` takes, and every consequence of that measurement is an ordinary unit\n * test over numbers.\n *\n * Times are handled throughout as MINUTES SINCE MIDNIGHT, converted at the edges from the\n * `HH:mm` strings the public API speaks. That is what lets the arithmetic be plain\n * addition instead of a string dance at every step.\n */\nimport {\n addDays,\n addMonths,\n buildMonthGrid,\n compareISO,\n daysInMonth,\n formatISO,\n isoOf,\n parseISO,\n} from '../../utils/date'\nimport { clamp } from '../../utils/number'\nimport { pad2 } from '../../utils/text'\nimport { minutesOf } from '../../utils/time'\n\nimport type { MonthCell } from '../../utils/date'\nimport type { CalendarEvent, CalendarEventId, CalendarEventTimes, CalendarView } from './types'\n\n/** An hour, in the minutes every time here is counted in: one row of a time grid. */\nexport const MINUTES_PER_HOUR = 60\n\n/** How many minutes a day holds. The upper bound of every time computed here. */\nexport const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR\n\nconst DAYS_PER_WEEK = 7\n\n/**\n * A day in milliseconds, for counting the days between two dates.\n *\n * It is safe here because both ends come from `parseISO`, which builds LOCAL midnights: the\n * difference between two of them is a whole number of days everywhere except across a\n * daylight-saving boundary, and `Math.round` absorbs the hour that gains or loses there.\n */\nconst MS_PER_DAY = 24 * 60 * 60 * 1000\n\n/**\n * How far the pointer must travel before a press becomes a drag rather than a click.\n *\n * It is what keeps a click a click, on a card as on an empty cell: a press that never moves\n * this far writes nothing on release and is handled as a plain activation. Without it, the\n * hand's natural tremor during a click would register as a one-pixel drag and every click\n * would silently move its event.\n */\nexport const DRAG_THRESHOLD = 3\n\n/**\n * The last tie-break of every ordering below: a stable total order over event ids.\n *\n * TRAP — this must NOT be `localeCompare`. An id is an opaque key, never user-facing text,\n * so there is nothing here to collate; and `localeCompare` with no locale resolves against\n * the RUNTIME's default, which differs between Node and the browser. The three orderings it\n * settles all decide rendered markup — `packDayColumn` assigns columns, `packAllDay`\n * assigns lanes, `eventsByDay` sets the order chips are listed in — so a divergent order is\n * a hydration mismatch, silent in dev and visible only as a card in the wrong place. It is\n * the same hazard `VDataTable` avoids by passing its locale explicitly.\n *\n * Comparing by code point is also allocation-free, which matters because all three run again\n * every time a drag carries its event into another slot.\n */\nfunction compareId(a: CalendarEventId, b: CalendarEventId): number {\n const left = String(a)\n const right = String(b)\n return left < right ? -1 : left > right ? 1 : 0\n}\n\n/** The slice of the day a calendar is showing, as minutes since midnight. */\nexport interface TimeWindow {\n start: number\n end: number\n}\n\n/** Turns the `dayStart`/`dayEnd` props, given in hours, into the window the rest works in. */\nexport function windowOf(startHour: number, endHour: number): TimeWindow {\n // The start stops an hour short of midnight, so the hour kept on screen fits in the day.\n const start = clamp(\n Math.round(startHour * MINUTES_PER_HOUR),\n 0,\n MINUTES_PER_DAY - MINUTES_PER_HOUR,\n )\n const end = clamp(\n Math.round(endHour * MINUTES_PER_HOUR),\n start + MINUTES_PER_HOUR,\n MINUTES_PER_DAY,\n )\n return { start, end }\n}\n\n/**\n * Minutes since midnight back into an `HH:mm` string.\n *\n * TRAP: the value is held one minute short of midnight rather than allowed to reach it.\n * A day's last moment is 1440 in this arithmetic, but `24:00` is not a time any of the\n * design system's helpers accept (`isValidTime` rejects it). Writing\n * `00:00` on the following day would turn an ordinary evening appointment into one that\n * runs past midnight, which every gesture then treats differently. One minute is invisible at\n * every zoom this component offers; an event that changed kind under the pointer is not.\n */\nexport function timeOf(minutes: number): string {\n const held = clamp(Math.round(minutes), 0, MINUTES_PER_DAY - 1)\n return `${pad2(Math.floor(held / MINUTES_PER_HOUR))}:${pad2(held % MINUTES_PER_HOUR)}`\n}\n\n/** An `HH:mm` string into minutes since midnight; `fallback` when it is not one. */\nexport function minutesAt(time: string | undefined, fallback: number): number {\n return minutesOf(time) ?? fallback\n}\n\n/**\n * The weekdays a calendar shows, in reading order.\n *\n * The array does double duty: it says which days are VISIBLE: `[1,2,3,4,5]` hides the\n * weekend everywhere, and its first entry is the day a week starts on. Given nothing, the\n * seven days rotated to `firstDayOfWeek`, which the calendar takes from its prop of that\n * name or from the locale.\n */\nexport function normalizeWeekdays(\n weekdays: readonly number[] | undefined,\n firstDayOfWeek: number,\n): number[] {\n const seen = new Set<number>()\n const kept: number[] = []\n for (const day of weekdays ?? []) {\n const value = Math.trunc(day)\n if (value >= 0 && value <= 6 && !seen.has(value)) {\n seen.add(value)\n kept.push(value)\n }\n }\n if (kept.length > 0) return kept\n // Monday when the day given is not a number, the fallback `firstDayOfWeekFor` uses too.\n const first = Number.isFinite(firstDayOfWeek) ? ((Math.trunc(firstDayOfWeek) % 7) + 7) % 7 : 1\n return Array.from({ length: DAYS_PER_WEEK }, (_, i) => (first + i) % 7)\n}\n\n/** Whether a date falls on one of the visible weekdays. */\nexport function isVisibleDay(iso: string, weekdays: readonly number[]): boolean {\n const date = parseISO(iso)\n return date !== null && weekdays.includes(date.getDay())\n}\n\n/**\n * The first visible day at or after `iso` (or at or before it, going backwards).\n *\n * The loop is bounded at seven because a non-empty weekday list always contains one of any\n * seven consecutive days: the bound is a guard against an empty list, never a real limit.\n */\nexport function snapToVisibleDay(\n iso: string,\n weekdays: readonly number[],\n direction: -1 | 1 = 1,\n): string {\n if (weekdays.length === 0) return iso\n let current = iso\n for (let i = 0; i < DAYS_PER_WEEK; i++) {\n if (isVisibleDay(current, weekdays)) return current\n current = addDays(current, direction)\n }\n return iso\n}\n\n/** Moves `count` VISIBLE days from `iso`, skipping over the hidden ones. */\nexport function advanceVisibleDays(\n iso: string,\n count: number,\n weekdays: readonly number[],\n): string {\n if (count === 0 || weekdays.length === 0) return iso\n const direction = count > 0 ? 1 : -1\n let current = iso\n let remaining = Math.abs(count)\n // Bounded so a malformed weekday list can never spin: at worst one visible day costs\n // seven calendar days, so this is the same walk with its worst case written down.\n let guard = Math.abs(count) * DAYS_PER_WEEK + DAYS_PER_WEEK\n while (remaining > 0 && guard-- > 0) {\n current = addDays(current, direction)\n if (isVisibleDay(current, weekdays)) remaining--\n }\n return current\n}\n\n/** How many day columns a view shows, before the visible-weekday filter applies. */\nexport function columnCount(view: CalendarView, customDays: number): number {\n if (view === 'day') return 1\n if (view === '4days') return 4\n if (view === 'custom') return clamp(Math.trunc(customDays), 1, 31)\n return DAYS_PER_WEEK\n}\n\n/**\n * The days a view shows, in reading order.\n *\n * `day`, `4days` and `custom` run forward from the anchor over the visible days alone, so\n * a four-day view of a Monday-to-Friday calendar shows four WORKING days rather than four\n * calendar ones. `week` shows the week the anchor falls in, starting on `weekdays[0]`.\n * `month` returns every day of the anchor's month that is visible: the grid that renders\n * them cuts them back into weeks itself.\n *\n * `year` returns an empty list on purpose: that view is twelve month names, not a row of\n * days, and giving it a 365-entry list nothing reads would be an invitation to read it.\n */\nexport function visibleDays(\n anchor: string,\n view: CalendarView,\n weekdays: readonly number[],\n customDays: number,\n): string[] {\n if (weekdays.length === 0 || parseISO(anchor) === null) return []\n if (view === 'year') return []\n\n if (view === 'month') {\n const date = parseISO(anchor)!\n const year = date.getFullYear()\n const month0 = date.getMonth()\n const days: string[] = []\n for (let day = 1; day <= daysInMonth(year, month0); day++) {\n const iso = isoOf(year, month0, day)\n if (isVisibleDay(iso, weekdays)) days.push(iso)\n }\n return days\n }\n\n if (view === 'week') {\n const date = parseISO(anchor)!\n const offset = (date.getDay() - weekdays[0]! + DAYS_PER_WEEK) % DAYS_PER_WEEK\n const weekStart = addDays(anchor, -offset)\n const days: string[] = []\n for (let i = 0; i < DAYS_PER_WEEK; i++) {\n const iso = addDays(weekStart, i)\n if (isVisibleDay(iso, weekdays)) days.push(iso)\n }\n return days\n }\n\n const total = columnCount(view, customDays)\n const days: string[] = [snapToVisibleDay(anchor, weekdays)]\n for (let i = 1; i < total; i++) days.push(advanceVisibleDays(days[i - 1]!, 1, weekdays))\n return days\n}\n\n/**\n * Where Previous and Next land.\n *\n * TRAP: the day-shaped views step over VISIBLE days, not calendar ones. Stepping by one\n * calendar day from a Friday in a Monday-to-Friday calendar would land on a Saturday the\n * grid does not draw; the view would then show the same week it already showed, and the\n * button would look broken with nothing in the console to say why.\n */\nexport function stepAnchor(\n anchor: string,\n view: CalendarView,\n direction: -1 | 1,\n weekdays: readonly number[],\n customDays: number,\n): string {\n if (parseISO(anchor) === null) return anchor\n if (view === 'month') return addMonths(anchor, direction)\n if (view === 'year') return addMonths(anchor, direction * 12)\n if (view === 'week') return snapToVisibleDay(addDays(anchor, direction * DAYS_PER_WEEK), weekdays)\n return advanceVisibleDays(anchor, direction * columnCount(view, customDays), weekdays)\n}\n\n/** The first and last day a view covers, which is what names it in the toolbar. */\nexport function visibleRange(\n anchor: string,\n view: CalendarView,\n weekdays: readonly number[],\n customDays: number,\n): { start: string; end: string } {\n if (view === 'year') {\n // No clock read here: the server and the browser must name the same range.\n const date = parseISO(anchor)\n if (!date) return { start: anchor, end: anchor }\n return { start: isoOf(date.getFullYear(), 0, 1), end: isoOf(date.getFullYear(), 11, 31) }\n }\n const days = visibleDays(anchor, view, weekdays, customDays)\n if (days.length === 0) return { start: anchor, end: anchor }\n return { start: days[0]!, end: days[days.length - 1]! }\n}\n\n/**\n * How long an event lasts, in minutes, counting the days between its dates.\n *\n * A time that is not one reads as the far end of its day: midnight for a start, the next\n * midnight for an end, so a malformed event errs towards being long, and towards the band.\n */\nexport function durationOf(times: CalendarEventTimes): number {\n return (\n daySpan(times) * MINUTES_PER_DAY +\n minutesAt(times.endTime, MINUTES_PER_DAY) -\n minutesAt(times.startTime, 0)\n )\n}\n\n/** Whether an event runs from one day into the next without lasting a whole day. */\nexport function spansMidnight(times: CalendarEventTimes): boolean {\n return compareISO(times.start, times.end) < 0 && durationOf(times) < MINUTES_PER_DAY\n}\n\n/**\n * Whether an event belongs in the band above the grid rather than in a day's column.\n *\n * A day's column holds up to twenty-four hours, so an event running past midnight for less\n * than that is drawn as two cards, the evening in one column and the early morning in the next.\n * Only one lasting a whole day or more becomes a bar. Exactly twenty-four hours is a bar:\n * two cards covering the same hours of two columns would read as two events.\n *\n * An event whose end comes before its start has no column to go in either, so it goes to the\n * band, where `packAllDay` finds no day it covers and draws nothing.\n */\nexport function isAllDayEvent(event: CalendarEvent): boolean {\n if (event.allDay === true) return true\n const order = compareISO(event.start, event.end)\n if (order === 0) return false\n return order > 0 || !spansMidnight(event)\n}\n\n/**\n * Which piece of its event a box is: the whole of it, or one side of midnight for an event\n * that runs from one day into the next.\n */\nexport type SegmentPart = 'whole' | 'head' | 'tail'\n\n/** One event's box in one day column, in minutes since midnight. */\nexport interface EventSegment {\n id: CalendarEventId\n /** Which of the visible columns it belongs to. */\n dayIndex: number\n start: number\n end: number\n /**\n * Whether the card carries on past its top or bottom edge: the window cut it off, or\n * midnight did. Both are drawn the same way.\n */\n clippedStart: boolean\n clippedEnd: boolean\n /**\n * `head` is the evening of an event running past midnight and `tail` its morning. Only a\n * `whole` or a `tail` holds the event's real end, which is what its resize strip drags.\n */\n part: SegmentPart\n}\n\n/**\n * Cuts the timed events into the boxes the day columns draw. All-day events produce none;\n * they belong in the band. Nor does an event whose day is not on show or whose hours fall\n * entirely outside the window.\n *\n * `minDuration` is applied HERE, by stretching the end, so that `packDayColumn` below sees\n * ordinary intervals and can stay a plain interval algorithm. Were the minimum applied\n * during packing instead, two events fifteen minutes apart would be laid out as though\n * they did not overlap and then drawn as though they did.\n */\nexport function timedSegments(\n events: readonly CalendarEvent[],\n days: readonly string[],\n timeWindow: TimeWindow,\n minDuration: number,\n): EventSegment[] {\n const index = new Map(days.map((iso, i) => [iso, i]))\n const segments: EventSegment[] = []\n\n const push = (\n id: CalendarEventId,\n dayIndex: number | undefined,\n rawStart: number,\n rawEnd: number,\n part: SegmentPart,\n ) => {\n if (dayIndex === undefined) return\n if (rawEnd <= timeWindow.start || rawStart >= timeWindow.end) return\n const start = clamp(rawStart, timeWindow.start, timeWindow.end - 1)\n const end = clamp(Math.max(rawEnd, start + minDuration), start + 1, timeWindow.end)\n segments.push({\n id,\n dayIndex,\n start,\n end,\n clippedStart: part === 'tail' || rawStart < timeWindow.start,\n clippedEnd: part === 'head' || rawEnd > timeWindow.end,\n part,\n })\n }\n\n for (const event of events) {\n if (isAllDayEvent(event)) continue\n const rawStart = minutesAt(event.startTime, timeWindow.start)\n\n if (compareISO(event.start, event.end) === 0) {\n const rawEnd = Math.max(minutesAt(event.endTime, rawStart + minDuration), rawStart + 1)\n push(event.id, index.get(event.start), rawStart, rawEnd, 'whole')\n continue\n }\n\n /*\n * Running past midnight: cut at midnight into two boxes, each in its own day's column. An\n * event ending at midnight exactly has nothing on its second day, so it is drawn whole — a\n * tail of no length would otherwise be stretched to a slot and show as a stub at 00:00.\n */\n const rawEnd = minutesAt(event.endTime, 0)\n if (rawEnd === 0) {\n push(event.id, index.get(event.start), rawStart, MINUTES_PER_DAY, 'whole')\n continue\n }\n push(event.id, index.get(event.start), rawStart, MINUTES_PER_DAY, 'head')\n push(event.id, index.get(event.end), 0, rawEnd, 'tail')\n }\n\n return segments\n}\n\n/** A segment once it knows how much of its column's width it gets. */\nexport interface PlacedSegment extends EventSegment {\n /** Which column of its cluster it sits in, counted from zero. */\n column: number\n /** How many columns it takes up: more than one when the room beside it is free. */\n span: number\n /** How many columns its cluster has. It is what both of the above are read against. */\n columns: number\n}\n\n/**\n * Shares one day's width out between the events happening at the same time.\n *\n * Three passes. First the segments are cut into CLUSTERS: runs joined by overlap, where an\n * event starting after everything before it has finished begins a new one. That is what\n * stops a crowded morning from squeezing a lone afternoon meeting into a sliver. Then,\n * inside a cluster, each event takes the first column whose last occupant has already\n * ended, which is the ordinary greedy colouring of an interval graph and uses as few\n * columns as the cluster can be drawn in. Finally each event grows sideways for as long as\n * nothing in the next column overlaps it, so a single event never leaves a gap it could\n * have filled.\n *\n * The order is fixed by start, then longest first, then by id, so the same list always\n * produces the same picture. Without the id to break the last tie, two events at the same\n * time for the same length would swap places whenever the array was re-sorted, and the\n * whole day would appear to shuffle for no reason.\n *\n * Overlap is strictly `a.start < b.end && b.start < a.end`, so events that merely touch\n * (one ending exactly as the next begins) share a column instead of splitting the width.\n */\nexport function packDayColumn(segments: readonly EventSegment[]): PlacedSegment[] {\n const sorted = [...segments].sort(\n (a, b) => a.start - b.start || b.end - b.start - (a.end - a.start) || compareId(a.id, b.id),\n )\n\n const placed: PlacedSegment[] = []\n let cluster: PlacedSegment[] = []\n let columnEnds: number[] = []\n let clusterEnd = -Infinity\n\n const flush = () => {\n const columns = columnEnds.length\n /*\n * The cluster filed by column first, so asking \"is the room beside me free?\" reads that one\n * column's occupants rather than the whole cluster: a crowded morning of k events across c\n * columns otherwise costs k squared per column crossed.\n */\n const byColumn: PlacedSegment[][] = Array.from({ length: columns }, () => [])\n for (const item of cluster) byColumn[item.column]!.push(item)\n for (const item of cluster) {\n let span = 1\n while (\n item.column + span < columns &&\n !byColumn[item.column + span]!.some(\n (other) => other.start < item.end && item.start < other.end,\n )\n ) {\n span++\n }\n item.span = span\n item.columns = columns\n }\n placed.push(...cluster)\n cluster = []\n columnEnds = []\n clusterEnd = -Infinity\n }\n\n for (const segment of sorted) {\n if (segment.start >= clusterEnd && cluster.length > 0) flush()\n\n let column = columnEnds.findIndex((end) => end <= segment.start)\n if (column === -1) {\n column = columnEnds.length\n columnEnds.push(segment.end)\n } else {\n columnEnds[column] = segment.end\n }\n\n cluster.push({ ...segment, column, span: 1, columns: 1 })\n clusterEnd = Math.max(clusterEnd, segment.end)\n }\n if (cluster.length > 0) flush()\n\n return placed\n}\n\n/**\n * One square of the month view: a day, and whether it belongs to the month on show.\n *\n * It is `utils/date`'s own type, re-exported rather than restated: `monthWeeks` below builds\n * its cells by calling `buildMonthGrid` from that module, so a second declaration here could\n * only ever drift away from the shape it is actually handed. The re-export keeps\n * `import { type MonthCell } from './layout'` working for the two views that read it.\n */\nexport type { MonthCell }\n\n/**\n * The month view, cut into weeks.\n *\n * It keeps the fixed six rows `buildMonthGrid` produces: padded with the neighbouring\n * months, so the grid is the same height whichever month is on show and the page does not\n * jump as one steps through the year. Each row is then filtered down to the visible\n * weekdays, which is what makes a Monday-to-Friday month five columns wide rather than\n * seven with two blanks.\n */\nexport function monthWeeks(anchor: string, weekdays: readonly number[]): MonthCell[][] {\n const date = parseISO(anchor)\n if (!date || weekdays.length === 0) return []\n\n const cells = buildMonthGrid(date.getFullYear(), date.getMonth(), weekdays[0]!)\n const weeks: MonthCell[][] = []\n for (let i = 0; i < cells.length; i += DAYS_PER_WEEK) {\n const week = cells\n .slice(i, i + DAYS_PER_WEEK)\n .filter((cell) => isVisibleDay(cell.iso, weekdays))\n if (week.length > 0) weeks.push(week)\n }\n return weeks\n}\n\n/** The first day of each month of the anchor's year, which is what the year view lays out. */\nexport function monthsOfYear(anchor: string): string[] {\n const date = parseISO(anchor)\n if (!date) return []\n const year = date.getFullYear()\n return Array.from({ length: 12 }, (_, month0) => isoOf(year, month0, 1))\n}\n\n/**\n * The last day an event puts anything on.\n *\n * Its `end` date, except for a timed event ending at midnight EXACTLY: it has nothing on\n * that day, which is why `timedSegments` draws it whole on its first day. Every other view\n * has to agree, or twenty-four hours from one midnight to the next becomes a two-day bar and\n * an evening ending at 00:00 is listed on the following day too. An all-day event's dates\n * are whole days, so its end is kept as it stands.\n */\nexport function lastDayOf(event: CalendarEvent): string {\n const endsAtMidnight =\n event.allDay !== true &&\n compareISO(event.start, event.end) < 0 &&\n minutesOf(event.endTime) === 0\n return endsAtMidnight ? addDays(event.end, -1) : event.end\n}\n\n/** Whether an event covers a given day at all, all-day or timed. */\nexport function coversDay(event: CalendarEvent, iso: string): boolean {\n return compareISO(iso, event.start) >= 0 && compareISO(iso, lastDayOf(event)) <= 0\n}\n\n/**\n * Every visible day's events, in the order a summary lists them: the all-day ones first, since\n * they frame the day rather than sit inside it, then the rest by when they start, the last tie\n * broken by id so the list cannot reshuffle for no reason.\n *\n * WHY ONE PASS RATHER THAN A FILTER PER DAY. The month view needs all 42 squares filled, and\n * filtering and sorting the list once per square walks the whole event list 42 times and sorts\n * it 42 times: `cells × events`, with a sort each. Measured on the bench: 7.8 ms per render at\n * 2000 events, and the month view rebuilds it every time a drag carries a chip onto another\n * day, so that is roughly half a frame spent rebuilding lists the gesture left alone.\n *\n * Here each event is placed once, into the days it actually covers, and each day is sorted\n * once: `events × span + cells × k log k`. The walk is bounded to the grid on both ends, so\n * an event running from last year costs its visible part and nothing more.\n */\nexport function eventsByDay<T extends CalendarEvent>(\n events: readonly T[],\n days: readonly string[],\n): Map<string, T[]> {\n const buckets = new Map<string, Ranked<T>[]>()\n for (const iso of days) buckets.set(iso, [])\n if (days.length === 0) return new Map()\n\n // The grid's own bounds. `days` is in order, so its ends bound every walk below — which\n // is what keeps a long-running event from being walked outside the month on show.\n const first = days[0]!\n const last = days[days.length - 1]!\n\n for (const event of events) {\n /*\n * TRAP: the walk below steps from one date to the next through `addDays`, which hands\n * back a string that is not a date unchanged. A datetime slipped into `start` would never\n * be stepped past, and the page would hang, on the server as well as in the browser.\n */\n if (!parseISO(event.start) || !parseISO(event.end)) continue\n const end = lastDayOf(event)\n const from = compareISO(event.start, first) > 0 ? event.start : first\n const to = compareISO(end, last) < 0 ? end : last\n if (compareISO(from, to) > 0) continue\n\n /*\n * Decorate-sort-undecorate, for the classic reason: the sort key is derived ONCE per\n * event rather than once per comparison, which is where the time in this function goes.\n * `minutesAt` re-parses an `HH:mm` string on every ask, and a 42-square month holding\n * 2000 events sorts some 17 000 pairs. Measured at 9.97 ms\n * against 4.19 ms with the key hoisted.\n */\n const ranked: Ranked<T> = {\n event,\n allDay: isAllDayEvent(event) ? 0 : 1,\n minutes: minutesAt(event.startTime, 0),\n id: String(event.id),\n }\n\n // An overnight event has been running since midnight on its second day, so it is ranked\n // from there, ahead of that morning's appointments, rather than by the evening before.\n const morning =\n ranked.allDay === 1 && compareISO(event.start, event.end) < 0\n ? { ...ranked, minutes: 0 }\n : ranked\n\n for (let iso = from; compareISO(iso, to) <= 0; iso = addDays(iso, 1)) {\n // A day the weekday filter hides has no bucket, so it is simply skipped: that is what\n // makes a Monday-to-Friday month cost nothing for the weekends it does not show.\n buckets.get(iso)?.push(iso === event.start ? ranked : morning)\n }\n }\n\n const byDay = new Map<string, T[]>()\n for (const [iso, list] of buckets) {\n list.sort(compareRanked)\n byDay.set(\n iso,\n list.map((item) => item.event),\n )\n }\n return byDay\n}\n\n/** An event with its sort key already worked out — see `eventsByDay`. */\ninterface Ranked<T> {\n event: T\n allDay: number\n minutes: number\n id: string\n}\n\n/** The order `eventsByDay` lists a day in, reading keys that are already computed. */\nfunction compareRanked<T>(a: Ranked<T>, b: Ranked<T>): number {\n return a.allDay - b.allDay || a.minutes - b.minutes || compareId(a.id, b.id)\n}\n\n/** One bar in the all-day band: the columns it covers, and the row it sits on. */\nexport interface AllDaySpan {\n id: CalendarEventId\n /** The first visible column it covers. */\n startIndex: number\n /** How many visible columns it covers, at least one. */\n span: number\n /** Whether it reaches beyond the days on show, on either side. */\n continuesBefore: boolean\n continuesAfter: boolean\n /** Which row of the band it sits on. */\n lane: number\n}\n\n/**\n * Stacks the all-day bars into as few rows as they fit in.\n *\n * A bar covers the run from the FIRST to the LAST visible day it touches, which is what\n * makes a hidden weekday in the middle of a range simply not there rather than a gap to\n * account for: an event running Friday to Monday in a Monday-to-Friday calendar shows as\n * one day on the Friday and one on the following Monday, because those are the two days it\n * actually covers on screen.\n *\n * The rows are the same greedy pass the day columns use: each bar takes the first row\n * whose last bar has already ended, and the ordering is fixed, longest first at equal\n * starts, so the band cannot reshuffle when the array is re-sorted.\n */\nexport function packAllDay(\n events: readonly CalendarEvent[],\n days: readonly string[],\n): AllDaySpan[] {\n const spans: AllDaySpan[] = []\n\n for (const event of events) {\n if (!isAllDayEvent(event)) continue\n\n let first = -1\n let last = -1\n for (let i = 0; i < days.length; i++) {\n if (coversDay(event, days[i]!)) {\n if (first === -1) first = i\n last = i\n }\n }\n if (first === -1) continue\n\n spans.push({\n id: event.id,\n startIndex: first,\n span: last - first + 1,\n continuesBefore: compareISO(event.start, days[first]!) < 0,\n continuesAfter: compareISO(lastDayOf(event), days[last]!) > 0,\n lane: 0,\n })\n }\n\n spans.sort((a, b) => a.startIndex - b.startIndex || b.span - a.span || compareId(a.id, b.id))\n\n const laneEnds: number[] = []\n for (const span of spans) {\n let lane = laneEnds.findIndex((end) => end < span.startIndex)\n if (lane === -1) {\n lane = laneEnds.length\n laneEnds.push(0)\n }\n laneEnds[lane] = span.startIndex + span.span - 1\n span.lane = lane\n }\n\n return spans\n}\n\n/** Where a moment sits in the visible window, as a unitless fraction the stylesheet scales. */\nexport function fractionOf(minutes: number, timeWindow: TimeWindow): number {\n const span = timeWindow.end - timeWindow.start\n return span > 0 ? clamp((minutes - timeWindow.start) / span, 0, 1) : 0\n}\n\n/** The measurements of the grid a pointer is being read against, in viewport coordinates. */\nexport interface GridGeometry {\n top: number\n height: number\n /** The edge the first column starts at: the left one normally, the right one in RTL. */\n inlineStart: number\n inlineSize: number\n columns: number\n}\n\n/**\n * Turns a point on the screen into a point on the calendar: which day column it fell in,\n * and what moment that height stands for.\n *\n * Both answers are brought back inside the grid, so a pointer dragged past an edge keeps\n * producing the nearest sensible value rather than none. That is what makes a drag that\n * wanders off the component behave as the user expects instead of stopping dead.\n */\nexport function pointToCell(\n point: { x: number; y: number },\n geometry: GridGeometry,\n timeWindow: TimeWindow,\n rtl: boolean,\n): { columnIndex: number; minutes: number } {\n const columnIndex = columnAt(point.x, geometry, rtl)\n\n const fraction = geometry.height > 0 ? (point.y - geometry.top) / geometry.height : 0\n const minutes = clamp(\n timeWindow.start + fraction * (timeWindow.end - timeWindow.start),\n timeWindow.start,\n timeWindow.end,\n )\n\n return { columnIndex, minutes }\n}\n\n/** Rounds a moment to the nearest step the calendar allows. */\nexport function snapToSlot(minutes: number, step: number): number {\n const size = step > 0 ? step : 1\n return clamp(Math.round(minutes / size) * size, 0, MINUTES_PER_DAY)\n}\n\n/** Rounds a moment DOWN to the step containing it: where a drawn slot begins. */\nexport function floorToSlot(minutes: number, step: number): number {\n const size = step > 0 ? step : 1\n return clamp(Math.floor(minutes / size) * size, 0, MINUTES_PER_DAY)\n}\n\n/**\n * How many whole days an event covers beyond its first: zero for one that starts and ends\n * on the same day, two for a trip from Monday to Wednesday.\n */\nexport function daySpan(times: CalendarEventTimes): number {\n const start = parseISO(times.start)\n const end = parseISO(times.end)\n if (!start || !end) return 0\n return Math.max(0, Math.round((end.getTime() - start.getTime()) / MS_PER_DAY))\n}\n\n/**\n * Moves an event to another day, KEEPING its length in days as well as its times.\n *\n * This is the counterpart of `timeGrid.ts`'s `moveEvent`, and the two must not be confused: that one is for\n * a timed move INSIDE a single day and deliberately collapses `end` onto `start`, so putting\n * a three-day trip through it would silently squash it into one Tuesday. This one is for the\n * whole-day gestures (an all-day bar dragged along the band, a chip dragged across a month),\n * where the times are carried along untouched and only the dates move.\n */\nexport function moveEventToDay(origin: CalendarEventTimes, target: string): CalendarEventTimes {\n if (parseISO(target) === null) return origin\n return {\n start: target,\n end: addDays(target, daySpan(origin)),\n startTime: origin.startTime,\n endTime: origin.endTime,\n }\n}\n\n/**\n * Which column a horizontal position falls in, counted in reading order and held inside the\n * grid. The inline half both grids share: the time grid's block axis is a moment, the month's\n * a row.\n */\nfunction columnAt(x: number, geometry: GridGeometry, rtl: boolean): number {\n const columns = Math.max(1, geometry.columns)\n const columnSize = geometry.inlineSize / columns\n const offset = rtl ? geometry.inlineStart - x : x - geometry.inlineStart\n return clamp(columnSize > 0 ? Math.floor(offset / columnSize) : 0, 0, columns - 1)\n}\n\n/** The measurements of a month grid: the same struct, with rows where a time window was. */\nexport interface MonthGeometry extends GridGeometry {\n rows: number\n}\n\n/**\n * Turns a point on the screen into a square of the month.\n *\n * The block axis is a ROW here rather than a moment, which is the whole difference from\n * `pointToCell`: the inline half is the same arithmetic, RTL flip included. It is sound\n * because every week of a month is drawn the same height: the rows share what space there is\n * and a day too full for its box clips rather than growing.\n */\nexport function pointToMonthCell(\n point: { x: number; y: number },\n geometry: MonthGeometry,\n rtl: boolean,\n): { columnIndex: number; rowIndex: number } {\n const rows = Math.max(1, geometry.rows)\n const columnIndex = columnAt(point.x, geometry, rtl)\n\n const rowSize = geometry.height / rows\n const rowIndex = clamp(\n rowSize > 0 ? Math.floor((point.y - geometry.top) / rowSize) : 0,\n 0,\n rows - 1,\n )\n\n return { columnIndex, rowIndex }\n}\n\n/**\n * Which inline edge a point is resting against, as a direction to page in: -1 for the start\n * edge, 1 for the end one, nothing in between.\n *\n * It answers in READING order, so the start edge is the left one normally and the right one\n * in a right-to-left page. That makes \"hold at the edge to go back\" mean the same\n * thing in both.\n */\nexport function inlineEdgeAt(\n x: number,\n geometry: { inlineStart: number; inlineSize: number },\n band: number,\n rtl: boolean,\n): -1 | 1 | null {\n if (band <= 0 || geometry.inlineSize <= 0) return null\n const offset = rtl ? geometry.inlineStart - x : x - geometry.inlineStart\n // Past the far side of the box counts as being against that edge: a pointer dragged clean\n // off the calendar is asking to keep going, not to stop.\n if (offset < band) return -1\n if (offset > geometry.inlineSize - band) return 1\n return null\n}\n\n/**\n * How hard a point near the top or bottom of a box is asking it to scroll: -1 at the very\n * top, 1 at the very bottom, 0 anywhere in the middle.\n *\n * A FRACTION and not a direction, so the speed can rise as the pointer nears the edge. At a\n * constant speed there is no setting that works: fast enough to be useful at the boundary is\n * uncontrollable a few pixels in, and gentle enough to be controllable never gets anywhere.\n */\nexport function blockEdgeAt(\n y: number,\n geometry: { top: number; height: number },\n band: number,\n): number {\n if (band <= 0 || geometry.height <= 0) return 0\n const fromTop = y - geometry.top\n const fromBottom = geometry.top + geometry.height - y\n if (fromTop < band) return -clamp((band - fromTop) / band, 0, 1)\n if (fromBottom < band) return clamp((band - fromBottom) / band, 0, 1)\n return 0\n}\n\n/**\n * Whether a point falls inside a box: how a drag decides, on release, whether it is over the\n * calendar at all or over the toolbar, or over the page beside it.\n *\n * It takes the box as the browser MEASURED it, and never the `GridGeometry` the rest of this file\n * works in. Containment is a physical question with no reading direction, where `inlineStart` is\n * the box's RIGHT edge in a right-to-left page: bracketing with it would walk off the far side of\n * the screen and make every right-to-left drop read as outside. `columns` says nothing about\n * whether a point is in a box either, so asking for a `GridGeometry` would ask every caller to\n * build a thing this needs no part of.\n *\n * The edges COUNT as inside: a rect is a pair of floats on a screen that may be scaled by a\n * fraction, and an exclusive boundary would refuse a drop that looked, and was, on target.\n *\n * TRAP: a box that measures nothing contains EVERYTHING. That is the same answer `inlineEdgeAt`\n * and `blockEdgeAt` give a box of no size, and for the same reason: with no measurement there is\n * no outside to be on. Take a zero-size box literally instead and the calendar refuses every drop\n * wherever nothing has been laid out: in a hidden tab, before the first frame, and in jsdom,\n * which measures nothing at all and where all but a handful of the drag tests would go red at\n * once.\n */\nexport function pointWithin(\n point: { x: number; y: number },\n box: { left: number; top: number; width: number; height: number },\n): boolean {\n if (box.width <= 0 || box.height <= 0) return true\n return (\n point.x >= box.left &&\n point.x <= box.left + box.width &&\n point.y >= box.top &&\n point.y <= box.top + box.height\n )\n}\n\n/** The times of an event, as the shape the move and resize events carry. */\nexport function timesOf(event: CalendarEvent): CalendarEventTimes {\n return {\n start: event.start,\n end: event.end,\n startTime: event.startTime,\n endTime: event.endTime,\n }\n}\n\n/**\n * Whether two placements are the same, field for field.\n *\n * It is what a drag asks before it writes its preview. A slot is a quarter of an hour, some\n * sixteen pixels at the default hour height, so most pointer moves land on the times already\n * shown, and the gesture state is deeply reactive, so an EQUAL but new object would still\n * re-run the whole layout and re-render every cell of the grid for a picture that did not\n * change.\n */\nexport function sameTimes(a: CalendarEventTimes, b: CalendarEventTimes): boolean {\n return (\n a.start === b.start && a.end === b.end && a.startTime === b.startTime && a.endTime === b.endTime\n )\n}\n\n/** Today, as the calendar reads it. Kept here so the component has one place to call. */\nexport function todayISO(): string {\n return formatISO(new Date())\n}\n"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ export declare function roundToSlot(minutes: number, step: number): number;
|
|
|
11
11
|
* show rather than counted here, so a hidden weekday is never landed on.
|
|
12
12
|
*
|
|
13
13
|
* An event pushed past the end of the window is held against it rather than having its tail
|
|
14
|
-
* cut off
|
|
14
|
+
* cut off: moving something must never change how long it is, which is the other gesture's
|
|
15
15
|
* job.
|
|
16
16
|
*/
|
|
17
17
|
export declare function moveEvent(origin: CalendarEventTimes, minuteDelta: number, timeWindow: TimeWindow): CalendarEventTimes;
|
|
@@ -28,7 +28,7 @@ export declare function resizeEvent(origin: CalendarEventTimes, endMinutes: numb
|
|
|
28
28
|
* FOUND it rather than off the times being written. An event that ran past midnight when it was
|
|
29
29
|
* taken hold of may go on doing so, or come back inside one day, or cross again: its start may
|
|
30
30
|
* be pushed into the day before or after `day`, and its end lands wherever its length takes it.
|
|
31
|
-
* Any other event is `moveEvent`'s, held inside `day
|
|
31
|
+
* Any other event is `moveEvent`'s, held inside `day`, so a drag never turns an ordinary
|
|
32
32
|
* appointment into an overnight one by accident.
|
|
33
33
|
*/
|
|
34
34
|
export declare function moveTimedEvent(times: CalendarEventTimes, day: string, startMinutes: number, timeWindow: TimeWindow, crossMidnight: boolean): CalendarEventTimes;
|
|
@@ -36,8 +36,8 @@ export declare function moveTimedEvent(times: CalendarEventTimes, day: string, s
|
|
|
36
36
|
* Where a timed event's end lands when it is dragged or nudged: `endMinutes` counted from the
|
|
37
37
|
* midnight that begins `endDay`.
|
|
38
38
|
*
|
|
39
|
-
* With `crossMidnight` false this is `resizeEvent`, and `endDay` is ignored. With it true
|
|
40
|
-
* event that ran past midnight when the gesture began
|
|
39
|
+
* With `crossMidnight` false this is `resizeEvent`, and `endDay` is ignored. With it true: an
|
|
40
|
+
* event that ran past midnight when the gesture began: the end may sit on the event's first
|
|
41
41
|
* day or on the next one, whichever `endDay` names (anything else is held to the nearer of the
|
|
42
42
|
* two), and it stays at least `minDuration` after the start and short of a whole day after it,
|
|
43
43
|
* so the event never turns into a bar under the pointer.
|
|
@@ -30,12 +30,12 @@ function d(r, a, c, u, d) {
|
|
|
30
30
|
start: a,
|
|
31
31
|
end: a
|
|
32
32
|
};
|
|
33
|
-
let f = e(i(r), 1, n - 1), p = Math.floor(c / n), m =
|
|
33
|
+
let f = e(i(r), 1, n - 1), p = Math.floor(c / n), m = c - p * n, h = o(r.startTime, u.start), g = h >= u.start && h < u.end ? e(m, u.start, u.end - 1) : m, _ = t(a, p), v = g + f;
|
|
34
34
|
return {
|
|
35
|
-
start:
|
|
36
|
-
end:
|
|
37
|
-
startTime: s(
|
|
38
|
-
endTime: s(
|
|
35
|
+
start: _,
|
|
36
|
+
end: v >= 1440 ? t(_, 1) : _,
|
|
37
|
+
startTime: s(g),
|
|
38
|
+
endTime: s(v % n)
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
function f(i, a, c, l, d, f) {
|
|
@@ -48,7 +48,7 @@ function f(i, a, c, l, d, f) {
|
|
|
48
48
|
start: i.start,
|
|
49
49
|
end: i.start,
|
|
50
50
|
startTime: s(p),
|
|
51
|
-
endTime: s(Math.min(m, d.end))
|
|
51
|
+
endTime: s(Math.max(Math.min(m, d.end), p + Math.max(l, 1)))
|
|
52
52
|
} : {
|
|
53
53
|
start: i.start,
|
|
54
54
|
end: t(i.start, 1),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeGrid.js","names":[],"sources":["../../../src/components/VCalendar/timeGrid.ts"],"sourcesContent":["// @core\n/**\n * Where a timed event lands when a time grid's gesture lets go of it: moved, stretched, or drawn\n * out of an empty slot.\n *\n * It is `layout.ts`'s arithmetic, pure and tested over plain numbers the same way, and it sits in\n * a module of its own for the size gate alone. The month and year views import `layout.ts` too,\n * and a module is weighed whole in every closure that reaches it: kept there, these functions\n * were paid for by two views that never move an event by the minute (the `VTimePicker/limits.ts`\n * argument).\n */\nimport { addDays } from '../../utils/date'\nimport { clamp } from '../../utils/number'\n\nimport {\n MINUTES_PER_DAY,\n daySpan,\n durationOf,\n floorToSlot,\n minutesAt,\n timeOf,\n type TimeWindow,\n} from './layout'\nimport type { CalendarEventTimes } from './types'\n\n/**\n * Rounds a moment to the nearest step WITHOUT holding it inside the day, for a start that a\n * move may push past either midnight.\n */\nexport function roundToSlot(minutes: number, step: number): number {\n const size = step > 0 ? step : 1\n return Math.round(minutes / size) * size\n}\n\n/**\n * Where an event lands when it is dragged or nudged along its day: it keeps its length and\n * moves by `minuteDelta`. Which DAY it lands on is the caller's to set, read from the days on\n * show rather than counted here, so a hidden weekday is never landed on.\n *\n * An event pushed past the end of the window is held against it rather than having its tail\n * cut off — moving something must never change how long it is, which is the other gesture's\n * job.\n */\nexport function moveEvent(\n origin: CalendarEventTimes,\n minuteDelta: number,\n timeWindow: TimeWindow,\n): CalendarEventTimes {\n const startMinutes = minutesAt(origin.startTime, timeWindow.start)\n const endMinutes = Math.max(minutesAt(origin.endTime, startMinutes), startMinutes + 1)\n const duration = endMinutes - startMinutes\n\n const nextStart = clamp(\n startMinutes + minuteDelta,\n timeWindow.start,\n Math.max(timeWindow.start, timeWindow.end - duration),\n )\n\n return {\n start: origin.start,\n end: origin.start,\n startTime: timeOf(nextStart),\n endTime: timeOf(nextStart + duration),\n }\n}\n\n/**\n * Where the end of an event lands when its bottom edge is dragged. It can never cross the\n * start: the shortest an event may become is one step.\n */\nexport function resizeEvent(\n origin: CalendarEventTimes,\n endMinutes: number,\n minDuration: number,\n timeWindow: TimeWindow,\n): CalendarEventTimes {\n const startMinutes = minutesAt(origin.startTime, timeWindow.start)\n const floor = Math.max(minDuration, 1)\n const end = clamp(endMinutes, startMinutes + floor, timeWindow.end)\n\n return {\n start: origin.start,\n end: origin.start,\n startTime: timeOf(startMinutes),\n endTime: timeOf(end),\n }\n}\n\n/**\n * Where a timed event lands when it is dragged or nudged: it starts at `startMinutes` counted\n * from the midnight that begins `day`, and keeps its length.\n *\n * `crossMidnight` says which of two rules applies, and it is read off the event as the gesture\n * FOUND it rather than off the times being written. An event that ran past midnight when it was\n * taken hold of may go on doing so, or come back inside one day, or cross again: its start may\n * be pushed into the day before or after `day`, and its end lands wherever its length takes it.\n * Any other event is `moveEvent`'s, held inside `day` — so a drag never turns an ordinary\n * appointment into an overnight one by accident.\n */\nexport function moveTimedEvent(\n times: CalendarEventTimes,\n day: string,\n startMinutes: number,\n timeWindow: TimeWindow,\n crossMidnight: boolean,\n): CalendarEventTimes {\n if (!crossMidnight) {\n const from = minutesAt(times.startTime, timeWindow.start)\n return { ...moveEvent(times, startMinutes - from, timeWindow), start: day, end: day }\n }\n\n const duration = clamp(durationOf(times), 1, MINUTES_PER_DAY - 1)\n const dayShift = Math.floor(startMinutes / MINUTES_PER_DAY)\n const start = clamp(\n startMinutes - dayShift * MINUTES_PER_DAY,\n timeWindow.start,\n timeWindow.end - 1,\n )\n const startDay = addDays(day, dayShift)\n const end = start + duration\n return {\n start: startDay,\n end: end >= MINUTES_PER_DAY ? addDays(startDay, 1) : startDay,\n startTime: timeOf(start),\n endTime: timeOf(end % MINUTES_PER_DAY),\n }\n}\n\n/**\n * Where a timed event's end lands when it is dragged or nudged: `endMinutes` counted from the\n * midnight that begins `endDay`.\n *\n * With `crossMidnight` false this is `resizeEvent`, and `endDay` is ignored. With it true — an\n * event that ran past midnight when the gesture began — the end may sit on the event's first\n * day or on the next one, whichever `endDay` names (anything else is held to the nearer of the\n * two), and it stays at least `minDuration` after the start and short of a whole day after it,\n * so the event never turns into a bar under the pointer.\n */\nexport function resizeTimedEvent(\n times: CalendarEventTimes,\n endDay: string,\n endMinutes: number,\n minDuration: number,\n timeWindow: TimeWindow,\n crossMidnight: boolean,\n): CalendarEventTimes {\n if (!crossMidnight) return resizeEvent(times, endMinutes, minDuration, timeWindow)\n\n const startMinutes = minutesAt(times.startTime, timeWindow.start)\n // `daySpan` already reads a day before the start as zero.\n const dayOffset = Math.min(daySpan({ ...times, end: endDay }), 1)\n const end = clamp(\n dayOffset * MINUTES_PER_DAY + endMinutes,\n startMinutes + Math.max(minDuration, 1),\n startMinutes + MINUTES_PER_DAY - 1,\n )\n\n // Back inside the first day, the end obeys the window like any other; the `timeOf` clamp\n // then writes midnight as 23:59 of that day rather than 00:00 of the next.\n if (end <= MINUTES_PER_DAY) {\n return {\n start: times.start,\n end: times.start,\n startTime: timeOf(startMinutes),\n endTime: timeOf(Math.min(end, timeWindow.end)),\n }\n }\n return {\n start: times.start,\n end: addDays(times.start, 1),\n startTime: timeOf(startMinutes),\n endTime: timeOf(end - MINUTES_PER_DAY),\n }\n}\n\n/**\n * The stretch of a day drawn out by a press at `anchor` and a pointer now at `pointer`, both in\n * minutes since midnight.\n *\n * It covers every slot from the one pressed to the one under the pointer, both included, so it\n * reads the same whichever way the pointer went: down from 10:00 to 11:10 is 10:00–11:15, up\n * from 10:00 to 9:10 is 9:00–10:15. The pressed slot is never given up, which is what keeps the\n * box from flipping over as the pointer crosses its own starting point.\n */\nexport function drawnSlot(\n day: string,\n anchor: number,\n pointer: number,\n step: number,\n timeWindow: TimeWindow,\n): CalendarEventTimes {\n const size = step > 0 ? step : 1\n const from = floorToSlot(anchor, size)\n const at = floorToSlot(pointer, size)\n const start = Math.max(timeWindow.start, Math.min(from, at))\n const end = clamp(Math.max(from, at) + size, start + 1, timeWindow.end)\n return { start: day, end: day, startTime: timeOf(start), endTime: timeOf(end) }\n}\n"],"mappings":";;;;AA6BA,SAAgB,EAAY,GAAiB,GAAsB;CACjE,IAAM,IAAO,IAAO,IAAI,IAAO;CAC/B,OAAO,KAAK,MAAM,IAAU,CAAI,IAAI;AACtC;AAWA,SAAgB,EACd,GACA,GACA,GACoB;CACpB,IAAM,IAAe,EAAU,EAAO,WAAW,EAAW,KAAK,GAE3D,IADa,KAAK,IAAI,EAAU,EAAO,SAAS,CAAY,GAAG,IAAe,CACnE,IAAa,GAExB,IAAY,EAChB,IAAe,GACf,EAAW,OACX,KAAK,IAAI,EAAW,OAAO,EAAW,MAAM,CAAQ,CACtD;CAEA,OAAO;EACL,OAAO,EAAO;EACd,KAAK,EAAO;EACZ,WAAW,EAAO,CAAS;EAC3B,SAAS,EAAO,IAAY,CAAQ;CACtC;AACF;AAMA,SAAgB,EACd,GACA,GACA,GACA,GACoB;CACpB,IAAM,IAAe,EAAU,EAAO,WAAW,EAAW,KAAK,GAE3D,IAAM,EAAM,GAAY,IADhB,KAAK,IAAI,GAAa,CACS,GAAO,EAAW,GAAG;CAElE,OAAO;EACL,OAAO,EAAO;EACd,KAAK,EAAO;EACZ,WAAW,EAAO,CAAY;EAC9B,SAAS,EAAO,CAAG;CACrB;AACF;AAaA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACoB;CACpB,IAAI,CAAC,GAEH,OAAO;EAAE,GAAG,EAAU,GAAO,IADhB,EAAU,EAAM,WAAW,EAAW,KACP,GAAM,CAAU;EAAG,OAAO;EAAK,KAAK;CAAI;CAGtF,IAAM,IAAW,EAAM,EAAW,CAAK,GAAG,GAAG,IAAkB,CAAC,GAC1D,IAAW,KAAK,MAAM,IAAe,CAAe,GACpD,IAAQ,EACZ,IAAe,IAAW,GAC1B,EAAW,OACX,EAAW,MAAM,CACnB,GACM,IAAW,EAAQ,GAAK,CAAQ,GAChC,IAAM,IAAQ;CACpB,OAAO;EACL,OAAO;EACP,KAAK,KAAA,OAAyB,EAAQ,GAAU,CAAC,IAAI;EACrD,WAAW,EAAO,CAAK;EACvB,SAAS,EAAO,IAAM,CAAe;CACvC;AACF;AAYA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,GACoB;CACpB,IAAI,CAAC,GAAe,OAAO,EAAY,GAAO,GAAY,GAAa,CAAU;CAEjF,IAAM,IAAe,EAAU,EAAM,WAAW,EAAW,KAAK,GAG1D,IAAM,EADM,KAAK,IAAI,EAAQ;EAAE,GAAG;EAAO,KAAK;CAAO,CAAC,GAAG,CAE7D,IAAY,IAAkB,GAC9B,IAAe,KAAK,IAAI,GAAa,CAAC,GACtC,IAAe,IAAkB,CACnC;CAYA,OARI,KAAA,OACK;EACL,OAAO,EAAM;EACb,KAAK,EAAM;EACX,WAAW,EAAO,CAAY;EAC9B,SAAS,EAAO,KAAK,IAAI,GAAK,EAAW,GAAG,CAAC;CAC/C,IAEK;EACL,OAAO,EAAM;EACb,KAAK,EAAQ,EAAM,OAAO,CAAC;EAC3B,WAAW,EAAO,CAAY;EAC9B,SAAS,EAAO,IAAM,CAAe;CACvC;AACF;AAWA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACoB;CACpB,IAAM,IAAO,IAAO,IAAI,IAAO,GACzB,IAAO,EAAY,GAAQ,CAAI,GAC/B,IAAK,EAAY,GAAS,CAAI,GAC9B,IAAQ,KAAK,IAAI,EAAW,OAAO,KAAK,IAAI,GAAM,CAAE,CAAC,GACrD,IAAM,EAAM,KAAK,IAAI,GAAM,CAAE,IAAI,GAAM,IAAQ,GAAG,EAAW,GAAG;CACtE,OAAO;EAAE,OAAO;EAAK,KAAK;EAAK,WAAW,EAAO,CAAK;EAAG,SAAS,EAAO,CAAG;CAAE;AAChF"}
|
|
1
|
+
{"version":3,"file":"timeGrid.js","names":[],"sources":["../../../src/components/VCalendar/timeGrid.ts"],"sourcesContent":["// @core\n/**\n * Where a timed event lands when a time grid's gesture lets go of it: moved, stretched, or drawn\n * out of an empty slot.\n *\n * It is `layout.ts`'s arithmetic, pure and tested over plain numbers the same way, and it sits in\n * a module of its own for the size gate alone. The month and year views import `layout.ts` too,\n * and a module is weighed whole in every closure that reaches it: kept there, these functions\n * were paid for by two views that never move an event by the minute (the `VTimePicker/limits.ts`\n * argument).\n */\nimport { addDays } from '../../utils/date'\nimport { clamp } from '../../utils/number'\n\nimport {\n MINUTES_PER_DAY,\n daySpan,\n durationOf,\n floorToSlot,\n minutesAt,\n timeOf,\n type TimeWindow,\n} from './layout'\nimport type { CalendarEventTimes } from './types'\n\n/**\n * Rounds a moment to the nearest step WITHOUT holding it inside the day, for a start that a\n * move may push past either midnight.\n */\nexport function roundToSlot(minutes: number, step: number): number {\n const size = step > 0 ? step : 1\n return Math.round(minutes / size) * size\n}\n\n/**\n * Where an event lands when it is dragged or nudged along its day: it keeps its length and\n * moves by `minuteDelta`. Which DAY it lands on is the caller's to set, read from the days on\n * show rather than counted here, so a hidden weekday is never landed on.\n *\n * An event pushed past the end of the window is held against it rather than having its tail\n * cut off: moving something must never change how long it is, which is the other gesture's\n * job.\n */\nexport function moveEvent(\n origin: CalendarEventTimes,\n minuteDelta: number,\n timeWindow: TimeWindow,\n): CalendarEventTimes {\n const startMinutes = minutesAt(origin.startTime, timeWindow.start)\n const endMinutes = Math.max(minutesAt(origin.endTime, startMinutes), startMinutes + 1)\n const duration = endMinutes - startMinutes\n\n const nextStart = clamp(\n startMinutes + minuteDelta,\n timeWindow.start,\n Math.max(timeWindow.start, timeWindow.end - duration),\n )\n\n return {\n start: origin.start,\n end: origin.start,\n startTime: timeOf(nextStart),\n endTime: timeOf(nextStart + duration),\n }\n}\n\n/**\n * Where the end of an event lands when its bottom edge is dragged. It can never cross the\n * start: the shortest an event may become is one step.\n */\nexport function resizeEvent(\n origin: CalendarEventTimes,\n endMinutes: number,\n minDuration: number,\n timeWindow: TimeWindow,\n): CalendarEventTimes {\n const startMinutes = minutesAt(origin.startTime, timeWindow.start)\n const floor = Math.max(minDuration, 1)\n const end = clamp(endMinutes, startMinutes + floor, timeWindow.end)\n\n return {\n start: origin.start,\n end: origin.start,\n startTime: timeOf(startMinutes),\n endTime: timeOf(end),\n }\n}\n\n/**\n * Where a timed event lands when it is dragged or nudged: it starts at `startMinutes` counted\n * from the midnight that begins `day`, and keeps its length.\n *\n * `crossMidnight` says which of two rules applies, and it is read off the event as the gesture\n * FOUND it rather than off the times being written. An event that ran past midnight when it was\n * taken hold of may go on doing so, or come back inside one day, or cross again: its start may\n * be pushed into the day before or after `day`, and its end lands wherever its length takes it.\n * Any other event is `moveEvent`'s, held inside `day`, so a drag never turns an ordinary\n * appointment into an overnight one by accident.\n */\nexport function moveTimedEvent(\n times: CalendarEventTimes,\n day: string,\n startMinutes: number,\n timeWindow: TimeWindow,\n crossMidnight: boolean,\n): CalendarEventTimes {\n if (!crossMidnight) {\n const from = minutesAt(times.startTime, timeWindow.start)\n return { ...moveEvent(times, startMinutes - from, timeWindow), start: day, end: day }\n }\n\n const duration = clamp(durationOf(times), 1, MINUTES_PER_DAY - 1)\n const dayShift = Math.floor(startMinutes / MINUTES_PER_DAY)\n const wanted = startMinutes - dayShift * MINUTES_PER_DAY\n /*\n * The start is held inside the window only when it was taken from inside it. An overnight\n * event may begin after the window closes (a 22:00 shift on a calendar showing 08:00 to\n * 20:00, whose morning card alone is drawn): pulled into the window, its start jumped hours\n * and left the slot grid at the first nudge. Left where it was asked to go, it cannot\n * vanish either, since moving it later lengthens its morning and moving it earlier brings\n * its evening into view.\n */\n const from = minutesAt(times.startTime, timeWindow.start)\n const start =\n from >= timeWindow.start && from < timeWindow.end\n ? clamp(wanted, timeWindow.start, timeWindow.end - 1)\n : wanted\n const startDay = addDays(day, dayShift)\n const end = start + duration\n return {\n start: startDay,\n end: end >= MINUTES_PER_DAY ? addDays(startDay, 1) : startDay,\n startTime: timeOf(start),\n endTime: timeOf(end % MINUTES_PER_DAY),\n }\n}\n\n/**\n * Where a timed event's end lands when it is dragged or nudged: `endMinutes` counted from the\n * midnight that begins `endDay`.\n *\n * With `crossMidnight` false this is `resizeEvent`, and `endDay` is ignored. With it true: an\n * event that ran past midnight when the gesture began: the end may sit on the event's first\n * day or on the next one, whichever `endDay` names (anything else is held to the nearer of the\n * two), and it stays at least `minDuration` after the start and short of a whole day after it,\n * so the event never turns into a bar under the pointer.\n */\nexport function resizeTimedEvent(\n times: CalendarEventTimes,\n endDay: string,\n endMinutes: number,\n minDuration: number,\n timeWindow: TimeWindow,\n crossMidnight: boolean,\n): CalendarEventTimes {\n if (!crossMidnight) return resizeEvent(times, endMinutes, minDuration, timeWindow)\n\n const startMinutes = minutesAt(times.startTime, timeWindow.start)\n // `daySpan` already reads a day before the start as zero.\n const dayOffset = Math.min(daySpan({ ...times, end: endDay }), 1)\n const end = clamp(\n dayOffset * MINUTES_PER_DAY + endMinutes,\n startMinutes + Math.max(minDuration, 1),\n startMinutes + MINUTES_PER_DAY - 1,\n )\n\n // Back inside the first day, the end obeys the window like any other; the `timeOf` clamp\n // then writes midnight as 23:59 of that day rather than 00:00 of the next.\n if (end <= MINUTES_PER_DAY) {\n return {\n start: times.start,\n end: times.start,\n startTime: timeOf(startMinutes),\n // The window caps the end, never below the shortest length: a start after the window\n // closes would otherwise be given an end before it.\n endTime: timeOf(\n Math.max(Math.min(end, timeWindow.end), startMinutes + Math.max(minDuration, 1)),\n ),\n }\n }\n return {\n start: times.start,\n end: addDays(times.start, 1),\n startTime: timeOf(startMinutes),\n endTime: timeOf(end - MINUTES_PER_DAY),\n }\n}\n\n/**\n * The stretch of a day drawn out by a press at `anchor` and a pointer now at `pointer`, both in\n * minutes since midnight.\n *\n * It covers every slot from the one pressed to the one under the pointer, both included, so it\n * reads the same whichever way the pointer went: down from 10:00 to 11:10 is 10:00–11:15, up\n * from 10:00 to 9:10 is 9:00–10:15. The pressed slot is never given up, which is what keeps the\n * box from flipping over as the pointer crosses its own starting point.\n */\nexport function drawnSlot(\n day: string,\n anchor: number,\n pointer: number,\n step: number,\n timeWindow: TimeWindow,\n): CalendarEventTimes {\n const size = step > 0 ? step : 1\n const from = floorToSlot(anchor, size)\n const at = floorToSlot(pointer, size)\n const start = Math.max(timeWindow.start, Math.min(from, at))\n const end = clamp(Math.max(from, at) + size, start + 1, timeWindow.end)\n return { start: day, end: day, startTime: timeOf(start), endTime: timeOf(end) }\n}\n"],"mappings":"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"}
|
|
@@ -6,13 +6,13 @@ export type CalendarEventId = string | number;
|
|
|
6
6
|
*
|
|
7
7
|
* The seven fields below are the contract. A consumer needing more of their own writes an
|
|
8
8
|
* interface extending this one and hands it to the component, which carries the type
|
|
9
|
-
* through to the `#event` slot
|
|
9
|
+
* through to the `#event` slot, so extra fields arrive typed rather than as `unknown`.
|
|
10
10
|
*/
|
|
11
11
|
export interface CalendarEvent {
|
|
12
12
|
/**
|
|
13
13
|
* What identifies this event. It must be unique among the events given and stable
|
|
14
14
|
* across renders: it is what the drag writes back to, what gives a card its key, and
|
|
15
|
-
* what decides the colour of an event that names none
|
|
15
|
+
* what decides the colour of an event that names none, so an event keeps its colour
|
|
16
16
|
* as the list is filtered or re-sorted.
|
|
17
17
|
*/
|
|
18
18
|
id: CalendarEventId;
|
|
@@ -20,7 +20,7 @@ export interface CalendarEvent {
|
|
|
20
20
|
title: string;
|
|
21
21
|
/** The day it starts on. */
|
|
22
22
|
start: string;
|
|
23
|
-
/** The day it ends on
|
|
23
|
+
/** The day it ends on: the same day as `start` for an ordinary appointment. */
|
|
24
24
|
end: string;
|
|
25
25
|
/** When it starts on that day. */
|
|
26
26
|
startTime: string;
|
|
@@ -35,7 +35,7 @@ export interface CalendarEvent {
|
|
|
35
35
|
*/
|
|
36
36
|
color?: string;
|
|
37
37
|
/**
|
|
38
|
-
* The name of a time zone, shown beside the times as an annotation
|
|
38
|
+
* The name of a time zone, shown beside the times as an annotation: "09:00 – 10:00
|
|
39
39
|
* (Europe/Paris)". It is DISPLAY ONLY: it never moves the card, which is always placed
|
|
40
40
|
* from the dates and times above, read as local time.
|
|
41
41
|
*/
|
|
@@ -49,7 +49,7 @@ export interface CalendarEvent {
|
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
51
|
* Which period the calendar is showing. `custom` is the general case and `4days` a
|
|
52
|
-
* preset on the same machinery
|
|
52
|
+
* preset on the same machinery: they differ only in where the number of days comes from.
|
|
53
53
|
*/
|
|
54
54
|
export type CalendarView = 'day' | '4days' | 'week' | 'month' | 'year' | 'custom';
|
|
55
55
|
/** Whether times are written on a twelve or a twenty-four hour clock. */
|