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