get-element-unallowed-roles.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import isValidRole from './is-valid-role';
  2. import getImplicitRole from './implicit-role';
  3. import getRoleType from './get-role-type';
  4. import isAriaRoleAllowedOnElement from './is-aria-role-allowed-on-element';
  5. import { tokenList, isHtmlElement, matchesSelector } from '../../core/utils';
  6. // dpub roles which are subclassing roles that are implicit on some native
  7. // HTML elements (img, link, etc.)
  8. const dpubRoles = [
  9. 'doc-backlink',
  10. 'doc-biblioentry',
  11. 'doc-biblioref',
  12. 'doc-cover',
  13. 'doc-endnote',
  14. 'doc-glossref',
  15. 'doc-noteref'
  16. ];
  17. /**
  18. * Returns all roles applicable to element in a list
  19. *
  20. * @method getRoleSegments
  21. * @private
  22. * @param {Element} node
  23. * @returns {Array} Roles list or empty list
  24. */
  25. function getRoleSegments(node) {
  26. let roles = [];
  27. if (!node) {
  28. return roles;
  29. }
  30. if (node.hasAttribute('role')) {
  31. const nodeRoles = tokenList(node.getAttribute('role').toLowerCase());
  32. roles = roles.concat(nodeRoles);
  33. }
  34. if (node.hasAttributeNS('http://www.idpf.org/2007/ops', 'type')) {
  35. const epubRoles = tokenList(
  36. node.getAttributeNS('http://www.idpf.org/2007/ops', 'type').toLowerCase()
  37. ).map(role => `doc-${role}`);
  38. roles = roles.concat(epubRoles);
  39. }
  40. // filter invalid roles
  41. roles = roles.filter(role => isValidRole(role));
  42. return roles;
  43. }
  44. /**
  45. * gets all unallowed roles for a given node
  46. * @method getElementUnallowedRoles
  47. * @param {Object} node HTMLElement to validate
  48. * @param {String} tagName tag name of a node
  49. * @param {String} allowImplicit option to allow implicit roles, defaults to true
  50. * @return {Array<String>} retruns an array of roles that are not allowed on the given node
  51. */
  52. function getElementUnallowedRoles(node, allowImplicit = true) {
  53. const tagName = node.nodeName.toUpperCase();
  54. // by pass custom elements
  55. if (!isHtmlElement(node)) {
  56. return [];
  57. }
  58. const roleSegments = getRoleSegments(node);
  59. const implicitRole = getImplicitRole(node);
  60. // stores all roles that are not allowed for a specific element most often an element only has one explicit role
  61. const unallowedRoles = roleSegments.filter(role => {
  62. // if role and implicit role are same, when allowImplicit: true
  63. // ignore as it is a redundant role
  64. if (allowImplicit && role === implicitRole) {
  65. return false;
  66. }
  67. // if role is a dpub role make sure it's used on an element with a valid
  68. // implicit role fallback
  69. if (allowImplicit && dpubRoles.includes(role)) {
  70. const roleType = getRoleType(role);
  71. if (implicitRole !== roleType) {
  72. return true;
  73. }
  74. }
  75. // Edge case:
  76. // setting implicit role row on tr element is allowed when child of table[role='grid']
  77. if (
  78. !allowImplicit &&
  79. !(
  80. role === 'row' &&
  81. tagName === 'TR' &&
  82. matchesSelector(node, 'table[role="grid"] > tr')
  83. )
  84. ) {
  85. return true;
  86. }
  87. // check if role is allowed on element
  88. return !isAriaRoleAllowedOnElement(node, role);
  89. });
  90. return unallowedRoles;
  91. }
  92. export default getElementUnallowedRoles;