focus-trap 6.2.0 → 6.3.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 +33 -0
- package/README.md +47 -29
- package/SECURITY.md +37 -0
- package/dist/focus-trap.esm.js +316 -215
- 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 +315 -214
- 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 +315 -214
- 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 +7 -6
- package/index.js +381 -271
- package/package.json +35 -30
package/dist/focus-trap.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* focus-trap 6.
|
|
2
|
+
* focus-trap 6.3.0
|
|
3
3
|
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
|
|
4
4
|
*/
|
|
5
5
|
(function (global, factory) {
|
|
@@ -100,7 +100,54 @@
|
|
|
100
100
|
};
|
|
101
101
|
}();
|
|
102
102
|
|
|
103
|
-
function
|
|
103
|
+
var isSelectableInput = function isSelectableInput(node) {
|
|
104
|
+
return node.tagName && node.tagName.toLowerCase() === 'input' && typeof node.select === 'function';
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
var isEscapeEvent = function isEscapeEvent(e) {
|
|
108
|
+
return e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
var isTabEvent = function isTabEvent(e) {
|
|
112
|
+
return e.key === 'Tab' || e.keyCode === 9;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
var delay = function delay(fn) {
|
|
116
|
+
return setTimeout(fn, 0);
|
|
117
|
+
}; // Array.find/findIndex() are not supported on IE; this replicates enough
|
|
118
|
+
// of Array.findIndex() for our needs
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
var findIndex = function findIndex(arr, fn) {
|
|
122
|
+
var idx = -1;
|
|
123
|
+
arr.every(function (value, i) {
|
|
124
|
+
if (fn(value)) {
|
|
125
|
+
idx = i;
|
|
126
|
+
return false; // break
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return true; // next
|
|
130
|
+
});
|
|
131
|
+
return idx;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Get an option's value when it could be a plain value, or a handler that provides
|
|
135
|
+
* the value.
|
|
136
|
+
* @param {*} value Option's value to check.
|
|
137
|
+
* @param {...*} [params] Any parameters to pass to the handler, if `value` is a function.
|
|
138
|
+
* @returns {*} The `value`, or the handler's returned value.
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
var valueOrHandler = function valueOrHandler(value) {
|
|
143
|
+
for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
144
|
+
params[_key - 1] = arguments[_key];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return typeof value === 'function' ? value.apply(void 0, params) : value;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
var createFocusTrap = function createFocusTrap(elements, userOptions) {
|
|
104
151
|
var doc = document;
|
|
105
152
|
|
|
106
153
|
var config = _objectSpread2({
|
|
@@ -112,143 +159,41 @@
|
|
|
112
159
|
var state = {
|
|
113
160
|
// @type {Array<HTMLElement>}
|
|
114
161
|
containers: [],
|
|
115
|
-
//
|
|
162
|
+
// list of objects identifying the first and last tabbable nodes in all containers/groups in
|
|
163
|
+
// the trap
|
|
164
|
+
// NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap
|
|
165
|
+
// is active, but the trap should never get to a state where there isn't at least one group
|
|
166
|
+
// with at least one tabbable node in it (that would lead to an error condition that would
|
|
167
|
+
// result in an error being thrown)
|
|
168
|
+
// @type {Array<{ container: HTMLElement, firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>}
|
|
116
169
|
tabbableGroups: [],
|
|
117
170
|
nodeFocusedBeforeActivation: null,
|
|
118
171
|
mostRecentlyFocusedNode: null,
|
|
119
172
|
active: false,
|
|
120
173
|
paused: false
|
|
121
174
|
};
|
|
122
|
-
var trap
|
|
123
|
-
activate: activate,
|
|
124
|
-
deactivate: deactivate,
|
|
125
|
-
pause: pause,
|
|
126
|
-
unpause: unpause,
|
|
127
|
-
updateContainerElements: updateContainerElements
|
|
128
|
-
};
|
|
129
|
-
updateContainerElements(elements);
|
|
130
|
-
return trap;
|
|
131
|
-
|
|
132
|
-
function updateContainerElements(containerElements) {
|
|
133
|
-
var elementsAsArray = [].concat(containerElements).filter(Boolean);
|
|
134
|
-
state.containers = elementsAsArray.map(function (element) {
|
|
135
|
-
return typeof element === 'string' ? doc.querySelector(element) : element;
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (state.active) {
|
|
139
|
-
updateTabbableNodes();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return trap;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function activate(activateOptions) {
|
|
146
|
-
if (state.active) return;
|
|
147
|
-
updateTabbableNodes();
|
|
148
|
-
state.active = true;
|
|
149
|
-
state.paused = false;
|
|
150
|
-
state.nodeFocusedBeforeActivation = doc.activeElement;
|
|
151
|
-
var onActivate = activateOptions && activateOptions.onActivate ? activateOptions.onActivate : config.onActivate;
|
|
152
|
-
|
|
153
|
-
if (onActivate) {
|
|
154
|
-
onActivate();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
addListeners();
|
|
158
|
-
return trap;
|
|
159
|
-
}
|
|
175
|
+
var trap; // eslint-disable-line prefer-const -- some private functions reference it, and its methods reference private functions, so we must declare here and define later
|
|
160
176
|
|
|
161
|
-
function
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
removeListeners();
|
|
165
|
-
state.active = false;
|
|
166
|
-
state.paused = false;
|
|
167
|
-
activeFocusTraps.deactivateTrap(trap);
|
|
168
|
-
var onDeactivate = deactivateOptions && deactivateOptions.onDeactivate !== undefined ? deactivateOptions.onDeactivate : config.onDeactivate;
|
|
169
|
-
|
|
170
|
-
if (onDeactivate) {
|
|
171
|
-
onDeactivate();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
var returnFocus = deactivateOptions && deactivateOptions.returnFocus !== undefined ? deactivateOptions.returnFocus : config.returnFocusOnDeactivate;
|
|
175
|
-
|
|
176
|
-
if (returnFocus) {
|
|
177
|
-
delay(function () {
|
|
178
|
-
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return trap;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function pause() {
|
|
186
|
-
if (state.paused || !state.active) return trap;
|
|
187
|
-
state.paused = true;
|
|
188
|
-
removeListeners();
|
|
189
|
-
return trap;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function unpause() {
|
|
193
|
-
if (!state.paused || !state.active) return trap;
|
|
194
|
-
state.paused = false;
|
|
195
|
-
updateTabbableNodes();
|
|
196
|
-
addListeners();
|
|
197
|
-
return trap;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function addListeners() {
|
|
201
|
-
if (!state.active) return; // There can be only one listening focus trap at a time
|
|
202
|
-
|
|
203
|
-
activeFocusTraps.activateTrap(trap); // Delay ensures that the focused element doesn't capture the event
|
|
204
|
-
// that caused the focus trap activation.
|
|
205
|
-
|
|
206
|
-
activeFocusDelay = config.delayInitialFocus ? delay(function () {
|
|
207
|
-
tryFocus(getInitialFocusNode());
|
|
208
|
-
}) : tryFocus(getInitialFocusNode());
|
|
209
|
-
doc.addEventListener('focusin', checkFocusIn, true);
|
|
210
|
-
doc.addEventListener('mousedown', checkPointerDown, {
|
|
211
|
-
capture: true,
|
|
212
|
-
passive: false
|
|
213
|
-
});
|
|
214
|
-
doc.addEventListener('touchstart', checkPointerDown, {
|
|
215
|
-
capture: true,
|
|
216
|
-
passive: false
|
|
217
|
-
});
|
|
218
|
-
doc.addEventListener('click', checkClick, {
|
|
219
|
-
capture: true,
|
|
220
|
-
passive: false
|
|
221
|
-
});
|
|
222
|
-
doc.addEventListener('keydown', checkKey, {
|
|
223
|
-
capture: true,
|
|
224
|
-
passive: false
|
|
177
|
+
var containersContain = function containersContain(element) {
|
|
178
|
+
return state.containers.some(function (container) {
|
|
179
|
+
return container.contains(element);
|
|
225
180
|
});
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function removeListeners() {
|
|
230
|
-
if (!state.active) return;
|
|
231
|
-
doc.removeEventListener('focusin', checkFocusIn, true);
|
|
232
|
-
doc.removeEventListener('mousedown', checkPointerDown, true);
|
|
233
|
-
doc.removeEventListener('touchstart', checkPointerDown, true);
|
|
234
|
-
doc.removeEventListener('click', checkClick, true);
|
|
235
|
-
doc.removeEventListener('keydown', checkKey, true);
|
|
236
|
-
return trap;
|
|
237
|
-
}
|
|
181
|
+
};
|
|
238
182
|
|
|
239
|
-
function getNodeForOption(optionName) {
|
|
183
|
+
var getNodeForOption = function getNodeForOption(optionName) {
|
|
240
184
|
var optionValue = config[optionName];
|
|
241
|
-
var node = optionValue;
|
|
242
185
|
|
|
243
186
|
if (!optionValue) {
|
|
244
187
|
return null;
|
|
245
188
|
}
|
|
246
189
|
|
|
190
|
+
var node = optionValue;
|
|
191
|
+
|
|
247
192
|
if (typeof optionValue === 'string') {
|
|
248
193
|
node = doc.querySelector(optionValue);
|
|
249
194
|
|
|
250
195
|
if (!node) {
|
|
251
|
-
throw new Error(
|
|
196
|
+
throw new Error("`".concat(optionName, "` refers to no known node"));
|
|
252
197
|
}
|
|
253
198
|
}
|
|
254
199
|
|
|
@@ -256,14 +201,14 @@
|
|
|
256
201
|
node = optionValue();
|
|
257
202
|
|
|
258
203
|
if (!node) {
|
|
259
|
-
throw new Error(
|
|
204
|
+
throw new Error("`".concat(optionName, "` did not return a node"));
|
|
260
205
|
}
|
|
261
206
|
}
|
|
262
207
|
|
|
263
208
|
return node;
|
|
264
|
-
}
|
|
209
|
+
};
|
|
265
210
|
|
|
266
|
-
function getInitialFocusNode() {
|
|
211
|
+
var getInitialFocusNode = function getInitialFocusNode() {
|
|
267
212
|
var node;
|
|
268
213
|
|
|
269
214
|
if (getNodeForOption('initialFocus') !== null) {
|
|
@@ -281,24 +226,67 @@
|
|
|
281
226
|
}
|
|
282
227
|
|
|
283
228
|
return node;
|
|
284
|
-
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
var updateTabbableNodes = function updateTabbableNodes() {
|
|
232
|
+
state.tabbableGroups = state.containers.map(function (container) {
|
|
233
|
+
var tabbableNodes = tabbable.tabbable(container);
|
|
285
234
|
|
|
286
|
-
|
|
235
|
+
if (tabbableNodes.length > 0) {
|
|
236
|
+
return {
|
|
237
|
+
container: container,
|
|
238
|
+
firstTabbableNode: tabbableNodes[0],
|
|
239
|
+
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return undefined;
|
|
244
|
+
}).filter(function (group) {
|
|
245
|
+
return !!group;
|
|
246
|
+
}); // remove groups with no tabbable nodes
|
|
247
|
+
// throw if no groups have tabbable nodes and we don't have a fallback focus node either
|
|
248
|
+
|
|
249
|
+
if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus')) {
|
|
250
|
+
throw new Error('Your focus-trap must have at least one container with at least one tabbable node in it at all times');
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
var tryFocus = function tryFocus(node) {
|
|
255
|
+
if (node === doc.activeElement) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!node || !node.focus) {
|
|
260
|
+
tryFocus(getInitialFocusNode());
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
node.focus({
|
|
265
|
+
preventScroll: !!config.preventScroll
|
|
266
|
+
});
|
|
267
|
+
state.mostRecentlyFocusedNode = node;
|
|
268
|
+
|
|
269
|
+
if (isSelectableInput(node)) {
|
|
270
|
+
node.select();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
var getReturnFocusNode = function getReturnFocusNode(previousActiveElement) {
|
|
287
275
|
var node = getNodeForOption('setReturnFocus');
|
|
288
276
|
return node ? node : previousActiveElement;
|
|
289
|
-
} // This needs to be done on mousedown and touchstart instead of click
|
|
277
|
+
}; // This needs to be done on mousedown and touchstart instead of click
|
|
290
278
|
// so that it precedes the focus event.
|
|
291
279
|
|
|
292
280
|
|
|
293
|
-
function checkPointerDown(e) {
|
|
281
|
+
var checkPointerDown = function checkPointerDown(e) {
|
|
294
282
|
if (containersContain(e.target)) {
|
|
295
283
|
// allow the click since it ocurred inside the trap
|
|
296
284
|
return;
|
|
297
285
|
}
|
|
298
286
|
|
|
299
|
-
if (config.clickOutsideDeactivates) {
|
|
287
|
+
if (valueOrHandler(config.clickOutsideDeactivates, e)) {
|
|
300
288
|
// immediately deactivate the trap
|
|
301
|
-
deactivate({
|
|
289
|
+
trap.deactivate({
|
|
302
290
|
// if, on deactivation, we should return focus to the node originally-focused
|
|
303
291
|
// when the trap was activated (or the configured `setReturnFocus` node),
|
|
304
292
|
// then assume it's also OK to return focus to the outside node that was
|
|
@@ -318,140 +306,253 @@
|
|
|
318
306
|
// then on mobile they will be blocked anyways if `touchstart` is blocked.)
|
|
319
307
|
|
|
320
308
|
|
|
321
|
-
if (
|
|
309
|
+
if (valueOrHandler(config.allowOutsideClick, e)) {
|
|
322
310
|
// allow the click outside the trap to take place
|
|
323
311
|
return;
|
|
324
312
|
} // otherwise, prevent the click
|
|
325
313
|
|
|
326
314
|
|
|
327
315
|
e.preventDefault();
|
|
328
|
-
} // In case focus escapes the trap for some strange reason, pull it back in.
|
|
316
|
+
}; // In case focus escapes the trap for some strange reason, pull it back in.
|
|
329
317
|
|
|
330
318
|
|
|
331
|
-
function checkFocusIn(e) {
|
|
332
|
-
// In Firefox when you Tab out of an iframe the Document is briefly focused.
|
|
333
|
-
if (containersContain(e.target) || e.target instanceof Document) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
319
|
+
var checkFocusIn = function checkFocusIn(e) {
|
|
320
|
+
var targetContained = containersContain(e.target); // In Firefox when you Tab out of an iframe the Document is briefly focused.
|
|
336
321
|
|
|
337
|
-
e.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
e.
|
|
344
|
-
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (isTabEvent(e)) {
|
|
349
|
-
checkTab(e);
|
|
350
|
-
return;
|
|
322
|
+
if (targetContained || e.target instanceof Document) {
|
|
323
|
+
if (targetContained) {
|
|
324
|
+
state.mostRecentlyFocusedNode = e.target;
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
// escaped! pull it back in to where it just left
|
|
328
|
+
e.stopImmediatePropagation();
|
|
329
|
+
tryFocus(state.mostRecentlyFocusedNode || getInitialFocusNode());
|
|
351
330
|
}
|
|
352
|
-
} // Hijack Tab events on the first and last focusable nodes of the trap,
|
|
331
|
+
}; // Hijack Tab events on the first and last focusable nodes of the trap,
|
|
353
332
|
// in order to prevent focus from escaping. If it escapes for even a
|
|
354
333
|
// moment it can end up scrolling the page and causing confusion so we
|
|
355
334
|
// kind of need to capture the action at the keydown phase.
|
|
356
335
|
|
|
357
336
|
|
|
358
|
-
function checkTab(e) {
|
|
337
|
+
var checkTab = function checkTab(e) {
|
|
359
338
|
updateTabbableNodes();
|
|
360
339
|
var destinationNode = null;
|
|
361
340
|
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
341
|
+
if (state.tabbableGroups.length > 0) {
|
|
342
|
+
// make sure the target is actually contained in a group
|
|
343
|
+
var containerIndex = findIndex(state.tabbableGroups, function (_ref) {
|
|
344
|
+
var container = _ref.container;
|
|
345
|
+
return container.contains(e.target);
|
|
366
346
|
});
|
|
367
347
|
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
348
|
+
if (containerIndex < 0) {
|
|
349
|
+
// target not found in any group: quite possible focus has escaped the trap,
|
|
350
|
+
// so bring it back in to...
|
|
351
|
+
if (e.shiftKey) {
|
|
352
|
+
// ...the last node in the last group
|
|
353
|
+
destinationNode = state.tabbableGroups[state.tabbableGroups.length - 1].lastTabbableNode;
|
|
354
|
+
} else {
|
|
355
|
+
// ...the first node in the first group
|
|
356
|
+
destinationNode = state.tabbableGroups[0].firstTabbableNode;
|
|
357
|
+
}
|
|
358
|
+
} else if (e.shiftKey) {
|
|
359
|
+
// REVERSE
|
|
360
|
+
var startOfGroupIndex = findIndex(state.tabbableGroups, function (_ref2) {
|
|
361
|
+
var firstTabbableNode = _ref2.firstTabbableNode;
|
|
362
|
+
return e.target === firstTabbableNode;
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (startOfGroupIndex >= 0) {
|
|
366
|
+
var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1;
|
|
367
|
+
var destinationGroup = state.tabbableGroups[destinationGroupIndex];
|
|
368
|
+
destinationNode = destinationGroup.lastTabbableNode;
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
// FORWARD
|
|
372
|
+
var lastOfGroupIndex = findIndex(state.tabbableGroups, function (_ref3) {
|
|
373
|
+
var lastTabbableNode = _ref3.lastTabbableNode;
|
|
374
|
+
return e.target === lastTabbableNode;
|
|
375
|
+
});
|
|
378
376
|
|
|
379
|
-
|
|
380
|
-
|
|
377
|
+
if (lastOfGroupIndex >= 0) {
|
|
378
|
+
var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1;
|
|
381
379
|
|
|
382
|
-
|
|
383
|
-
|
|
380
|
+
var _destinationGroup = state.tabbableGroups[_destinationGroupIndex];
|
|
381
|
+
destinationNode = _destinationGroup.firstTabbableNode;
|
|
382
|
+
}
|
|
384
383
|
}
|
|
384
|
+
} else {
|
|
385
|
+
destinationNode = getNodeForOption('fallbackFocus');
|
|
385
386
|
}
|
|
386
387
|
|
|
387
388
|
if (destinationNode) {
|
|
388
389
|
e.preventDefault();
|
|
389
390
|
tryFocus(destinationNode);
|
|
390
391
|
}
|
|
391
|
-
}
|
|
392
|
+
};
|
|
392
393
|
|
|
393
|
-
function
|
|
394
|
-
if (config.
|
|
395
|
-
|
|
394
|
+
var checkKey = function checkKey(e) {
|
|
395
|
+
if (config.escapeDeactivates !== false && isEscapeEvent(e)) {
|
|
396
|
+
e.preventDefault();
|
|
397
|
+
trap.deactivate();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
396
400
|
|
|
397
|
-
if (
|
|
401
|
+
if (isTabEvent(e)) {
|
|
402
|
+
checkTab(e);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
var checkClick = function checkClick(e) {
|
|
408
|
+
if (valueOrHandler(config.clickOutsideDeactivates, e)) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (containersContain(e.target)) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (valueOrHandler(config.allowOutsideClick, e)) {
|
|
398
417
|
return;
|
|
399
418
|
}
|
|
400
419
|
|
|
401
420
|
e.preventDefault();
|
|
402
421
|
e.stopImmediatePropagation();
|
|
403
|
-
}
|
|
422
|
+
}; //
|
|
423
|
+
// EVENT LISTENERS
|
|
424
|
+
//
|
|
404
425
|
|
|
405
|
-
function updateTabbableNodes() {
|
|
406
|
-
state.tabbableGroups = state.containers.map(function (container) {
|
|
407
|
-
var tabbableNodes = tabbable.tabbable(container);
|
|
408
|
-
return {
|
|
409
|
-
firstTabbableNode: tabbableNodes[0],
|
|
410
|
-
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1]
|
|
411
|
-
};
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
426
|
|
|
415
|
-
function
|
|
416
|
-
if (
|
|
427
|
+
var addListeners = function addListeners() {
|
|
428
|
+
if (!state.active) {
|
|
429
|
+
return;
|
|
430
|
+
} // There can be only one listening focus trap at a time
|
|
417
431
|
|
|
418
|
-
|
|
432
|
+
|
|
433
|
+
activeFocusTraps.activateTrap(trap); // Delay ensures that the focused element doesn't capture the event
|
|
434
|
+
// that caused the focus trap activation.
|
|
435
|
+
|
|
436
|
+
activeFocusDelay = config.delayInitialFocus ? delay(function () {
|
|
419
437
|
tryFocus(getInitialFocusNode());
|
|
438
|
+
}) : tryFocus(getInitialFocusNode());
|
|
439
|
+
doc.addEventListener('focusin', checkFocusIn, true);
|
|
440
|
+
doc.addEventListener('mousedown', checkPointerDown, {
|
|
441
|
+
capture: true,
|
|
442
|
+
passive: false
|
|
443
|
+
});
|
|
444
|
+
doc.addEventListener('touchstart', checkPointerDown, {
|
|
445
|
+
capture: true,
|
|
446
|
+
passive: false
|
|
447
|
+
});
|
|
448
|
+
doc.addEventListener('click', checkClick, {
|
|
449
|
+
capture: true,
|
|
450
|
+
passive: false
|
|
451
|
+
});
|
|
452
|
+
doc.addEventListener('keydown', checkKey, {
|
|
453
|
+
capture: true,
|
|
454
|
+
passive: false
|
|
455
|
+
});
|
|
456
|
+
return trap;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
var removeListeners = function removeListeners() {
|
|
460
|
+
if (!state.active) {
|
|
420
461
|
return;
|
|
421
462
|
}
|
|
422
463
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
464
|
+
doc.removeEventListener('focusin', checkFocusIn, true);
|
|
465
|
+
doc.removeEventListener('mousedown', checkPointerDown, true);
|
|
466
|
+
doc.removeEventListener('touchstart', checkPointerDown, true);
|
|
467
|
+
doc.removeEventListener('click', checkClick, true);
|
|
468
|
+
doc.removeEventListener('keydown', checkKey, true);
|
|
469
|
+
return trap;
|
|
470
|
+
}; //
|
|
471
|
+
// TRAP DEFINITION
|
|
472
|
+
//
|
|
427
473
|
|
|
428
|
-
if (isSelectableInput(node)) {
|
|
429
|
-
node.select();
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
474
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
475
|
+
trap = {
|
|
476
|
+
activate: function activate(activateOptions) {
|
|
477
|
+
if (state.active) {
|
|
478
|
+
return this;
|
|
479
|
+
}
|
|
439
480
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
481
|
+
updateTabbableNodes();
|
|
482
|
+
state.active = true;
|
|
483
|
+
state.paused = false;
|
|
484
|
+
state.nodeFocusedBeforeActivation = doc.activeElement;
|
|
485
|
+
var onActivate = activateOptions && activateOptions.onActivate ? activateOptions.onActivate : config.onActivate;
|
|
443
486
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
487
|
+
if (onActivate) {
|
|
488
|
+
onActivate();
|
|
489
|
+
}
|
|
447
490
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
491
|
+
addListeners();
|
|
492
|
+
return this;
|
|
493
|
+
},
|
|
494
|
+
deactivate: function deactivate(deactivateOptions) {
|
|
495
|
+
if (!state.active) {
|
|
496
|
+
return this;
|
|
497
|
+
}
|
|
451
498
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
499
|
+
clearTimeout(activeFocusDelay);
|
|
500
|
+
removeListeners();
|
|
501
|
+
state.active = false;
|
|
502
|
+
state.paused = false;
|
|
503
|
+
activeFocusTraps.deactivateTrap(trap);
|
|
504
|
+
var onDeactivate = deactivateOptions && deactivateOptions.onDeactivate !== undefined ? deactivateOptions.onDeactivate : config.onDeactivate;
|
|
505
|
+
|
|
506
|
+
if (onDeactivate) {
|
|
507
|
+
onDeactivate();
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
var returnFocus = deactivateOptions && deactivateOptions.returnFocus !== undefined ? deactivateOptions.returnFocus : config.returnFocusOnDeactivate;
|
|
511
|
+
|
|
512
|
+
if (returnFocus) {
|
|
513
|
+
delay(function () {
|
|
514
|
+
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return this;
|
|
519
|
+
},
|
|
520
|
+
pause: function pause() {
|
|
521
|
+
if (state.paused || !state.active) {
|
|
522
|
+
return this;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
state.paused = true;
|
|
526
|
+
removeListeners();
|
|
527
|
+
return this;
|
|
528
|
+
},
|
|
529
|
+
unpause: function unpause() {
|
|
530
|
+
if (!state.paused || !state.active) {
|
|
531
|
+
return this;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
state.paused = false;
|
|
535
|
+
updateTabbableNodes();
|
|
536
|
+
addListeners();
|
|
537
|
+
return this;
|
|
538
|
+
},
|
|
539
|
+
updateContainerElements: function updateContainerElements(containerElements) {
|
|
540
|
+
var elementsAsArray = [].concat(containerElements).filter(Boolean);
|
|
541
|
+
state.containers = elementsAsArray.map(function (element) {
|
|
542
|
+
return typeof element === 'string' ? doc.querySelector(element) : element;
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
if (state.active) {
|
|
546
|
+
updateTabbableNodes();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return this;
|
|
550
|
+
}
|
|
551
|
+
}; // initialize container elements
|
|
552
|
+
|
|
553
|
+
trap.updateContainerElements(elements);
|
|
554
|
+
return trap;
|
|
555
|
+
};
|
|
455
556
|
|
|
456
557
|
exports.createFocusTrap = createFocusTrap;
|
|
457
558
|
|