58c466215d17b06a2e065a088f6eb37dc25be9f9fcd353a315c3393d5e31149a781cf7dcaacdb9121d8fbef7afb348afb1ec6cbfc1bdf99ecba54f8c18280e 4.8 KB

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