index.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. var getDefaultParent = function (originalTarget) {
  2. if (typeof document === 'undefined') {
  3. return null;
  4. }
  5. var sampleTarget = Array.isArray(originalTarget) ? originalTarget[0] : originalTarget;
  6. return sampleTarget.ownerDocument.body;
  7. };
  8. var counterMap = new WeakMap();
  9. var uncontrolledNodes = new WeakMap();
  10. var markerMap = {};
  11. var lockCount = 0;
  12. var unwrapHost = function (node) {
  13. return node && (node.host || unwrapHost(node.parentNode));
  14. };
  15. var correctTargets = function (parent, targets) {
  16. return targets
  17. .map(function (target) {
  18. if (parent.contains(target)) {
  19. return target;
  20. }
  21. var correctedTarget = unwrapHost(target);
  22. if (correctedTarget && parent.contains(correctedTarget)) {
  23. return correctedTarget;
  24. }
  25. console.error('aria-hidden', target, 'in not contained inside', parent, '. Doing nothing');
  26. return null;
  27. })
  28. .filter(function (x) { return Boolean(x); });
  29. };
  30. /**
  31. * Marks everything except given node(or nodes) as aria-hidden
  32. * @param {Element | Element[]} originalTarget - elements to keep on the page
  33. * @param [parentNode] - top element, defaults to document.body
  34. * @param {String} [markerName] - a special attribute to mark every node
  35. * @param {String} [controlAttribute] - html Attribute to control
  36. * @return {Undo} undo command
  37. */
  38. var applyAttributeToOthers = function (originalTarget, parentNode, markerName, controlAttribute) {
  39. var targets = correctTargets(parentNode, Array.isArray(originalTarget) ? originalTarget : [originalTarget]);
  40. if (!markerMap[markerName]) {
  41. markerMap[markerName] = new WeakMap();
  42. }
  43. var markerCounter = markerMap[markerName];
  44. var hiddenNodes = [];
  45. var elementsToKeep = new Set();
  46. var elementsToStop = new Set(targets);
  47. var keep = function (el) {
  48. if (!el || elementsToKeep.has(el)) {
  49. return;
  50. }
  51. elementsToKeep.add(el);
  52. keep(el.parentNode);
  53. };
  54. targets.forEach(keep);
  55. var deep = function (parent) {
  56. if (!parent || elementsToStop.has(parent)) {
  57. return;
  58. }
  59. Array.prototype.forEach.call(parent.children, function (node) {
  60. if (elementsToKeep.has(node)) {
  61. deep(node);
  62. }
  63. else {
  64. var attr = node.getAttribute(controlAttribute);
  65. var alreadyHidden = attr !== null && attr !== 'false';
  66. var counterValue = (counterMap.get(node) || 0) + 1;
  67. var markerValue = (markerCounter.get(node) || 0) + 1;
  68. counterMap.set(node, counterValue);
  69. markerCounter.set(node, markerValue);
  70. hiddenNodes.push(node);
  71. if (counterValue === 1 && alreadyHidden) {
  72. uncontrolledNodes.set(node, true);
  73. }
  74. if (markerValue === 1) {
  75. node.setAttribute(markerName, 'true');
  76. }
  77. if (!alreadyHidden) {
  78. node.setAttribute(controlAttribute, 'true');
  79. }
  80. }
  81. });
  82. };
  83. deep(parentNode);
  84. elementsToKeep.clear();
  85. lockCount++;
  86. return function () {
  87. hiddenNodes.forEach(function (node) {
  88. var counterValue = counterMap.get(node) - 1;
  89. var markerValue = markerCounter.get(node) - 1;
  90. counterMap.set(node, counterValue);
  91. markerCounter.set(node, markerValue);
  92. if (!counterValue) {
  93. if (!uncontrolledNodes.has(node)) {
  94. node.removeAttribute(controlAttribute);
  95. }
  96. uncontrolledNodes.delete(node);
  97. }
  98. if (!markerValue) {
  99. node.removeAttribute(markerName);
  100. }
  101. });
  102. lockCount--;
  103. if (!lockCount) {
  104. // clear
  105. counterMap = new WeakMap();
  106. counterMap = new WeakMap();
  107. uncontrolledNodes = new WeakMap();
  108. markerMap = {};
  109. }
  110. };
  111. };
  112. /**
  113. * Marks everything except given node(or nodes) as aria-hidden
  114. * @param {Element | Element[]} originalTarget - elements to keep on the page
  115. * @param [parentNode] - top element, defaults to document.body
  116. * @param {String} [markerName] - a special attribute to mark every node
  117. * @return {Undo} undo command
  118. */
  119. export var hideOthers = function (originalTarget, parentNode, markerName) {
  120. if (markerName === void 0) { markerName = 'data-aria-hidden'; }
  121. var targets = Array.from(Array.isArray(originalTarget) ? originalTarget : [originalTarget]);
  122. var activeParentNode = parentNode || getDefaultParent(originalTarget);
  123. if (!activeParentNode) {
  124. return function () { return null; };
  125. }
  126. // we should not hide ariaLive elements - https://github.com/theKashey/aria-hidden/issues/10
  127. targets.push.apply(targets, Array.from(activeParentNode.querySelectorAll('[aria-live]')));
  128. return applyAttributeToOthers(targets, activeParentNode, markerName, 'aria-hidden');
  129. };
  130. /**
  131. * Marks everything except given node(or nodes) as inert
  132. * @param {Element | Element[]} originalTarget - elements to keep on the page
  133. * @param [parentNode] - top element, defaults to document.body
  134. * @param {String} [markerName] - a special attribute to mark every node
  135. * @return {Undo} undo command
  136. */
  137. export var inertOthers = function (originalTarget, parentNode, markerName) {
  138. if (markerName === void 0) { markerName = 'data-inert-ed'; }
  139. var activeParentNode = parentNode || getDefaultParent(originalTarget);
  140. if (!activeParentNode) {
  141. return function () { return null; };
  142. }
  143. return applyAttributeToOthers(originalTarget, activeParentNode, markerName, 'inert');
  144. };
  145. /**
  146. * @returns if current browser supports inert
  147. */
  148. export var supportsInert = function () {
  149. return typeof HTMLElement !== 'undefined' && HTMLElement.prototype.hasOwnProperty('inert');
  150. };
  151. /**
  152. * Automatic function to "suppress" DOM elements - _hide_ or _inert_ in the best possible way
  153. * @param {Element | Element[]} originalTarget - elements to keep on the page
  154. * @param [parentNode] - top element, defaults to document.body
  155. * @param {String} [markerName] - a special attribute to mark every node
  156. * @return {Undo} undo command
  157. */
  158. export var suppressOthers = function (originalTarget, parentNode, markerName) {
  159. if (markerName === void 0) { markerName = 'data-suppressed'; }
  160. return (supportsInert() ? inertOthers : hideOthers)(originalTarget, parentNode, markerName);
  161. };