focus-trap 4.0.2 → 5.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 +15 -0
- package/README.md +8 -0
- package/dist/focus-trap.js +54 -72
- package/dist/focus-trap.min.js +1 -1
- package/index.d.ts +8 -0
- package/index.js +41 -12
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.0
|
|
4
|
+
|
|
5
|
+
- Add `setReturnFocus` option that allows you to set which element receives focus when the trap closes.
|
|
6
|
+
|
|
7
|
+
## 5.0.2
|
|
8
|
+
|
|
9
|
+
- Add `allowOutsideClick` option that allows you to pass a click event through, even when `clickOutsideDeactivates` is `false`.
|
|
10
|
+
|
|
11
|
+
## 5.0.0
|
|
12
|
+
|
|
13
|
+
- Update Tabbable to improve performance (see [Tabbable's changelog](https://github.com/davidtheclark/tabbable/blob/master/CHANGELOG.md)).
|
|
14
|
+
- **Breaking (kind of):** if the `onActivate` callback changes the list of tabbable nodes and the `initialFocus` option is not used, the initial focus will still go to the first element present before the callback.
|
|
15
|
+
- Improve performance of activating a trap.
|
|
16
|
+
- Register document-level event listeners as active (`passive: false`).
|
|
17
|
+
|
|
3
18
|
## 4.0.2
|
|
4
19
|
|
|
5
20
|
- Fix reference to root element that caused errors within Shadow DOM.
|
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# focus-trap
|
|
2
2
|
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
**SEEKING CO-MAINTAINERS!** Continued development of this project is going to require the work of one or more dedicated co-maintainers (or forkers). If you're interested, please comment in [this issue](https://github.com/davidtheclark/focus-trap/issues/85).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
3
9
|
Trap focus within a DOM node.
|
|
4
10
|
|
|
5
11
|
There may come a time when you find it important to trap focus within a DOM node — so that when a user hits `Tab` or `Shift+Tab` or clicks around, she can't escape a certain cycle of focusable elements.
|
|
@@ -63,6 +69,8 @@ Returns a new focus trap on `element`.
|
|
|
63
69
|
- **escapeDeactivates** {boolean}: Default: `true`. If `false`, the `Escape` key will not trigger deactivation of the focus trap. This can be useful if you want to force the user to make a decision instead of allowing an easy way out.
|
|
64
70
|
- **clickOutsideDeactivates** {boolean}: Default: `false`. If `true`, a click outside the focus trap will deactivate the focus trap and allow the click event to do its thing.
|
|
65
71
|
- **returnFocusOnDeactivate** {boolean}: Default: `true`. If `false`, when the trap is deactivated, focus will *not* return to the element that had focus before activation.
|
|
72
|
+
- **setReturnFocus** {element|string|function}: By default, focus trap on deactivation will return to the element that was focused before activation. With this option you can specify another element to programmatically receive focus after deactivation. Can be a DOM node, or a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns a DOM node.
|
|
73
|
+
- **allowOutsideClick** {function}: If set and returns `true`, a click outside the focus trap will not be prevented, even when `clickOutsideDeactivates` is `false`.
|
|
66
74
|
|
|
67
75
|
### focusTrap.activate([activateOptions])
|
|
68
76
|
|
package/dist/focus-trap.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
var tabbable = require('tabbable');
|
|
3
3
|
var xtend = require('xtend');
|
|
4
4
|
|
|
5
|
+
var activeFocusDelay;
|
|
6
|
+
|
|
5
7
|
var activeFocusTraps = (function() {
|
|
6
8
|
var trapQueue = [];
|
|
7
9
|
return {
|
|
@@ -91,6 +93,8 @@ function focusTrap(element, userOptions) {
|
|
|
91
93
|
function deactivate(deactivateOptions) {
|
|
92
94
|
if (!state.active) return;
|
|
93
95
|
|
|
96
|
+
clearTimeout(activeFocusDelay);
|
|
97
|
+
|
|
94
98
|
removeListeners();
|
|
95
99
|
state.active = false;
|
|
96
100
|
state.paused = false;
|
|
@@ -111,7 +115,7 @@ function focusTrap(element, userOptions) {
|
|
|
111
115
|
: config.returnFocusOnDeactivate;
|
|
112
116
|
if (returnFocus) {
|
|
113
117
|
delay(function() {
|
|
114
|
-
tryFocus(state.nodeFocusedBeforeActivation);
|
|
118
|
+
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
|
|
115
119
|
});
|
|
116
120
|
}
|
|
117
121
|
|
|
@@ -127,6 +131,7 @@ function focusTrap(element, userOptions) {
|
|
|
127
131
|
function unpause() {
|
|
128
132
|
if (!state.paused || !state.active) return;
|
|
129
133
|
state.paused = false;
|
|
134
|
+
updateTabbableNodes();
|
|
130
135
|
addListeners();
|
|
131
136
|
}
|
|
132
137
|
|
|
@@ -136,18 +141,29 @@ function focusTrap(element, userOptions) {
|
|
|
136
141
|
// There can be only one listening focus trap at a time
|
|
137
142
|
activeFocusTraps.activateTrap(trap);
|
|
138
143
|
|
|
139
|
-
updateTabbableNodes();
|
|
140
|
-
|
|
141
144
|
// Delay ensures that the focused element doesn't capture the event
|
|
142
145
|
// that caused the focus trap activation.
|
|
143
|
-
delay(function() {
|
|
146
|
+
activeFocusDelay = delay(function() {
|
|
144
147
|
tryFocus(getInitialFocusNode());
|
|
145
148
|
});
|
|
149
|
+
|
|
146
150
|
doc.addEventListener('focusin', checkFocusIn, true);
|
|
147
|
-
doc.addEventListener('mousedown', checkPointerDown,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
doc.addEventListener('mousedown', checkPointerDown, {
|
|
152
|
+
capture: true,
|
|
153
|
+
passive: false
|
|
154
|
+
});
|
|
155
|
+
doc.addEventListener('touchstart', checkPointerDown, {
|
|
156
|
+
capture: true,
|
|
157
|
+
passive: false
|
|
158
|
+
});
|
|
159
|
+
doc.addEventListener('click', checkClick, {
|
|
160
|
+
capture: true,
|
|
161
|
+
passive: false
|
|
162
|
+
});
|
|
163
|
+
doc.addEventListener('keydown', checkKey, {
|
|
164
|
+
capture: true,
|
|
165
|
+
passive: false
|
|
166
|
+
});
|
|
151
167
|
|
|
152
168
|
return trap;
|
|
153
169
|
}
|
|
@@ -197,13 +213,18 @@ function focusTrap(element, userOptions) {
|
|
|
197
213
|
|
|
198
214
|
if (!node) {
|
|
199
215
|
throw new Error(
|
|
200
|
-
|
|
216
|
+
'Your focus-trap needs to have at least one focusable element'
|
|
201
217
|
);
|
|
202
218
|
}
|
|
203
219
|
|
|
204
220
|
return node;
|
|
205
221
|
}
|
|
206
222
|
|
|
223
|
+
function getReturnFocusNode(previousActiveElement) {
|
|
224
|
+
var node = getNodeForOption('setReturnFocus');
|
|
225
|
+
return node ? node : previousActiveElement;
|
|
226
|
+
}
|
|
227
|
+
|
|
207
228
|
// This needs to be done on mousedown and touchstart instead of click
|
|
208
229
|
// so that it precedes the focus event.
|
|
209
230
|
function checkPointerDown(e) {
|
|
@@ -212,9 +233,15 @@ function focusTrap(element, userOptions) {
|
|
|
212
233
|
deactivate({
|
|
213
234
|
returnFocus: !tabbable.isFocusable(e.target)
|
|
214
235
|
});
|
|
215
|
-
|
|
216
|
-
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
// This is needed for mobile devices.
|
|
239
|
+
// (If we'll only let `click` events through,
|
|
240
|
+
// then on mobile they will be blocked anyways if `touchstart` is blocked.)
|
|
241
|
+
if (config.allowOutsideClick && config.allowOutsideClick(e)) {
|
|
242
|
+
return;
|
|
217
243
|
}
|
|
244
|
+
e.preventDefault();
|
|
218
245
|
}
|
|
219
246
|
|
|
220
247
|
// In case focus escapes the trap for some strange reason, pull it back in.
|
|
@@ -260,6 +287,9 @@ function focusTrap(element, userOptions) {
|
|
|
260
287
|
function checkClick(e) {
|
|
261
288
|
if (config.clickOutsideDeactivates) return;
|
|
262
289
|
if (container.contains(e.target)) return;
|
|
290
|
+
if (config.allowOutsideClick && config.allowOutsideClick(e)) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
263
293
|
e.preventDefault();
|
|
264
294
|
e.stopImmediatePropagation();
|
|
265
295
|
}
|
|
@@ -277,7 +307,6 @@ function focusTrap(element, userOptions) {
|
|
|
277
307
|
tryFocus(getInitialFocusNode());
|
|
278
308
|
return;
|
|
279
309
|
}
|
|
280
|
-
|
|
281
310
|
node.focus();
|
|
282
311
|
state.mostRecentlyFocusedNode = node;
|
|
283
312
|
if (isSelectableInput(node)) {
|
|
@@ -329,11 +358,9 @@ var matches = typeof Element === 'undefined'
|
|
|
329
358
|
function tabbable(el, options) {
|
|
330
359
|
options = options || {};
|
|
331
360
|
|
|
332
|
-
var elementDocument = el.ownerDocument || el;
|
|
333
361
|
var regularTabbables = [];
|
|
334
362
|
var orderedTabbables = [];
|
|
335
363
|
|
|
336
|
-
var untouchabilityChecker = new UntouchabilityChecker(elementDocument);
|
|
337
364
|
var candidates = el.querySelectorAll(candidateSelector);
|
|
338
365
|
|
|
339
366
|
if (options.includeContainer) {
|
|
@@ -347,7 +374,7 @@ function tabbable(el, options) {
|
|
|
347
374
|
for (i = 0; i < candidates.length; i++) {
|
|
348
375
|
candidate = candidates[i];
|
|
349
376
|
|
|
350
|
-
if (!isNodeMatchingSelectorTabbable(candidate
|
|
377
|
+
if (!isNodeMatchingSelectorTabbable(candidate)) continue;
|
|
351
378
|
|
|
352
379
|
candidateTabindex = getTabindex(candidate);
|
|
353
380
|
if (candidateTabindex === 0) {
|
|
@@ -372,9 +399,9 @@ function tabbable(el, options) {
|
|
|
372
399
|
tabbable.isTabbable = isTabbable;
|
|
373
400
|
tabbable.isFocusable = isFocusable;
|
|
374
401
|
|
|
375
|
-
function isNodeMatchingSelectorTabbable(node
|
|
402
|
+
function isNodeMatchingSelectorTabbable(node) {
|
|
376
403
|
if (
|
|
377
|
-
!isNodeMatchingSelectorFocusable(node
|
|
404
|
+
!isNodeMatchingSelectorFocusable(node)
|
|
378
405
|
|| isNonTabbableRadio(node)
|
|
379
406
|
|| getTabindex(node) < 0
|
|
380
407
|
) {
|
|
@@ -383,18 +410,17 @@ function isNodeMatchingSelectorTabbable(node, untouchabilityChecker) {
|
|
|
383
410
|
return true;
|
|
384
411
|
}
|
|
385
412
|
|
|
386
|
-
function isTabbable(node
|
|
413
|
+
function isTabbable(node) {
|
|
387
414
|
if (!node) throw new Error('No node provided');
|
|
388
415
|
if (matches.call(node, candidateSelector) === false) return false;
|
|
389
|
-
return isNodeMatchingSelectorTabbable(node
|
|
416
|
+
return isNodeMatchingSelectorTabbable(node);
|
|
390
417
|
}
|
|
391
418
|
|
|
392
|
-
function isNodeMatchingSelectorFocusable(node
|
|
393
|
-
untouchabilityChecker = untouchabilityChecker || new UntouchabilityChecker(node.ownerDocument || node);
|
|
419
|
+
function isNodeMatchingSelectorFocusable(node) {
|
|
394
420
|
if (
|
|
395
421
|
node.disabled
|
|
396
422
|
|| isHiddenInput(node)
|
|
397
|
-
||
|
|
423
|
+
|| isHidden(node)
|
|
398
424
|
) {
|
|
399
425
|
return false;
|
|
400
426
|
}
|
|
@@ -402,10 +428,10 @@ function isNodeMatchingSelectorFocusable(node, untouchabilityChecker) {
|
|
|
402
428
|
}
|
|
403
429
|
|
|
404
430
|
var focusableCandidateSelector = candidateSelectors.concat('iframe').join(',');
|
|
405
|
-
function isFocusable(node
|
|
431
|
+
function isFocusable(node) {
|
|
406
432
|
if (!node) throw new Error('No node provided');
|
|
407
433
|
if (matches.call(node, focusableCandidateSelector) === false) return false;
|
|
408
|
-
return isNodeMatchingSelectorFocusable(node
|
|
434
|
+
return isNodeMatchingSelectorFocusable(node);
|
|
409
435
|
}
|
|
410
436
|
|
|
411
437
|
function getTabindex(node) {
|
|
@@ -421,13 +447,6 @@ function sortOrderedTabbables(a, b) {
|
|
|
421
447
|
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex;
|
|
422
448
|
}
|
|
423
449
|
|
|
424
|
-
// Array.prototype.find not available in IE.
|
|
425
|
-
function find(list, predicate) {
|
|
426
|
-
for (var i = 0, length = list.length; i < length; i++) {
|
|
427
|
-
if (predicate(list[i])) return list[i];
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
450
|
function isContentEditable(node) {
|
|
432
451
|
return node.contentEditable === 'true';
|
|
433
452
|
}
|
|
@@ -465,47 +484,10 @@ function isTabbableRadio(node) {
|
|
|
465
484
|
return !checked || checked === node;
|
|
466
485
|
}
|
|
467
486
|
|
|
468
|
-
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
// Node cache must be refreshed on every check, in case
|
|
473
|
-
// the content of the element has changed. The cache contains tuples
|
|
474
|
-
// mapping nodes to their boolean result.
|
|
475
|
-
this.cache = [];
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// getComputedStyle accurately reflects `visibility: hidden` of ancestors
|
|
479
|
-
// but not `display: none`, so we need to recursively check parents.
|
|
480
|
-
UntouchabilityChecker.prototype.hasDisplayNone = function hasDisplayNone(node, nodeComputedStyle) {
|
|
481
|
-
if (node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
482
|
-
|
|
483
|
-
// Search for a cached result.
|
|
484
|
-
var cached = find(this.cache, function(item) {
|
|
485
|
-
return item === node;
|
|
486
|
-
});
|
|
487
|
-
if (cached) return cached[1];
|
|
488
|
-
|
|
489
|
-
nodeComputedStyle = nodeComputedStyle || this.doc.defaultView.getComputedStyle(node);
|
|
490
|
-
|
|
491
|
-
var result = false;
|
|
492
|
-
|
|
493
|
-
if (nodeComputedStyle.display === 'none') {
|
|
494
|
-
result = true;
|
|
495
|
-
} else if (node.parentNode) {
|
|
496
|
-
result = this.hasDisplayNone(node.parentNode);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
this.cache.push([node, result]);
|
|
500
|
-
|
|
501
|
-
return result;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
UntouchabilityChecker.prototype.isUntouchable = function isUntouchable(node) {
|
|
505
|
-
if (node === this.doc.documentElement) return false;
|
|
506
|
-
var computedStyle = this.doc.defaultView.getComputedStyle(node);
|
|
507
|
-
if (this.hasDisplayNone(node, computedStyle)) return true;
|
|
508
|
-
return computedStyle.visibility === 'hidden';
|
|
487
|
+
function isHidden(node) {
|
|
488
|
+
// offsetParent being null will allow detecting cases where an element is invisible or inside an invisible element,
|
|
489
|
+
// as long as the element does not use position: fixed. For them, their visibility has to be checked directly as well.
|
|
490
|
+
return node.offsetParent === null || getComputedStyle(node).visibility === 'hidden';
|
|
509
491
|
}
|
|
510
492
|
|
|
511
493
|
module.exports = tabbable;
|
package/dist/focus-trap.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r}()({1:[function(require,module,exports){var tabbable=require("tabbable");var xtend=require("xtend");var activeFocusTraps=function(){var trapQueue=[];return{activateTrap:function(trap){if(trapQueue.length>0){var activeTrap=trapQueue[trapQueue.length-1];if(activeTrap!==trap){activeTrap.pause()}}var trapIndex=trapQueue.indexOf(trap);if(trapIndex===-1){trapQueue.push(trap)}else{trapQueue.splice(trapIndex,1);trapQueue.push(trap)}},deactivateTrap:function(trap){var trapIndex=trapQueue.indexOf(trap);if(trapIndex!==-1){trapQueue.splice(trapIndex,1)}if(trapQueue.length>0){trapQueue[trapQueue.length-1].unpause()}}}}();function focusTrap(element,userOptions){var doc=document;var container=typeof element==="string"?doc.querySelector(element):element;var config=xtend({returnFocusOnDeactivate:true,escapeDeactivates:true},userOptions);var state={firstTabbableNode:null,lastTabbableNode:null,nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:false,paused:false};var trap={activate:activate,deactivate:deactivate,pause:pause,unpause:unpause};return trap;function activate(activateOptions){if(state.active)return;updateTabbableNodes();state.active=true;state.paused=false;state.nodeFocusedBeforeActivation=doc.activeElement;var onActivate=activateOptions&&activateOptions.onActivate?activateOptions.onActivate:config.onActivate;if(onActivate){onActivate()}addListeners();return trap}function deactivate(deactivateOptions){if(!state.active)return;removeListeners();state.active=false;state.paused=false;activeFocusTraps.deactivateTrap(trap);var onDeactivate=deactivateOptions&&deactivateOptions.onDeactivate!==undefined?deactivateOptions.onDeactivate:config.onDeactivate;if(onDeactivate){onDeactivate()}var returnFocus=deactivateOptions&&deactivateOptions.returnFocus!==undefined?deactivateOptions.returnFocus:config.returnFocusOnDeactivate;if(returnFocus){delay(function(){tryFocus(state.nodeFocusedBeforeActivation)})}return trap}function pause(){if(state.paused||!state.active)return;state.paused=true;removeListeners()}function unpause(){if(!state.paused||!state.active)return;state.paused=false;addListeners()}function addListeners(){if(!state.active)return;activeFocusTraps.activateTrap(trap);updateTabbableNodes();delay(function(){tryFocus(getInitialFocusNode())});doc.addEventListener("focusin",checkFocusIn,true);doc.addEventListener("mousedown",checkPointerDown,true);doc.addEventListener("touchstart",checkPointerDown,true);doc.addEventListener("click",checkClick,true);doc.addEventListener("keydown",checkKey,true);return trap}function removeListeners(){if(!state.active)return;doc.removeEventListener("focusin",checkFocusIn,true);doc.removeEventListener("mousedown",checkPointerDown,true);doc.removeEventListener("touchstart",checkPointerDown,true);doc.removeEventListener("click",checkClick,true);doc.removeEventListener("keydown",checkKey,true);return trap}function getNodeForOption(optionName){var optionValue=config[optionName];var node=optionValue;if(!optionValue){return null}if(typeof optionValue==="string"){node=doc.querySelector(optionValue);if(!node){throw new Error("`"+optionName+"` refers to no known node")}}if(typeof optionValue==="function"){node=optionValue();if(!node){throw new Error("`"+optionName+"` did not return a node")}}return node}function getInitialFocusNode(){var node;if(getNodeForOption("initialFocus")!==null){node=getNodeForOption("initialFocus")}else if(container.contains(doc.activeElement)){node=doc.activeElement}else{node=state.firstTabbableNode||getNodeForOption("fallbackFocus")}if(!node){throw new Error("You can't have a focus-trap without at least one focusable element")}return node}function checkPointerDown(e){if(container.contains(e.target))return;if(config.clickOutsideDeactivates){deactivate({returnFocus:!tabbable.isFocusable(e.target)})}else{e.preventDefault()}}function checkFocusIn(e){if(container.contains(e.target)||e.target instanceof Document){return}e.stopImmediatePropagation();tryFocus(state.mostRecentlyFocusedNode||getInitialFocusNode())}function checkKey(e){if(config.escapeDeactivates!==false&&isEscapeEvent(e)){e.preventDefault();deactivate();return}if(isTabEvent(e)){checkTab(e);return}}function checkTab(e){updateTabbableNodes();if(e.shiftKey&&e.target===state.firstTabbableNode){e.preventDefault();tryFocus(state.lastTabbableNode);return}if(!e.shiftKey&&e.target===state.lastTabbableNode){e.preventDefault();tryFocus(state.firstTabbableNode);return}}function checkClick(e){if(config.clickOutsideDeactivates)return;if(container.contains(e.target))return;e.preventDefault();e.stopImmediatePropagation()}function updateTabbableNodes(){var tabbableNodes=tabbable(container);state.firstTabbableNode=tabbableNodes[0]||getInitialFocusNode();state.lastTabbableNode=tabbableNodes[tabbableNodes.length-1]||getInitialFocusNode()}function tryFocus(node){if(node===doc.activeElement)return;if(!node||!node.focus){tryFocus(getInitialFocusNode());return}node.focus();state.mostRecentlyFocusedNode=node;if(isSelectableInput(node)){node.select()}}}function isSelectableInput(node){return node.tagName&&node.tagName.toLowerCase()==="input"&&typeof node.select==="function"}function isEscapeEvent(e){return e.key==="Escape"||e.key==="Esc"||e.keyCode===27}function isTabEvent(e){return e.key==="Tab"||e.keyCode===9}function delay(fn){return setTimeout(fn,0)}module.exports=focusTrap},{tabbable:2,xtend:3}],2:[function(require,module,exports){var candidateSelectors=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'];var candidateSelector=candidateSelectors.join(",");var matches=typeof Element==="undefined"?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector;function tabbable(el,options){options=options||{};var elementDocument=el.ownerDocument||el;var regularTabbables=[];var orderedTabbables=[];var untouchabilityChecker=new UntouchabilityChecker(elementDocument);var candidates=el.querySelectorAll(candidateSelector);if(options.includeContainer){if(matches.call(el,candidateSelector)){candidates=Array.prototype.slice.apply(candidates);candidates.unshift(el)}}var i,candidate,candidateTabindex;for(i=0;i<candidates.length;i++){candidate=candidates[i];if(!isNodeMatchingSelectorTabbable(candidate,untouchabilityChecker))continue;candidateTabindex=getTabindex(candidate);if(candidateTabindex===0){regularTabbables.push(candidate)}else{orderedTabbables.push({documentOrder:i,tabIndex:candidateTabindex,node:candidate})}}var tabbableNodes=orderedTabbables.sort(sortOrderedTabbables).map(function(a){return a.node}).concat(regularTabbables);return tabbableNodes}tabbable.isTabbable=isTabbable;tabbable.isFocusable=isFocusable;function isNodeMatchingSelectorTabbable(node,untouchabilityChecker){if(!isNodeMatchingSelectorFocusable(node,untouchabilityChecker)||isNonTabbableRadio(node)||getTabindex(node)<0){return false}return true}function isTabbable(node,untouchabilityChecker){if(!node)throw new Error("No node provided");if(matches.call(node,candidateSelector)===false)return false;return isNodeMatchingSelectorTabbable(node,untouchabilityChecker)}function isNodeMatchingSelectorFocusable(node,untouchabilityChecker){untouchabilityChecker=untouchabilityChecker||new UntouchabilityChecker(node.ownerDocument||node);if(node.disabled||isHiddenInput(node)||untouchabilityChecker.isUntouchable(node)){return false}return true}var focusableCandidateSelector=candidateSelectors.concat("iframe").join(",");function isFocusable(node,untouchabilityChecker){if(!node)throw new Error("No node provided");if(matches.call(node,focusableCandidateSelector)===false)return false;return isNodeMatchingSelectorFocusable(node,untouchabilityChecker)}function getTabindex(node){var tabindexAttr=parseInt(node.getAttribute("tabindex"),10);if(!isNaN(tabindexAttr))return tabindexAttr;if(isContentEditable(node))return 0;return node.tabIndex}function sortOrderedTabbables(a,b){return a.tabIndex===b.tabIndex?a.documentOrder-b.documentOrder:a.tabIndex-b.tabIndex}function find(list,predicate){for(var i=0,length=list.length;i<length;i++){if(predicate(list[i]))return list[i]}}function isContentEditable(node){return node.contentEditable==="true"}function isInput(node){return node.tagName==="INPUT"}function isHiddenInput(node){return isInput(node)&&node.type==="hidden"}function isRadio(node){return isInput(node)&&node.type==="radio"}function isNonTabbableRadio(node){return isRadio(node)&&!isTabbableRadio(node)}function getCheckedRadio(nodes){for(var i=0;i<nodes.length;i++){if(nodes[i].checked){return nodes[i]}}}function isTabbableRadio(node){if(!node.name)return true;var radioSet=node.ownerDocument.querySelectorAll('input[type="radio"][name="'+node.name+'"]');var checked=getCheckedRadio(radioSet);return!checked||checked===node}function UntouchabilityChecker(elementDocument){this.doc=elementDocument;this.cache=[]}UntouchabilityChecker.prototype.hasDisplayNone=function hasDisplayNone(node,nodeComputedStyle){if(node.nodeType!==Node.ELEMENT_NODE)return false;var cached=find(this.cache,function(item){return item===node});if(cached)return cached[1];nodeComputedStyle=nodeComputedStyle||this.doc.defaultView.getComputedStyle(node);var result=false;if(nodeComputedStyle.display==="none"){result=true}else if(node.parentNode){result=this.hasDisplayNone(node.parentNode)}this.cache.push([node,result]);return result};UntouchabilityChecker.prototype.isUntouchable=function isUntouchable(node){if(node===this.doc.documentElement)return false;var computedStyle=this.doc.defaultView.getComputedStyle(node);if(this.hasDisplayNone(node,computedStyle))return true;return computedStyle.visibility==="hidden"};module.exports=tabbable},{}],3:[function(require,module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(){var target={};for(var i=0;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(hasOwnProperty.call(source,key)){target[key]=source[key]}}}return target}},{}]},{},[1])(1)});
|
|
1
|
+
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r}()({1:[function(require,module,exports){var tabbable=require("tabbable");var xtend=require("xtend");var activeFocusDelay;var activeFocusTraps=function(){var trapQueue=[];return{activateTrap:function(trap){if(trapQueue.length>0){var activeTrap=trapQueue[trapQueue.length-1];if(activeTrap!==trap){activeTrap.pause()}}var trapIndex=trapQueue.indexOf(trap);if(trapIndex===-1){trapQueue.push(trap)}else{trapQueue.splice(trapIndex,1);trapQueue.push(trap)}},deactivateTrap:function(trap){var trapIndex=trapQueue.indexOf(trap);if(trapIndex!==-1){trapQueue.splice(trapIndex,1)}if(trapQueue.length>0){trapQueue[trapQueue.length-1].unpause()}}}}();function focusTrap(element,userOptions){var doc=document;var container=typeof element==="string"?doc.querySelector(element):element;var config=xtend({returnFocusOnDeactivate:true,escapeDeactivates:true},userOptions);var state={firstTabbableNode:null,lastTabbableNode:null,nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:false,paused:false};var trap={activate:activate,deactivate:deactivate,pause:pause,unpause:unpause};return trap;function activate(activateOptions){if(state.active)return;updateTabbableNodes();state.active=true;state.paused=false;state.nodeFocusedBeforeActivation=doc.activeElement;var onActivate=activateOptions&&activateOptions.onActivate?activateOptions.onActivate:config.onActivate;if(onActivate){onActivate()}addListeners();return trap}function deactivate(deactivateOptions){if(!state.active)return;clearTimeout(activeFocusDelay);removeListeners();state.active=false;state.paused=false;activeFocusTraps.deactivateTrap(trap);var onDeactivate=deactivateOptions&&deactivateOptions.onDeactivate!==undefined?deactivateOptions.onDeactivate:config.onDeactivate;if(onDeactivate){onDeactivate()}var returnFocus=deactivateOptions&&deactivateOptions.returnFocus!==undefined?deactivateOptions.returnFocus:config.returnFocusOnDeactivate;if(returnFocus){delay(function(){tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation))})}return trap}function pause(){if(state.paused||!state.active)return;state.paused=true;removeListeners()}function unpause(){if(!state.paused||!state.active)return;state.paused=false;updateTabbableNodes();addListeners()}function addListeners(){if(!state.active)return;activeFocusTraps.activateTrap(trap);activeFocusDelay=delay(function(){tryFocus(getInitialFocusNode())});doc.addEventListener("focusin",checkFocusIn,true);doc.addEventListener("mousedown",checkPointerDown,{capture:true,passive:false});doc.addEventListener("touchstart",checkPointerDown,{capture:true,passive:false});doc.addEventListener("click",checkClick,{capture:true,passive:false});doc.addEventListener("keydown",checkKey,{capture:true,passive:false});return trap}function removeListeners(){if(!state.active)return;doc.removeEventListener("focusin",checkFocusIn,true);doc.removeEventListener("mousedown",checkPointerDown,true);doc.removeEventListener("touchstart",checkPointerDown,true);doc.removeEventListener("click",checkClick,true);doc.removeEventListener("keydown",checkKey,true);return trap}function getNodeForOption(optionName){var optionValue=config[optionName];var node=optionValue;if(!optionValue){return null}if(typeof optionValue==="string"){node=doc.querySelector(optionValue);if(!node){throw new Error("`"+optionName+"` refers to no known node")}}if(typeof optionValue==="function"){node=optionValue();if(!node){throw new Error("`"+optionName+"` did not return a node")}}return node}function getInitialFocusNode(){var node;if(getNodeForOption("initialFocus")!==null){node=getNodeForOption("initialFocus")}else if(container.contains(doc.activeElement)){node=doc.activeElement}else{node=state.firstTabbableNode||getNodeForOption("fallbackFocus")}if(!node){throw new Error("Your focus-trap needs to have at least one focusable element")}return node}function getReturnFocusNode(previousActiveElement){var node=getNodeForOption("setReturnFocus");return node?node:previousActiveElement}function checkPointerDown(e){if(container.contains(e.target))return;if(config.clickOutsideDeactivates){deactivate({returnFocus:!tabbable.isFocusable(e.target)});return}if(config.allowOutsideClick&&config.allowOutsideClick(e)){return}e.preventDefault()}function checkFocusIn(e){if(container.contains(e.target)||e.target instanceof Document){return}e.stopImmediatePropagation();tryFocus(state.mostRecentlyFocusedNode||getInitialFocusNode())}function checkKey(e){if(config.escapeDeactivates!==false&&isEscapeEvent(e)){e.preventDefault();deactivate();return}if(isTabEvent(e)){checkTab(e);return}}function checkTab(e){updateTabbableNodes();if(e.shiftKey&&e.target===state.firstTabbableNode){e.preventDefault();tryFocus(state.lastTabbableNode);return}if(!e.shiftKey&&e.target===state.lastTabbableNode){e.preventDefault();tryFocus(state.firstTabbableNode);return}}function checkClick(e){if(config.clickOutsideDeactivates)return;if(container.contains(e.target))return;if(config.allowOutsideClick&&config.allowOutsideClick(e)){return}e.preventDefault();e.stopImmediatePropagation()}function updateTabbableNodes(){var tabbableNodes=tabbable(container);state.firstTabbableNode=tabbableNodes[0]||getInitialFocusNode();state.lastTabbableNode=tabbableNodes[tabbableNodes.length-1]||getInitialFocusNode()}function tryFocus(node){if(node===doc.activeElement)return;if(!node||!node.focus){tryFocus(getInitialFocusNode());return}node.focus();state.mostRecentlyFocusedNode=node;if(isSelectableInput(node)){node.select()}}}function isSelectableInput(node){return node.tagName&&node.tagName.toLowerCase()==="input"&&typeof node.select==="function"}function isEscapeEvent(e){return e.key==="Escape"||e.key==="Esc"||e.keyCode===27}function isTabEvent(e){return e.key==="Tab"||e.keyCode===9}function delay(fn){return setTimeout(fn,0)}module.exports=focusTrap},{tabbable:2,xtend:3}],2:[function(require,module,exports){var candidateSelectors=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'];var candidateSelector=candidateSelectors.join(",");var matches=typeof Element==="undefined"?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector;function tabbable(el,options){options=options||{};var regularTabbables=[];var orderedTabbables=[];var candidates=el.querySelectorAll(candidateSelector);if(options.includeContainer){if(matches.call(el,candidateSelector)){candidates=Array.prototype.slice.apply(candidates);candidates.unshift(el)}}var i,candidate,candidateTabindex;for(i=0;i<candidates.length;i++){candidate=candidates[i];if(!isNodeMatchingSelectorTabbable(candidate))continue;candidateTabindex=getTabindex(candidate);if(candidateTabindex===0){regularTabbables.push(candidate)}else{orderedTabbables.push({documentOrder:i,tabIndex:candidateTabindex,node:candidate})}}var tabbableNodes=orderedTabbables.sort(sortOrderedTabbables).map(function(a){return a.node}).concat(regularTabbables);return tabbableNodes}tabbable.isTabbable=isTabbable;tabbable.isFocusable=isFocusable;function isNodeMatchingSelectorTabbable(node){if(!isNodeMatchingSelectorFocusable(node)||isNonTabbableRadio(node)||getTabindex(node)<0){return false}return true}function isTabbable(node){if(!node)throw new Error("No node provided");if(matches.call(node,candidateSelector)===false)return false;return isNodeMatchingSelectorTabbable(node)}function isNodeMatchingSelectorFocusable(node){if(node.disabled||isHiddenInput(node)||isHidden(node)){return false}return true}var focusableCandidateSelector=candidateSelectors.concat("iframe").join(",");function isFocusable(node){if(!node)throw new Error("No node provided");if(matches.call(node,focusableCandidateSelector)===false)return false;return isNodeMatchingSelectorFocusable(node)}function getTabindex(node){var tabindexAttr=parseInt(node.getAttribute("tabindex"),10);if(!isNaN(tabindexAttr))return tabindexAttr;if(isContentEditable(node))return 0;return node.tabIndex}function sortOrderedTabbables(a,b){return a.tabIndex===b.tabIndex?a.documentOrder-b.documentOrder:a.tabIndex-b.tabIndex}function isContentEditable(node){return node.contentEditable==="true"}function isInput(node){return node.tagName==="INPUT"}function isHiddenInput(node){return isInput(node)&&node.type==="hidden"}function isRadio(node){return isInput(node)&&node.type==="radio"}function isNonTabbableRadio(node){return isRadio(node)&&!isTabbableRadio(node)}function getCheckedRadio(nodes){for(var i=0;i<nodes.length;i++){if(nodes[i].checked){return nodes[i]}}}function isTabbableRadio(node){if(!node.name)return true;var radioSet=node.ownerDocument.querySelectorAll('input[type="radio"][name="'+node.name+'"]');var checked=getCheckedRadio(radioSet);return!checked||checked===node}function isHidden(node){return node.offsetParent===null||getComputedStyle(node).visibility==="hidden"}module.exports=tabbable},{}],3:[function(require,module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(){var target={};for(var i=0;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(hasOwnProperty.call(source,key)){target[key]=source[key]}}}return target}},{}]},{},[1])(1)});
|
package/index.d.ts
CHANGED
|
@@ -41,6 +41,12 @@ declare module "focus-trap" {
|
|
|
41
41
|
*/
|
|
42
42
|
returnFocusOnDeactivate?: boolean;
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* By default, focus trap on deactivation will return to the element
|
|
46
|
+
* that was focused before activation.
|
|
47
|
+
*/
|
|
48
|
+
setReturnFocus?: FocusTarget;
|
|
49
|
+
|
|
44
50
|
/**
|
|
45
51
|
* Default: `true`. If `false`, the `Escape` key will not trigger
|
|
46
52
|
* deactivation of the focus trap. This can be useful if you want
|
|
@@ -54,6 +60,8 @@ declare module "focus-trap" {
|
|
|
54
60
|
* deactivate the focus trap and allow the click event to do its thing.
|
|
55
61
|
*/
|
|
56
62
|
clickOutsideDeactivates?: boolean;
|
|
63
|
+
|
|
64
|
+
allowOutsideClick?: (event: MouseEvent) => boolean;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
type ActivateOptions = Pick<Options, "onActivate">;
|
package/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
var tabbable = require('tabbable');
|
|
2
2
|
var xtend = require('xtend');
|
|
3
3
|
|
|
4
|
+
var activeFocusDelay;
|
|
5
|
+
|
|
4
6
|
var activeFocusTraps = (function() {
|
|
5
7
|
var trapQueue = [];
|
|
6
8
|
return {
|
|
@@ -90,6 +92,8 @@ function focusTrap(element, userOptions) {
|
|
|
90
92
|
function deactivate(deactivateOptions) {
|
|
91
93
|
if (!state.active) return;
|
|
92
94
|
|
|
95
|
+
clearTimeout(activeFocusDelay);
|
|
96
|
+
|
|
93
97
|
removeListeners();
|
|
94
98
|
state.active = false;
|
|
95
99
|
state.paused = false;
|
|
@@ -110,7 +114,7 @@ function focusTrap(element, userOptions) {
|
|
|
110
114
|
: config.returnFocusOnDeactivate;
|
|
111
115
|
if (returnFocus) {
|
|
112
116
|
delay(function() {
|
|
113
|
-
tryFocus(state.nodeFocusedBeforeActivation);
|
|
117
|
+
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
|
|
114
118
|
});
|
|
115
119
|
}
|
|
116
120
|
|
|
@@ -126,6 +130,7 @@ function focusTrap(element, userOptions) {
|
|
|
126
130
|
function unpause() {
|
|
127
131
|
if (!state.paused || !state.active) return;
|
|
128
132
|
state.paused = false;
|
|
133
|
+
updateTabbableNodes();
|
|
129
134
|
addListeners();
|
|
130
135
|
}
|
|
131
136
|
|
|
@@ -135,18 +140,29 @@ function focusTrap(element, userOptions) {
|
|
|
135
140
|
// There can be only one listening focus trap at a time
|
|
136
141
|
activeFocusTraps.activateTrap(trap);
|
|
137
142
|
|
|
138
|
-
updateTabbableNodes();
|
|
139
|
-
|
|
140
143
|
// Delay ensures that the focused element doesn't capture the event
|
|
141
144
|
// that caused the focus trap activation.
|
|
142
|
-
delay(function() {
|
|
145
|
+
activeFocusDelay = delay(function() {
|
|
143
146
|
tryFocus(getInitialFocusNode());
|
|
144
147
|
});
|
|
148
|
+
|
|
145
149
|
doc.addEventListener('focusin', checkFocusIn, true);
|
|
146
|
-
doc.addEventListener('mousedown', checkPointerDown,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
doc.addEventListener('mousedown', checkPointerDown, {
|
|
151
|
+
capture: true,
|
|
152
|
+
passive: false
|
|
153
|
+
});
|
|
154
|
+
doc.addEventListener('touchstart', checkPointerDown, {
|
|
155
|
+
capture: true,
|
|
156
|
+
passive: false
|
|
157
|
+
});
|
|
158
|
+
doc.addEventListener('click', checkClick, {
|
|
159
|
+
capture: true,
|
|
160
|
+
passive: false
|
|
161
|
+
});
|
|
162
|
+
doc.addEventListener('keydown', checkKey, {
|
|
163
|
+
capture: true,
|
|
164
|
+
passive: false
|
|
165
|
+
});
|
|
150
166
|
|
|
151
167
|
return trap;
|
|
152
168
|
}
|
|
@@ -196,13 +212,18 @@ function focusTrap(element, userOptions) {
|
|
|
196
212
|
|
|
197
213
|
if (!node) {
|
|
198
214
|
throw new Error(
|
|
199
|
-
|
|
215
|
+
'Your focus-trap needs to have at least one focusable element'
|
|
200
216
|
);
|
|
201
217
|
}
|
|
202
218
|
|
|
203
219
|
return node;
|
|
204
220
|
}
|
|
205
221
|
|
|
222
|
+
function getReturnFocusNode(previousActiveElement) {
|
|
223
|
+
var node = getNodeForOption('setReturnFocus');
|
|
224
|
+
return node ? node : previousActiveElement;
|
|
225
|
+
}
|
|
226
|
+
|
|
206
227
|
// This needs to be done on mousedown and touchstart instead of click
|
|
207
228
|
// so that it precedes the focus event.
|
|
208
229
|
function checkPointerDown(e) {
|
|
@@ -211,9 +232,15 @@ function focusTrap(element, userOptions) {
|
|
|
211
232
|
deactivate({
|
|
212
233
|
returnFocus: !tabbable.isFocusable(e.target)
|
|
213
234
|
});
|
|
214
|
-
|
|
215
|
-
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// This is needed for mobile devices.
|
|
238
|
+
// (If we'll only let `click` events through,
|
|
239
|
+
// then on mobile they will be blocked anyways if `touchstart` is blocked.)
|
|
240
|
+
if (config.allowOutsideClick && config.allowOutsideClick(e)) {
|
|
241
|
+
return;
|
|
216
242
|
}
|
|
243
|
+
e.preventDefault();
|
|
217
244
|
}
|
|
218
245
|
|
|
219
246
|
// In case focus escapes the trap for some strange reason, pull it back in.
|
|
@@ -259,6 +286,9 @@ function focusTrap(element, userOptions) {
|
|
|
259
286
|
function checkClick(e) {
|
|
260
287
|
if (config.clickOutsideDeactivates) return;
|
|
261
288
|
if (container.contains(e.target)) return;
|
|
289
|
+
if (config.allowOutsideClick && config.allowOutsideClick(e)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
262
292
|
e.preventDefault();
|
|
263
293
|
e.stopImmediatePropagation();
|
|
264
294
|
}
|
|
@@ -276,7 +306,6 @@ function focusTrap(element, userOptions) {
|
|
|
276
306
|
tryFocus(getInitialFocusNode());
|
|
277
307
|
return;
|
|
278
308
|
}
|
|
279
|
-
|
|
280
309
|
node.focus();
|
|
281
310
|
state.mostRecentlyFocusedNode = node;
|
|
282
311
|
if (isSelectableInput(node)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "focus-trap",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Trap focus within a DOM node.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/davidtheclark/focus-trap#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"tabbable": "^
|
|
39
|
+
"tabbable": "^4.0.0",
|
|
40
40
|
"xtend": "^4.0.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|