UNPKG

@nex-ui/react

Version:

🎉 A beautiful, modern, and reliable React component library.

78 lines (76 loc) • 3.07 kB
// https://github.com/focus-trap/tabbable/blob/master/src/index.js#L676 const candidateSelectors = [ 'input:not([inert])', 'select:not([inert])', 'textarea:not([inert])', 'a[href]:not([inert])', 'button:not([inert])', '[tabindex]:not(slot):not([inert])', 'audio[controls]:not([inert])', 'video[controls]:not([inert])', '[contenteditable]:not([contenteditable="false"]):not([inert])', 'details>summary:first-of-type:not([inert])', 'details:not([inert])' ]; const candidateSelector = candidateSelectors.join(','); function isNonTabbableRadio(node) { if (node.tagName !== 'INPUT' || node.type !== 'radio') { return false; } if (!node.name) { return false; } const getRadio = (selector)=>node.ownerDocument.querySelector(`input[type="radio"]${selector}`); let roving = getRadio(`[name="${node.name}"]:checked`); if (!roving) { roving = getRadio(`[name="${node.name}"]`); } return roving !== node; } function isNodeMatchingSelectorFocusable(node) { if (node.disabled || node.tagName === 'INPUT' && node.type === 'hidden' || isNonTabbableRadio(node)) { return false; } return true; } function getTabIndex(node) { const tabindexAttr = parseInt(node.getAttribute('tabindex') || '', 10); if (!Number.isNaN(tabindexAttr)) { return tabindexAttr; } // Browsers do not return `tabIndex` correctly for contentEditable nodes; // https://issues.chromium.org/issues/41283952 // so if they don't have a tabindex attribute specifically set, assume it's 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 (// jest not support contentEditable // istanbul ignore next node.contentEditable === 'true' || (node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { return 0; } return node.tabIndex; } function getTabbable(root) { const regularTabNodes = []; const orderedTabNodes = []; Array.from(root.querySelectorAll(candidateSelector)).forEach((node, i)=>{ const nodeTabIndex = getTabIndex(node); if (nodeTabIndex === -1 || !isNodeMatchingSelectorFocusable(node)) { return; } if (nodeTabIndex === 0) { regularTabNodes.push(node); } else { orderedTabNodes.push({ documentOrder: i, tabIndex: nodeTabIndex, node: node }); } }); return orderedTabNodes.sort((a, b)=>a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex).map((a)=>a.node).concat(regularTabNodes); } export { getTabbable };