import { ref, onMounted, onBeforeUnmount } from 'vue';
|
import { FOCUSOUT_PREVENTED, FOCUSOUT_PREVENTED_OPTS } from './tokens.mjs';
|
|
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 (process.env.NODE_ENV === "test")
|
return false;
|
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);
|
const first = getVisibleElement(focusable, container);
|
const last = getVisibleElement(focusable.reverse(), container);
|
return [first, last];
|
};
|
const isSelectable = (element) => {
|
return element instanceof HTMLInputElement && "select" in element;
|
};
|
const tryFocus = (element, shouldSelect) => {
|
if (element && element.focus) {
|
const prevFocusedElement = document.activeElement;
|
element.focus({ 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) => {
|
var _a, _b;
|
stack = removeFromStack(stack, layer);
|
(_b = (_a = stack[0]) == null ? void 0 : _a.resume) == null ? void 0 : _b.call(_a);
|
};
|
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
|
});
|
};
|
|
export { createFocusOutPreventedEvent, focusFirstDescendant, focusableStack, getEdges, getVisibleElement, isFocusCausedByUserEvent, isHidden, obtainAllFocusableElements, tryFocus, useFocusReason };
|
//# sourceMappingURL=utils.mjs.map
|