import getRootNode from '../dom/get-root-node'; import cache from '../../core/base/cache'; import { tokenList } from '../../core/utils'; import standards from '../../standards'; import { sanitize } from '../text/'; const idRefsRegex = /^idrefs?$/; /** * Cache all ID references of a node and its children */ function cacheIdRefs(node, idRefs, refAttrs) { if (node.hasAttribute) { if (node.nodeName.toUpperCase() === 'LABEL' && node.hasAttribute('for')) { const id = node.getAttribute('for'); idRefs[id] = idRefs[id] || []; idRefs[id].push(node); } for (let i = 0; i < refAttrs.length; ++i) { const attr = refAttrs[i]; const attrValue = sanitize(node.getAttribute(attr) || ''); if (!attrValue) { continue; } const tokens = tokenList(attrValue); for (let k = 0; k < tokens.length; ++k) { idRefs[tokens[k]] = idRefs[tokens[k]] || []; idRefs[tokens[k]].push(node); } } } for (let i = 0; i < node.children.length; i++) { cacheIdRefs(node.children[i], idRefs, refAttrs); } } /** * Return all DOM nodes that use the nodes ID in the accessibility tree. * @param {Element} node * @returns {Element[]} */ function getAccessibleRefs(node) { node = node.actualNode || node; let root = getRootNode(node); root = root.documentElement || root; // account for shadow roots let idRefsByRoot = cache.get('idRefsByRoot'); if (!idRefsByRoot) { idRefsByRoot = new WeakMap(); cache.set('idRefsByRoot', idRefsByRoot); } let idRefs = idRefsByRoot.get(root); if (!idRefs) { idRefs = {}; idRefsByRoot.set(root, idRefs); const refAttrs = Object.keys(standards.ariaAttrs).filter(attr => { const { type } = standards.ariaAttrs[attr]; return idRefsRegex.test(type); }); cacheIdRefs(root, idRefs, refAttrs); } return idRefs[node.id] || []; } export default getAccessibleRefs;