Skip to content

Commit 458f9fd

Browse files
feat(a11y): add keyboard focus for month and year
1 parent 69b8355 commit 458f9fd

File tree

2 files changed

+126
-131
lines changed

2 files changed

+126
-131
lines changed

src/calendar/table-month.vue

Lines changed: 64 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,18 @@
3131
<td
3232
v-for="(cell, j) in row"
3333
:key="j"
34-
:ref="`month-cell-${cell.text}`"
34+
:ref="handleRefName(cell, i, j)"
3535
class="cell"
3636
role="button"
37-
tabindex="0"
37+
:disabled="isDisabled(cell)"
38+
:tabindex="handleTabIndex(cell)"
3839
:data-month="cell.month"
3940
:class="getCellClasses(cell.month)"
40-
@blur="onBlur(i, j)"
4141
@keydown.tab.prevent.stop
42-
@keydown.up.prevent="handleArrowUp(i, j)"
43-
@keydown.down.prevent="handleArrowDown(i, j)"
44-
@keydown.left.prevent="handleArrowLeft(i, j)"
45-
@keydown.right.prevent="handleArrowRight(i, j)"
42+
@keydown.up.prevent="handleArrowUp(cell, i, j)"
43+
@keydown.down.prevent="handleArrowDown(cell, i, j)"
44+
@keydown.left.prevent="handleArrowLeft(cell, i, j)"
45+
@keydown.right.prevent="handleArrowRight(cell, i, j)"
4646
>
4747
<div>{{ cell.text }}</div>
4848
</td>
@@ -56,7 +56,7 @@
5656
import { chunk } from '../util/base';
5757
import IconButton from './icon-button';
5858
import { getLocale } from '../locale';
59-
import { setYear } from '../util/date';
59+
import { createDate, setYear } from '../util/date';
6060
6161
export default {
6262
name: 'TableMonth',
@@ -82,6 +82,10 @@ export default {
8282
type: Function,
8383
default: () => [],
8484
},
85+
isDisabled: {
86+
type: Function,
87+
default: () => false,
88+
},
8589
},
8690
computed: {
8791
calendarYear() {
@@ -98,6 +102,12 @@ export default {
98102
locale() {
99103
return this.getLocale();
100104
},
105+
refsArray() {
106+
if (this.$refs) {
107+
return Object.entries(this.$refs);
108+
}
109+
return [];
110+
},
101111
},
102112
methods: {
103113
isDisabledArrows(type) {
@@ -115,68 +125,64 @@ export default {
115125
}
116126
return this.disabledCalendarChanger(date, type);
117127
},
118-
handleArrowUp(row, column) {
128+
handleArrowUp(cell, row, column) {
119129
if (row === 0) {
120130
return;
121131
}
122-
const month = this.months[row - 1][column];
123-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
132+
const refName = this.handleRefName(cell, row - 1, column);
133+
const ref = this.$refs[refName]?.[0];
124134
if (ref) {
125135
ref.focus();
126-
ref.classList.add('focus');
127136
}
128137
},
129-
handleArrowDown(row, column) {
138+
handleArrowDown(cell, row, column) {
130139
if (row === this.months.length - 1) {
131140
return;
132141
}
133-
const month = this.months[row + 1][column];
134-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
142+
const refName = this.handleRefName(cell, row + 1, column);
143+
const ref = this.$refs[refName]?.[0];
135144
if (ref) {
136145
ref.focus();
137-
ref.classList.add('focus');
138146
}
139147
},
140-
handleArrowLeft(row, column) {
141-
if (column <= 0) {
142-
if (row === 0 && column === 0) {
143-
this.handleIconDoubleLeftClick();
144-
const lastMonth = this.months[this.months.length - 1];
145-
const month = lastMonth[lastMonth.length - 1];
146-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
147-
if (ref) {
148-
ref.focus();
148+
handleArrowLeft(cell, row, column) {
149+
const currentRefName = this.handleRefName(cell, row, column);
150+
const firstRef = this.refsArray[0];
151+
if (currentRefName !== firstRef[0]) {
152+
const refName = this.handleRefName(cell, row, column - 1);
153+
const ref = this.$refs[refName]?.[0];
154+
if (ref) {
155+
ref.focus();
156+
}
157+
} else {
158+
this.handleIconDoubleLeftClick();
159+
const lastRef = this.refsArray[this.refsArray.length - 1];
160+
if (lastRef.length) {
161+
const element = lastRef[1];
162+
if (element.length) {
163+
element[0].focus();
149164
}
150165
}
151-
return;
152-
}
153-
const month = this.months[row][column - 1];
154-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
155-
if (ref) {
156-
ref.focus();
157-
ref.classList.add('focus');
158166
}
159167
},
160-
handleArrowRight(row, column) {
161-
if (column >= 2) {
162-
if (row === this.months.length - 1) {
163-
const lastRow = this.months[row];
164-
if (column === lastRow.length - 1) {
165-
this.handleIconDoubleRightClick();
166-
const month = this.months[0][0];
167-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
168-
if (ref) {
169-
ref.focus();
170-
}
168+
handleArrowRight(cell, row, column) {
169+
const currentRefName = this.handleRefName(cell, row, column);
170+
const lastRef = this.refsArray[this.refsArray.length - 1];
171+
if (currentRefName !== lastRef[0]) {
172+
const refName = this.handleRefName(cell, row, column + 1);
173+
const ref = this.$refs[refName]?.[0];
174+
if (ref) {
175+
ref.focus();
176+
}
177+
} else {
178+
this.handleIconDoubleRightClick();
179+
const firstRef = this.refsArray[0];
180+
if (firstRef.length) {
181+
const element = firstRef[1];
182+
if (element.length) {
183+
element[0].focus();
171184
}
172185
}
173-
return;
174-
}
175-
const month = this.months[row][column + 1];
176-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
177-
if (ref) {
178-
ref.focus();
179-
ref.classList.add('focus');
180186
}
181187
},
182188
handleIconDoubleLeftClick() {
@@ -206,22 +212,16 @@ export default {
206212
this.$emit('select', parseInt(month, 10));
207213
}
208214
},
209-
moveToFirstCell() {
210-
const month = this.months[0][0];
211-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
212-
if (ref) {
213-
setTimeout(() => {
214-
ref.focus();
215-
ref.classList.add('focus');
216-
}, 1);
215+
handleRefName(cellDate, row, col) {
216+
const date = createDate(cellDate, 0);
217+
if (!this.isDisabled(date)) {
218+
return `year-cell-${row}-${col}`;
217219
}
220+
return undefined;
218221
},
219-
onBlur(i, j) {
220-
const month = this.months[i][j];
221-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
222-
if (ref) {
223-
ref.classList.remove('focus');
224-
}
222+
handleTabIndex(cellDate) {
223+
const date = createDate(cellDate, 0);
224+
return this.isDisabled(date) ? -1 : 0;
225225
},
226226
},
227227
};

src/calendar/table-year.vue

Lines changed: 62 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,19 @@
2626
<tr v-for="(row, i) in years" :key="i">
2727
<td
2828
v-for="(cell, j) in row"
29-
:ref="handleRef(cell)"
29+
:ref="handleRefName(cell, i, j)"
3030
:key="j"
3131
aria-hidden="false"
3232
class="cell"
3333
role="button"
3434
:tabindex="handleTabIndex(cell)"
3535
:data-year="cell"
3636
:class="getCellClasses(cell)"
37-
@blur.prevent="onBlur(i, j)"
3837
@keydown.tab.prevent.stop
39-
@keydown.up.prevent="handleArrowUp(i, j)"
40-
@keydown.down.prevent="handleArrowDown(i, j)"
41-
@keydown.left.prevent="handleArrowLeft(i, j)"
42-
@keydown.right.prevent="handleArrowRight(i, j)"
38+
@keydown.up.prevent="handleArrowUp(cell, i, j)"
39+
@keydown.down.prevent="handleArrowDown(cell, i, j)"
40+
@keydown.left.prevent="handleArrowLeft(cell, i, j)"
41+
@keydown.right.prevent="handleArrowRight(cell, i, j)"
4342
>
4443
<div>{{ cell }}</div>
4544
</td>
@@ -52,7 +51,7 @@
5251
<script>
5352
import IconButton from './icon-button';
5453
import { chunk } from '../util/base';
55-
import { setYear } from '../util/date';
54+
import { createDate, setYear } from '../util/date';
5655
import { getLocale } from '../locale';
5756
5857
export default {
@@ -82,6 +81,10 @@ export default {
8281
getYearPanel: {
8382
type: Function,
8483
},
84+
isDisabled: {
85+
type: Function,
86+
default: () => false,
87+
},
8588
},
8689
computed: {
8790
years() {
@@ -101,6 +104,12 @@ export default {
101104
locale() {
102105
return this.getLocale();
103106
},
107+
refsArray() {
108+
if (this.$refs) {
109+
return Object.entries(this.$refs);
110+
}
111+
return [];
112+
},
104113
},
105114
methods: {
106115
isDisabledArrows(type) {
@@ -126,66 +135,64 @@ export default {
126135
}
127136
return chunk(years, 2);
128137
},
129-
handleArrowUp(row, column) {
138+
handleArrowUp(cell, row, column) {
130139
if (row === 0) {
131140
return;
132141
}
133-
const year = this.years[row - 1][column];
134-
const ref = this.$refs[`year-cell-${year}`]?.[0];
142+
const refName = this.handleRefName(cell, row - 1, column);
143+
const ref = this.$refs[refName]?.[0];
135144
if (ref) {
136145
ref.focus();
137-
ref.classList.add('focus');
138146
}
139147
},
140-
handleArrowDown(row, column) {
148+
handleArrowDown(cell, row, column) {
141149
if (row === this.years.length - 1) {
142150
return;
143151
}
144-
const year = this.years[row + 1][column];
145-
const ref = this.$refs[`year-cell-${year}`]?.[0];
152+
const refName = this.handleRefName(cell, row + 1, column);
153+
const ref = this.$refs[refName]?.[0];
146154
if (ref) {
147155
ref.focus();
148-
ref.classList.add('focus');
149156
}
150157
},
151-
handleArrowLeft(row, column) {
152-
if (column % 2 === 0) {
153-
if (row === 0 && column === 0) {
154-
this.handleIconDoubleLeftClick();
155-
const ref = this.$refs[`year-cell-${this.lastYear}`]?.[0];
156-
if (ref) {
157-
ref.focus();
158+
handleArrowLeft(cell, row, column) {
159+
const currentRefName = this.handleRefName(cell, row, column);
160+
const firstRef = this.refsArray[0];
161+
if (currentRefName !== firstRef[0]) {
162+
const refName = this.handleRefName(cell, row, column - 1);
163+
const ref = this.$refs[refName]?.[0];
164+
if (ref) {
165+
ref.focus();
166+
}
167+
} else {
168+
this.handleIconDoubleLeftClick();
169+
const lastRef = this.refsArray[this.refsArray.length - 1];
170+
if (lastRef.length) {
171+
const element = lastRef[1];
172+
if (element.length) {
173+
element[0].focus();
158174
}
159175
}
160-
return;
161-
}
162-
const year = this.years[row][column - 1];
163-
const ref = this.$refs[`year-cell-${year}`]?.[0];
164-
if (ref) {
165-
ref.focus();
166-
ref.classList.add('focus');
167176
}
168177
},
169-
handleArrowRight(row, column) {
170-
if (column % 2 === 1) {
171-
if (row === this.years.length - 1) {
172-
const lastRow = this.years[row];
173-
if (column === lastRow.length - 1) {
174-
this.handleIconDoubleRightClick();
175-
const year = this.years[0][0];
176-
const ref = this.$refs[`year-cell-${year}`]?.[0];
177-
if (ref) {
178-
ref.focus();
179-
}
178+
handleArrowRight(cell, row, column) {
179+
const currentRefName = this.handleRefName(cell, row, column);
180+
const lastRef = this.refsArray[this.refsArray.length - 1];
181+
if (currentRefName !== lastRef[0]) {
182+
const refName = this.handleRefName(cell, row, column + 1);
183+
const ref = this.$refs[refName]?.[0];
184+
if (ref) {
185+
ref.focus();
186+
}
187+
} else {
188+
this.handleIconDoubleRightClick();
189+
const firstRef = this.refsArray[0];
190+
if (firstRef.length) {
191+
const element = firstRef[1];
192+
if (element.length) {
193+
element[0].focus();
180194
}
181195
}
182-
return;
183-
}
184-
const year = this.years[row][column + 1];
185-
const ref = this.$refs[`year-cell-${year}`]?.[0];
186-
if (ref) {
187-
ref.focus();
188-
ref.classList.add('focus');
189196
}
190197
},
191198
handleIconDoubleLeftClick() {
@@ -213,28 +220,16 @@ export default {
213220
this.selectedYear = parseInt(year, 10);
214221
}
215222
},
216-
handleRef(cellDate) {
217-
return this.disabledCalendarChanger(cellDate, 'year') ? undefined : `year-cell-${cellDate}`;
218-
},
219-
handleTabIndex(cellDate) {
220-
return this.disabledCalendarChanger(cellDate, 'year') ? -1 : 0;
221-
},
222-
moveToFirstCell() {
223-
const year = this.years[0][0];
224-
const ref = this.$refs[`year-cell-${year}`]?.[0];
225-
if (ref) {
226-
setTimeout(() => {
227-
ref.focus();
228-
ref.classList.add('focus');
229-
}, 1);
223+
handleRefName(cellDate, row, col) {
224+
const date = createDate(cellDate, 0);
225+
if (!this.isDisabled(date)) {
226+
return `year-cell-${row}-${col}`;
230227
}
228+
return undefined;
231229
},
232-
onBlur(i, j) {
233-
const year = this.years[i][j];
234-
const ref = this.$refs[`year-cell-${year}`]?.[0];
235-
if (ref) {
236-
ref.classList.remove('focus');
237-
}
230+
handleTabIndex(cellDate) {
231+
const date = createDate(cellDate, 0);
232+
return this.isDisabled(date) ? -1 : 0;
238233
},
239234
},
240235
};

0 commit comments

Comments
 (0)