These files should not be added
This commit is contained in:
337
build/javascript/node_modules/focus-trap/index.js
generated
vendored
337
build/javascript/node_modules/focus-trap/index.js
generated
vendored
@@ -1,337 +0,0 @@
|
||||
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 {
|
||||
// move this existing trap to the front of the queue
|
||||
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;
|
||||
|
||||
// There can be only one listening focus trap at a time
|
||||
activeFocusTraps.activateTrap(trap);
|
||||
|
||||
// Delay ensures that the focused element doesn't capture the event
|
||||
// that caused the focus trap activation.
|
||||
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;
|
||||
}
|
||||
|
||||
// This needs to be done on mousedown and touchstart instead of click
|
||||
// so that it precedes the focus event.
|
||||
function checkPointerDown(e) {
|
||||
if (container.contains(e.target)) return;
|
||||
if (config.clickOutsideDeactivates) {
|
||||
deactivate({
|
||||
returnFocus: !tabbable.isFocusable(e.target)
|
||||
});
|
||||
return;
|
||||
}
|
||||
// This is needed for mobile devices.
|
||||
// (If we'll only let `click` events through,
|
||||
// then on mobile they will be blocked anyways if `touchstart` is blocked.)
|
||||
if (config.allowOutsideClick && config.allowOutsideClick(e)) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// In case focus escapes the trap for some strange reason, pull it back in.
|
||||
function checkFocusIn(e) {
|
||||
// In Firefox when you Tab out of an iframe the Document is briefly focused.
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Hijack Tab events on the first and last focusable nodes of the trap,
|
||||
// in order to prevent focus from escaping. If it escapes for even a
|
||||
// moment it can end up scrolling the page and causing confusion so we
|
||||
// kind of need to capture the action at the keydown phase.
|
||||
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;
|
||||
Reference in New Issue
Block a user