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