import isValidRole from './is-valid-role'; import getImplicitRole from './implicit-role'; import getRoleType from './get-role-type'; import isAriaRoleAllowedOnElement from './is-aria-role-allowed-on-element'; import { tokenList, isHtmlElement, matchesSelector } from '../../core/utils'; // dpub roles which are subclassing roles that are implicit on some native // HTML elements (img, link, etc.) const dpubRoles = [ 'doc-backlink', 'doc-biblioentry', 'doc-biblioref', 'doc-cover', 'doc-endnote', 'doc-glossref', 'doc-noteref' ]; /** * Returns all roles applicable to element in a list * * @method getRoleSegments * @private * @param {Element} node * @returns {Array} Roles list or empty list */ function getRoleSegments(node) { let roles = []; if (!node) { return roles; } if (node.hasAttribute('role')) { const nodeRoles = tokenList(node.getAttribute('role').toLowerCase()); roles = roles.concat(nodeRoles); } if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) { const epubRoles = tokenList( node.getAttributeNS('http://www.idpf.org/2007/ops', 'type').toLowerCase() ).map(role => `doc-${role}`); roles = roles.concat(epubRoles); } // filter invalid roles roles = roles.filter(role => isValidRole(role)); return roles; } /** * gets all unallowed roles for a given node * @method getElementUnallowedRoles * @param {Object} node HTMLElement to validate * @param {String} tagName tag name of a node * @param {String} allowImplicit option to allow implicit roles, defaults to true * @return {Array} retruns an array of roles that are not allowed on the given node */ function getElementUnallowedRoles(node, allowImplicit = true) { const tagName = node.nodeName.toUpperCase(); // by pass custom elements if (!isHtmlElement(node)) { return []; } const roleSegments = getRoleSegments(node); const implicitRole = getImplicitRole(node); // stores all roles that are not allowed for a specific element most often an element only has one explicit role const unallowedRoles = roleSegments.filter(role => { // if role and implicit role are same, when allowImplicit: true // ignore as it is a redundant role if (allowImplicit && role === implicitRole) { return false; } // if role is a dpub role make sure it's used on an element with a valid // implicit role fallback if (allowImplicit && dpubRoles.includes(role)) { const roleType = getRoleType(role); if (implicitRole !== roleType) { return true; } } // Edge case: // setting implicit role row on tr element is allowed when child of table[role='grid'] if ( !allowImplicit && !( role === 'row' && tagName === 'TR' && matchesSelector(node, 'table[role="grid"] > tr') ) ) { return true; } // check if role is allowed on element return !isAriaRoleAllowedOnElement(node, role); }); return unallowedRoles; } export default getElementUnallowedRoles;