@miyauci/get-event-listeners
Version:
Ponyfill for getEventListeners
185 lines (184 loc) • 8.73 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createContext = exports.updateEventListener = exports.createGetEventListener = exports.createRemoveEventListener = exports.createAddEventListener = exports.getEventListenersWithContext = exports.removeAnEventListener = exports.addAnEventListener = void 0;
// Copyright © 2023 Tomoki Miyauchi. All rights reserved. MIT license.
// This module is browser compatible.
const dntShim = __importStar(require("./_dnt.shims.js"));
const utils_js_1 = require("./utils.js");
const deps_js_1 = require("./deps.js");
/** Register {@linkcode listener} if the {@linkcode registry} has not {@linkcode listener}.
* If {@linkcode listener} `signal` is passed, add algorithm.
*
* @see https://dom.spec.whatwg.org/#add-an-event-listener
*/
function addAnEventListener(target, listener, registry) {
const { type, signal, callback } = listener;
// 1. If eventTarget is a ServiceWorkerGlobalScope object, its service worker’s script resource’s has ever been evaluated flag is set, and listener’s type matches the type attribute value of any of the service worker events, then report a warning to the console that this might not give the expected results. [SERVICE-WORKERS]
// 2. If listener’s signal is not null and is aborted, then return.
if (!(0, deps_js_1.isNull)(signal) && signal.aborted)
return;
// 3. If listener’s callback is null, then return.
if ((0, deps_js_1.isNull)(callback))
return;
// 4. If listener’s passive is null, then set it to the default passive value given listener’s type and eventTarget.
const passive = listener.passive ?? (0, utils_js_1.defaultPassiveValue)(type, target);
const listenerMap = (0, deps_js_1.insert)(registry, target, () => new Map());
const finalListener = { ...listener, passive };
const key = (0, utils_js_1.toKey)(finalListener);
// 5. If eventTarget’s event listener list does not contain an event listener whose type is listener’s type, callback is listener’s callback, and capture is listener’s capture, then append listener to eventTarget’s event listener list.
if (!listenerMap.has(key)) {
listenerMap.set(key, finalListener);
}
// 6. If listener’s signal is not null, then add the following abort steps to it:
if (!(0, deps_js_1.isNull)(signal)) {
// 1. Remove an event listener with eventTarget and listener.
signal.addEventListener("abort", handleAbort, { once: true });
}
function handleAbort() {
listenerMap.delete(key);
}
}
exports.addAnEventListener = addAnEventListener;
/** Remove {@linkcode listener} from {@linkcode registry}.
* @see https://dom.spec.whatwg.org/#remove-an-event-listener
*/
function removeAnEventListener(target, listener, registry) {
// 1. If eventTarget is a ServiceWorkerGlobalScope object and its service worker’s set of event types to handle contains listener’s type, then report a warning to the console that this might not give the expected results. [SERVICE-WORKERS]
const eventListenerList = registry.get(target);
if (eventListenerList) {
// 2. Set listener’s removed to true and remove listener from eventTarget’s event listener list.
eventListenerList.delete((0, utils_js_1.toKey)(listener));
}
}
exports.removeAnEventListener = removeAnEventListener;
function getEventListenersWithContext(context) {
const eventListeners = context.registry.get(context.target);
if (!eventListeners)
return {};
const listeners = [...eventListeners.values()]
.filter(({ signal }) => !signal?.aborted)
.map(_listener2Listener);
return (0, deps_js_1.groupBy)(listeners, ({ type }) => type);
}
exports.getEventListenersWithContext = getEventListenersWithContext;
function createAddEventListener(context) {
return new Proxy(context.addEventListener, {
apply: (target, thisArg, argArray) => handleAddition(target, thisArg ?? dntShim.dntGlobalThis, argArray, context.registry),
});
}
exports.createAddEventListener = createAddEventListener;
function createRemoveEventListener(context) {
return new Proxy(context.removeEventListener, {
apply: (target, thisArg, argArray) => handleRemoval(target, thisArg ?? dntShim.dntGlobalThis, argArray, context.registry),
});
}
exports.createRemoveEventListener = createRemoveEventListener;
function createGetEventListener(registry) {
/** Get {@linkcode EventListeners}.
* Before this can be done, {@linkcode setup} must be performed.
*/
function getEventListeners(target) {
return getEventListenersWithContext({ target, registry });
}
return getEventListeners;
}
exports.createGetEventListener = createGetEventListener;
function handleAddition(target, thisArg, argArray, registry) {
const [type, callback, options] = argArray;
if (!callback)
return target.apply(thisArg, argArray);
const { once, ...rest } = (0, utils_js_1.flatOptionsMore)(options);
const overrodeCallback = once
? new Proxy((0, utils_js_1.toHandler)(callback), { apply: handleApply })
: undefined;
const listener = {
type,
callback,
once,
...rest,
overrodeCallback,
};
addAnEventListener(thisArg, listener, registry);
return target.apply(thisArg, [
type,
listener.overrodeCallback ?? listener.callback,
options,
]);
function handleApply(target, thisArg, argArray) {
removeAnEventListener(thisArg, listener, registry);
return target.apply(thisArg, argArray);
}
}
function handleRemoval(target, thisArg, argArray, registry) {
const [type, callback, options] = argArray;
if (!callback || !registry.has(thisArg)) {
return target.apply(thisArg, argArray);
}
const capture = (0, utils_js_1.flatOptions)(options);
const listenerMap = registry.get(thisArg);
const key = (0, utils_js_1.toKey)({ callback, type, capture });
if (!listenerMap.has(key))
return target.apply(thisArg, argArray);
const eventListener = listenerMap.get(key);
listenerMap.delete(key);
const listener = eventListener.overrodeCallback ?? eventListener.callback;
return target.apply(thisArg, [type, listener, options]);
}
function _listener2Listener(listener) {
const { type, capture: useCapture, passive, once } = listener;
return {
type,
listener: listener.callback,
useCapture,
once,
passive: passive ?? false,
};
}
/** Setup event listener monitoring.
* Performs a side effect and changes the prototype of `EventTarget`.
*/
function updateEventListener() {
const { addEventListener, removeEventListener, getEventListeners } = createContext();
EventTarget.prototype.addEventListener = addEventListener;
EventTarget.prototype.removeEventListener = removeEventListener;
return getEventListeners;
}
exports.updateEventListener = updateEventListener;
function createContext(options) {
const add = options?.addEventListener ??
EventTarget.prototype.addEventListener;
const remove = options?.removeEventListener ??
EventTarget.prototype.removeEventListener;
const registry = options?.registry ?? new WeakMap();
const addEventListener = createAddEventListener({
addEventListener: add,
registry,
});
const removeEventListener = createRemoveEventListener({ removeEventListener: remove, registry });
const getEventListeners = createGetEventListener(registry);
return { addEventListener, removeEventListener, getEventListeners };
}
exports.createContext = createContext;