jobiqo-cl
Version:
[](https://circleci.com/gh/jobiqo/jobiqo-cl)
203 lines (166 loc) • 5.97 kB
JavaScript
var candidateSelectors = [
'input',
'select',
'textarea',
'a[href]',
'button',
'[tabindex]',
'audio[controls]',
'video[controls]',
'[contenteditable]:not([contenteditable="false"])',
];
var candidateSelector = candidateSelectors.join(',');
var matches = typeof Element === 'undefined'
? function () {}
: Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
function tabbable(el, options) {
options = options || {};
var elementDocument = el.ownerDocument || el;
var regularTabbables = [];
var orderedTabbables = [];
var untouchabilityChecker = new UntouchabilityChecker(elementDocument);
var candidates = el.querySelectorAll(candidateSelector);
if (options.includeContainer) {
if (matches.call(el, candidateSelector)) {
candidates = Array.prototype.slice.apply(candidates);
candidates.unshift(el);
}
}
var i, candidate, candidateTabindex;
for (i = 0; i < candidates.length; i++) {
candidate = candidates[i];
if (!isNodeMatchingSelectorTabbable(candidate, untouchabilityChecker)) continue;
candidateTabindex = getTabindex(candidate);
if (candidateTabindex === 0) {
regularTabbables.push(candidate);
} else {
orderedTabbables.push({
documentOrder: i,
tabIndex: candidateTabindex,
node: candidate,
});
}
}
var tabbableNodes = orderedTabbables
.sort(sortOrderedTabbables)
.map(function(a) { return a.node })
.concat(regularTabbables);
return tabbableNodes;
}
tabbable.isTabbable = isTabbable;
tabbable.isFocusable = isFocusable;
function isNodeMatchingSelectorTabbable(node, untouchabilityChecker) {
if (
!isNodeMatchingSelectorFocusable(node, untouchabilityChecker)
|| isNonTabbableRadio(node)
|| getTabindex(node) < 0
) {
return false;
}
return true;
}
function isTabbable(node, untouchabilityChecker) {
if (!node) throw new Error('No node provided');
if (matches.call(node, candidateSelector) === false) return false;
return isNodeMatchingSelectorTabbable(node, untouchabilityChecker);
}
function isNodeMatchingSelectorFocusable(node, untouchabilityChecker) {
untouchabilityChecker = untouchabilityChecker || new UntouchabilityChecker(node.ownerDocument || node);
if (
node.disabled
|| isHiddenInput(node)
|| untouchabilityChecker.isUntouchable(node)
) {
return false;
}
return true;
}
var focusableCandidateSelector = candidateSelectors.concat('iframe').join(',');
function isFocusable(node, untouchabilityChecker) {
if (!node) throw new Error('No node provided');
if (matches.call(node, focusableCandidateSelector) === false) return false;
return isNodeMatchingSelectorFocusable(node, untouchabilityChecker);
}
function getTabindex(node) {
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10);
if (!isNaN(tabindexAttr)) return tabindexAttr;
// Browsers do not return `tabIndex` correctly for contentEditable nodes;
// so if they don't have a tabindex attribute specifically set, assume it's 0.
if (isContentEditable(node)) return 0;
return node.tabIndex;
}
function sortOrderedTabbables(a, b) {
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex;
}
// Array.prototype.find not available in IE.
function find(list, predicate) {
for (var i = 0, length = list.length; i < length; i++) {
if (predicate(list[i])) return list[i];
}
}
function isContentEditable(node) {
return node.contentEditable === 'true';
}
function isInput(node) {
return node.tagName === 'INPUT';
}
function isHiddenInput(node) {
return isInput(node) && node.type === 'hidden';
}
function isRadio(node) {
return isInput(node) && node.type === 'radio';
}
function isNonTabbableRadio(node) {
return isRadio(node) && !isTabbableRadio(node);
}
function getCheckedRadio(nodes) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].checked) {
return nodes[i];
}
}
}
function isTabbableRadio(node) {
if (!node.name) return true;
// This won't account for the edge case where you have radio groups with the same
// in separate forms on the same page.
var radioSet = node.ownerDocument.querySelectorAll('input[type="radio"][name="' + node.name + '"]');
var checked = getCheckedRadio(radioSet);
return !checked || checked === node;
}
// An element is "untouchable" if *it or one of its ancestors* has
// `visibility: hidden` or `display: none`.
function UntouchabilityChecker(elementDocument) {
this.doc = elementDocument;
// Node cache must be refreshed on every check, in case
// the content of the element has changed. The cache contains tuples
// mapping nodes to their boolean result.
this.cache = [];
}
// getComputedStyle accurately reflects `visibility: hidden` of ancestors
// but not `display: none`, so we need to recursively check parents.
UntouchabilityChecker.prototype.hasDisplayNone = function hasDisplayNone(node, nodeComputedStyle) {
if (node.nodeType !== Node.ELEMENT_NODE) return false;
// Search for a cached result.
var cached = find(this.cache, function(item) {
return item === node;
});
if (cached) return cached[1];
nodeComputedStyle = nodeComputedStyle || this.doc.defaultView.getComputedStyle(node);
var result = false;
if (nodeComputedStyle.display === 'none') {
result = true;
} else if (node.parentNode) {
result = this.hasDisplayNone(node.parentNode);
}
this.cache.push([node, result]);
return result;
};
UntouchabilityChecker.prototype.isUntouchable = function isUntouchable(node) {
if (node === this.doc.documentElement) return false;
var computedStyle = this.doc.defaultView.getComputedStyle(node);
if (this.hasDisplayNone(node, computedStyle)) return true;
return computedStyle.visibility === 'hidden';
};
var tabbable_1 = tabbable;
export { tabbable_1 as __moduleExports };