HTMLUtilities.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /* *
  2. *
  3. * (c) 2009-2021 Øystein Moseng
  4. *
  5. * Utility functions for accessibility module.
  6. *
  7. * License: www.highcharts.com/license
  8. *
  9. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  10. *
  11. * */
  12. import H from '../../Core/Globals.js';
  13. var doc = H.doc, win = H.win;
  14. import U from '../../Core/Utilities.js';
  15. var merge = U.merge;
  16. /* eslint-disable valid-jsdoc */
  17. /**
  18. * @private
  19. * @param {Highcharts.HTMLDOMElement} el
  20. * @param {string} className
  21. * @return {void}
  22. */
  23. function addClass(el, className) {
  24. if (el.classList) {
  25. el.classList.add(className);
  26. }
  27. else if (el.className.indexOf(className) < 0) {
  28. // Note: Dumb check for class name exists, should be fine for practical
  29. // use cases, but will return false positives if the element has a class
  30. // that contains the className.
  31. el.className += className;
  32. }
  33. }
  34. /**
  35. * @private
  36. * @param {string} str
  37. * @return {string}
  38. */
  39. function escapeStringForHTML(str) {
  40. return str
  41. .replace(/&/g, '&amp;')
  42. .replace(/</g, '&lt;')
  43. .replace(/>/g, '&gt;')
  44. .replace(/"/g, '&quot;')
  45. .replace(/'/g, '&#x27;')
  46. .replace(/\//g, '&#x2F;');
  47. }
  48. /**
  49. * Get an element by ID
  50. * @param {string} id
  51. * @private
  52. * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|null}
  53. */
  54. function getElement(id) {
  55. return doc.getElementById(id);
  56. }
  57. /**
  58. * Get a fake mouse event of a given type
  59. * @param {string} type
  60. * @private
  61. * @return {global.MouseEvent}
  62. */
  63. function getFakeMouseEvent(type) {
  64. if (typeof win.MouseEvent === 'function') {
  65. return new win.MouseEvent(type);
  66. }
  67. // No MouseEvent support, try using initMouseEvent
  68. if (doc.createEvent) {
  69. var evt = doc.createEvent('MouseEvent');
  70. if (evt.initMouseEvent) {
  71. evt.initMouseEvent(type, true, // Bubble
  72. true, // Cancel
  73. win, // View
  74. type === 'click' ? 1 : 0, // Detail
  75. // Coords
  76. 0, 0, 0, 0,
  77. // Pressed keys
  78. false, false, false, false, 0, // button
  79. null // related target
  80. );
  81. return evt;
  82. }
  83. }
  84. return { type: type };
  85. }
  86. /**
  87. * Get an appropriate heading level for an element. Corresponds to the
  88. * heading level below the previous heading in the DOM.
  89. *
  90. * Note: Only detects previous headings in the DOM that are siblings,
  91. * ancestors, or previous siblings of ancestors. Headings that are nested below
  92. * siblings of ancestors (cousins et.al) are not picked up. This is because it
  93. * is ambiguous whether or not the nesting is for layout purposes or indicates a
  94. * separate section.
  95. *
  96. * @private
  97. * @param {Highcharts.HTMLDOMElement} [element]
  98. * @return {string} The heading tag name (h1, h2 etc).
  99. * If no nearest heading is found, "p" is returned.
  100. */
  101. function getHeadingTagNameForElement(element) {
  102. var getIncreasedHeadingLevel = function (tagName) {
  103. var headingLevel = parseInt(tagName.slice(1), 10);
  104. var newLevel = Math.min(6, headingLevel + 1);
  105. return 'h' + newLevel;
  106. };
  107. var isHeading = function (tagName) { return /H[1-6]/.test(tagName); };
  108. var getPreviousSiblingsHeading = function (el) {
  109. var sibling = el;
  110. while (sibling = sibling.previousSibling) { // eslint-disable-line
  111. var tagName = sibling.tagName || '';
  112. if (isHeading(tagName)) {
  113. return tagName;
  114. }
  115. }
  116. return '';
  117. };
  118. var getHeadingRecursive = function (el) {
  119. var prevSiblingsHeading = getPreviousSiblingsHeading(el);
  120. if (prevSiblingsHeading) {
  121. return getIncreasedHeadingLevel(prevSiblingsHeading);
  122. }
  123. // No previous siblings are headings, try parent node
  124. var parent = el.parentElement;
  125. if (!parent) {
  126. return 'p';
  127. }
  128. var parentTagName = parent.tagName;
  129. if (isHeading(parentTagName)) {
  130. return getIncreasedHeadingLevel(parentTagName);
  131. }
  132. return getHeadingRecursive(parent);
  133. };
  134. return getHeadingRecursive(element);
  135. }
  136. /**
  137. * Remove an element from the DOM.
  138. * @private
  139. * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} [element]
  140. * @return {void}
  141. */
  142. function removeElement(element) {
  143. if (element && element.parentNode) {
  144. element.parentNode.removeChild(element);
  145. }
  146. }
  147. /**
  148. * Utility function. Reverses child nodes of a DOM element.
  149. * @private
  150. * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} node
  151. * @return {void}
  152. */
  153. function reverseChildNodes(node) {
  154. var i = node.childNodes.length;
  155. while (i--) {
  156. node.appendChild(node.childNodes[i]);
  157. }
  158. }
  159. /**
  160. * Set attributes on element. Set to null to remove attribute.
  161. * @private
  162. * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el
  163. * @param {Highcharts.HTMLAttributes|Highcharts.SVGAttributes} attrs
  164. * @return {void}
  165. */
  166. function setElAttrs(el, attrs) {
  167. Object.keys(attrs).forEach(function (attr) {
  168. var val = attrs[attr];
  169. if (val === null) {
  170. el.removeAttribute(attr);
  171. }
  172. else {
  173. el.setAttribute(attr, val);
  174. }
  175. });
  176. }
  177. /**
  178. * Used for aria-label attributes, painting on a canvas will fail if the
  179. * text contains tags.
  180. * @private
  181. * @param {string} str
  182. * @return {string}
  183. */
  184. function stripHTMLTagsFromString(str) {
  185. return typeof str === 'string' ?
  186. str.replace(/<\/?[^>]+(>|$)/g, '') : str;
  187. }
  188. /**
  189. * Utility function for hiding an element visually, but still keeping it
  190. * available to screen reader users.
  191. * @private
  192. * @param {Highcharts.HTMLDOMElement} element
  193. * @return {void}
  194. */
  195. function visuallyHideElement(element) {
  196. var hiddenStyle = {
  197. position: 'absolute',
  198. width: '1px',
  199. height: '1px',
  200. overflow: 'hidden',
  201. whiteSpace: 'nowrap',
  202. clip: 'rect(1px, 1px, 1px, 1px)',
  203. marginTop: '-3px',
  204. '-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)',
  205. filter: 'alpha(opacity=1)',
  206. opacity: '0.01'
  207. };
  208. merge(true, element.style, hiddenStyle);
  209. }
  210. var HTMLUtilities = {
  211. addClass: addClass,
  212. escapeStringForHTML: escapeStringForHTML,
  213. getElement: getElement,
  214. getFakeMouseEvent: getFakeMouseEvent,
  215. getHeadingTagNameForElement: getHeadingTagNameForElement,
  216. removeElement: removeElement,
  217. reverseChildNodes: reverseChildNodes,
  218. setElAttrs: setElAttrs,
  219. stripHTMLTagsFromString: stripHTMLTagsFromString,
  220. visuallyHideElement: visuallyHideElement
  221. };
  222. export default HTMLUtilities;