Skip to content

Commit 9e8577f

Browse files
authored
fix(Calendar): change placeholder when focusing other dates (#2184)
* fix(Calendar): change placeholder when focusing other dates * fix(Calendar/RangeCalendar): prevent edge case when you could navigate to dates out of bounds
1 parent 28f5991 commit 9e8577f

File tree

5 files changed

+105
-2
lines changed

5 files changed

+105
-2
lines changed

packages/core/src/Calendar/Calendar.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,3 +899,64 @@ describe('calendar - edge cases', () => {
899899
expect(getByTestId('date-0-1-31')).toHaveFocus()
900900
})
901901
})
902+
903+
describe('calendar - tabindex states', () => {
904+
it('sets tabindex to 0 for focused date', async () => {
905+
const { getByTestId } = setup({
906+
calendarProps: {
907+
modelValue: calendarDate,
908+
},
909+
})
910+
911+
const focusedDay = getByTestId('date-1-20')
912+
expect(focusedDay).toHaveAttribute('tabindex', '0')
913+
})
914+
915+
it('sets tabindex to -1 for non-focused dates in current view', async () => {
916+
const { getByTestId } = setup({
917+
calendarProps: {
918+
modelValue: calendarDate,
919+
},
920+
})
921+
922+
const nonFocusedDay = getByTestId('date-1-15')
923+
expect(nonFocusedDay).toHaveAttribute('tabindex', '-1')
924+
})
925+
926+
it('sets tabindex to undefined for dates outside current view', async () => {
927+
const { getByTestId } = setup({
928+
calendarProps: {
929+
modelValue: calendarDate,
930+
},
931+
})
932+
933+
const outsideViewDay = getByTestId('date-12-31')
934+
expect(outsideViewDay).not.toHaveAttribute('tabindex')
935+
})
936+
937+
it('sets tabindex to undefined for disabled dates', async () => {
938+
const { getByTestId } = setup({
939+
calendarProps: {
940+
modelValue: calendarDate,
941+
isDateDisabled: (date: DateValue) => date.day === 15,
942+
},
943+
})
944+
945+
const disabledDay = getByTestId('date-1-15')
946+
expect(disabledDay).not.toHaveAttribute('tabindex')
947+
})
948+
949+
it('sets tabindex to undefined for dates outside visible view', async () => {
950+
const { getByTestId } = setup({
951+
calendarProps: {
952+
modelValue: calendarDate,
953+
numberOfMonths: 1,
954+
},
955+
})
956+
957+
// Dates outside visible view have data-outside-visible-view
958+
const outsideVisibleDay = getByTestId('date-0-12-30')
959+
expect(outsideVisibleDay).toHaveAttribute('data-outside-visible-view')
960+
expect(outsideVisibleDay).not.toHaveAttribute('tabindex')
961+
})
962+
})

packages/core/src/Calendar/CalendarCellTrigger.vue

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
isToday,
1010
} from '@internationalized/date'
1111
import { computed, nextTick } from 'vue'
12-
import { getDaysInMonth, toDate } from '@/date'
12+
import { getDaysInMonth, parseStringToDateValue, toDate } from '@/date'
1313
import { useKbd } from '@/shared'
1414
import { getSelectableCells } from './utils'
1515
@@ -137,9 +137,17 @@ function handleArrowKey(e: KeyboardEvent) {
137137
const newIndex = index + add
138138
139139
if (newIndex >= 0 && newIndex < allCollectionItems.length) {
140+
const newDate = allCollectionItems[newIndex].getAttribute('data-value')
141+
const newDateValue = parseStringToDateValue(newDate!, rootContext.placeholder.value)
142+
const minValue = rootContext.minValue.value
143+
const maxValue = rootContext.maxValue.value
144+
if ((minValue && newDateValue.compare(minValue) < 0) || (maxValue && newDateValue.compare(maxValue) > 0))
145+
return
146+
140147
if (allCollectionItems[newIndex].hasAttribute('data-disabled')) {
141148
shiftFocus(allCollectionItems[newIndex], add)
142149
}
150+
rootContext.onPlaceholderChange(newDateValue)
143151
allCollectionItems[newIndex].focus()
144152
return
145153
}
@@ -159,15 +167,20 @@ function handleArrowKey(e: KeyboardEvent) {
159167
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
160168
shiftFocus(newCollectionItems[computedIndex], add)
161169
}
170+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
162171
newCollectionItems[
163172
computedIndex
164173
].focus()
174+
175+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
165176
return
166177
}
167178
const computedIndex = newCollectionItems.length - Math.abs(newIndex)
168179
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
169180
shiftFocus(newCollectionItems[computedIndex], add)
170181
}
182+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
183+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
171184
newCollectionItems[
172185
computedIndex
173186
].focus()
@@ -195,6 +208,8 @@ function handleArrowKey(e: KeyboardEvent) {
195208
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
196209
shiftFocus(newCollectionItems[computedIndex], add)
197210
}
211+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
212+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
198213
newCollectionItems[computedIndex].focus()
199214
return
200215
}
@@ -204,6 +219,9 @@ function handleArrowKey(e: KeyboardEvent) {
204219
shiftFocus(newCollectionItems[computedIndex], add)
205220
}
206221
222+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
223+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
224+
207225
newCollectionItems[computedIndex].focus()
208226
})
209227
}

