@virtualstate/app-history
Version:
Native JavaScript [app-history](https://github.com/WICG/app-history) implementation
1,541 lines (1,438 loc) • 78.9 kB
JavaScript
function isEvent(value) {
function isLike(value) {
return !!value;
}
return isLike(value) && (typeof value.type === "string" ||
typeof value.type === "symbol");
}
function assertEvent(value, type) {
if (!isEvent(value)) {
throw new Error("Expected event");
}
if (typeof type !== "undefined" && value.type !== type) {
throw new Error(`Expected event type ${type}, got ${value.type.toString()}`);
}
}
function isParallelEvent(value) {
return isEvent(value) && value.parallel !== false;
}
class AbortError extends Error {
constructor(message) {
super(`AbortError${message ? `: ${message}` : ""}`);
this.name = "AbortError";
}
}
function isAbortError(error) {
return error instanceof Error && error.name === "AbortError";
}
class InvalidStateError extends Error {
constructor(message) {
super(`InvalidStateError${message ? `: ${message}` : ""}`);
this.name = "InvalidStateError";
}
}
function isInvalidStateError(error) {
return error instanceof Error && error.name === "InvalidStateError";
}
function isAbortSignal(value) {
function isAbortSignalLike(value) {
return typeof value === "object";
}
return (isAbortSignalLike(value) &&
typeof value.aborted === "boolean" &&
typeof value.addEventListener === "function");
}
function isSignalEvent(value) {
function isSignalEventLike(value) {
return value.hasOwnProperty("signal");
}
return (isEvent(value) &&
isSignalEventLike(value) &&
isAbortSignal(value.signal));
}
function isSignalHandled(event, error) {
if (isSignalEvent(event) && event.signal.aborted && error instanceof Error && isAbortError(error)) {
return true;
}
}
/**
* @experimental
*/
const EventTargetListeners$1 = Symbol.for("@opennetwork/environment/events/target/listeners");
/**
* @experimental
*/
const EventTargetListenersIgnore = Symbol.for("@opennetwork/environment/events/target/listeners/ignore");
/**
* @experimental
*/
const EventTargetListenersMatch = Symbol.for("@opennetwork/environment/events/target/listeners/match");
/**
* @experimental
*/
const EventTargetListenersThis = Symbol.for("@opennetwork/environment/events/target/listeners/this");
const EventDescriptorSymbol = Symbol.for("@opennetwork/environment/events/descriptor");
function matchEventCallback(type, callback, options) {
const optionsDescriptor = isOptionsDescriptor(options) ? options : undefined;
return descriptor => {
if (optionsDescriptor) {
return optionsDescriptor === descriptor;
}
return (!callback || callback === descriptor.callback) && type === descriptor.type;
};
function isOptionsDescriptor(options) {
function isLike(options) {
return !!options;
}
return isLike(options) && options[EventDescriptorSymbol] === true;
}
}
function isFunctionEventCallback(fn) {
return typeof fn === "function";
}
class EventTargetListeners {
#listeners = [];
[EventTargetListenersIgnore] = new WeakSet();
get [EventTargetListeners$1]() {
return [...(this.#listeners ?? [])];
}
[EventTargetListenersMatch](type) {
const external = this[EventTargetListeners$1];
const matched = [
...new Set([...(external ?? []), ...(this.#listeners ?? [])])
]
.filter(descriptor => descriptor.type === type || descriptor.type === "*")
.filter(descriptor => !this[EventTargetListenersIgnore]?.has(descriptor));
const listener = typeof type === "string" ? this[`on${type}`] : undefined;
if (typeof listener === "function" && isFunctionEventCallback(listener)) {
matched.push({
type,
callback: listener,
[EventDescriptorSymbol]: true
});
}
return matched;
}
addEventListener(type, callback, options) {
const listener = {
...options,
isListening: () => !!this.#listeners?.find(matchEventCallback(type, callback)),
descriptor: {
[EventDescriptorSymbol]: true,
...options,
type,
callback
},
timestamp: Date.now()
};
if (listener.isListening()) {
return;
}
this.#listeners?.push(listener.descriptor);
}
removeEventListener(type, callback, options) {
if (!isFunctionEventCallback(callback)) {
return;
}
const externalListeners = this[EventTargetListeners$1] ?? this.#listeners ?? [];
const externalIndex = externalListeners.findIndex(matchEventCallback(type, callback, options));
if (externalIndex === -1) {
return;
}
const index = this.#listeners?.findIndex(matchEventCallback(type, callback, options)) ?? -1;
if (index !== -1) {
this.#listeners?.splice(index, 1);
}
const descriptor = externalListeners[externalIndex];
if (descriptor) {
this[EventTargetListenersIgnore]?.add(descriptor);
}
}
hasEventListener(type, callback) {
if (callback && !isFunctionEventCallback(callback)) {
return false;
}
const foundIndex = this.#listeners?.findIndex(matchEventCallback(type, callback)) ?? -1;
return foundIndex > -1;
}
}
class AsyncEventTarget extends EventTargetListeners {
[EventTargetListenersThis];
constructor(thisValue = undefined) {
super();
this[EventTargetListenersThis] = thisValue;
}
async dispatchEvent(event) {
const listeners = this[EventTargetListenersMatch]?.(event.type) ?? [];
// Don't even dispatch an aborted event
if (isSignalEvent(event) && event.signal.aborted) {
throw new AbortError();
}
const parallel = isParallelEvent(event);
const promises = [];
for (let index = 0; index < listeners.length; index += 1) {
const descriptor = listeners[index];
const promise = (async () => {
// Remove the listener before invoking the callback
// This ensures that inside of the callback causes no more additional event triggers to this
// listener
if (descriptor.once) {
// by passing the descriptor as the options, we get an internal redirect
// that forces an instance level object equals, meaning
// we will only remove _this_ descriptor!
this.removeEventListener(descriptor.type, descriptor.callback, descriptor);
}
await descriptor.callback.call(this[EventTargetListenersThis] ?? this, event);
})();
if (!parallel) {
try {
await promise;
}
catch (error) {
if (!isSignalHandled(event, error)) {
await Promise.reject(error);
}
}
if (isSignalEvent(event) && event.signal.aborted) {
// bye
return;
}
}
else {
promises.push(promise);
}
}
if (promises.length) {
// Allows for all promises to settle finish so we can stay within the event, we then
// will utilise Promise.all which will reject with the first rejected promise
const results = await Promise.allSettled(promises);
const rejected = results.filter((result) => {
return result.status === "rejected";
});
if (rejected.length) {
let unhandled = rejected;
// If the event was aborted, then allow abort errors to occur, and handle these as handled errors
// The dispatcher does not care about this because they requested it
//
// There may be other unhandled errors that are more pressing to the task they are doing.
//
// The dispatcher can throw an abort error if they need to throw it up the chain
if (isSignalEvent(event) && event.signal.aborted) {
unhandled = unhandled.filter(result => !isSignalHandled(event, result.reason));
}
if (unhandled.length === 1) {
await Promise.reject(unhandled[0].reason);
throw unhandled[0].reason; // We shouldn't get here
}
else if (unhandled.length > 1) {
throw new AggregateError(unhandled.map(({ reason }) => reason));
}
}
}
}
}
var asyncEventTarget = /*#__PURE__*/Object.freeze({
__proto__: null,
AsyncEventTarget: AsyncEventTarget
});
const defaultModule = { EventTarget: AsyncEventTarget, AsyncEventTarget, SyncEventTarget: AsyncEventTarget };
let module;
try {
module = await Promise.resolve().then(function () { return asyncEventTarget; });
console.log("Using @virtualstate/app-history/event-target", module);
}
catch {
console.log("Using default");
module = defaultModule;
}
const EventTargetImplementation = module.EventTarget || module.SyncEventTarget || module.AsyncEventTarget;
function assertEventTarget(target) {
if (typeof target !== "function") {
throw new Error("Could not load EventTarget implementation");
}
}
class EventTarget$1 extends AsyncEventTarget {
constructor(...args) {
super();
if (EventTargetImplementation) {
assertEventTarget(EventTargetImplementation);
const { dispatchEvent } = new EventTargetImplementation(...args);
this.dispatchEvent = dispatchEvent;
}
}
}
class AppHistoryEventTarget extends EventTarget$1 {
addEventListener(type, listener, options) {
assertEventCallback(listener);
return super.addEventListener(type, listener, typeof options === "boolean" ? { once: options } : options);
function assertEventCallback(listener) {
if (typeof listener !== "function")
throw new Error("Please us the function variant of event listener");
}
}
removeEventListener(type, listener, options) {
assertEventCallback(listener);
return super.removeEventListener(type, listener);
function assertEventCallback(listener) {
if (typeof listener !== "function")
throw new Error("Please us the function variant of event listener");
}
}
}
const { v4 } = await Promise.resolve().then(function () { return _rollup_plugin_ignore_empty_module_placeholder$1; })
.catch(console.error)
.catch(() => undefined)
.then((mod) => mod ?? ({ v4() {
return `0101010-0101010-${Math.random()}`.replace(".", "");
} }));
const AppHistoryEntryNavigationType = Symbol.for("@virtualstate/app-history/entry/navigationType");
const AppHistoryEntryKnownAs = Symbol.for("@virtualstate/app-history/entry/knownAs");
const AppHistoryEntrySetState = Symbol.for("@virtualstate/app-history/entry/setState");
class AppHistoryEntry extends AppHistoryEventTarget {
#index;
#state;
get index() {
return typeof this.#index === "number" ? this.#index : this.#index();
}
key;
id;
url;
sameDocument;
get [AppHistoryEntryNavigationType]() {
return this.#options.navigationType;
}
get [AppHistoryEntryKnownAs]() {
const set = new Set(this.#options[AppHistoryEntryKnownAs]);
set.add(this.id);
return set;
}
#options;
get [EventTargetListeners$1]() {
return [...(super[EventTargetListeners$1] ?? []), ...(this.#options[EventTargetListeners$1] ?? [])];
}
constructor(init) {
super();
this.#options = init;
this.key = init.key || v4();
this.id = v4();
this.url = init.url ?? undefined;
this.#index = init.index;
this.sameDocument = init.sameDocument ?? true;
this.#state = init.state;
}
getState() {
const state = this.#state;
/**
* https://github.com/WICG/app-history/blob/7c0332b30746b14863f717404402bc49e497a2b2/spec.bs#L1406
* Note that in general, unless the state value is a primitive, entry.getState() !== entry.getState(), since a fresh copy is returned each time.
*/
if (typeof state === "undefined" ||
typeof state === "number" ||
typeof state === "boolean" ||
typeof state === "symbol" ||
typeof state === "bigint" ||
typeof state === "string") {
return state;
}
if (typeof state === "function") {
console.warn("State passed to appHistory.navigate was a function, this may be unintentional");
console.warn("Unless a state value is primitive, with a standard implementation of appHistory");
console.warn("your state value will be serialized and deserialized before this point, meaning");
console.warn("a function would not be usable.");
}
return {
...state
};
}
[AppHistoryEntrySetState](state) {
this.#state = state;
}
}
/**
* @param handleCatch rejected promises automatically to allow free usage
*/
function deferred(handleCatch) {
let resolve = undefined, reject = undefined;
const promise = new Promise((resolveFn, rejectFn) => {
resolve = resolveFn;
reject = rejectFn;
});
ok(resolve);
ok(reject);
return {
resolve,
reject,
promise: handleCatch ? (promise.catch(handleCatch)) : promise
};
}
function ok(value) {
if (!value) {
throw new Error("Value not provided");
}
}
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* @copyright 2015 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
/**
* @typedef {object} PrivateData
* @property {EventTarget} eventTarget The event target.
* @property {{type:string}} event The original event object.
* @property {number} eventPhase The current event phase.
* @property {EventTarget|null} currentTarget The current event target.
* @property {boolean} canceled The flag to prevent default.
* @property {boolean} stopped The flag to stop propagation.
* @property {boolean} immediateStopped The flag to stop propagation immediately.
* @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null.
* @property {number} timeStamp The unix time.
* @private
*/
/**
* Private data for event wrappers.
* @type {WeakMap<Event, PrivateData>}
* @private
*/
const privateData = new WeakMap();
/**
* Cache for wrapper classes.
* @type {WeakMap<Object, Function>}
* @private
*/
const wrappers = new WeakMap();
/**
* Get private data.
* @param {Event} event The event object to get private data.
* @returns {PrivateData} The private data of the event.
* @private
*/
function pd(event) {
const retv = privateData.get(event);
console.assert(
retv != null,
"'this' is expected an Event object, but got",
event
);
return retv
}
/**
* https://dom.spec.whatwg.org/#set-the-canceled-flag
* @param data {PrivateData} private data.
*/
function setCancelFlag(data) {
if (data.passiveListener != null) {
if (
typeof console !== "undefined" &&
typeof console.error === "function"
) {
console.error(
"Unable to preventDefault inside passive event listener invocation.",
data.passiveListener
);
}
return
}
if (!data.event.cancelable) {
return
}
data.canceled = true;
if (typeof data.event.preventDefault === "function") {
data.event.preventDefault();
}
}
/**
* @see https://dom.spec.whatwg.org/#interface-event
* @private
*/
/**
* The event wrapper.
* @constructor
* @param {EventTarget} eventTarget The event target of this dispatching.
* @param {Event|{type:string}} event The original event to wrap.
*/
function Event(eventTarget, event) {
privateData.set(this, {
eventTarget,
event,
eventPhase: 2,
currentTarget: eventTarget,
canceled: false,
stopped: false,
immediateStopped: false,
passiveListener: null,
timeStamp: event.timeStamp || Date.now(),
});
// https://heycam.github.io/webidl/#Unforgeable
Object.defineProperty(this, "isTrusted", { value: false, enumerable: true });
// Define accessors
const keys = Object.keys(event);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (!(key in this)) {
Object.defineProperty(this, key, defineRedirectDescriptor(key));
}
}
}
// Should be enumerable, but class methods are not enumerable.
Event.prototype = {
/**
* The type of this event.
* @type {string}
*/
get type() {
return pd(this).event.type
},
/**
* The target of this event.
* @type {EventTarget}
*/
get target() {
return pd(this).eventTarget
},
/**
* The target of this event.
* @type {EventTarget}
*/
get currentTarget() {
return pd(this).currentTarget
},
/**
* @returns {EventTarget[]} The composed path of this event.
*/
composedPath() {
const currentTarget = pd(this).currentTarget;
if (currentTarget == null) {
return []
}
return [currentTarget]
},
/**
* Constant of NONE.
* @type {number}
*/
get NONE() {
return 0
},
/**
* Constant of CAPTURING_PHASE.
* @type {number}
*/
get CAPTURING_PHASE() {
return 1
},
/**
* Constant of AT_TARGET.
* @type {number}
*/
get AT_TARGET() {
return 2
},
/**
* Constant of BUBBLING_PHASE.
* @type {number}
*/
get BUBBLING_PHASE() {
return 3
},
/**
* The target of this event.
* @type {number}
*/
get eventPhase() {
return pd(this).eventPhase
},
/**
* Stop event bubbling.
* @returns {void}
*/
stopPropagation() {
const data = pd(this);
data.stopped = true;
if (typeof data.event.stopPropagation === "function") {
data.event.stopPropagation();
}
},
/**
* Stop event bubbling.
* @returns {void}
*/
stopImmediatePropagation() {
const data = pd(this);
data.stopped = true;
data.immediateStopped = true;
if (typeof data.event.stopImmediatePropagation === "function") {
data.event.stopImmediatePropagation();
}
},
/**
* The flag to be bubbling.
* @type {boolean}
*/
get bubbles() {
return Boolean(pd(this).event.bubbles)
},
/**
* The flag to be cancelable.
* @type {boolean}
*/
get cancelable() {
return Boolean(pd(this).event.cancelable)
},
/**
* Cancel this event.
* @returns {void}
*/
preventDefault() {
setCancelFlag(pd(this));
},
/**
* The flag to indicate cancellation state.
* @type {boolean}
*/
get defaultPrevented() {
return pd(this).canceled
},
/**
* The flag to be composed.
* @type {boolean}
*/
get composed() {
return Boolean(pd(this).event.composed)
},
/**
* The unix time of this event.
* @type {number}
*/
get timeStamp() {
return pd(this).timeStamp
},
/**
* The target of this event.
* @type {EventTarget}
* @deprecated
*/
get srcElement() {
return pd(this).eventTarget
},
/**
* The flag to stop event bubbling.
* @type {boolean}
* @deprecated
*/
get cancelBubble() {
return pd(this).stopped
},
set cancelBubble(value) {
if (!value) {
return
}
const data = pd(this);
data.stopped = true;
if (typeof data.event.cancelBubble === "boolean") {
data.event.cancelBubble = true;
}
},
/**
* The flag to indicate cancellation state.
* @type {boolean}
* @deprecated
*/
get returnValue() {
return !pd(this).canceled
},
set returnValue(value) {
if (!value) {
setCancelFlag(pd(this));
}
},
/**
* Initialize this event object. But do nothing under event dispatching.
* @param {string} type The event type.
* @param {boolean} [bubbles=false] The flag to be possible to bubble up.
* @param {boolean} [cancelable=false] The flag to be possible to cancel.
* @deprecated
*/
initEvent() {
// Do nothing.
},
};
// `constructor` is not enumerable.
Object.defineProperty(Event.prototype, "constructor", {
value: Event,
configurable: true,
writable: true,
});
// Ensure `event instanceof window.Event` is `true`.
if (typeof window !== "undefined" && typeof window.Event !== "undefined") {
Object.setPrototypeOf(Event.prototype, window.Event.prototype);
// Make association for wrappers.
wrappers.set(window.Event.prototype, Event);
}
/**
* Get the property descriptor to redirect a given property.
* @param {string} key Property name to define property descriptor.
* @returns {PropertyDescriptor} The property descriptor to redirect the property.
* @private
*/
function defineRedirectDescriptor(key) {
return {
get() {
return pd(this).event[key]
},
set(value) {
pd(this).event[key] = value;
},
configurable: true,
enumerable: true,
}
}
/**
* Get the property descriptor to call a given method property.
* @param {string} key Property name to define property descriptor.
* @returns {PropertyDescriptor} The property descriptor to call the method property.
* @private
*/
function defineCallDescriptor(key) {
return {
value() {
const event = pd(this).event;
return event[key].apply(event, arguments)
},
configurable: true,
enumerable: true,
}
}
/**
* Define new wrapper class.
* @param {Function} BaseEvent The base wrapper class.
* @param {Object} proto The prototype of the original event.
* @returns {Function} The defined wrapper class.
* @private
*/
function defineWrapper(BaseEvent, proto) {
const keys = Object.keys(proto);
if (keys.length === 0) {
return BaseEvent
}
/** CustomEvent */
function CustomEvent(eventTarget, event) {
BaseEvent.call(this, eventTarget, event);
}
CustomEvent.prototype = Object.create(BaseEvent.prototype, {
constructor: { value: CustomEvent, configurable: true, writable: true },
});
// Define accessors.
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
if (!(key in BaseEvent.prototype)) {
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
const isFunc = typeof descriptor.value === "function";
Object.defineProperty(
CustomEvent.prototype,
key,
isFunc
? defineCallDescriptor(key)
: defineRedirectDescriptor(key)
);
}
}
return CustomEvent
}
/**
* Get the wrapper class of a given prototype.
* @param {Object} proto The prototype of the original event to get its wrapper.
* @returns {Function} The wrapper class.
* @private
*/
function getWrapper(proto) {
if (proto == null || proto === Object.prototype) {
return Event
}
let wrapper = wrappers.get(proto);
if (wrapper == null) {
wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto);
wrappers.set(proto, wrapper);
}
return wrapper
}
/**
* Wrap a given event to management a dispatching.
* @param {EventTarget} eventTarget The event target of this dispatching.
* @param {Object} event The event to wrap.
* @returns {Event} The wrapper instance.
* @private
*/
function wrapEvent(eventTarget, event) {
const Wrapper = getWrapper(Object.getPrototypeOf(event));
return new Wrapper(eventTarget, event)
}
/**
* Get the immediateStopped flag of a given event.
* @param {Event} event The event to get.
* @returns {boolean} The flag to stop propagation immediately.
* @private
*/
function isStopped(event) {
return pd(event).immediateStopped
}
/**
* Set the current event phase of a given event.
* @param {Event} event The event to set current target.
* @param {number} eventPhase New event phase.
* @returns {void}
* @private
*/
function setEventPhase(event, eventPhase) {
pd(event).eventPhase = eventPhase;
}
/**
* Set the current target of a given event.
* @param {Event} event The event to set current target.
* @param {EventTarget|null} currentTarget New current target.
* @returns {void}
* @private
*/
function setCurrentTarget(event, currentTarget) {
pd(event).currentTarget = currentTarget;
}
/**
* Set a passive listener of a given event.
* @param {Event} event The event to set current target.
* @param {Function|null} passiveListener New passive listener.
* @returns {void}
* @private
*/
function setPassiveListener(event, passiveListener) {
pd(event).passiveListener = passiveListener;
}
/**
* @typedef {object} ListenerNode
* @property {Function} listener
* @property {1|2|3} listenerType
* @property {boolean} passive
* @property {boolean} once
* @property {ListenerNode|null} next
* @private
*/
/**
* @type {WeakMap<object, Map<string, ListenerNode>>}
* @private
*/
const listenersMap = new WeakMap();
// Listener types
const CAPTURE = 1;
const BUBBLE = 2;
const ATTRIBUTE = 3;
/**
* Check whether a given value is an object or not.
* @param {any} x The value to check.
* @returns {boolean} `true` if the value is an object.
*/
function isObject(x) {
return x !== null && typeof x === "object" //eslint-disable-line no-restricted-syntax
}
/**
* Get listeners.
* @param {EventTarget} eventTarget The event target to get.
* @returns {Map<string, ListenerNode>} The listeners.
* @private
*/
function getListeners(eventTarget) {
const listeners = listenersMap.get(eventTarget);
if (listeners == null) {
throw new TypeError(
"'this' is expected an EventTarget object, but got another value."
)
}
return listeners
}
/**
* Get the property descriptor for the event attribute of a given event.
* @param {string} eventName The event name to get property descriptor.
* @returns {PropertyDescriptor} The property descriptor.
* @private
*/
function defineEventAttributeDescriptor(eventName) {
return {
get() {
const listeners = getListeners(this);
let node = listeners.get(eventName);
while (node != null) {
if (node.listenerType === ATTRIBUTE) {
return node.listener
}
node = node.next;
}
return null
},
set(listener) {
if (typeof listener !== "function" && !isObject(listener)) {
listener = null; // eslint-disable-line no-param-reassign
}
const listeners = getListeners(this);
// Traverse to the tail while removing old value.
let prev = null;
let node = listeners.get(eventName);
while (node != null) {
if (node.listenerType === ATTRIBUTE) {
// Remove old value.
if (prev !== null) {
prev.next = node.next;
} else if (node.next !== null) {
listeners.set(eventName, node.next);
} else {
listeners.delete(eventName);
}
} else {
prev = node;
}
node = node.next;
}
// Add new value.
if (listener !== null) {
const newNode = {
listener,
listenerType: ATTRIBUTE,
passive: false,
once: false,
next: null,
};
if (prev === null) {
listeners.set(eventName, newNode);
} else {
prev.next = newNode;
}
}
},
configurable: true,
enumerable: true,
}
}
/**
* Define an event attribute (e.g. `eventTarget.onclick`).
* @param {Object} eventTargetPrototype The event target prototype to define an event attrbite.
* @param {string} eventName The event name to define.
* @returns {void}
*/
function defineEventAttribute(eventTargetPrototype, eventName) {
Object.defineProperty(
eventTargetPrototype,
`on${eventName}`,
defineEventAttributeDescriptor(eventName)
);
}
/**
* Define a custom EventTarget with event attributes.
* @param {string[]} eventNames Event names for event attributes.
* @returns {EventTarget} The custom EventTarget.
* @private
*/
function defineCustomEventTarget(eventNames) {
/** CustomEventTarget */
function CustomEventTarget() {
EventTarget.call(this);
}
CustomEventTarget.prototype = Object.create(EventTarget.prototype, {
constructor: {
value: CustomEventTarget,
configurable: true,
writable: true,
},
});
for (let i = 0; i < eventNames.length; ++i) {
defineEventAttribute(CustomEventTarget.prototype, eventNames[i]);
}
return CustomEventTarget
}
/**
* EventTarget.
*
* - This is constructor if no arguments.
* - This is a function which returns a CustomEventTarget constructor if there are arguments.
*
* For example:
*
* class A extends EventTarget {}
* class B extends EventTarget("message") {}
* class C extends EventTarget("message", "error") {}
* class D extends EventTarget(["message", "error"]) {}
*/
function EventTarget() {
/*eslint-disable consistent-return */
if (this instanceof EventTarget) {
listenersMap.set(this, new Map());
return
}
if (arguments.length === 1 && Array.isArray(arguments[0])) {
return defineCustomEventTarget(arguments[0])
}
if (arguments.length > 0) {
const types = new Array(arguments.length);
for (let i = 0; i < arguments.length; ++i) {
types[i] = arguments[i];
}
return defineCustomEventTarget(types)
}
throw new TypeError("Cannot call a class as a function")
/*eslint-enable consistent-return */
}
// Should be enumerable, but class methods are not enumerable.
EventTarget.prototype = {
/**
* Add a given listener to this event target.
* @param {string} eventName The event name to add.
* @param {Function} listener The listener to add.
* @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
* @returns {void}
*/
addEventListener(eventName, listener, options) {
if (listener == null) {
return
}
if (typeof listener !== "function" && !isObject(listener)) {
throw new TypeError("'listener' should be a function or an object.")
}
const listeners = getListeners(this);
const optionsIsObj = isObject(options);
const capture = optionsIsObj
? Boolean(options.capture)
: Boolean(options);
const listenerType = capture ? CAPTURE : BUBBLE;
const newNode = {
listener,
listenerType,
passive: optionsIsObj && Boolean(options.passive),
once: optionsIsObj && Boolean(options.once),
next: null,
};
// Set it as the first node if the first node is null.
let node = listeners.get(eventName);
if (node === undefined) {
listeners.set(eventName, newNode);
return
}
// Traverse to the tail while checking duplication..
let prev = null;
while (node != null) {
if (
node.listener === listener &&
node.listenerType === listenerType
) {
// Should ignore duplication.
return
}
prev = node;
node = node.next;
}
// Add it.
prev.next = newNode;
},
/**
* Remove a given listener from this event target.
* @param {string} eventName The event name to remove.
* @param {Function} listener The listener to remove.
* @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
* @returns {void}
*/
removeEventListener(eventName, listener, options) {
if (listener == null) {
return
}
const listeners = getListeners(this);
const capture = isObject(options)
? Boolean(options.capture)
: Boolean(options);
const listenerType = capture ? CAPTURE : BUBBLE;
let prev = null;
let node = listeners.get(eventName);
while (node != null) {
if (
node.listener === listener &&
node.listenerType === listenerType
) {
if (prev !== null) {
prev.next = node.next;
} else if (node.next !== null) {
listeners.set(eventName, node.next);
} else {
listeners.delete(eventName);
}
return
}
prev = node;
node = node.next;
}
},
/**
* Dispatch a given event.
* @param {Event|{type:string}} event The event to dispatch.
* @returns {boolean} `false` if canceled.
*/
dispatchEvent(event) {
if (event == null || typeof event.type !== "string") {
throw new TypeError('"event.type" should be a string.')
}
// If listeners aren't registered, terminate.
const listeners = getListeners(this);
const eventName = event.type;
let node = listeners.get(eventName);
if (node == null) {
return true
}
// Since we cannot rewrite several properties, so wrap object.
const wrappedEvent = wrapEvent(this, event);
// This doesn't process capturing phase and bubbling phase.
// This isn't participating in a tree.
let prev = null;
while (node != null) {
// Remove this listener if it's once
if (node.once) {
if (prev !== null) {
prev.next = node.next;
} else if (node.next !== null) {
listeners.set(eventName, node.next);
} else {
listeners.delete(eventName);
}
} else {
prev = node;
}
// Call this listener
setPassiveListener(
wrappedEvent,
node.passive ? node.listener : null
);
if (typeof node.listener === "function") {
try {
node.listener.call(this, wrappedEvent);
} catch (err) {
if (
typeof console !== "undefined" &&
typeof console.error === "function"
) {
console.error(err);
}
}
} else if (
node.listenerType !== ATTRIBUTE &&
typeof node.listener.handleEvent === "function"
) {
node.listener.handleEvent(wrappedEvent);
}
// Break if `event.stopImmediatePropagation` was called.
if (isStopped(wrappedEvent)) {
break
}
node = node.next;
}
setPassiveListener(wrappedEvent, null);
setEventPhase(wrappedEvent, 0);
setCurrentTarget(wrappedEvent, null);
return !wrappedEvent.defaultPrevented
},
};
// `constructor` is not enumerable.
Object.defineProperty(EventTarget.prototype, "constructor", {
value: EventTarget,
configurable: true,
writable: true,
});
// Ensure `eventTarget instanceof window.EventTarget` is `true`.
if (
typeof window !== "undefined" &&
typeof window.EventTarget !== "undefined"
) {
Object.setPrototypeOf(EventTarget.prototype, window.EventTarget.prototype);
}
/**
* @author Toru Nagashima <https://github.com/mysticatea>
* See LICENSE file in root directory for full license.
*/
/**
* The signal class.
* @see https://dom.spec.whatwg.org/#abortsignal
*/
class AbortSignal extends EventTarget {
/**
* AbortSignal cannot be constructed directly.
*/
constructor() {
super();
throw new TypeError("AbortSignal cannot be constructed directly");
}
/**
* Returns `true` if this `AbortSignal`'s `AbortController` has signaled to abort, and `false` otherwise.
*/
get aborted() {
const aborted = abortedFlags.get(this);
if (typeof aborted !== "boolean") {
throw new TypeError(`Expected 'this' to be an 'AbortSignal' object, but got ${this === null ? "null" : typeof this}`);
}
return aborted;
}
}
defineEventAttribute(AbortSignal.prototype, "abort");
/**
* Create an AbortSignal object.
*/
function createAbortSignal() {
const signal = Object.create(AbortSignal.prototype);
EventTarget.call(signal);
abortedFlags.set(signal, false);
return signal;
}
/**
* Abort a given signal.
*/
function abortSignal(signal) {
if (abortedFlags.get(signal) !== false) {
return;
}
abortedFlags.set(signal, true);
signal.dispatchEvent({ type: "abort" });
}
/**
* Aborted flag for each instances.
*/
const abortedFlags = new WeakMap();
// Properties should be enumerable.
Object.defineProperties(AbortSignal.prototype, {
aborted: { enumerable: true },
});
// `toString()` should return `"[object AbortSignal]"`
if (typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol") {
Object.defineProperty(AbortSignal.prototype, Symbol.toStringTag, {
configurable: true,
value: "AbortSignal",
});
}
/**
* The AbortController.
* @see https://dom.spec.whatwg.org/#abortcontroller
*/
class AbortController {
/**
* Initialize this controller.
*/
constructor() {
signals.set(this, createAbortSignal());
}
/**
* Returns the `AbortSignal` object associated with this object.
*/
get signal() {
return getSignal(this);
}
/**
* Abort and signal to any observers that the associated activity is to be aborted.
*/
abort() {
abortSignal(getSignal(this));
}
}
/**
* Associated signals.
*/
const signals = new WeakMap();
/**
* Get the associated signal of a given controller.
*/
function getSignal(controller) {
const signal = signals.get(controller);
if (signal == null) {
throw new TypeError(`Expected 'this' to be an 'AbortController' object, but got ${controller === null ? "null" : typeof controller}`);
}
return signal;
}
// Properties should be enumerable.
Object.defineProperties(AbortController.prototype, {
signal: { enumerable: true },
abort: { enumerable: true },
});
if (typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol") {
Object.defineProperty(AbortController.prototype, Symbol.toStringTag, {
configurable: true,
value: "AbortController",
});
}
const Rollback = Symbol.for("@virtualstate/app-history/rollback");
const Unset = Symbol.for("@virtualstate/app-history/unset");
const AppHistoryTransitionParentEventTarget = Symbol.for("@virtualstate/app-history/transition/parentEventTarget");
const AppHistoryTransitionFinishedDeferred = Symbol.for("@virtualstate/app-history/transition/deferred/finished");
const AppHistoryTransitionCommittedDeferred = Symbol.for("@virtualstate/app-history/transition/deferred/committed");
const AppHistoryTransitionNavigationType = Symbol.for("@virtualstate/app-history/transition/navigationType");
const AppHistoryTransitionInitialEntries = Symbol.for("@virtualstate/app-history/transition/entries/initial");
const AppHistoryTransitionFinishedEntries = Symbol.for("@virtualstate/app-history/transition/entries/finished");
const AppHistoryTransitionInitialIndex = Symbol.for("@virtualstate/app-history/transition/index/initial");
const AppHistoryTransitionFinishedIndex = Symbol.for("@virtualstate/app-history/transition/index/finished");
const AppHistoryTransitionEntry = Symbol.for("@virtualstate/app-history/transition/entry");
const AppHistoryTransitionIsCommitted = Symbol.for("@virtualstate/app-history/transition/isCommitted");
const AppHistoryTransitionIsFinished = Symbol.for("@virtualstate/app-history/transition/isFinished");
const AppHistoryTransitionIsRejected = Symbol.for("@virtualstate/app-history/transition/isRejected");
const AppHistoryTransitionKnown = Symbol.for("@virtualstate/app-history/transition/known");
const AppHistoryTransitionPromises = Symbol.for("@virtualstate/app-history/transition/promises");
const AppHistoryTransitionWhile = Symbol.for("@virtualstate/app-history/transition/while");
const AppHistoryTransitionIsOngoing = Symbol.for("@virtualstate/app-history/transition/isOngoing");
const AppHistoryTransitionIsPending = Symbol.for("@virtualstate/app-history/transition/isPending");
const AppHistoryTransitionWait = Symbol.for("@virtualstate/app-history/transition/wait");
const AppHistoryTransitionPromiseResolved = Symbol.for("@virtualstate/app-history/transition/promise/resolved");
const AppHistoryTransitionRejected = Symbol.for("@virtualstate/app-history/transition/rejected");
const AppHistoryTransitionCommit = Symbol.for("@virtualstate/app-history/transition/commit");
const AppHistoryTransitionFinish = Symbol.for("@virtualstate/app-history/transition/finish");
const AppHistoryTransitionStart = Symbol.for("@virtualstate/app-history/transition/start");
const AppHistoryTransitionStartDeadline = Symbol.for("@virtualstate/app-history/transition/start/deadline");
const AppHistoryTransitionError = Symbol.for("@virtualstate/app-history/transition/error");
const AppHistoryTransitionFinally = Symbol.for("@virtualstate/app-history/transition/finally");
const AppHistoryTransitionAbort = Symbol.for("@virtualstate/app-history/transition/abort");
class AppHistoryTransition extends EventTarget$1 {
finished;
/**
* @experimental
*/
committed;
from;
navigationType;
#options;
[AppHistoryTransitionFinishedDeferred] = deferred();
[AppHistoryTransitionCommittedDeferred] = deferred();
get [AppHistoryTransitionIsPending]() {
return !!this.#promises.size;
}
get [AppHistoryTransitionNavigationType]() {
return this.#options[AppHistoryTransitionNavigationType];
}
get [AppHistoryTransitionInitialEntries]() {
return this.#options[AppHistoryTransitionInitialEntries];
}
get [AppHistoryTransitionInitialIndex]() {
return this.#options[AppHistoryTransitionInitialIndex];
}
[AppHistoryTransitionFinishedEntries];
[AppHistoryTransitionFinishedIndex];
[AppHistoryTransitionIsCommitted] = false;
[AppHistoryTransitionIsFinished] = false;
[AppHistoryTransitionIsRejected] = false;
[AppHistoryTransitionIsOngoing] = false;
[AppHistoryTransitionKnown] = new Set();
[AppHistoryTransitionEntry];
#promises = new Set();
#rolledBack = false;
#abortController = new AbortController();
get signal() {
return this.#abortController.signal;
}
get [AppHistoryTransitionPromises]() {
return this.#promises;
}
constructor(init) {
super();
this[AppHistoryTransitionFinishedDeferred] =
init[AppHistoryTransitionFinishedDeferred] ?? this[AppHistoryTransitionFinishedDeferred];
this[AppHistoryTransitionCommittedDeferred] =
init[AppHistoryTransitionCommittedDeferred] ?? this[AppHistoryTransitionCommittedDeferred];
this.#options = init;
const finished = this.finished = this[AppHistoryTransitionFinishedDeferred].promise;
const committed = this.committed = this[AppHistoryTransitionCommittedDeferred].promise;
// Auto catching abort
void finished.catch(error => error);
void committed.catch(error => error);
this.from = init.from;
this.navigationType = init.navigationType;
this[AppHistoryTransitionFinishedEntries] = init[AppHistoryTransitionFinishedEntries];
this[AppHistoryTransitionFinishedIndex] = init[AppHistoryTransitionFinishedIndex];
const known = init[AppHistoryTransitionKnown];
if (known) {
for (const entry of known) {
this[AppHistoryTransitionKnown].add(entry);
}
}
this[AppHistoryTransitionEntry] = init[AppHistoryTransitionEntry];
// Event listeners
{
// Events to promises
{
this.addEventListener(AppHistoryTransitionCommit, this.#onCommitPromise, { once: true });
this.addEventListener(AppHistoryTransitionFinish, this.#onFinishPromise, { once: true });
}
// Events to property setters
{
this.addEventListener(AppHistoryTransitionCommit, this.#onCommitSetProperty, { once: true });
this.addEventListener(AppHistoryTransitionFinish, this.#onFinishSetProperty, { once: true });
}
// Rejection + Abort
{
this.addEventListener(AppHistoryTransitionError, this.#onError, { once: true });
this.addEventListener(AppHistoryTransitionAbort, () => {
if (!this[AppHistoryTransitionIsFinished]) {
return this[AppHistoryTransitionRejected](new AbortError());
}
});
}
// Proxy all events from this transition onto entry + the parent event target
//
// The parent could be another transition, or the appHistory, this allows us to
// "bubble up" events layer by layer
//
// In this implementation, this allows individual transitions to "intercept" navigate and break the child
// transition from happening
//
// TODO WARN this may not be desired behaviour vs standard spec'd appHistory
{
this.addEventListener("*", this[AppHistoryTransitionEntry].dispatchEvent.bind(this[AppHistoryTransitionEntry]));
this.addEventListener("*", init[AppHistoryTransitionParentEventTarget].dispatchEvent.bind(init[AppHistoryTransitionParentEventTarget]));
}
}
}
rollback = (options) => {
// console.log({ rolled: this.#rolledBack });
if (this.#rolledBack) {
// TODO
throw new InvalidStateError("Rollback invoked multiple times: Please raise an issue at https://github.com/virtualstate/app-history with the use case where you want to use a rollback multiple times, this may have been unexpected behaviour");
}
this.#rolledBack = true;
return this.#options.rollback(options);
};
#onCommitSetProperty = () => {
this[AppHistoryTransitionIsCommitted] = true;
};
#onFinishSetProperty = () => {
this[AppHistoryTransitionIsFinished] = true;
};
#onFinishPromise = () => {
// console.log("onFinishPromise")
this[AppHistoryTransitionFinishedDeferred].resolve(this[AppHistoryTransitionEntry]);
};
#onCommitPromise = () => {
if (this.signal.aborted) ;
else {
this[AppHistoryTransitionCommittedDeferred].resolve(this[AppHistoryTransitionEntry]);
}
};
#onError = (event) => {
return this[AppHistoryTransitionRejected](event.error);
};
[AppHistoryTransitionPromiseResolved] = (...promises) => {
for (const promise of promises) {
this.#promises.delete(promise);
}
};
[AppHistoryTransitionRejected] = async (reason) => {
if (this[AppHistoryTransitionIsRejected])
return;
this[AppHistoryTransitionIsRejected] = true;
this[AppHistoryTransitionAbort]();
const navigationType = this[AppHistoryTransitionNavigationType];
// console.log({ navigationType, reason, entry: this[AppHistoryTransitionEntry] });
if ((typeof navigationType === "string" || navigationType === Rollback)) {
// console.log("navigateerror", { reason, z: isInvalidStateError(reason) });
await this.dispatchEvent({
type: "navigateerror",
error: reason,
get message() {
if (reason instanceof Error) {
return reason.message;
}
return `${reason}`;
}
});
// console.log("navigateerror finishe