element-plus
Version:
A Component Library for Vue 3
121 lines (119 loc) • 4.35 kB
JavaScript
import { focusElement } from "../../../utils/dom/aria.mjs";
import { FOCUSOUT_PREVENTED, FOCUSOUT_PREVENTED_OPTS } from "./tokens.mjs";
import { onBeforeUnmount, onMounted, ref } from "vue";
//#region ../../packages/components/focus-trap/src/utils.ts
const focusReason = ref();
const lastUserFocusTimestamp = ref(0);
const lastAutomatedFocusTimestamp = ref(0);
let focusReasonUserCount = 0;
const obtainAllFocusableElements = (element) => {
const nodes = [];
const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, { acceptNode: (node) => {
const isHiddenInput = node.tagName === "INPUT" && node.type === "hidden";
if (node.disabled || node.hidden || isHiddenInput) return NodeFilter.FILTER_SKIP;
return node.tabIndex >= 0 || node === document.activeElement ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
} });
while (walker.nextNode()) nodes.push(walker.currentNode);
return nodes;
};
const getVisibleElement = (elements, container) => {
for (const element of elements) if (!isHidden(element, container)) return element;
};
const isHidden = (element, container) => {
if (getComputedStyle(element).visibility === "hidden") return true;
while (element) {
if (container && element === container) return false;
if (getComputedStyle(element).display === "none") return true;
element = element.parentElement;
}
return false;
};
const getEdges = (container) => {
const focusable = obtainAllFocusableElements(container);
return [getVisibleElement(focusable, container), getVisibleElement(focusable.reverse(), container)];
};
const isSelectable = (element) => {
return element instanceof HTMLInputElement && "select" in element;
};
const tryFocus = (element, shouldSelect) => {
if (element) {
const prevFocusedElement = document.activeElement;
focusElement(element, { preventScroll: true });
lastAutomatedFocusTimestamp.value = window.performance.now();
if (element !== prevFocusedElement && isSelectable(element) && shouldSelect) element.select();
}
};
function removeFromStack(list, item) {
const copy = [...list];
const idx = list.indexOf(item);
if (idx !== -1) copy.splice(idx, 1);
return copy;
}
const createFocusableStack = () => {
let stack = [];
const push = (layer) => {
const currentLayer = stack[0];
if (currentLayer && layer !== currentLayer) currentLayer.pause();
stack = removeFromStack(stack, layer);
stack.unshift(layer);
};
const remove = (layer) => {
stack = removeFromStack(stack, layer);
stack[0]?.resume?.();
};
return {
push,
remove
};
};
const focusFirstDescendant = (elements, shouldSelect = false) => {
const prevFocusedElement = document.activeElement;
for (const element of elements) {
tryFocus(element, shouldSelect);
if (document.activeElement !== prevFocusedElement) return;
}
};
const focusableStack = createFocusableStack();
const isFocusCausedByUserEvent = () => {
return lastUserFocusTimestamp.value > lastAutomatedFocusTimestamp.value;
};
const notifyFocusReasonPointer = () => {
focusReason.value = "pointer";
lastUserFocusTimestamp.value = window.performance.now();
};
const notifyFocusReasonKeydown = () => {
focusReason.value = "keyboard";
lastUserFocusTimestamp.value = window.performance.now();
};
const useFocusReason = () => {
onMounted(() => {
if (focusReasonUserCount === 0) {
document.addEventListener("mousedown", notifyFocusReasonPointer);
document.addEventListener("touchstart", notifyFocusReasonPointer);
document.addEventListener("keydown", notifyFocusReasonKeydown);
}
focusReasonUserCount++;
});
onBeforeUnmount(() => {
focusReasonUserCount--;
if (focusReasonUserCount <= 0) {
document.removeEventListener("mousedown", notifyFocusReasonPointer);
document.removeEventListener("touchstart", notifyFocusReasonPointer);
document.removeEventListener("keydown", notifyFocusReasonKeydown);
}
});
return {
focusReason,
lastUserFocusTimestamp,
lastAutomatedFocusTimestamp
};
};
const createFocusOutPreventedEvent = (detail) => {
return new CustomEvent(FOCUSOUT_PREVENTED, {
...FOCUSOUT_PREVENTED_OPTS,
detail
});
};
//#endregion
export { createFocusOutPreventedEvent, focusFirstDescendant, focusableStack, getEdges, getVisibleElement, isFocusCausedByUserEvent, isHidden, obtainAllFocusableElements, tryFocus, useFocusReason };
//# sourceMappingURL=utils.mjs.map