aria-required-parent-evaluate.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import { getExplicitRole, getRole, requiredContext } from '../../commons/aria';
  2. import { getRootNode } from '../../commons/dom';
  3. import { getNodeFromTree, escapeSelector } from '../../core/utils';
  4. function getMissingContext(virtualNode, ownGroupRoles, reqContext, includeElement) {
  5. const explicitRole = getExplicitRole(virtualNode);
  6. if (!reqContext) {
  7. reqContext = requiredContext(explicitRole);
  8. }
  9. if (!reqContext) {
  10. return null;
  11. }
  12. let vNode = includeElement ? virtualNode : virtualNode.parent;
  13. while (vNode) {
  14. const parentRole = getRole(vNode);
  15. // if parent node has role=group and role=group is an allowed
  16. // context, check next parent
  17. if (reqContext.includes('group') && parentRole === 'group') {
  18. // Allow the own role; i.e. tree > treeitem > group > treeitem
  19. if (ownGroupRoles.includes(explicitRole)) {
  20. reqContext.push(explicitRole);
  21. }
  22. vNode = vNode.parent;
  23. continue;
  24. }
  25. // if parent node has a role that is not the required role and not
  26. // presentational we will fail the check
  27. if (reqContext.includes(parentRole)) {
  28. return null;
  29. } else if (parentRole && !['presentation', 'none'].includes(parentRole)) {
  30. return reqContext;
  31. }
  32. vNode = vNode.parent;
  33. }
  34. return reqContext;
  35. }
  36. function getAriaOwners(element) {
  37. var owners = [],
  38. o = null;
  39. while (element) {
  40. if (element.getAttribute('id')) {
  41. const id = escapeSelector(element.getAttribute('id'));
  42. const doc = getRootNode(element);
  43. o = doc.querySelector(`[aria-owns~=${id}]`);
  44. if (o) {
  45. owners.push(o);
  46. }
  47. }
  48. element = element.parentElement;
  49. }
  50. return owners.length ? owners : null;
  51. }
  52. /**
  53. * Check if the element has a parent with a required role.
  54. *
  55. * Required parent roles are taken from the `ariaRoles` standards object from the roles `requiredContext` property.
  56. *
  57. * ##### Data:
  58. * <table class="props">
  59. * <thead>
  60. * <tr>
  61. * <th>Type</th>
  62. * <th>Description</th>
  63. * </tr>
  64. * </thead>
  65. * <tbody>
  66. * <tr>
  67. * <td><code>String[]</code></td>
  68. * <td>List of all missing required parent roles</td>
  69. * </tr>
  70. * </tbody>
  71. * </table>
  72. *
  73. * @memberof checks
  74. * @return {Boolean} True if the element has a parent with a required role. False otherwise.
  75. */
  76. function ariaRequiredParentEvaluate(node, options, virtualNode) {
  77. const ownGroupRoles = (
  78. options && Array.isArray(options.ownGroupRoles)
  79. ? options.ownGroupRoles
  80. : []
  81. );
  82. let missingParents = getMissingContext(virtualNode, ownGroupRoles);
  83. if (!missingParents) {
  84. return true;
  85. }
  86. const owners = getAriaOwners(node);
  87. if (owners) {
  88. for (let i = 0, l = owners.length; i < l; i++) {
  89. missingParents = getMissingContext(
  90. getNodeFromTree(owners[i]),
  91. ownGroupRoles,
  92. missingParents,
  93. true
  94. );
  95. if (!missingParents) {
  96. return true;
  97. }
  98. }
  99. }
  100. this.data(missingParents);
  101. return false;
  102. }
  103. export default ariaRequiredParentEvaluate;