/
focusTrapJs.ts
106 lines (92 loc) · 3.16 KB
/
focusTrapJs.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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// https://github.com/alexandrzavalii/focus-trap-js/blob/master/src/index.js v1.1.0
export const candidateSelectors = [
'input',
'select',
'textarea',
'a[href]',
'button',
'[tabindex]',
'audio[controls]',
'video[controls]',
'[contenteditable]:not([contenteditable="false"])',
];
function isHidden(node: any) {
// offsetParent being null will allow detecting cases where an element is invisible or inside an invisible element,
// as long as the element does not use position: fixed. For them, their visibility has to be checked directly as well.
return (
node.offsetParent === null || getComputedStyle(node).visibility === 'hidden'
);
}
function getCheckedRadio(nodes: any, form: any) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].checked && nodes[i].form === form) {
return nodes[i];
}
}
}
function isNotRadioOrTabbableRadio(node: any) {
if (node.tagName !== 'INPUT' || node.type !== 'radio' || !node.name) {
return true;
}
var radioScope = node.form || node.ownerDocument;
var radioSet = radioScope.querySelectorAll(
'input[type="radio"][name="' + node.name + '"]'
);
var checked = getCheckedRadio(radioSet, node.form);
return checked === node || (checked === undefined && radioSet[0] === node);
}
export function getAllTabbingElements(parentElem: any) {
var currentActiveElement = document.activeElement;
var tabbableNodes = parentElem.querySelectorAll(candidateSelectors.join(','));
var onlyTabbable = [];
for (var i = 0; i < tabbableNodes.length; i++) {
var node = tabbableNodes[i];
if (
currentActiveElement === node ||
(!node.disabled &&
getTabindex(node) > -1 &&
!isHidden(node) &&
isNotRadioOrTabbableRadio(node))
) {
onlyTabbable.push(node);
}
}
return onlyTabbable;
}
export function tabTrappingKey(event: any, parentElem: any) {
// check if current event keyCode is tab
if (!event || event.key !== 'Tab') return;
if (!parentElem || !parentElem.contains) {
if (process && process.env.NODE_ENV === 'development') {
console.warn('focus-trap-js: parent element is not defined');
}
return false;
}
if (!parentElem.contains(event.target)) {
return false;
}
var allTabbingElements = getAllTabbingElements(parentElem);
var firstFocusableElement = allTabbingElements[0];
var lastFocusableElement = allTabbingElements[allTabbingElements.length - 1];
if (event.shiftKey && event.target === firstFocusableElement) {
lastFocusableElement.focus();
event.preventDefault();
return true;
} else if (!event.shiftKey && event.target === lastFocusableElement) {
firstFocusableElement.focus();
event.preventDefault();
return true;
}
return false;
}
function getTabindex(node: any) {
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10);
if (!isNaN(tabindexAttr)) return tabindexAttr;
// Browsers do not return tabIndex correctly for contentEditable nodes;
// so if they don't have a tabindex attribute specifically set, assume it's 0.
if (isContentEditable(node)) return 0;
return node.tabIndex;
}
function isContentEditable(node: any) {
return node.getAttribute('contentEditable');
}