@@ -7,6 +7,7 @@ export default {
7
7
delay: 0 ,
8
8
refs: [],
9
9
},
10
+ inlineTabbableCell: null ,
10
11
isActive: false ,
11
12
isRevertingToOpenDate: false ,
12
13
navElements: [],
@@ -47,6 +48,17 @@ export default {
47
48
},
48
49
},
49
50
methods: {
51
+ /**
52
+ * Returns true, unless tabbing should be focus-trapped
53
+ * @return {Boolean}
54
+ */
55
+ allowNormalTabbing (event ) {
56
+ if (! this .isOpen ) {
57
+ return true
58
+ }
59
+
60
+ return this .isTabbingAwayFromInlineDatepicker (event )
61
+ },
50
62
/**
51
63
* Focuses the first non-disabled element found in the `focus.refs` array and sets `navElementsFocusedIndex`
52
64
*/
@@ -63,6 +75,22 @@ export default {
63
75
}
64
76
}
65
77
},
78
+ /**
79
+ * Ensures the most recently focused tabbable cell is focused when tabbing backwards to an inline calendar
80
+ * If no element has previously been focused, the tabbable cell is reset and focused
81
+ */
82
+ focusInlineTabbableCell () {
83
+ if (this .inlineTabbableCell ) {
84
+ this .inlineTabbableCell .focus ()
85
+
86
+ return
87
+ }
88
+
89
+ this .resetTabbableCell = true
90
+ this .setTabbableCell ()
91
+ this .tabbableCell .focus ()
92
+ this .resetTabbableCell = false
93
+ },
66
94
/**
67
95
* Returns the currently focused cell element, if there is one...
68
96
*/
@@ -181,7 +209,7 @@ export default {
181
209
document .datepickerId = this .datepickerId
182
210
183
211
this .isActive = true
184
-
212
+ this . setInlineTabbableCell ()
185
213
this .setAllElements ()
186
214
this .setNavElements ()
187
215
},
@@ -198,6 +226,57 @@ export default {
198
226
hasArrowedToNewPage () {
199
227
return this .focus .refs && this .focus .refs [0 ] === ' arrow-to-cell'
200
228
},
229
+ /**
230
+ * Returns true if the user is tabbing away from an inline datepicker
231
+ * @return {Boolean}
232
+ */
233
+ isTabbingAwayFromInlineDatepicker (event ) {
234
+ if (! this .inline ) {
235
+ return false
236
+ }
237
+
238
+ if (this .isTabbingAwayFromFirstNavElement (event )) {
239
+ this .tabAwayFromFirstElement ()
240
+
241
+ return true
242
+ }
243
+
244
+ if (this .isTabbingAwayFromLastNavElement (event )) {
245
+ this .tabAwayFromLastElement ()
246
+
247
+ return true
248
+ }
249
+
250
+ return false
251
+ },
252
+ /**
253
+ * Used for inline calendars; returns true if the user tabs backwards from the first focusable element
254
+ * @param {object} event Used to determine whether we are tabbing forwards or backwards
255
+ * @return {Boolean}
256
+ */
257
+ isTabbingAwayFromFirstNavElement (event ) {
258
+ if (! event .shiftKey ) {
259
+ return false
260
+ }
261
+
262
+ const firstNavElement = this .navElements [0 ]
263
+
264
+ return document .activeElement === firstNavElement
265
+ },
266
+ /**
267
+ * Used for inline calendars; returns true if the user tabs forwards from the last focusable element
268
+ * @param {object} event Used to determine whether we are tabbing forwards or backwards
269
+ * @return {Boolean}
270
+ */
271
+ isTabbingAwayFromLastNavElement (event ) {
272
+ if (event .shiftKey ) {
273
+ return false
274
+ }
275
+
276
+ const lastNavElement = this .navElements [this .navElements .length - 1 ]
277
+
278
+ return document .activeElement === lastNavElement
279
+ },
201
280
/**
202
281
* Resets the focus to the open date
203
282
*/
@@ -239,6 +318,17 @@ export default {
239
318
this .resetTabbableCell = false
240
319
})
241
320
},
321
+ /**
322
+ * Stores the current tabbableCell of an inline datepicker
323
+ * N.B. This is used when tabbing back (shift + tab) to an inline calendar from further down the page
324
+ */
325
+ setInlineTabbableCell () {
326
+ if (! this .inline ) {
327
+ return
328
+ }
329
+
330
+ this .inlineTabbableCell = this .tabbableCell
331
+ },
242
332
/**
243
333
* Sets the direction of the slide transition and whether or not to delay application of the focus
244
334
* @param {Date|Number} startDate The date from which to measure
@@ -339,6 +429,26 @@ export default {
339
429
this .transitionName = isInTheFuture ? ' slide-right' : ' slide-left'
340
430
}
341
431
},
432
+ /**
433
+ * Focuses the first focusable element in the datepicker, so that the previous element on the page will be tabbed to
434
+ */
435
+ tabAwayFromFirstElement () {
436
+ const firstElement = this .allElements [0 ]
437
+
438
+ firstElement .focus ()
439
+
440
+ this .tabbableCell = this .inlineTabbableCell
441
+ },
442
+ /**
443
+ * Focuses the last focusable element in the datepicker, so that the next element on the page will be tabbed to
444
+ */
445
+ tabAwayFromLastElement () {
446
+ const lastElement = this .allElements [this .allElements .length - 1 ]
447
+
448
+ lastElement .focus ()
449
+
450
+ this .tabbableCell = this .inlineTabbableCell
451
+ },
342
452
/**
343
453
* Tab backwards through the focus-trapped elements
344
454
*/
@@ -368,10 +478,10 @@ export default {
368
478
* @param event
369
479
*/
370
480
tabThroughNavigation (event ) {
371
- // Allow normal tabbing when closed
372
- if (! this .isOpen ) {
481
+ if (this .allowNormalTabbing (event )) {
373
482
return
374
483
}
484
+
375
485
event .preventDefault ()
376
486
377
487
if (event .shiftKey ) {
@@ -380,6 +490,30 @@ export default {
380
490
this .tabForwards ()
381
491
}
382
492
},
493
+ /**
494
+ * Special cases for when tabbing to an inline datepicker
495
+ */
496
+ tabToCorrectInlineCell () {
497
+ const lastElement = this .allElements [this .allElements .length - 1 ]
498
+ const isACell = this .hasClass (lastElement, ' cell' )
499
+ const isLastElementFocused = document .activeElement === lastElement
500
+
501
+ // If there are no focusable elements in the footer slots and the inline datepicker has been tabbed to (backwards)
502
+ if (isACell && isLastElementFocused) {
503
+ this .focusInlineTabbableCell ()
504
+ return
505
+ }
506
+
507
+ // If `show-header` is false and the inline datepicker has been tabbed to (forwards)
508
+ this .$nextTick (() => {
509
+ const isFirstCell =
510
+ document .activeElement .getAttribute (' data-id' ) === ' 0'
511
+
512
+ if (isFirstCell) {
513
+ this .focusInlineTabbableCell ()
514
+ }
515
+ })
516
+ },
383
517
/**
384
518
* Update which cell in the picker should be focus-trapped
385
519
*/
0 commit comments