Socket
Socket
Sign inDemoInstall

focus-lock

Package Overview
Dependencies
1
Maintainers
1
Versions
56
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.10.2 to 0.11.0

CHANGELOG.md

18

dist/es2015/constants.d.ts

@@ -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;

3

dist/es2015/focusInside.d.ts

@@ -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;
};

23

dist/es2015/focusMerge.js
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 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc