subtree-text.js 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import accessibleTextVirtual from './accessible-text-virtual';
  2. import namedFromContents from '../aria/named-from-contents';
  3. import getOwnedVirtual from '../aria/get-owned-virtual';
  4. import getElementsByContentType from '../standards/get-elements-by-content-type';
  5. /**
  6. * Get the accessible text for an element that can get its name from content
  7. *
  8. * @param {VirtualNode} element
  9. * @param {Object} context
  10. * @property {Bool} strict Should the name computation strictly follow AccName 1.1
  11. * @return {String} Accessible text
  12. */
  13. function subtreeText(virtualNode, context = {}) {
  14. const { alreadyProcessed } = accessibleTextVirtual;
  15. context.startNode = context.startNode || virtualNode;
  16. const { strict, inControlContext, inLabelledByContext } = context;
  17. if (
  18. alreadyProcessed(virtualNode, context) ||
  19. virtualNode.props.nodeType !== 1
  20. ) {
  21. return '';
  22. }
  23. if (
  24. !namedFromContents(virtualNode, { strict }) &&
  25. !context.subtreeDescendant
  26. ) {
  27. return '';
  28. }
  29. /**
  30. * Note: Strictly speaking if a child isn't named from content and it has no accessible name
  31. * accName says to ignore it. Browsers do this fairly consistently, but screen readers have
  32. * chosen to ignore this, but only for direct content, not for labels / aria-labelledby.
  33. * That way in `a[href] > article > #text` the text is used for the accessible name,
  34. * See: https://github.com/dequelabs/axe-core/issues/1461
  35. */
  36. if (!strict) {
  37. const subtreeDescendant = !inControlContext && !inLabelledByContext;
  38. context = { subtreeDescendant, ...context };
  39. }
  40. return getOwnedVirtual(virtualNode).reduce((contentText, child) => {
  41. return appendAccessibleText(contentText, child, context);
  42. }, '');
  43. }
  44. const phrasingElements = getElementsByContentType('phrasing').concat(['#text']);
  45. function appendAccessibleText(contentText, virtualNode, context) {
  46. const nodeName = virtualNode.props.nodeName;
  47. let contentTextAdd = accessibleTextVirtual(virtualNode, context);
  48. if (!contentTextAdd) {
  49. return contentText;
  50. }
  51. if (!phrasingElements.includes(nodeName)) {
  52. // Append space, if necessary
  53. if (contentTextAdd[0] !== ' ') {
  54. contentTextAdd += ' ';
  55. }
  56. // Prepend space, if necessary
  57. if (contentText && contentText[contentText.length - 1] !== ' ') {
  58. contentTextAdd = ' ' + contentTextAdd;
  59. }
  60. }
  61. return contentText + contentTextAdd;
  62. }
  63. export default subtreeText;