259 lines
9.9 KiB
JavaScript
Raw Normal View History

2024-01-29 09:26:07 +08:00
import { trapOn, trapOff } from './traps';
import { getEventTarget } from './utils';
// currently `once` and `passive` is not supported
function createDelegate() {
if (typeof window === 'undefined') {
return {
on: () => { },
off: () => { }
};
}
const propagationStopped = new WeakMap();
const immediatePropagationStopped = new WeakMap();
function trackPropagation() {
propagationStopped.set(this, true);
}
function trackImmediate() {
propagationStopped.set(this, true);
immediatePropagationStopped.set(this, true);
}
function spy(event, propName, fn) {
const source = event[propName];
event[propName] = function () {
fn.apply(event, arguments);
return source.apply(event, arguments);
};
return event;
}
function unspy(event, propName) {
event[propName] = Event.prototype[propName];
}
const currentTargets = new WeakMap();
const currentTargetDescriptor = Object.getOwnPropertyDescriptor(Event.prototype, 'currentTarget');
function getCurrentTarget() {
var _a;
return (_a = currentTargets.get(this)) !== null && _a !== void 0 ? _a : null;
}
function defineCurrentTarget(event, getter) {
if (currentTargetDescriptor === undefined)
return;
Object.defineProperty(event, 'currentTarget', {
configurable: true,
enumerable: true,
get: getter !== null && getter !== void 0 ? getter : currentTargetDescriptor.get
});
}
const phaseToTypeToElToHandlers = {
bubble: {},
capture: {}
};
const typeToWindowEventHandlers = {};
function createUnifiedHandler() {
const delegeteHandler = function (e) {
const { type, eventPhase, bubbles } = e;
const target = getEventTarget(e);
if (eventPhase === 2)
return;
const phase = eventPhase === 1 ? 'capture' : 'bubble';
let cursor = target;
const path = [];
// collecting bubble path
while (true) {
if (cursor === null)
cursor = window;
path.push(cursor);
if (cursor === window) {
break;
}
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
cursor = (cursor.parentNode || null);
}
const captureElToHandlers = phaseToTypeToElToHandlers.capture[type];
const bubbleElToHandlers = phaseToTypeToElToHandlers.bubble[type];
spy(e, 'stopPropagation', trackPropagation);
spy(e, 'stopImmediatePropagation', trackImmediate);
defineCurrentTarget(e, getCurrentTarget);
if (phase === 'capture') {
if (captureElToHandlers === undefined)
return;
// capture
for (let i = path.length - 1; i >= 0; --i) {
if (propagationStopped.has(e))
break;
const target = path[i];
const handlers = captureElToHandlers.get(target);
if (handlers !== undefined) {
currentTargets.set(e, target);
for (const handler of handlers) {
if (immediatePropagationStopped.has(e))
break;
handler(e);
}
}
if (i === 0 && !bubbles && bubbleElToHandlers !== undefined) {
const bubbleHandlers = bubbleElToHandlers.get(target);
if (bubbleHandlers !== undefined) {
for (const handler of bubbleHandlers) {
if (immediatePropagationStopped.has(e))
break;
handler(e);
}
}
}
}
}
else if (phase === 'bubble') {
if (bubbleElToHandlers === undefined)
return;
// bubble
for (let i = 0; i < path.length; ++i) {
if (propagationStopped.has(e))
break;
const target = path[i];
const handlers = bubbleElToHandlers.get(target);
if (handlers !== undefined) {
currentTargets.set(e, target);
for (const handler of handlers) {
if (immediatePropagationStopped.has(e))
break;
handler(e);
}
}
}
}
unspy(e, 'stopPropagation');
unspy(e, 'stopImmediatePropagation');
defineCurrentTarget(e);
};
delegeteHandler.displayName = 'evtdUnifiedHandler';
return delegeteHandler;
}
function createUnifiedWindowEventHandler() {
const delegateHandler = function (e) {
const { type, eventPhase } = e;
if (eventPhase !== 2)
return;
const handlers = typeToWindowEventHandlers[type];
if (handlers === undefined)
return;
handlers.forEach((handler) => handler(e));
};
delegateHandler.displayName = 'evtdUnifiedWindowEventHandler';
return delegateHandler;
}
const unifiedHandler = createUnifiedHandler();
const unfiendWindowEventHandler = createUnifiedWindowEventHandler();
function ensureElToHandlers(phase, type) {
const phaseHandlers = phaseToTypeToElToHandlers[phase];
if (phaseHandlers[type] === undefined) {
phaseHandlers[type] = new Map();
window.addEventListener(type, unifiedHandler, phase === 'capture');
}
return phaseHandlers[type];
}
function ensureWindowEventHandlers(type) {
const windowEventHandlers = typeToWindowEventHandlers[type];
if (windowEventHandlers === undefined) {
typeToWindowEventHandlers[type] = new Set();
window.addEventListener(type, unfiendWindowEventHandler);
}
return typeToWindowEventHandlers[type];
}
function ensureHandlers(elToHandlers, el) {
let elHandlers = elToHandlers.get(el);
if (elHandlers === undefined) {
elToHandlers.set(el, (elHandlers = new Set()));
}
return elHandlers;
}
function handlerExist(el, phase, type, handler) {
const elToHandlers = phaseToTypeToElToHandlers[phase][type];
// phase ${type} event has handlers
if (elToHandlers !== undefined) {
const handlers = elToHandlers.get(el);
// phase using el with ${type} event has handlers
if (handlers !== undefined) {
if (handlers.has(handler))
return true;
}
}
return false;
}
function windowEventHandlerExist(type, handler) {
const handlers = typeToWindowEventHandlers[type];
if (handlers !== undefined) {
if (handlers.has(handler)) {
return true;
}
}
return false;
}
function on(type, el, handler, options) {
let mergedHandler;
if (typeof options === 'object' && options.once === true) {
mergedHandler = (e) => {
off(type, el, mergedHandler, options);
handler(e);
};
}
else {
mergedHandler = handler;
}
const trapped = trapOn(type, el, mergedHandler, options);
if (trapped)
return;
const phase = options === true ||
(typeof options === 'object' && options.capture === true)
? 'capture'
: 'bubble';
const elToHandlers = ensureElToHandlers(phase, type);
const handlers = ensureHandlers(elToHandlers, el);
if (!handlers.has(mergedHandler))
handlers.add(mergedHandler);
if (el === window) {
const windowEventHandlers = ensureWindowEventHandlers(type);
if (!windowEventHandlers.has(mergedHandler)) {
windowEventHandlers.add(mergedHandler);
}
}
}
function off(type, el, handler, options) {
const trapped = trapOff(type, el, handler, options);
if (trapped)
return;
const capture = options === true ||
(typeof options === 'object' && options.capture === true);
const phase = capture ? 'capture' : 'bubble';
const elToHandlers = ensureElToHandlers(phase, type);
const handlers = ensureHandlers(elToHandlers, el);
if (el === window) {
const mirrorPhase = capture ? 'bubble' : 'capture';
if (!handlerExist(el, mirrorPhase, type, handler) &&
windowEventHandlerExist(type, handler)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const windowEventHandlers = typeToWindowEventHandlers[type];
windowEventHandlers.delete(handler);
if (windowEventHandlers.size === 0) {
window.removeEventListener(type, unfiendWindowEventHandler);
typeToWindowEventHandlers[type] = undefined;
}
}
}
if (handlers.has(handler))
handlers.delete(handler);
if (handlers.size === 0) {
elToHandlers.delete(el);
}
if (elToHandlers.size === 0) {
window.removeEventListener(type, unifiedHandler, phase === 'capture');
phaseToTypeToElToHandlers[phase][type] = undefined;
}
}
return {
on: on,
off: off
};
}
const { on, off } = createDelegate();
export { on, off };