Skip to content

fix(RangeCalendar): maximum days with fixed date #2015

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions packages/core/src/RangeCalendar/RangeCalendar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -726,8 +726,8 @@ describe('handles maximumDays', () => {
})

const startDay = getByTestId('date-1-15')
const maximumDay = getByTestId('date-1-20') // 5 days ahead
const beyondMaximumDay = getByTestId('date-1-21') // 6 days ahead
const maximumDay = getByTestId('date-1-19') // 5 days ahead
const beyondMaximumDay = getByTestId('date-1-20') // 6 days ahead

await user.click(startDay)
expect(startDay).toHaveAttribute('data-selection-start')
Expand All @@ -739,12 +739,12 @@ describe('handles maximumDays', () => {
expect(beyondMaximumDay).not.toHaveAttribute('data-selected')

// Should be limited to 5 days
expect(getByTestId('date-1-20')).not.toHaveAttribute('data-disabled')
expect(getByTestId('date-1-19')).not.toHaveAttribute('data-disabled')

await user.click(maximumDay)

// Check that all days within the maximum range are selected
for (let day = 15; day <= 20; day++) {
for (let day = 15; day < 20; day++) {
expect(getByTestId(`date-1-${day}`)).toHaveAttribute('data-selected')
}
})
Expand All @@ -771,10 +771,10 @@ describe('handles maximumDays', () => {
expect(getByTestId('date-1-14')).toHaveAttribute('aria-disabled', 'true')

// Days within the limit should not be disabled
expect(getByTestId('date-1-13')).not.toHaveAttribute('aria-disabled', 'true')
expect(getByTestId('date-1-12')).not.toHaveAttribute('aria-disabled', 'true')

// Complete the range selection with a date within the limit
await user.click(getByTestId('date-1-13'))
await user.click(getByTestId('date-1-12'))

// Distant dates should no longer be disabled
expect(getByTestId('date-1-20')).not.toHaveAttribute('aria-disabled', 'true')
Expand All @@ -784,7 +784,7 @@ describe('handles maximumDays', () => {
expect(getByTestId('date-1-15')).toHaveAttribute('data-selection-start')

// Days beyond the maximum limit from the new start date should be disabled
expect(getByTestId('date-1-19')).toHaveAttribute('aria-disabled', 'true')
expect(getByTestId('date-1-18')).not.toHaveAttribute('aria-disabled', 'true')
expect(getByTestId('date-1-18')).toHaveAttribute('aria-disabled', 'true')
expect(getByTestId('date-1-17')).not.toHaveAttribute('aria-disabled', 'true')
})
})
31 changes: 24 additions & 7 deletions packages/core/src/RangeCalendar/useRangeCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Ref } from 'vue'
import type { Matcher } from '@/date'
import { isSameDay } from '@internationalized/date'
import { computed } from 'vue'
import { areAllDaysBetweenValid, isBefore, isBetween } from '@/date'
import { areAllDaysBetweenValid, getDaysBetween, isBefore, isBetween } from '@/date'

export type UseRangeCalendarProps = {
start: Ref<DateValue | undefined>
Expand Down Expand Up @@ -76,12 +76,29 @@ export function useRangeCalendarState(props: UseRangeCalendarProps) {
if (props.isDateDisabled(date))
return true

if (!props.maximumDays?.value || !props.start.value || props.end.value || isSameDay(props.start.value, date))
return false

// Check if exceeds maximum days limit
if (Math.abs(date.compare(props.start.value)) > props.maximumDays.value)
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)
}
}

if (!props.start.value || props.end.value || isSameDay(props.start.value, date))
return false

return false
}
Expand Down Expand Up @@ -111,7 +128,7 @@ export function useRangeCalendarState(props: UseRangeCalendarProps) {

// If maximum days is set and the range exceeds it, limit the highlight
// We only apply this when we're in the middle of a selection (no end date yet)
if (props.maximumDays?.value && !props.end.value && Math.abs(end.compare(start)) > props.maximumDays.value) {
if (props.maximumDays?.value && !props.end.value) {
// Determine the direction of selection and limit to maximum days
const cappedEnd = isStartBeforeFocused
? start.add({ days: props.maximumDays.value })
Expand Down