@@ -39,20 +39,22 @@ export interface KeyboardHandlerProps {
39
39
* @param {boolean } noVerticalArrowHandling Flag indicating that the included vertical arrow key handling should be ignored
40
40
* @param {boolean } noHorizontalArrowHandling Flag indicating that the included horizontal arrow key handling should be ignored
41
41
* @param {boolean } updateTabIndex Flag indicating that the tabIndex of the currently focused element and next focused element should be updated, in the case of using a roving tabIndex
42
+ * @param {boolean } onlyTraverseSiblings Flag indicating that next focusable element of a horizontal movement will be this element's sibling
42
43
*/
43
44
export const handleArrows = (
44
45
event : KeyboardEvent ,
45
46
navigableElements : Element [ ] ,
46
47
isActiveElement : ( element : Element ) => boolean = element => document . activeElement . contains ( element ) ,
47
48
getFocusableElement : ( element : Element ) => Element = element => element ,
48
- validSiblingTags : string [ ] = [ 'A' , 'BUTTON' ] ,
49
+ validSiblingTags : string [ ] = [ 'A' , 'BUTTON' , 'INPUT' ] ,
49
50
noVerticalArrowHandling : boolean = false ,
50
51
noHorizontalArrowHandling : boolean = false ,
51
- updateTabIndex : boolean = true
52
+ updateTabIndex : boolean = true ,
53
+ onlyTraverseSiblings : boolean = true
52
54
) => {
53
55
const activeElement = document . activeElement ;
54
56
const key = event . key ;
55
- let moveTarget = null ;
57
+ let moveTarget : Element = null ;
56
58
57
59
// Handle vertical arrow keys. If noVerticalArrowHandling is passed, skip this block
58
60
if ( ! noVerticalArrowHandling ) {
@@ -62,23 +64,30 @@ export const handleArrows = (
62
64
63
65
// Traverse navigableElements to find the element which is currently active
64
66
let currentIndex = - 1 ;
67
+ // while (currentIndex === -1) {
65
68
navigableElements . forEach ( ( element , index ) => {
66
69
if ( isActiveElement ( element ) ) {
67
70
// Once found, move up or down the array by 1. Determined by the vertical arrow key direction
68
- const increment = key === 'ArrowUp' ? - 1 : 1 ;
69
- currentIndex = index + increment ;
71
+ let increment = 0 ;
70
72
71
- if ( currentIndex >= navigableElements . length ) {
72
- currentIndex = 0 ;
73
- }
74
- if ( currentIndex < 0 ) {
75
- currentIndex = navigableElements . length - 1 ;
76
- }
73
+ // keep increasing the increment until you've tried the whole navigableElement
74
+ while ( ! moveTarget && increment < navigableElements . length && increment * - 1 < navigableElements . length ) {
75
+ key === 'ArrowUp' ? increment -- : increment ++ ;
76
+ currentIndex = index + increment ;
77
+
78
+ if ( currentIndex >= navigableElements . length ) {
79
+ currentIndex = 0 ;
80
+ }
81
+ if ( currentIndex < 0 ) {
82
+ currentIndex = navigableElements . length - 1 ;
83
+ }
77
84
78
- // Set the next target element
79
- moveTarget = getFocusableElement ( navigableElements [ currentIndex ] ) ;
85
+ // Set the next target element (undefined if none found)
86
+ moveTarget = getFocusableElement ( navigableElements [ currentIndex ] ) ;
87
+ }
80
88
}
81
89
} ) ;
90
+ // }
82
91
}
83
92
}
84
93
@@ -87,21 +96,47 @@ export const handleArrows = (
87
96
if ( [ 'ArrowLeft' , 'ArrowRight' ] . includes ( key ) ) {
88
97
event . preventDefault ( ) ;
89
98
event . stopImmediatePropagation ( ) ; // For menus in menus
90
- let nextSibling = activeElement ;
91
-
92
- // While a sibling exists, check each sibling to determine if it should be focussed
93
- while ( nextSibling ) {
94
- // Set the next checked sibling, determined by the horizontal arrow key direction
95
- nextSibling = key === 'ArrowLeft' ? nextSibling . previousElementSibling : nextSibling . nextElementSibling ;
96
- if ( nextSibling ) {
97
- if ( validSiblingTags . includes ( nextSibling . tagName ) ) {
98
- // If the sibling's tag is included in validSiblingTags, set the next target element and break the loop
99
- moveTarget = nextSibling ;
100
- break ;
99
+
100
+ let currentIndex = - 1 ;
101
+ navigableElements . forEach ( ( element , index ) => {
102
+ if ( isActiveElement ( element ) ) {
103
+ const activeRow = navigableElements [ index ] . querySelectorAll ( validSiblingTags . join ( ',' ) ) ; // all focusable elements in my row
104
+
105
+ if ( ! activeRow . length || onlyTraverseSiblings ) {
106
+ let nextSibling = activeElement ;
107
+ // While a sibling exists, check each sibling to determine if it should be focussed
108
+ while ( nextSibling ) {
109
+ // Set the next checked sibling, determined by the horizontal arrow key direction
110
+ nextSibling = key === 'ArrowLeft' ? nextSibling . previousElementSibling : nextSibling . nextElementSibling ;
111
+ if ( nextSibling ) {
112
+ if ( validSiblingTags . includes ( nextSibling . tagName ) ) {
113
+ // If the sibling's tag is included in validSiblingTags, set the next target element and break the loop
114
+ moveTarget = nextSibling ;
115
+ break ;
116
+ }
117
+ // If the sibling's tag is not valid, skip to the next sibling if possible
118
+ }
119
+ }
120
+ } else {
121
+ activeRow . forEach ( ( focusableElement , index ) => {
122
+ if ( event . target === focusableElement ) {
123
+ // Once found, move up or down the array by 1. Determined by the vertical arrow key direction
124
+ const increment = key === 'ArrowLeft' ? - 1 : 1 ;
125
+ currentIndex = index + increment ;
126
+ if ( currentIndex >= activeRow . length ) {
127
+ currentIndex = 0 ;
128
+ }
129
+ if ( currentIndex < 0 ) {
130
+ currentIndex = activeRow . length - 1 ;
131
+ }
132
+
133
+ // Set the next target element
134
+ moveTarget = activeRow [ currentIndex ] ;
135
+ }
136
+ } ) ;
101
137
}
102
- // If the sibling's tag is not valid, skip to the next sibling if possible
103
138
}
104
- }
139
+ } ) ;
105
140
}
106
141
}
107
142
@@ -112,7 +147,7 @@ export const handleArrows = (
112
147
( activeElement as HTMLElement ) . tabIndex = - 1 ;
113
148
( moveTarget as HTMLElement ) . tabIndex = 0 ;
114
149
}
115
- // If a move target has been set by eithe arrow handler, focus that target
150
+ // If a move target has been set by either arrow handler, focus that target
116
151
( moveTarget as HTMLElement ) . focus ( ) ;
117
152
}
118
153
} ;
0 commit comments