tiny-event-intercept
Version:
Lightweight (~1.0KB) TypeScript library for conditional event interception with browser-standard API. Zero dependencies, SSR compatible, robust edge case handling.
74 lines (73 loc) • 2.72 kB
JavaScript
const NOOP_CLEANUP = () => {};
function isValidEventTarget(target) {
return target != null && typeof target === "object" && "addEventListener" in target && typeof target.addEventListener === "function" && "removeEventListener" in target && typeof target.removeEventListener === "function";
}
function resolveTarget(target) {
if (target === null) return null;
if (typeof target === "function") try {
return target();
} catch {
return null;
}
return target;
}
function normalizeEvents(events) {
return Array.isArray(events) ? events : [events];
}
function isValidInterceptOptions(options) {
return options != null && typeof options === "object" && "events" in options && "when" in options && typeof options.when === "function" && "listener" in options && typeof options.listener === "function";
}
function interceptEvents(target, options) {
if (typeof window === "undefined" || typeof document === "undefined") return NOOP_CLEANUP;
if (!isValidInterceptOptions(options)) {
console?.warn?.("interceptEvents: invalid options");
return NOOP_CLEANUP;
}
const resolvedTarget = resolveTarget(target) || document;
if (!isValidEventTarget(resolvedTarget)) {
console?.warn?.("interceptEvents: invalid target element");
return NOOP_CLEANUP;
}
const eventList = normalizeEvents(options.events);
const validEvents = eventList.filter((eventType) => typeof eventType === "string" && eventType.trim().length > 0);
if (validEvents.length === 0) {
console?.warn?.("interceptEvents: no valid events provided");
return NOOP_CLEANUP;
}
const uniqueEvents = [...new Set(validEvents)];
const { when, listener } = options;
const listenerOptions = {
capture: options.capture ?? false,
passive: options.passive ?? false,
once: options.once ?? false,
...options.signal && { signal: options.signal }
};
const listeners = new Map();
let isCleanedUp = false;
uniqueEvents.forEach((eventType) => {
const eventListener = (event) => {
if (isCleanedUp) return;
if (when()) listener(event);
};
resolvedTarget.addEventListener(eventType, eventListener, listenerOptions);
listeners.set(eventType, eventListener);
});
const cleanup = () => {
if (isCleanedUp) return;
isCleanedUp = true;
listeners.forEach((eventListener, eventType) => {
resolvedTarget.removeEventListener(eventType, eventListener, listenerOptions);
});
listeners.clear();
};
if (typeof window !== "undefined" && window.addEventListener) {
const autoCleanup = () => cleanup();
window.addEventListener("beforeunload", autoCleanup, { once: true });
return () => {
window.removeEventListener("beforeunload", autoCleanup);
cleanup();
};
}
return cleanup;
}
exports.interceptEvents = interceptEvents;