focus-trap 2.4.6 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +25 -7
- package/dist/focus-trap.js +371 -199
- package/dist/focus-trap.min.js +1 -1
- package/index.js +185 -135
- package/package.json +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.2
|
|
4
|
+
|
|
5
|
+
- Fix reference to root element that caused errors within Shadow DOM.
|
|
6
|
+
|
|
7
|
+
(Release 4.0.1 was a mistake, containing no changes.)
|
|
8
|
+
|
|
9
|
+
## 4.0.0
|
|
10
|
+
|
|
11
|
+
- **Breaking (kind of):** Focus trap now manages a queue of traps, so when a trap is paused because another trap activates, it will be unpaused when that other trap deactivates. If Trap A was automatically *paused* because Trap B activated (existing behavior), when Trap B is deactivated Trap A will be automatically *unpaused* (new behavior).
|
|
12
|
+
|
|
13
|
+
## 3.0.0
|
|
14
|
+
|
|
15
|
+
- **Breaking (kind of):** Update Tabbable to detect more elements and be more careful with radio buttons (see [Tabbable's changelog](https://github.com/davidtheclark/tabbable/blob/master/CHANGELOG.md)).
|
|
16
|
+
- **Breaking (kind of):** If `clickOutsideDeactivates` and `returnFocusOnDeactivate` are both `true`, focus will be returned to the pre-trap element only if the clicked element is not focusable.
|
|
17
|
+
|
|
3
18
|
## 2.4.6
|
|
4
19
|
|
|
5
20
|
- Add slight delay before moving focus to the first element in the trap.
|
package/README.md
CHANGED
|
@@ -4,16 +4,14 @@ Trap focus within a DOM node.
|
|
|
4
4
|
|
|
5
5
|
There may come a time when you find it important to trap focus within a DOM node — so that when a user hits `Tab` or `Shift+Tab` or clicks around, she can't escape a certain cycle of focusable elements.
|
|
6
6
|
|
|
7
|
-
You will definitely face this challenge when you are
|
|
7
|
+
You will definitely face this challenge when you are trying to build **accessible modals**.
|
|
8
8
|
|
|
9
|
-
This module is a little **vanilla JS** solution to that problem.
|
|
9
|
+
This module is a little, modular **vanilla JS** solution to that problem.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Use it in your higher-level components. For example, if you are using React check out [focus-trap-react](https://github.com/davidtheclark/focus-trap-react), a light wrapper around this library. If you are not a React user, consider creating light wrappers in your framework-of-choice.
|
|
12
12
|
|
|
13
13
|
## What it does
|
|
14
14
|
|
|
15
|
-
[Check out the demos.](http://davidtheclark.github.io/focus-trap/demo/)
|
|
16
|
-
|
|
17
15
|
When a focus trap is activated, this is what should happen:
|
|
18
16
|
|
|
19
17
|
- Some element within the focus trap receives focus. By default, this will be the first element in the focus trap's tab order (as determined by [tabbable](https://github.com/davidtheclark/tabbable)). Alternately, you can specify an element that should receive this initial focus.
|
|
@@ -26,6 +24,8 @@ When the focus trap is deactivated, this is what should happen:
|
|
|
26
24
|
- Focus is passed to *whichever element had focus when the trap was activated* (e.g. the button that opened the modal or menu).
|
|
27
25
|
- Tabbing and clicking behave normally everywhere.
|
|
28
26
|
|
|
27
|
+
[Check out the demos.](http://davidtheclark.github.io/focus-trap/demo/)
|
|
28
|
+
|
|
29
29
|
For more advanced usage (e.g. focus traps within focus traps), you can also pause a focus trap's behavior without deactivating it entirely, then unpause at will.
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
@@ -121,7 +121,7 @@ Returns the `focusTrap`.
|
|
|
121
121
|
|
|
122
122
|
## Examples
|
|
123
123
|
|
|
124
|
-
Read code in `demo/`
|
|
124
|
+
Read code in `demo/` and [see how it works](http://davidtheclark.github.io/focus-trap/demo/).
|
|
125
125
|
|
|
126
126
|
Here's what happens in `demo-one.js`:
|
|
127
127
|
|
|
@@ -147,7 +147,25 @@ document.getElementById('deactivate-one').addEventListener('click', function ()
|
|
|
147
147
|
|
|
148
148
|
## Other details
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
### One at a time
|
|
151
|
+
|
|
152
|
+
*Only one focus trap can be listening at a time.* If a second focus trap is activated the first will automatically pause. The first trap is unpaused and again traps focus when the second is deactivated.
|
|
153
|
+
|
|
154
|
+
Focus trap manages a queue of traps: if A activates; then B activates, pausing A; then C activates, pausing B; when C then deactivates, B is unpaused; and when B then deactivates, A is unpaused.
|
|
155
|
+
|
|
156
|
+
### Use predictable elements for the first and last tabbable elements in your trap
|
|
157
|
+
|
|
158
|
+
The focus trap will work best if the *first* and *last* focusable elements in your trap are simple elements that all browsers treat the same, like buttons and inputs.**
|
|
159
|
+
|
|
160
|
+
Tabbing will work as expected with trickier, less predictable elements — like iframes, shadow trees, audio and video elements, etc. — as long as they are *between* more predictable elements (that is, if they are not the first or last tabbable element in the trap).
|
|
161
|
+
|
|
162
|
+
This limitation is ultimately rooted in browser inconsistencies and inadequacies, but it comes to focus-trap through its dependency [Tabbable](https://github.com/davidtheclark/tabbable). You can read about more details [in the Tabbable documentation](https://github.com/davidtheclark/tabbable#more-details).
|
|
163
|
+
|
|
164
|
+
### Your trap should include a tabbable element or a focusable container
|
|
165
|
+
|
|
166
|
+
You can't have a focus trap without focus, so an error will be thrown if you try to initialize focus-trap with an element that contains no tabbable nodes.
|
|
167
|
+
|
|
168
|
+
If you find yourself in this situation, you should give you container `tabindex="-1"` and set it as `initialFocus` or `fallbackFocus`. A couple of demos illustrate this.
|
|
151
169
|
|
|
152
170
|
## Development
|
|
153
171
|
|
package/dist/focus-trap.js
CHANGED
|
@@ -1,53 +1,87 @@
|
|
|
1
|
-
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap = f()}})(function(){var define,module,exports;return (function e
|
|
1
|
+
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
|
2
2
|
var tabbable = require('tabbable');
|
|
3
|
+
var xtend = require('xtend');
|
|
4
|
+
|
|
5
|
+
var activeFocusTraps = (function() {
|
|
6
|
+
var trapQueue = [];
|
|
7
|
+
return {
|
|
8
|
+
activateTrap: function(trap) {
|
|
9
|
+
if (trapQueue.length > 0) {
|
|
10
|
+
var activeTrap = trapQueue[trapQueue.length - 1];
|
|
11
|
+
if (activeTrap !== trap) {
|
|
12
|
+
activeTrap.pause();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
var trapIndex = trapQueue.indexOf(trap);
|
|
17
|
+
if (trapIndex === -1) {
|
|
18
|
+
trapQueue.push(trap);
|
|
19
|
+
} else {
|
|
20
|
+
// move this existing trap to the front of the queue
|
|
21
|
+
trapQueue.splice(trapIndex, 1);
|
|
22
|
+
trapQueue.push(trap);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
3
25
|
|
|
4
|
-
|
|
26
|
+
deactivateTrap: function(trap) {
|
|
27
|
+
var trapIndex = trapQueue.indexOf(trap);
|
|
28
|
+
if (trapIndex !== -1) {
|
|
29
|
+
trapQueue.splice(trapIndex, 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (trapQueue.length > 0) {
|
|
33
|
+
trapQueue[trapQueue.length - 1].unpause();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
5
38
|
|
|
6
39
|
function focusTrap(element, userOptions) {
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
:
|
|
40
|
+
var doc = document;
|
|
41
|
+
var container =
|
|
42
|
+
typeof element === 'string' ? doc.querySelector(element) : element;
|
|
43
|
+
|
|
44
|
+
var config = xtend(
|
|
45
|
+
{
|
|
46
|
+
returnFocusOnDeactivate: true,
|
|
47
|
+
escapeDeactivates: true
|
|
48
|
+
},
|
|
49
|
+
userOptions
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
var state = {
|
|
53
|
+
firstTabbableNode: null,
|
|
54
|
+
lastTabbableNode: null,
|
|
55
|
+
nodeFocusedBeforeActivation: null,
|
|
56
|
+
mostRecentlyFocusedNode: null,
|
|
57
|
+
active: false,
|
|
58
|
+
paused: false
|
|
59
|
+
};
|
|
26
60
|
|
|
27
61
|
var trap = {
|
|
28
62
|
activate: activate,
|
|
29
63
|
deactivate: deactivate,
|
|
30
64
|
pause: pause,
|
|
31
|
-
unpause: unpause
|
|
65
|
+
unpause: unpause
|
|
32
66
|
};
|
|
33
67
|
|
|
34
68
|
return trap;
|
|
35
69
|
|
|
36
70
|
function activate(activateOptions) {
|
|
37
|
-
if (active) return;
|
|
71
|
+
if (state.active) return;
|
|
38
72
|
|
|
39
|
-
|
|
40
|
-
onActivate: (activateOptions && activateOptions.onActivate !== undefined)
|
|
41
|
-
? activateOptions.onActivate
|
|
42
|
-
: config.onActivate,
|
|
43
|
-
};
|
|
73
|
+
updateTabbableNodes();
|
|
44
74
|
|
|
45
|
-
active = true;
|
|
46
|
-
paused = false;
|
|
47
|
-
nodeFocusedBeforeActivation =
|
|
75
|
+
state.active = true;
|
|
76
|
+
state.paused = false;
|
|
77
|
+
state.nodeFocusedBeforeActivation = doc.activeElement;
|
|
48
78
|
|
|
49
|
-
|
|
50
|
-
|
|
79
|
+
var onActivate =
|
|
80
|
+
activateOptions && activateOptions.onActivate
|
|
81
|
+
? activateOptions.onActivate
|
|
82
|
+
: config.onActivate;
|
|
83
|
+
if (onActivate) {
|
|
84
|
+
onActivate();
|
|
51
85
|
}
|
|
52
86
|
|
|
53
87
|
addListeners();
|
|
@@ -55,79 +89,77 @@ function focusTrap(element, userOptions) {
|
|
|
55
89
|
}
|
|
56
90
|
|
|
57
91
|
function deactivate(deactivateOptions) {
|
|
58
|
-
if (!active) return;
|
|
59
|
-
|
|
60
|
-
var defaultedDeactivateOptions = {
|
|
61
|
-
returnFocus: (deactivateOptions && deactivateOptions.returnFocus !== undefined)
|
|
62
|
-
? deactivateOptions.returnFocus
|
|
63
|
-
: config.returnFocusOnDeactivate,
|
|
64
|
-
onDeactivate: (deactivateOptions && deactivateOptions.onDeactivate !== undefined)
|
|
65
|
-
? deactivateOptions.onDeactivate
|
|
66
|
-
: config.onDeactivate,
|
|
67
|
-
};
|
|
92
|
+
if (!state.active) return;
|
|
68
93
|
|
|
69
94
|
removeListeners();
|
|
95
|
+
state.active = false;
|
|
96
|
+
state.paused = false;
|
|
97
|
+
|
|
98
|
+
activeFocusTraps.deactivateTrap(trap);
|
|
70
99
|
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
var onDeactivate =
|
|
101
|
+
deactivateOptions && deactivateOptions.onDeactivate !== undefined
|
|
102
|
+
? deactivateOptions.onDeactivate
|
|
103
|
+
: config.onDeactivate;
|
|
104
|
+
if (onDeactivate) {
|
|
105
|
+
onDeactivate();
|
|
73
106
|
}
|
|
74
107
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
var returnFocus =
|
|
109
|
+
deactivateOptions && deactivateOptions.returnFocus !== undefined
|
|
110
|
+
? deactivateOptions.returnFocus
|
|
111
|
+
: config.returnFocusOnDeactivate;
|
|
112
|
+
if (returnFocus) {
|
|
113
|
+
delay(function() {
|
|
114
|
+
tryFocus(state.nodeFocusedBeforeActivation);
|
|
115
|
+
});
|
|
79
116
|
}
|
|
80
117
|
|
|
81
|
-
|
|
82
|
-
paused = false;
|
|
83
|
-
return this;
|
|
118
|
+
return trap;
|
|
84
119
|
}
|
|
85
120
|
|
|
86
121
|
function pause() {
|
|
87
|
-
if (paused || !active) return;
|
|
88
|
-
paused = true;
|
|
122
|
+
if (state.paused || !state.active) return;
|
|
123
|
+
state.paused = true;
|
|
89
124
|
removeListeners();
|
|
90
125
|
}
|
|
91
126
|
|
|
92
127
|
function unpause() {
|
|
93
|
-
if (!paused || !active) return;
|
|
94
|
-
paused = false;
|
|
128
|
+
if (!state.paused || !state.active) return;
|
|
129
|
+
state.paused = false;
|
|
95
130
|
addListeners();
|
|
96
131
|
}
|
|
97
132
|
|
|
98
133
|
function addListeners() {
|
|
99
|
-
if (!active) return;
|
|
134
|
+
if (!state.active) return;
|
|
100
135
|
|
|
101
136
|
// There can be only one listening focus trap at a time
|
|
102
|
-
|
|
103
|
-
listeningFocusTrap.pause();
|
|
104
|
-
}
|
|
105
|
-
listeningFocusTrap = trap;
|
|
137
|
+
activeFocusTraps.activateTrap(trap);
|
|
106
138
|
|
|
107
139
|
updateTabbableNodes();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
|
|
141
|
+
// Delay ensures that the focused element doesn't capture the event
|
|
142
|
+
// that caused the focus trap activation.
|
|
143
|
+
delay(function() {
|
|
144
|
+
tryFocus(getInitialFocusNode());
|
|
145
|
+
});
|
|
146
|
+
doc.addEventListener('focusin', checkFocusIn, true);
|
|
147
|
+
doc.addEventListener('mousedown', checkPointerDown, true);
|
|
148
|
+
doc.addEventListener('touchstart', checkPointerDown, true);
|
|
149
|
+
doc.addEventListener('click', checkClick, true);
|
|
150
|
+
doc.addEventListener('keydown', checkKey, true);
|
|
117
151
|
|
|
118
152
|
return trap;
|
|
119
153
|
}
|
|
120
154
|
|
|
121
155
|
function removeListeners() {
|
|
122
|
-
if (!active
|
|
156
|
+
if (!state.active) return;
|
|
123
157
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
listeningFocusTrap = null;
|
|
158
|
+
doc.removeEventListener('focusin', checkFocusIn, true);
|
|
159
|
+
doc.removeEventListener('mousedown', checkPointerDown, true);
|
|
160
|
+
doc.removeEventListener('touchstart', checkPointerDown, true);
|
|
161
|
+
doc.removeEventListener('click', checkClick, true);
|
|
162
|
+
doc.removeEventListener('keydown', checkKey, true);
|
|
131
163
|
|
|
132
164
|
return trap;
|
|
133
165
|
}
|
|
@@ -139,7 +171,7 @@ function focusTrap(element, userOptions) {
|
|
|
139
171
|
return null;
|
|
140
172
|
}
|
|
141
173
|
if (typeof optionValue === 'string') {
|
|
142
|
-
node =
|
|
174
|
+
node = doc.querySelector(optionValue);
|
|
143
175
|
if (!node) {
|
|
144
176
|
throw new Error('`' + optionName + '` refers to no known node');
|
|
145
177
|
}
|
|
@@ -153,211 +185,351 @@ function focusTrap(element, userOptions) {
|
|
|
153
185
|
return node;
|
|
154
186
|
}
|
|
155
187
|
|
|
156
|
-
function
|
|
188
|
+
function getInitialFocusNode() {
|
|
157
189
|
var node;
|
|
158
190
|
if (getNodeForOption('initialFocus') !== null) {
|
|
159
191
|
node = getNodeForOption('initialFocus');
|
|
160
|
-
} else if (container.contains(
|
|
161
|
-
node =
|
|
192
|
+
} else if (container.contains(doc.activeElement)) {
|
|
193
|
+
node = doc.activeElement;
|
|
162
194
|
} else {
|
|
163
|
-
node =
|
|
195
|
+
node = state.firstTabbableNode || getNodeForOption('fallbackFocus');
|
|
164
196
|
}
|
|
165
197
|
|
|
166
198
|
if (!node) {
|
|
167
|
-
throw new Error(
|
|
199
|
+
throw new Error(
|
|
200
|
+
"You can't have a focus-trap without at least one focusable element"
|
|
201
|
+
);
|
|
168
202
|
}
|
|
169
203
|
|
|
170
204
|
return node;
|
|
171
205
|
}
|
|
172
206
|
|
|
173
207
|
// This needs to be done on mousedown and touchstart instead of click
|
|
174
|
-
// so that it precedes the focus event
|
|
208
|
+
// so that it precedes the focus event.
|
|
175
209
|
function checkPointerDown(e) {
|
|
176
|
-
if (config.clickOutsideDeactivates && !container.contains(e.target)) {
|
|
177
|
-
deactivate({ returnFocus: false });
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function checkClick(e) {
|
|
182
|
-
if (config.clickOutsideDeactivates) return;
|
|
183
210
|
if (container.contains(e.target)) return;
|
|
184
|
-
|
|
185
|
-
|
|
211
|
+
if (config.clickOutsideDeactivates) {
|
|
212
|
+
deactivate({
|
|
213
|
+
returnFocus: !tabbable.isFocusable(e.target)
|
|
214
|
+
});
|
|
215
|
+
} else {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
}
|
|
186
218
|
}
|
|
187
219
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
e.
|
|
192
|
-
|
|
193
|
-
if (typeof e.target.blur === 'function') e.target.blur();
|
|
194
|
-
|
|
195
|
-
if (tabEvent) {
|
|
196
|
-
readjustFocus(tabEvent);
|
|
220
|
+
// In case focus escapes the trap for some strange reason, pull it back in.
|
|
221
|
+
function checkFocusIn(e) {
|
|
222
|
+
// In Firefox when you Tab out of an iframe the Document is briefly focused.
|
|
223
|
+
if (container.contains(e.target) || e.target instanceof Document) {
|
|
224
|
+
return;
|
|
197
225
|
}
|
|
226
|
+
e.stopImmediatePropagation();
|
|
227
|
+
tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
|
|
198
228
|
}
|
|
199
229
|
|
|
200
230
|
function checkKey(e) {
|
|
201
|
-
if (e.key === 'Tab' || e.keyCode === 9) {
|
|
202
|
-
handleTab(e);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
231
|
if (config.escapeDeactivates !== false && isEscapeEvent(e)) {
|
|
232
|
+
e.preventDefault();
|
|
206
233
|
deactivate();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (isTabEvent(e)) {
|
|
237
|
+
checkTab(e);
|
|
238
|
+
return;
|
|
207
239
|
}
|
|
208
240
|
}
|
|
209
241
|
|
|
210
|
-
|
|
242
|
+
// Hijack Tab events on the first and last focusable nodes of the trap,
|
|
243
|
+
// in order to prevent focus from escaping. If it escapes for even a
|
|
244
|
+
// moment it can end up scrolling the page and causing confusion so we
|
|
245
|
+
// kind of need to capture the action at the keydown phase.
|
|
246
|
+
function checkTab(e) {
|
|
211
247
|
updateTabbableNodes();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
248
|
+
if (e.shiftKey && e.target === state.firstTabbableNode) {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
tryFocus(state.lastTabbableNode);
|
|
251
|
+
return;
|
|
215
252
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (e.shiftKey) {
|
|
221
|
-
if (e.target === firstTabbableNode || tabbableNodes.indexOf(e.target) === -1) {
|
|
222
|
-
return tryFocus(lastTabbableNode);
|
|
223
|
-
}
|
|
224
|
-
return tryFocus(tabbableNodes[currentFocusIndex - 1]);
|
|
253
|
+
if (!e.shiftKey && e.target === state.lastTabbableNode) {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
tryFocus(state.firstTabbableNode);
|
|
256
|
+
return;
|
|
225
257
|
}
|
|
258
|
+
}
|
|
226
259
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
260
|
+
function checkClick(e) {
|
|
261
|
+
if (config.clickOutsideDeactivates) return;
|
|
262
|
+
if (container.contains(e.target)) return;
|
|
263
|
+
e.preventDefault();
|
|
264
|
+
e.stopImmediatePropagation();
|
|
230
265
|
}
|
|
231
266
|
|
|
232
267
|
function updateTabbableNodes() {
|
|
233
|
-
tabbableNodes = tabbable(container);
|
|
234
|
-
firstTabbableNode = tabbableNodes[0];
|
|
235
|
-
lastTabbableNode =
|
|
268
|
+
var tabbableNodes = tabbable(container);
|
|
269
|
+
state.firstTabbableNode = tabbableNodes[0] || getInitialFocusNode();
|
|
270
|
+
state.lastTabbableNode =
|
|
271
|
+
tabbableNodes[tabbableNodes.length - 1] || getInitialFocusNode();
|
|
236
272
|
}
|
|
237
273
|
|
|
238
|
-
function
|
|
239
|
-
if (
|
|
274
|
+
function tryFocus(node) {
|
|
275
|
+
if (node === doc.activeElement) return;
|
|
276
|
+
if (!node || !node.focus) {
|
|
277
|
+
tryFocus(getInitialFocusNode());
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
240
280
|
|
|
241
|
-
|
|
281
|
+
node.focus();
|
|
282
|
+
state.mostRecentlyFocusedNode = node;
|
|
283
|
+
if (isSelectableInput(node)) {
|
|
284
|
+
node.select();
|
|
285
|
+
}
|
|
242
286
|
}
|
|
243
287
|
}
|
|
244
288
|
|
|
289
|
+
function isSelectableInput(node) {
|
|
290
|
+
return (
|
|
291
|
+
node.tagName &&
|
|
292
|
+
node.tagName.toLowerCase() === 'input' &&
|
|
293
|
+
typeof node.select === 'function'
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
245
297
|
function isEscapeEvent(e) {
|
|
246
298
|
return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
|
|
247
299
|
}
|
|
248
300
|
|
|
249
|
-
function
|
|
250
|
-
|
|
251
|
-
|
|
301
|
+
function isTabEvent(e) {
|
|
302
|
+
return e.key === 'Tab' || e.keyCode === 9;
|
|
303
|
+
}
|
|
252
304
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
node.select();
|
|
256
|
-
}
|
|
305
|
+
function delay(fn) {
|
|
306
|
+
return setTimeout(fn, 0);
|
|
257
307
|
}
|
|
258
308
|
|
|
259
309
|
module.exports = focusTrap;
|
|
260
310
|
|
|
261
|
-
},{"tabbable":2}],2:[function(require,module,exports){
|
|
262
|
-
|
|
263
|
-
|
|
311
|
+
},{"tabbable":2,"xtend":3}],2:[function(require,module,exports){
|
|
312
|
+
var candidateSelectors = [
|
|
313
|
+
'input',
|
|
314
|
+
'select',
|
|
315
|
+
'textarea',
|
|
316
|
+
'a[href]',
|
|
317
|
+
'button',
|
|
318
|
+
'[tabindex]',
|
|
319
|
+
'audio[controls]',
|
|
320
|
+
'video[controls]',
|
|
321
|
+
'[contenteditable]:not([contenteditable="false"])',
|
|
322
|
+
];
|
|
323
|
+
var candidateSelector = candidateSelectors.join(',');
|
|
324
|
+
|
|
325
|
+
var matches = typeof Element === 'undefined'
|
|
326
|
+
? function () {}
|
|
327
|
+
: Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
|
|
328
|
+
|
|
329
|
+
function tabbable(el, options) {
|
|
330
|
+
options = options || {};
|
|
331
|
+
|
|
332
|
+
var elementDocument = el.ownerDocument || el;
|
|
333
|
+
var regularTabbables = [];
|
|
264
334
|
var orderedTabbables = [];
|
|
265
335
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
var isUnavailable = createIsUnavailable();
|
|
336
|
+
var untouchabilityChecker = new UntouchabilityChecker(elementDocument);
|
|
337
|
+
var candidates = el.querySelectorAll(candidateSelector);
|
|
269
338
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
'[tabindex]',
|
|
277
|
-
];
|
|
278
|
-
|
|
279
|
-
var candidates = el.querySelectorAll(candidateSelectors);
|
|
339
|
+
if (options.includeContainer) {
|
|
340
|
+
if (matches.call(el, candidateSelector)) {
|
|
341
|
+
candidates = Array.prototype.slice.apply(candidates);
|
|
342
|
+
candidates.unshift(el);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
280
345
|
|
|
281
|
-
var candidate,
|
|
282
|
-
for (
|
|
346
|
+
var i, candidate, candidateTabindex;
|
|
347
|
+
for (i = 0; i < candidates.length; i++) {
|
|
283
348
|
candidate = candidates[i];
|
|
284
|
-
candidateIndex = candidate.tabIndex;
|
|
285
|
-
|
|
286
|
-
if (
|
|
287
|
-
candidateIndex < 0
|
|
288
|
-
|| (candidate.tagName === 'INPUT' && candidate.type === 'hidden')
|
|
289
|
-
|| candidate.disabled
|
|
290
|
-
|| isUnavailable(candidate)
|
|
291
|
-
) {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
349
|
|
|
295
|
-
if (
|
|
296
|
-
|
|
350
|
+
if (!isNodeMatchingSelectorTabbable(candidate, untouchabilityChecker)) continue;
|
|
351
|
+
|
|
352
|
+
candidateTabindex = getTabindex(candidate);
|
|
353
|
+
if (candidateTabindex === 0) {
|
|
354
|
+
regularTabbables.push(candidate);
|
|
297
355
|
} else {
|
|
298
356
|
orderedTabbables.push({
|
|
299
|
-
|
|
357
|
+
documentOrder: i,
|
|
358
|
+
tabIndex: candidateTabindex,
|
|
300
359
|
node: candidate,
|
|
301
360
|
});
|
|
302
361
|
}
|
|
303
362
|
}
|
|
304
363
|
|
|
305
364
|
var tabbableNodes = orderedTabbables
|
|
306
|
-
.sort(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
.map(function(a) {
|
|
310
|
-
return a.node
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
Array.prototype.push.apply(tabbableNodes, basicTabbables);
|
|
365
|
+
.sort(sortOrderedTabbables)
|
|
366
|
+
.map(function(a) { return a.node })
|
|
367
|
+
.concat(regularTabbables);
|
|
314
368
|
|
|
315
369
|
return tabbableNodes;
|
|
316
370
|
}
|
|
317
371
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
372
|
+
tabbable.isTabbable = isTabbable;
|
|
373
|
+
tabbable.isFocusable = isFocusable;
|
|
374
|
+
|
|
375
|
+
function isNodeMatchingSelectorTabbable(node, untouchabilityChecker) {
|
|
376
|
+
if (
|
|
377
|
+
!isNodeMatchingSelectorFocusable(node, untouchabilityChecker)
|
|
378
|
+
|| isNonTabbableRadio(node)
|
|
379
|
+
|| getTabindex(node) < 0
|
|
380
|
+
) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function isTabbable(node, untouchabilityChecker) {
|
|
387
|
+
if (!node) throw new Error('No node provided');
|
|
388
|
+
if (matches.call(node, candidateSelector) === false) return false;
|
|
389
|
+
return isNodeMatchingSelectorTabbable(node, untouchabilityChecker);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function isNodeMatchingSelectorFocusable(node, untouchabilityChecker) {
|
|
393
|
+
untouchabilityChecker = untouchabilityChecker || new UntouchabilityChecker(node.ownerDocument || node);
|
|
394
|
+
if (
|
|
395
|
+
node.disabled
|
|
396
|
+
|| isHiddenInput(node)
|
|
397
|
+
|| untouchabilityChecker.isUntouchable(node)
|
|
398
|
+
) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
var focusableCandidateSelector = candidateSelectors.concat('iframe').join(',');
|
|
405
|
+
function isFocusable(node, untouchabilityChecker) {
|
|
406
|
+
if (!node) throw new Error('No node provided');
|
|
407
|
+
if (matches.call(node, focusableCandidateSelector) === false) return false;
|
|
408
|
+
return isNodeMatchingSelectorFocusable(node, untouchabilityChecker);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function getTabindex(node) {
|
|
412
|
+
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10);
|
|
413
|
+
if (!isNaN(tabindexAttr)) return tabindexAttr;
|
|
414
|
+
// Browsers do not return `tabIndex` correctly for contentEditable nodes;
|
|
415
|
+
// so if they don't have a tabindex attribute specifically set, assume it's 0.
|
|
416
|
+
if (isContentEditable(node)) return 0;
|
|
417
|
+
return node.tabIndex;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function sortOrderedTabbables(a, b) {
|
|
421
|
+
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Array.prototype.find not available in IE.
|
|
425
|
+
function find(list, predicate) {
|
|
426
|
+
for (var i = 0, length = list.length; i < length; i++) {
|
|
427
|
+
if (predicate(list[i])) return list[i];
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function isContentEditable(node) {
|
|
432
|
+
return node.contentEditable === 'true';
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function isInput(node) {
|
|
436
|
+
return node.tagName === 'INPUT';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function isHiddenInput(node) {
|
|
440
|
+
return isInput(node) && node.type === 'hidden';
|
|
441
|
+
}
|
|
322
442
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
// "off" state, so we need to recursively check parents.
|
|
443
|
+
function isRadio(node) {
|
|
444
|
+
return isInput(node) && node.type === 'radio';
|
|
445
|
+
}
|
|
327
446
|
|
|
328
|
-
|
|
329
|
-
|
|
447
|
+
function isNonTabbableRadio(node) {
|
|
448
|
+
return isRadio(node) && !isTabbableRadio(node);
|
|
449
|
+
}
|
|
330
450
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
451
|
+
function getCheckedRadio(nodes) {
|
|
452
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
453
|
+
if (nodes[i].checked) {
|
|
454
|
+
return nodes[i];
|
|
334
455
|
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function isTabbableRadio(node) {
|
|
460
|
+
if (!node.name) return true;
|
|
461
|
+
// This won't account for the edge case where you have radio groups with the same
|
|
462
|
+
// in separate forms on the same page.
|
|
463
|
+
var radioSet = node.ownerDocument.querySelectorAll('input[type="radio"][name="' + node.name + '"]');
|
|
464
|
+
var checked = getCheckedRadio(radioSet);
|
|
465
|
+
return !checked || checked === node;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// An element is "untouchable" if *it or one of its ancestors* has
|
|
469
|
+
// `visibility: hidden` or `display: none`.
|
|
470
|
+
function UntouchabilityChecker(elementDocument) {
|
|
471
|
+
this.doc = elementDocument;
|
|
472
|
+
// Node cache must be refreshed on every check, in case
|
|
473
|
+
// the content of the element has changed. The cache contains tuples
|
|
474
|
+
// mapping nodes to their boolean result.
|
|
475
|
+
this.cache = [];
|
|
476
|
+
}
|
|
335
477
|
|
|
336
|
-
|
|
478
|
+
// getComputedStyle accurately reflects `visibility: hidden` of ancestors
|
|
479
|
+
// but not `display: none`, so we need to recursively check parents.
|
|
480
|
+
UntouchabilityChecker.prototype.hasDisplayNone = function hasDisplayNone(node, nodeComputedStyle) {
|
|
481
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
482
|
+
|
|
483
|
+
// Search for a cached result.
|
|
484
|
+
var cached = find(this.cache, function(item) {
|
|
485
|
+
return item === node;
|
|
486
|
+
});
|
|
487
|
+
if (cached) return cached[1];
|
|
488
|
+
|
|
489
|
+
nodeComputedStyle = nodeComputedStyle || this.doc.defaultView.getComputedStyle(node);
|
|
337
490
|
|
|
338
491
|
var result = false;
|
|
339
492
|
|
|
340
493
|
if (nodeComputedStyle.display === 'none') {
|
|
341
494
|
result = true;
|
|
342
495
|
} else if (node.parentNode) {
|
|
343
|
-
result =
|
|
496
|
+
result = this.hasDisplayNone(node.parentNode);
|
|
344
497
|
}
|
|
345
498
|
|
|
346
|
-
|
|
499
|
+
this.cache.push([node, result]);
|
|
347
500
|
|
|
348
501
|
return result;
|
|
349
|
-
|
|
502
|
+
}
|
|
350
503
|
|
|
351
|
-
|
|
352
|
-
|
|
504
|
+
UntouchabilityChecker.prototype.isUntouchable = function isUntouchable(node) {
|
|
505
|
+
if (node === this.doc.documentElement) return false;
|
|
506
|
+
var computedStyle = this.doc.defaultView.getComputedStyle(node);
|
|
507
|
+
if (this.hasDisplayNone(node, computedStyle)) return true;
|
|
508
|
+
return computedStyle.visibility === 'hidden';
|
|
509
|
+
}
|
|
353
510
|
|
|
354
|
-
|
|
511
|
+
module.exports = tabbable;
|
|
355
512
|
|
|
356
|
-
|
|
513
|
+
},{}],3:[function(require,module,exports){
|
|
514
|
+
module.exports = extend
|
|
357
515
|
|
|
358
|
-
|
|
359
|
-
|
|
516
|
+
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
517
|
+
|
|
518
|
+
function extend() {
|
|
519
|
+
var target = {}
|
|
520
|
+
|
|
521
|
+
for (var i = 0; i < arguments.length; i++) {
|
|
522
|
+
var source = arguments[i]
|
|
523
|
+
|
|
524
|
+
for (var key in source) {
|
|
525
|
+
if (hasOwnProperty.call(source, key)) {
|
|
526
|
+
target[key] = source[key]
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return target
|
|
360
532
|
}
|
|
361
533
|
|
|
362
534
|
},{}]},{},[1])(1)
|
|
363
|
-
});
|
|
535
|
+
});
|
package/dist/focus-trap.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){var tabbable=require("tabbable");var listeningFocusTrap=null;function focusTrap(element,userOptions){var tabbableNodes=[];var firstTabbableNode=null;var lastTabbableNode=null;var nodeFocusedBeforeActivation=null;var active=false;var paused=false;var tabEvent=null;var container=typeof element==="string"?document.querySelector(element):element;var config=userOptions||{};config.returnFocusOnDeactivate=userOptions&&userOptions.returnFocusOnDeactivate!==undefined?userOptions.returnFocusOnDeactivate:true;config.escapeDeactivates=userOptions&&userOptions.escapeDeactivates!==undefined?userOptions.escapeDeactivates:true;var trap={activate:activate,deactivate:deactivate,pause:pause,unpause:unpause};return trap;function activate(activateOptions){if(active)return;var defaultedActivateOptions={onActivate:activateOptions&&activateOptions.onActivate!==undefined?activateOptions.onActivate:config.onActivate};active=true;paused=false;nodeFocusedBeforeActivation=document.activeElement;if(defaultedActivateOptions.onActivate){defaultedActivateOptions.onActivate()}addListeners();return trap}function deactivate(deactivateOptions){if(!active)return;var defaultedDeactivateOptions={returnFocus:deactivateOptions&&deactivateOptions.returnFocus!==undefined?deactivateOptions.returnFocus:config.returnFocusOnDeactivate,onDeactivate:deactivateOptions&&deactivateOptions.onDeactivate!==undefined?deactivateOptions.onDeactivate:config.onDeactivate};removeListeners();if(defaultedDeactivateOptions.onDeactivate){defaultedDeactivateOptions.onDeactivate()}if(defaultedDeactivateOptions.returnFocus){setTimeout(function(){tryFocus(nodeFocusedBeforeActivation)},0)}active=false;paused=false;return this}function pause(){if(paused||!active)return;paused=true;removeListeners()}function unpause(){if(!paused||!active)return;paused=false;addListeners()}function addListeners(){if(!active)return;if(listeningFocusTrap){listeningFocusTrap.pause()}listeningFocusTrap=trap;updateTabbableNodes();setTimeout(function(){tryFocus(firstFocusNode())},0);document.addEventListener("focus",checkFocus,true);document.addEventListener("click",checkClick,true);document.addEventListener("mousedown",checkPointerDown,true);document.addEventListener("touchstart",checkPointerDown,true);document.addEventListener("keydown",checkKey,true);return trap}function removeListeners(){if(!active||listeningFocusTrap!==trap)return;document.removeEventListener("focus",checkFocus,true);document.removeEventListener("click",checkClick,true);document.removeEventListener("mousedown",checkPointerDown,true);document.removeEventListener("touchstart",checkPointerDown,true);document.removeEventListener("keydown",checkKey,true);listeningFocusTrap=null;return trap}function getNodeForOption(optionName){var optionValue=config[optionName];var node=optionValue;if(!optionValue){return null}if(typeof optionValue==="string"){node=document.querySelector(optionValue);if(!node){throw new Error("`"+optionName+"` refers to no known node")}}if(typeof optionValue==="function"){node=optionValue();if(!node){throw new Error("`"+optionName+"` did not return a node")}}return node}function firstFocusNode(){var node;if(getNodeForOption("initialFocus")!==null){node=getNodeForOption("initialFocus")}else if(container.contains(document.activeElement)){node=document.activeElement}else{node=tabbableNodes[0]||getNodeForOption("fallbackFocus")}if(!node){throw new Error("You can't have a focus-trap without at least one focusable element")}return node}function checkPointerDown(e){if(config.clickOutsideDeactivates&&!container.contains(e.target)){deactivate({returnFocus:false})}}function checkClick(e){if(config.clickOutsideDeactivates)return;if(container.contains(e.target))return;e.preventDefault();e.stopImmediatePropagation()}function checkFocus(e){if(container.contains(e.target))return;e.preventDefault();e.stopImmediatePropagation();if(typeof e.target.blur==="function")e.target.blur();if(tabEvent){readjustFocus(tabEvent)}}function checkKey(e){if(e.key==="Tab"||e.keyCode===9){handleTab(e)}if(config.escapeDeactivates!==false&&isEscapeEvent(e)){deactivate()}}function handleTab(e){updateTabbableNodes();if(e.target.hasAttribute("tabindex")&&Number(e.target.getAttribute("tabindex"))<0){return tabEvent=e}e.preventDefault();var currentFocusIndex=tabbableNodes.indexOf(e.target);if(e.shiftKey){if(e.target===firstTabbableNode||tabbableNodes.indexOf(e.target)===-1){return tryFocus(lastTabbableNode)}return tryFocus(tabbableNodes[currentFocusIndex-1])}if(e.target===lastTabbableNode)return tryFocus(firstTabbableNode);tryFocus(tabbableNodes[currentFocusIndex+1])}function updateTabbableNodes(){tabbableNodes=tabbable(container);firstTabbableNode=tabbableNodes[0];lastTabbableNode=tabbableNodes[tabbableNodes.length-1]}function readjustFocus(e){if(e.shiftKey)return tryFocus(lastTabbableNode);tryFocus(firstTabbableNode)}}function isEscapeEvent(e){return e.key==="Escape"||e.key==="Esc"||e.keyCode===27}function tryFocus(node){if(!node||!node.focus)return;if(node===document.activeElement)return;node.focus();if(node.tagName.toLowerCase()==="input"){node.select()}}module.exports=focusTrap},{tabbable:2}],2:[function(require,module,exports){module.exports=function(el){var basicTabbables=[];var orderedTabbables=[];var isUnavailable=createIsUnavailable();var candidateSelectors=["input","select","a[href]","textarea","button","[tabindex]"];var candidates=el.querySelectorAll(candidateSelectors);var candidate,candidateIndex;for(var i=0,l=candidates.length;i<l;i++){candidate=candidates[i];candidateIndex=candidate.tabIndex;if(candidateIndex<0||candidate.tagName==="INPUT"&&candidate.type==="hidden"||candidate.disabled||isUnavailable(candidate)){continue}if(candidateIndex===0){basicTabbables.push(candidate)}else{orderedTabbables.push({tabIndex:candidateIndex,node:candidate})}}var tabbableNodes=orderedTabbables.sort(function(a,b){return a.tabIndex-b.tabIndex}).map(function(a){return a.node});Array.prototype.push.apply(tabbableNodes,basicTabbables);return tabbableNodes};function createIsUnavailable(){var isOffCache=[];function isOff(node,nodeComputedStyle){if(node===document.documentElement)return false;for(var i=0,length=isOffCache.length;i<length;i++){if(isOffCache[i][0]===node)return isOffCache[i][1]}nodeComputedStyle=nodeComputedStyle||window.getComputedStyle(node);var result=false;if(nodeComputedStyle.display==="none"){result=true}else if(node.parentNode){result=isOff(node.parentNode)}isOffCache.push([node,result]);return result}return function isUnavailable(node){if(node===document.documentElement)return false;var computedStyle=window.getComputedStyle(node);if(isOff(node,computedStyle))return true;return computedStyle.visibility==="hidden"}}},{}]},{},[1])(1)});
|
|
1
|
+
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r}()({1:[function(require,module,exports){var tabbable=require("tabbable");var xtend=require("xtend");var activeFocusTraps=function(){var trapQueue=[];return{activateTrap:function(trap){if(trapQueue.length>0){var activeTrap=trapQueue[trapQueue.length-1];if(activeTrap!==trap){activeTrap.pause()}}var trapIndex=trapQueue.indexOf(trap);if(trapIndex===-1){trapQueue.push(trap)}else{trapQueue.splice(trapIndex,1);trapQueue.push(trap)}},deactivateTrap:function(trap){var trapIndex=trapQueue.indexOf(trap);if(trapIndex!==-1){trapQueue.splice(trapIndex,1)}if(trapQueue.length>0){trapQueue[trapQueue.length-1].unpause()}}}}();function focusTrap(element,userOptions){var doc=document;var container=typeof element==="string"?doc.querySelector(element):element;var config=xtend({returnFocusOnDeactivate:true,escapeDeactivates:true},userOptions);var state={firstTabbableNode:null,lastTabbableNode:null,nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:false,paused:false};var trap={activate:activate,deactivate:deactivate,pause:pause,unpause:unpause};return trap;function activate(activateOptions){if(state.active)return;updateTabbableNodes();state.active=true;state.paused=false;state.nodeFocusedBeforeActivation=doc.activeElement;var onActivate=activateOptions&&activateOptions.onActivate?activateOptions.onActivate:config.onActivate;if(onActivate){onActivate()}addListeners();return trap}function deactivate(deactivateOptions){if(!state.active)return;removeListeners();state.active=false;state.paused=false;activeFocusTraps.deactivateTrap(trap);var onDeactivate=deactivateOptions&&deactivateOptions.onDeactivate!==undefined?deactivateOptions.onDeactivate:config.onDeactivate;if(onDeactivate){onDeactivate()}var returnFocus=deactivateOptions&&deactivateOptions.returnFocus!==undefined?deactivateOptions.returnFocus:config.returnFocusOnDeactivate;if(returnFocus){delay(function(){tryFocus(state.nodeFocusedBeforeActivation)})}return trap}function pause(){if(state.paused||!state.active)return;state.paused=true;removeListeners()}function unpause(){if(!state.paused||!state.active)return;state.paused=false;addListeners()}function addListeners(){if(!state.active)return;activeFocusTraps.activateTrap(trap);updateTabbableNodes();delay(function(){tryFocus(getInitialFocusNode())});doc.addEventListener("focusin",checkFocusIn,true);doc.addEventListener("mousedown",checkPointerDown,true);doc.addEventListener("touchstart",checkPointerDown,true);doc.addEventListener("click",checkClick,true);doc.addEventListener("keydown",checkKey,true);return trap}function removeListeners(){if(!state.active)return;doc.removeEventListener("focusin",checkFocusIn,true);doc.removeEventListener("mousedown",checkPointerDown,true);doc.removeEventListener("touchstart",checkPointerDown,true);doc.removeEventListener("click",checkClick,true);doc.removeEventListener("keydown",checkKey,true);return trap}function getNodeForOption(optionName){var optionValue=config[optionName];var node=optionValue;if(!optionValue){return null}if(typeof optionValue==="string"){node=doc.querySelector(optionValue);if(!node){throw new Error("`"+optionName+"` refers to no known node")}}if(typeof optionValue==="function"){node=optionValue();if(!node){throw new Error("`"+optionName+"` did not return a node")}}return node}function getInitialFocusNode(){var node;if(getNodeForOption("initialFocus")!==null){node=getNodeForOption("initialFocus")}else if(container.contains(doc.activeElement)){node=doc.activeElement}else{node=state.firstTabbableNode||getNodeForOption("fallbackFocus")}if(!node){throw new Error("You can't have a focus-trap without at least one focusable element")}return node}function checkPointerDown(e){if(container.contains(e.target))return;if(config.clickOutsideDeactivates){deactivate({returnFocus:!tabbable.isFocusable(e.target)})}else{e.preventDefault()}}function checkFocusIn(e){if(container.contains(e.target)||e.target instanceof Document){return}e.stopImmediatePropagation();tryFocus(state.mostRecentlyFocusedNode||getInitialFocusNode())}function checkKey(e){if(config.escapeDeactivates!==false&&isEscapeEvent(e)){e.preventDefault();deactivate();return}if(isTabEvent(e)){checkTab(e);return}}function checkTab(e){updateTabbableNodes();if(e.shiftKey&&e.target===state.firstTabbableNode){e.preventDefault();tryFocus(state.lastTabbableNode);return}if(!e.shiftKey&&e.target===state.lastTabbableNode){e.preventDefault();tryFocus(state.firstTabbableNode);return}}function checkClick(e){if(config.clickOutsideDeactivates)return;if(container.contains(e.target))return;e.preventDefault();e.stopImmediatePropagation()}function updateTabbableNodes(){var tabbableNodes=tabbable(container);state.firstTabbableNode=tabbableNodes[0]||getInitialFocusNode();state.lastTabbableNode=tabbableNodes[tabbableNodes.length-1]||getInitialFocusNode()}function tryFocus(node){if(node===doc.activeElement)return;if(!node||!node.focus){tryFocus(getInitialFocusNode());return}node.focus();state.mostRecentlyFocusedNode=node;if(isSelectableInput(node)){node.select()}}}function isSelectableInput(node){return node.tagName&&node.tagName.toLowerCase()==="input"&&typeof node.select==="function"}function isEscapeEvent(e){return e.key==="Escape"||e.key==="Esc"||e.keyCode===27}function isTabEvent(e){return e.key==="Tab"||e.keyCode===9}function delay(fn){return setTimeout(fn,0)}module.exports=focusTrap},{tabbable:2,xtend:3}],2:[function(require,module,exports){var candidateSelectors=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'];var candidateSelector=candidateSelectors.join(",");var matches=typeof Element==="undefined"?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector;function tabbable(el,options){options=options||{};var elementDocument=el.ownerDocument||el;var regularTabbables=[];var orderedTabbables=[];var untouchabilityChecker=new UntouchabilityChecker(elementDocument);var candidates=el.querySelectorAll(candidateSelector);if(options.includeContainer){if(matches.call(el,candidateSelector)){candidates=Array.prototype.slice.apply(candidates);candidates.unshift(el)}}var i,candidate,candidateTabindex;for(i=0;i<candidates.length;i++){candidate=candidates[i];if(!isNodeMatchingSelectorTabbable(candidate,untouchabilityChecker))continue;candidateTabindex=getTabindex(candidate);if(candidateTabindex===0){regularTabbables.push(candidate)}else{orderedTabbables.push({documentOrder:i,tabIndex:candidateTabindex,node:candidate})}}var tabbableNodes=orderedTabbables.sort(sortOrderedTabbables).map(function(a){return a.node}).concat(regularTabbables);return tabbableNodes}tabbable.isTabbable=isTabbable;tabbable.isFocusable=isFocusable;function isNodeMatchingSelectorTabbable(node,untouchabilityChecker){if(!isNodeMatchingSelectorFocusable(node,untouchabilityChecker)||isNonTabbableRadio(node)||getTabindex(node)<0){return false}return true}function isTabbable(node,untouchabilityChecker){if(!node)throw new Error("No node provided");if(matches.call(node,candidateSelector)===false)return false;return isNodeMatchingSelectorTabbable(node,untouchabilityChecker)}function isNodeMatchingSelectorFocusable(node,untouchabilityChecker){untouchabilityChecker=untouchabilityChecker||new UntouchabilityChecker(node.ownerDocument||node);if(node.disabled||isHiddenInput(node)||untouchabilityChecker.isUntouchable(node)){return false}return true}var focusableCandidateSelector=candidateSelectors.concat("iframe").join(",");function isFocusable(node,untouchabilityChecker){if(!node)throw new Error("No node provided");if(matches.call(node,focusableCandidateSelector)===false)return false;return isNodeMatchingSelectorFocusable(node,untouchabilityChecker)}function getTabindex(node){var tabindexAttr=parseInt(node.getAttribute("tabindex"),10);if(!isNaN(tabindexAttr))return tabindexAttr;if(isContentEditable(node))return 0;return node.tabIndex}function sortOrderedTabbables(a,b){return a.tabIndex===b.tabIndex?a.documentOrder-b.documentOrder:a.tabIndex-b.tabIndex}function find(list,predicate){for(var i=0,length=list.length;i<length;i++){if(predicate(list[i]))return list[i]}}function isContentEditable(node){return node.contentEditable==="true"}function isInput(node){return node.tagName==="INPUT"}function isHiddenInput(node){return isInput(node)&&node.type==="hidden"}function isRadio(node){return isInput(node)&&node.type==="radio"}function isNonTabbableRadio(node){return isRadio(node)&&!isTabbableRadio(node)}function getCheckedRadio(nodes){for(var i=0;i<nodes.length;i++){if(nodes[i].checked){return nodes[i]}}}function isTabbableRadio(node){if(!node.name)return true;var radioSet=node.ownerDocument.querySelectorAll('input[type="radio"][name="'+node.name+'"]');var checked=getCheckedRadio(radioSet);return!checked||checked===node}function UntouchabilityChecker(elementDocument){this.doc=elementDocument;this.cache=[]}UntouchabilityChecker.prototype.hasDisplayNone=function hasDisplayNone(node,nodeComputedStyle){if(node.nodeType!==Node.ELEMENT_NODE)return false;var cached=find(this.cache,function(item){return item===node});if(cached)return cached[1];nodeComputedStyle=nodeComputedStyle||this.doc.defaultView.getComputedStyle(node);var result=false;if(nodeComputedStyle.display==="none"){result=true}else if(node.parentNode){result=this.hasDisplayNone(node.parentNode)}this.cache.push([node,result]);return result};UntouchabilityChecker.prototype.isUntouchable=function isUntouchable(node){if(node===this.doc.documentElement)return false;var computedStyle=this.doc.defaultView.getComputedStyle(node);if(this.hasDisplayNone(node,computedStyle))return true;return computedStyle.visibility==="hidden"};module.exports=tabbable},{}],3:[function(require,module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(){var target={};for(var i=0;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(hasOwnProperty.call(source,key)){target[key]=source[key]}}}return target}},{}]},{},[1])(1)});
|
package/index.js
CHANGED
|
@@ -1,52 +1,86 @@
|
|
|
1
1
|
var tabbable = require('tabbable');
|
|
2
|
+
var xtend = require('xtend');
|
|
3
|
+
|
|
4
|
+
var activeFocusTraps = (function() {
|
|
5
|
+
var trapQueue = [];
|
|
6
|
+
return {
|
|
7
|
+
activateTrap: function(trap) {
|
|
8
|
+
if (trapQueue.length > 0) {
|
|
9
|
+
var activeTrap = trapQueue[trapQueue.length - 1];
|
|
10
|
+
if (activeTrap !== trap) {
|
|
11
|
+
activeTrap.pause();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
var trapIndex = trapQueue.indexOf(trap);
|
|
16
|
+
if (trapIndex === -1) {
|
|
17
|
+
trapQueue.push(trap);
|
|
18
|
+
} else {
|
|
19
|
+
// move this existing trap to the front of the queue
|
|
20
|
+
trapQueue.splice(trapIndex, 1);
|
|
21
|
+
trapQueue.push(trap);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
deactivateTrap: function(trap) {
|
|
26
|
+
var trapIndex = trapQueue.indexOf(trap);
|
|
27
|
+
if (trapIndex !== -1) {
|
|
28
|
+
trapQueue.splice(trapIndex, 1);
|
|
29
|
+
}
|
|
2
30
|
|
|
3
|
-
|
|
31
|
+
if (trapQueue.length > 0) {
|
|
32
|
+
trapQueue[trapQueue.length - 1].unpause();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
4
37
|
|
|
5
38
|
function focusTrap(element, userOptions) {
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
:
|
|
39
|
+
var doc = document;
|
|
40
|
+
var container =
|
|
41
|
+
typeof element === 'string' ? doc.querySelector(element) : element;
|
|
42
|
+
|
|
43
|
+
var config = xtend(
|
|
44
|
+
{
|
|
45
|
+
returnFocusOnDeactivate: true,
|
|
46
|
+
escapeDeactivates: true
|
|
47
|
+
},
|
|
48
|
+
userOptions
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
var state = {
|
|
52
|
+
firstTabbableNode: null,
|
|
53
|
+
lastTabbableNode: null,
|
|
54
|
+
nodeFocusedBeforeActivation: null,
|
|
55
|
+
mostRecentlyFocusedNode: null,
|
|
56
|
+
active: false,
|
|
57
|
+
paused: false
|
|
58
|
+
};
|
|
25
59
|
|
|
26
60
|
var trap = {
|
|
27
61
|
activate: activate,
|
|
28
62
|
deactivate: deactivate,
|
|
29
63
|
pause: pause,
|
|
30
|
-
unpause: unpause
|
|
64
|
+
unpause: unpause
|
|
31
65
|
};
|
|
32
66
|
|
|
33
67
|
return trap;
|
|
34
68
|
|
|
35
69
|
function activate(activateOptions) {
|
|
36
|
-
if (active) return;
|
|
70
|
+
if (state.active) return;
|
|
37
71
|
|
|
38
|
-
|
|
39
|
-
onActivate: (activateOptions && activateOptions.onActivate !== undefined)
|
|
40
|
-
? activateOptions.onActivate
|
|
41
|
-
: config.onActivate,
|
|
42
|
-
};
|
|
72
|
+
updateTabbableNodes();
|
|
43
73
|
|
|
44
|
-
active = true;
|
|
45
|
-
paused = false;
|
|
46
|
-
nodeFocusedBeforeActivation =
|
|
74
|
+
state.active = true;
|
|
75
|
+
state.paused = false;
|
|
76
|
+
state.nodeFocusedBeforeActivation = doc.activeElement;
|
|
47
77
|
|
|
48
|
-
|
|
49
|
-
|
|
78
|
+
var onActivate =
|
|
79
|
+
activateOptions && activateOptions.onActivate
|
|
80
|
+
? activateOptions.onActivate
|
|
81
|
+
: config.onActivate;
|
|
82
|
+
if (onActivate) {
|
|
83
|
+
onActivate();
|
|
50
84
|
}
|
|
51
85
|
|
|
52
86
|
addListeners();
|
|
@@ -54,79 +88,77 @@ function focusTrap(element, userOptions) {
|
|
|
54
88
|
}
|
|
55
89
|
|
|
56
90
|
function deactivate(deactivateOptions) {
|
|
57
|
-
if (!active) return;
|
|
58
|
-
|
|
59
|
-
var defaultedDeactivateOptions = {
|
|
60
|
-
returnFocus: (deactivateOptions && deactivateOptions.returnFocus !== undefined)
|
|
61
|
-
? deactivateOptions.returnFocus
|
|
62
|
-
: config.returnFocusOnDeactivate,
|
|
63
|
-
onDeactivate: (deactivateOptions && deactivateOptions.onDeactivate !== undefined)
|
|
64
|
-
? deactivateOptions.onDeactivate
|
|
65
|
-
: config.onDeactivate,
|
|
66
|
-
};
|
|
91
|
+
if (!state.active) return;
|
|
67
92
|
|
|
68
93
|
removeListeners();
|
|
94
|
+
state.active = false;
|
|
95
|
+
state.paused = false;
|
|
69
96
|
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
activeFocusTraps.deactivateTrap(trap);
|
|
98
|
+
|
|
99
|
+
var onDeactivate =
|
|
100
|
+
deactivateOptions && deactivateOptions.onDeactivate !== undefined
|
|
101
|
+
? deactivateOptions.onDeactivate
|
|
102
|
+
: config.onDeactivate;
|
|
103
|
+
if (onDeactivate) {
|
|
104
|
+
onDeactivate();
|
|
72
105
|
}
|
|
73
106
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
var returnFocus =
|
|
108
|
+
deactivateOptions && deactivateOptions.returnFocus !== undefined
|
|
109
|
+
? deactivateOptions.returnFocus
|
|
110
|
+
: config.returnFocusOnDeactivate;
|
|
111
|
+
if (returnFocus) {
|
|
112
|
+
delay(function() {
|
|
113
|
+
tryFocus(state.nodeFocusedBeforeActivation);
|
|
114
|
+
});
|
|
78
115
|
}
|
|
79
116
|
|
|
80
|
-
|
|
81
|
-
paused = false;
|
|
82
|
-
return this;
|
|
117
|
+
return trap;
|
|
83
118
|
}
|
|
84
119
|
|
|
85
120
|
function pause() {
|
|
86
|
-
if (paused || !active) return;
|
|
87
|
-
paused = true;
|
|
121
|
+
if (state.paused || !state.active) return;
|
|
122
|
+
state.paused = true;
|
|
88
123
|
removeListeners();
|
|
89
124
|
}
|
|
90
125
|
|
|
91
126
|
function unpause() {
|
|
92
|
-
if (!paused || !active) return;
|
|
93
|
-
paused = false;
|
|
127
|
+
if (!state.paused || !state.active) return;
|
|
128
|
+
state.paused = false;
|
|
94
129
|
addListeners();
|
|
95
130
|
}
|
|
96
131
|
|
|
97
132
|
function addListeners() {
|
|
98
|
-
if (!active) return;
|
|
133
|
+
if (!state.active) return;
|
|
99
134
|
|
|
100
135
|
// There can be only one listening focus trap at a time
|
|
101
|
-
|
|
102
|
-
listeningFocusTrap.pause();
|
|
103
|
-
}
|
|
104
|
-
listeningFocusTrap = trap;
|
|
136
|
+
activeFocusTraps.activateTrap(trap);
|
|
105
137
|
|
|
106
138
|
updateTabbableNodes();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
139
|
+
|
|
140
|
+
// Delay ensures that the focused element doesn't capture the event
|
|
141
|
+
// that caused the focus trap activation.
|
|
142
|
+
delay(function() {
|
|
143
|
+
tryFocus(getInitialFocusNode());
|
|
144
|
+
});
|
|
145
|
+
doc.addEventListener('focusin', checkFocusIn, true);
|
|
146
|
+
doc.addEventListener('mousedown', checkPointerDown, true);
|
|
147
|
+
doc.addEventListener('touchstart', checkPointerDown, true);
|
|
148
|
+
doc.addEventListener('click', checkClick, true);
|
|
149
|
+
doc.addEventListener('keydown', checkKey, true);
|
|
116
150
|
|
|
117
151
|
return trap;
|
|
118
152
|
}
|
|
119
153
|
|
|
120
154
|
function removeListeners() {
|
|
121
|
-
if (!active
|
|
122
|
-
|
|
123
|
-
document.removeEventListener('focus', checkFocus, true);
|
|
124
|
-
document.removeEventListener('click', checkClick, true);
|
|
125
|
-
document.removeEventListener('mousedown', checkPointerDown, true);
|
|
126
|
-
document.removeEventListener('touchstart', checkPointerDown, true);
|
|
127
|
-
document.removeEventListener('keydown', checkKey, true);
|
|
155
|
+
if (!state.active) return;
|
|
128
156
|
|
|
129
|
-
|
|
157
|
+
doc.removeEventListener('focusin', checkFocusIn, true);
|
|
158
|
+
doc.removeEventListener('mousedown', checkPointerDown, true);
|
|
159
|
+
doc.removeEventListener('touchstart', checkPointerDown, true);
|
|
160
|
+
doc.removeEventListener('click', checkClick, true);
|
|
161
|
+
doc.removeEventListener('keydown', checkKey, true);
|
|
130
162
|
|
|
131
163
|
return trap;
|
|
132
164
|
}
|
|
@@ -138,7 +170,7 @@ function focusTrap(element, userOptions) {
|
|
|
138
170
|
return null;
|
|
139
171
|
}
|
|
140
172
|
if (typeof optionValue === 'string') {
|
|
141
|
-
node =
|
|
173
|
+
node = doc.querySelector(optionValue);
|
|
142
174
|
if (!node) {
|
|
143
175
|
throw new Error('`' + optionName + '` refers to no known node');
|
|
144
176
|
}
|
|
@@ -152,107 +184,125 @@ function focusTrap(element, userOptions) {
|
|
|
152
184
|
return node;
|
|
153
185
|
}
|
|
154
186
|
|
|
155
|
-
function
|
|
187
|
+
function getInitialFocusNode() {
|
|
156
188
|
var node;
|
|
157
189
|
if (getNodeForOption('initialFocus') !== null) {
|
|
158
190
|
node = getNodeForOption('initialFocus');
|
|
159
|
-
} else if (container.contains(
|
|
160
|
-
node =
|
|
191
|
+
} else if (container.contains(doc.activeElement)) {
|
|
192
|
+
node = doc.activeElement;
|
|
161
193
|
} else {
|
|
162
|
-
node =
|
|
194
|
+
node = state.firstTabbableNode || getNodeForOption('fallbackFocus');
|
|
163
195
|
}
|
|
164
196
|
|
|
165
197
|
if (!node) {
|
|
166
|
-
throw new Error(
|
|
198
|
+
throw new Error(
|
|
199
|
+
"You can't have a focus-trap without at least one focusable element"
|
|
200
|
+
);
|
|
167
201
|
}
|
|
168
202
|
|
|
169
203
|
return node;
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
// This needs to be done on mousedown and touchstart instead of click
|
|
173
|
-
// so that it precedes the focus event
|
|
207
|
+
// so that it precedes the focus event.
|
|
174
208
|
function checkPointerDown(e) {
|
|
175
|
-
if (config.clickOutsideDeactivates && !container.contains(e.target)) {
|
|
176
|
-
deactivate({ returnFocus: false });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function checkClick(e) {
|
|
181
|
-
if (config.clickOutsideDeactivates) return;
|
|
182
209
|
if (container.contains(e.target)) return;
|
|
183
|
-
|
|
184
|
-
|
|
210
|
+
if (config.clickOutsideDeactivates) {
|
|
211
|
+
deactivate({
|
|
212
|
+
returnFocus: !tabbable.isFocusable(e.target)
|
|
213
|
+
});
|
|
214
|
+
} else {
|
|
215
|
+
e.preventDefault();
|
|
216
|
+
}
|
|
185
217
|
}
|
|
186
218
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
e.
|
|
191
|
-
|
|
192
|
-
if (typeof e.target.blur === 'function') e.target.blur();
|
|
193
|
-
|
|
194
|
-
if (tabEvent) {
|
|
195
|
-
readjustFocus(tabEvent);
|
|
219
|
+
// In case focus escapes the trap for some strange reason, pull it back in.
|
|
220
|
+
function checkFocusIn(e) {
|
|
221
|
+
// In Firefox when you Tab out of an iframe the Document is briefly focused.
|
|
222
|
+
if (container.contains(e.target) || e.target instanceof Document) {
|
|
223
|
+
return;
|
|
196
224
|
}
|
|
225
|
+
e.stopImmediatePropagation();
|
|
226
|
+
tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
|
|
197
227
|
}
|
|
198
228
|
|
|
199
229
|
function checkKey(e) {
|
|
200
|
-
if (e.key === 'Tab' || e.keyCode === 9) {
|
|
201
|
-
handleTab(e);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
230
|
if (config.escapeDeactivates !== false && isEscapeEvent(e)) {
|
|
231
|
+
e.preventDefault();
|
|
205
232
|
deactivate();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (isTabEvent(e)) {
|
|
236
|
+
checkTab(e);
|
|
237
|
+
return;
|
|
206
238
|
}
|
|
207
239
|
}
|
|
208
240
|
|
|
209
|
-
|
|
241
|
+
// Hijack Tab events on the first and last focusable nodes of the trap,
|
|
242
|
+
// in order to prevent focus from escaping. If it escapes for even a
|
|
243
|
+
// moment it can end up scrolling the page and causing confusion so we
|
|
244
|
+
// kind of need to capture the action at the keydown phase.
|
|
245
|
+
function checkTab(e) {
|
|
210
246
|
updateTabbableNodes();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
247
|
+
if (e.shiftKey && e.target === state.firstTabbableNode) {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
tryFocus(state.lastTabbableNode);
|
|
250
|
+
return;
|
|
214
251
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (e.shiftKey) {
|
|
220
|
-
if (e.target === firstTabbableNode || tabbableNodes.indexOf(e.target) === -1) {
|
|
221
|
-
return tryFocus(lastTabbableNode);
|
|
222
|
-
}
|
|
223
|
-
return tryFocus(tabbableNodes[currentFocusIndex - 1]);
|
|
252
|
+
if (!e.shiftKey && e.target === state.lastTabbableNode) {
|
|
253
|
+
e.preventDefault();
|
|
254
|
+
tryFocus(state.firstTabbableNode);
|
|
255
|
+
return;
|
|
224
256
|
}
|
|
257
|
+
}
|
|
225
258
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
259
|
+
function checkClick(e) {
|
|
260
|
+
if (config.clickOutsideDeactivates) return;
|
|
261
|
+
if (container.contains(e.target)) return;
|
|
262
|
+
e.preventDefault();
|
|
263
|
+
e.stopImmediatePropagation();
|
|
229
264
|
}
|
|
230
265
|
|
|
231
266
|
function updateTabbableNodes() {
|
|
232
|
-
tabbableNodes = tabbable(container);
|
|
233
|
-
firstTabbableNode = tabbableNodes[0];
|
|
234
|
-
lastTabbableNode =
|
|
267
|
+
var tabbableNodes = tabbable(container);
|
|
268
|
+
state.firstTabbableNode = tabbableNodes[0] || getInitialFocusNode();
|
|
269
|
+
state.lastTabbableNode =
|
|
270
|
+
tabbableNodes[tabbableNodes.length - 1] || getInitialFocusNode();
|
|
235
271
|
}
|
|
236
272
|
|
|
237
|
-
function
|
|
238
|
-
if (
|
|
273
|
+
function tryFocus(node) {
|
|
274
|
+
if (node === doc.activeElement) return;
|
|
275
|
+
if (!node || !node.focus) {
|
|
276
|
+
tryFocus(getInitialFocusNode());
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
239
279
|
|
|
240
|
-
|
|
280
|
+
node.focus();
|
|
281
|
+
state.mostRecentlyFocusedNode = node;
|
|
282
|
+
if (isSelectableInput(node)) {
|
|
283
|
+
node.select();
|
|
284
|
+
}
|
|
241
285
|
}
|
|
242
286
|
}
|
|
243
287
|
|
|
288
|
+
function isSelectableInput(node) {
|
|
289
|
+
return (
|
|
290
|
+
node.tagName &&
|
|
291
|
+
node.tagName.toLowerCase() === 'input' &&
|
|
292
|
+
typeof node.select === 'function'
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
244
296
|
function isEscapeEvent(e) {
|
|
245
297
|
return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
|
|
246
298
|
}
|
|
247
299
|
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
300
|
+
function isTabEvent(e) {
|
|
301
|
+
return e.key === 'Tab' || e.keyCode === 9;
|
|
302
|
+
}
|
|
251
303
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
node.select();
|
|
255
|
-
}
|
|
304
|
+
function delay(fn) {
|
|
305
|
+
return setTimeout(fn, 0);
|
|
256
306
|
}
|
|
257
307
|
|
|
258
308
|
module.exports = focusTrap;
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "focus-trap",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.2",
|
|
4
4
|
"description": "Trap focus within a DOM node.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"precommit": "lint-staged",
|
|
9
|
+
"format": "prettier --write '**/*.{js,json}'",
|
|
8
10
|
"lint": "eslint .",
|
|
9
11
|
"demo-bundle": "browserify demo/js/index.js -o demo/demo-bundle.js",
|
|
10
12
|
"clean": "del-cli dist && make-dir dist",
|
|
@@ -34,14 +36,18 @@
|
|
|
34
36
|
},
|
|
35
37
|
"homepage": "https://github.com/davidtheclark/focus-trap#readme",
|
|
36
38
|
"dependencies": {
|
|
37
|
-
"tabbable": "^1.
|
|
39
|
+
"tabbable": "^3.1.2",
|
|
40
|
+
"xtend": "^4.0.1"
|
|
38
41
|
},
|
|
39
42
|
"devDependencies": {
|
|
40
43
|
"browserify": "^13.3.0",
|
|
41
44
|
"budo": "^9.4.1",
|
|
42
45
|
"del-cli": "^1.1.0",
|
|
43
46
|
"eslint": "^3.13.1",
|
|
47
|
+
"husky": "^0.14.3",
|
|
48
|
+
"lint-staged": "^7.2.0",
|
|
44
49
|
"make-dir-cli": "^1.0.0",
|
|
50
|
+
"prettier": "^1.14.0",
|
|
45
51
|
"uglify-js": "^3.3.22"
|
|
46
52
|
},
|
|
47
53
|
"files": [
|