focus-lock
Advanced tools
Comparing version 0.10.2 to 0.11.0
@@ -0,4 +1,22 @@ | ||
/** | ||
* defines a focus group | ||
*/ | ||
export declare const FOCUS_GROUP = "data-focus-lock"; | ||
/** | ||
* disables element discovery inside a group marked by key | ||
*/ | ||
export declare const FOCUS_DISABLED = "data-focus-lock-disabled"; | ||
/** | ||
* allows uncontrolled focus within the marked area, effectively disabling focus lock for it's content | ||
*/ | ||
export declare const FOCUS_ALLOW = "data-no-focus-lock"; | ||
/** | ||
* instructs autofocus engine to pick default autofocus inside a given node | ||
* can be set on the element or container | ||
*/ | ||
export declare const FOCUS_AUTO = "data-autofocus-inside"; | ||
/** | ||
* instructs autofocus to ignore elements within a given node | ||
* can be set on the element or container | ||
*/ | ||
export declare const FOCUS_NO_AUTOFOCUS = "data-no-autofocus"; |
@@ -0,4 +1,22 @@ | ||
/** | ||
* defines a focus group | ||
*/ | ||
export var FOCUS_GROUP = 'data-focus-lock'; | ||
/** | ||
* disables element discovery inside a group marked by key | ||
*/ | ||
export var FOCUS_DISABLED = 'data-focus-lock-disabled'; | ||
/** | ||
* allows uncontrolled focus within the marked area, effectively disabling focus lock for it's content | ||
*/ | ||
export var FOCUS_ALLOW = 'data-no-focus-lock'; | ||
/** | ||
* instructs autofocus engine to pick default autofocus inside a given node | ||
* can be set on the element or container | ||
*/ | ||
export var FOCUS_AUTO = 'data-autofocus-inside'; | ||
/** | ||
* instructs autofocus to ignore elements within a given node | ||
* can be set on the element or container | ||
*/ | ||
export var FOCUS_NO_AUTOFOCUS = 'data-no-autofocus'; |
interface FocusableIn { | ||
node: HTMLElement; | ||
/** | ||
* tab index | ||
*/ | ||
index: number; | ||
/** | ||
* true, if this node belongs to a Lock | ||
*/ | ||
lockItem: boolean; | ||
/** | ||
* true, if this node is a focus-guard (system node) | ||
*/ | ||
guard: boolean; | ||
} | ||
/** | ||
* return list of focusable elements inside a given top node | ||
* @deprecated use {@link getFocusableIn}. Yep, there is typo in the function name | ||
*/ | ||
export declare const getFocusabledIn: (topNode: HTMLElement) => FocusableIn[]; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
*/ | ||
export declare const getFocusableIn: (topNode: HTMLElement) => FocusableIn[]; | ||
export {}; |
@@ -5,2 +5,6 @@ import { getTabbableNodes } from './utils/DOMutils'; | ||
import { getTopCommonParent } from './utils/parenting'; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
* @deprecated use {@link getFocusableIn}. Yep, there is typo in the function name | ||
*/ | ||
export var getFocusabledIn = function (topNode) { | ||
@@ -30,1 +34,5 @@ var entries = getAllAffectedNodes(topNode).filter(isNotAGuard); | ||
}; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
*/ | ||
export var getFocusableIn = getFocusabledIn; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* @returns {Boolean} true, if the current focus is inside given node or nodes | ||
*/ | ||
export declare const focusInside: (topNode: HTMLElement | HTMLElement[]) => boolean; |
@@ -0,1 +1,2 @@ | ||
import { contains } from './utils/DOMutils'; | ||
import { getAllAffectedNodes } from './utils/all-affected'; | ||
@@ -8,2 +9,5 @@ import { toArray } from './utils/array'; | ||
}; | ||
/** | ||
* @returns {Boolean} true, if the current focus is inside given node or nodes | ||
*/ | ||
export var focusInside = function (topNode) { | ||
@@ -14,3 +18,3 @@ var activeElement = document && getActiveElement(); | ||
} | ||
return getAllAffectedNodes(topNode).reduce(function (result, node) { return result || node.contains(activeElement) || focusInsideIframe(node); }, false); | ||
return getAllAffectedNodes(topNode).some(function (node) { return contains(node, activeElement) || focusInsideIframe(node); }); | ||
}; |
@@ -0,1 +1,6 @@ | ||
/** | ||
* focus is hidden FROM the focus-lock | ||
* ie contained inside a node focus-lock shall ignore | ||
* @returns {boolean} focus is currently is in "allow" area | ||
*/ | ||
export declare const focusIsHidden: () => boolean; |
import { FOCUS_ALLOW } from './constants'; | ||
import { contains } from './utils/DOMutils'; | ||
import { toArray } from './utils/array'; | ||
import { getActiveElement } from './utils/getActiveElement'; | ||
/** | ||
* focus is hidden FROM the focus-lock | ||
* ie contained inside a node focus-lock shall ignore | ||
* @returns {boolean} focus is currently is in "allow" area | ||
*/ | ||
export var focusIsHidden = function () { | ||
@@ -9,3 +15,4 @@ var activeElement = document && getActiveElement(); | ||
} | ||
return toArray(document.querySelectorAll("[".concat(FOCUS_ALLOW, "]"))).some(function (node) { return node.contains(activeElement); }); | ||
// this does not support setting FOCUS_ALLOW within shadow dom | ||
return toArray(document.querySelectorAll("[".concat(FOCUS_ALLOW, "]"))).some(function (node) { return contains(node, activeElement); }); | ||
}; |
@@ -0,3 +1,8 @@ | ||
/** | ||
* given top node(s) and the last active element return the element to be focused next | ||
* @param topNode | ||
* @param lastNode | ||
*/ | ||
export declare const getFocusMerge: (topNode: Element | Element[], lastNode: Element | null) => undefined | { | ||
node: HTMLElement; | ||
}; |
import { NEW_FOCUS, newFocus } from './solver'; | ||
import { getAllTabbableNodes, getTabbableNodes } from './utils/DOMutils'; | ||
import { filterAutoFocusable, getAllTabbableNodes, getTabbableNodes } from './utils/DOMutils'; | ||
import { getAllAffectedNodes } from './utils/all-affected'; | ||
@@ -9,9 +9,18 @@ import { pickFirstFocus } from './utils/firstFocus'; | ||
var findAutoFocused = function (autoFocusables) { | ||
return function (node) { var _a; return node.autofocus || !!((_a = getDataset(node)) === null || _a === void 0 ? void 0 : _a.autofocus) || autoFocusables.indexOf(node) >= 0; }; | ||
return function (node) { var _a; | ||
// @ts-expect-error | ||
return node.autofocus || !!((_a = getDataset(node)) === null || _a === void 0 ? void 0 : _a.autofocus) || autoFocusables.indexOf(node) >= 0; }; | ||
}; | ||
var reorderNodes = function (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(isDefined); | ||
}; | ||
/** | ||
* given top node(s) and the last active element return the element to be focused next | ||
* @param topNode | ||
* @param lastNode | ||
*/ | ||
export var getFocusMerge = function (topNode, lastNode) { | ||
@@ -44,10 +53,10 @@ var activeElement = document && getActiveElement(); | ||
if (newId === NEW_FOCUS) { | ||
var autoFocusable = anyFocusable | ||
.map(function (_a) { | ||
var autoFocusable = filterAutoFocusable(anyFocusable.map(function (_a) { | ||
var node = _a.node; | ||
return node; | ||
}) | ||
.filter(findAutoFocused(allParentAutofocusables(entries, visibilityCache))); | ||
})).filter(findAutoFocused(allParentAutofocusables(entries, visibilityCache))); | ||
return { | ||
node: autoFocusable && autoFocusable.length ? pickFirstFocus(autoFocusable) : pickFirstFocus(innerNodes), | ||
node: autoFocusable && autoFocusable.length | ||
? pickFirstFocus(autoFocusable) | ||
: pickFirstFocus(filterAutoFocusable(innerNodes)), | ||
}; | ||
@@ -54,0 +63,0 @@ } |
@@ -5,3 +5,3 @@ import * as constants from './constants'; | ||
import { getFocusMerge as focusMerge } from './focusMerge'; | ||
import { getFocusabledIn } from './focusables'; | ||
import { getFocusabledIn, getFocusableIn } from './focusables'; | ||
import { setFocus } from './setFocus'; | ||
@@ -12,3 +12,3 @@ import { focusNextElement, focusPrevElement } from './sibling'; | ||
import { getActiveElement } from './utils/getActiveElement'; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusableIn, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export default setFocus; |
@@ -5,3 +5,3 @@ import * as constants from './constants'; | ||
import { getFocusMerge as focusMerge } from './focusMerge'; | ||
import { getFocusabledIn } from './focusables'; | ||
import { getFocusabledIn, getFocusableIn } from './focusables'; | ||
import { setFocus } from './setFocus'; | ||
@@ -12,3 +12,4 @@ import { focusNextElement, focusPrevElement } from './sibling'; | ||
import { getActiveElement } from './utils/getActiveElement'; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusableIn, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export default setFocus; | ||
// |
@@ -5,3 +5,10 @@ export declare const focusOn: (target: Element | HTMLFrameElement | HTMLElement, focusOptions?: FocusOptions | undefined) => void; | ||
} | ||
/** | ||
* Sets focus at a given node. The last focused element will help to determine which element(first or last) should be focused. | ||
* HTML markers (see {@link import('./constants').FOCUS_AUTO} constants) can control autofocus | ||
* @param topNode | ||
* @param lastNode | ||
* @param options | ||
*/ | ||
export declare const setFocus: (topNode: HTMLElement, lastNode: Element, options?: FocusLockFocusOptions) => void; | ||
export {}; |
@@ -12,2 +12,9 @@ import { getFocusMerge } from './focusMerge'; | ||
var lockDisabled = false; | ||
/** | ||
* Sets focus at a given node. The last focused element will help to determine which element(first or last) should be focused. | ||
* HTML markers (see {@link import('./constants').FOCUS_AUTO} constants) can control autofocus | ||
* @param topNode | ||
* @param lastNode | ||
* @param options | ||
*/ | ||
export var setFocus = function (topNode, lastNode, options) { | ||
@@ -21,2 +28,3 @@ if (options === void 0) { options = {}; } | ||
if (guardCount > 2) { | ||
// tslint:disable-next-line:no-console | ||
console.error('FocusLock: focus-fighting detected. Only one focus management system could be active. ' + | ||
@@ -23,0 +31,0 @@ 'See https://github.com/theKashey/focus-lock/#focus-fighting'); |
interface FocusNextOptions { | ||
/** | ||
* the component to "scope" focus in | ||
* @default document.body | ||
*/ | ||
scope?: HTMLElement | HTMLDocument; | ||
/** | ||
* enables cycling inside the scope | ||
* @default true | ||
*/ | ||
cycle?: boolean; | ||
/** | ||
* options for focus action to control it more precisely (ie. `{ preventScroll: true }`) | ||
*/ | ||
focusOptions?: FocusOptions; | ||
} | ||
/** | ||
* focuses next element in the tab-order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export declare const focusNextElement: (baseElement: Element, options?: FocusNextOptions) => void; | ||
/** | ||
* focuses prev element in the tab order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export declare const focusPrevElement: (baseElement: Element, options?: FocusNextOptions) => void; | ||
export {}; |
import { focusOn } from './setFocus'; | ||
import { getTabbableNodes } from './utils/DOMutils'; | ||
import { getTabbableNodes, contains } from './utils/DOMutils'; | ||
var getRelativeFocusable = function (element, scope) { | ||
if (!element || !scope || !scope.contains(element)) { | ||
if (!element || !scope || !contains(scope, element)) { | ||
return {}; | ||
@@ -28,2 +28,7 @@ } | ||
}; | ||
/** | ||
* focuses next element in the tab-order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export var focusNextElement = function (baseElement, options) { | ||
@@ -38,2 +43,7 @@ if (options === void 0) { options = {}; } | ||
}; | ||
/** | ||
* focuses prev element in the tab order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export var focusPrevElement = function (baseElement, options) { | ||
@@ -40,0 +50,0 @@ if (options === void 0) { options = {}; } |
export declare const NEW_FOCUS = "NEW_FOCUS"; | ||
/** | ||
* Main solver for the "find next focus" question | ||
* @param innerNodes | ||
* @param outerNodes | ||
* @param activeElement | ||
* @param lastNode | ||
* @returns {number|string|undefined|*} | ||
*/ | ||
export declare const newFocus: (innerNodes: HTMLElement[], outerNodes: HTMLElement[], activeElement: HTMLElement | undefined, lastNode: HTMLElement | null) => number | undefined | typeof NEW_FOCUS; |
@@ -5,2 +5,10 @@ import { correctNodes } from './utils/correctFocus'; | ||
export var NEW_FOCUS = 'NEW_FOCUS'; | ||
/** | ||
* Main solver for the "find next focus" question | ||
* @param innerNodes | ||
* @param outerNodes | ||
* @param activeElement | ||
* @param lastNode | ||
* @returns {number|string|undefined|*} | ||
*/ | ||
export var newFocus = function (innerNodes, outerNodes, activeElement, lastNode) { | ||
@@ -11,2 +19,3 @@ var cnt = innerNodes.length; | ||
var isOnGuard = isGuard(activeElement); | ||
// focus is inside | ||
if (activeElement && innerNodes.indexOf(activeElement) >= 0) { | ||
@@ -26,23 +35,31 @@ return undefined; | ||
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) { | ||
@@ -54,3 +71,4 @@ if (Math.abs(indexDiff) > 1) { | ||
} | ||
// do nothing | ||
return undefined; | ||
}; |
@@ -5,2 +5,5 @@ declare const _default: { | ||
}; | ||
/** | ||
* @deprecated does nothing | ||
*/ | ||
export default _default; |
@@ -0,1 +1,5 @@ | ||
/* eslint-disable */ | ||
/** | ||
* @deprecated does nothing | ||
*/ | ||
export default { | ||
@@ -2,0 +6,0 @@ attach: function () { }, |
@@ -0,1 +1,6 @@ | ||
/** | ||
* returns all "focus containers" inside a given node | ||
* @param node | ||
* @returns {T} | ||
*/ | ||
export declare const getAllAffectedNodes: (node: Element | Element[]) => Element[]; |
import { FOCUS_DISABLED, FOCUS_GROUP } from '../constants'; | ||
import { asArray, toArray } from './array'; | ||
/** | ||
* in case of multiple nodes nested inside each other | ||
* keeps only top ones | ||
* this is O(nlogn) | ||
* @param nodes | ||
* @returns {*} | ||
*/ | ||
var filterNested = function (nodes) { | ||
@@ -9,2 +16,3 @@ var contained = new Set(); | ||
var position = nodes[i].compareDocumentPosition(nodes[j]); | ||
/* eslint-disable no-bitwise */ | ||
if ((position & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0) { | ||
@@ -16,2 +24,3 @@ contained.add(j); | ||
} | ||
/* eslint-enable */ | ||
} | ||
@@ -21,5 +30,15 @@ } | ||
}; | ||
/** | ||
* finds top most parent for a node | ||
* @param node | ||
* @returns {*} | ||
*/ | ||
var getTopParent = function (node) { | ||
return node.parentNode ? getTopParent(node.parentNode) : node; | ||
}; | ||
/** | ||
* returns all "focus containers" inside a given node | ||
* @param node | ||
* @returns {T} | ||
*/ | ||
export var getAllAffectedNodes = function (node) { | ||
@@ -26,0 +45,0 @@ var nodes = asArray(node); |
@@ -0,1 +1,4 @@ | ||
/* | ||
IE11 support | ||
*/ | ||
export var toArray = function (a) { | ||
@@ -2,0 +5,0 @@ var ret = Array(a.length); |
export declare const correctNode: (node: HTMLElement, nodes: HTMLElement[]) => HTMLElement; | ||
/** | ||
* giving a set of radio inputs keeps only selected (tabbable) ones | ||
* @param nodes | ||
*/ | ||
export declare const correctNodes: (nodes: HTMLElement[]) => HTMLElement[]; |
@@ -14,6 +14,12 @@ import { isRadioElement } from './is'; | ||
}; | ||
/** | ||
* giving a set of radio inputs keeps only selected (tabbable) ones | ||
* @param nodes | ||
*/ | ||
export var correctNodes = function (nodes) { | ||
// IE11 has no Set(array) 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); }); | ||
}; |
import { VisibilityCache } from './is'; | ||
import { NodeIndex } from './tabOrder'; | ||
/** | ||
* given list of focusable elements keeps the ones user can interact with | ||
* @param nodes | ||
* @param visibilityCache | ||
*/ | ||
export declare const filterFocusable: (nodes: HTMLElement[], visibilityCache: VisibilityCache) => HTMLElement[]; | ||
export declare const filterAutoFocusable: (nodes: HTMLElement[], cache?: VisibilityCache) => HTMLElement[]; | ||
/** | ||
* only tabbable ones | ||
* (but with guards which would be ignored) | ||
*/ | ||
export declare const getTabbableNodes: (topNodes: Element[], visibilityCache: VisibilityCache, withGuards?: boolean | undefined) => NodeIndex[]; | ||
/** | ||
* actually anything "focusable", not only tabbable | ||
* (without guards, as long as they are not expected to be focused) | ||
*/ | ||
export declare const getAllTabbableNodes: (topNodes: Element[], visibilityCache: VisibilityCache) => NodeIndex[]; | ||
/** | ||
* return list of nodes which are expected to be auto-focused | ||
* @param topNode | ||
* @param visibilityCache | ||
*/ | ||
export declare const parentAutofocusables: (topNode: Element, visibilityCache: VisibilityCache) => Element[]; | ||
export declare const contains: (scope: Element | ShadowRoot, element: Element) => boolean; |
import { toArray } from './array'; | ||
import { isVisibleCached, notHiddenInput } from './is'; | ||
import { isAutoFocusAllowedCached, isVisibleCached, notHiddenInput } from './is'; | ||
import { orderByTabIndex } from './tabOrder'; | ||
import { getFocusables, getParentAutofocusables } from './tabUtils'; | ||
/** | ||
* given list of focusable elements keeps the ones user can interact with | ||
* @param nodes | ||
* @param visibilityCache | ||
*/ | ||
export var filterFocusable = function (nodes, visibilityCache) { | ||
@@ -10,10 +15,35 @@ return toArray(nodes) | ||
}; | ||
export var filterAutoFocusable = function (nodes, cache) { | ||
if (cache === void 0) { cache = new Map(); } | ||
return toArray(nodes).filter(function (node) { return isAutoFocusAllowedCached(cache, node); }); | ||
}; | ||
/** | ||
* only tabbable ones | ||
* (but with guards which would be ignored) | ||
*/ | ||
export var getTabbableNodes = function (topNodes, visibilityCache, withGuards) { | ||
return orderByTabIndex(filterFocusable(getFocusables(topNodes, withGuards), visibilityCache), true, withGuards); | ||
}; | ||
/** | ||
* actually anything "focusable", not only tabbable | ||
* (without guards, as long as they are not expected to be focused) | ||
*/ | ||
export var getAllTabbableNodes = function (topNodes, visibilityCache) { | ||
return orderByTabIndex(filterFocusable(getFocusables(topNodes), visibilityCache), false); | ||
}; | ||
/** | ||
* return list of nodes which are expected to be auto-focused | ||
* @param topNode | ||
* @param visibilityCache | ||
*/ | ||
export var parentAutofocusables = function (topNode, visibilityCache) { | ||
return filterFocusable(getParentAutofocusables(topNode), visibilityCache); | ||
}; | ||
/* | ||
* Determines if element is contained in scope, including nested shadow DOMs | ||
*/ | ||
export var contains = function (scope, element) { | ||
return ((scope.shadowRoot | ||
? contains(scope.shadowRoot, element) | ||
: scope.contains(element)) || Array.from(scope.children).some(function (child) { return contains(child, element); })); | ||
}; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* returns active element from document or from nested shadowdoms | ||
*/ | ||
export declare const getActiveElement: () => HTMLElement | undefined; |
@@ -0,7 +1,17 @@ | ||
var getNestedShadowActiveElement = function (shadowRoot) { | ||
return shadowRoot.activeElement | ||
? shadowRoot.activeElement.shadowRoot | ||
? getNestedShadowActiveElement(shadowRoot.activeElement.shadowRoot) | ||
: shadowRoot.activeElement | ||
: undefined; | ||
}; | ||
/** | ||
* returns active element from document or from nested shadowdoms | ||
*/ | ||
export var getActiveElement = function () { | ||
return (document.activeElement | ||
? document.activeElement.shadowRoot | ||
? document.activeElement.shadowRoot.activeElement | ||
? getNestedShadowActiveElement(document.activeElement.shadowRoot) | ||
: document.activeElement | ||
: undefined); | ||
: undefined); // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
}; |
export declare type VisibilityCache = Map<Element | undefined, boolean>; | ||
export declare const isVisibleCached: (visibilityCache: VisibilityCache, node: Element | undefined) => boolean; | ||
export declare const isAutoFocusAllowedCached: (cache: VisibilityCache, node: Element | undefined) => boolean; | ||
export declare const getDataset: (node: Element) => HTMLElement['dataset'] | undefined; | ||
@@ -8,4 +9,5 @@ export declare const isHTMLButtonElement: (node: Element) => node is HTMLInputElement; | ||
export declare const notHiddenInput: (node: Element) => boolean; | ||
export declare const isAutoFocusAllowed: (node: Element) => boolean; | ||
export declare const isGuard: (node: Element | undefined) => boolean; | ||
export declare const isNotAGuard: (node: Element | undefined) => boolean; | ||
export declare const isDefined: <T>(x: T | null | undefined) => x is T; |
@@ -0,2 +1,5 @@ | ||
import { FOCUS_NO_AUTOFOCUS } from '../constants'; | ||
var isElementHidden = function (node) { | ||
// we can measure only "elements" | ||
// consider others as "visible" | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
@@ -11,11 +14,15 @@ return false; | ||
}; | ||
var getParentNode = function (node) { | ||
// DOCUMENT_FRAGMENT_NODE can also point on ShadowRoot. In this case .host will point on the next node | ||
return node.parentNode && node.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE | ||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
node.parentNode.host | ||
: node.parentNode; | ||
}; | ||
var isTopNode = function (node) { | ||
// @ts-ignore | ||
return node === document || (node && node.nodeType === Node.DOCUMENT_NODE); | ||
}; | ||
var isVisibleUncached = function (node, checkParent) { | ||
return !node || | ||
node === document || | ||
(node && node.nodeType === Node.DOCUMENT_NODE) || | ||
(!isElementHidden(node) && | ||
checkParent(node.parentNode && node.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE | ||
? | ||
node.parentNode.host | ||
: node.parentNode)); | ||
return !node || isTopNode(node) || (!isElementHidden(node) && checkParent(getParentNode(node))); | ||
}; | ||
@@ -31,3 +38,16 @@ export var isVisibleCached = function (visibilityCache, node) { | ||
}; | ||
var isAutoFocusAllowedUncached = function (node, checkParent) { | ||
return node && !isTopNode(node) ? (isAutoFocusAllowed(node) ? checkParent(getParentNode(node)) : false) : true; | ||
}; | ||
export var isAutoFocusAllowedCached = function (cache, node) { | ||
var cached = cache.get(node); | ||
if (cached !== undefined) { | ||
return cached; | ||
} | ||
var result = isAutoFocusAllowedUncached(node, isAutoFocusAllowedCached.bind(undefined, cache)); | ||
cache.set(node, result); | ||
return result; | ||
}; | ||
export var getDataset = function (node) { | ||
// @ts-ignore | ||
return node.dataset; | ||
@@ -41,7 +61,10 @@ }; | ||
export var notHiddenInput = function (node) { | ||
return !((isHTMLInputElement(node) || isHTMLButtonElement(node)) && (node.type === 'hidden' || node.disabled)) && | ||
!node.ariaDisabled; | ||
return !((isHTMLInputElement(node) || isHTMLButtonElement(node)) && (node.type === 'hidden' || node.disabled)); | ||
}; | ||
export var isAutoFocusAllowed = function (node) { | ||
var attribute = node.getAttribute(FOCUS_NO_AUTOFOCUS); | ||
return ![true, 'true', ''].includes(attribute); | ||
}; | ||
export var isGuard = function (node) { var _a; return Boolean(node && ((_a = getDataset(node)) === null || _a === void 0 ? void 0 : _a.focusGuard)); }; | ||
export var isNotAGuard = function (node) { return !isGuard(node); }; | ||
export var isDefined = function (x) { return Boolean(x); }; |
import { VisibilityCache } from './is'; | ||
/** | ||
* finds a parent for both nodeA and nodeB | ||
* @param nodeA | ||
* @param nodeB | ||
* @returns {boolean|*} | ||
*/ | ||
export declare const getCommonParent: (nodeA: Element, nodeB: Element) => Element | false; | ||
export declare const getTopCommonParent: (baseActiveElement: Element | Element[], leftEntry: Element | Element[], rightEntries: Element[]) => Element; | ||
/** | ||
* return list of nodes which are expected to be autofocused inside a given top nodes | ||
* @param entries | ||
* @param visibilityCache | ||
*/ | ||
export declare const allParentAutofocusables: (entries: Element[], visibilityCache: VisibilityCache) => Element[]; |
import { parentAutofocusables } from './DOMutils'; | ||
import { contains } from './DOMutils'; | ||
import { asArray } from './array'; | ||
@@ -7,9 +8,16 @@ var getParents = function (node, parents) { | ||
if (node.parentNode) { | ||
getParents(node.parentNode, parents); | ||
getParents(node.parentNode.host || node.parentNode, parents); | ||
} | ||
return parents; | ||
}; | ||
/** | ||
* finds a parent for both nodeA and nodeB | ||
* @param nodeA | ||
* @param nodeB | ||
* @returns {boolean|*} | ||
*/ | ||
export var getCommonParent = function (nodeA, nodeB) { | ||
var parentsA = getParents(nodeA); | ||
var parentsB = getParents(nodeB); | ||
// tslint:disable-next-line:prefer-for-of | ||
for (var i = 0; i < parentsA.length; i += 1) { | ||
@@ -33,3 +41,3 @@ var currentParent = parentsA[i]; | ||
if (common) { | ||
if (!topCommon || common.contains(topCommon)) { | ||
if (!topCommon || contains(common, topCommon)) { | ||
topCommon = common; | ||
@@ -43,6 +51,12 @@ } | ||
}); | ||
// TODO: add assert here? | ||
return topCommon; | ||
}; | ||
/** | ||
* return list of nodes which are expected to be autofocused inside a given top nodes | ||
* @param entries | ||
* @param visibilityCache | ||
*/ | ||
export var allParentAutofocusables = function (entries, visibilityCache) { | ||
return entries.reduce(function (acc, node) { return acc.concat(parentAutofocusables(node, visibilityCache)); }, []); | ||
}; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* list of the object to be considered as focusable | ||
*/ | ||
export declare const tabbables: string[]; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* list of the object to be considered as focusable | ||
*/ | ||
export var tabbables = [ | ||
@@ -6,2 +9,4 @@ 'button:enabled', | ||
'input:enabled', | ||
// elements with explicit roles will also use explicit tabindex | ||
// '[role="button"]', | ||
'a[href]', | ||
@@ -8,0 +13,0 @@ 'area[href]', |
export declare const getFocusables: (parents: Element[], withGuards?: boolean | undefined) => HTMLElement[]; | ||
/** | ||
* return a list of focusable nodes within an area marked as "auto-focusable" | ||
* @param parent | ||
*/ | ||
export declare const getParentAutofocusables: (parent: Element) => HTMLElement[]; |
@@ -6,5 +6,15 @@ import { FOCUS_AUTO } from '../constants'; | ||
var queryGuardTabbables = "".concat(queryTabbables, ", [data-focus-guard]"); | ||
var getFocusablesWithShadowDom = function (parent, withGuards) { | ||
var _a; | ||
return toArray(((_a = parent.shadowRoot) === null || _a === void 0 ? void 0 : _a.children) || parent.children).reduce(function (acc, child) { | ||
return acc.concat(child.matches(withGuards ? queryGuardTabbables : queryTabbables) ? [child] : [], getFocusablesWithShadowDom(child)); | ||
}, []); | ||
}; | ||
export var getFocusables = function (parents, withGuards) { | ||
return parents.reduce(function (acc, parent) { | ||
return acc.concat(toArray(parent.querySelectorAll(withGuards ? queryGuardTabbables : queryTabbables)), parent.parentNode | ||
return acc.concat( | ||
// add all tabbables inside and within shadow DOMs in DOM order | ||
getFocusablesWithShadowDom(parent, withGuards), | ||
// add if node is tabbable itself | ||
parent.parentNode | ||
? toArray(parent.parentNode.querySelectorAll(queryTabbables)).filter(function (node) { return node === parent; }) | ||
@@ -14,2 +24,6 @@ : []); | ||
}; | ||
/** | ||
* return a list of focusable nodes within an area marked as "auto-focusable" | ||
* @param parent | ||
*/ | ||
export var getParentAutofocusables = function (parent) { | ||
@@ -16,0 +30,0 @@ var parentFocus = parent.querySelectorAll("[".concat(FOCUS_AUTO, "]")); |
@@ -0,4 +1,22 @@ | ||
/** | ||
* defines a focus group | ||
*/ | ||
export declare const FOCUS_GROUP = "data-focus-lock"; | ||
/** | ||
* disables element discovery inside a group marked by key | ||
*/ | ||
export declare const FOCUS_DISABLED = "data-focus-lock-disabled"; | ||
/** | ||
* allows uncontrolled focus within the marked area, effectively disabling focus lock for it's content | ||
*/ | ||
export declare const FOCUS_ALLOW = "data-no-focus-lock"; | ||
/** | ||
* instructs autofocus engine to pick default autofocus inside a given node | ||
* can be set on the element or container | ||
*/ | ||
export declare const FOCUS_AUTO = "data-autofocus-inside"; | ||
/** | ||
* instructs autofocus to ignore elements within a given node | ||
* can be set on the element or container | ||
*/ | ||
export declare const FOCUS_NO_AUTOFOCUS = "data-no-autofocus"; |
@@ -0,4 +1,22 @@ | ||
/** | ||
* defines a focus group | ||
*/ | ||
export const FOCUS_GROUP = 'data-focus-lock'; | ||
/** | ||
* disables element discovery inside a group marked by key | ||
*/ | ||
export const FOCUS_DISABLED = 'data-focus-lock-disabled'; | ||
/** | ||
* allows uncontrolled focus within the marked area, effectively disabling focus lock for it's content | ||
*/ | ||
export const FOCUS_ALLOW = 'data-no-focus-lock'; | ||
/** | ||
* instructs autofocus engine to pick default autofocus inside a given node | ||
* can be set on the element or container | ||
*/ | ||
export const FOCUS_AUTO = 'data-autofocus-inside'; | ||
/** | ||
* instructs autofocus to ignore elements within a given node | ||
* can be set on the element or container | ||
*/ | ||
export const FOCUS_NO_AUTOFOCUS = 'data-no-autofocus'; |
interface FocusableIn { | ||
node: HTMLElement; | ||
/** | ||
* tab index | ||
*/ | ||
index: number; | ||
/** | ||
* true, if this node belongs to a Lock | ||
*/ | ||
lockItem: boolean; | ||
/** | ||
* true, if this node is a focus-guard (system node) | ||
*/ | ||
guard: boolean; | ||
} | ||
/** | ||
* return list of focusable elements inside a given top node | ||
* @deprecated use {@link getFocusableIn}. Yep, there is typo in the function name | ||
*/ | ||
export declare const getFocusabledIn: (topNode: HTMLElement) => FocusableIn[]; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
*/ | ||
export declare const getFocusableIn: (topNode: HTMLElement) => FocusableIn[]; | ||
export {}; |
@@ -5,2 +5,6 @@ import { getTabbableNodes } from './utils/DOMutils'; | ||
import { getTopCommonParent } from './utils/parenting'; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
* @deprecated use {@link getFocusableIn}. Yep, there is typo in the function name | ||
*/ | ||
export const getFocusabledIn = (topNode) => { | ||
@@ -21,1 +25,5 @@ const entries = getAllAffectedNodes(topNode).filter(isNotAGuard); | ||
}; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
*/ | ||
export const getFocusableIn = getFocusabledIn; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* @returns {Boolean} true, if the current focus is inside given node or nodes | ||
*/ | ||
export declare const focusInside: (topNode: HTMLElement | HTMLElement[]) => boolean; |
@@ -0,1 +1,2 @@ | ||
import { contains } from './utils/DOMutils'; | ||
import { getAllAffectedNodes } from './utils/all-affected'; | ||
@@ -6,2 +7,5 @@ import { toArray } from './utils/array'; | ||
const focusInsideIframe = (topNode) => Boolean(toArray(topNode.querySelectorAll('iframe')).some((node) => focusInFrame(node))); | ||
/** | ||
* @returns {Boolean} true, if the current focus is inside given node or nodes | ||
*/ | ||
export const focusInside = (topNode) => { | ||
@@ -12,3 +16,3 @@ const activeElement = document && getActiveElement(); | ||
} | ||
return getAllAffectedNodes(topNode).reduce((result, node) => result || node.contains(activeElement) || focusInsideIframe(node), false); | ||
return getAllAffectedNodes(topNode).some((node) => contains(node, activeElement) || focusInsideIframe(node)); | ||
}; |
@@ -0,1 +1,6 @@ | ||
/** | ||
* focus is hidden FROM the focus-lock | ||
* ie contained inside a node focus-lock shall ignore | ||
* @returns {boolean} focus is currently is in "allow" area | ||
*/ | ||
export declare const focusIsHidden: () => boolean; |
import { FOCUS_ALLOW } from './constants'; | ||
import { contains } from './utils/DOMutils'; | ||
import { toArray } from './utils/array'; | ||
import { getActiveElement } from './utils/getActiveElement'; | ||
/** | ||
* focus is hidden FROM the focus-lock | ||
* ie contained inside a node focus-lock shall ignore | ||
* @returns {boolean} focus is currently is in "allow" area | ||
*/ | ||
export const focusIsHidden = () => { | ||
@@ -9,3 +15,4 @@ const activeElement = document && getActiveElement(); | ||
} | ||
return toArray(document.querySelectorAll(`[${FOCUS_ALLOW}]`)).some((node) => node.contains(activeElement)); | ||
// this does not support setting FOCUS_ALLOW within shadow dom | ||
return toArray(document.querySelectorAll(`[${FOCUS_ALLOW}]`)).some((node) => contains(node, activeElement)); | ||
}; |
@@ -0,3 +1,8 @@ | ||
/** | ||
* given top node(s) and the last active element return the element to be focused next | ||
* @param topNode | ||
* @param lastNode | ||
*/ | ||
export declare const getFocusMerge: (topNode: Element | Element[], lastNode: Element | null) => undefined | { | ||
node: HTMLElement; | ||
}; |
import { NEW_FOCUS, newFocus } from './solver'; | ||
import { getAllTabbableNodes, getTabbableNodes } from './utils/DOMutils'; | ||
import { filterAutoFocusable, getAllTabbableNodes, getTabbableNodes } from './utils/DOMutils'; | ||
import { getAllAffectedNodes } from './utils/all-affected'; | ||
@@ -8,8 +8,17 @@ import { pickFirstFocus } from './utils/firstFocus'; | ||
import { allParentAutofocusables, getTopCommonParent } from './utils/parenting'; | ||
const findAutoFocused = (autoFocusables) => (node) => { var _a; return node.autofocus || !!((_a = getDataset(node)) === null || _a === void 0 ? void 0 : _a.autofocus) || autoFocusables.indexOf(node) >= 0; }; | ||
const findAutoFocused = (autoFocusables) => (node) => { var _a; | ||
// @ts-expect-error | ||
return node.autofocus || !!((_a = getDataset(node)) === null || _a === void 0 ? void 0 : _a.autofocus) || autoFocusables.indexOf(node) >= 0; }; | ||
const reorderNodes = (srcNodes, dstNodes) => { | ||
const remap = new Map(); | ||
// no Set(dstNodes) for IE11 :( | ||
dstNodes.forEach((entity) => remap.set(entity.node, entity)); | ||
// remap to dstNodes | ||
return srcNodes.map((node) => remap.get(node)).filter(isDefined); | ||
}; | ||
/** | ||
* given top node(s) and the last active element return the element to be focused next | ||
* @param topNode | ||
* @param lastNode | ||
*/ | ||
export const getFocusMerge = (topNode, lastNode) => { | ||
@@ -33,7 +42,7 @@ const activeElement = document && getActiveElement(); | ||
if (newId === NEW_FOCUS) { | ||
const autoFocusable = anyFocusable | ||
.map(({ node }) => node) | ||
.filter(findAutoFocused(allParentAutofocusables(entries, visibilityCache))); | ||
const autoFocusable = filterAutoFocusable(anyFocusable.map(({ node }) => node)).filter(findAutoFocused(allParentAutofocusables(entries, visibilityCache))); | ||
return { | ||
node: autoFocusable && autoFocusable.length ? pickFirstFocus(autoFocusable) : pickFirstFocus(innerNodes), | ||
node: autoFocusable && autoFocusable.length | ||
? pickFirstFocus(autoFocusable) | ||
: pickFirstFocus(filterAutoFocusable(innerNodes)), | ||
}; | ||
@@ -40,0 +49,0 @@ } |
@@ -5,3 +5,3 @@ import * as constants from './constants'; | ||
import { getFocusMerge as focusMerge } from './focusMerge'; | ||
import { getFocusabledIn } from './focusables'; | ||
import { getFocusabledIn, getFocusableIn } from './focusables'; | ||
import { setFocus } from './setFocus'; | ||
@@ -12,3 +12,3 @@ import { focusNextElement, focusPrevElement } from './sibling'; | ||
import { getActiveElement } from './utils/getActiveElement'; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusableIn, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export default setFocus; |
@@ -5,3 +5,3 @@ import * as constants from './constants'; | ||
import { getFocusMerge as focusMerge } from './focusMerge'; | ||
import { getFocusabledIn } from './focusables'; | ||
import { getFocusabledIn, getFocusableIn } from './focusables'; | ||
import { setFocus } from './setFocus'; | ||
@@ -12,3 +12,4 @@ import { focusNextElement, focusPrevElement } from './sibling'; | ||
import { getActiveElement } from './utils/getActiveElement'; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusableIn, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export default setFocus; | ||
// |
@@ -5,3 +5,10 @@ export declare const focusOn: (target: Element | HTMLFrameElement | HTMLElement, focusOptions?: FocusOptions | undefined) => void; | ||
} | ||
/** | ||
* Sets focus at a given node. The last focused element will help to determine which element(first or last) should be focused. | ||
* HTML markers (see {@link import('./constants').FOCUS_AUTO} constants) can control autofocus | ||
* @param topNode | ||
* @param lastNode | ||
* @param options | ||
*/ | ||
export declare const setFocus: (topNode: HTMLElement, lastNode: Element, options?: FocusLockFocusOptions) => void; | ||
export {}; |
@@ -12,2 +12,9 @@ import { getFocusMerge } from './focusMerge'; | ||
let lockDisabled = false; | ||
/** | ||
* Sets focus at a given node. The last focused element will help to determine which element(first or last) should be focused. | ||
* HTML markers (see {@link import('./constants').FOCUS_AUTO} constants) can control autofocus | ||
* @param topNode | ||
* @param lastNode | ||
* @param options | ||
*/ | ||
export const setFocus = (topNode, lastNode, options = {}) => { | ||
@@ -20,2 +27,3 @@ const focusable = getFocusMerge(topNode, lastNode); | ||
if (guardCount > 2) { | ||
// tslint:disable-next-line:no-console | ||
console.error('FocusLock: focus-fighting detected. Only one focus management system could be active. ' + | ||
@@ -22,0 +30,0 @@ 'See https://github.com/theKashey/focus-lock/#focus-fighting'); |
interface FocusNextOptions { | ||
/** | ||
* the component to "scope" focus in | ||
* @default document.body | ||
*/ | ||
scope?: HTMLElement | HTMLDocument; | ||
/** | ||
* enables cycling inside the scope | ||
* @default true | ||
*/ | ||
cycle?: boolean; | ||
/** | ||
* options for focus action to control it more precisely (ie. `{ preventScroll: true }`) | ||
*/ | ||
focusOptions?: FocusOptions; | ||
} | ||
/** | ||
* focuses next element in the tab-order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export declare const focusNextElement: (baseElement: Element, options?: FocusNextOptions) => void; | ||
/** | ||
* focuses prev element in the tab order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export declare const focusPrevElement: (baseElement: Element, options?: FocusNextOptions) => void; | ||
export {}; |
import { focusOn } from './setFocus'; | ||
import { getTabbableNodes } from './utils/DOMutils'; | ||
import { getTabbableNodes, contains } from './utils/DOMutils'; | ||
const getRelativeFocusable = (element, scope) => { | ||
if (!element || !scope || !scope.contains(element)) { | ||
if (!element || !scope || !contains(scope, element)) { | ||
return {}; | ||
@@ -23,2 +23,7 @@ } | ||
}, options); | ||
/** | ||
* focuses next element in the tab-order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export const focusNextElement = (baseElement, options = {}) => { | ||
@@ -32,2 +37,7 @@ const { scope, cycle } = defaultOptions(options); | ||
}; | ||
/** | ||
* focuses prev element in the tab order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export const focusPrevElement = (baseElement, options = {}) => { | ||
@@ -34,0 +44,0 @@ const { scope, cycle } = defaultOptions(options); |
export declare const NEW_FOCUS = "NEW_FOCUS"; | ||
/** | ||
* Main solver for the "find next focus" question | ||
* @param innerNodes | ||
* @param outerNodes | ||
* @param activeElement | ||
* @param lastNode | ||
* @returns {number|string|undefined|*} | ||
*/ | ||
export declare const newFocus: (innerNodes: HTMLElement[], outerNodes: HTMLElement[], activeElement: HTMLElement | undefined, lastNode: HTMLElement | null) => number | undefined | typeof NEW_FOCUS; |
@@ -5,2 +5,10 @@ import { correctNodes } from './utils/correctFocus'; | ||
export const NEW_FOCUS = 'NEW_FOCUS'; | ||
/** | ||
* Main solver for the "find next focus" question | ||
* @param innerNodes | ||
* @param outerNodes | ||
* @param activeElement | ||
* @param lastNode | ||
* @returns {number|string|undefined|*} | ||
*/ | ||
export const newFocus = (innerNodes, outerNodes, activeElement, lastNode) => { | ||
@@ -11,2 +19,3 @@ const cnt = innerNodes.length; | ||
const isOnGuard = isGuard(activeElement); | ||
// focus is inside | ||
if (activeElement && innerNodes.indexOf(activeElement) >= 0) { | ||
@@ -26,23 +35,31 @@ return undefined; | ||
const 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) { | ||
@@ -54,3 +71,4 @@ if (Math.abs(indexDiff) > 1) { | ||
} | ||
// do nothing | ||
return undefined; | ||
}; |
@@ -5,2 +5,5 @@ declare const _default: { | ||
}; | ||
/** | ||
* @deprecated does nothing | ||
*/ | ||
export default _default; |
@@ -0,1 +1,5 @@ | ||
/* eslint-disable */ | ||
/** | ||
* @deprecated does nothing | ||
*/ | ||
export default { | ||
@@ -2,0 +6,0 @@ attach() { }, |
@@ -0,1 +1,6 @@ | ||
/** | ||
* returns all "focus containers" inside a given node | ||
* @param node | ||
* @returns {T} | ||
*/ | ||
export declare const getAllAffectedNodes: (node: Element | Element[]) => Element[]; |
import { FOCUS_DISABLED, FOCUS_GROUP } from '../constants'; | ||
import { asArray, toArray } from './array'; | ||
/** | ||
* in case of multiple nodes nested inside each other | ||
* keeps only top ones | ||
* this is O(nlogn) | ||
* @param nodes | ||
* @returns {*} | ||
*/ | ||
const filterNested = (nodes) => { | ||
@@ -9,2 +16,3 @@ const contained = new Set(); | ||
const position = nodes[i].compareDocumentPosition(nodes[j]); | ||
/* eslint-disable no-bitwise */ | ||
if ((position & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0) { | ||
@@ -16,2 +24,3 @@ contained.add(j); | ||
} | ||
/* eslint-enable */ | ||
} | ||
@@ -21,3 +30,13 @@ } | ||
}; | ||
/** | ||
* finds top most parent for a node | ||
* @param node | ||
* @returns {*} | ||
*/ | ||
const getTopParent = (node) => node.parentNode ? getTopParent(node.parentNode) : node; | ||
/** | ||
* returns all "focus containers" inside a given node | ||
* @param node | ||
* @returns {T} | ||
*/ | ||
export const getAllAffectedNodes = (node) => { | ||
@@ -24,0 +43,0 @@ const nodes = asArray(node); |
@@ -0,1 +1,4 @@ | ||
/* | ||
IE11 support | ||
*/ | ||
export const toArray = (a) => { | ||
@@ -2,0 +5,0 @@ const ret = Array(a.length); |
export declare const correctNode: (node: HTMLElement, nodes: HTMLElement[]) => HTMLElement; | ||
/** | ||
* giving a set of radio inputs keeps only selected (tabbable) ones | ||
* @param nodes | ||
*/ | ||
export declare const correctNodes: (nodes: HTMLElement[]) => HTMLElement[]; |
@@ -12,6 +12,12 @@ import { isRadioElement } from './is'; | ||
}; | ||
/** | ||
* giving a set of radio inputs keeps only selected (tabbable) ones | ||
* @param nodes | ||
*/ | ||
export const correctNodes = (nodes) => { | ||
// IE11 has no Set(array) constructor | ||
const resultSet = new Set(); | ||
nodes.forEach((node) => resultSet.add(correctNode(node, nodes))); | ||
// using filter to support IE11 | ||
return nodes.filter((node) => resultSet.has(node)); | ||
}; |
import { VisibilityCache } from './is'; | ||
import { NodeIndex } from './tabOrder'; | ||
/** | ||
* given list of focusable elements keeps the ones user can interact with | ||
* @param nodes | ||
* @param visibilityCache | ||
*/ | ||
export declare const filterFocusable: (nodes: HTMLElement[], visibilityCache: VisibilityCache) => HTMLElement[]; | ||
export declare const filterAutoFocusable: (nodes: HTMLElement[], cache?: VisibilityCache) => HTMLElement[]; | ||
/** | ||
* only tabbable ones | ||
* (but with guards which would be ignored) | ||
*/ | ||
export declare const getTabbableNodes: (topNodes: Element[], visibilityCache: VisibilityCache, withGuards?: boolean | undefined) => NodeIndex[]; | ||
/** | ||
* actually anything "focusable", not only tabbable | ||
* (without guards, as long as they are not expected to be focused) | ||
*/ | ||
export declare const getAllTabbableNodes: (topNodes: Element[], visibilityCache: VisibilityCache) => NodeIndex[]; | ||
/** | ||
* return list of nodes which are expected to be auto-focused | ||
* @param topNode | ||
* @param visibilityCache | ||
*/ | ||
export declare const parentAutofocusables: (topNode: Element, visibilityCache: VisibilityCache) => Element[]; | ||
export declare const contains: (scope: Element | ShadowRoot, element: Element) => boolean; |
import { toArray } from './array'; | ||
import { isVisibleCached, notHiddenInput } from './is'; | ||
import { isAutoFocusAllowedCached, isVisibleCached, notHiddenInput } from './is'; | ||
import { orderByTabIndex } from './tabOrder'; | ||
import { getFocusables, getParentAutofocusables } from './tabUtils'; | ||
/** | ||
* given list of focusable elements keeps the ones user can interact with | ||
* @param nodes | ||
* @param visibilityCache | ||
*/ | ||
export const filterFocusable = (nodes, visibilityCache) => toArray(nodes) | ||
.filter((node) => isVisibleCached(visibilityCache, node)) | ||
.filter((node) => notHiddenInput(node)); | ||
export const filterAutoFocusable = (nodes, cache = new Map()) => toArray(nodes).filter((node) => isAutoFocusAllowedCached(cache, node)); | ||
/** | ||
* only tabbable ones | ||
* (but with guards which would be ignored) | ||
*/ | ||
export const getTabbableNodes = (topNodes, visibilityCache, withGuards) => orderByTabIndex(filterFocusable(getFocusables(topNodes, withGuards), visibilityCache), true, withGuards); | ||
/** | ||
* actually anything "focusable", not only tabbable | ||
* (without guards, as long as they are not expected to be focused) | ||
*/ | ||
export const getAllTabbableNodes = (topNodes, visibilityCache) => orderByTabIndex(filterFocusable(getFocusables(topNodes), visibilityCache), false); | ||
/** | ||
* return list of nodes which are expected to be auto-focused | ||
* @param topNode | ||
* @param visibilityCache | ||
*/ | ||
export const parentAutofocusables = (topNode, visibilityCache) => filterFocusable(getParentAutofocusables(topNode), visibilityCache); | ||
/* | ||
* Determines if element is contained in scope, including nested shadow DOMs | ||
*/ | ||
export const contains = (scope, element) => { | ||
return ((scope.shadowRoot | ||
? contains(scope.shadowRoot, element) | ||
: scope.contains(element)) || Array.from(scope.children).some((child) => contains(child, element))); | ||
}; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* returns active element from document or from nested shadowdoms | ||
*/ | ||
export declare const getActiveElement: () => HTMLElement | undefined; |
@@ -0,7 +1,15 @@ | ||
const getNestedShadowActiveElement = (shadowRoot) => shadowRoot.activeElement | ||
? shadowRoot.activeElement.shadowRoot | ||
? getNestedShadowActiveElement(shadowRoot.activeElement.shadowRoot) | ||
: shadowRoot.activeElement | ||
: undefined; | ||
/** | ||
* returns active element from document or from nested shadowdoms | ||
*/ | ||
export const getActiveElement = () => { | ||
return (document.activeElement | ||
? document.activeElement.shadowRoot | ||
? document.activeElement.shadowRoot.activeElement | ||
? getNestedShadowActiveElement(document.activeElement.shadowRoot) | ||
: document.activeElement | ||
: undefined); | ||
: undefined); // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
}; |
export declare type VisibilityCache = Map<Element | undefined, boolean>; | ||
export declare const isVisibleCached: (visibilityCache: VisibilityCache, node: Element | undefined) => boolean; | ||
export declare const isAutoFocusAllowedCached: (cache: VisibilityCache, node: Element | undefined) => boolean; | ||
export declare const getDataset: (node: Element) => HTMLElement['dataset'] | undefined; | ||
@@ -8,4 +9,5 @@ export declare const isHTMLButtonElement: (node: Element) => node is HTMLInputElement; | ||
export declare const notHiddenInput: (node: Element) => boolean; | ||
export declare const isAutoFocusAllowed: (node: Element) => boolean; | ||
export declare const isGuard: (node: Element | undefined) => boolean; | ||
export declare const isNotAGuard: (node: Element | undefined) => boolean; | ||
export declare const isDefined: <T>(x: T | null | undefined) => x is T; |
@@ -0,2 +1,5 @@ | ||
import { FOCUS_NO_AUTOFOCUS } from '../constants'; | ||
const isElementHidden = (node) => { | ||
// we can measure only "elements" | ||
// consider others as "visible" | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
@@ -11,10 +14,12 @@ return false; | ||
}; | ||
const isVisibleUncached = (node, checkParent) => !node || | ||
node === document || | ||
(node && node.nodeType === Node.DOCUMENT_NODE) || | ||
(!isElementHidden(node) && | ||
checkParent(node.parentNode && node.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE | ||
? | ||
node.parentNode.host | ||
: node.parentNode)); | ||
const getParentNode = (node) => | ||
// DOCUMENT_FRAGMENT_NODE can also point on ShadowRoot. In this case .host will point on the next node | ||
node.parentNode && node.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE | ||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
node.parentNode.host | ||
: node.parentNode; | ||
const isTopNode = (node) => | ||
// @ts-ignore | ||
node === document || (node && node.nodeType === Node.DOCUMENT_NODE); | ||
const isVisibleUncached = (node, checkParent) => !node || isTopNode(node) || (!isElementHidden(node) && checkParent(getParentNode(node))); | ||
export const isVisibleCached = (visibilityCache, node) => { | ||
@@ -29,10 +34,25 @@ const cached = visibilityCache.get(node); | ||
}; | ||
export const getDataset = (node) => node.dataset; | ||
const isAutoFocusAllowedUncached = (node, checkParent) => node && !isTopNode(node) ? (isAutoFocusAllowed(node) ? checkParent(getParentNode(node)) : false) : true; | ||
export const isAutoFocusAllowedCached = (cache, node) => { | ||
const cached = cache.get(node); | ||
if (cached !== undefined) { | ||
return cached; | ||
} | ||
const result = isAutoFocusAllowedUncached(node, isAutoFocusAllowedCached.bind(undefined, cache)); | ||
cache.set(node, result); | ||
return result; | ||
}; | ||
export const getDataset = (node) => | ||
// @ts-ignore | ||
node.dataset; | ||
export const isHTMLButtonElement = (node) => node.tagName === 'BUTTON'; | ||
export const isHTMLInputElement = (node) => node.tagName === 'INPUT'; | ||
export const isRadioElement = (node) => isHTMLInputElement(node) && node.type === 'radio'; | ||
export const notHiddenInput = (node) => !((isHTMLInputElement(node) || isHTMLButtonElement(node)) && (node.type === 'hidden' || node.disabled)) && | ||
!node.ariaDisabled; | ||
export const notHiddenInput = (node) => !((isHTMLInputElement(node) || isHTMLButtonElement(node)) && (node.type === 'hidden' || node.disabled)); | ||
export const isAutoFocusAllowed = (node) => { | ||
const attribute = node.getAttribute(FOCUS_NO_AUTOFOCUS); | ||
return ![true, 'true', ''].includes(attribute); | ||
}; | ||
export const isGuard = (node) => { var _a; return Boolean(node && ((_a = getDataset(node)) === null || _a === void 0 ? void 0 : _a.focusGuard)); }; | ||
export const isNotAGuard = (node) => !isGuard(node); | ||
export const isDefined = (x) => Boolean(x); |
import { VisibilityCache } from './is'; | ||
/** | ||
* finds a parent for both nodeA and nodeB | ||
* @param nodeA | ||
* @param nodeB | ||
* @returns {boolean|*} | ||
*/ | ||
export declare const getCommonParent: (nodeA: Element, nodeB: Element) => Element | false; | ||
export declare const getTopCommonParent: (baseActiveElement: Element | Element[], leftEntry: Element | Element[], rightEntries: Element[]) => Element; | ||
/** | ||
* return list of nodes which are expected to be autofocused inside a given top nodes | ||
* @param entries | ||
* @param visibilityCache | ||
*/ | ||
export declare const allParentAutofocusables: (entries: Element[], visibilityCache: VisibilityCache) => Element[]; |
import { parentAutofocusables } from './DOMutils'; | ||
import { contains } from './DOMutils'; | ||
import { asArray } from './array'; | ||
@@ -6,9 +7,16 @@ const getParents = (node, parents = []) => { | ||
if (node.parentNode) { | ||
getParents(node.parentNode, parents); | ||
getParents(node.parentNode.host || node.parentNode, parents); | ||
} | ||
return parents; | ||
}; | ||
/** | ||
* finds a parent for both nodeA and nodeB | ||
* @param nodeA | ||
* @param nodeB | ||
* @returns {boolean|*} | ||
*/ | ||
export const getCommonParent = (nodeA, nodeB) => { | ||
const parentsA = getParents(nodeA); | ||
const parentsB = getParents(nodeB); | ||
// tslint:disable-next-line:prefer-for-of | ||
for (let i = 0; i < parentsA.length; i += 1) { | ||
@@ -32,3 +40,3 @@ const currentParent = parentsA[i]; | ||
if (common) { | ||
if (!topCommon || common.contains(topCommon)) { | ||
if (!topCommon || contains(common, topCommon)) { | ||
topCommon = common; | ||
@@ -42,4 +50,10 @@ } | ||
}); | ||
// TODO: add assert here? | ||
return topCommon; | ||
}; | ||
/** | ||
* return list of nodes which are expected to be autofocused inside a given top nodes | ||
* @param entries | ||
* @param visibilityCache | ||
*/ | ||
export const allParentAutofocusables = (entries, visibilityCache) => entries.reduce((acc, node) => acc.concat(parentAutofocusables(node, visibilityCache)), []); |
@@ -0,1 +1,4 @@ | ||
/** | ||
* list of the object to be considered as focusable | ||
*/ | ||
export declare const tabbables: string[]; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* list of the object to be considered as focusable | ||
*/ | ||
export const tabbables = [ | ||
@@ -6,2 +9,4 @@ 'button:enabled', | ||
'input:enabled', | ||
// elements with explicit roles will also use explicit tabindex | ||
// '[role="button"]', | ||
'a[href]', | ||
@@ -8,0 +13,0 @@ 'area[href]', |
export declare const getFocusables: (parents: Element[], withGuards?: boolean | undefined) => HTMLElement[]; | ||
/** | ||
* return a list of focusable nodes within an area marked as "auto-focusable" | ||
* @param parent | ||
*/ | ||
export declare const getParentAutofocusables: (parent: Element) => HTMLElement[]; |
@@ -6,5 +6,17 @@ import { FOCUS_AUTO } from '../constants'; | ||
const queryGuardTabbables = `${queryTabbables}, [data-focus-guard]`; | ||
export const getFocusables = (parents, withGuards) => parents.reduce((acc, parent) => acc.concat(toArray(parent.querySelectorAll(withGuards ? queryGuardTabbables : queryTabbables)), parent.parentNode | ||
const getFocusablesWithShadowDom = (parent, withGuards) => { | ||
var _a; | ||
return toArray(((_a = parent.shadowRoot) === null || _a === void 0 ? void 0 : _a.children) || parent.children).reduce((acc, child) => acc.concat(child.matches(withGuards ? queryGuardTabbables : queryTabbables) ? [child] : [], getFocusablesWithShadowDom(child)), []); | ||
}; | ||
export const getFocusables = (parents, withGuards) => parents.reduce((acc, parent) => acc.concat( | ||
// add all tabbables inside and within shadow DOMs in DOM order | ||
getFocusablesWithShadowDom(parent, withGuards), | ||
// add if node is tabbable itself | ||
parent.parentNode | ||
? toArray(parent.parentNode.querySelectorAll(queryTabbables)).filter((node) => node === parent) | ||
: []), []); | ||
/** | ||
* return a list of focusable nodes within an area marked as "auto-focusable" | ||
* @param parent | ||
*/ | ||
export const getParentAutofocusables = (parent) => { | ||
@@ -11,0 +23,0 @@ const parentFocus = parent.querySelectorAll(`[${FOCUS_AUTO}]`); |
@@ -0,4 +1,22 @@ | ||
/** | ||
* defines a focus group | ||
*/ | ||
export declare const FOCUS_GROUP = "data-focus-lock"; | ||
/** | ||
* disables element discovery inside a group marked by key | ||
*/ | ||
export declare const FOCUS_DISABLED = "data-focus-lock-disabled"; | ||
/** | ||
* allows uncontrolled focus within the marked area, effectively disabling focus lock for it's content | ||
*/ | ||
export declare const FOCUS_ALLOW = "data-no-focus-lock"; | ||
/** | ||
* instructs autofocus engine to pick default autofocus inside a given node | ||
* can be set on the element or container | ||
*/ | ||
export declare const FOCUS_AUTO = "data-autofocus-inside"; | ||
/** | ||
* instructs autofocus to ignore elements within a given node | ||
* can be set on the element or container | ||
*/ | ||
export declare const FOCUS_NO_AUTOFOCUS = "data-no-autofocus"; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.FOCUS_AUTO = exports.FOCUS_ALLOW = exports.FOCUS_DISABLED = exports.FOCUS_GROUP = void 0; | ||
exports.FOCUS_NO_AUTOFOCUS = exports.FOCUS_AUTO = exports.FOCUS_ALLOW = exports.FOCUS_DISABLED = exports.FOCUS_GROUP = void 0; | ||
/** | ||
* defines a focus group | ||
*/ | ||
exports.FOCUS_GROUP = 'data-focus-lock'; | ||
/** | ||
* disables element discovery inside a group marked by key | ||
*/ | ||
exports.FOCUS_DISABLED = 'data-focus-lock-disabled'; | ||
/** | ||
* allows uncontrolled focus within the marked area, effectively disabling focus lock for it's content | ||
*/ | ||
exports.FOCUS_ALLOW = 'data-no-focus-lock'; | ||
/** | ||
* instructs autofocus engine to pick default autofocus inside a given node | ||
* can be set on the element or container | ||
*/ | ||
exports.FOCUS_AUTO = 'data-autofocus-inside'; | ||
/** | ||
* instructs autofocus to ignore elements within a given node | ||
* can be set on the element or container | ||
*/ | ||
exports.FOCUS_NO_AUTOFOCUS = 'data-no-autofocus'; |
interface FocusableIn { | ||
node: HTMLElement; | ||
/** | ||
* tab index | ||
*/ | ||
index: number; | ||
/** | ||
* true, if this node belongs to a Lock | ||
*/ | ||
lockItem: boolean; | ||
/** | ||
* true, if this node is a focus-guard (system node) | ||
*/ | ||
guard: boolean; | ||
} | ||
/** | ||
* return list of focusable elements inside a given top node | ||
* @deprecated use {@link getFocusableIn}. Yep, there is typo in the function name | ||
*/ | ||
export declare const getFocusabledIn: (topNode: HTMLElement) => FocusableIn[]; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
*/ | ||
export declare const getFocusableIn: (topNode: HTMLElement) => FocusableIn[]; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getFocusabledIn = void 0; | ||
exports.getFocusableIn = exports.getFocusabledIn = void 0; | ||
var DOMutils_1 = require("./utils/DOMutils"); | ||
@@ -8,2 +8,6 @@ var all_affected_1 = require("./utils/all-affected"); | ||
var parenting_1 = require("./utils/parenting"); | ||
/** | ||
* return list of focusable elements inside a given top node | ||
* @deprecated use {@link getFocusableIn}. Yep, there is typo in the function name | ||
*/ | ||
var getFocusabledIn = function (topNode) { | ||
@@ -34,1 +38,5 @@ var entries = (0, all_affected_1.getAllAffectedNodes)(topNode).filter(is_1.isNotAGuard); | ||
exports.getFocusabledIn = getFocusabledIn; | ||
/** | ||
* return list of focusable elements inside a given top node | ||
*/ | ||
exports.getFocusableIn = exports.getFocusabledIn; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* @returns {Boolean} true, if the current focus is inside given node or nodes | ||
*/ | ||
export declare const focusInside: (topNode: HTMLElement | HTMLElement[]) => boolean; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.focusInside = void 0; | ||
var DOMutils_1 = require("./utils/DOMutils"); | ||
var all_affected_1 = require("./utils/all-affected"); | ||
@@ -11,2 +12,5 @@ var array_1 = require("./utils/array"); | ||
}; | ||
/** | ||
* @returns {Boolean} true, if the current focus is inside given node or nodes | ||
*/ | ||
var focusInside = function (topNode) { | ||
@@ -17,4 +21,4 @@ var activeElement = document && (0, getActiveElement_1.getActiveElement)(); | ||
} | ||
return (0, all_affected_1.getAllAffectedNodes)(topNode).reduce(function (result, node) { return result || node.contains(activeElement) || focusInsideIframe(node); }, false); | ||
return (0, all_affected_1.getAllAffectedNodes)(topNode).some(function (node) { return (0, DOMutils_1.contains)(node, activeElement) || focusInsideIframe(node); }); | ||
}; | ||
exports.focusInside = focusInside; |
@@ -0,1 +1,6 @@ | ||
/** | ||
* focus is hidden FROM the focus-lock | ||
* ie contained inside a node focus-lock shall ignore | ||
* @returns {boolean} focus is currently is in "allow" area | ||
*/ | ||
export declare const focusIsHidden: () => boolean; |
@@ -5,4 +5,10 @@ "use strict"; | ||
var constants_1 = require("./constants"); | ||
var DOMutils_1 = require("./utils/DOMutils"); | ||
var array_1 = require("./utils/array"); | ||
var getActiveElement_1 = require("./utils/getActiveElement"); | ||
/** | ||
* focus is hidden FROM the focus-lock | ||
* ie contained inside a node focus-lock shall ignore | ||
* @returns {boolean} focus is currently is in "allow" area | ||
*/ | ||
var focusIsHidden = function () { | ||
@@ -13,4 +19,5 @@ var activeElement = document && (0, getActiveElement_1.getActiveElement)(); | ||
} | ||
return (0, array_1.toArray)(document.querySelectorAll("[".concat(constants_1.FOCUS_ALLOW, "]"))).some(function (node) { return node.contains(activeElement); }); | ||
// this does not support setting FOCUS_ALLOW within shadow dom | ||
return (0, array_1.toArray)(document.querySelectorAll("[".concat(constants_1.FOCUS_ALLOW, "]"))).some(function (node) { return (0, DOMutils_1.contains)(node, activeElement); }); | ||
}; | ||
exports.focusIsHidden = focusIsHidden; |
@@ -0,3 +1,8 @@ | ||
/** | ||
* given top node(s) and the last active element return the element to be focused next | ||
* @param topNode | ||
* @param lastNode | ||
*/ | ||
export declare const getFocusMerge: (topNode: Element | Element[], lastNode: Element | null) => undefined | { | ||
node: HTMLElement; | ||
}; |
@@ -12,9 +12,18 @@ "use strict"; | ||
var findAutoFocused = function (autoFocusables) { | ||
return function (node) { var _a; return node.autofocus || !!((_a = (0, is_1.getDataset)(node)) === null || _a === void 0 ? void 0 : _a.autofocus) || autoFocusables.indexOf(node) >= 0; }; | ||
return function (node) { var _a; | ||
// @ts-expect-error | ||
return node.autofocus || !!((_a = (0, is_1.getDataset)(node)) === null || _a === void 0 ? void 0 : _a.autofocus) || autoFocusables.indexOf(node) >= 0; }; | ||
}; | ||
var reorderNodes = function (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(is_1.isDefined); | ||
}; | ||
/** | ||
* given top node(s) and the last active element return the element to be focused next | ||
* @param topNode | ||
* @param lastNode | ||
*/ | ||
var getFocusMerge = function (topNode, lastNode) { | ||
@@ -47,10 +56,10 @@ var activeElement = document && (0, getActiveElement_1.getActiveElement)(); | ||
if (newId === solver_1.NEW_FOCUS) { | ||
var autoFocusable = anyFocusable | ||
.map(function (_a) { | ||
var autoFocusable = (0, DOMutils_1.filterAutoFocusable)(anyFocusable.map(function (_a) { | ||
var node = _a.node; | ||
return node; | ||
}) | ||
.filter(findAutoFocused((0, parenting_1.allParentAutofocusables)(entries, visibilityCache))); | ||
})).filter(findAutoFocused((0, parenting_1.allParentAutofocusables)(entries, visibilityCache))); | ||
return { | ||
node: autoFocusable && autoFocusable.length ? (0, firstFocus_1.pickFirstFocus)(autoFocusable) : (0, firstFocus_1.pickFirstFocus)(innerNodes), | ||
node: autoFocusable && autoFocusable.length | ||
? (0, firstFocus_1.pickFirstFocus)(autoFocusable) | ||
: (0, firstFocus_1.pickFirstFocus)((0, DOMutils_1.filterAutoFocusable)(innerNodes)), | ||
}; | ||
@@ -57,0 +66,0 @@ } |
@@ -5,3 +5,3 @@ import * as constants from './constants'; | ||
import { getFocusMerge as focusMerge } from './focusMerge'; | ||
import { getFocusabledIn } from './focusables'; | ||
import { getFocusabledIn, getFocusableIn } from './focusables'; | ||
import { setFocus } from './setFocus'; | ||
@@ -12,3 +12,3 @@ import { focusNextElement, focusPrevElement } from './sibling'; | ||
import { getActiveElement } from './utils/getActiveElement'; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export { tabHook, focusInside, focusIsHidden, focusMerge, getFocusableIn, getFocusabledIn, constants, getAllAffectedNodes, focusNextElement, focusPrevElement, getActiveElement, }; | ||
export default setFocus; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getActiveElement = exports.focusPrevElement = exports.focusNextElement = exports.getAllAffectedNodes = exports.constants = exports.getFocusabledIn = exports.focusMerge = exports.focusIsHidden = exports.focusInside = exports.tabHook = void 0; | ||
exports.getActiveElement = exports.focusPrevElement = exports.focusNextElement = exports.getAllAffectedNodes = exports.constants = exports.getFocusabledIn = exports.getFocusableIn = exports.focusMerge = exports.focusIsHidden = exports.focusInside = exports.tabHook = void 0; | ||
var tslib_1 = require("tslib"); | ||
@@ -15,2 +15,3 @@ var constants = (0, tslib_1.__importStar)(require("./constants")); | ||
Object.defineProperty(exports, "getFocusabledIn", { enumerable: true, get: function () { return focusables_1.getFocusabledIn; } }); | ||
Object.defineProperty(exports, "getFocusableIn", { enumerable: true, get: function () { return focusables_1.getFocusableIn; } }); | ||
var setFocus_1 = require("./setFocus"); | ||
@@ -27,1 +28,2 @@ var sibling_1 = require("./sibling"); | ||
exports.default = setFocus_1.setFocus; | ||
// |
@@ -5,3 +5,10 @@ export declare const focusOn: (target: Element | HTMLFrameElement | HTMLElement, focusOptions?: FocusOptions | undefined) => void; | ||
} | ||
/** | ||
* Sets focus at a given node. The last focused element will help to determine which element(first or last) should be focused. | ||
* HTML markers (see {@link import('./constants').FOCUS_AUTO} constants) can control autofocus | ||
* @param topNode | ||
* @param lastNode | ||
* @param options | ||
*/ | ||
export declare const setFocus: (topNode: HTMLElement, lastNode: Element, options?: FocusLockFocusOptions) => void; | ||
export {}; |
@@ -16,2 +16,9 @@ "use strict"; | ||
var lockDisabled = false; | ||
/** | ||
* Sets focus at a given node. The last focused element will help to determine which element(first or last) should be focused. | ||
* HTML markers (see {@link import('./constants').FOCUS_AUTO} constants) can control autofocus | ||
* @param topNode | ||
* @param lastNode | ||
* @param options | ||
*/ | ||
var setFocus = function (topNode, lastNode, options) { | ||
@@ -25,2 +32,3 @@ if (options === void 0) { options = {}; } | ||
if (guardCount > 2) { | ||
// tslint:disable-next-line:no-console | ||
console.error('FocusLock: focus-fighting detected. Only one focus management system could be active. ' + | ||
@@ -27,0 +35,0 @@ 'See https://github.com/theKashey/focus-lock/#focus-fighting'); |
interface FocusNextOptions { | ||
/** | ||
* the component to "scope" focus in | ||
* @default document.body | ||
*/ | ||
scope?: HTMLElement | HTMLDocument; | ||
/** | ||
* enables cycling inside the scope | ||
* @default true | ||
*/ | ||
cycle?: boolean; | ||
/** | ||
* options for focus action to control it more precisely (ie. `{ preventScroll: true }`) | ||
*/ | ||
focusOptions?: FocusOptions; | ||
} | ||
/** | ||
* focuses next element in the tab-order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export declare const focusNextElement: (baseElement: Element, options?: FocusNextOptions) => void; | ||
/** | ||
* focuses prev element in the tab order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
export declare const focusPrevElement: (baseElement: Element, options?: FocusNextOptions) => void; | ||
export {}; |
@@ -7,3 +7,3 @@ "use strict"; | ||
var getRelativeFocusable = function (element, scope) { | ||
if (!element || !scope || !scope.contains(element)) { | ||
if (!element || !scope || !(0, DOMutils_1.contains)(scope, element)) { | ||
return {}; | ||
@@ -32,2 +32,7 @@ } | ||
}; | ||
/** | ||
* focuses next element in the tab-order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
var focusNextElement = function (baseElement, options) { | ||
@@ -43,2 +48,7 @@ if (options === void 0) { options = {}; } | ||
exports.focusNextElement = focusNextElement; | ||
/** | ||
* focuses prev element in the tab order | ||
* @param baseElement - common parent to scope active element search or tab cycle order | ||
* @param {FocusNextOptions} [options] - focus options | ||
*/ | ||
var focusPrevElement = function (baseElement, options) { | ||
@@ -45,0 +55,0 @@ if (options === void 0) { options = {}; } |
export declare const NEW_FOCUS = "NEW_FOCUS"; | ||
/** | ||
* Main solver for the "find next focus" question | ||
* @param innerNodes | ||
* @param outerNodes | ||
* @param activeElement | ||
* @param lastNode | ||
* @returns {number|string|undefined|*} | ||
*/ | ||
export declare const newFocus: (innerNodes: HTMLElement[], outerNodes: HTMLElement[], activeElement: HTMLElement | undefined, lastNode: HTMLElement | null) => number | undefined | typeof NEW_FOCUS; |
@@ -8,2 +8,10 @@ "use strict"; | ||
exports.NEW_FOCUS = 'NEW_FOCUS'; | ||
/** | ||
* Main solver for the "find next focus" question | ||
* @param innerNodes | ||
* @param outerNodes | ||
* @param activeElement | ||
* @param lastNode | ||
* @returns {number|string|undefined|*} | ||
*/ | ||
var newFocus = function (innerNodes, outerNodes, activeElement, lastNode) { | ||
@@ -14,2 +22,3 @@ var cnt = innerNodes.length; | ||
var isOnGuard = (0, is_1.isGuard)(activeElement); | ||
// focus is inside | ||
if (activeElement && innerNodes.indexOf(activeElement) >= 0) { | ||
@@ -29,23 +38,31 @@ return undefined; | ||
var returnLastNode = (0, firstFocus_1.pickFocusable)(innerNodes, cnt - 1); | ||
// new focus | ||
if (activeIndex === -1 || lastNodeInside === -1) { | ||
return exports.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) { | ||
@@ -57,4 +74,5 @@ if (Math.abs(indexDiff) > 1) { | ||
} | ||
// do nothing | ||
return undefined; | ||
}; | ||
exports.newFocus = newFocus; |
@@ -5,2 +5,5 @@ declare const _default: { | ||
}; | ||
/** | ||
* @deprecated does nothing | ||
*/ | ||
export default _default; |
"use strict"; | ||
/* eslint-disable */ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* @deprecated does nothing | ||
*/ | ||
exports.default = { | ||
@@ -4,0 +8,0 @@ attach: function () { }, |
@@ -0,1 +1,6 @@ | ||
/** | ||
* returns all "focus containers" inside a given node | ||
* @param node | ||
* @returns {T} | ||
*/ | ||
export declare const getAllAffectedNodes: (node: Element | Element[]) => Element[]; |
@@ -6,2 +6,9 @@ "use strict"; | ||
var array_1 = require("./array"); | ||
/** | ||
* in case of multiple nodes nested inside each other | ||
* keeps only top ones | ||
* this is O(nlogn) | ||
* @param nodes | ||
* @returns {*} | ||
*/ | ||
var filterNested = function (nodes) { | ||
@@ -13,2 +20,3 @@ var contained = new Set(); | ||
var position = nodes[i].compareDocumentPosition(nodes[j]); | ||
/* eslint-disable no-bitwise */ | ||
if ((position & Node.DOCUMENT_POSITION_CONTAINED_BY) > 0) { | ||
@@ -20,2 +28,3 @@ contained.add(j); | ||
} | ||
/* eslint-enable */ | ||
} | ||
@@ -25,5 +34,15 @@ } | ||
}; | ||
/** | ||
* finds top most parent for a node | ||
* @param node | ||
* @returns {*} | ||
*/ | ||
var getTopParent = function (node) { | ||
return node.parentNode ? getTopParent(node.parentNode) : node; | ||
}; | ||
/** | ||
* returns all "focus containers" inside a given node | ||
* @param node | ||
* @returns {T} | ||
*/ | ||
var getAllAffectedNodes = function (node) { | ||
@@ -30,0 +49,0 @@ var nodes = (0, array_1.asArray)(node); |
"use strict"; | ||
/* | ||
IE11 support | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -3,0 +6,0 @@ exports.asArray = exports.toArray = void 0; |
export declare const correctNode: (node: HTMLElement, nodes: HTMLElement[]) => HTMLElement; | ||
/** | ||
* giving a set of radio inputs keeps only selected (tabbable) ones | ||
* @param nodes | ||
*/ | ||
export declare const correctNodes: (nodes: HTMLElement[]) => HTMLElement[]; |
@@ -18,7 +18,13 @@ "use strict"; | ||
exports.correctNode = correctNode; | ||
/** | ||
* giving a set of radio inputs keeps only selected (tabbable) ones | ||
* @param nodes | ||
*/ | ||
var correctNodes = function (nodes) { | ||
// IE11 has no Set(array) constructor | ||
var resultSet = new Set(); | ||
nodes.forEach(function (node) { return resultSet.add((0, exports.correctNode)(node, nodes)); }); | ||
// using filter to support IE11 | ||
return nodes.filter(function (node) { return resultSet.has(node); }); | ||
}; | ||
exports.correctNodes = correctNodes; |
import { VisibilityCache } from './is'; | ||
import { NodeIndex } from './tabOrder'; | ||
/** | ||
* given list of focusable elements keeps the ones user can interact with | ||
* @param nodes | ||
* @param visibilityCache | ||
*/ | ||
export declare const filterFocusable: (nodes: HTMLElement[], visibilityCache: VisibilityCache) => HTMLElement[]; | ||
export declare const filterAutoFocusable: (nodes: HTMLElement[], cache?: VisibilityCache) => HTMLElement[]; | ||
/** | ||
* only tabbable ones | ||
* (but with guards which would be ignored) | ||
*/ | ||
export declare const getTabbableNodes: (topNodes: Element[], visibilityCache: VisibilityCache, withGuards?: boolean | undefined) => NodeIndex[]; | ||
/** | ||
* actually anything "focusable", not only tabbable | ||
* (without guards, as long as they are not expected to be focused) | ||
*/ | ||
export declare const getAllTabbableNodes: (topNodes: Element[], visibilityCache: VisibilityCache) => NodeIndex[]; | ||
/** | ||
* return list of nodes which are expected to be auto-focused | ||
* @param topNode | ||
* @param visibilityCache | ||
*/ | ||
export declare const parentAutofocusables: (topNode: Element, visibilityCache: VisibilityCache) => Element[]; | ||
export declare const contains: (scope: Element | ShadowRoot, element: Element) => boolean; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.parentAutofocusables = exports.getAllTabbableNodes = exports.getTabbableNodes = exports.filterFocusable = void 0; | ||
exports.contains = exports.parentAutofocusables = exports.getAllTabbableNodes = exports.getTabbableNodes = exports.filterAutoFocusable = exports.filterFocusable = void 0; | ||
var array_1 = require("./array"); | ||
@@ -8,2 +8,7 @@ var is_1 = require("./is"); | ||
var tabUtils_1 = require("./tabUtils"); | ||
/** | ||
* given list of focusable elements keeps the ones user can interact with | ||
* @param nodes | ||
* @param visibilityCache | ||
*/ | ||
var filterFocusable = function (nodes, visibilityCache) { | ||
@@ -15,2 +20,11 @@ return (0, array_1.toArray)(nodes) | ||
exports.filterFocusable = filterFocusable; | ||
var filterAutoFocusable = function (nodes, cache) { | ||
if (cache === void 0) { cache = new Map(); } | ||
return (0, array_1.toArray)(nodes).filter(function (node) { return (0, is_1.isAutoFocusAllowedCached)(cache, node); }); | ||
}; | ||
exports.filterAutoFocusable = filterAutoFocusable; | ||
/** | ||
* only tabbable ones | ||
* (but with guards which would be ignored) | ||
*/ | ||
var getTabbableNodes = function (topNodes, visibilityCache, withGuards) { | ||
@@ -20,2 +34,6 @@ return (0, tabOrder_1.orderByTabIndex)((0, exports.filterFocusable)((0, tabUtils_1.getFocusables)(topNodes, withGuards), visibilityCache), true, withGuards); | ||
exports.getTabbableNodes = getTabbableNodes; | ||
/** | ||
* actually anything "focusable", not only tabbable | ||
* (without guards, as long as they are not expected to be focused) | ||
*/ | ||
var getAllTabbableNodes = function (topNodes, visibilityCache) { | ||
@@ -25,2 +43,7 @@ return (0, tabOrder_1.orderByTabIndex)((0, exports.filterFocusable)((0, tabUtils_1.getFocusables)(topNodes), visibilityCache), false); | ||
exports.getAllTabbableNodes = getAllTabbableNodes; | ||
/** | ||
* return list of nodes which are expected to be auto-focused | ||
* @param topNode | ||
* @param visibilityCache | ||
*/ | ||
var parentAutofocusables = function (topNode, visibilityCache) { | ||
@@ -30,1 +53,10 @@ return (0, exports.filterFocusable)((0, tabUtils_1.getParentAutofocusables)(topNode), visibilityCache); | ||
exports.parentAutofocusables = parentAutofocusables; | ||
/* | ||
* Determines if element is contained in scope, including nested shadow DOMs | ||
*/ | ||
var contains = function (scope, element) { | ||
return ((scope.shadowRoot | ||
? (0, exports.contains)(scope.shadowRoot, element) | ||
: scope.contains(element)) || Array.from(scope.children).some(function (child) { return (0, exports.contains)(child, element); })); | ||
}; | ||
exports.contains = contains; |
@@ -0,1 +1,4 @@ | ||
/** | ||
* returns active element from document or from nested shadowdoms | ||
*/ | ||
export declare const getActiveElement: () => HTMLElement | undefined; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getActiveElement = void 0; | ||
var getNestedShadowActiveElement = function (shadowRoot) { | ||
return shadowRoot.activeElement | ||
? shadowRoot.activeElement.shadowRoot | ||
? getNestedShadowActiveElement(shadowRoot.activeElement.shadowRoot) | ||
: shadowRoot.activeElement | ||
: undefined; | ||
}; | ||
/** | ||
* returns active element from document or from nested shadowdoms | ||
*/ | ||
var getActiveElement = function () { | ||
return (document.activeElement | ||
? document.activeElement.shadowRoot | ||
? document.activeElement.shadowRoot.activeElement | ||
? getNestedShadowActiveElement(document.activeElement.shadowRoot) | ||
: document.activeElement | ||
: undefined); | ||
: undefined); // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
}; | ||
exports.getActiveElement = getActiveElement; |
export declare type VisibilityCache = Map<Element | undefined, boolean>; | ||
export declare const isVisibleCached: (visibilityCache: VisibilityCache, node: Element | undefined) => boolean; | ||
export declare const isAutoFocusAllowedCached: (cache: VisibilityCache, node: Element | undefined) => boolean; | ||
export declare const getDataset: (node: Element) => HTMLElement['dataset'] | undefined; | ||
@@ -8,4 +9,5 @@ export declare const isHTMLButtonElement: (node: Element) => node is HTMLInputElement; | ||
export declare const notHiddenInput: (node: Element) => boolean; | ||
export declare const isAutoFocusAllowed: (node: Element) => boolean; | ||
export declare const isGuard: (node: Element | undefined) => boolean; | ||
export declare const isNotAGuard: (node: Element | undefined) => boolean; | ||
export declare const isDefined: <T>(x: T | null | undefined) => x is T; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isDefined = exports.isNotAGuard = exports.isGuard = exports.notHiddenInput = exports.isRadioElement = exports.isHTMLInputElement = exports.isHTMLButtonElement = exports.getDataset = exports.isVisibleCached = void 0; | ||
exports.isDefined = exports.isNotAGuard = exports.isGuard = exports.isAutoFocusAllowed = exports.notHiddenInput = exports.isRadioElement = exports.isHTMLInputElement = exports.isHTMLButtonElement = exports.getDataset = exports.isAutoFocusAllowedCached = exports.isVisibleCached = void 0; | ||
var constants_1 = require("../constants"); | ||
var isElementHidden = function (node) { | ||
// we can measure only "elements" | ||
// consider others as "visible" | ||
if (node.nodeType !== Node.ELEMENT_NODE) { | ||
@@ -14,11 +17,15 @@ return false; | ||
}; | ||
var getParentNode = function (node) { | ||
// DOCUMENT_FRAGMENT_NODE can also point on ShadowRoot. In this case .host will point on the next node | ||
return node.parentNode && node.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE | ||
? // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
node.parentNode.host | ||
: node.parentNode; | ||
}; | ||
var isTopNode = function (node) { | ||
// @ts-ignore | ||
return node === document || (node && node.nodeType === Node.DOCUMENT_NODE); | ||
}; | ||
var isVisibleUncached = function (node, checkParent) { | ||
return !node || | ||
node === document || | ||
(node && node.nodeType === Node.DOCUMENT_NODE) || | ||
(!isElementHidden(node) && | ||
checkParent(node.parentNode && node.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE | ||
? | ||
node.parentNode.host | ||
: node.parentNode)); | ||
return !node || isTopNode(node) || (!isElementHidden(node) && checkParent(getParentNode(node))); | ||
}; | ||
@@ -35,3 +42,17 @@ var isVisibleCached = function (visibilityCache, node) { | ||
exports.isVisibleCached = isVisibleCached; | ||
var isAutoFocusAllowedUncached = function (node, checkParent) { | ||
return node && !isTopNode(node) ? ((0, exports.isAutoFocusAllowed)(node) ? checkParent(getParentNode(node)) : false) : true; | ||
}; | ||
var isAutoFocusAllowedCached = function (cache, node) { | ||
var cached = cache.get(node); | ||
if (cached !== undefined) { | ||
return cached; | ||
} | ||
var result = isAutoFocusAllowedUncached(node, exports.isAutoFocusAllowedCached.bind(undefined, cache)); | ||
cache.set(node, result); | ||
return result; | ||
}; | ||
exports.isAutoFocusAllowedCached = isAutoFocusAllowedCached; | ||
var getDataset = function (node) { | ||
// @ts-ignore | ||
return node.dataset; | ||
@@ -49,6 +70,10 @@ }; | ||
var notHiddenInput = function (node) { | ||
return !(((0, exports.isHTMLInputElement)(node) || (0, exports.isHTMLButtonElement)(node)) && (node.type === 'hidden' || node.disabled)) && | ||
!node.ariaDisabled; | ||
return !(((0, exports.isHTMLInputElement)(node) || (0, exports.isHTMLButtonElement)(node)) && (node.type === 'hidden' || node.disabled)); | ||
}; | ||
exports.notHiddenInput = notHiddenInput; | ||
var isAutoFocusAllowed = function (node) { | ||
var attribute = node.getAttribute(constants_1.FOCUS_NO_AUTOFOCUS); | ||
return ![true, 'true', ''].includes(attribute); | ||
}; | ||
exports.isAutoFocusAllowed = isAutoFocusAllowed; | ||
var isGuard = function (node) { var _a; return Boolean(node && ((_a = (0, exports.getDataset)(node)) === null || _a === void 0 ? void 0 : _a.focusGuard)); }; | ||
@@ -55,0 +80,0 @@ exports.isGuard = isGuard; |
import { VisibilityCache } from './is'; | ||
/** | ||
* finds a parent for both nodeA and nodeB | ||
* @param nodeA | ||
* @param nodeB | ||
* @returns {boolean|*} | ||
*/ | ||
export declare const getCommonParent: (nodeA: Element, nodeB: Element) => Element | false; | ||
export declare const getTopCommonParent: (baseActiveElement: Element | Element[], leftEntry: Element | Element[], rightEntries: Element[]) => Element; | ||
/** | ||
* return list of nodes which are expected to be autofocused inside a given top nodes | ||
* @param entries | ||
* @param visibilityCache | ||
*/ | ||
export declare const allParentAutofocusables: (entries: Element[], visibilityCache: VisibilityCache) => Element[]; |
@@ -5,2 +5,3 @@ "use strict"; | ||
var DOMutils_1 = require("./DOMutils"); | ||
var DOMutils_2 = require("./DOMutils"); | ||
var array_1 = require("./array"); | ||
@@ -11,9 +12,16 @@ var getParents = function (node, parents) { | ||
if (node.parentNode) { | ||
getParents(node.parentNode, parents); | ||
getParents(node.parentNode.host || node.parentNode, parents); | ||
} | ||
return parents; | ||
}; | ||
/** | ||
* finds a parent for both nodeA and nodeB | ||
* @param nodeA | ||
* @param nodeB | ||
* @returns {boolean|*} | ||
*/ | ||
var getCommonParent = function (nodeA, nodeB) { | ||
var parentsA = getParents(nodeA); | ||
var parentsB = getParents(nodeB); | ||
// tslint:disable-next-line:prefer-for-of | ||
for (var i = 0; i < parentsA.length; i += 1) { | ||
@@ -38,3 +46,3 @@ var currentParent = parentsA[i]; | ||
if (common) { | ||
if (!topCommon || common.contains(topCommon)) { | ||
if (!topCommon || (0, DOMutils_2.contains)(common, topCommon)) { | ||
topCommon = common; | ||
@@ -48,5 +56,11 @@ } | ||
}); | ||
// TODO: add assert here? | ||
return topCommon; | ||
}; | ||
exports.getTopCommonParent = getTopCommonParent; | ||
/** | ||
* return list of nodes which are expected to be autofocused inside a given top nodes | ||
* @param entries | ||
* @param visibilityCache | ||
*/ | ||
var allParentAutofocusables = function (entries, visibilityCache) { | ||
@@ -53,0 +67,0 @@ return entries.reduce(function (acc, node) { return acc.concat((0, DOMutils_1.parentAutofocusables)(node, visibilityCache)); }, []); |
@@ -0,1 +1,4 @@ | ||
/** | ||
* list of the object to be considered as focusable | ||
*/ | ||
export declare const tabbables: string[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.tabbables = void 0; | ||
/** | ||
* list of the object to be considered as focusable | ||
*/ | ||
exports.tabbables = [ | ||
@@ -9,2 +12,4 @@ 'button:enabled', | ||
'input:enabled', | ||
// elements with explicit roles will also use explicit tabindex | ||
// '[role="button"]', | ||
'a[href]', | ||
@@ -11,0 +16,0 @@ 'area[href]', |
export declare const getFocusables: (parents: Element[], withGuards?: boolean | undefined) => HTMLElement[]; | ||
/** | ||
* return a list of focusable nodes within an area marked as "auto-focusable" | ||
* @param parent | ||
*/ | ||
export declare const getParentAutofocusables: (parent: Element) => HTMLElement[]; |
@@ -9,5 +9,15 @@ "use strict"; | ||
var queryGuardTabbables = "".concat(queryTabbables, ", [data-focus-guard]"); | ||
var getFocusablesWithShadowDom = function (parent, withGuards) { | ||
var _a; | ||
return (0, array_1.toArray)(((_a = parent.shadowRoot) === null || _a === void 0 ? void 0 : _a.children) || parent.children).reduce(function (acc, child) { | ||
return acc.concat(child.matches(withGuards ? queryGuardTabbables : queryTabbables) ? [child] : [], getFocusablesWithShadowDom(child)); | ||
}, []); | ||
}; | ||
var getFocusables = function (parents, withGuards) { | ||
return parents.reduce(function (acc, parent) { | ||
return acc.concat((0, array_1.toArray)(parent.querySelectorAll(withGuards ? queryGuardTabbables : queryTabbables)), parent.parentNode | ||
return acc.concat( | ||
// add all tabbables inside and within shadow DOMs in DOM order | ||
getFocusablesWithShadowDom(parent, withGuards), | ||
// add if node is tabbable itself | ||
parent.parentNode | ||
? (0, array_1.toArray)(parent.parentNode.querySelectorAll(queryTabbables)).filter(function (node) { return node === parent; }) | ||
@@ -18,2 +28,6 @@ : []); | ||
exports.getFocusables = getFocusables; | ||
/** | ||
* return a list of focusable nodes within an area marked as "auto-focusable" | ||
* @param parent | ||
*/ | ||
var getParentAutofocusables = function (parent) { | ||
@@ -20,0 +34,0 @@ var parentFocus = parent.querySelectorAll("[".concat(constants_1.FOCUS_AUTO, "]")); |
{ | ||
"name": "focus-lock", | ||
"version": "0.10.2", | ||
"version": "0.11.0", | ||
"description": "DOM trap for a focus", | ||
@@ -5,0 +5,0 @@ "main": "dist/es5/index.js", |
@@ -51,2 +51,17 @@ # focus-lock | ||
# Declarative control | ||
`Focus-lock` provides not only API to be called by some other scripts, but also a way one can leave instructions inside HTML markup | ||
to amend focus behavior in a desired way. | ||
These are data-attributes one can add on the elements: | ||
- `data-focus-lock` to create a focus group (scattered focus) | ||
- `data-focus-lock-disabled` marks such group as disables and removes from the list | ||
- `data-no-focus-lock` focus-lock will ignore focus inside marked area | ||
- `data-autofocus-inside` focus-lock will try to autofocus elements within selected area | ||
- `data-no-autofocus` focus-lock will not autofocus any node within marked area | ||
These markers are available as `import * as markers from 'focus-lock/constants'` | ||
# Focus fighting | ||
@@ -53,0 +68,0 @@ |
126333
131
2875
89