Skip to content

Commit

Permalink
feat(components): [date-picker] work with modal focus trap; a11y cont…
Browse files Browse the repository at this point in the history
…rols and attributes (element-plus#7598)

* feat(components): [date-picker] a11y controls and attributes

* feat(components): [date-picker] keyboard controls for picker

* feat(components): [date-picker] unit test complete

* feat(components): [date-picker] remove immediate watch date
  • Loading branch information
opengraphica authored May 10, 2022
1 parent 5a326ef commit 42ff59f
Show file tree
Hide file tree
Showing 30 changed files with 688 additions and 276 deletions.
33 changes: 18 additions & 15 deletions packages/components/date-picker/__tests__/date-picker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ describe('DatePicker', () => {
await nextTick()
const spans = document.querySelectorAll('.el-date-picker__header-label')
const arrowLeftElm = document.querySelector(
'.el-date-picker__prev-btn.arrow-left'
'.el-date-picker__prev-btn .arrow-left'
) as HTMLElement
const arrowRightElm = document.querySelector(
'.el-date-picker__next-btn.arrow-right'
'.el-date-picker__next-btn .arrow-right'
) as HTMLElement
expect(spans[0].textContent).toContain(date.year())
expect(spans[1].textContent).toContain(date.format('MMMM'))
const arrowLeftYeayElm = document.querySelector(
'.el-date-picker__prev-btn.d-arrow-left'
'.el-date-picker__prev-btn .d-arrow-left'
) as HTMLElement
arrowLeftYeayElm.click()
let count = 20
Expand Down Expand Up @@ -224,7 +224,7 @@ describe('DatePicker', () => {
const changeHandler = vi.fn()
const focusHandler = vi.fn()
const blurHandler = vi.fn()
let onChangeValue
let onChangeValue: Date | undefined
const wrapper = _mount(
`<el-date-picker
v-model="value"
Expand All @@ -250,16 +250,19 @@ describe('DatePicker', () => {
)

const input = wrapper.find('input')
input.trigger('blur')
input.trigger('focus')
input.trigger('blur')
await nextTick()
await rAF()
expect(focusHandler).toHaveBeenCalledTimes(1)
expect(blurHandler).toHaveBeenCalledTimes(1)
input.trigger('focus')
await nextTick()
;(document.querySelector('td.available') as HTMLElement).click()
await nextTick()
await nextTick() // onchange is triggered by props.modelValue update
await rAF()
expect(changeHandler).toHaveBeenCalledTimes(1)
expect(blurHandler).toHaveBeenCalledTimes(1)
expect(onChangeValue.getTime()).toBe(new Date(2016, 9, 1).getTime())
expect(onChangeValue?.getTime()).toBe(new Date(2016, 9, 1).getTime())
})

it('shortcuts', async () => {
Expand Down Expand Up @@ -603,10 +606,10 @@ describe('DatePicker Navigation', () => {
)[0]
;(yearLabel as HTMLElement).click()
await nextTick()
const year1999Label = document.querySelectorAll('.el-year-table td a')[1]
const year1999Label = document.querySelectorAll('.el-year-table td')[1]
;(year1999Label as HTMLElement).click()
await nextTick()
const juneLabel = document.querySelectorAll('.el-month-table td a')[5]
const juneLabel = document.querySelectorAll('.el-month-table td')[5]
;(juneLabel as HTMLElement).click()
await nextTick()
expect(getYearLabel()).toContain('2001')
Expand All @@ -616,7 +619,7 @@ describe('DatePicker Navigation', () => {
)[1]
;(monthLabel as HTMLElement).click()
await nextTick()
const janLabel = document.querySelectorAll('.el-month-table td a')[0]
const janLabel = document.querySelectorAll('.el-month-table td')[0]
;(janLabel as HTMLElement).click()
await nextTick()
expect(getYearLabel()).toContain('2001')
Expand Down Expand Up @@ -645,7 +648,7 @@ describe('MonthPicker', () => {
(document.querySelector('.el-month-table') as HTMLElement).style.display
).toBe('')
expect(document.querySelector('.el-year-table')).toBeNull()
;(document.querySelector('.el-month-table a.cell') as HTMLElement).click()
;(document.querySelector('.el-month-table .cell') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value.getMonth()).toBe(0)
Expand All @@ -670,7 +673,7 @@ describe('MonthPicker', () => {
input.trigger('focus')
await nextTick()
{
;(document.querySelector('.el-month-table a.cell') as HTMLElement).click()
;(document.querySelector('.el-month-table .cell') as HTMLElement).click()
}
await nextTick()
expect(wrapper.findComponent(Input).vm.modelValue).toBe('2020-01')
Expand Down Expand Up @@ -711,7 +714,7 @@ describe('YearPicker', () => {
}

await nextTick()
;(document.querySelector('.el-year-table a.cell') as HTMLElement).click()
;(document.querySelector('.el-year-table .cell') as HTMLElement).click()
await nextTick()
const vm = wrapper.vm as any
expect(vm.value.getFullYear()).toBe(2030)
Expand All @@ -735,7 +738,7 @@ describe('YearPicker', () => {
input.trigger('blur')
input.trigger('focus')
await nextTick()
const cell = document.querySelector('.el-year-table a.cell') as HTMLElement
const cell = document.querySelector('.el-year-table .cell') as HTMLElement
cell.click()
await nextTick()
expect((wrapper.vm as any).value).toBe(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
<template>
<table
role="grid"
:aria-label="t('el.datepicker.dateTablePrompt')"
cellspacing="0"
cellpadding="0"
class="el-date-table"
:class="{ 'is-week-mode': selectionMode === 'week' }"
@click="handleClick"
@click="handlePickDate"
@mousemove="handleMouseMove"
>
<tbody>
<tbody ref="tbodyRef">
<tr>
<th v-if="showWeekNumber">{{ t('el.datepicker.week') }}</th>
<th v-for="(week, key) in WEEKS" :key="key">
<th v-if="showWeekNumber" scope="col">{{ t('el.datepicker.week') }}</th>
<th
v-for="(week, key) in WEEKS"
:key="key"
scope="col"
:aria-label="t('el.datepicker.weeksFull.' + week)"
>
{{ t('el.datepicker.weeks.' + week) }}
</th>
</tr>
Expand All @@ -23,7 +30,12 @@
<td
v-for="(cell, key_) in row"
:key="key_"
:ref="(el) => isSelectedCell(cell) && (currentCellRef = el)"
:class="getCellClasses(cell)"
:aria-current="cell.isCurrent ? 'date' : undefined"
:aria-selected="`${cell.isCurrent}`"
:tabindex="isSelectedCell(cell) ? 0 : -1"
@focus="handleFocus"
>
<el-date-picker-cell :cell="cell" />
</td>
Expand All @@ -33,7 +45,7 @@
</template>

<script lang="ts">
import { computed, defineComponent, ref } from 'vue'
import { computed, defineComponent, nextTick, ref, watch } from 'vue'
import dayjs from 'dayjs'
import { useLocale } from '@element-plus/hooks'
import { castArray } from '@element-plus/utils'
Expand Down Expand Up @@ -62,7 +74,7 @@ export default defineComponent({
},
selectionMode: {
type: String,
default: 'day',
default: 'date',
},
showWeekNumber: {
type: Boolean,
Expand All @@ -83,9 +95,12 @@ export default defineComponent({
},
},
emits: ['changerange', 'pick', 'select'],
expose: ['focus'],
setup(props, ctx) {
const { t, lang } = useLocale()
const tbodyRef = ref<HTMLElement>()
const currentCellRef = ref<HTMLElement>()
// data
const lastRow = ref(null)
const lastColumn = ref(null)
Expand Down Expand Up @@ -116,6 +131,12 @@ export default defineComponent({
)
})
const hasCurrent = computed<boolean>(() => {
return rows.value.flat().some((row) => {
return row.isCurrent
})
})
const rows = computed(() => {
// TODO: refactory rows / getCellClasses
const startOfMonth = props.date.startOf('month')
Expand Down Expand Up @@ -244,9 +265,23 @@ export default defineComponent({
return rows_
})
watch(
() => props.date,
async () => {
if (tbodyRef.value?.contains(document.activeElement)) {
await nextTick()
currentCellRef.value?.focus()
}
}
)
const focus = async () => {
currentCellRef.value?.focus()
}
const isCurrent = (cell): boolean => {
return (
props.selectionMode === 'day' &&
props.selectionMode === 'date' &&
(cell.type === 'normal' || cell.type === 'today') &&
cellMatchesDate(cell, props.parsedValue)
)
Expand Down Expand Up @@ -342,20 +377,28 @@ export default defineComponent({
}
}
const handleClick = (event) => {
let target = event.target
const isSelectedCell = (cell: DateCell) => {
return (
(!hasCurrent.value && cell?.text === 1 && cell.type === 'normal') ||
cell.isCurrent
)
}
while (target) {
if (target.tagName === 'TD') {
break
}
target = target.parentNode
const handleFocus = (event: Event) => {
if (!hasCurrent.value && props.selectionMode === 'date') {
handlePickDate(event, true)
}
}
const handlePickDate = (event: Event, isKeyboardMovement = false) => {
let target = event.target as HTMLElement
target = target?.closest('td')
if (!target || target.tagName !== 'TD') return
const row = target.parentNode.rowIndex - 1
const column = target.cellIndex
const row = (target.parentNode as HTMLTableRowElement).rowIndex - 1
const column = (target as HTMLTableCellElement).cellIndex
const cell = rows.value[row][column]
if (cell.disabled || cell.type === 'week') return
Expand All @@ -374,8 +417,8 @@ export default defineComponent({
}
ctx.emit('select', false)
}
} else if (props.selectionMode === 'day') {
ctx.emit('pick', newDate)
} else if (props.selectionMode === 'date') {
ctx.emit('pick', newDate, isKeyboardMovement)
} else if (props.selectionMode === 'week') {
const weekNumber = newDate.week()
const value = `${newDate.year()}w${weekNumber}`
Expand Down Expand Up @@ -419,13 +462,19 @@ export default defineComponent({
}
return {
tbodyRef,
currentCellRef,
handleMouseMove,
t,
hasCurrent,
rows,
isSelectedCell,
isWeekActive,
getCellClasses,
WEEKS,
handleClick,
handleFocus,
handlePickDate,
focus,
}
},
})
Expand Down
Loading

0 comments on commit 42ff59f

Please sign in to comment.