UNPKG

tabbable

Version:

Returns an array of all tabbable DOM nodes within a containing node.

231 lines (180 loc) 7.14 kB
/*! * tabbable 5.2.0 * @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details']; var candidateSelector = /* #__PURE__ */candidateSelectors.join(','); var matches = typeof Element === 'undefined' ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; var getCandidates = function getCandidates(el, includeContainer, filter) { var candidates = Array.prototype.slice.apply(el.querySelectorAll(candidateSelector)); if (includeContainer && matches.call(el, candidateSelector)) { candidates.unshift(el); } candidates = candidates.filter(filter); return candidates; }; var isContentEditable = function isContentEditable(node) { return node.contentEditable === 'true'; }; var getTabindex = 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; } // in Chrome, <details/>, <audio controls/> and <video controls/> elements get a default // `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM, // yet they are still part of the regular tab order; in FF, they get a default // `tabIndex` of 0; since Chrome still puts those elements in the regular tab // order, consider their tab index to be 0. if ((node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { return 0; } return node.tabIndex; }; var sortOrderedTabbables = function sortOrderedTabbables(a, b) { return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex; }; var isInput = function isInput(node) { return node.tagName === 'INPUT'; }; var isHiddenInput = function isHiddenInput(node) { return isInput(node) && node.type === 'hidden'; }; var isDetailsWithSummary = function isDetailsWithSummary(node) { var r = node.tagName === 'DETAILS' && Array.prototype.slice.apply(node.children).some(function (child) { return child.tagName === 'SUMMARY'; }); return r; }; var getCheckedRadio = function getCheckedRadio(nodes, form) { for (var i = 0; i < nodes.length; i++) { if (nodes[i].checked && nodes[i].form === form) { return nodes[i]; } } }; var isTabbableRadio = function isTabbableRadio(node) { if (!node.name) { return true; } var radioScope = node.form || node.ownerDocument; var queryRadios = function queryRadios(name) { return radioScope.querySelectorAll('input[type="radio"][name="' + name + '"]'); }; var radioSet; if (typeof window !== 'undefined' && typeof window.CSS !== 'undefined' && typeof window.CSS.escape === 'function') { radioSet = queryRadios(window.CSS.escape(node.name)); } else { try { radioSet = queryRadios(node.name); } catch (err) { // eslint-disable-next-line no-console console.error('Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s', err.message); return false; } } var checked = getCheckedRadio(radioSet, node.form); return !checked || checked === node; }; var isRadio = function isRadio(node) { return isInput(node) && node.type === 'radio'; }; var isNonTabbableRadio = function isNonTabbableRadio(node) { return isRadio(node) && !isTabbableRadio(node); }; var isHidden = function isHidden(node, displayCheck) { if (getComputedStyle(node).visibility === 'hidden') { return true; } var isDirectSummary = matches.call(node, 'details>summary:first-of-type'); var nodeUnderDetails = isDirectSummary ? node.parentElement : node; if (matches.call(nodeUnderDetails, 'details:not([open]) *')) { return true; } if (!displayCheck || displayCheck === 'full') { while (node) { if (getComputedStyle(node).display === 'none') { return true; } node = node.parentElement; } } else if (displayCheck === 'non-zero-area') { var _node$getBoundingClie = node.getBoundingClientRect(), width = _node$getBoundingClie.width, height = _node$getBoundingClie.height; return width === 0 && height === 0; } return false; }; var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(options, node) { if (node.disabled || isHiddenInput(node) || isHidden(node, options.displayCheck) || /* For a details element with a summary, the summary element gets the focused */ isDetailsWithSummary(node)) { return false; } return true; }; var isNodeMatchingSelectorTabbable = function isNodeMatchingSelectorTabbable(options, node) { if (!isNodeMatchingSelectorFocusable(options, node) || isNonTabbableRadio(node) || getTabindex(node) < 0) { return false; } return true; }; var tabbable = function tabbable(el, options) { options = options || {}; var regularTabbables = []; var orderedTabbables = []; var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); candidates.forEach(function (candidate, i) { var 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; }; var focusable = function focusable(el, options) { options = options || {}; var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); return candidates; }; var isTabbable = function isTabbable(node, options) { options = options || {}; if (!node) { throw new Error('No node provided'); } if (matches.call(node, candidateSelector) === false) { return false; } return isNodeMatchingSelectorTabbable(options, node); }; var focusableCandidateSelector = /* #__PURE__ */candidateSelectors.concat('iframe').join(','); var isFocusable = function isFocusable(node, options) { options = options || {}; if (!node) { throw new Error('No node provided'); } if (matches.call(node, focusableCandidateSelector) === false) { return false; } return isNodeMatchingSelectorFocusable(options, node); }; exports.focusable = focusable; exports.isFocusable = isFocusable; exports.isTabbable = isTabbable; exports.tabbable = tabbable; //# sourceMappingURL=index.js.map