UNPKG

reka-ui

Version:

Vue port for Radix UI Primitives.

202 lines (173 loc) 6.08 kB
/* * Adapted from https://github.com/melt-ui/melt-ui/blob/develop/src/lib/builders/range-calendar/create.ts */ import type { DateValue } from '@internationalized/date' import type { Ref } from 'vue' import type { Matcher } from '@/date' import { isSameDay } from '@internationalized/date' import { computed } from 'vue' import { areAllDaysBetweenValid, getDaysBetween, isBefore, isBetween } from '@/date' export type UseRangeCalendarProps = { start: Ref<DateValue | undefined> end: Ref<DateValue | undefined> isDateDisabled: Matcher isDateUnavailable: Matcher isDateHighlightable?: Matcher focusedValue: Ref<DateValue | undefined> allowNonContiguousRanges: Ref<boolean> fixedDate: Ref<'start' | 'end' | undefined> maximumDays?: Ref<number | undefined> } export function useRangeCalendarState(props: UseRangeCalendarProps) { const isStartInvalid = computed(() => { if (!props.start.value) return false if (props.isDateDisabled(props.start.value)) return true return false }) const isEndInvalid = computed(() => { if (!props.end.value) return false if (props.isDateDisabled(props.end.value)) return true return false }) const isInvalid = computed( () => { if (isStartInvalid.value || isEndInvalid.value) return true if (props.start.value && props.end.value && isBefore(props.end.value, props.start.value)) return true return false }, ) const isSelectionStart = (date: DateValue) => { if (!props.start.value) return false return isSameDay(props.start.value, date) } const isSelectionEnd = (date: DateValue) => { if (!props.end.value) return false return isSameDay(props.end.value, date) } const isSelected = (date: DateValue) => { if (props.start.value && isSameDay(props.start.value, date)) return true if (props.end.value && isSameDay(props.end.value, date)) return true if (props.end.value && props.start.value) return isBetween(date, props.start.value, props.end.value) return false } const rangeIsDateDisabled = (date: DateValue) => { if (props.isDateDisabled(date)) return true if (props.maximumDays?.value) { if (props.start.value && props.end.value) { if (props.fixedDate.value) { const diff = getDaysBetween(props.start.value, props.end.value).length if (diff <= props.maximumDays.value) { const daysLeft = props.maximumDays.value - diff - 1 const startLimit = props.start.value.subtract({ days: daysLeft }) const endLimit = props.end.value.add({ days: daysLeft }) return !isBetween(date, startLimit, endLimit) } } return false } if (props.start.value) { const maxDate = props.start.value.add({ days: props.maximumDays.value }) const minDate = props.start.value.subtract({ days: props.maximumDays.value }) return !isBetween(date, minDate, maxDate) } } return false } const isDateHighlightable = (date: DateValue) => { if (props.isDateHighlightable?.(date)) return true return false } const highlightedRange = computed(() => { if (props.start.value && props.end.value && !props.fixedDate.value) return null if (!props.start.value || !props.focusedValue.value) return null const isStartBeforeFocused = isBefore(props.start.value, props.focusedValue.value) const start = isStartBeforeFocused ? props.start.value : props.focusedValue.value const end = isStartBeforeFocused ? props.focusedValue.value : props.start.value if (isSameDay(start, end)) { return { start, end, } } if (props.maximumDays?.value && !props.end.value) { const maximumDays = props.maximumDays.value const anchor = props.start.value const focused = props.focusedValue.value if (!isBefore(focused, anchor)) return { start: anchor, end: anchor.add({ days: maximumDays - 1 }) } return { start: anchor.subtract({ days: maximumDays - 1 }), end: anchor } } const isValid = areAllDaysBetweenValid(start, end, props.allowNonContiguousRanges.value ? () => false : props.isDateUnavailable, rangeIsDateDisabled, props.isDateHighlightable) if (isValid) { return { start, end, } } return null }) const isHighlightedStart = (date: DateValue) => { if (!highlightedRange.value || !highlightedRange.value.start) return false return isSameDay(highlightedRange.value.start, date) } const isHighlightedEnd = (date: DateValue) => { if (!highlightedRange.value || !highlightedRange.value.end) return false return isSameDay(highlightedRange.value.end, date) } const hasSelectedDate = computed(() => { return !!(props.start.value || props.end.value) }) const isStartDateDisabled = computed(() => { return !!(props.start.value && props.isDateDisabled(props.start.value)) }) const isEndDateDisabled = computed(() => { return !!(props.end.value && props.isDateDisabled(props.end.value)) }) const isSelectedDisabled = computed(() => { const hasStart = !!props.start.value const hasEnd = !!props.end.value if (!hasStart && !hasEnd) return false if (hasStart && hasEnd) return isStartDateDisabled.value && isEndDateDisabled.value return (hasStart && isStartDateDisabled.value) || (hasEnd && isEndDateDisabled.value) }) const selectedFocusableDate = computed(() => { if (props.start.value && !isStartDateDisabled.value) return props.start.value if (props.end.value && !isEndDateDisabled.value) return props.end.value return undefined }) return { isInvalid, isSelected, isDateHighlightable, highlightedRange, isSelectionStart, isSelectionEnd, isHighlightedStart, isHighlightedEnd, isDateDisabled: rangeIsDateDisabled, hasSelectedDate, isSelectedDisabled, selectedFocusableDate, } }