export function awaitEvent(target, type) {
return new Promise((fulfill, reject) => {
target.addEventListener(type, function wrappedCallback(...args) {
fulfill(...args);
}, { once: true });
});
}
import MultiMap from "js/MultiMap.js";
const ImmediateListenersSymbol = Symbol("immediate-listeners");
export class ImmediateEvent {
constructor(type, options) {
console.assert(type);
this._type = type;
this._detail = (typeof options === "object" && options !== null && "detail" in options) ? options.detail : null;
this._target = null;
this._currentTarget = null;
this._propagationStopped = false;
this._defaultPrevented = false;
}
// Public
get type() { return this._type; }
get detail() { return this._detail; }
get target() { return this._target; }
get currentTarget() { return this._currentTarget; }
stopPropagation() {
this._propagationStopped = true;
}
preventDefault() {
this._defaultPrevented = true;
}
}
export function addImmediateEventListener(target, type, listener, options) {
if (typeof options === "boolean")
options = { capture: options };
target[ImmediateListenersSymbol] ??= new MultiMap;
for (let listenerForType of target[ImmediateListenersSymbol].get(type)) {
if (listenerForType.listener === listener && listenerForType.capture === options?.capture)
return;
}
target[ImmediateListenersSymbol].add(type, {listener, options});
}
export function removeImmediateEventListener(target, type, listener, options) {
if (typeof options === "boolean")
options = { capture: options };
if (!target[ImmediateListenersSymbol])
return;
for (let listenerForType of target[ImmediateListenersSymbol].get(type)) {
if (listenerForType.listener === listener && listenerForType.capture === options?.capture)
target[ImmediateListenersSymbol].delete(type, listenerForType);
}
}
export function dispatchImmediateEvent(target, event) {
console.assert(event instanceof ImmediateEvent, event);
console.assert(!event._target);
console.assert(!event._currentTarget);
console.assert(!event._propagationStopped);
console.assert(!event._defaultPrevented);
event._target = target;
event._currentTarget = target;
let targets = [];
while (target) {
targets.push(target);
if (globalThis.Node && target instanceof globalThis.Node && target.parentNode)
target = target.parentNode;
else if (target !== globalThis)
target = globalThis;
else
target = null;
}
function callImmediateEventListeners(currentTarget, capture) {
event._currentTarget = currentTarget;
if (!event._currentTarget.hasOwnProperty(ImmediateListenersSymbol))
return;
for (let listenerForType of Array.from(event._currentTarget[ImmediateListenersSymbol].get(type))) {
if (!listenerForType.options?.capture !== !capture)
continue;
listenerForType.listener.call(event._currentTarget, event);
if (listenerForType.options?.once)
event._currentTarget[ImmediateListenersSymbol].delete(type, listenerForType);
if (event._propagationStopped)
break;
}
}
for (let i = targets.length - 1; i > 0 && !event._propagationStopped; --i)
callImmediateEventListeners(targets[i], true);
event._propagationStopped = false;
for (let i = 0; i < targets.length && !event._propagationStopped; ++i)
callImmediateEventListeners(targets[i], false);
return event._defaultPrevented;
}
import {
addImmediateEventListener
} from "html/EventTarget.js";
export function awaitImmediateEvent(type, options) {
let predicate = (typeof options === "object" && options !== null && typeof options.predicate === "function") ? options.predicate : function() { return true; };
return new Promise((fulfill, reject) => {
addImmediateEventListener(target, type, function wrappedCallback(...args) {
if (!predicate(...args))
return;
removeImmediateEventListener(target, type, wrappedCallback);
fulfill(...args);
});
});
}