jobiqo-cl
Version:
[](https://circleci.com/gh/jobiqo/jobiqo-cl)
288 lines (240 loc) • 7.36 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
require('../tabbable/index.js');
require('../xtend/immutable.js');
var index$1 = require('../../_virtual/index5.js_commonjs-proxy');
var immutable$1 = require('../../_virtual/immutable.js_commonjs-proxy');
var listeningFocusTrap = null;
function focusTrap(element, userOptions) {
var doc = document;
var container =
typeof element === 'string' ? doc.querySelector(element) : element;
var config = immutable$1.default(
{
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;
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;
// There can be only one listening focus trap at a time
if (listeningFocusTrap) {
listeningFocusTrap.pause();
}
listeningFocusTrap = trap;
updateTabbableNodes();
// Delay ensures that the focused element doesn't capture the event
// that caused the focus trap activation.
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 || listeningFocusTrap !== trap) 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);
listeningFocusTrap = null;
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;
}
// 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: !index$1.default.isFocusable(e.target)
});
} else {
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;
e.preventDefault();
e.stopImmediatePropagation();
}
function updateTabbableNodes() {
var tabbableNodes = index$1.default(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);
}
var focusTrap_1 = focusTrap;
exports.__moduleExports = focusTrap_1;