Skip to content

Commit 6c73d06

Browse files
feat(a11y): add keyboard focus for month and year
1 parent b484555 commit 6c73d06

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
@@ -33,18 +33,18 @@
3333
<td
3434
v-for="(cell, j) in row"
3535
:key="j"
36-
:ref="`month-cell-${cell.text}`"
36+
:ref="handleRefName(cell, i, j)"
3737
class="cell"
3838
role="button"
39-
tabindex="0"
39+
:disabled="isDisabled(cell)"
40+
:tabindex="handleTabIndex(cell)"
4041
:data-month="cell.month"
4142
:class="getCellClasses(cell.month)"
42-
@blur="onBlur(i, j)"
4343
@keydown.tab.prevent.stop
44-
@keydown.up.prevent="handleArrowUp(i, j)"
45-
@keydown.down.prevent="handleArrowDown(i, j)"
46-
@keydown.left.prevent="handleArrowLeft(i, j)"
47-
@keydown.right.prevent="handleArrowRight(i, j)"
44+
@keydown.up.prevent="handleArrowUp(cell, i, j)"
45+
@keydown.down.prevent="handleArrowDown(cell, i, j)"
46+
@keydown.left.prevent="handleArrowLeft(cell, i, j)"
47+
@keydown.right.prevent="handleArrowRight(cell, i, j)"
4848
>
4949
<div>{{ cell.text }}</div>
5050
</td>
@@ -58,7 +58,7 @@
5858
import { chunk } from '../util/base';
5959
import IconButton from './icon-button';
6060
import { getLocale } from '../locale';
61-
import { setYear } from '../util/date';
61+
import { createDate, setYear } from '../util/date';
6262
6363
export default {
6464
name: 'TableMonth',
@@ -84,6 +84,10 @@ export default {
8484
type: Function,
8585
default: () => [],
8686
},
87+
isDisabled: {
88+
type: Function,
89+
default: () => false,
90+
},
8791
},
8892
computed: {
8993
calendarYear() {
@@ -100,6 +104,12 @@ export default {
100104
locale() {
101105
return this.getLocale();
102106
},
107+
refsArray() {
108+
if (this.$refs) {
109+
return Object.entries(this.$refs);
110+
}
111+
return [];
112+
},
103113
},
104114
methods: {
105115
isDisabledArrows(type) {
@@ -117,68 +127,64 @@ export default {
117127
}
118128
return this.disabledCalendarChanger(date, type);
119129
},
120-
handleArrowUp(row, column) {
130+
handleArrowUp(cell, row, column) {
121131
if (row === 0) {
122132
return;
123133
}
124-
const month = this.months[row - 1][column];
125-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
134+
const refName = this.handleRefName(cell, row - 1, column);
135+
const ref = this.$refs[refName]?.[0];
126136
if (ref) {
127137
ref.focus();
128-
ref.classList.add('focus');
129138
}
130139
},
131-
handleArrowDown(row, column) {
140+
handleArrowDown(cell, row, column) {
132141
if (row === this.months.length - 1) {
133142
return;
134143
}
135-
const month = this.months[row + 1][column];
136-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
144+
const refName = this.handleRefName(cell, row + 1, column);
145+
const ref = this.$refs[refName]?.[0];
137146
if (ref) {
138147
ref.focus();
139-
ref.classList.add('focus');
140148
}
141149
},
142-
handleArrowLeft(row, column) {
143-
if (column <= 0) {
144-
if (row === 0 && column === 0) {
145-
this.handleIconDoubleLeftClick();
146-
const lastMonth = this.months[this.months.length - 1];
147-
const month = lastMonth[lastMonth.length - 1];
148-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
149-
if (ref) {
150-
ref.focus();
150+
handleArrowLeft(cell, row, column) {
151+
const currentRefName = this.handleRefName(cell, row, column);
152+
const firstRef = this.refsArray[0];
153+
if (currentRefName !== firstRef[0]) {
154+
const refName = this.handleRefName(cell, row, column - 1);
155+
const ref = this.$refs[refName]?.[0];
156+
if (ref) {
157+
ref.focus();
158+
}
159+
} else {
160+
this.handleIconDoubleLeftClick();
161+
const lastRef = this.refsArray[this.refsArray.length - 1];
162+
if (lastRef.length) {
163+
const element = lastRef[1];
164+
if (element.length) {
165+
element[0].focus();
151166
}
152167
}
153-
return;
154-
}
155-
const month = this.months[row][column - 1];
156-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
157-
if (ref) {
158-
ref.focus();
159-
ref.classList.add('focus');
160168
}
161169
},
162-
handleArrowRight(row, column) {
163-
if (column >= 2) {
164-
if (row === this.months.length - 1) {
165-
const lastRow = this.months[row];
166-
if (column === lastRow.length - 1) {
167-
this.handleIconDoubleRightClick();
168-
const month = this.months[0][0];
169-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
170-
if (ref) {
171-
ref.focus();
172-
}
170+
handleArrowRight(cell, row, column) {
171+
const currentRefName = this.handleRefName(cell, row, column);
172+
const lastRef = this.refsArray[this.refsArray.length - 1];
173+
if (currentRefName !== lastRef[0]) {
174+
const refName = this.handleRefName(cell, row, column + 1);
175+
const ref = this.$refs[refName]?.[0];
176+
if (ref) {
177+
ref.focus();
178+
}
179+
} else {
180+
this.handleIconDoubleRightClick();
181+
const firstRef = this.refsArray[0];
182+
if (firstRef.length) {
183+
const element = firstRef[1];
184+
if (element.length) {
185+
element[0].focus();
173186
}
174187
}
175-
return;
176-
}
177-
const month = this.months[row][column + 1];
178-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
179-
if (ref) {
180-
ref.focus();
181-
ref.classList.add('focus');
182188
}
183189
},
184190
handleIconDoubleLeftClick() {
@@ -208,22 +214,16 @@ export default {
208214
this.$emit('select', parseInt(month, 10));
209215
}
210216
},
211-
moveToFirstCell() {
212-
const month = this.months[0][0];
213-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
214-
if (ref) {
215-
setTimeout(() => {
216-
ref.focus();
217-
ref.classList.add('focus');
218-
}, 1);
217+
handleRefName(cellDate, row, col) {
218+
const date = createDate(cellDate, 0);
219+
if (!this.isDisabled(date)) {
220+
return `year-cell-${row}-${col}`;
219221
}
222+
return undefined;
220223
},
221-
onBlur(i, j) {
222-
const month = this.months[i][j];
223-
const ref = this.$refs[`month-cell-${month.text}`]?.[0];
224-
if (ref) {
225-
ref.classList.remove('focus');
226-
}
224+
handleTabIndex(cellDate) {
225+
const date = createDate(cellDate, 0);
226+
return this.isDisabled(date) ? -1 : 0;
227227
},
228228
},
229229
};

src/calendar/table-year.vue

Lines changed: 62 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,19 @@
2828
<tr v-for="(row, i) in years" :key="i">
2929
<td
3030
v-for="(cell, j) in row"
31-
:ref="handleRef(cell)"
31+
:ref="handleRefName(cell, i, j)"
3232
:key="j"
3333
aria-hidden="false"
3434
class="cell"
3535
role="button"
3636
:tabindex="handleTabIndex(cell)"
3737
:data-year="cell"
3838
:class="getCellClasses(cell)"
39-
@blur.prevent="onBlur(i, j)"
4039
@keydown.tab.prevent.stop
41-
@keydown.up.prevent="handleArrowUp(i, j)"
42-
@keydown.down.prevent="handleArrowDown(i, j)"
43-
@keydown.left.prevent="handleArrowLeft(i, j)"
44-
@keydown.right.prevent="handleArrowRight(i, j)"
40+
@keydown.up.prevent="handleArrowUp(cell, i, j)"
41+
@keydown.down.prevent="handleArrowDown(cell, i, j)"
42+
@keydown.left.prevent="handleArrowLeft(cell, i, j)"
43+
@keydown.right.prevent="handleArrowRight(cell, i, j)"
4544
>
4645
<div>{{ cell }}</div>
4746
</td>
@@ -54,7 +53,7 @@
5453
<script>
5554
import IconButton from './icon-button';
5655
import { chunk } from '../util/base';
57-
import { setYear } from '../util/date';
56+
import { createDate, setYear } from '../util/date';
5857
import { getLocale } from '../locale';
5958
6059
export default {
@@ -84,6 +83,10 @@ export default {
8483
getYearPanel: {
8584
type: Function,
8685
},
86+
isDisabled: {
87+
type: Function,
88+
default: () => false,
89+
},
8790
},
8891
computed: {
8992
years() {
@@ -103,6 +106,12 @@ export default {
103106
locale() {
104107
return this.getLocale();
105108
},
109+
refsArray() {
110+
if (this.$refs) {
111+
return Object.entries(this.$refs);
112+
}
113+
return [];
114+
},
106115
},
107116
methods: {
108117
isDisabledArrows(type) {
@@ -128,66 +137,64 @@ export default {
128137
}
129138
return chunk(years, 2);
130139
},
131-
handleArrowUp(row, column) {
140+
handleArrowUp(cell, row, column) {
132141
if (row === 0) {
133142
return;
134143
}
135-
const year = this.years[row - 1][column];
136-
const ref = this.$refs[`year-cell-${year}`]?.[0];
144+
const refName = this.handleRefName(cell, row - 1, column);
145+
const ref = this.$refs[refName]?.[0];
137146
if (ref) {
138147
ref.focus();
139-
ref.classList.add('focus');
140148
}
141149
},
142-
handleArrowDown(row, column) {
150+
handleArrowDown(cell, row, column) {
143151
if (row === this.years.length - 1) {
144152
return;
145153
}
146-
const year = this.years[row + 1][column];
147-
const ref = this.$refs[`year-cell-${year}`]?.[0];
154+
const refName = this.handleRefName(cell, row + 1, column);
155+
const ref = this.$refs[refName]?.[0];
148156
if (ref) {
149157
ref.focus();
150-
ref.classList.add('focus');
151158
}
152159
},
153-
handleArrowLeft(row, column) {
154-
if (column % 2 === 0) {
155-
if (row === 0 && column === 0) {
156-
this.handleIconDoubleLeftClick();
157-
const ref = this.$refs[`year-cell-${this.lastYear}`]?.[0];
158-
if (ref) {
159-
ref.focus();
160+
handleArrowLeft(cell, row, column) {
161+
const currentRefName = this.handleRefName(cell, row, column);
162+
const firstRef = this.refsArray[0];
163+
if (currentRefName !== firstRef[0]) {
164+
const refName = this.handleRefName(cell, row, column - 1);
165+
const ref = this.$refs[refName]?.[0];
166+
if (ref) {
167+
ref.focus();
168+
}
169+
} else {
170+
this.handleIconDoubleLeftClick();
171+
const lastRef = this.refsArray[this.refsArray.length - 1];
172+
if (lastRef.length) {
173+
const element = lastRef[1];
174+
if (element.length) {
175+
element[0].focus();
160176
}
161177
}
162-
return;
163-
}
164-
const year = this.years[row][column - 1];
165-
const ref = this.$refs[`year-cell-${year}`]?.[0];
166-
if (ref) {
167-
ref.focus();
168-
ref.classList.add('focus');
169178
}
170179
},
171-
handleArrowRight(row, column) {
172-
if (column % 2 === 1) {
173-
if (row === this.years.length - 1) {
174-
const lastRow = this.years[row];
175-
if (column === lastRow.length - 1) {
176-
this.handleIconDoubleRightClick();
177-
const year = this.years[0][0];
178-
const ref = this.$refs[`year-cell-${year}`]?.[0];
179-
if (ref) {
180-
ref.focus();
181-
}
180+
handleArrowRight(cell, row, column) {
181+
const currentRefName = this.handleRefName(cell, row, column);
182+
const lastRef = this.refsArray[this.refsArray.length - 1];
183+
if (currentRefName !== lastRef[0]) {
184+
const refName = this.handleRefName(cell, row, column + 1);
185+
const ref = this.$refs[refName]?.[0];
186+
if (ref) {
187+
ref.focus();
188+
}
189+
} else {
190+
this.handleIconDoubleRightClick();
191+
const firstRef = this.refsArray[0];
192+
if (firstRef.length) {
193+
const element = firstRef[1];
194+
if (element.length) {
195+
element[0].focus();
182196
}
183197
}
184-
return;
185-
}
186-
const year = this.years[row][column + 1];
187-
const ref = this.$refs[`year-cell-${year}`]?.[0];
188-
if (ref) {
189-
ref.focus();
190-
ref.classList.add('focus');
191198
}
192199
},
193200
handleIconDoubleLeftClick() {
@@ -215,28 +222,16 @@ export default {
215222
this.selectedYear = parseInt(year, 10);
216223
}
217224
},
218-
handleRef(cellDate) {
219-
return this.disabledCalendarChanger(cellDate, 'year') ? undefined : `year-cell-${cellDate}`;
220-
},
221-
handleTabIndex(cellDate) {
222-
return this.disabledCalendarChanger(cellDate, 'year') ? -1 : 0;
223-
},
224-
moveToFirstCell() {
225-
const year = this.years[0][0];
226-
const ref = this.$refs[`year-cell-${year}`]?.[0];
227-
if (ref) {
228-
setTimeout(() => {
229-
ref.focus();
230-
ref.classList.add('focus');
231-
}, 1);
225+
handleRefName(cellDate, row, col) {
226+
const date = createDate(cellDate, 0);
227+
if (!this.isDisabled(date)) {
228+
return `year-cell-${row}-${col}`;
232229
}
230+
return undefined;
233231
},
234-
onBlur(i, j) {
235-
const year = this.years[i][j];
236-
const ref = this.$refs[`year-cell-${year}`]?.[0];
237-
if (ref) {
238-
ref.classList.remove('focus');
239-
}
232+
handleTabIndex(cellDate) {
233+
const date = createDate(cellDate, 0);
234+
return this.isDisabled(date) ? -1 : 0;
240235
},
241236
},
242237
};

0 commit comments

Comments
 (0)