@zag-js/dom-query
Version:
The dom helper library for zag.js machines
1,251 lines (1,220 loc) • 44.8 kB
JavaScript
// src/caret.ts
function isCaretAtStart(input) {
if (!input) return false;
try {
return input.selectionStart === 0 && input.selectionEnd === 0;
} catch {
return input.value === "";
}
}
function setCaretToEnd(input) {
if (!input) return;
const start = input.selectionStart ?? 0;
const end = input.selectionEnd ?? 0;
if (Math.abs(end - start) !== 0) return;
if (start !== 0) return;
input.setSelectionRange(input.value.length, input.value.length);
}
// src/shared.ts
var clamp = (value) => Math.max(0, Math.min(1, value));
var wrap = (v, idx) => {
return v.map((_, index) => v[(Math.max(idx, 0) + index) % v.length]);
};
var pipe = (...fns) => (arg) => fns.reduce((acc, fn) => fn(acc), arg);
var noop = () => void 0;
var isObject = (v) => typeof v === "object" && v !== null;
var MAX_Z_INDEX = 2147483647;
var dataAttr = (guard) => guard ? "" : void 0;
var ariaAttr = (guard) => guard ? "true" : void 0;
// src/node.ts
var ELEMENT_NODE = 1;
var DOCUMENT_NODE = 9;
var DOCUMENT_FRAGMENT_NODE = 11;
var isHTMLElement = (el) => isObject(el) && el.nodeType === ELEMENT_NODE && typeof el.nodeName === "string";
var isDocument = (el) => isObject(el) && el.nodeType === DOCUMENT_NODE;
var isWindow = (el) => isObject(el) && el === el.window;
var isVisualViewport = (el) => isObject(el) && el.constructor.name === "VisualViewport";
var getNodeName = (node) => {
if (isHTMLElement(node)) return node.localName || "";
return "#document";
};
function isRootElement(node) {
return ["html", "body", "#document"].includes(getNodeName(node));
}
var isNode = (el) => isObject(el) && el.nodeType !== void 0;
var isShadowRoot = (el) => isNode(el) && el.nodeType === DOCUMENT_FRAGMENT_NODE && "host" in el;
var isInputElement = (el) => isHTMLElement(el) && el.localName === "input";
var isAnchorElement = (el) => !!el?.matches("a[href]");
var isElementVisible = (el) => {
if (!isHTMLElement(el)) return false;
return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0;
};
function isActiveElement(element) {
if (!element) return false;
const rootNode = element.getRootNode();
return getActiveElement(rootNode) === element;
}
var TEXTAREA_SELECT_REGEX = /(textarea|select)/;
function isEditableElement(el) {
if (el == null || !isHTMLElement(el)) return false;
try {
return isInputElement(el) && el.selectionStart != null || TEXTAREA_SELECT_REGEX.test(el.localName) || el.isContentEditable || el.getAttribute("contenteditable") === "true" || el.getAttribute("contenteditable") === "";
} catch {
return false;
}
}
function contains(parent, child) {
if (!parent || !child) return false;
if (!isHTMLElement(parent) || !isHTMLElement(child)) return false;
const rootNode = child.getRootNode?.();
if (parent === child) return true;
if (parent.contains(child)) return true;
if (rootNode && isShadowRoot(rootNode)) {
let next = child;
while (next) {
if (parent === next) return true;
next = next.parentNode || next.host;
}
}
return false;
}
function getDocument(el) {
if (isDocument(el)) return el;
if (isWindow(el)) return el.document;
return el?.ownerDocument ?? document;
}
function getDocumentElement(el) {
return getDocument(el).documentElement;
}
function getWindow(el) {
if (isShadowRoot(el)) return getWindow(el.host);
if (isDocument(el)) return el.defaultView ?? window;
if (isHTMLElement(el)) return el.ownerDocument?.defaultView ?? window;
return window;
}
function getActiveElement(rootNode) {
let activeElement = rootNode.activeElement;
while (activeElement?.shadowRoot) {
const el = activeElement.shadowRoot.activeElement;
if (!el || el === activeElement) break;
else activeElement = el;
}
return activeElement;
}
function getParentNode(node) {
if (getNodeName(node) === "html") return node;
const result = node.assignedSlot || node.parentNode || isShadowRoot(node) && node.host || getDocumentElement(node);
return isShadowRoot(result) ? result.host : result;
}
function getRootNode(node) {
let result;
try {
result = node.getRootNode({ composed: true });
if (isDocument(result) || isShadowRoot(result)) return result;
} catch {
}
return node.ownerDocument ?? document;
}
// src/computed-style.ts
var styleCache = /* @__PURE__ */ new WeakMap();
function getComputedStyle(el) {
if (!styleCache.has(el)) {
styleCache.set(el, getWindow(el).getComputedStyle(el));
}
return styleCache.get(el);
}
// src/controller.ts
var INTERACTIVE_CONTAINER_ROLE = /* @__PURE__ */ new Set(["menu", "listbox", "dialog", "grid", "tree", "region"]);
var isInteractiveContainerRole = (role) => INTERACTIVE_CONTAINER_ROLE.has(role);
var getAriaControls = (element) => element.getAttribute("aria-controls")?.split(" ") || [];
function isControlledElement(container, element) {
const visitedIds = /* @__PURE__ */ new Set();
const rootNode = getRootNode(container);
const checkElement = (searchRoot) => {
const controllingElements = searchRoot.querySelectorAll("[aria-controls]");
for (const controller of controllingElements) {
if (controller.getAttribute("aria-expanded") !== "true") continue;
const controlledIds = getAriaControls(controller);
for (const id of controlledIds) {
if (!id || visitedIds.has(id)) continue;
visitedIds.add(id);
const controlledElement = rootNode.getElementById(id);
if (controlledElement) {
const role = controlledElement.getAttribute("role");
const modal = controlledElement.getAttribute("aria-modal") === "true";
if (role && isInteractiveContainerRole(role) && !modal) {
if (controlledElement === element || controlledElement.contains(element)) {
return true;
}
if (checkElement(controlledElement)) {
return true;
}
}
}
}
}
return false;
};
return checkElement(container);
}
function findControlledElements(searchRoot, callback) {
const rootNode = getRootNode(searchRoot);
const visitedIds = /* @__PURE__ */ new Set();
const findRecursive = (root) => {
const controllingElements = root.querySelectorAll("[aria-controls]");
for (const controller of controllingElements) {
if (controller.getAttribute("aria-expanded") !== "true") continue;
const controlledIds = getAriaControls(controller);
for (const id of controlledIds) {
if (!id || visitedIds.has(id)) continue;
visitedIds.add(id);
const controlledElement = rootNode.getElementById(id);
if (controlledElement) {
const role = controlledElement.getAttribute("role");
const modal = controlledElement.getAttribute("aria-modal") === "true";
if (role && INTERACTIVE_CONTAINER_ROLE.has(role) && !modal) {
callback(controlledElement);
findRecursive(controlledElement);
}
}
}
}
};
findRecursive(searchRoot);
}
function getControlledElements(container) {
const controlledElements = /* @__PURE__ */ new Set();
findControlledElements(container, (controlledElement) => {
if (!container.contains(controlledElement)) {
controlledElements.add(controlledElement);
}
});
return Array.from(controlledElements);
}
function isInteractiveContainerElement(element) {
const role = element.getAttribute("role");
return Boolean(role && INTERACTIVE_CONTAINER_ROLE.has(role));
}
function isControllerElement(element) {
return element.hasAttribute("aria-controls") && element.getAttribute("aria-expanded") === "true";
}
function hasControllerElements(element) {
if (isControllerElement(element)) return true;
return Boolean(element.querySelector?.('[aria-controls][aria-expanded="true"]'));
}
function isControlledByExpandedController(element) {
if (!element.id) return false;
const rootNode = getRootNode(element);
const escapedId = CSS.escape(element.id);
const selector = `[aria-controls~="${escapedId}"][aria-expanded="true"], [aria-controls="${escapedId}"][aria-expanded="true"]`;
const controller = rootNode.querySelector(selector);
return Boolean(controller && isInteractiveContainerElement(element));
}
// src/data-url.ts
function getDataUrl(svg, opts) {
const { type, quality = 0.92, background } = opts;
if (!svg) throw new Error("[zag-js > getDataUrl]: Could not find the svg element");
const win = getWindow(svg);
const doc = win.document;
const svgBounds = svg.getBoundingClientRect();
const svgClone = svg.cloneNode(true);
if (!svgClone.hasAttribute("viewBox")) {
svgClone.setAttribute("viewBox", `0 0 ${svgBounds.width} ${svgBounds.height}`);
}
const serializer = new win.XMLSerializer();
const source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svgClone);
const svgString = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
if (type === "image/svg+xml") {
return Promise.resolve(svgString).then((str) => {
svgClone.remove();
return str;
});
}
const dpr = win.devicePixelRatio || 1;
const canvas = doc.createElement("canvas");
const image = new win.Image();
image.src = svgString;
canvas.width = svgBounds.width * dpr;
canvas.height = svgBounds.height * dpr;
const context = canvas.getContext("2d");
if (type === "image/jpeg" || background) {
context.fillStyle = background || "white";
context.fillRect(0, 0, canvas.width, canvas.height);
}
return new Promise((resolve) => {
image.onload = () => {
context?.drawImage(image, 0, 0, canvas.width, canvas.height);
resolve(canvas.toDataURL(type, quality));
svgClone.remove();
};
});
}
// src/platform.ts
var isDom = () => typeof document !== "undefined";
function getPlatform() {
const agent = navigator.userAgentData;
return agent?.platform ?? navigator.platform;
}
function getUserAgent() {
const ua2 = navigator.userAgentData;
if (ua2 && Array.isArray(ua2.brands)) {
return ua2.brands.map(({ brand, version }) => `${brand}/${version}`).join(" ");
}
return navigator.userAgent;
}
var pt = (v) => isDom() && v.test(getPlatform());
var ua = (v) => isDom() && v.test(getUserAgent());
var vn = (v) => isDom() && v.test(navigator.vendor);
var isTouchDevice = () => isDom() && !!navigator.maxTouchPoints;
var isIPhone = () => pt(/^iPhone/i);
var isIPad = () => pt(/^iPad/i) || isMac() && navigator.maxTouchPoints > 1;
var isIos = () => isIPhone() || isIPad();
var isApple = () => isMac() || isIos();
var isMac = () => pt(/^Mac/i);
var isSafari = () => isApple() && vn(/apple/i);
var isFirefox = () => ua(/Firefox/i);
var isChrome = () => ua(/Chrome/i);
var isWebKit = () => ua(/AppleWebKit/i) && !isChrome();
var isAndroid = () => ua(/Android/i);
// src/event.ts
function getBeforeInputValue(event) {
const { selectionStart, selectionEnd, value } = event.currentTarget;
const data = event.data;
return value.slice(0, selectionStart) + (data ?? "") + value.slice(selectionEnd);
}
function getComposedPath(event) {
return event.composedPath?.() ?? event.nativeEvent?.composedPath?.();
}
function getEventTarget(event) {
const composedPath = getComposedPath(event);
return composedPath?.[0] ?? event.target;
}
function isOpeningInNewTab(event) {
const element = event.currentTarget;
if (!element) return false;
const validElement = element.matches("a[href], button[type='submit'], input[type='submit']");
if (!validElement) return false;
const isMiddleClick = event.button === 1;
const isModKeyClick = isCtrlOrMetaKey(event);
return isMiddleClick || isModKeyClick;
}
function isDownloadingEvent(event) {
const element = event.currentTarget;
if (!element) return false;
const localName = element.localName;
if (!event.altKey) return false;
if (localName === "a") return true;
if (localName === "button" && element.type === "submit") return true;
if (localName === "input" && element.type === "submit") return true;
return false;
}
function isComposingEvent(event) {
return getNativeEvent(event).isComposing || event.keyCode === 229;
}
function isKeyboardClick(e) {
return e.detail === 0 || e.clientX === 0 && e.clientY === 0;
}
function isCtrlOrMetaKey(e) {
if (isMac()) return e.metaKey;
return e.ctrlKey;
}
function isPrintableKey(e) {
return e.key.length === 1 && !e.ctrlKey && !e.metaKey;
}
function isVirtualPointerEvent(e) {
return e.width === 0 && e.height === 0 || e.width === 1 && e.height === 1 && e.pressure === 0 && e.detail === 0 && e.pointerType === "mouse";
}
function isVirtualClick(e) {
if (e.pointerType === "" && e.isTrusted) return true;
if (isAndroid() && e.pointerType) {
return e.type === "click" && e.buttons === 1;
}
return e.detail === 0 && !e.pointerType;
}
var isLeftClick = (e) => e.button === 0;
var isContextMenuEvent = (e) => {
return e.button === 2 || isMac() && e.ctrlKey && e.button === 0;
};
var isModifierKey = (e) => e.ctrlKey || e.altKey || e.metaKey;
var isTouchEvent = (event) => "touches" in event && event.touches.length > 0;
var keyMap = {
Up: "ArrowUp",
Down: "ArrowDown",
Esc: "Escape",
" ": "Space",
",": "Comma",
Left: "ArrowLeft",
Right: "ArrowRight"
};
var rtlKeyMap = {
ArrowLeft: "ArrowRight",
ArrowRight: "ArrowLeft"
};
function getEventKey(event, options = {}) {
const { dir = "ltr", orientation = "horizontal" } = options;
let key = event.key;
key = keyMap[key] ?? key;
const isRtl = dir === "rtl" && orientation === "horizontal";
if (isRtl && key in rtlKeyMap) key = rtlKeyMap[key];
return key;
}
function getNativeEvent(event) {
return event.nativeEvent ?? event;
}
var pageKeys = /* @__PURE__ */ new Set(["PageUp", "PageDown"]);
var arrowKeys = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]);
function getEventStep(event) {
if (event.ctrlKey || event.metaKey) {
return 0.1;
} else {
const isPageKey = pageKeys.has(event.key);
const isSkipKey = isPageKey || event.shiftKey && arrowKeys.has(event.key);
return isSkipKey ? 10 : 1;
}
}
function getEventPoint(event, type = "client") {
const point = isTouchEvent(event) ? event.touches[0] || event.changedTouches[0] : event;
return { x: point[`${type}X`], y: point[`${type}Y`] };
}
var addDomEvent = (target, eventName, handler, options) => {
const node = typeof target === "function" ? target() : target;
node?.addEventListener(eventName, handler, options);
return () => {
node?.removeEventListener(eventName, handler, options);
};
};
// src/form.ts
function getDescriptor(el, options) {
const { type = "HTMLInputElement", property = "value" } = options;
const proto = getWindow(el)[type].prototype;
return Object.getOwnPropertyDescriptor(proto, property) ?? {};
}
function getElementType(el) {
if (el.localName === "input") return "HTMLInputElement";
if (el.localName === "textarea") return "HTMLTextAreaElement";
if (el.localName === "select") return "HTMLSelectElement";
}
function setElementValue(el, value, property = "value") {
if (!el) return;
const type = getElementType(el);
if (type) {
const descriptor = getDescriptor(el, { type, property });
descriptor.set?.call(el, value);
}
el.setAttribute(property, value);
}
function setElementChecked(el, checked) {
if (!el) return;
const descriptor = getDescriptor(el, { type: "HTMLInputElement", property: "checked" });
descriptor.set?.call(el, checked);
if (checked) el.setAttribute("checked", "");
else el.removeAttribute("checked");
}
function dispatchInputValueEvent(el, options) {
const { value, bubbles = true } = options;
if (!el) return;
const win = getWindow(el);
if (!(el instanceof win.HTMLInputElement)) return;
setElementValue(el, `${value}`);
el.dispatchEvent(new win.Event("input", { bubbles }));
}
function dispatchInputCheckedEvent(el, options) {
const { checked, bubbles = true } = options;
if (!el) return;
const win = getWindow(el);
if (!(el instanceof win.HTMLInputElement)) return;
setElementChecked(el, checked);
el.dispatchEvent(new win.Event("click", { bubbles }));
}
function getClosestForm(el) {
return isFormElement(el) ? el.form : el.closest("form");
}
function isFormElement(el) {
return el.matches("textarea, input, select, button");
}
function trackFormReset(el, callback) {
if (!el) return;
const form = getClosestForm(el);
const onReset = (e) => {
if (e.defaultPrevented) return;
callback();
};
form?.addEventListener("reset", onReset, { passive: true });
return () => form?.removeEventListener("reset", onReset);
}
function trackFieldsetDisabled(el, callback) {
const fieldset = el?.closest("fieldset");
if (!fieldset) return;
callback(fieldset.disabled);
const win = getWindow(fieldset);
const obs = new win.MutationObserver(() => callback(fieldset.disabled));
obs.observe(fieldset, {
attributes: true,
attributeFilter: ["disabled"]
});
return () => obs.disconnect();
}
function trackFormControl(el, options) {
if (!el) return;
const { onFieldsetDisabledChange, onFormReset } = options;
const cleanups = [trackFormReset(el, onFormReset), trackFieldsetDisabled(el, onFieldsetDisabledChange)];
return () => cleanups.forEach((cleanup) => cleanup?.());
}
// src/tabbable.ts
var isFrame = (el) => isHTMLElement(el) && el.tagName === "IFRAME";
var hasTabIndex = (el) => !Number.isNaN(parseInt(el.getAttribute("tabindex") || "0", 10));
var hasNegativeTabIndex = (el) => parseInt(el.getAttribute("tabindex") || "0", 10) < 0;
var focusableSelector = "input:not([type='hidden']):not([disabled]), select:not([disabled]), textarea:not([disabled]), a[href], button:not([disabled]), [tabindex], iframe, object, embed, area[href], audio[controls], video[controls], [contenteditable]:not([contenteditable='false']), details > summary:first-of-type";
var getFocusables = (container, includeContainer = false) => {
if (!container) return [];
const elements = Array.from(container.querySelectorAll(focusableSelector));
const include = includeContainer == true || includeContainer == "if-empty" && elements.length === 0;
if (include && isHTMLElement(container) && isFocusable(container)) {
elements.unshift(container);
}
const focusableElements = elements.filter(isFocusable);
focusableElements.forEach((element, i) => {
if (isFrame(element) && element.contentDocument) {
const frameBody = element.contentDocument.body;
focusableElements.splice(i, 1, ...getFocusables(frameBody));
}
});
return focusableElements;
};
function isFocusable(element) {
if (!element || element.closest("[inert]")) return false;
return element.matches(focusableSelector) && isElementVisible(element);
}
function getFirstFocusable(container, includeContainer) {
const [first] = getFocusables(container, includeContainer);
return first || null;
}
function getTabbables(container, includeContainer) {
if (!container) return [];
const elements = Array.from(container.querySelectorAll(focusableSelector));
const tabbableElements = elements.filter(isTabbable);
if (includeContainer && isTabbable(container)) {
tabbableElements.unshift(container);
}
tabbableElements.forEach((element, i) => {
if (isFrame(element) && element.contentDocument) {
const frameBody = element.contentDocument.body;
const allFrameTabbable = getTabbables(frameBody);
tabbableElements.splice(i, 1, ...allFrameTabbable);
}
});
if (!tabbableElements.length && includeContainer) {
return elements;
}
return tabbableElements;
}
function isTabbable(el) {
if (el != null && el.tabIndex > 0) return true;
return isFocusable(el) && !hasNegativeTabIndex(el);
}
function getFirstTabbable(container, includeContainer) {
const [first] = getTabbables(container, includeContainer);
return first || null;
}
function getLastTabbable(container, includeContainer) {
const elements = getTabbables(container, includeContainer);
return elements[elements.length - 1] || null;
}
function getTabbableEdges(container, includeContainer) {
const elements = getTabbables(container, includeContainer);
const first = elements[0] || null;
const last = elements[elements.length - 1] || null;
return [first, last];
}
function getNextTabbable(container, current) {
const tabbables = getTabbables(container);
const doc = container?.ownerDocument || document;
const currentElement = current ?? getActiveElement(doc);
if (!currentElement) return null;
const index = tabbables.indexOf(currentElement);
return tabbables[index + 1] || null;
}
function getTabIndex(node) {
if (node.tabIndex < 0) {
if ((/^(audio|video|details)$/.test(node.localName) || isEditableElement(node)) && !hasTabIndex(node)) {
return 0;
}
}
return node.tabIndex;
}
// src/initial-focus.ts
function getInitialFocus(options) {
const { root, getInitialEl, filter, enabled = true } = options;
if (!enabled) return;
let node = null;
node || (node = typeof getInitialEl === "function" ? getInitialEl() : getInitialEl);
node || (node = root?.querySelector("[data-autofocus],[autofocus]"));
if (!node) {
const tabbables = getTabbables(root);
node = filter ? tabbables.filter(filter)[0] : tabbables[0];
}
return node || root || void 0;
}
function isValidTabEvent(event) {
const container = event.currentTarget;
if (!container) return false;
const [firstTabbable, lastTabbable] = getTabbableEdges(container);
if (isActiveElement(firstTabbable) && event.shiftKey) return false;
if (isActiveElement(lastTabbable) && !event.shiftKey) return false;
if (!firstTabbable && !lastTabbable) return false;
return true;
}
// src/raf.ts
function nextTick(fn) {
const set = /* @__PURE__ */ new Set();
function raf2(fn2) {
const id = globalThis.requestAnimationFrame(fn2);
set.add(() => globalThis.cancelAnimationFrame(id));
}
raf2(() => raf2(fn));
return function cleanup() {
set.forEach((fn2) => fn2());
};
}
function raf(fn) {
let cleanup;
const id = globalThis.requestAnimationFrame(() => {
cleanup = fn();
});
return () => {
globalThis.cancelAnimationFrame(id);
cleanup?.();
};
}
function queueBeforeEvent(el, type, cb) {
const cancelTimer = raf(() => {
el.removeEventListener(type, exec, true);
cb();
});
const exec = () => {
cancelTimer();
cb();
};
el.addEventListener(type, exec, { once: true, capture: true });
return cancelTimer;
}
// src/mutation-observer.ts
function observeAttributesImpl(node, options) {
if (!node) return;
const { attributes, callback: fn } = options;
const win = node.ownerDocument.defaultView || window;
const obs = new win.MutationObserver((changes) => {
for (const change of changes) {
if (change.type === "attributes" && change.attributeName && attributes.includes(change.attributeName)) {
fn(change);
}
}
});
obs.observe(node, { attributes: true, attributeFilter: attributes });
return () => obs.disconnect();
}
function observeAttributes(nodeOrFn, options) {
const { defer } = options;
const func = defer ? raf : (v) => v();
const cleanups = [];
cleanups.push(
func(() => {
const node = typeof nodeOrFn === "function" ? nodeOrFn() : nodeOrFn;
cleanups.push(observeAttributesImpl(node, options));
})
);
return () => {
cleanups.forEach((fn) => fn?.());
};
}
function observeChildrenImpl(node, options) {
const { callback: fn } = options;
if (!node) return;
const win = node.ownerDocument.defaultView || window;
const obs = new win.MutationObserver(fn);
obs.observe(node, { childList: true, subtree: true });
return () => obs.disconnect();
}
function observeChildren(nodeOrFn, options) {
const { defer } = options;
const func = defer ? raf : (v) => v();
const cleanups = [];
cleanups.push(
func(() => {
const node = typeof nodeOrFn === "function" ? nodeOrFn() : nodeOrFn;
cleanups.push(observeChildrenImpl(node, options));
})
);
return () => {
cleanups.forEach((fn) => fn?.());
};
}
// src/navigate.ts
function clickIfLink(el) {
const click = () => {
const win = getWindow(el);
el.dispatchEvent(new win.MouseEvent("click"));
};
if (isFirefox()) {
queueBeforeEvent(el, "keyup", click);
} else {
queueMicrotask(click);
}
}
// src/overflow.ts
function getNearestOverflowAncestor(el) {
const parentNode = getParentNode(el);
if (isRootElement(parentNode)) return getDocument(parentNode).body;
if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) return parentNode;
return getNearestOverflowAncestor(parentNode);
}
function getOverflowAncestors(el, list = []) {
const scrollableAncestor = getNearestOverflowAncestor(el);
const isBody = scrollableAncestor === el.ownerDocument.body;
const win = getWindow(scrollableAncestor);
if (isBody) {
return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : []);
}
return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, []));
}
var getElementRect = (el) => {
if (isHTMLElement(el)) return el.getBoundingClientRect();
if (isVisualViewport(el)) return { top: 0, left: 0, bottom: el.height, right: el.width };
return { top: 0, left: 0, bottom: el.innerHeight, right: el.innerWidth };
};
function isInView(el, ancestor) {
if (!isHTMLElement(el)) return true;
const ancestorRect = getElementRect(ancestor);
const elRect = el.getBoundingClientRect();
return elRect.top >= ancestorRect.top && elRect.left >= ancestorRect.left && elRect.bottom <= ancestorRect.bottom && elRect.right <= ancestorRect.right;
}
var OVERFLOW_RE = /auto|scroll|overlay|hidden|clip/;
var nonOverflowValues = /* @__PURE__ */ new Set(["inline", "contents"]);
function isOverflowElement(el) {
const win = getWindow(el);
const { overflow, overflowX, overflowY, display } = win.getComputedStyle(el);
return OVERFLOW_RE.test(overflow + overflowY + overflowX) && !nonOverflowValues.has(display);
}
function isScrollable(el) {
return el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth;
}
function scrollIntoView(el, options) {
const { rootEl, ...scrollOptions } = options || {};
if (!el || !rootEl) return;
if (!isOverflowElement(rootEl) || !isScrollable(rootEl)) return;
el.scrollIntoView(scrollOptions);
}
function getScrollPosition(element) {
if (isHTMLElement(element)) {
return { scrollLeft: element.scrollLeft, scrollTop: element.scrollTop };
}
return { scrollLeft: element.scrollX, scrollTop: element.scrollY };
}
// src/point.ts
function getRelativePoint(point, element) {
const { left, top, width, height } = element.getBoundingClientRect();
const offset = { x: point.x - left, y: point.y - top };
const percent = { x: clamp(offset.x / width), y: clamp(offset.y / height) };
function getPercentValue(options = {}) {
const { dir = "ltr", orientation = "horizontal", inverted } = options;
const invertX = typeof inverted === "object" ? inverted.x : inverted;
const invertY = typeof inverted === "object" ? inverted.y : inverted;
if (orientation === "horizontal") {
return dir === "rtl" || invertX ? 1 - percent.x : percent.x;
}
return invertY ? 1 - percent.y : percent.y;
}
return { offset, percent, getPercentValue };
}
// src/pointer-lock.ts
function requestPointerLock(doc, fn) {
const body = doc.body;
const supported = "pointerLockElement" in doc || "mozPointerLockElement" in doc;
const isLocked = () => !!doc.pointerLockElement;
function onPointerChange() {
fn?.(isLocked());
}
function onPointerError(event) {
if (isLocked()) fn?.(false);
console.error("PointerLock error occurred:", event);
doc.exitPointerLock();
}
if (!supported) return;
try {
body.requestPointerLock();
} catch {
}
const cleanup = [
addDomEvent(doc, "pointerlockchange", onPointerChange, false),
addDomEvent(doc, "pointerlockerror", onPointerError, false)
];
return () => {
cleanup.forEach((cleanup2) => cleanup2());
doc.exitPointerLock();
};
}
// src/text-selection.ts
var state = "default";
var userSelect = "";
var elementMap = /* @__PURE__ */ new WeakMap();
function disableTextSelectionImpl(options = {}) {
const { target, doc } = options;
const docNode = doc ?? document;
const rootEl = docNode.documentElement;
if (isIos()) {
if (state === "default") {
userSelect = rootEl.style.webkitUserSelect;
rootEl.style.webkitUserSelect = "none";
}
state = "disabled";
} else if (target) {
elementMap.set(target, target.style.userSelect);
target.style.userSelect = "none";
}
return () => restoreTextSelection({ target, doc: docNode });
}
function restoreTextSelection(options = {}) {
const { target, doc } = options;
const docNode = doc ?? document;
const rootEl = docNode.documentElement;
if (isIos()) {
if (state !== "disabled") return;
state = "restoring";
setTimeout(() => {
nextTick(() => {
if (state === "restoring") {
if (rootEl.style.webkitUserSelect === "none") {
rootEl.style.webkitUserSelect = userSelect || "";
}
userSelect = "";
state = "default";
}
});
}, 300);
} else {
if (target && elementMap.has(target)) {
const prevUserSelect = elementMap.get(target);
if (target.style.userSelect === "none") {
target.style.userSelect = prevUserSelect ?? "";
}
if (target.getAttribute("style") === "") {
target.removeAttribute("style");
}
elementMap.delete(target);
}
}
}
function disableTextSelection(options = {}) {
const { defer, target, ...restOptions } = options;
const func = defer ? raf : (v) => v();
const cleanups = [];
cleanups.push(
func(() => {
const node = typeof target === "function" ? target() : target;
cleanups.push(disableTextSelectionImpl({ ...restOptions, target: node }));
})
);
return () => {
cleanups.forEach((fn) => fn?.());
};
}
// src/pointer-move.ts
function trackPointerMove(doc, handlers) {
const { onPointerMove, onPointerUp } = handlers;
const handleMove = (event) => {
const point = getEventPoint(event);
const distance = Math.sqrt(point.x ** 2 + point.y ** 2);
const moveBuffer = event.pointerType === "touch" ? 10 : 5;
if (distance < moveBuffer) return;
if (event.pointerType === "mouse" && event.button === 0) {
handleUp(event);
return;
}
onPointerMove({ point, event });
};
const handleUp = (event) => {
const point = getEventPoint(event);
onPointerUp({ point, event });
};
const cleanups = [
addDomEvent(doc, "pointermove", handleMove, false),
addDomEvent(doc, "pointerup", handleUp, false),
addDomEvent(doc, "pointercancel", handleUp, false),
addDomEvent(doc, "contextmenu", handleUp, false),
disableTextSelection({ doc })
];
return () => {
cleanups.forEach((cleanup) => cleanup());
};
}
// src/press.ts
function trackPress(options) {
const {
pointerNode,
keyboardNode = pointerNode,
onPress,
onPressStart,
onPressEnd,
isValidKey = (e) => e.key === "Enter"
} = options;
if (!pointerNode) return noop;
const win = getWindow(pointerNode);
let removeStartListeners = noop;
let removeEndListeners = noop;
let removeAccessibleListeners = noop;
const getInfo = (event) => ({
point: getEventPoint(event),
event
});
function startPress(event) {
onPressStart?.(getInfo(event));
}
function cancelPress(event) {
onPressEnd?.(getInfo(event));
}
const startPointerPress = (startEvent) => {
removeEndListeners();
const endPointerPress = (endEvent) => {
const target = getEventTarget(endEvent);
if (contains(pointerNode, target)) {
onPress?.(getInfo(endEvent));
} else {
onPressEnd?.(getInfo(endEvent));
}
};
const removePointerUpListener = addDomEvent(win, "pointerup", endPointerPress, { passive: !onPress, once: true });
const removePointerCancelListener = addDomEvent(win, "pointercancel", cancelPress, {
passive: !onPressEnd,
once: true
});
removeEndListeners = pipe(removePointerUpListener, removePointerCancelListener);
if (isActiveElement(keyboardNode) && startEvent.pointerType === "mouse") {
startEvent.preventDefault();
}
startPress(startEvent);
};
const removePointerListener = addDomEvent(pointerNode, "pointerdown", startPointerPress, { passive: !onPressStart });
const removeFocusListener = addDomEvent(keyboardNode, "focus", startAccessiblePress);
removeStartListeners = pipe(removePointerListener, removeFocusListener);
function startAccessiblePress() {
const handleKeydown = (keydownEvent) => {
if (!isValidKey(keydownEvent)) return;
const handleKeyup = (keyupEvent) => {
if (!isValidKey(keyupEvent)) return;
const evt2 = new win.PointerEvent("pointerup");
const info = getInfo(evt2);
onPress?.(info);
onPressEnd?.(info);
};
removeEndListeners();
removeEndListeners = addDomEvent(keyboardNode, "keyup", handleKeyup);
const evt = new win.PointerEvent("pointerdown");
startPress(evt);
};
const handleBlur = () => {
const evt = new win.PointerEvent("pointercancel");
cancelPress(evt);
};
const removeKeydownListener = addDomEvent(keyboardNode, "keydown", handleKeydown);
const removeBlurListener = addDomEvent(keyboardNode, "blur", handleBlur);
removeAccessibleListeners = pipe(removeKeydownListener, removeBlurListener);
}
return () => {
removeStartListeners();
removeEndListeners();
removeAccessibleListeners();
};
}
// src/proxy-tab-focus.ts
function proxyTabFocusImpl(container, options = {}) {
const { triggerElement, onFocus, onFocusEnter } = options;
const doc = container?.ownerDocument || document;
const body = doc.body;
function onKeyDown(event) {
if (event.key !== "Tab") return;
let elementToFocus = null;
const [firstTabbable, lastTabbable] = getTabbableEdges(container, true);
const nextTabbableAfterTrigger = getNextTabbable(body, triggerElement);
const noTabbableElements = !firstTabbable && !lastTabbable;
if (event.shiftKey && isActiveElement(nextTabbableAfterTrigger)) {
onFocusEnter?.();
elementToFocus = lastTabbable;
} else if (event.shiftKey && (isActiveElement(firstTabbable) || noTabbableElements)) {
elementToFocus = triggerElement;
} else if (!event.shiftKey && isActiveElement(triggerElement)) {
onFocusEnter?.();
elementToFocus = firstTabbable;
} else if (!event.shiftKey && (isActiveElement(lastTabbable) || noTabbableElements)) {
elementToFocus = nextTabbableAfterTrigger;
}
if (!elementToFocus) return;
event.preventDefault();
if (typeof onFocus === "function") {
onFocus(elementToFocus);
} else {
elementToFocus.focus();
}
}
return addDomEvent(doc, "keydown", onKeyDown, true);
}
function proxyTabFocus(container, options) {
const { defer, triggerElement, ...restOptions } = options;
const func = defer ? raf : (v) => v();
const cleanups = [];
cleanups.push(
func(() => {
const node = typeof container === "function" ? container() : container;
const trigger = typeof triggerElement === "function" ? triggerElement() : triggerElement;
cleanups.push(proxyTabFocusImpl(node, { triggerElement: trigger, ...restOptions }));
})
);
return () => {
cleanups.forEach((fn) => fn?.());
};
}
// src/query.ts
function queryAll(root, selector) {
return Array.from(root?.querySelectorAll(selector) ?? []);
}
function query(root, selector) {
return root?.querySelector(selector) ?? null;
}
var defaultItemToId = (v) => v.id;
function itemById(v, id, itemToId = defaultItemToId) {
return v.find((item) => itemToId(item) === id);
}
function indexOfId(v, id, itemToId = defaultItemToId) {
const item = itemById(v, id, itemToId);
return item ? v.indexOf(item) : -1;
}
function nextById(v, id, loop = true) {
let idx = indexOfId(v, id);
idx = loop ? (idx + 1) % v.length : Math.min(idx + 1, v.length - 1);
return v[idx];
}
function prevById(v, id, loop = true) {
let idx = indexOfId(v, id);
if (idx === -1) return loop ? v[v.length - 1] : null;
idx = loop ? (idx - 1 + v.length) % v.length : Math.max(0, idx - 1);
return v[idx];
}
// src/resize-observer.ts
function trackElementRect(elements, options) {
const { onEntry, measure, box = "border-box" } = options;
const elems = (Array.isArray(elements) ? elements : [elements]).filter(isHTMLElement);
const win = getWindow(elems[0]);
const trigger = (entries) => {
const rects = elems.map((el) => measure(el));
onEntry({ rects, entries });
};
trigger([]);
const obs = new win.ResizeObserver(trigger);
elems.forEach((el) => obs.observe(el, { box }));
return () => obs.disconnect();
}
// src/scope.ts
function createScope(methods) {
const dom = {
getRootNode: (ctx) => ctx.getRootNode?.() ?? document,
getDoc: (ctx) => getDocument(dom.getRootNode(ctx)),
getWin: (ctx) => dom.getDoc(ctx).defaultView ?? window,
getActiveElement: (ctx) => getActiveElement(dom.getRootNode(ctx)),
isActiveElement,
getById: (ctx, id) => dom.getRootNode(ctx).getElementById(id),
setValue: (elem, value) => {
if (elem == null || value == null) return;
setElementValue(elem, value.toString());
}
};
return { ...dom, ...methods };
}
// src/searchable.ts
var sanitize = (str) => str.split("").map((char) => {
const code = char.charCodeAt(0);
if (code > 0 && code < 128) return char;
if (code >= 128 && code <= 255) return `/x${code.toString(16)}`.replace("/", "\\");
return "";
}).join("").trim();
var getValueText = (el) => {
return sanitize(el.dataset?.valuetext ?? el.textContent ?? "");
};
var match = (valueText, query2) => {
return valueText.trim().toLowerCase().startsWith(query2.toLowerCase());
};
function getByText(v, text, currentId, itemToId = defaultItemToId) {
const index = currentId ? indexOfId(v, currentId, itemToId) : -1;
let items = currentId ? wrap(v, index) : v;
const isSingleKey = text.length === 1;
if (isSingleKey) {
items = items.filter((item) => itemToId(item) !== currentId);
}
return items.find((item) => match(getValueText(item), text));
}
// src/set.ts
function setAttribute(el, attr, v) {
const prev = el.getAttribute(attr);
const exists = prev != null;
el.setAttribute(attr, v);
return () => {
if (!exists) {
el.removeAttribute(attr);
} else {
el.setAttribute(attr, prev);
}
};
}
function setProperty(el, prop, v) {
const exists = prop in el;
const prev = el[prop];
el[prop] = v;
return () => {
if (!exists) {
delete el[prop];
} else {
el[prop] = prev;
}
};
}
function setStyle(el, style) {
if (!el) return noop;
const prev = Object.keys(style).reduce((acc, key) => {
acc[key] = el.style.getPropertyValue(key);
return acc;
}, {});
Object.assign(el.style, style);
return () => {
Object.assign(el.style, prev);
if (el.style.length === 0) {
el.removeAttribute("style");
}
};
}
function setStyleProperty(el, prop, value) {
if (!el) return noop;
const prev = el.style.getPropertyValue(prop);
el.style.setProperty(prop, value);
return () => {
el.style.setProperty(prop, prev);
if (el.style.length === 0) {
el.removeAttribute("style");
}
};
}
// src/typeahead.ts
function getByTypeaheadImpl(baseItems, options) {
const { state: state2, activeId, key, timeout = 350, itemToId } = options;
const search = state2.keysSoFar + key;
const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
const query2 = isRepeated ? search[0] : search;
let items = baseItems.slice();
const next = getByText(items, query2, activeId, itemToId);
function cleanup() {
clearTimeout(state2.timer);
state2.timer = -1;
}
function update(value) {
state2.keysSoFar = value;
cleanup();
if (value !== "") {
state2.timer = +setTimeout(() => {
update("");
cleanup();
}, timeout);
}
}
update(search);
return next;
}
var getByTypeahead = /* @__PURE__ */ Object.assign(getByTypeaheadImpl, {
defaultOptions: { keysSoFar: "", timer: -1 },
isValidEvent: isValidTypeaheadEvent
});
function isValidTypeaheadEvent(event) {
return event.key.length === 1 && !event.ctrlKey && !event.metaKey;
}
// src/visual-viewport.ts
function trackVisualViewport(doc, fn) {
const win = doc?.defaultView || window;
const onResize = () => {
fn?.(getViewportSize(win));
};
onResize();
return addDomEvent(win.visualViewport ?? win, "resize", onResize);
}
function getViewportSize(win) {
return {
width: win.visualViewport?.width || win.innerWidth,
height: win.visualViewport?.height || win.innerHeight
};
}
// src/visually-hidden.ts
var visuallyHiddenStyle = {
border: "0",
clip: "rect(0 0 0 0)",
height: "1px",
margin: "-1px",
overflow: "hidden",
padding: "0",
position: "absolute",
width: "1px",
whiteSpace: "nowrap",
wordWrap: "normal"
};
function setVisuallyHidden(el) {
Object.assign(el.style, visuallyHiddenStyle);
}
// src/wait-for.ts
function waitForPromise(promise, controller, timeout) {
const { signal } = controller;
const wrappedPromise = new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error(`Timeout of ${timeout}ms exceeded`));
}, timeout);
signal.addEventListener("abort", () => {
clearTimeout(timeoutId);
reject(new Error("Promise aborted"));
});
promise.then((result) => {
if (!signal.aborted) {
clearTimeout(timeoutId);
resolve(result);
}
}).catch((error) => {
if (!signal.aborted) {
clearTimeout(timeoutId);
reject(error);
}
});
});
const abort = () => controller.abort();
return [wrappedPromise, abort];
}
function waitForElement(target, options) {
const { timeout, rootNode } = options;
const win = getWindow(rootNode);
const doc = getDocument(rootNode);
const controller = new win.AbortController();
return waitForPromise(
new Promise((resolve) => {
const el = target();
if (el) {
resolve(el);
return;
}
const observer = new win.MutationObserver(() => {
const el2 = target();
if (el2 && el2.isConnected) {
observer.disconnect();
resolve(el2);
}
});
observer.observe(doc.body, {
childList: true,
subtree: true
});
}),
controller,
timeout
);
}
export { MAX_Z_INDEX, addDomEvent, ariaAttr, clickIfLink, contains, createScope, dataAttr, defaultItemToId, disableTextSelection, dispatchInputCheckedEvent, dispatchInputValueEvent, findControlledElements, getActiveElement, getBeforeInputValue, getByText, getByTypeahead, getComputedStyle, getControlledElements, getDataUrl, getDocument, getDocumentElement, getEventKey, getEventPoint, getEventStep, getEventTarget, getFirstFocusable, getFirstTabbable, getFocusables, getInitialFocus, getLastTabbable, getNativeEvent, getNearestOverflowAncestor, getNextTabbable, getNodeName, getOverflowAncestors, getParentNode, getPlatform, getRelativePoint, getRootNode, getScrollPosition, getTabIndex, getTabbableEdges, getTabbables, getUserAgent, getWindow, hasControllerElements, indexOfId, isActiveElement, isAnchorElement, isAndroid, isApple, isCaretAtStart, isChrome, isComposingEvent, isContextMenuEvent, isControlledByExpandedController, isControlledElement, isControllerElement, isCtrlOrMetaKey, isDocument, isDom, isDownloadingEvent, isEditableElement, isElementVisible, isFirefox, isFocusable, isHTMLElement, isIPad, isIPhone, isInView, isInputElement, isInteractiveContainerElement, isIos, isKeyboardClick, isLeftClick, isMac, isModifierKey, isNode, isOpeningInNewTab, isOverflowElement, isPrintableKey, isRootElement, isSafari, isShadowRoot, isTabbable, isTouchDevice, isTouchEvent, isValidTabEvent, isVirtualClick, isVirtualPointerEvent, isVisualViewport, isWebKit, isWindow, itemById, nextById, nextTick, observeAttributes, observeChildren, prevById, proxyTabFocus, query, queryAll, queueBeforeEvent, raf, requestPointerLock, restoreTextSelection, scrollIntoView, setAttribute, setCaretToEnd, setElementChecked, setElementValue, setProperty, setStyle, setStyleProperty, setVisuallyHidden, trackElementRect, trackFormControl, trackPointerMove, trackPress, trackVisualViewport, visuallyHiddenStyle, waitForElement, waitForPromise };