src/query/focusable.strict.js
// https://www.w3.org/TR/html5/editing.html#focusable
// https://www.w3.org/WAI/PF/aria-practices/#keyboard
import isFocusable from '../is/focusable';
import isFocusRelevant from '../is/focus-relevant';
import getDocument from '../util/get-document';
function createFilter(condition) {
// see https://developer.mozilla.org/en-US/docs/Web/API/NodeFilter
const filter = function(node) {
if (node.shadowRoot) {
// return ShadowRoot elements regardless of them being focusable,
// so they can be walked recursively later
return NodeFilter.FILTER_ACCEPT;
}
if (condition(node)) {
// finds elements that could have been found by document.querySelectorAll()
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
};
// IE requires a function, Browsers require {acceptNode: function}
// see http://www.bennadel.com/blog/2607-finding-html-comment-nodes-in-the-dom-using-treewalker.htm
filter.acceptNode = filter;
return filter;
}
const PossiblyFocusableFilter = createFilter(isFocusRelevant);
export default function queryFocusableStrict({
context,
includeContext,
includeOnlyTabbable,
strategy,
} = {}) {
if (!context) {
context = document.documentElement;
}
const _isFocusable = isFocusable.rules.except({
onlyTabbable: includeOnlyTabbable,
});
const _document = getDocument(context);
// see https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker
const walker = _document.createTreeWalker(
// root element to start search in
context,
// element type filter
NodeFilter.SHOW_ELEMENT,
// custom NodeFilter filter
strategy === 'all' ? PossiblyFocusableFilter : createFilter(_isFocusable),
// deprecated, but IE requires it
false
);
let list = [];
while (walker.nextNode()) {
if (walker.currentNode.shadowRoot) {
if (_isFocusable(walker.currentNode)) {
list.push(walker.currentNode);
}
list = list.concat(queryFocusableStrict({
context: walker.currentNode.shadowRoot,
includeOnlyTabbable,
strategy,
}));
} else {
list.push(walker.currentNode);
}
}
// add context if requested and focusable
if (includeContext) {
if (strategy === 'all') {
if (isFocusRelevant(context)) {
list.unshift(context);
}
} else if (_isFocusable(context)) {
list.unshift(context);
}
}
return list;
}