omni/firefox/focus.js
2022-01-18 11:32:37 +00:00

476 lines
14 KiB
JavaScript

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.focusLock = factory());
}(this, function () { 'use strict';
var toArray = function toArray(a) {
var ret = Array(a.length);
for (var i = 0; i < a.length; ++i) {
ret[i] = a[i];
}
return ret;
};
var arrayFind = function arrayFind(array, search) {
return array.filter(function (a) {
return a === search;
})[0];
};
var asArray = function asArray(a) {
return Array.isArray(a) ? a : [a];
};
var tabSort = function tabSort(a, b) {
var tabDiff = a.tabIndex - b.tabIndex;
var indexDiff = a.index - b.index;
if (tabDiff) {
if (!a.tabIndex) return 1;
if (!b.tabIndex) return -1;
}
return tabDiff || indexDiff;
};
var orderByTabIndex = function orderByTabIndex(nodes, filterNegative, keepGuards) {
return toArray(nodes).map(function (node, index) {
return {
node: node,
index: index,
tabIndex: keepGuards && node.tabIndex === -1 ? (node.dataset || {}).focusGuard ? 0 : -1 : node.tabIndex
};
}).filter(function (data) {
return !filterNegative || data.tabIndex >= 0;
}).sort(tabSort);
};
var tabbables = ['button:enabled:not([readonly])', 'select:enabled:not([readonly])', 'textarea:enabled:not([readonly])', 'input:enabled:not([readonly])', 'a[href]', 'area[href]', 'iframe', 'object', 'embed', '[tabindex]', '[contenteditable]', '[autofocus]'];
var FOCUS_GROUP = 'data-focus-lock';
var FOCUS_DISABLED = 'data-focus-lock-disabled';
var FOCUS_ALLOW = 'data-no-focus-lock';
var FOCUS_AUTO = 'data-autofocus-inside';
var queryTabbables = tabbables.join(',');
var queryGuardTabbables = queryTabbables + ', [data-focus-guard]';
var getFocusables = function getFocusables(parents, withGuards) {
return parents.reduce(function (acc, parent) {
return acc.concat(
// add all tabbables inside
toArray(parent.querySelectorAll(withGuards ? queryGuardTabbables : queryTabbables)),
// add if node is tabble itself
parent.parentNode ? toArray(parent.parentNode.querySelectorAll(tabbables.join(','))).filter(function (node) {
return node === parent;
}) : []);
}, []);
};
var getParentAutofocusables = function getParentAutofocusables(parent) {
var parentFocus = parent.querySelectorAll('[' + FOCUS_AUTO + ']');
return toArray(parentFocus).map(function (node) {
return getFocusables([node]);
}).reduce(function (acc, nodes) {
return acc.concat(nodes);
}, []);
};
var isElementHidden = function isElementHidden(computedStyle) {
if (!computedStyle || !computedStyle.getPropertyValue) {
return false;
}
return computedStyle.getPropertyValue('display') === 'none' || computedStyle.getPropertyValue('visibility') === 'hidden';
};
var isVisible = function isVisible(node) {
return !node || node === document || !isElementHidden(window.getComputedStyle(node, null)) && isVisible(node.parentNode);
};
var notHiddenInput = function notHiddenInput(node) {
return !((node.tagName === 'INPUT' || node.tagName === 'BUTTON') && (node.type === 'hidden' || node.disabled));
};
var getParents = function getParents(node) {
var parents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
parents.push(node);
if (node.parentNode) {
getParents(node.parentNode, parents);
}
return parents;
};
var getCommonParent = function getCommonParent(nodea, nodeb) {
var parentsA = getParents(nodea);
var parentsB = getParents(nodeb);
for (var i = 0; i < parentsA.length; i += 1) {
var currentParent = parentsA[i];
if (parentsB.indexOf(currentParent) >= 0) {
return currentParent;
}
}
return false;
};
var filterFocusable = function filterFocusable(nodes) {
return toArray(nodes).filter(function (node) {
return isVisible(node);
}).filter(function (node) {
return notHiddenInput(node);
});
};
var getTabbableNodes = function getTabbableNodes(topNodes, withGuards) {
return orderByTabIndex(filterFocusable(getFocusables(topNodes, withGuards)), true, withGuards);
};
var getAllTabbableNodes = function getAllTabbableNodes(topNodes) {
return orderByTabIndex(filterFocusable(getFocusables(topNodes)), false);
};
var parentAutofocusables = function parentAutofocusables(topNode) {
return filterFocusable(getParentAutofocusables(topNode));
};
var isRadio = function isRadio(node) {
return node.tagName === 'INPUT' && node.type === 'radio';
};
var findSelectedRadio = function findSelectedRadio(node, nodes) {
return nodes.filter(isRadio).filter(function (el) {
return el.name === node.name;
}).filter(function (el) {
return el.checked;
})[0] || node;
};
var pickFirstFocus = function pickFirstFocus(nodes) {
if (nodes[0] && nodes.length > 1) {
if (isRadio(nodes[0]) && nodes[0].name) {
return findSelectedRadio(nodes[0], nodes);
}
}
return nodes[0];
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var filterNested = function filterNested(nodes) {
var l = nodes.length;
for (var i = 0; i < l; i += 1) {
var _loop = function _loop(j) {
if (i !== j) {
if (nodes[i].contains(nodes[j])) {
return {
v: filterNested(nodes.filter(function (x) {
return x !== nodes[j];
}))
};
}
}
};
for (var j = 0; j < l; j += 1) {
var _ret = _loop(j);
if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v;
}
}
return nodes;
};
var getTopParent = function getTopParent(node) {
return node.parentNode ? getTopParent(node.parentNode) : node;
};
var getAllAffectedNodes = function getAllAffectedNodes(node) {
var nodes = asArray(node);
return nodes.filter(Boolean).reduce(function (acc, currentNode) {
var group = currentNode.getAttribute(FOCUS_GROUP);
acc.push.apply(acc, group ? filterNested(toArray(getTopParent(currentNode).querySelectorAll('[' + FOCUS_GROUP + '="' + group + '"]:not([' + FOCUS_DISABLED + '="disabled"])'))) : [currentNode]);
return acc;
}, []);
};
var findAutoFocused = function findAutoFocused(autoFocusables) {
return function (node) {
return !!node.autofocus || node.dataset && !!node.dataset.autofocus || autoFocusables.indexOf(node) >= 0;
};
};
var newFocus = function newFocus(innerNodes, outerNodes, activeElement, lastNode, autoFocused) {
var cnt = innerNodes.length;
var firstFocus = innerNodes[0];
var lastFocus = innerNodes[cnt - 1];
// focus is inside
if (innerNodes.indexOf(activeElement) >= 0) {
return undefined;
}
var activeIndex = outerNodes.indexOf(activeElement);
var lastIndex = outerNodes.indexOf(lastNode || activeIndex);
var lastNodeInside = innerNodes.indexOf(lastNode);
var indexDiff = activeIndex - lastIndex;
var firstNodeIndex = outerNodes.indexOf(firstFocus);
var lastNodeIndex = outerNodes.indexOf(lastFocus);
// new focus
if (activeIndex === -1 || lastNodeInside === -1) {
return innerNodes.indexOf(autoFocused.length ? pickFirstFocus(autoFocused) : pickFirstFocus(innerNodes));
}
// old focus
if (!indexDiff && lastNodeInside >= 0) {
return lastNodeInside;
}
// jump out
if (indexDiff && Math.abs(indexDiff) > 1) {
return lastNodeInside;
}
// focus above lock
if (activeIndex <= firstNodeIndex) {
return cnt - 1;
}
// focus below lock
if (activeIndex > lastNodeIndex) {
return 0;
}
// index is inside tab order, but outside Lock
if (indexDiff) {
if (Math.abs(indexDiff) > 1) {
return lastNodeInside;
}
return (cnt + lastNodeInside + indexDiff) % cnt;
}
// do nothing
return undefined;
};
var getTopCommonParent = function getTopCommonParent(baseActiveElement, leftEntry, rightEntries) {
var activeElements = asArray(baseActiveElement);
var leftEntries = asArray(leftEntry);
var activeElement = activeElements[0];
var topCommon = null;
leftEntries.filter(Boolean).forEach(function (entry) {
topCommon = getCommonParent(topCommon || entry, entry) || topCommon;
rightEntries.filter(Boolean).forEach(function (subEntry) {
var common = getCommonParent(activeElement, subEntry);
if (common) {
if (!topCommon || common.contains(topCommon)) {
topCommon = common;
} else {
topCommon = getCommonParent(common, topCommon);
}
}
});
});
return topCommon;
};
var allParentAutofocusables = function allParentAutofocusables(entries) {
return entries.reduce(function (acc, node) {
return acc.concat(parentAutofocusables(node));
}, []);
};
var notAGuard = function notAGuard(node) {
return !(node.dataset && node.dataset.focusGuard);
};
var reorderNodes = function reorderNodes(srcNodes, dstNodes) {
return srcNodes.map(function (dnode) {
return dstNodes.find(function (_ref) {
var node = _ref.node;
return dnode === node;
});
}).filter(Boolean);
};
var getFocusMerge = function getFocusMerge(topNode, lastNode) {
var activeElement = document && document.activeElement;
var entries = getAllAffectedNodes(topNode).filter(notAGuard);
var commonParent = getTopCommonParent(activeElement || topNode, topNode, entries);
var innerElements = getTabbableNodes(entries).filter(function (_ref5) {
var node = _ref5.node;
return notAGuard(node);
});
if (!innerElements[0]) {
innerElements = getAllTabbableNodes(entries).filter(function (_ref6) {
var node = _ref6.node;
return notAGuard(node);
});
if (!innerElements[0]) {
return undefined;
}
}
var outerNodes = getTabbableNodes([commonParent]).map(function (_ref7) {
var node = _ref7.node;
return node;
});
var orderedInnerElements = reorderNodes(outerNodes, innerElements);
var innerNodes = orderedInnerElements.map(function (_ref8) {
var node = _ref8.node;
return node;
});
var newId = newFocus(innerNodes, outerNodes, activeElement, lastNode, innerNodes.filter(findAutoFocused(allParentAutofocusables(entries))));
if (newId === undefined) {
return newId;
}
return orderedInnerElements[newId];
};
var focusInFrame = function focusInFrame(frame) {
return frame === document.activeElement;
};
var focusInsideIframe = function focusInsideIframe(topNode) {
return !!arrayFind(toArray(topNode.querySelectorAll('iframe')), focusInFrame);
};
var focusInside = function focusInside(topNode) {
var activeElement = document && document.activeElement;
if (!activeElement || activeElement.dataset && activeElement.dataset.focusGuard) {
return false;
}
return getAllAffectedNodes(topNode).reduce(function (result, node) {
return result || node.contains(activeElement) || focusInsideIframe(node);
}, false);
};
var focusIsHidden = function focusIsHidden() {
return document && toArray(document.querySelectorAll('[' + FOCUS_ALLOW + ']')).some(function (node) {
return node.contains(document.activeElement);
});
};
var focusOn = function focusOn(target) {
target.focus();
if (target.contentWindow) {
target.contentWindow.focus();
}
};
var guardCount = 0;
var lockDisabled = false;
var setFocus = (function (topNode, lastNode) {
var focusable = getFocusMerge(topNode, lastNode);
if (lockDisabled) {
return;
}
if (focusable) {
if (guardCount > 2) {
// eslint-disable-next-line no-console
console.error('FocusLock: focus-fighting detected. Only one focus management system could be active. ' + 'See https://github.com/theKashey/focus-lock/#focus-fighting');
lockDisabled = true;
setTimeout(function () {
lockDisabled = false;
}, 1);
return;
}
guardCount++;
focusOn(focusable.node);
guardCount--;
}
});
var lastActiveTrap = 0;
var lastActiveFocus = null;
var focusOnBody = function focusOnBody() {
return document && document.activeElement === document.body;
};
var isFreeFocus = function isFreeFocus() {
return focusOnBody() || focusIsHidden();
};
var activateTrap = function activateTrap() {
var result = false;
if (lastActiveTrap) {
var observed = lastActiveTrap;
if (!isFreeFocus()) {
if (observed && !focusInside(observed)) {
result = setFocus(observed, lastActiveFocus);
}
lastActiveFocus = document.activeElement;
}
}
return result;
};
var reducePropsToState = function reducePropsToState(propsList) {
return propsList.filter(function (node) {
return node;
}).slice(-1)[0];
};
var handleStateChangeOnClient = function handleStateChangeOnClient(trap) {
lastActiveTrap = trap;
if (trap) {
activateTrap();
}
};
var instances = [];
var emitChange = function emitChange(event) {
if (handleStateChangeOnClient(reducePropsToState(instances))) {
event && event.preventDefault();
return true;
}
return false;
};
var attachHandler = function attachHandler() {
document.addEventListener('focusin', emitChange);
};
var detachHandler = function detachHandler() {
document.removeEventListener('focusin', emitChange);
};
var focusLock = {
on: function on(domNode) {
if (instances.length === 0) {
attachHandler();
}
if (instances.indexOf(domNode) < 0) {
instances.push(domNode);
emitChange();
}
},
off: function off(domNode) {
instances = instances.filter(function (node) {
return node !== domNode;
});
emitChange();
if (instances.length === 0) {
detachHandler();
}
}
};
return focusLock;
}));