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