scrollable-region-focusable-matches.js 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { hasContentVirtual } from '../commons/dom';
  2. import { getExplicitRole } from '../commons/aria';
  3. import standards from '../standards';
  4. import {
  5. querySelectorAll,
  6. getScroll,
  7. closest,
  8. getRootNode,
  9. tokenList
  10. } from '../core/utils';
  11. function scrollableRegionFocusableMatches(node, virtualNode) {
  12. /**
  13. * Note:
  14. * `excludeHidden=true` for this rule, thus considering only elements in the accessibility tree.
  15. */
  16. /**
  17. * if not scrollable -> `return`
  18. */
  19. if (!!getScroll(node, 13) === false) {
  20. return false;
  21. }
  22. /**
  23. * ignore scrollable regions owned by combobox. limit to roles
  24. * ownable by combobox so we don't keep calling closest for every
  25. * node (which would be slow)
  26. * @see https://github.com/dequelabs/axe-core/issues/1763
  27. */
  28. const role = getExplicitRole(virtualNode);
  29. if (standards.ariaRoles.combobox.requiredOwned.includes(role)) {
  30. // in ARIA 1.1 the container has role=combobox
  31. if (closest(virtualNode, '[role~="combobox"]')) {
  32. return false;
  33. }
  34. // in ARIA 1.0 and 1.2 the combobox owns (1.0) or controls (1.2)
  35. // the listbox
  36. const id = virtualNode.attr('id');
  37. if (id) {
  38. const doc = getRootNode(node);
  39. const owned = Array.from(
  40. doc.querySelectorAll(`[aria-owns~="${id}"], [aria-controls~="${id}"]`)
  41. );
  42. const comboboxOwned = owned.some(el => {
  43. const roles = tokenList(el.getAttribute('role'));
  44. return roles.includes('combobox');
  45. });
  46. if (comboboxOwned) {
  47. return false;
  48. }
  49. }
  50. }
  51. /**
  52. * check if node has visible contents
  53. */
  54. const nodeAndDescendents = querySelectorAll(virtualNode, '*');
  55. const hasVisibleChildren = nodeAndDescendents.some(elm =>
  56. hasContentVirtual(
  57. elm,
  58. true, // noRecursion
  59. true // ignoreAria
  60. )
  61. );
  62. if (!hasVisibleChildren) {
  63. return false;
  64. }
  65. return true;
  66. }
  67. export default scrollableRegionFocusableMatches;