focus-trap 7.0.0 → 7.2.0
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 +17 -0
- package/README.md +43 -31
- package/dist/focus-trap.esm.js +138 -166
- package/dist/focus-trap.esm.js.map +1 -1
- package/dist/focus-trap.esm.min.js +2 -2
- package/dist/focus-trap.esm.min.js.map +1 -1
- package/dist/focus-trap.js +138 -166
- package/dist/focus-trap.js.map +1 -1
- package/dist/focus-trap.min.js +2 -2
- package/dist/focus-trap.min.js.map +1 -1
- package/dist/focus-trap.umd.js +138 -166
- package/dist/focus-trap.umd.js.map +1 -1
- package/dist/focus-trap.umd.min.js +2 -2
- package/dist/focus-trap.umd.min.js.map +1 -1
- package/index.d.ts +37 -5
- package/index.js +74 -45
- package/package.json +20 -20
package/dist/focus-trap.esm.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* focus-trap 7.
|
|
2
|
+
* focus-trap 7.2.0
|
|
3
3
|
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
|
|
4
4
|
*/
|
|
5
5
|
import { tabbable, focusable, isTabbable, isFocusable } from 'tabbable';
|
|
6
6
|
|
|
7
7
|
function ownKeys(object, enumerableOnly) {
|
|
8
8
|
var keys = Object.keys(object);
|
|
9
|
-
|
|
10
9
|
if (Object.getOwnPropertySymbols) {
|
|
11
10
|
var symbols = Object.getOwnPropertySymbols(object);
|
|
12
11
|
enumerableOnly && (symbols = symbols.filter(function (sym) {
|
|
13
12
|
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
|
|
14
13
|
})), keys.push.apply(keys, symbols);
|
|
15
14
|
}
|
|
16
|
-
|
|
17
15
|
return keys;
|
|
18
16
|
}
|
|
19
|
-
|
|
20
17
|
function _objectSpread2(target) {
|
|
21
18
|
for (var i = 1; i < arguments.length; i++) {
|
|
22
19
|
var source = null != arguments[i] ? arguments[i] : {};
|
|
@@ -26,11 +23,10 @@ function _objectSpread2(target) {
|
|
|
26
23
|
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
27
24
|
});
|
|
28
25
|
}
|
|
29
|
-
|
|
30
26
|
return target;
|
|
31
27
|
}
|
|
32
|
-
|
|
33
28
|
function _defineProperty(obj, key, value) {
|
|
29
|
+
key = _toPropertyKey(key);
|
|
34
30
|
if (key in obj) {
|
|
35
31
|
Object.defineProperty(obj, key, {
|
|
36
32
|
value: value,
|
|
@@ -41,64 +37,75 @@ function _defineProperty(obj, key, value) {
|
|
|
41
37
|
} else {
|
|
42
38
|
obj[key] = value;
|
|
43
39
|
}
|
|
44
|
-
|
|
45
40
|
return obj;
|
|
46
41
|
}
|
|
42
|
+
function _toPrimitive(input, hint) {
|
|
43
|
+
if (typeof input !== "object" || input === null) return input;
|
|
44
|
+
var prim = input[Symbol.toPrimitive];
|
|
45
|
+
if (prim !== undefined) {
|
|
46
|
+
var res = prim.call(input, hint || "default");
|
|
47
|
+
if (typeof res !== "object") return res;
|
|
48
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
49
|
+
}
|
|
50
|
+
return (hint === "string" ? String : Number)(input);
|
|
51
|
+
}
|
|
52
|
+
function _toPropertyKey(arg) {
|
|
53
|
+
var key = _toPrimitive(arg, "string");
|
|
54
|
+
return typeof key === "symbol" ? key : String(key);
|
|
55
|
+
}
|
|
47
56
|
|
|
48
|
-
var activeFocusTraps =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (activeTrap !== trap) {
|
|
56
|
-
activeTrap.pause();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
var trapIndex = trapQueue.indexOf(trap);
|
|
61
|
-
|
|
62
|
-
if (trapIndex === -1) {
|
|
63
|
-
trapQueue.push(trap);
|
|
64
|
-
} else {
|
|
65
|
-
// move this existing trap to the front of the queue
|
|
66
|
-
trapQueue.splice(trapIndex, 1);
|
|
67
|
-
trapQueue.push(trap);
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
deactivateTrap: function deactivateTrap(trap) {
|
|
71
|
-
var trapIndex = trapQueue.indexOf(trap);
|
|
72
|
-
|
|
73
|
-
if (trapIndex !== -1) {
|
|
74
|
-
trapQueue.splice(trapIndex, 1);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (trapQueue.length > 0) {
|
|
78
|
-
trapQueue[trapQueue.length - 1].unpause();
|
|
57
|
+
var activeFocusTraps = {
|
|
58
|
+
activateTrap: function activateTrap(trapStack, trap) {
|
|
59
|
+
if (trapStack.length > 0) {
|
|
60
|
+
var activeTrap = trapStack[trapStack.length - 1];
|
|
61
|
+
if (activeTrap !== trap) {
|
|
62
|
+
activeTrap.pause();
|
|
79
63
|
}
|
|
80
64
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
65
|
+
var trapIndex = trapStack.indexOf(trap);
|
|
66
|
+
if (trapIndex === -1) {
|
|
67
|
+
trapStack.push(trap);
|
|
68
|
+
} else {
|
|
69
|
+
// move this existing trap to the front of the queue
|
|
70
|
+
trapStack.splice(trapIndex, 1);
|
|
71
|
+
trapStack.push(trap);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
deactivateTrap: function deactivateTrap(trapStack, trap) {
|
|
75
|
+
var trapIndex = trapStack.indexOf(trap);
|
|
76
|
+
if (trapIndex !== -1) {
|
|
77
|
+
trapStack.splice(trapIndex, 1);
|
|
78
|
+
}
|
|
79
|
+
if (trapStack.length > 0) {
|
|
80
|
+
trapStack[trapStack.length - 1].unpause();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
84
|
var isSelectableInput = function isSelectableInput(node) {
|
|
85
85
|
return node.tagName && node.tagName.toLowerCase() === 'input' && typeof node.select === 'function';
|
|
86
86
|
};
|
|
87
|
-
|
|
88
87
|
var isEscapeEvent = function isEscapeEvent(e) {
|
|
89
88
|
return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
|
|
90
89
|
};
|
|
91
|
-
|
|
92
90
|
var isTabEvent = function isTabEvent(e) {
|
|
93
91
|
return e.key === 'Tab' || e.keyCode === 9;
|
|
94
92
|
};
|
|
95
93
|
|
|
94
|
+
// checks for TAB by default
|
|
95
|
+
var isKeyForward = function isKeyForward(e) {
|
|
96
|
+
return isTabEvent(e) && !e.shiftKey;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// checks for SHIFT+TAB by default
|
|
100
|
+
var isKeyBackward = function isKeyBackward(e) {
|
|
101
|
+
return isTabEvent(e) && e.shiftKey;
|
|
102
|
+
};
|
|
96
103
|
var delay = function delay(fn) {
|
|
97
104
|
return setTimeout(fn, 0);
|
|
98
|
-
};
|
|
99
|
-
// of Array.findIndex() for our needs
|
|
100
|
-
|
|
105
|
+
};
|
|
101
106
|
|
|
107
|
+
// Array.find/findIndex() are not supported on IE; this replicates enough
|
|
108
|
+
// of Array.findIndex() for our needs
|
|
102
109
|
var findIndex = function findIndex(arr, fn) {
|
|
103
110
|
var idx = -1;
|
|
104
111
|
arr.every(function (value, i) {
|
|
@@ -109,8 +116,10 @@ var findIndex = function findIndex(arr, fn) {
|
|
|
109
116
|
|
|
110
117
|
return true; // next
|
|
111
118
|
});
|
|
119
|
+
|
|
112
120
|
return idx;
|
|
113
121
|
};
|
|
122
|
+
|
|
114
123
|
/**
|
|
115
124
|
* Get an option's value when it could be a plain value, or a handler that provides
|
|
116
125
|
* the value.
|
|
@@ -118,16 +127,12 @@ var findIndex = function findIndex(arr, fn) {
|
|
|
118
127
|
* @param {...*} [params] Any parameters to pass to the handler, if `value` is a function.
|
|
119
128
|
* @returns {*} The `value`, or the handler's returned value.
|
|
120
129
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
123
130
|
var valueOrHandler = function valueOrHandler(value) {
|
|
124
131
|
for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
125
132
|
params[_key - 1] = arguments[_key];
|
|
126
133
|
}
|
|
127
|
-
|
|
128
134
|
return typeof value === 'function' ? value.apply(void 0, params) : value;
|
|
129
135
|
};
|
|
130
|
-
|
|
131
136
|
var getActualTarget = function getActualTarget(event) {
|
|
132
137
|
// NOTE: If the trap is _inside_ a shadow DOM, event.target will always be the
|
|
133
138
|
// shadow host. However, event.target.composedPath() will be an array of
|
|
@@ -139,17 +144,21 @@ var getActualTarget = function getActualTarget(event) {
|
|
|
139
144
|
return event.target.shadowRoot && typeof event.composedPath === 'function' ? event.composedPath()[0] : event.target;
|
|
140
145
|
};
|
|
141
146
|
|
|
147
|
+
// NOTE: this must be _outside_ `createFocusTrap()` to make sure all traps in this
|
|
148
|
+
// current instance use the same stack if `userOptions.trapStack` isn't specified
|
|
149
|
+
var internalTrapStack = [];
|
|
142
150
|
var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
143
151
|
// SSR: a live trap shouldn't be created in this type of environment so this
|
|
144
152
|
// should be safe code to execute if the `document` option isn't specified
|
|
145
153
|
var doc = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.document) || document;
|
|
146
|
-
|
|
154
|
+
var trapStack = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.trapStack) || internalTrapStack;
|
|
147
155
|
var config = _objectSpread2({
|
|
148
156
|
returnFocusOnDeactivate: true,
|
|
149
157
|
escapeDeactivates: true,
|
|
150
|
-
delayInitialFocus: true
|
|
158
|
+
delayInitialFocus: true,
|
|
159
|
+
isKeyForward: isKeyForward,
|
|
160
|
+
isKeyBackward: isKeyBackward
|
|
151
161
|
}, userOptions);
|
|
152
|
-
|
|
153
162
|
var state = {
|
|
154
163
|
// containers given to createFocusTrap()
|
|
155
164
|
// @type {Array<HTMLElement>}
|
|
@@ -169,6 +178,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
169
178
|
// }>}
|
|
170
179
|
containerGroups: [],
|
|
171
180
|
// same order/length as `containers` list
|
|
181
|
+
|
|
172
182
|
// references to objects in `containerGroups`, but only those that actually have
|
|
173
183
|
// tabbable nodes in them
|
|
174
184
|
// NOTE: same order as `containers` and `containerGroups`, but __not necessarily__
|
|
@@ -192,10 +202,10 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
192
202
|
* @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName`
|
|
193
203
|
* IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used.
|
|
194
204
|
*/
|
|
195
|
-
|
|
196
205
|
var getOption = function getOption(configOverrideOptions, optionName, configOptionName) {
|
|
197
206
|
return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName];
|
|
198
207
|
};
|
|
208
|
+
|
|
199
209
|
/**
|
|
200
210
|
* Finds the index of the container that contains the element.
|
|
201
211
|
* @param {HTMLElement} element
|
|
@@ -203,16 +213,15 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
203
213
|
* `state.containerGroups` (the order/length of these lists are the same); -1
|
|
204
214
|
* if the element isn't found.
|
|
205
215
|
*/
|
|
206
|
-
|
|
207
|
-
|
|
208
216
|
var findContainerIndex = function findContainerIndex(element) {
|
|
209
217
|
// NOTE: search `containerGroups` because it's possible a group contains no tabbable
|
|
210
218
|
// nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`)
|
|
211
219
|
// and we still need to find the element in there
|
|
212
220
|
return state.containerGroups.findIndex(function (_ref) {
|
|
213
221
|
var container = _ref.container,
|
|
214
|
-
|
|
215
|
-
return container.contains(element) ||
|
|
222
|
+
tabbableNodes = _ref.tabbableNodes;
|
|
223
|
+
return container.contains(element) ||
|
|
224
|
+
// fall back to explicit tabbable search which will take into consideration any
|
|
216
225
|
// web components if the `tabbableOptions.getShadowRoot` option was used for
|
|
217
226
|
// the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't
|
|
218
227
|
// look inside web components even if open)
|
|
@@ -221,6 +230,7 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
221
230
|
});
|
|
222
231
|
});
|
|
223
232
|
};
|
|
233
|
+
|
|
224
234
|
/**
|
|
225
235
|
* Gets the node for the given option, which is expected to be an option that
|
|
226
236
|
* can be either a DOM node, a string that is a selector to get a node, `false`
|
|
@@ -234,19 +244,14 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
234
244
|
* @throws {Error} If the option is set, not `false`, and is not, or does not
|
|
235
245
|
* resolve to a node.
|
|
236
246
|
*/
|
|
237
|
-
|
|
238
|
-
|
|
239
247
|
var getNodeForOption = function getNodeForOption(optionName) {
|
|
240
248
|
var optionValue = config[optionName];
|
|
241
|
-
|
|
242
249
|
if (typeof optionValue === 'function') {
|
|
243
250
|
for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
244
251
|
params[_key2 - 1] = arguments[_key2];
|
|
245
252
|
}
|
|
246
|
-
|
|
247
253
|
optionValue = optionValue.apply(void 0, params);
|
|
248
254
|
}
|
|
249
|
-
|
|
250
255
|
if (optionValue === true) {
|
|
251
256
|
optionValue = undefined; // use default value
|
|
252
257
|
}
|
|
@@ -254,56 +259,51 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
254
259
|
if (!optionValue) {
|
|
255
260
|
if (optionValue === undefined || optionValue === false) {
|
|
256
261
|
return optionValue;
|
|
257
|
-
}
|
|
258
|
-
|
|
262
|
+
}
|
|
263
|
+
// else, empty string (invalid), null (invalid), 0 (invalid)
|
|
259
264
|
|
|
260
265
|
throw new Error("`".concat(optionName, "` was specified but was not a node, or did not return a node"));
|
|
261
266
|
}
|
|
262
|
-
|
|
263
267
|
var node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point
|
|
264
268
|
|
|
265
269
|
if (typeof optionValue === 'string') {
|
|
266
270
|
node = doc.querySelector(optionValue); // resolve to node, or null if fails
|
|
267
|
-
|
|
268
271
|
if (!node) {
|
|
269
272
|
throw new Error("`".concat(optionName, "` as selector refers to no known node"));
|
|
270
273
|
}
|
|
271
274
|
}
|
|
272
|
-
|
|
273
275
|
return node;
|
|
274
276
|
};
|
|
275
|
-
|
|
276
277
|
var getInitialFocusNode = function getInitialFocusNode() {
|
|
277
|
-
var node = getNodeForOption('initialFocus');
|
|
278
|
+
var node = getNodeForOption('initialFocus');
|
|
278
279
|
|
|
280
|
+
// false explicitly indicates we want no initialFocus at all
|
|
279
281
|
if (node === false) {
|
|
280
282
|
return false;
|
|
281
283
|
}
|
|
282
|
-
|
|
283
284
|
if (node === undefined) {
|
|
284
285
|
// option not specified: use fallback options
|
|
285
286
|
if (findContainerIndex(doc.activeElement) >= 0) {
|
|
286
287
|
node = doc.activeElement;
|
|
287
288
|
} else {
|
|
288
289
|
var firstTabbableGroup = state.tabbableGroups[0];
|
|
289
|
-
var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode;
|
|
290
|
+
var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode;
|
|
290
291
|
|
|
292
|
+
// NOTE: `fallbackFocus` option function cannot return `false` (not supported)
|
|
291
293
|
node = firstTabbableNode || getNodeForOption('fallbackFocus');
|
|
292
294
|
}
|
|
293
295
|
}
|
|
294
|
-
|
|
295
296
|
if (!node) {
|
|
296
297
|
throw new Error('Your focus-trap needs to have at least one focusable element');
|
|
297
298
|
}
|
|
298
|
-
|
|
299
299
|
return node;
|
|
300
300
|
};
|
|
301
|
-
|
|
302
301
|
var updateTabbableNodes = function updateTabbableNodes() {
|
|
303
302
|
state.containerGroups = state.containers.map(function (container) {
|
|
304
|
-
var tabbableNodes = tabbable(container, config.tabbableOptions);
|
|
305
|
-
// are a superset of tabbable nodes
|
|
303
|
+
var tabbableNodes = tabbable(container, config.tabbableOptions);
|
|
306
304
|
|
|
305
|
+
// NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes
|
|
306
|
+
// are a superset of tabbable nodes
|
|
307
307
|
var focusableNodes = focusable(container, config.tabbableOptions);
|
|
308
308
|
return {
|
|
309
309
|
container: container,
|
|
@@ -311,7 +311,6 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
311
311
|
focusableNodes: focusableNodes,
|
|
312
312
|
firstTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[0] : null,
|
|
313
313
|
lastTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[tabbableNodes.length - 1] : null,
|
|
314
|
-
|
|
315
314
|
/**
|
|
316
315
|
* Finds the __tabbable__ node that follows the given node in the specified direction,
|
|
317
316
|
* in this container, if any.
|
|
@@ -335,17 +334,14 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
335
334
|
var nodeIdx = focusableNodes.findIndex(function (n) {
|
|
336
335
|
return n === node;
|
|
337
336
|
});
|
|
338
|
-
|
|
339
337
|
if (nodeIdx < 0) {
|
|
340
338
|
return undefined;
|
|
341
339
|
}
|
|
342
|
-
|
|
343
340
|
if (forward) {
|
|
344
341
|
return focusableNodes.slice(nodeIdx + 1).find(function (n) {
|
|
345
342
|
return isTabbable(n, config.tabbableOptions);
|
|
346
343
|
});
|
|
347
344
|
}
|
|
348
|
-
|
|
349
345
|
return focusableNodes.slice(0, nodeIdx).reverse().find(function (n) {
|
|
350
346
|
return isTabbable(n, config.tabbableOptions);
|
|
351
347
|
});
|
|
@@ -354,53 +350,46 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
354
350
|
});
|
|
355
351
|
state.tabbableGroups = state.containerGroups.filter(function (group) {
|
|
356
352
|
return group.tabbableNodes.length > 0;
|
|
357
|
-
});
|
|
353
|
+
});
|
|
358
354
|
|
|
355
|
+
// throw if no groups have tabbable nodes and we don't have a fallback focus node either
|
|
359
356
|
if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus') // returning false not supported for this option
|
|
360
357
|
) {
|
|
361
358
|
throw new Error('Your focus-trap must have at least one container with at least one tabbable node in it at all times');
|
|
362
359
|
}
|
|
363
360
|
};
|
|
364
|
-
|
|
365
361
|
var tryFocus = function tryFocus(node) {
|
|
366
362
|
if (node === false) {
|
|
367
363
|
return;
|
|
368
364
|
}
|
|
369
|
-
|
|
370
365
|
if (node === doc.activeElement) {
|
|
371
366
|
return;
|
|
372
367
|
}
|
|
373
|
-
|
|
374
368
|
if (!node || !node.focus) {
|
|
375
369
|
tryFocus(getInitialFocusNode());
|
|
376
370
|
return;
|
|
377
371
|
}
|
|
378
|
-
|
|
379
372
|
node.focus({
|
|
380
373
|
preventScroll: !!config.preventScroll
|
|
381
374
|
});
|
|
382
375
|
state.mostRecentlyFocusedNode = node;
|
|
383
|
-
|
|
384
376
|
if (isSelectableInput(node)) {
|
|
385
377
|
node.select();
|
|
386
378
|
}
|
|
387
379
|
};
|
|
388
|
-
|
|
389
380
|
var getReturnFocusNode = function getReturnFocusNode(previousActiveElement) {
|
|
390
381
|
var node = getNodeForOption('setReturnFocus', previousActiveElement);
|
|
391
382
|
return node ? node : node === false ? false : previousActiveElement;
|
|
392
|
-
};
|
|
393
|
-
// so that it precedes the focus event.
|
|
394
|
-
|
|
383
|
+
};
|
|
395
384
|
|
|
385
|
+
// This needs to be done on mousedown and touchstart instead of click
|
|
386
|
+
// so that it precedes the focus event.
|
|
396
387
|
var checkPointerDown = function checkPointerDown(e) {
|
|
397
388
|
var target = getActualTarget(e);
|
|
398
|
-
|
|
399
389
|
if (findContainerIndex(target) >= 0) {
|
|
400
390
|
// allow the click since it ocurred inside the trap
|
|
401
391
|
return;
|
|
402
392
|
}
|
|
403
|
-
|
|
404
393
|
if (valueOrHandler(config.clickOutsideDeactivates, e)) {
|
|
405
394
|
// immediately deactivate the trap
|
|
406
395
|
trap.deactivate({
|
|
@@ -418,25 +407,26 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
418
407
|
returnFocus: config.returnFocusOnDeactivate && !isFocusable(target, config.tabbableOptions)
|
|
419
408
|
});
|
|
420
409
|
return;
|
|
421
|
-
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// This is needed for mobile devices.
|
|
422
413
|
// (If we'll only let `click` events through,
|
|
423
414
|
// then on mobile they will be blocked anyways if `touchstart` is blocked.)
|
|
424
|
-
|
|
425
|
-
|
|
426
415
|
if (valueOrHandler(config.allowOutsideClick, e)) {
|
|
427
416
|
// allow the click outside the trap to take place
|
|
428
417
|
return;
|
|
429
|
-
}
|
|
430
|
-
|
|
418
|
+
}
|
|
431
419
|
|
|
420
|
+
// otherwise, prevent the click
|
|
432
421
|
e.preventDefault();
|
|
433
|
-
};
|
|
434
|
-
|
|
422
|
+
};
|
|
435
423
|
|
|
424
|
+
// In case focus escapes the trap for some strange reason, pull it back in.
|
|
436
425
|
var checkFocusIn = function checkFocusIn(e) {
|
|
437
426
|
var target = getActualTarget(e);
|
|
438
|
-
var targetContained = findContainerIndex(target) >= 0;
|
|
427
|
+
var targetContained = findContainerIndex(target) >= 0;
|
|
439
428
|
|
|
429
|
+
// In Firefox when you Tab out of an iframe the Document is briefly focused.
|
|
440
430
|
if (targetContained || target instanceof Document) {
|
|
441
431
|
if (targetContained) {
|
|
442
432
|
state.mostRecentlyFocusedNode = target;
|
|
@@ -446,42 +436,41 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
446
436
|
e.stopImmediatePropagation();
|
|
447
437
|
tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
|
|
448
438
|
}
|
|
449
|
-
};
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
// Hijack key nav events on the first and last focusable nodes of the trap,
|
|
450
442
|
// in order to prevent focus from escaping. If it escapes for even a
|
|
451
443
|
// moment it can end up scrolling the page and causing confusion so we
|
|
452
444
|
// kind of need to capture the action at the keydown phase.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
var target = getActualTarget(e);
|
|
445
|
+
var checkKeyNav = function checkKeyNav(event) {
|
|
446
|
+
var isBackward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
447
|
+
var target = getActualTarget(event);
|
|
457
448
|
updateTabbableNodes();
|
|
458
449
|
var destinationNode = null;
|
|
459
|
-
|
|
460
450
|
if (state.tabbableGroups.length > 0) {
|
|
461
451
|
// make sure the target is actually contained in a group
|
|
462
452
|
// NOTE: the target may also be the container itself if it's focusable
|
|
463
453
|
// with tabIndex='-1' and was given initial focus
|
|
464
454
|
var containerIndex = findContainerIndex(target);
|
|
465
455
|
var containerGroup = containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined;
|
|
466
|
-
|
|
467
456
|
if (containerIndex < 0) {
|
|
468
457
|
// target not found in any group: quite possible focus has escaped the trap,
|
|
469
|
-
// so bring it back
|
|
470
|
-
if (
|
|
458
|
+
// so bring it back into...
|
|
459
|
+
if (isBackward) {
|
|
471
460
|
// ...the last node in the last group
|
|
472
461
|
destinationNode = state.tabbableGroups[state.tabbableGroups.length - 1].lastTabbableNode;
|
|
473
462
|
} else {
|
|
474
463
|
// ...the first node in the first group
|
|
475
464
|
destinationNode = state.tabbableGroups[0].firstTabbableNode;
|
|
476
465
|
}
|
|
477
|
-
} else if (
|
|
466
|
+
} else if (isBackward) {
|
|
478
467
|
// REVERSE
|
|
468
|
+
|
|
479
469
|
// is the target the first tabbable node in a group?
|
|
480
470
|
var startOfGroupIndex = findIndex(state.tabbableGroups, function (_ref2) {
|
|
481
471
|
var firstTabbableNode = _ref2.firstTabbableNode;
|
|
482
472
|
return target === firstTabbableNode;
|
|
483
473
|
});
|
|
484
|
-
|
|
485
474
|
if (startOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) {
|
|
486
475
|
// an exception case where the target is either the container itself, or
|
|
487
476
|
// a non-tabbable node that was given focus (i.e. tabindex is negative
|
|
@@ -491,7 +480,6 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
491
480
|
// first tabbable node, and go to the last tabbable node of the LAST group
|
|
492
481
|
startOfGroupIndex = containerIndex;
|
|
493
482
|
}
|
|
494
|
-
|
|
495
483
|
if (startOfGroupIndex >= 0) {
|
|
496
484
|
// YES: then shift+tab should go to the last tabbable node in the
|
|
497
485
|
// previous group (and wrap around to the last tabbable node of
|
|
@@ -499,15 +487,19 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
499
487
|
var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1;
|
|
500
488
|
var destinationGroup = state.tabbableGroups[destinationGroupIndex];
|
|
501
489
|
destinationNode = destinationGroup.lastTabbableNode;
|
|
490
|
+
} else if (!isTabEvent(event)) {
|
|
491
|
+
// user must have customized the nav keys so we have to move focus manually _within_
|
|
492
|
+
// the active group: do this based on the order determined by tabbable()
|
|
493
|
+
destinationNode = containerGroup.nextTabbableNode(target, false);
|
|
502
494
|
}
|
|
503
495
|
} else {
|
|
504
496
|
// FORWARD
|
|
497
|
+
|
|
505
498
|
// is the target the last tabbable node in a group?
|
|
506
499
|
var lastOfGroupIndex = findIndex(state.tabbableGroups, function (_ref3) {
|
|
507
500
|
var lastTabbableNode = _ref3.lastTabbableNode;
|
|
508
501
|
return target === lastTabbableNode;
|
|
509
502
|
});
|
|
510
|
-
|
|
511
503
|
if (lastOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) {
|
|
512
504
|
// an exception case where the target is the container itself, or
|
|
513
505
|
// a non-tabbable node that was given focus (i.e. tabindex is negative
|
|
@@ -517,73 +509,76 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
517
509
|
// last tabbable node, and go to the first tabbable node of the FIRST group
|
|
518
510
|
lastOfGroupIndex = containerIndex;
|
|
519
511
|
}
|
|
520
|
-
|
|
521
512
|
if (lastOfGroupIndex >= 0) {
|
|
522
513
|
// YES: then tab should go to the first tabbable node in the next
|
|
523
514
|
// group (and wrap around to the first tabbable node of the FIRST
|
|
524
515
|
// group if it's the last tabbable node of the LAST group)
|
|
525
516
|
var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1;
|
|
526
|
-
|
|
527
517
|
var _destinationGroup = state.tabbableGroups[_destinationGroupIndex];
|
|
528
518
|
destinationNode = _destinationGroup.firstTabbableNode;
|
|
519
|
+
} else if (!isTabEvent(event)) {
|
|
520
|
+
// user must have customized the nav keys so we have to move focus manually _within_
|
|
521
|
+
// the active group: do this based on the order determined by tabbable()
|
|
522
|
+
destinationNode = containerGroup.nextTabbableNode(target);
|
|
529
523
|
}
|
|
530
524
|
}
|
|
531
525
|
} else {
|
|
526
|
+
// no groups available
|
|
532
527
|
// NOTE: the fallbackFocus option does not support returning false to opt-out
|
|
533
528
|
destinationNode = getNodeForOption('fallbackFocus');
|
|
534
529
|
}
|
|
535
|
-
|
|
536
530
|
if (destinationNode) {
|
|
537
|
-
|
|
531
|
+
if (isTabEvent(event)) {
|
|
532
|
+
// since tab natively moves focus, we wouldn't have a destination node unless we
|
|
533
|
+
// were on the edge of a container and had to move to the next/previous edge, in
|
|
534
|
+
// which case we want to prevent default to keep the browser from moving focus
|
|
535
|
+
// to where it normally would
|
|
536
|
+
event.preventDefault();
|
|
537
|
+
}
|
|
538
538
|
tryFocus(destinationNode);
|
|
539
|
-
}
|
|
540
|
-
|
|
539
|
+
}
|
|
540
|
+
// else, let the browser take care of [shift+]tab and move the focus
|
|
541
541
|
};
|
|
542
542
|
|
|
543
|
-
var checkKey = function checkKey(
|
|
544
|
-
if (isEscapeEvent(
|
|
545
|
-
|
|
543
|
+
var checkKey = function checkKey(event) {
|
|
544
|
+
if (isEscapeEvent(event) && valueOrHandler(config.escapeDeactivates, event) !== false) {
|
|
545
|
+
event.preventDefault();
|
|
546
546
|
trap.deactivate();
|
|
547
547
|
return;
|
|
548
548
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
checkTab(e);
|
|
552
|
-
return;
|
|
549
|
+
if (config.isKeyForward(event) || config.isKeyBackward(event)) {
|
|
550
|
+
checkKeyNav(event, config.isKeyBackward(event));
|
|
553
551
|
}
|
|
554
552
|
};
|
|
555
|
-
|
|
556
553
|
var checkClick = function checkClick(e) {
|
|
557
554
|
var target = getActualTarget(e);
|
|
558
|
-
|
|
559
555
|
if (findContainerIndex(target) >= 0) {
|
|
560
556
|
return;
|
|
561
557
|
}
|
|
562
|
-
|
|
563
558
|
if (valueOrHandler(config.clickOutsideDeactivates, e)) {
|
|
564
559
|
return;
|
|
565
560
|
}
|
|
566
|
-
|
|
567
561
|
if (valueOrHandler(config.allowOutsideClick, e)) {
|
|
568
562
|
return;
|
|
569
563
|
}
|
|
570
|
-
|
|
571
564
|
e.preventDefault();
|
|
572
565
|
e.stopImmediatePropagation();
|
|
573
|
-
};
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
//
|
|
574
569
|
// EVENT LISTENERS
|
|
575
570
|
//
|
|
576
571
|
|
|
577
|
-
|
|
578
572
|
var addListeners = function addListeners() {
|
|
579
573
|
if (!state.active) {
|
|
580
574
|
return;
|
|
581
|
-
}
|
|
575
|
+
}
|
|
582
576
|
|
|
577
|
+
// There can be only one listening focus trap at a time
|
|
578
|
+
activeFocusTraps.activateTrap(trapStack, trap);
|
|
583
579
|
|
|
584
|
-
|
|
580
|
+
// Delay ensures that the focused element doesn't capture the event
|
|
585
581
|
// that caused the focus trap activation.
|
|
586
|
-
|
|
587
582
|
state.delayInitialFocusTimer = config.delayInitialFocus ? delay(function () {
|
|
588
583
|
tryFocus(getInitialFocusNode());
|
|
589
584
|
}) : tryFocus(getInitialFocusNode());
|
|
@@ -606,70 +601,58 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
606
601
|
});
|
|
607
602
|
return trap;
|
|
608
603
|
};
|
|
609
|
-
|
|
610
604
|
var removeListeners = function removeListeners() {
|
|
611
605
|
if (!state.active) {
|
|
612
606
|
return;
|
|
613
607
|
}
|
|
614
|
-
|
|
615
608
|
doc.removeEventListener('focusin', checkFocusIn, true);
|
|
616
609
|
doc.removeEventListener('mousedown', checkPointerDown, true);
|
|
617
610
|
doc.removeEventListener('touchstart', checkPointerDown, true);
|
|
618
611
|
doc.removeEventListener('click', checkClick, true);
|
|
619
612
|
doc.removeEventListener('keydown', checkKey, true);
|
|
620
613
|
return trap;
|
|
621
|
-
};
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
//
|
|
622
617
|
// TRAP DEFINITION
|
|
623
618
|
//
|
|
624
619
|
|
|
625
|
-
|
|
626
620
|
trap = {
|
|
627
621
|
get active() {
|
|
628
622
|
return state.active;
|
|
629
623
|
},
|
|
630
|
-
|
|
631
624
|
get paused() {
|
|
632
625
|
return state.paused;
|
|
633
626
|
},
|
|
634
|
-
|
|
635
627
|
activate: function activate(activateOptions) {
|
|
636
628
|
if (state.active) {
|
|
637
629
|
return this;
|
|
638
630
|
}
|
|
639
|
-
|
|
640
631
|
var onActivate = getOption(activateOptions, 'onActivate');
|
|
641
632
|
var onPostActivate = getOption(activateOptions, 'onPostActivate');
|
|
642
633
|
var checkCanFocusTrap = getOption(activateOptions, 'checkCanFocusTrap');
|
|
643
|
-
|
|
644
634
|
if (!checkCanFocusTrap) {
|
|
645
635
|
updateTabbableNodes();
|
|
646
636
|
}
|
|
647
|
-
|
|
648
637
|
state.active = true;
|
|
649
638
|
state.paused = false;
|
|
650
639
|
state.nodeFocusedBeforeActivation = doc.activeElement;
|
|
651
|
-
|
|
652
640
|
if (onActivate) {
|
|
653
641
|
onActivate();
|
|
654
642
|
}
|
|
655
|
-
|
|
656
643
|
var finishActivation = function finishActivation() {
|
|
657
644
|
if (checkCanFocusTrap) {
|
|
658
645
|
updateTabbableNodes();
|
|
659
646
|
}
|
|
660
|
-
|
|
661
647
|
addListeners();
|
|
662
|
-
|
|
663
648
|
if (onPostActivate) {
|
|
664
649
|
onPostActivate();
|
|
665
650
|
}
|
|
666
651
|
};
|
|
667
|
-
|
|
668
652
|
if (checkCanFocusTrap) {
|
|
669
653
|
checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
|
|
670
654
|
return this;
|
|
671
655
|
}
|
|
672
|
-
|
|
673
656
|
finishActivation();
|
|
674
657
|
return this;
|
|
675
658
|
},
|
|
@@ -677,46 +660,38 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
677
660
|
if (!state.active) {
|
|
678
661
|
return this;
|
|
679
662
|
}
|
|
680
|
-
|
|
681
663
|
var options = _objectSpread2({
|
|
682
664
|
onDeactivate: config.onDeactivate,
|
|
683
665
|
onPostDeactivate: config.onPostDeactivate,
|
|
684
666
|
checkCanReturnFocus: config.checkCanReturnFocus
|
|
685
667
|
}, deactivateOptions);
|
|
686
|
-
|
|
687
668
|
clearTimeout(state.delayInitialFocusTimer); // noop if undefined
|
|
688
|
-
|
|
689
669
|
state.delayInitialFocusTimer = undefined;
|
|
690
670
|
removeListeners();
|
|
691
671
|
state.active = false;
|
|
692
672
|
state.paused = false;
|
|
693
|
-
activeFocusTraps.deactivateTrap(trap);
|
|
673
|
+
activeFocusTraps.deactivateTrap(trapStack, trap);
|
|
694
674
|
var onDeactivate = getOption(options, 'onDeactivate');
|
|
695
675
|
var onPostDeactivate = getOption(options, 'onPostDeactivate');
|
|
696
676
|
var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus');
|
|
697
677
|
var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate');
|
|
698
|
-
|
|
699
678
|
if (onDeactivate) {
|
|
700
679
|
onDeactivate();
|
|
701
680
|
}
|
|
702
|
-
|
|
703
681
|
var finishDeactivation = function finishDeactivation() {
|
|
704
682
|
delay(function () {
|
|
705
683
|
if (returnFocus) {
|
|
706
684
|
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
|
|
707
685
|
}
|
|
708
|
-
|
|
709
686
|
if (onPostDeactivate) {
|
|
710
687
|
onPostDeactivate();
|
|
711
688
|
}
|
|
712
689
|
});
|
|
713
690
|
};
|
|
714
|
-
|
|
715
691
|
if (returnFocus && checkCanReturnFocus) {
|
|
716
692
|
checkCanReturnFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)).then(finishDeactivation, finishDeactivation);
|
|
717
693
|
return this;
|
|
718
694
|
}
|
|
719
|
-
|
|
720
695
|
finishDeactivation();
|
|
721
696
|
return this;
|
|
722
697
|
},
|
|
@@ -724,7 +699,6 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
724
699
|
if (state.paused || !state.active) {
|
|
725
700
|
return this;
|
|
726
701
|
}
|
|
727
|
-
|
|
728
702
|
state.paused = true;
|
|
729
703
|
removeListeners();
|
|
730
704
|
return this;
|
|
@@ -733,7 +707,6 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
733
707
|
if (!state.paused || !state.active) {
|
|
734
708
|
return this;
|
|
735
709
|
}
|
|
736
|
-
|
|
737
710
|
state.paused = false;
|
|
738
711
|
updateTabbableNodes();
|
|
739
712
|
addListeners();
|
|
@@ -744,15 +717,14 @@ var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
|
744
717
|
state.containers = elementsAsArray.map(function (element) {
|
|
745
718
|
return typeof element === 'string' ? doc.querySelector(element) : element;
|
|
746
719
|
});
|
|
747
|
-
|
|
748
720
|
if (state.active) {
|
|
749
721
|
updateTabbableNodes();
|
|
750
722
|
}
|
|
751
|
-
|
|
752
723
|
return this;
|
|
753
724
|
}
|
|
754
|
-
};
|
|
725
|
+
};
|
|
755
726
|
|
|
727
|
+
// initialize container elements
|
|
756
728
|
trap.updateContainerElements(elements);
|
|
757
729
|
return trap;
|
|
758
730
|
};
|