focus-lock
Advanced tools
Comparing version 0.7.0 to 0.8.0
export var FOCUS_GROUP = 'data-focus-lock'; | ||
export var FOCUS_DISABLED = 'data-focus-lock-disabled'; | ||
export var FOCUS_ALLOW = 'data-no-focus-lock'; | ||
export var FOCUS_AUTO = 'data-autofocus-inside'; | ||
export var FOCUS_AUTO = 'data-autofocus-inside'; |
@@ -1,23 +0,13 @@ | ||
import getAllAffectedNodes from './utils/all-affected'; | ||
import { arrayFind, toArray } from './utils/array'; | ||
var focusInFrame = function focusInFrame(frame) { | ||
return frame === document.activeElement; | ||
import { getAllAffectedNodes } from './utils/all-affected'; | ||
import { toArray } from './utils/array'; | ||
var focusInFrame = function (frame) { return frame === document.activeElement; }; | ||
var focusInsideIframe = function (topNode) { | ||
return Boolean(toArray(topNode.querySelectorAll('iframe')).some(function (node) { return focusInFrame(node); })); | ||
}; | ||
var focusInsideIframe = function focusInsideIframe(topNode) { | ||
return !!arrayFind(toArray(topNode.querySelectorAll('iframe')), focusInFrame); | ||
export var focusInside = function (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 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); | ||
}; | ||
export default focusInside; |
@@ -0,10 +1,6 @@ | ||
import { FOCUS_ALLOW } from './constants'; | ||
import { toArray } from './utils/array'; | ||
import { FOCUS_ALLOW } from './constants'; | ||
var focusIsHidden = function focusIsHidden() { | ||
return document && toArray(document.querySelectorAll('[' + FOCUS_ALLOW + ']')).some(function (node) { | ||
return node.contains(document.activeElement); | ||
}); | ||
export var focusIsHidden = function () { | ||
return document && | ||
toArray(document.querySelectorAll("[" + FOCUS_ALLOW + "]")).some(function (node) { return node.contains(document.activeElement); }); | ||
}; | ||
export default focusIsHidden; |
@@ -1,196 +0,55 @@ | ||
import { getCommonParent, getTabbableNodes, getAllTabbableNodes, parentAutofocusables } from './utils/DOMutils'; | ||
import pickFirstFocus, { pickFocusable } from './utils/firstFocus'; | ||
import getAllAffectedNodes from './utils/all-affected'; | ||
import { asArray } from './utils/array'; | ||
import { correctNodes } from './utils/correctFocus'; | ||
var findAutoFocused = function findAutoFocused(autoFocusables) { | ||
return function (node) { | ||
return !!node.autofocus || node.dataset && !!node.dataset.autofocus || autoFocusables.indexOf(node) >= 0; | ||
}; | ||
import { NEW_FOCUS, newFocus } from './solver'; | ||
import { getAllAffectedNodes } from './utils/all-affected'; | ||
import { getAllTabbableNodes, getTabbableNodes } from './utils/DOMutils'; | ||
import { pickFirstFocus } from './utils/firstFocus'; | ||
import { isDefined, isNotAGuard } from './utils/is'; | ||
import { allParentAutofocusables, getTopCommonParent } from './utils/parenting'; | ||
var findAutoFocused = function (autoFocusables) { return function (node) { | ||
return node.autofocus || (node.dataset && !!node.dataset.autofocus) || autoFocusables.indexOf(node) >= 0; | ||
}; }; | ||
var reorderNodes = function (srcNodes, dstNodes) { | ||
var remap = new Map(); | ||
dstNodes.forEach(function (entity) { return remap.set(entity.node, entity); }); | ||
return srcNodes.map(function (node) { return remap.get(node); }).filter(isDefined); | ||
}; | ||
var isGuard = function isGuard(node) { | ||
return node && node.dataset && node.dataset.focusGuard; | ||
}; | ||
var notAGuard = function notAGuard(node) { | ||
return !isGuard(node); | ||
}; | ||
export var NEW_FOCUS = 'NEW_FOCUS'; | ||
export var newFocus = function newFocus(innerNodes, outerNodes, activeElement, lastNode) { | ||
var cnt = innerNodes.length; | ||
var firstFocus = innerNodes[0]; | ||
var lastFocus = innerNodes[cnt - 1]; | ||
var isOnGuard = isGuard(activeElement); | ||
// 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); | ||
var correctedNodes = correctNodes(outerNodes); | ||
var correctedIndexDiff = correctedNodes.indexOf(activeElement) - correctedNodes.indexOf(lastNode || activeIndex); | ||
var returnFirstNode = pickFocusable(innerNodes, 0); | ||
var returnLastNode = pickFocusable(innerNodes, cnt - 1); | ||
// new focus | ||
if (activeIndex === -1 || lastNodeInside === -1) { | ||
return NEW_FOCUS; | ||
} | ||
// old focus | ||
if (!indexDiff && lastNodeInside >= 0) { | ||
return lastNodeInside; | ||
} | ||
// first element | ||
if (activeIndex <= firstNodeIndex && isOnGuard && Math.abs(indexDiff) > 1) { | ||
return returnLastNode; | ||
} | ||
// last element | ||
if (activeIndex >= lastNodeIndex && isOnGuard && Math.abs(indexDiff) > 1) { | ||
return returnFirstNode; | ||
} | ||
// jump out, but not on the guard | ||
if (indexDiff && Math.abs(correctedIndexDiff) > 1) { | ||
return lastNodeInside; | ||
} | ||
// focus above lock | ||
if (activeIndex <= firstNodeIndex) { | ||
return returnLastNode; | ||
} | ||
// focus below lock | ||
if (activeIndex > lastNodeIndex) { | ||
return returnFirstNode; | ||
} | ||
// 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); | ||
} | ||
} | ||
export var getFocusMerge = function (topNode, lastNode) { | ||
var activeElement = (document && document.activeElement); | ||
var entries = getAllAffectedNodes(topNode).filter(isNotAGuard); | ||
var commonParent = getTopCommonParent(activeElement || topNode, topNode, entries); | ||
var anyFocusable = getAllTabbableNodes(entries); | ||
var innerElements = getTabbableNodes(entries).filter(function (_a) { | ||
var node = _a.node; | ||
return isNotAGuard(node); | ||
}); | ||
}); | ||
return topCommon; | ||
}; | ||
var allParentAutofocusables = function allParentAutofocusables(entries) { | ||
return entries.reduce(function (acc, node) { | ||
return acc.concat(parentAutofocusables(node)); | ||
}, []); | ||
}; | ||
var reorderNodes = function reorderNodes(srcNodes, dstNodes) { | ||
var remap = new Map(); | ||
// no Set(dstNodes) for IE11 :( | ||
dstNodes.forEach(function (entity) { | ||
return remap.set(entity.node, entity); | ||
}); | ||
// remap to dstNodes | ||
return srcNodes.map(function (node) { | ||
return remap.get(node); | ||
}).filter(Boolean); | ||
}; | ||
export var getFocusabledIn = function getFocusabledIn(topNode) { | ||
var entries = getAllAffectedNodes(topNode).filter(notAGuard); | ||
var commonParent = getTopCommonParent(topNode, topNode, entries); | ||
var outerNodes = getTabbableNodes([commonParent], true); | ||
var innerElements = getTabbableNodes(entries).filter(function (_ref) { | ||
var node = _ref.node; | ||
return notAGuard(node); | ||
}).map(function (_ref2) { | ||
var node = _ref2.node; | ||
return node; | ||
}); | ||
return outerNodes.map(function (_ref3) { | ||
var node = _ref3.node, | ||
index = _ref3.index; | ||
return { | ||
node: node, | ||
index: index, | ||
lockItem: innerElements.indexOf(node) >= 0, | ||
guard: isGuard(node) | ||
}; | ||
}); | ||
}; | ||
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 anyFocusable = getAllTabbableNodes(entries); | ||
var innerElements = getTabbableNodes(entries).filter(function (_ref4) { | ||
var node = _ref4.node; | ||
return notAGuard(node); | ||
}); | ||
if (!innerElements[0]) { | ||
innerElements = anyFocusable; | ||
if (!innerElements[0]) { | ||
return undefined; | ||
innerElements = anyFocusable; | ||
if (!innerElements[0]) { | ||
return undefined; | ||
} | ||
} | ||
} | ||
var outerNodes = getAllTabbableNodes([commonParent]).map(function (_ref5) { | ||
var node = _ref5.node; | ||
return node; | ||
}); | ||
var orderedInnerElements = reorderNodes(outerNodes, innerElements); | ||
var innerNodes = orderedInnerElements.map(function (_ref6) { | ||
var node = _ref6.node; | ||
return node; | ||
}); | ||
var newId = newFocus(innerNodes, outerNodes, activeElement, lastNode); | ||
if (newId === "NEW_FOCUS") { | ||
var autoFocusable = anyFocusable.map(function (_ref7) { | ||
var node = _ref7.node; | ||
return node; | ||
}).filter(findAutoFocused(allParentAutofocusables(entries))); | ||
return { | ||
node: autoFocusable && autoFocusable.length ? pickFirstFocus(autoFocusable) : pickFirstFocus(innerNodes) | ||
}; | ||
} | ||
if (newId === undefined) { | ||
return newId; | ||
} | ||
return orderedInnerElements[newId]; | ||
var outerNodes = getAllTabbableNodes([commonParent]).map(function (_a) { | ||
var node = _a.node; | ||
return node; | ||
}); | ||
var orderedInnerElements = reorderNodes(outerNodes, innerElements); | ||
var innerNodes = orderedInnerElements.map(function (_a) { | ||
var node = _a.node; | ||
return node; | ||
}); | ||
var newId = newFocus(innerNodes, outerNodes, activeElement, lastNode); | ||
if (newId === NEW_FOCUS) { | ||
var autoFocusable = anyFocusable | ||
.map(function (_a) { | ||
var node = _a.node; | ||
return node; | ||
}) | ||
.filter(findAutoFocused(allParentAutofocusables(entries))); | ||
return { | ||
node: autoFocusable && autoFocusable.length ? pickFirstFocus(autoFocusable) : pickFirstFocus(innerNodes), | ||
}; | ||
} | ||
if (newId === undefined) { | ||
return newId; | ||
} | ||
return orderedInnerElements[newId]; | ||
}; | ||
export default getFocusMerge; |
@@ -0,11 +1,11 @@ | ||
import * as constants from './constants'; | ||
import { getFocusabledIn } from './focusables'; | ||
import { focusInside } from './focusInside'; | ||
import { focusIsHidden } from './focusIsHidden'; | ||
import { getFocusMerge as focusMerge } from './focusMerge'; | ||
import { setFocus } from './setFocus'; | ||
import { focusNextElement, focusPrevElement } from './sibling'; | ||
import tabHook from './tabHook'; | ||
import focusMerge, { getFocusabledIn } from './focusMerge'; | ||
import focusInside from './focusInside'; | ||
import focusIsHidden from './focusIsHidden'; | ||
import setFocus from './setFocus'; | ||
import * as constants from './constants'; | ||
import getAllAffectedNodes from './utils/all-affected'; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusabledIn, constants, getAllAffectedNodes }; | ||
export default setFocus; | ||
import { getAllAffectedNodes } from './utils/all-affected'; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, }; | ||
export default setFocus; |
@@ -1,34 +0,29 @@ | ||
import getFocusMerge from './focusMerge'; | ||
export var focusOn = function focusOn(target) { | ||
target.focus(); | ||
if (target.contentWindow) { | ||
target.contentWindow.focus(); | ||
} | ||
import { getFocusMerge } from './focusMerge'; | ||
export var focusOn = function (target) { | ||
target.focus(); | ||
if ('contentWindow' in target && target.contentWindow) { | ||
target.contentWindow.focus(); | ||
} | ||
}; | ||
var guardCount = 0; | ||
var lockDisabled = false; | ||
export default (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; | ||
export var setFocus = function (topNode, lastNode) { | ||
var focusable = getFocusMerge(topNode, lastNode); | ||
if (lockDisabled) { | ||
return; | ||
} | ||
guardCount++; | ||
focusOn(focusable.node); | ||
guardCount--; | ||
} | ||
}); | ||
if (focusable) { | ||
if (guardCount > 2) { | ||
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--; | ||
} | ||
}; |
export default { | ||
attach: function attach() {}, | ||
detach: function detach() {} | ||
}; | ||
attach: function () { }, | ||
detach: function () { }, | ||
}; |
@@ -1,43 +0,31 @@ | ||
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; }; | ||
import { FOCUS_DISABLED, FOCUS_GROUP } from '../constants'; | ||
import { asArray, toArray } from './array'; | ||
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]; | ||
})) | ||
}; | ||
var filterNested = function (nodes) { | ||
var contained = new Set(); | ||
var l = nodes.length; | ||
for (var i = 0; i < l; i += 1) { | ||
for (var j = i + 1; j < l; j += 1) { | ||
var position = nodes[i].compareDocumentPosition(nodes[j]); | ||
if ((position & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0) { | ||
contained.add(j); | ||
} | ||
if ((position & Node.DOCUMENT_POSITION_CONTAINS) > 0) { | ||
contained.add(i); | ||
} | ||
} | ||
} | ||
}; | ||
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; | ||
return nodes.filter(function (_, index) { return !contained.has(index); }); | ||
}; | ||
var getTopParent = function getTopParent(node) { | ||
return node.parentNode ? getTopParent(node.parentNode) : node; | ||
var getTopParent = function (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; | ||
}, []); | ||
export var getAllAffectedNodes = function (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; | ||
}, []); | ||
}; | ||
export default getAllAffectedNodes; |
@@ -1,17 +0,8 @@ | ||
export var toArray = function toArray(a) { | ||
var ret = Array(a.length); | ||
for (var i = 0; i < a.length; ++i) { | ||
ret[i] = a[i]; | ||
} | ||
return ret; | ||
export var toArray = function (a) { | ||
var ret = Array(a.length); | ||
for (var i = 0; i < a.length; ++i) { | ||
ret[i] = a[i]; | ||
} | ||
return ret; | ||
}; | ||
export var arrayFind = function arrayFind(array, search) { | ||
return array.filter(function (a) { | ||
return a === search; | ||
})[0]; | ||
}; | ||
export var asArray = function asArray(a) { | ||
return Array.isArray(a) ? a : [a]; | ||
}; | ||
export var asArray = function (a) { return (Array.isArray(a) ? a : [a]); }; |
@@ -1,30 +0,18 @@ | ||
var isRadio = function isRadio(node) { | ||
return node.tagName === 'INPUT' && node.type === 'radio'; | ||
var isRadio = function (node) { return node.tagName === 'INPUT' && node.type === 'radio'; }; | ||
var findSelectedRadio = function (node, nodes) { | ||
return nodes | ||
.filter(isRadio) | ||
.filter(function (el) { return el.name === node.name; }) | ||
.filter(function (el) { return el.checked; })[0] || node; | ||
}; | ||
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; | ||
export var correctNode = function (node, nodes) { | ||
if (isRadio(node) && node.name) { | ||
return findSelectedRadio(node, nodes); | ||
} | ||
return node; | ||
}; | ||
export var correctNode = function correctNode(node, nodes) { | ||
if (isRadio(node) && node.name) { | ||
return findSelectedRadio(node, nodes); | ||
} | ||
return node; | ||
export var correctNodes = function (nodes) { | ||
var resultSet = new Set(); | ||
nodes.forEach(function (node) { return resultSet.add(correctNode(node, nodes)); }); | ||
return nodes.filter(function (node) { return resultSet.has(node); }); | ||
}; | ||
export var correctNodes = function correctNodes(nodes) { | ||
// IE11 has no Set constructor | ||
var resultSet = new Set(); | ||
nodes.forEach(function (node) { | ||
return resultSet.add(correctNode(node, nodes)); | ||
}); | ||
// using filter to support IE11 | ||
return nodes.filter(function (node) { | ||
return resultSet.has(node); | ||
}); | ||
}; |
@@ -0,64 +1,18 @@ | ||
import { toArray } from './array'; | ||
import { isVisible, notHiddenInput } from './is'; | ||
import { orderByTabIndex } from './tabOrder'; | ||
import { getFocusables, getParentAutofocusables } from './tabUtils'; | ||
import { toArray } from './array'; | ||
var isElementHidden = function isElementHidden(computedStyle) { | ||
if (!computedStyle || !computedStyle.getPropertyValue) { | ||
return false; | ||
} | ||
return computedStyle.getPropertyValue('display') === 'none' || computedStyle.getPropertyValue('visibility') === 'hidden'; | ||
export var filterFocusable = function (nodes) { | ||
return toArray(nodes) | ||
.filter(function (node) { return isVisible(node); }) | ||
.filter(function (node) { return notHiddenInput(node); }); | ||
}; | ||
export var isVisible = function isVisible(node) { | ||
return !node || node === document || node.nodeType === Node.DOCUMENT_NODE || !isElementHidden(window.getComputedStyle(node, null)) && isVisible(node.parentNode && node.parentNode.nodeType === node.DOCUMENT_FRAGMENT_NODE ? node.parentNode.host : node.parentNode); | ||
export var getTabbableNodes = function (topNodes, withGuards) { | ||
return orderByTabIndex(filterFocusable(getFocusables(topNodes, withGuards)), true, withGuards); | ||
}; | ||
export var notHiddenInput = function notHiddenInput(node) { | ||
return !((node.tagName === 'INPUT' || node.tagName === 'BUTTON') && (node.type === 'hidden' || node.disabled)); | ||
export var getAllTabbableNodes = function (topNodes) { | ||
return orderByTabIndex(filterFocusable(getFocusables(topNodes)), false); | ||
}; | ||
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; | ||
export var parentAutofocusables = function (topNode) { | ||
return filterFocusable(getParentAutofocusables(topNode)); | ||
}; | ||
export 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; | ||
}; | ||
export var filterFocusable = function filterFocusable(nodes) { | ||
return toArray(nodes).filter(function (node) { | ||
return isVisible(node); | ||
}).filter(function (node) { | ||
return notHiddenInput(node); | ||
}); | ||
}; | ||
export var getTabbableNodes = function getTabbableNodes(topNodes, withGuards) { | ||
return orderByTabIndex(filterFocusable(getFocusables(topNodes, withGuards)), true, withGuards); | ||
}; | ||
/** | ||
* actually anything focusable | ||
*/ | ||
export var getAllTabbableNodes = function getAllTabbableNodes(topNodes) { | ||
return orderByTabIndex(filterFocusable(getFocusables(topNodes)), false); | ||
}; | ||
export var parentAutofocusables = function parentAutofocusables(topNode) { | ||
return filterFocusable(getParentAutofocusables(topNode)); | ||
}; |
import { correctNode } from './correctFocus'; | ||
var pickFirstFocus = function pickFirstFocus(nodes) { | ||
if (nodes[0] && nodes.length > 1) { | ||
return correctNode(nodes[0], nodes); | ||
} | ||
return nodes[0]; | ||
export var pickFirstFocus = function (nodes) { | ||
if (nodes[0] && nodes.length > 1) { | ||
return correctNode(nodes[0], nodes); | ||
} | ||
return nodes[0]; | ||
}; | ||
export var pickFocusable = function pickFocusable(nodes, index) { | ||
if (nodes.length > 1) { | ||
return nodes.indexOf(correctNode(nodes[index], nodes)); | ||
} | ||
return index; | ||
export var pickFocusable = function (nodes, index) { | ||
if (nodes.length > 1) { | ||
return nodes.indexOf(correctNode(nodes[index], nodes)); | ||
} | ||
return index; | ||
}; | ||
export default pickFirstFocus; |
@@ -1,1 +0,17 @@ | ||
export default ['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]']; | ||
export var tabbables = [ | ||
'button:enabled', | ||
'select:enabled', | ||
'textarea:enabled', | ||
'input:enabled', | ||
'a[href]', | ||
'area[href]', | ||
'summary', | ||
'iframe', | ||
'object', | ||
'embed', | ||
'audio[controls]', | ||
'video[controls]', | ||
'[tabindex]', | ||
'[contenteditable]', | ||
'[autofocus]', | ||
]; |
import { toArray } from './array'; | ||
export 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; | ||
export var tabSort = function (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; | ||
}; | ||
export 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); | ||
}; | ||
export var orderByTabIndex = function (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); | ||
}; |
@@ -1,27 +0,18 @@ | ||
import tabbables from './tabbables'; | ||
import { FOCUS_AUTO } from '../constants'; | ||
import { toArray } from './array'; | ||
import { FOCUS_AUTO } from '../constants'; | ||
import { tabbables } from './tabbables'; | ||
var queryTabbables = tabbables.join(','); | ||
var queryGuardTabbables = queryTabbables + ', [data-focus-guard]'; | ||
export 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 queryGuardTabbables = queryTabbables + ", [data-focus-guard]"; | ||
export var getFocusables = function (parents, withGuards) { | ||
return parents.reduce(function (acc, parent) { | ||
return acc.concat(toArray(parent.querySelectorAll(withGuards ? queryGuardTabbables : queryTabbables)), parent.parentNode | ||
? toArray(parent.parentNode.querySelectorAll(queryTabbables)).filter(function (node) { return node === parent; }) | ||
: []); | ||
}, []); | ||
}; | ||
export 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); | ||
}, []); | ||
}; | ||
export var getParentAutofocusables = function (parent) { | ||
var parentFocus = parent.querySelectorAll("[" + FOCUS_AUTO + "]"); | ||
return toArray(parentFocus) | ||
.map(function (node) { return getFocusables([node]); }) | ||
.reduce(function (acc, nodes) { return acc.concat(nodes); }, []); | ||
}; |
{ | ||
"name": "focus-lock", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "DOM trap for a focus", | ||
"main": "dist/cjs/index.js", | ||
"main": "dist/es5/index.js", | ||
"jsnext:main": "dist/es2015/index.js", | ||
@@ -10,12 +10,17 @@ "module": "dist/es2015/index.js", | ||
"scripts": { | ||
"build:cjs": "NODE_ENV=cjs babel src -d dist/cjs", | ||
"build:es5": "NODE_ENV=es2015 babel src -d dist/es2015", | ||
"build": "rm -Rf ./dist && yarn build:es5 && yarn build:cjs", | ||
"test": "NODE_ENV=cjs npm run test:pick -- '_tests/**/*spec.js'", | ||
"test:pick": "NODE_ENV=cjs mocha --compilers js:babel-core/register -r jsdom-global/register", | ||
"prepublish": "npm run build && npm run changelog", | ||
"lint": "eslint src tests", | ||
"lint:fix": "eslint src tests --fix", | ||
"size": "yarn size-limit", | ||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" | ||
"dev": "lib-builder dev", | ||
"test": "jest", | ||
"test:ci": "jest --runInBand --coverage", | ||
"build": "lib-builder build && yarn size:report", | ||
"release": "yarn build && yarn test", | ||
"size": "npx size-limit", | ||
"size:report": "npx size-limit --json > .size.json", | ||
"lint": "lib-builder lint", | ||
"format": "lib-builder format", | ||
"update": "lib-builder update", | ||
"docz:dev": "docz dev", | ||
"docz:build": "docz build", | ||
"prepublish": "yarn build && yarn changelog", | ||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", | ||
"changelog:rewrite": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" | ||
}, | ||
@@ -32,4 +37,3 @@ "repository": { | ||
"files": [ | ||
"dist", | ||
"constants" | ||
"dist" | ||
], | ||
@@ -43,22 +47,35 @@ "author": "theKashey <thekashey@gmail.com>", | ||
"devDependencies": { | ||
"babel-cli": "^6.24.1", | ||
"babel-core": "^6.25.0", | ||
"babel-eslint": "^7.2.3", | ||
"babel-preset-env": "1.6.0", | ||
"chai": "^4.1.0", | ||
"chai-enzyme": "^0.8.0", | ||
"conventional-changelog-cli": "^2.0.12", | ||
"enzyme": "^2.9.1", | ||
"eslint": "^4.2.0", | ||
"eslint-config-airbnb": "15.1.0", | ||
"eslint-plugin-import": "^2.7.0", | ||
"eslint-plugin-jsx-a11y": "6.0.2", | ||
"eslint-plugin-mocha": "^4.11.0", | ||
"eslint-plugin-react": "^7.3.0", | ||
"jsdom": "11.1.0", | ||
"jsdom-global": "3.0.2", | ||
"mocha": "^3.4.2", | ||
"sinon": "3.2.1", | ||
"size-limit": "^0.21.1" | ||
"@theuiteam/lib-builder": "^0.0.11", | ||
"@size-limit/preset-small-lib": "^2.1.6" | ||
}, | ||
"types": "dist/es5/index.d.ts", | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"dependencies": { | ||
"tslib": "^1.9.3" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
"*.{ts,tsx}": [ | ||
"prettier --write", | ||
"tslint --fix", | ||
"git add" | ||
], | ||
"*.{js,css,json,md}": [ | ||
"prettier --write", | ||
"git add" | ||
] | ||
}, | ||
"prettier": { | ||
"printWidth": 120, | ||
"trailingComma": "es5", | ||
"tabWidth": 2, | ||
"semi": true, | ||
"singleQuote": true | ||
} | ||
} |
# focus-lock | ||
It is a trap! We got your focus and will not let him out! | ||
@@ -9,11 +10,13 @@ | ||
This is a base package for: | ||
- [react-focus-lock](https://github.com/theKashey/react-focus-lock) | ||
- [react-focus-lock](https://github.com/theKashey/react-focus-lock) | ||
[![downloads](https://badgen.net/npm/dm/react-focus-lock)](https://www.npmtrends.com/react-focus-lock) | ||
- [vue-focus-lock](https://github.com/theKashey/vue-focus-lock) | ||
- [vue-focus-lock](https://github.com/theKashey/vue-focus-lock) | ||
[![downloads](https://badgen.net/npm/dm/vue-focus-lock)](https://www.npmtrends.com/vue-focus-lock) | ||
- [dom-focus-lock](https://github.com/theKashey/dom-focus-lock) | ||
- [dom-focus-lock](https://github.com/theKashey/dom-focus-lock) | ||
[![downloads](https://badgen.net/npm/dm/dom-focus-lock)](https://www.npmtrends.com/dom-focus-lock) | ||
Provides a low level API, to be used by final realization. Usually everything | ||
can be solved in 3 lines | ||
Provides a low level API, to be used by final realization. | ||
Usually everything can be solved in 3 lines | ||
```js | ||
@@ -27,14 +30,28 @@ import moveFocusInside, { focusInside } from 'focus-lock'; | ||
## Additional API | ||
### Programmatic focus management | ||
```ts | ||
import { focusNextElement, focusPrevElement } from 'focus-lock'; | ||
focusNextElement(sourceElement, { | ||
scope: theBoundingDOMNode, | ||
}); // -> next tabbable element | ||
``` | ||
# WHY? | ||
From [MDN Article about accessible dialogs](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_dialog_role): | ||
- The dialog must be properly labeled | ||
- Keyboard __focus must be managed__ correctly | ||
- The dialog must be properly labeled | ||
- Keyboard **focus must be managed** correctly | ||
This one is about managing the focus. | ||
I'v got a good [article about focus management, dialogs and WAI-ARIA](https://medium.com/@antonkorzunov/its-a-focus-trap-699a04d66fb5). | ||
I'v got a good [article about focus management, dialogs and WAI-ARIA](https://medium.com/@antonkorzunov/its-a-focus-trap-699a04d66fb5). | ||
# Focus fighting | ||
It is possible, that more that one "focus management system" is present on the site. | ||
For example you are using FocusLock for your content, and also using some | ||
For example, you are using FocusLock for your content, and also using some | ||
Modal dialog, with FocusTrap inside. | ||
@@ -49,4 +66,4 @@ | ||
You may also land a peace by special data attribute - `data-no-focus-lock`(constants.FOCUS_ALLOW). It will | ||
remove focus management from all nested elements, letting you open modals, forms, or | ||
use any third party component safely. Focus lock will just do nothing, while focus is on the marked elements. | ||
remove focus management from all nested elements, letting you open modals, forms, or | ||
use any third party component safely. Focus lock will just do nothing, while focus is on the marked elements. | ||
@@ -56,6 +73,5 @@ # API | ||
`default(topNode, lastNode)` (aka setFocus), moves focus inside topNode, keeping in mind that last focus inside was - lastNode | ||
# Licence | ||
MIT | ||
MIT |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
56175
2
84
1123
74
1
1
+ Addedtslib@^1.9.3
+ Addedtslib@1.14.1(transitive)