Skip to content
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
1 change: 1 addition & 0 deletions packages/core/src/Calendar/Calendar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@ describe('calendar - edge cases', () => {
expect(heading).toHaveTextContent('January - April 2025')

await user.keyboard(kbd.ARROW_RIGHT)
expect(getByTestId('heading')).toHaveTextContent('February - May 2025')
expect(getByTestId('date-3-5-1')).toHaveFocus()

const firstDayOfMonth = getByTestId('date-0-2-1')
Expand Down
118 changes: 25 additions & 93 deletions packages/core/src/Calendar/CalendarCellTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import {
isToday,
} from '@internationalized/date'
import { computed, nextTick } from 'vue'
import { getDaysInMonth, parseStringToDateValue, toDate } from '@/date'
import { toDate } from '@/date'
import { useKbd } from '@/shared'
import { getSelectableCells } from './utils'

export interface CalendarCellTriggerProps extends PrimitiveProps {
/** The date value provided to the cell trigger */
Expand Down Expand Up @@ -112,119 +111,52 @@ function handleArrowKey(e: KeyboardEvent) {
const sign = rootContext.dir.value === 'rtl' ? -1 : 1
switch (e.code) {
case kbd.ARROW_RIGHT:
shiftFocus(currentElement.value, sign)
shiftFocus(props.day, sign)
break
case kbd.ARROW_LEFT:
shiftFocus(currentElement.value, -sign)
shiftFocus(props.day, -sign)
break
case kbd.ARROW_UP:
shiftFocus(currentElement.value, -indexIncrementation)
shiftFocus(props.day, -indexIncrementation)
break
case kbd.ARROW_DOWN:
shiftFocus(currentElement.value, indexIncrementation)
shiftFocus(props.day, indexIncrementation)
break
case kbd.ENTER:
case kbd.SPACE_CODE:
changeDate(props.day)
}

function shiftFocus(node: HTMLElement, add: number) {
const allCollectionItems: HTMLElement[] = getSelectableCells(parentElement)
if (!allCollectionItems.length)
return

const index = allCollectionItems.indexOf(node)
const newIndex = index + add

if (newIndex >= 0 && newIndex < allCollectionItems.length) {
const newDate = allCollectionItems[newIndex].getAttribute('data-value')
const newDateValue = parseStringToDateValue(newDate!, rootContext.placeholder.value)
const minValue = rootContext.minValue.value
const maxValue = rootContext.maxValue.value
if ((minValue && newDateValue.compare(minValue) < 0) || (maxValue && newDateValue.compare(maxValue) > 0))
return
function shiftFocus(day: DateValue, add: number) {
const candidateDayValue = day.add({ days: add })

if (allCollectionItems[newIndex].hasAttribute('data-disabled')) {
shiftFocus(allCollectionItems[newIndex], add)
}
rootContext.onPlaceholderChange(newDateValue)
allCollectionItems[newIndex].focus()
if ((rootContext.minValue.value && candidateDayValue.compare(rootContext.minValue.value) < 0) || (rootContext.maxValue.value && candidateDayValue.compare(rootContext.maxValue.value) > 0))
return
}

if (newIndex < 0) {
if (rootContext.isPrevButtonDisabled())
return
rootContext.prevPage()
nextTick(() => {
const newCollectionItems: HTMLElement[] = getSelectableCells(parentElement)
if (!newCollectionItems.length)
const candidateDay = parentElement.querySelector<HTMLElement>(`[data-value='${candidateDayValue.toString()}']:not([data-outside-view])`)
// If the date is not found it means we must change the page
if (!candidateDay) {
if (add > 0) {
if (rootContext.isNextButtonDisabled())
return
if (!rootContext.pagedNavigation.value && rootContext.numberOfMonths.value > 1) {
// Placeholder is set to first month of the new page
const numberOfDays = getDaysInMonth(rootContext.placeholder.value)
const computedIndex = numberOfDays - Math.abs(newIndex)
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
newCollectionItems[
computedIndex
].focus()

rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
rootContext.nextPage()
}
else {
if (rootContext.isPrevButtonDisabled())
return
}
const computedIndex = newCollectionItems.length - Math.abs(newIndex)
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
newCollectionItems[
computedIndex
].focus()
rootContext.prevPage()
}
nextTick(() => {
shiftFocus(day, add)
})
return
}

if (newIndex >= allCollectionItems.length) {
if (rootContext.isNextButtonDisabled())
return
rootContext.nextPage()
nextTick(() => {
const newCollectionItems: HTMLElement[] = getSelectableCells(parentElement)
if (!newCollectionItems.length)
return

if (!rootContext.pagedNavigation.value && rootContext.numberOfMonths.value > 1) {
// Placeholder is set to first month of the new page
const numberOfDays = getDaysInMonth(
rootContext.placeholder.value.add({ months: rootContext.numberOfMonths.value - 1 }),
)

const computedIndex = newIndex - allCollectionItems.length + (newCollectionItems.length - numberOfDays)

if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
newCollectionItems[computedIndex].focus()
return
}

const computedIndex = newIndex - allCollectionItems.length
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}

const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))

newCollectionItems[computedIndex].focus()
})
if (candidateDay && candidateDay.hasAttribute('data-disabled')) {
return shiftFocus(candidateDayValue, add)
}
rootContext.onPlaceholderChange(candidateDayValue)
candidateDay?.focus()
}
}
</script>
Expand Down
5 changes: 0 additions & 5 deletions packages/core/src/Calendar/utils.ts

This file was deleted.

116 changes: 25 additions & 91 deletions packages/core/src/RangeCalendar/RangeCalendarCellTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
isToday,
} from '@internationalized/date'
import { computed, nextTick } from 'vue'
import { getSelectableCells } from '@/Calendar/utils'
import { getDaysInMonth, isBetweenInclusive, parseStringToDateValue, toDate } from '@/date'
import { isBetweenInclusive, toDate } from '@/date'
import { useKbd } from '@/shared'

export interface RangeCalendarCellTriggerProps extends PrimitiveProps {
Expand Down Expand Up @@ -179,117 +178,52 @@ function handleArrowKey(e: KeyboardEvent) {
const sign = rootContext.dir.value === 'rtl' ? -1 : 1
switch (e.code) {
case kbd.ARROW_RIGHT:
shiftFocus(currentElement.value, sign)
shiftFocus(props.day, sign)
break
case kbd.ARROW_LEFT:
shiftFocus(currentElement.value, -sign)
shiftFocus(props.day, -sign)
break
case kbd.ARROW_UP:
shiftFocus(currentElement.value, -indexIncrementation)
shiftFocus(props.day, -indexIncrementation)
break
case kbd.ARROW_DOWN:
shiftFocus(currentElement.value, indexIncrementation)
shiftFocus(props.day, indexIncrementation)
break
case kbd.ENTER:
case kbd.SPACE_CODE:
changeDate(e, props.day)
}

function shiftFocus(node: HTMLElement, add: number) {
const allCollectionItems: HTMLElement[] = getSelectableCells(parentElement)
if (!allCollectionItems.length)
return

const index = allCollectionItems.indexOf(node)
const newIndex = index + add

if (newIndex >= 0 && newIndex < allCollectionItems.length) {
const newDate = allCollectionItems[newIndex].getAttribute('data-value')
const newDateValue = parseStringToDateValue(newDate!, rootContext.placeholder.value)
const minValue = rootContext.minValue.value
const maxValue = rootContext.maxValue.value
if ((minValue && newDateValue.compare(minValue) < 0) || (maxValue && newDateValue.compare(maxValue) > 0))
return
function shiftFocus(day: DateValue, add: number) {
const candidateDayValue = day.add({ days: add })

if (allCollectionItems[newIndex].hasAttribute('data-disabled')) {
shiftFocus(allCollectionItems[newIndex], add)
}
rootContext.onPlaceholderChange(newDateValue)
allCollectionItems[newIndex].focus()
if ((rootContext.minValue.value && candidateDayValue.compare(rootContext.minValue.value) < 0) || (rootContext.maxValue.value && candidateDayValue.compare(rootContext.maxValue.value) > 0))
return
}

if (newIndex < 0) {
if (rootContext.isPrevButtonDisabled())
return
rootContext.prevPage()
nextTick(() => {
const newCollectionItems: HTMLElement[] = getSelectableCells(parentElement)
if (!newCollectionItems.length)
const candidateDay = parentElement.querySelector<HTMLElement>(`[data-value='${candidateDayValue.toString()}']:not([data-outside-view])`)
// If the date is not found it means we must change the page
if (!candidateDay) {
if (add > 0) {
if (rootContext.isNextButtonDisabled())
return
if (!rootContext.pagedNavigation.value && rootContext.numberOfMonths.value > 1) {
// Placeholder is set to first month of the new page
const numberOfDays = getDaysInMonth(rootContext.placeholder.value)
const computedIndex = numberOfDays - Math.abs(newIndex)
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
newCollectionItems[
computedIndex
].focus()
rootContext.nextPage()
}
else {
if (rootContext.isPrevButtonDisabled())
return
}
const computedIndex = newCollectionItems.length - Math.abs(newIndex)
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
newCollectionItems[
computedIndex
].focus()
rootContext.prevPage()
}
nextTick(() => {
shiftFocus(day, add)
})
return
}

if (newIndex >= allCollectionItems.length) {
if (rootContext.isNextButtonDisabled())
return
rootContext.nextPage()
nextTick(() => {
const newCollectionItems: HTMLElement[] = getSelectableCells(parentElement)
if (!newCollectionItems.length)
return

if (!rootContext.pagedNavigation.value && rootContext.numberOfMonths.value > 1) {
// Placeholder is set to first month of the new page
const numberOfDays = getDaysInMonth(
rootContext.placeholder.value.add({ months: rootContext.numberOfMonths.value - 1 }),
)

const computedIndex = newIndex - allCollectionItems.length + (newCollectionItems.length - numberOfDays)

if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
newCollectionItems[computedIndex].focus()
return
}

const computedIndex = newIndex - allCollectionItems.length
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
shiftFocus(newCollectionItems[computedIndex], add)
}

const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
newCollectionItems[computedIndex].focus()
})
if (candidateDay && candidateDay.hasAttribute('data-disabled')) {
return shiftFocus(candidateDayValue, add)
}
rootContext.onPlaceholderChange(candidateDayValue)
candidateDay?.focus()
}
}
</script>
Expand Down
Loading