-
Notifications
You must be signed in to change notification settings - Fork 252
/
Copy pathgetTabDestination.ts
91 lines (80 loc) · 2.69 KB
/
getTabDestination.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import {isDisabled} from '../misc/isDisabled'
import {isElementType} from '../misc/isElementType'
import {isVisible} from '../misc/isVisible'
import {FOCUSABLE_SELECTOR} from './selector'
export function getTabDestination(activeElement: Element, shift: boolean) {
const document = activeElement.ownerDocument
const focusableElements = document.querySelectorAll(FOCUSABLE_SELECTOR)
const enabledElements = Array.from(focusableElements).filter(
el =>
el === activeElement ||
!(Number(el.getAttribute('tabindex')) < 0 || isDisabled(el)),
)
// tabindex has no effect if the active element has negative tabindex
if (Number(activeElement.getAttribute('tabindex')) >= 0) {
enabledElements.sort((a, b) => {
const i = Number(a.getAttribute('tabindex'))
const j = Number(b.getAttribute('tabindex'))
if (i === j) {
return 0
} else if (i === 0) {
return 1
} else if (j === 0) {
return -1
}
return i - j
})
}
const checkedRadio: Record<string, HTMLInputElement> = {}
let prunedElements = [document.body]
const activeRadioGroup = isElementType(activeElement, 'input', {
type: 'radio',
})
? activeElement.name
: undefined
enabledElements.forEach(currentElement => {
const el = currentElement as HTMLInputElement
// For radio groups keep only the active radio
// If there is no active radio, keep only the checked radio
// If there is no checked radio, treat like everything else
if (isElementType(el, 'input', {type: 'radio'}) && el.name) {
// If the active element is part of the group, add only that
if (el === activeElement) {
prunedElements.push(el)
return
} else if (el.name === activeRadioGroup) {
return
}
// If we stumble upon a checked radio, remove the others
if (el.checked) {
prunedElements = prunedElements.filter(
e => !isElementType(e, 'input', {type: 'radio', name: el.name}),
)
prunedElements.push(el)
checkedRadio[el.name] = el
return
}
// If we already found the checked one, skip
if (typeof checkedRadio[el.name] !== 'undefined') {
return
}
}
prunedElements.push(el)
})
for (let index = prunedElements.findIndex(el => el === activeElement); ; ) {
index += shift ? -1 : 1
// loop at overflow
if (index === prunedElements.length) {
index = 0
} else if (index === -1) {
index = prunedElements.length - 1
}
if (
prunedElements[index] === activeElement ||
prunedElements[index] === document.body ||
isVisible(prunedElements[index])
) {
return prunedElements[index]
}
}
}