text_accessibility.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * JavaScript code in this page
  4. *
  5. * Copyright 2022 Mozilla Foundation
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @licend The above is the entire license notice for the
  20. * JavaScript code in this page
  21. */
  22. "use strict";
  23. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.TextAccessibilityManager = void 0;
  27. var _ui_utils = require("./ui_utils.js");
  28. class TextAccessibilityManager {
  29. #enabled = false;
  30. #textChildren = null;
  31. #textNodes = new Map();
  32. #waitingElements = new Map();
  33. setTextMapping(textDivs) {
  34. this.#textChildren = textDivs;
  35. }
  36. static #compareElementPositions(e1, e2) {
  37. const rect1 = e1.getBoundingClientRect();
  38. const rect2 = e2.getBoundingClientRect();
  39. if (rect1.width === 0 && rect1.height === 0) {
  40. return +1;
  41. }
  42. if (rect2.width === 0 && rect2.height === 0) {
  43. return -1;
  44. }
  45. const top1 = rect1.y;
  46. const bot1 = rect1.y + rect1.height;
  47. const mid1 = rect1.y + rect1.height / 2;
  48. const top2 = rect2.y;
  49. const bot2 = rect2.y + rect2.height;
  50. const mid2 = rect2.y + rect2.height / 2;
  51. if (mid1 <= top2 && mid2 >= bot1) {
  52. return -1;
  53. }
  54. if (mid2 <= top1 && mid1 >= bot2) {
  55. return +1;
  56. }
  57. const centerX1 = rect1.x + rect1.width / 2;
  58. const centerX2 = rect2.x + rect2.width / 2;
  59. return centerX1 - centerX2;
  60. }
  61. enable() {
  62. if (this.#enabled) {
  63. throw new Error("TextAccessibilityManager is already enabled.");
  64. }
  65. if (!this.#textChildren) {
  66. throw new Error("Text divs and strings have not been set.");
  67. }
  68. this.#enabled = true;
  69. this.#textChildren = this.#textChildren.slice();
  70. this.#textChildren.sort(TextAccessibilityManager.#compareElementPositions);
  71. if (this.#textNodes.size > 0) {
  72. const textChildren = this.#textChildren;
  73. for (const [id, nodeIndex] of this.#textNodes) {
  74. const element = document.getElementById(id);
  75. if (!element) {
  76. this.#textNodes.delete(id);
  77. continue;
  78. }
  79. this.#addIdToAriaOwns(id, textChildren[nodeIndex]);
  80. }
  81. }
  82. for (const [element, isRemovable] of this.#waitingElements) {
  83. this.addPointerInTextLayer(element, isRemovable);
  84. }
  85. this.#waitingElements.clear();
  86. }
  87. disable() {
  88. if (!this.#enabled) {
  89. return;
  90. }
  91. this.#waitingElements.clear();
  92. this.#textChildren = null;
  93. this.#enabled = false;
  94. }
  95. removePointerInTextLayer(element) {
  96. if (!this.#enabled) {
  97. this.#waitingElements.delete(element);
  98. return;
  99. }
  100. const children = this.#textChildren;
  101. if (!children || children.length === 0) {
  102. return;
  103. }
  104. const {
  105. id
  106. } = element;
  107. const nodeIndex = this.#textNodes.get(id);
  108. if (nodeIndex === undefined) {
  109. return;
  110. }
  111. const node = children[nodeIndex];
  112. this.#textNodes.delete(id);
  113. let owns = node.getAttribute("aria-owns");
  114. if (owns?.includes(id)) {
  115. owns = owns.split(" ").filter(x => x !== id).join(" ");
  116. if (owns) {
  117. node.setAttribute("aria-owns", owns);
  118. } else {
  119. node.removeAttribute("aria-owns");
  120. node.setAttribute("role", "presentation");
  121. }
  122. }
  123. }
  124. #addIdToAriaOwns(id, node) {
  125. const owns = node.getAttribute("aria-owns");
  126. if (!owns?.includes(id)) {
  127. node.setAttribute("aria-owns", owns ? `${owns} ${id}` : id);
  128. }
  129. node.removeAttribute("role");
  130. }
  131. addPointerInTextLayer(element, isRemovable) {
  132. const {
  133. id
  134. } = element;
  135. if (!id) {
  136. return;
  137. }
  138. if (!this.#enabled) {
  139. this.#waitingElements.set(element, isRemovable);
  140. return;
  141. }
  142. if (isRemovable) {
  143. this.removePointerInTextLayer(element);
  144. }
  145. const children = this.#textChildren;
  146. if (!children || children.length === 0) {
  147. return;
  148. }
  149. const index = (0, _ui_utils.binarySearchFirstItem)(children, node => TextAccessibilityManager.#compareElementPositions(element, node) < 0);
  150. const nodeIndex = Math.max(0, index - 1);
  151. this.#addIdToAriaOwns(id, children[nodeIndex]);
  152. this.#textNodes.set(id, nodeIndex);
  153. }
  154. moveElementInDOM(container, element, contentElement, isRemovable) {
  155. this.addPointerInTextLayer(contentElement, isRemovable);
  156. if (!container.hasChildNodes()) {
  157. container.append(element);
  158. return;
  159. }
  160. const children = Array.from(container.childNodes).filter(node => node !== element);
  161. if (children.length === 0) {
  162. return;
  163. }
  164. const elementToCompare = contentElement || element;
  165. const index = (0, _ui_utils.binarySearchFirstItem)(children, node => TextAccessibilityManager.#compareElementPositions(elementToCompare, node) < 0);
  166. if (index === 0) {
  167. children[0].before(element);
  168. } else {
  169. children[index - 1].after(element);
  170. }
  171. }
  172. }
  173. exports.TextAccessibilityManager = TextAccessibilityManager;