@ariakit/core
Version:
Ariakit core
226 lines (223 loc) • 7.03 kB
JavaScript
"use client";
// src/utils/dom.ts
var canUseDOM = checkIsBrowser();
function checkIsBrowser() {
var _a;
return typeof window !== "undefined" && !!((_a = window.document) == null ? void 0 : _a.createElement);
}
function getDocument(node) {
if (!node) return document;
if ("self" in node) return node.document;
return node.ownerDocument || document;
}
function getWindow(node) {
if (!node) return self;
if ("self" in node) return node.self;
return getDocument(node).defaultView || window;
}
function getActiveElement(node, activeDescendant = false) {
const { activeElement } = getDocument(node);
if (!(activeElement == null ? void 0 : activeElement.nodeName)) {
return null;
}
if (isFrame(activeElement) && activeElement.contentDocument) {
return getActiveElement(
activeElement.contentDocument.body,
activeDescendant
);
}
if (activeDescendant) {
const id = activeElement.getAttribute("aria-activedescendant");
if (id) {
const element = getDocument(activeElement).getElementById(id);
if (element) {
return element;
}
}
}
return activeElement;
}
function contains(parent, child) {
return parent === child || parent.contains(child);
}
function isFrame(element) {
return element.tagName === "IFRAME";
}
function isButton(element) {
const tagName = element.tagName.toLowerCase();
if (tagName === "button") return true;
if (tagName === "input" && element.type) {
return buttonInputTypes.indexOf(element.type) !== -1;
}
return false;
}
var buttonInputTypes = [
"button",
"color",
"file",
"image",
"reset",
"submit"
];
function isVisible(element) {
if (typeof element.checkVisibility === "function") {
return element.checkVisibility();
}
const htmlElement = element;
return htmlElement.offsetWidth > 0 || htmlElement.offsetHeight > 0 || element.getClientRects().length > 0;
}
function isTextField(element) {
try {
const isTextInput = element instanceof HTMLInputElement && element.selectionStart !== null;
const isTextArea = element.tagName === "TEXTAREA";
return isTextInput || isTextArea || false;
} catch (error) {
return false;
}
}
function isTextbox(element) {
return element.isContentEditable || isTextField(element);
}
function getTextboxValue(element) {
if (isTextField(element)) {
return element.value;
}
if (element.isContentEditable) {
const range = getDocument(element).createRange();
range.selectNodeContents(element);
return range.toString();
}
return "";
}
function getTextboxSelection(element) {
let start = 0;
let end = 0;
if (isTextField(element)) {
start = element.selectionStart || 0;
end = element.selectionEnd || 0;
} else if (element.isContentEditable) {
const selection = getDocument(element).getSelection();
if ((selection == null ? void 0 : selection.rangeCount) && selection.anchorNode && contains(element, selection.anchorNode) && selection.focusNode && contains(element, selection.focusNode)) {
const range = selection.getRangeAt(0);
const nextRange = range.cloneRange();
nextRange.selectNodeContents(element);
nextRange.setEnd(range.startContainer, range.startOffset);
start = nextRange.toString().length;
nextRange.setEnd(range.endContainer, range.endOffset);
end = nextRange.toString().length;
}
}
return { start, end };
}
function getPopupRole(element, fallback) {
const allowedPopupRoles = ["dialog", "menu", "listbox", "tree", "grid"];
const role = element == null ? void 0 : element.getAttribute("role");
if (role && allowedPopupRoles.indexOf(role) !== -1) {
return role;
}
return fallback;
}
function getPopupItemRole(element, fallback) {
var _a;
const itemRoleByPopupRole = {
menu: "menuitem",
listbox: "option",
tree: "treeitem"
};
const popupRole = getPopupRole(element);
if (!popupRole) return fallback;
const key = popupRole;
return (_a = itemRoleByPopupRole[key]) != null ? _a : fallback;
}
function scrollIntoViewIfNeeded(element, arg) {
if (isPartiallyHidden(element) && "scrollIntoView" in element) {
element.scrollIntoView(arg);
}
}
function getScrollingElement(element) {
if (!element) return null;
const isScrollableOverflow = (overflow) => {
if (overflow === "auto") return true;
if (overflow === "scroll") return true;
return false;
};
if (element.clientHeight && element.scrollHeight > element.clientHeight) {
const { overflowY } = getComputedStyle(element);
if (isScrollableOverflow(overflowY)) return element;
} else if (element.clientWidth && element.scrollWidth > element.clientWidth) {
const { overflowX } = getComputedStyle(element);
if (isScrollableOverflow(overflowX)) return element;
}
return getScrollingElement(element.parentElement) || document.scrollingElement || document.body;
}
function isPartiallyHidden(element) {
const elementRect = element.getBoundingClientRect();
const scroller = getScrollingElement(element);
if (!scroller) return false;
const scrollerRect = scroller.getBoundingClientRect();
const isHTML = scroller.tagName === "HTML";
const scrollerTop = isHTML ? scrollerRect.top + scroller.scrollTop : scrollerRect.top;
const scrollerBottom = isHTML ? scroller.clientHeight : scrollerRect.bottom;
const scrollerLeft = isHTML ? scrollerRect.left + scroller.scrollLeft : scrollerRect.left;
const scrollerRight = isHTML ? scroller.clientWidth : scrollerRect.right;
const top = elementRect.top < scrollerTop;
const left = elementRect.left < scrollerLeft;
const bottom = elementRect.bottom > scrollerBottom;
const right = elementRect.right > scrollerRight;
return top || left || bottom || right;
}
function setSelectionRange(element, ...args) {
if (/text|search|password|tel|url/i.test(element.type)) {
element.setSelectionRange(...args);
}
}
function sortBasedOnDOMPosition(items, getElement) {
const pairs = items.map((item, index) => [index, item]);
let isOrderDifferent = false;
pairs.sort(([indexA, a], [indexB, b]) => {
const elementA = getElement(a);
const elementB = getElement(b);
if (elementA === elementB) return 0;
if (!elementA || !elementB) return 0;
if (isElementPreceding(elementA, elementB)) {
if (indexA > indexB) {
isOrderDifferent = true;
}
return -1;
}
if (indexA < indexB) {
isOrderDifferent = true;
}
return 1;
});
if (isOrderDifferent) {
return pairs.map(([_, item]) => item);
}
return items;
}
function isElementPreceding(a, b) {
return Boolean(
b.compareDocumentPosition(a) & Node.DOCUMENT_POSITION_PRECEDING
);
}
export {
canUseDOM,
getDocument,
getWindow,
getActiveElement,
contains,
isFrame,
isButton,
isVisible,
isTextField,
isTextbox,
getTextboxValue,
getTextboxSelection,
getPopupRole,
getPopupItemRole,
scrollIntoViewIfNeeded,
getScrollingElement,
isPartiallyHidden,
setSelectionRange,
sortBasedOnDOMPosition
};