aria.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const FOCUSABLE_ELEMENT_SELECTORS = `a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])`;
  2. /**
  3. * Determine if the testing element is visible on screen no matter if its on the viewport or not
  4. */
  5. export const isVisible = (element: HTMLElement) => {
  6. if (process.env.NODE_ENV === 'test') return true;
  7. const computed = getComputedStyle(element);
  8. // element.offsetParent won't work on fix positioned
  9. // WARNING: potential issue here, going to need some expert advices on this issue
  10. return computed.position === 'fixed' ? false : element.offsetParent !== null;
  11. };
  12. export const obtainAllFocusableElements = (element: HTMLElement): HTMLElement[] => {
  13. return Array.from(element.querySelectorAll<HTMLElement>(FOCUSABLE_ELEMENT_SELECTORS)).filter(
  14. (item: HTMLElement) => isFocusable(item) && isVisible(item),
  15. );
  16. };
  17. /**
  18. * @desc Determine if target element is focusable
  19. * @param element {HTMLElement}
  20. * @returns {Boolean} true if it is focusable
  21. */
  22. export const isFocusable = (element: HTMLElement): boolean => {
  23. if (
  24. element.tabIndex > 0 ||
  25. (element.tabIndex === 0 && element.getAttribute('tabIndex') !== null)
  26. ) {
  27. return true;
  28. }
  29. // HTMLButtonElement has disabled
  30. if ((element as HTMLButtonElement).disabled) {
  31. return false;
  32. }
  33. switch (element.nodeName) {
  34. case 'A': {
  35. // casting current element to Specific HTMLElement in order to be more type precise
  36. return (
  37. !!(element as HTMLAnchorElement).href && (element as HTMLAnchorElement).rel !== 'ignore'
  38. );
  39. }
  40. case 'INPUT': {
  41. return !(
  42. (element as HTMLInputElement).type === 'hidden' ||
  43. (element as HTMLInputElement).type === 'file'
  44. );
  45. }
  46. case 'BUTTON':
  47. case 'SELECT':
  48. case 'TEXTAREA': {
  49. return true;
  50. }
  51. default: {
  52. return false;
  53. }
  54. }
  55. };
  56. /**
  57. * @desc Set Attempt to set focus on the current node.
  58. * @param element
  59. * The node to attempt to focus on.
  60. * @returns
  61. * true if element is focused.
  62. */
  63. export const attemptFocus = (element: HTMLElement): boolean => {
  64. if (!isFocusable(element)) {
  65. return false;
  66. }
  67. // Remove the old try catch block since there will be no error to be thrown
  68. element.focus?.();
  69. return document.activeElement === element;
  70. };
  71. /**
  72. * Trigger an event
  73. * mouseenter, mouseleave, mouseover, keyup, change, click, etc.
  74. * @param {HTMLElement} elm
  75. * @param {String} name
  76. * @param {*} opts
  77. */
  78. export const triggerEvent = function (
  79. elm: HTMLElement,
  80. name: string,
  81. ...opts: Array<boolean>
  82. ): HTMLElement {
  83. let eventName: string;
  84. if (name.includes('mouse') || name.includes('click')) {
  85. eventName = 'MouseEvents';
  86. } else if (name.includes('key')) {
  87. eventName = 'KeyboardEvent';
  88. } else {
  89. eventName = 'HTMLEvents';
  90. }
  91. const evt = document.createEvent(eventName);
  92. evt.initEvent(name, ...opts);
  93. elm.dispatchEvent(evt);
  94. return elm;
  95. };
  96. export const isLeaf = (el: HTMLElement) => !el.getAttribute('aria-owns');
  97. export const getSibling = (el: HTMLElement, distance: number, elClass: string) => {
  98. const { parentNode } = el;
  99. if (!parentNode) return null;
  100. const siblings = parentNode.querySelectorAll(elClass);
  101. const index = Array.prototype.indexOf.call(siblings, el);
  102. return siblings[index + distance] || null;
  103. };
  104. export const focusNode = (el: HTMLElement) => {
  105. if (!el) return;
  106. el.focus();
  107. !isLeaf(el) && el.click();
  108. };