utils.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var vue = require('vue');
  4. var tokens = require('./tokens.js');
  5. var types = require('../../../utils/types.js');
  6. var aria = require('../../../utils/dom/aria.js');
  7. const focusReason = vue.ref();
  8. const lastUserFocusTimestamp = vue.ref(0);
  9. const lastAutomatedFocusTimestamp = vue.ref(0);
  10. let focusReasonUserCount = 0;
  11. const obtainAllFocusableElements = (element) => {
  12. const nodes = [];
  13. const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, {
  14. acceptNode: (node) => {
  15. const isHiddenInput = node.tagName === "INPUT" && node.type === "hidden";
  16. if (node.disabled || node.hidden || isHiddenInput)
  17. return NodeFilter.FILTER_SKIP;
  18. return node.tabIndex >= 0 || node === document.activeElement ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
  19. }
  20. });
  21. while (walker.nextNode())
  22. nodes.push(walker.currentNode);
  23. return nodes;
  24. };
  25. const getVisibleElement = (elements, container) => {
  26. for (const element of elements) {
  27. if (!isHidden(element, container))
  28. return element;
  29. }
  30. };
  31. const isHidden = (element, container) => {
  32. if (process.env.NODE_ENV === "test")
  33. return false;
  34. if (getComputedStyle(element).visibility === "hidden")
  35. return true;
  36. while (element) {
  37. if (container && element === container)
  38. return false;
  39. if (getComputedStyle(element).display === "none")
  40. return true;
  41. element = element.parentElement;
  42. }
  43. return false;
  44. };
  45. const getEdges = (container) => {
  46. const focusable = obtainAllFocusableElements(container);
  47. const first = getVisibleElement(focusable, container);
  48. const last = getVisibleElement(focusable.reverse(), container);
  49. return [first, last];
  50. };
  51. const isSelectable = (element) => {
  52. return element instanceof HTMLInputElement && "select" in element;
  53. };
  54. const tryFocus = (element, shouldSelect) => {
  55. if (element && element.focus) {
  56. const prevFocusedElement = document.activeElement;
  57. let cleanup = false;
  58. if (types.isElement(element) && !aria.isFocusable(element) && !element.getAttribute("tabindex")) {
  59. element.setAttribute("tabindex", "-1");
  60. cleanup = true;
  61. }
  62. element.focus({ preventScroll: true });
  63. lastAutomatedFocusTimestamp.value = window.performance.now();
  64. if (element !== prevFocusedElement && isSelectable(element) && shouldSelect) {
  65. element.select();
  66. }
  67. if (types.isElement(element) && cleanup) {
  68. element.removeAttribute("tabindex");
  69. }
  70. }
  71. };
  72. function removeFromStack(list, item) {
  73. const copy = [...list];
  74. const idx = list.indexOf(item);
  75. if (idx !== -1) {
  76. copy.splice(idx, 1);
  77. }
  78. return copy;
  79. }
  80. const createFocusableStack = () => {
  81. let stack = [];
  82. const push = (layer) => {
  83. const currentLayer = stack[0];
  84. if (currentLayer && layer !== currentLayer) {
  85. currentLayer.pause();
  86. }
  87. stack = removeFromStack(stack, layer);
  88. stack.unshift(layer);
  89. };
  90. const remove = (layer) => {
  91. var _a, _b;
  92. stack = removeFromStack(stack, layer);
  93. (_b = (_a = stack[0]) == null ? void 0 : _a.resume) == null ? void 0 : _b.call(_a);
  94. };
  95. return {
  96. push,
  97. remove
  98. };
  99. };
  100. const focusFirstDescendant = (elements, shouldSelect = false) => {
  101. const prevFocusedElement = document.activeElement;
  102. for (const element of elements) {
  103. tryFocus(element, shouldSelect);
  104. if (document.activeElement !== prevFocusedElement)
  105. return;
  106. }
  107. };
  108. const focusableStack = createFocusableStack();
  109. const isFocusCausedByUserEvent = () => {
  110. return lastUserFocusTimestamp.value > lastAutomatedFocusTimestamp.value;
  111. };
  112. const notifyFocusReasonPointer = () => {
  113. focusReason.value = "pointer";
  114. lastUserFocusTimestamp.value = window.performance.now();
  115. };
  116. const notifyFocusReasonKeydown = () => {
  117. focusReason.value = "keyboard";
  118. lastUserFocusTimestamp.value = window.performance.now();
  119. };
  120. const useFocusReason = () => {
  121. vue.onMounted(() => {
  122. if (focusReasonUserCount === 0) {
  123. document.addEventListener("mousedown", notifyFocusReasonPointer);
  124. document.addEventListener("touchstart", notifyFocusReasonPointer);
  125. document.addEventListener("keydown", notifyFocusReasonKeydown);
  126. }
  127. focusReasonUserCount++;
  128. });
  129. vue.onBeforeUnmount(() => {
  130. focusReasonUserCount--;
  131. if (focusReasonUserCount <= 0) {
  132. document.removeEventListener("mousedown", notifyFocusReasonPointer);
  133. document.removeEventListener("touchstart", notifyFocusReasonPointer);
  134. document.removeEventListener("keydown", notifyFocusReasonKeydown);
  135. }
  136. });
  137. return {
  138. focusReason,
  139. lastUserFocusTimestamp,
  140. lastAutomatedFocusTimestamp
  141. };
  142. };
  143. const createFocusOutPreventedEvent = (detail) => {
  144. return new CustomEvent(tokens.FOCUSOUT_PREVENTED, {
  145. ...tokens.FOCUSOUT_PREVENTED_OPTS,
  146. detail
  147. });
  148. };
  149. exports.createFocusOutPreventedEvent = createFocusOutPreventedEvent;
  150. exports.focusFirstDescendant = focusFirstDescendant;
  151. exports.focusableStack = focusableStack;
  152. exports.getEdges = getEdges;
  153. exports.getVisibleElement = getVisibleElement;
  154. exports.isFocusCausedByUserEvent = isFocusCausedByUserEvent;
  155. exports.isHidden = isHidden;
  156. exports.obtainAllFocusableElements = obtainAllFocusableElements;
  157. exports.tryFocus = tryFocus;
  158. exports.useFocusReason = useFocusReason;
  159. //# sourceMappingURL=utils.js.map