ariakit-utils
Version:
Ariakit utils
183 lines (168 loc) • 5.27 kB
JavaScript
import { contains } from './dom.js';
import { isApple } from './platform.js';
/**
* Returns `true` if `event` has been fired within a React Portal element.
*/
function isPortalEvent(event) {
return Boolean(event.currentTarget && !contains(event.currentTarget, event.target));
}
/**
* Returns `true` if `event.target` and `event.currentTarget` are the same.
*/
function isSelfTarget(event) {
return event.target === event.currentTarget;
}
/**
* Checks whether the user event is triggering a page navigation in a new tab.
*/
function isOpeningInNewTab(event) {
const element = event.currentTarget;
if (!element) return false;
const isAppleDevice = isApple();
if (isAppleDevice && !event.metaKey) return false;
if (!isAppleDevice && !event.ctrlKey) return false;
const tagName = element.tagName.toLowerCase();
if (tagName === "a") return true;
if (tagName === "button" && element.type === "submit") return true;
if (tagName === "input" && element.type === "submit") return true;
return false;
}
/**
* Checks whether the user event is triggering a download.
*/
function isDownloading(event) {
const element = event.currentTarget;
if (!element) return false;
const tagName = element.tagName.toLowerCase();
if (!event.altKey) return false;
if (tagName === "a") return true;
if (tagName === "button" && element.type === "submit") return true;
if (tagName === "input" && element.type === "submit") return true;
return false;
}
/**
* Creates and dispatches an event.
* @example
* fireEvent(document.getElementById("id"), "blur", {
* bubbles: true,
* cancelable: true,
* });
*/
function fireEvent(element, type, eventInit) {
const event = new Event(type, eventInit);
return element.dispatchEvent(event);
}
/**
* Creates and dispatches a blur event.
* @example
* fireBlurEvent(document.getElementById("id"));
*/
function fireBlurEvent(element, eventInit) {
const event = new FocusEvent("blur", eventInit);
const defaultAllowed = element.dispatchEvent(event);
const bubbleInit = {
...eventInit,
bubbles: true
};
element.dispatchEvent(new FocusEvent("focusout", bubbleInit));
return defaultAllowed;
}
/**
* Creates and dispatches a focus event.
* @example
* fireFocusEvent(document.getElementById("id"));
*/
function fireFocusEvent(element, eventInit) {
const event = new FocusEvent("focus", eventInit);
const defaultAllowed = element.dispatchEvent(event);
const bubbleInit = {
...eventInit,
bubbles: true
};
element.dispatchEvent(new FocusEvent("focusin", bubbleInit));
return defaultAllowed;
}
/**
* Creates and dispatches a keyboard event.
* @example
* fireKeyboardEvent(document.getElementById("id"), "keydown", {
* key: "ArrowDown",
* shiftKey: true,
* });
*/
function fireKeyboardEvent(element, type, eventInit) {
const event = new KeyboardEvent(type, eventInit);
return element.dispatchEvent(event);
}
/**
* Creates and dispatches a click event.
* @example
* fireClickEvent(document.getElementById("id"));
*/
function fireClickEvent(element, eventInit) {
const event = new MouseEvent("click", eventInit);
return element.dispatchEvent(event);
}
/**
* Checks whether the focus/blur event is happening from/to outside of the
* container element.
* @example
* const element = document.getElementById("id");
* element.addEventListener("blur", (event) => {
* if (isFocusEventOutside(event)) {
* // ...
* }
* });
*/
function isFocusEventOutside(event, container) {
const containerElement = container || event.currentTarget;
const relatedTarget = event.relatedTarget;
return !relatedTarget || !contains(containerElement, relatedTarget);
}
/**
* Runs a callback on the next animation frame, but before a certain event.
*/
function queueBeforeEvent(element, type, callback) {
const raf = requestAnimationFrame(() => {
element.removeEventListener(type, callImmediately, true);
callback();
});
const callImmediately = () => {
cancelAnimationFrame(raf);
callback();
};
// By listening to the event in the capture phase, we make sure the callback
// is fired before the respective React events.
element.addEventListener(type, callImmediately, {
once: true,
capture: true
});
return raf;
}
/**
* Adds a global event listener, including on child frames.
*/
function addGlobalEventListener(type, listener, options, scope) {
if (scope === void 0) {
scope = window;
}
// Prevent errors from "sandbox" frames.
try {
scope.document.addEventListener(type, listener, options);
} catch (e) {}
const listeners = [];
for (let i = 0; i < scope.frames?.length; i += 1) {
const frameWindow = scope.frames[i];
if (frameWindow) {
listeners.push(addGlobalEventListener(type, listener, options, frameWindow));
}
}
const removeEventListener = () => {
try {
scope.document.removeEventListener(type, listener, options);
} catch (e) {}
listeners.forEach(listener => listener());
};
return removeEventListener;
}
export { addGlobalEventListener, fireBlurEvent, fireClickEvent, fireEvent, fireFocusEvent, fireKeyboardEvent, isDownloading, isFocusEventOutside, isOpeningInNewTab, isPortalEvent, isSelfTarget, queueBeforeEvent };