packages/core/src/Calendar/CalendarRoot.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type CalendarRootContext = {
4545
formatter: Formatter
4646
dir: Ref<Direction>
4747
disableDaysOutsideCurrentView: Ref<boolean>
48+
minValue: Ref<DateValue | undefined>
49+
maxValue: Ref<DateValue | undefined>
4850
}
4951
5052
export interface CalendarRootProps extends PrimitiveProps {
@@ -323,6 +325,8 @@ provideCalendarRootContext({
323325
onPlaceholderChange,
324326
onDateChange,
325327
disableDaysOutsideCurrentView,
328+
minValue,
329+
maxValue,
326330
})
327331
</script>
328332

packages/core/src/RangeCalendar/RangeCalendarCellTrigger.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@internationalized/date'
1111
import { computed, nextTick } from 'vue'
1212
import { getSelectableCells } from '@/Calendar/utils'
13-
import { getDaysInMonth, isBetweenInclusive, toDate } from '@/date'
13+
import { getDaysInMonth, isBetweenInclusive, parseStringToDateValue, toDate } from '@/date'
1414
import { useKbd } from '@/shared'
1515
1616
export interface RangeCalendarCellTriggerProps extends PrimitiveProps {
@@ -204,9 +204,17 @@ function handleArrowKey(e: KeyboardEvent) {
204204
const newIndex = index + add
205205
206206
if (newIndex >= 0 && newIndex < allCollectionItems.length) {
207+
const newDate = allCollectionItems[newIndex].getAttribute('data-value')
208+
const newDateValue = parseStringToDateValue(newDate!, rootContext.placeholder.value)
209+
const minValue = rootContext.minValue.value
210+
const maxValue = rootContext.maxValue.value
211+
if ((minValue && newDateValue.compare(minValue) < 0) || (maxValue && newDateValue.compare(maxValue) > 0))
212+
return
213+
207214
if (allCollectionItems[newIndex].hasAttribute('data-disabled')) {
208215
shiftFocus(allCollectionItems[newIndex], add)
209216
}
217+
rootContext.onPlaceholderChange(newDateValue)
210218
allCollectionItems[newIndex].focus()
211219
return
212220
}
@@ -226,6 +234,8 @@ function handleArrowKey(e: KeyboardEvent) {
226234
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
227235
shiftFocus(newCollectionItems[computedIndex], add)
228236
}
237+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
238+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
229239
newCollectionItems[
230240
computedIndex
231241
].focus()
@@ -235,6 +245,8 @@ function handleArrowKey(e: KeyboardEvent) {
235245
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
236246
shiftFocus(newCollectionItems[computedIndex], add)
237247
}
248+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
249+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
238250
newCollectionItems[
239251
computedIndex
240252
].focus()
@@ -262,6 +274,8 @@ function handleArrowKey(e: KeyboardEvent) {
262274
if (newCollectionItems[computedIndex].hasAttribute('data-disabled')) {
263275
shiftFocus(newCollectionItems[computedIndex], add)
264276
}
277+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
278+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
265279
newCollectionItems[computedIndex].focus()
266280
return
267281
}
@@ -271,6 +285,8 @@ function handleArrowKey(e: KeyboardEvent) {
271285
shiftFocus(newCollectionItems[computedIndex], add)
272286
}
273287
288+
const newDate = newCollectionItems[computedIndex].getAttribute('data-value')
289+
rootContext.onPlaceholderChange(parseStringToDateValue(newDate!, rootContext.placeholder.value))
274290
newCollectionItems[computedIndex].focus()
275291
})
276292
}

packages/core/src/RangeCalendar/RangeCalendarRoot.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ type RangeCalendarRootContext = {
6666
disableDaysOutsideCurrentView: Ref<boolean>
6767
fixedDate: Ref<'start' | 'end' | undefined>
6868
maximumDays: Ref<number | undefined>
69+
minValue: Ref<DateValue | undefined>
70+
maxValue: Ref<DateValue | undefined>
6971
}
7072
7173
export interface RangeCalendarRootProps extends PrimitiveProps {
@@ -435,6 +437,8 @@ provideRangeCalendarRootContext({
435437
disableDaysOutsideCurrentView,
436438
fixedDate,
437439
maximumDays,
440+
minValue,
441+
maxValue,
438442
})
439443
440444
onMounted(() => {

0 commit comments

Comments
 (0)