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