Skip to content

Commit d2d8a39

Browse files
perf(tab): avert visibility check on irrelevant elements (#967)
Check visibility only on elements which are next/previous in tab order and thus considered a potential target. Co-authored-by: Philipp Fritsche <[email protected]>
1 parent 9dd3985 commit d2d8a39

File tree

2 files changed

+25
-13
lines changed

2 files changed

+25
-13
lines changed

src/utils/focus/getTabDestination.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,11 @@ export function getTabDestination(activeElement: Element, shift: boolean) {
1010
const enabledElements = Array.from(focusableElements).filter(
1111
el =>
1212
el === activeElement ||
13-
(el.getAttribute('tabindex') !== '-1' &&
14-
!isDisabled(el) &&
15-
// Hidden elements are not tabable
16-
isVisible(el)),
13+
!(Number(el.getAttribute('tabindex')) < 0 || isDisabled(el)),
1714
)
1815

19-
if (activeElement.getAttribute('tabindex') !== '-1') {
20-
// tabindex has no effect if the active element has tabindex="-1"
16+
// tabindex has no effect if the active element has negative tabindex
17+
if (Number(activeElement.getAttribute('tabindex')) >= 0) {
2118
enabledElements.sort((a, b) => {
2219
const i = Number(a.getAttribute('tabindex'))
2320
const j = Number(b.getAttribute('tabindex'))
@@ -73,9 +70,22 @@ export function getTabDestination(activeElement: Element, shift: boolean) {
7370
prunedElements.push(el)
7471
})
7572

76-
const currentIndex = prunedElements.findIndex(el => el === activeElement)
73+
for (let index = prunedElements.findIndex(el => el === activeElement); ; ) {
74+
index += shift ? -1 : 1
7775

78-
const nextIndex = shift ? currentIndex - 1 : currentIndex + 1
79-
const defaultIndex = shift ? prunedElements.length - 1 : 0
80-
return prunedElements[nextIndex] || prunedElements[defaultIndex]
76+
// loop at overflow
77+
if (index === prunedElements.length) {
78+
index = 0
79+
} else if (index === -1) {
80+
index = prunedElements.length - 1
81+
}
82+
83+
if (
84+
prunedElements[index] === activeElement ||
85+
prunedElements[index] === document.body ||
86+
isVisible(prunedElements[index])
87+
) {
88+
return prunedElements[index]
89+
}
90+
}
8191
}

tests/utils/focus/getTabDestination.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ test('get next focusable element in tab order', () => {
4646
const {
4747
elements: [elA, elB, elC, elD, elE, elF],
4848
} = setup(`
49-
<input id="a" tabIndex="2"/>
49+
<input id="a" tabIndex="2">
5050
<input id="b" tabIndex="0">
51-
<input id="c" tabIndex="-1"/>
51+
<input id="c" tabIndex="-1">
5252
<input id="d" tabIndex="0">
5353
<input id="e" tabIndex="1">
54-
<input id="f" />
54+
<input id="f">
55+
<input id="g" tabIndex="-999">
5556
`)
5657

5758
assertTabOrder([elE, elA, elB, elD, elF])
@@ -72,6 +73,7 @@ test('exclude hidden elements', () => {
7273
} = setup(`
7374
<input/>
7475
<input type="hidden"/>
76+
<input hidden />
7577
<input style="visibility: hidden"/>
7678
<input style="display: none"/>
7779
`)

0 commit comments

Comments
 (0)