dom: deliver standalone event target listeners
This commit is contained in:
1 parent
ca551a996b
commit
ec5950da06
4 files changed
+197
-1
No files matched your search
Vendored
+97
-1
@@ -753,6 +753,94 @@ function _shallowCloneNode(node) {
|
||||
return el;
|
||||
}
|
||||
|
||||
// EventTarget listener state belongs to the JS wrapper rather than the backing
|
||||
// DOM node. This is also what makes `new EventTarget()` and subclasses used by
|
||||
// framework schedulers work: those targets deliberately have no native node id.
|
||||
const _eventTargetListeners = new WeakMap();
|
||||
function _eventCapture(options) {
|
||||
return typeof options === "boolean" ? options : !!(options && options.capture);
|
||||
}
|
||||
function _eventTargetAdd(target, type, callback, options) {
|
||||
if (callback == null) return;
|
||||
const isFunction = typeof callback === "function";
|
||||
if (!isFunction && typeof callback.handleEvent !== "function") return;
|
||||
type = String(type);
|
||||
const capture = _eventCapture(options);
|
||||
const signal = options && typeof options === "object" ? options.signal : null;
|
||||
if (signal && signal.aborted) return;
|
||||
let byType = _eventTargetListeners.get(target);
|
||||
if (!byType) {
|
||||
byType = new Map();
|
||||
_eventTargetListeners.set(target, byType);
|
||||
}
|
||||
let listeners = byType.get(type);
|
||||
if (!listeners) {
|
||||
listeners = [];
|
||||
byType.set(type, listeners);
|
||||
}
|
||||
if (listeners.some((entry) => entry.callback === callback && entry.capture === capture)) return;
|
||||
const entry = {
|
||||
callback,
|
||||
capture,
|
||||
once: !!(options && typeof options === "object" && options.once),
|
||||
passive: !!(options && typeof options === "object" && options.passive),
|
||||
signal,
|
||||
abortHandler: null,
|
||||
};
|
||||
listeners.push(entry);
|
||||
if (signal && typeof signal.addEventListener === "function") {
|
||||
entry.abortHandler = () => _eventTargetRemove(target, type, callback, capture);
|
||||
signal.addEventListener("abort", entry.abortHandler, { once: true });
|
||||
}
|
||||
}
|
||||
function _eventTargetRemove(target, type, callback, options) {
|
||||
const byType = _eventTargetListeners.get(target);
|
||||
if (!byType) return;
|
||||
type = String(type);
|
||||
const listeners = byType.get(type);
|
||||
if (!listeners) return;
|
||||
const capture = _eventCapture(options);
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
const entry = listeners[i];
|
||||
if (entry.callback !== callback || entry.capture !== capture) continue;
|
||||
listeners.splice(i, 1);
|
||||
if (entry.signal && entry.abortHandler && typeof entry.signal.removeEventListener === "function") {
|
||||
entry.signal.removeEventListener("abort", entry.abortHandler);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (listeners.length === 0) byType.delete(type);
|
||||
if (byType.size === 0) _eventTargetListeners.delete(target);
|
||||
}
|
||||
function _eventTargetDispatch(target, event) {
|
||||
if (!event || typeof event.type === "undefined") {
|
||||
throw new TypeError("Failed to execute 'dispatchEvent' on 'EventTarget': parameter 1 is not of type 'Event'.");
|
||||
}
|
||||
if (String(event.type) === "") {
|
||||
throw new DOMException("The event's type was not specified.", "InvalidStateError");
|
||||
}
|
||||
if (!event.target) event.target = target;
|
||||
event.currentTarget = target;
|
||||
event.eventPhase = 2;
|
||||
const listeners = (_eventTargetListeners.get(target)?.get(String(event.type)) || []).slice();
|
||||
for (const entry of listeners) {
|
||||
const current = _eventTargetListeners.get(target)?.get(String(event.type));
|
||||
if (!current || !current.includes(entry)) continue;
|
||||
if (entry.once) _eventTargetRemove(target, event.type, entry.callback, entry.capture);
|
||||
const callback = entry.callback;
|
||||
try {
|
||||
if (typeof callback === "function") callback.call(target, event);
|
||||
else callback.handleEvent.call(callback, event);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (event._immediatePropagationStopped) break;
|
||||
}
|
||||
event.currentTarget = null;
|
||||
event.eventPhase = 0;
|
||||
return !event.defaultPrevented;
|
||||
}
|
||||
|
||||
class Node {
|
||||
static ELEMENT_NODE = 1;
|
||||
static ATTRIBUTE_NODE = 2;
|
||||
@@ -1033,7 +1121,15 @@ class Node {
|
||||
return true;
|
||||
}
|
||||
isSameNode(other) { return other && this._nid === other._nid; }
|
||||
addEventListener() {} removeEventListener() {} dispatchEvent() { return true; }
|
||||
addEventListener(type, callback, options) {
|
||||
_eventTargetAdd(this, type, callback, options);
|
||||
}
|
||||
removeEventListener(type, callback, options) {
|
||||
_eventTargetRemove(this, type, callback, options);
|
||||
}
|
||||
dispatchEvent(event) {
|
||||
return _eventTargetDispatch(this, event);
|
||||
}
|
||||
}
|
||||
class CharacterData extends Node {
|
||||
get data() {
|
||||
|
||||
Reference in new issue
Block a user