@virtualstate/navigation
Version:
Native JavaScript [navigation](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API) implementation
1 lines • 200 kB
Source Map (JSON)
{"version":3,"file":"rollup.cjs","sources":["rollup.js"],"sourcesContent":["function isEvent(value) {\n function isLike(value) {\n return !!value;\n }\n return (isLike(value) &&\n (typeof value.type === \"string\" || typeof value.type === \"symbol\"));\n}\nfunction assertEvent(value, type) {\n if (!isEvent(value)) {\n throw new Error(\"Expected event\");\n }\n if (typeof type !== \"undefined\" && value.type !== type) {\n throw new Error(`Expected event type ${String(type)}, got ${value.type.toString()}`);\n }\n}\n\nfunction isParallelEvent(value) {\n return isEvent(value) && value.parallel !== false;\n}\n\nclass AbortError extends Error {\n constructor(message) {\n super(`AbortError${message ? `: ${message}` : \"\"}`);\n this.name = \"AbortError\";\n }\n}\nfunction isAbortError(error) {\n return error instanceof Error && error.name === \"AbortError\";\n}\nclass InvalidStateError extends Error {\n constructor(message) {\n super(`InvalidStateError${message ? `: ${message}` : \"\"}`);\n this.name = \"InvalidStateError\";\n }\n}\nfunction isInvalidStateError(error) {\n return error instanceof Error && error.name === \"InvalidStateError\";\n}\n\nfunction isAbortSignal(value) {\n function isAbortSignalLike(value) {\n return typeof value === \"object\";\n }\n return (isAbortSignalLike(value) &&\n typeof value.aborted === \"boolean\" &&\n typeof value.addEventListener === \"function\");\n}\nfunction isSignalEvent(value) {\n function isSignalEventLike(value) {\n return value.hasOwnProperty(\"signal\");\n }\n return (isEvent(value) && isSignalEventLike(value) && isAbortSignal(value.signal));\n}\nfunction isSignalHandled(event, error) {\n if (isSignalEvent(event) &&\n event.signal.aborted &&\n error instanceof Error &&\n isAbortError(error)) {\n return true;\n }\n}\n\n/**\n * @experimental\n */\nconst EventTargetListeners$1 = Symbol.for(\"@opennetwork/environment/events/target/listeners\");\n/**\n * @experimental\n */\nconst EventTargetListenersIgnore = Symbol.for(\"@opennetwork/environment/events/target/listeners/ignore\");\n/**\n * @experimental\n */\nconst EventTargetListenersMatch = Symbol.for(\"@opennetwork/environment/events/target/listeners/match\");\n/**\n * @experimental\n */\nconst EventTargetListenersThis = Symbol.for(\"@opennetwork/environment/events/target/listeners/this\");\n\nconst EventDescriptorSymbol = Symbol.for(\"@opennetwork/environment/events/descriptor\");\n\nfunction matchEventCallback(type, callback, options) {\n const optionsDescriptor = isOptionsDescriptor(options) ? options : undefined;\n return (descriptor) => {\n if (optionsDescriptor) {\n return optionsDescriptor === descriptor;\n }\n return ((!callback || callback === descriptor.callback) &&\n type === descriptor.type);\n };\n function isOptionsDescriptor(options) {\n function isLike(options) {\n return !!options;\n }\n return isLike(options) && options[EventDescriptorSymbol] === true;\n }\n}\n\nfunction isFunctionEventCallback(fn) {\n return typeof fn === \"function\";\n}\nconst EventTargetDescriptors = Symbol.for(\"@virtualstate/navigation/event-target/descriptors\");\nclass EventTargetListeners {\n [EventTargetDescriptors] = [];\n [EventTargetListenersIgnore] = new WeakSet();\n get [EventTargetListeners$1]() {\n return [...(this[EventTargetDescriptors] ?? [])];\n }\n [EventTargetListenersMatch](type) {\n const external = this[EventTargetListeners$1];\n const matched = [\n ...new Set([...(external ?? []), ...(this[EventTargetDescriptors] ?? [])]),\n ]\n .filter((descriptor) => descriptor.type === type || descriptor.type === \"*\")\n .filter((descriptor) => !this[EventTargetListenersIgnore]?.has(descriptor));\n const listener = typeof type === \"string\" ? this[`on${type}`] : undefined;\n if (typeof listener === \"function\" && isFunctionEventCallback(listener)) {\n matched.push({\n type,\n callback: listener,\n [EventDescriptorSymbol]: true,\n });\n }\n return matched;\n }\n addEventListener(type, callback, options) {\n const listener = {\n ...options,\n isListening: () => !!this[EventTargetDescriptors]?.find(matchEventCallback(type, callback)),\n descriptor: {\n [EventDescriptorSymbol]: true,\n ...options,\n type,\n callback,\n },\n timestamp: Date.now(),\n };\n if (listener.isListening()) {\n return;\n }\n this[EventTargetDescriptors]?.push(listener.descriptor);\n }\n removeEventListener(type, callback, options) {\n if (!isFunctionEventCallback(callback)) {\n return;\n }\n const externalListeners = this[EventTargetListeners$1] ?? this[EventTargetDescriptors] ?? [];\n const externalIndex = externalListeners.findIndex(matchEventCallback(type, callback, options));\n if (externalIndex === -1) {\n return;\n }\n const index = this[EventTargetDescriptors]?.findIndex(matchEventCallback(type, callback, options)) ??\n -1;\n if (index !== -1) {\n this[EventTargetDescriptors]?.splice(index, 1);\n }\n const descriptor = externalListeners[externalIndex];\n if (descriptor) {\n this[EventTargetListenersIgnore]?.add(descriptor);\n }\n }\n hasEventListener(type, callback) {\n if (callback && !isFunctionEventCallback(callback)) {\n return false;\n }\n const foundIndex = this[EventTargetDescriptors]?.findIndex(matchEventCallback(type, callback)) ?? -1;\n return foundIndex > -1;\n }\n}\n\nclass AsyncEventTarget extends EventTargetListeners {\n [EventTargetListenersThis];\n constructor(thisValue = undefined) {\n super();\n this[EventTargetListenersThis] = thisValue;\n }\n async dispatchEvent(event) {\n const listeners = this[EventTargetListenersMatch]?.(event.type) ?? [];\n // Don't even dispatch an aborted event\n if (isSignalEvent(event) && event.signal.aborted) {\n throw new AbortError();\n }\n const parallel = isParallelEvent(event);\n const promises = [];\n for (let index = 0; index < listeners.length; index += 1) {\n const descriptor = listeners[index];\n const promise = (async () => {\n // Remove the listener before invoking the callback\n // This ensures that inside of the callback causes no more additional event triggers to this\n // listener\n if (descriptor.once) {\n // by passing the descriptor as the options, we get an internal redirect\n // that forces an instance level object equals, meaning\n // we will only remove _this_ descriptor!\n this.removeEventListener(descriptor.type, descriptor.callback, descriptor);\n }\n await descriptor.callback.call(this[EventTargetListenersThis] ?? this, event);\n })();\n if (!parallel) {\n try {\n await promise;\n }\n catch (error) {\n if (!isSignalHandled(event, error)) {\n await Promise.reject(error);\n }\n }\n if (isSignalEvent(event) && event.signal.aborted) {\n // bye\n return;\n }\n }\n else {\n promises.push(promise);\n }\n }\n if (promises.length) {\n // Allows for all promises to settle finish so we can stay within the event, we then\n // will utilise Promise.all which will reject with the first rejected promise\n const results = await Promise.allSettled(promises);\n const rejected = results.filter((result) => {\n return result.status === \"rejected\";\n });\n if (rejected.length) {\n let unhandled = rejected;\n // If the event was aborted, then allow abort errors to occur, and handle these as handled errors\n // The dispatcher does not care about this because they requested it\n //\n // There may be other unhandled errors that are more pressing to the task they are doing.\n //\n // The dispatcher can throw an abort error if they need to throw it up the chain\n if (isSignalEvent(event) && event.signal.aborted) {\n unhandled = unhandled.filter((result) => !isSignalHandled(event, result.reason));\n }\n if (unhandled.length === 1) {\n await Promise.reject(unhandled[0].reason);\n throw unhandled[0].reason; // We shouldn't get here\n }\n else if (unhandled.length > 1) {\n throw new AggregateError(unhandled.map(({ reason }) => reason));\n }\n }\n }\n }\n}\n\nconst defaultEventTargetModule = {\n EventTarget: AsyncEventTarget,\n AsyncEventTarget,\n SyncEventTarget: AsyncEventTarget,\n};\nlet eventTargetModule = defaultEventTargetModule;\n//\n// try {\n// eventTargetModule = await import(\"@virtualstate/navigation/event-target\");\n// console.log(\"Using @virtualstate/navigation/event-target\", eventTargetModule);\n// } catch {\n// console.log(\"Using defaultEventTargetModule\");\n// eventTargetModule = defaultEventTargetModule;\n// }\nconst EventTargetImplementation = eventTargetModule.EventTarget || eventTargetModule.SyncEventTarget || eventTargetModule.AsyncEventTarget;\nfunction assertEventTarget(target) {\n if (typeof target !== \"function\") {\n throw new Error(\"Could not load EventTarget implementation\");\n }\n}\nclass EventTarget extends AsyncEventTarget {\n constructor(...args) {\n super();\n if (EventTargetImplementation) {\n assertEventTarget(EventTargetImplementation);\n const { dispatchEvent } = new EventTargetImplementation(...args);\n this.dispatchEvent = dispatchEvent;\n }\n }\n}\n\nfunction isInterceptEvent(value) {\n function isInterceptEventLike(value) {\n return isEvent(value);\n }\n return (isInterceptEventLike(value) && typeof value.intercept === \"function\");\n}\n\nclass NavigationEventTarget extends EventTarget {\n addEventListener(type, listener, options) {\n assertEventCallback(listener);\n return super.addEventListener(type, listener, typeof options === \"boolean\" ? { once: options } : options);\n function assertEventCallback(listener) {\n if (typeof listener !== \"function\")\n throw new Error(\"Please us the function variant of event listener\");\n }\n }\n removeEventListener(type, listener, options) {\n assertEventCallback(listener);\n return super.removeEventListener(type, listener);\n function assertEventCallback(listener) {\n if (typeof listener !== \"function\")\n throw new Error(\"Please us the function variant of event listener\");\n }\n }\n}\n\nconst isWebCryptoSupported = \"crypto\" in globalThis && typeof globalThis.crypto.randomUUID === \"function\";\nconst v4 = isWebCryptoSupported\n ? globalThis.crypto.randomUUID.bind(globalThis.crypto)\n : () => Array.from({ length: 5 }, () => `${Math.random()}`.replace(/^0\\./, \"\"))\n .join(\"-\")\n .replace(\".\", \"\");\n\n// To prevent cyclic imports, where a circular is used, instead use the prototype interface\n// and then copy over the \"private\" symbol\nconst NavigationGetState$1 = Symbol.for(\"@virtualstate/navigation/getState\");\nconst NavigationHistoryEntryNavigationType = Symbol.for(\"@virtualstate/navigation/entry/navigationType\");\nconst NavigationHistoryEntryKnownAs = Symbol.for(\"@virtualstate/navigation/entry/knownAs\");\nconst NavigationHistoryEntrySetState = Symbol.for(\"@virtualstate/navigation/entry/setState\");\nfunction isPrimitiveValue(state) {\n return (typeof state === \"number\" ||\n typeof state === \"boolean\" ||\n typeof state === \"symbol\" ||\n typeof state === \"bigint\" ||\n typeof state === \"string\");\n}\nfunction isValue(state) {\n return !!(state || isPrimitiveValue(state));\n}\nclass NavigationHistoryEntry extends NavigationEventTarget {\n #index;\n #state;\n get index() {\n return typeof this.#index === \"number\" ? this.#index : this.#index();\n }\n key;\n id;\n url;\n sameDocument;\n get [NavigationHistoryEntryNavigationType]() {\n return this.#options.navigationType;\n }\n get [NavigationHistoryEntryKnownAs]() {\n const set = new Set(this.#options[NavigationHistoryEntryKnownAs]);\n set.add(this.id);\n return set;\n }\n #options;\n get [EventTargetListeners$1]() {\n return [\n ...(super[EventTargetListeners$1] ?? []),\n ...(this.#options[EventTargetListeners$1] ?? []),\n ];\n }\n constructor(init) {\n super();\n this.#options = init;\n this.key = init.key || v4();\n this.id = v4();\n this.url = init.url ?? undefined;\n this.#index = init.index;\n this.sameDocument = init.sameDocument ?? true;\n this.#state = init.state ?? undefined;\n }\n [NavigationGetState$1]() {\n return this.#options?.getState?.(this);\n }\n getState() {\n let state = this.#state;\n if (!isValue(state)) {\n const external = this[NavigationGetState$1]();\n if (isValue(external)) {\n state = this.#state = external;\n }\n }\n /**\n * https://github.com/WICG/app-history/blob/7c0332b30746b14863f717404402bc49e497a2b2/spec.bs#L1406\n * Note that in general, unless the state value is a primitive, entry.getState() !== entry.getState(), since a fresh copy is returned each time.\n */\n if (typeof state === \"undefined\" ||\n isPrimitiveValue(state)) {\n return state;\n }\n if (typeof state === \"function\") {\n console.warn(\"State passed to Navigation.navigate was a function, this may be unintentional\");\n console.warn(\"Unless a state value is primitive, with a standard implementation of Navigation\");\n console.warn(\"your state value will be serialized and deserialized before this point, meaning\");\n console.warn(\"a function would not be usable.\");\n }\n return {\n ...state,\n };\n }\n [NavigationHistoryEntrySetState](state) {\n this.#state = state;\n }\n}\n\n/**\n * @param handleCatch rejected promises automatically to allow free usage\n */\nfunction deferred(handleCatch) {\n let resolve = undefined, reject = undefined;\n const promise = new Promise((resolveFn, rejectFn) => {\n resolve = resolveFn;\n reject = rejectFn;\n });\n ok$1(resolve);\n ok$1(reject);\n return {\n resolve,\n reject,\n promise: handleCatch ? promise.catch(handleCatch) : promise,\n };\n}\nfunction ok$1(value) {\n if (!value) {\n throw new Error(\"Value not provided\");\n }\n}\n\nconst GlobalAbortController = typeof AbortController !== \"undefined\" ? AbortController : undefined;\n\nif (!GlobalAbortController) {\n throw new Error(\"AbortController expected to be available or polyfilled\");\n}\nconst AbortController$1 = GlobalAbortController;\n\nfunction isPromise(value) {\n return (like(value) &&\n typeof value.then === \"function\");\n}\nfunction ok(value, message = \"Expected value\") {\n if (!value) {\n throw new Error(message);\n }\n}\nfunction isPromiseRejectedResult(value) {\n return value.status === \"rejected\";\n}\nfunction like(value) {\n return !!value;\n}\n\nconst THIS_WILL_BE_REMOVED = \"This will be removed when the first major release of @virtualstate/navigation is published\";\nconst WARNINGS = {\n EVENT_INTERCEPT_HANDLER: `You are using a non standard interface, please update your code to use event.intercept({ async handler() {} })\\n${THIS_WILL_BE_REMOVED}`\n};\nlet GLOBAL_IS_WARNINGS_IGNORED = false;\nlet GLOBAL_IS_WARNINGS_TRACED = true;\nfunction setIgnoreWarnings(ignore) {\n GLOBAL_IS_WARNINGS_IGNORED = ignore;\n}\nfunction setTraceWarnings(ignore) {\n GLOBAL_IS_WARNINGS_TRACED = ignore;\n}\nfunction logWarning(warning, ...message) {\n if (GLOBAL_IS_WARNINGS_IGNORED) {\n return;\n }\n try {\n if (GLOBAL_IS_WARNINGS_TRACED) {\n console.trace(WARNINGS[warning], ...message);\n }\n else {\n console.warn(WARNINGS[warning], ...message);\n }\n }\n catch {\n // We don't want attempts to log causing issues\n // maybe we don't have a console\n }\n}\n\nconst Rollback = Symbol.for(\"@virtualstate/navigation/rollback\");\nconst Unset = Symbol.for(\"@virtualstate/navigation/unset\");\nconst NavigationTransitionParentEventTarget = Symbol.for(\"@virtualstate/navigation/transition/parentEventTarget\");\nconst NavigationTransitionFinishedDeferred = Symbol.for(\"@virtualstate/navigation/transition/deferred/finished\");\nconst NavigationTransitionCommittedDeferred = Symbol.for(\"@virtualstate/navigation/transition/deferred/committed\");\nconst NavigationTransitionNavigationType = Symbol.for(\"@virtualstate/navigation/transition/navigationType\");\nconst NavigationTransitionInitialEntries = Symbol.for(\"@virtualstate/navigation/transition/entries/initial\");\nconst NavigationTransitionFinishedEntries = Symbol.for(\"@virtualstate/navigation/transition/entries/finished\");\nconst NavigationTransitionInitialIndex = Symbol.for(\"@virtualstate/navigation/transition/index/initial\");\nconst NavigationTransitionFinishedIndex = Symbol.for(\"@virtualstate/navigation/transition/index/finished\");\nconst NavigationTransitionEntry = Symbol.for(\"@virtualstate/navigation/transition/entry\");\nconst NavigationTransitionIsCommitted = Symbol.for(\"@virtualstate/navigation/transition/isCommitted\");\nconst NavigationTransitionIsFinished = Symbol.for(\"@virtualstate/navigation/transition/isFinished\");\nconst NavigationTransitionIsRejected = Symbol.for(\"@virtualstate/navigation/transition/isRejected\");\nconst NavigationTransitionKnown = Symbol.for(\"@virtualstate/navigation/transition/known\");\nconst NavigationTransitionPromises = Symbol.for(\"@virtualstate/navigation/transition/promises\");\nconst NavigationIntercept = Symbol.for(\"@virtualstate/navigation/intercept\");\nconst NavigationTransitionIsOngoing = Symbol.for(\"@virtualstate/navigation/transition/isOngoing\");\nconst NavigationTransitionIsPending = Symbol.for(\"@virtualstate/navigation/transition/isPending\");\nconst NavigationTransitionIsAsync = Symbol.for(\"@virtualstate/navigation/transition/isAsync\");\nconst NavigationTransitionWait = Symbol.for(\"@virtualstate/navigation/transition/wait\");\nconst NavigationTransitionPromiseResolved = Symbol.for(\"@virtualstate/navigation/transition/promise/resolved\");\nconst NavigationTransitionRejected = Symbol.for(\"@virtualstate/navigation/transition/rejected\");\nconst NavigationTransitionBeforeCommit = Symbol.for(\"@virtualstate/navigation/transition/beforeCommit\");\nconst NavigationTransitionCommit = Symbol.for(\"@virtualstate/navigation/transition/commit\");\nconst NavigationTransitionFinish = Symbol.for(\"@virtualstate/navigation/transition/finish\");\nconst NavigationTransitionStart = Symbol.for(\"@virtualstate/navigation/transition/start\");\nconst NavigationTransitionStartDeadline = Symbol.for(\"@virtualstate/navigation/transition/start/deadline\");\nconst NavigationTransitionError = Symbol.for(\"@virtualstate/navigation/transition/error\");\nconst NavigationTransitionFinally = Symbol.for(\"@virtualstate/navigation/transition/finally\");\nconst NavigationTransitionAbort = Symbol.for(\"@virtualstate/navigation/transition/abort\");\nconst NavigationTransitionInterceptOptionsCommit = Symbol.for(\"@virtualstate/navigation/transition/intercept/options/commit\");\nconst NavigationTransitionCommitIsManual = Symbol.for(\"@virtualstate/navigation/transition/commit/isManual\");\nclass NavigationTransition extends EventTarget {\n finished;\n /**\n * @experimental\n */\n committed;\n from;\n navigationType;\n /**\n * true if transition has an async intercept\n */\n [NavigationTransitionIsAsync] = false;\n /**\n * @experimental\n */\n [NavigationTransitionInterceptOptionsCommit];\n #options;\n [NavigationTransitionFinishedDeferred] = deferred();\n [NavigationTransitionCommittedDeferred] = deferred();\n get [NavigationTransitionIsPending]() {\n return !!this.#promises.size;\n }\n get [NavigationTransitionNavigationType]() {\n return this.#options[NavigationTransitionNavigationType];\n }\n get [NavigationTransitionInitialEntries]() {\n return this.#options[NavigationTransitionInitialEntries];\n }\n get [NavigationTransitionInitialIndex]() {\n return this.#options[NavigationTransitionInitialIndex];\n }\n get [NavigationTransitionCommitIsManual]() {\n return !!(this[NavigationTransitionInterceptOptionsCommit]?.includes(\"after-transition\") ||\n this[NavigationTransitionInterceptOptionsCommit]?.includes(\"manual\"));\n }\n [NavigationTransitionFinishedEntries];\n [NavigationTransitionFinishedIndex];\n [NavigationTransitionIsCommitted] = false;\n [NavigationTransitionIsFinished] = false;\n [NavigationTransitionIsRejected] = false;\n [NavigationTransitionIsOngoing] = false;\n [NavigationTransitionKnown] = new Set();\n [NavigationTransitionEntry];\n #promises = new Set();\n #rolledBack = false;\n #abortController = new AbortController$1();\n get signal() {\n return this.#abortController.signal;\n }\n get [NavigationTransitionPromises]() {\n return this.#promises;\n }\n constructor(init) {\n super();\n this[NavigationTransitionInterceptOptionsCommit] = [];\n this[NavigationTransitionFinishedDeferred] =\n init[NavigationTransitionFinishedDeferred] ??\n this[NavigationTransitionFinishedDeferred];\n this[NavigationTransitionCommittedDeferred] =\n init[NavigationTransitionCommittedDeferred] ??\n this[NavigationTransitionCommittedDeferred];\n this.#options = init;\n const finished = (this.finished =\n this[NavigationTransitionFinishedDeferred].promise);\n const committed = (this.committed =\n this[NavigationTransitionCommittedDeferred].promise);\n // Auto catching abort\n void finished.catch((error) => error);\n void committed.catch((error) => error);\n this.from = init.from;\n this.navigationType = init.navigationType;\n this[NavigationTransitionFinishedEntries] =\n init[NavigationTransitionFinishedEntries];\n this[NavigationTransitionFinishedIndex] =\n init[NavigationTransitionFinishedIndex];\n const known = init[NavigationTransitionKnown];\n if (known) {\n for (const entry of known) {\n this[NavigationTransitionKnown].add(entry);\n }\n }\n this[NavigationTransitionEntry] = init[NavigationTransitionEntry];\n // Event listeners\n {\n // Events to promises\n {\n this.addEventListener(NavigationTransitionCommit, this.#onCommitPromise, { once: true });\n this.addEventListener(NavigationTransitionFinish, this.#onFinishPromise, { once: true });\n }\n // Events to property setters\n {\n this.addEventListener(NavigationTransitionCommit, this.#onCommitSetProperty, { once: true });\n this.addEventListener(NavigationTransitionFinish, this.#onFinishSetProperty, { once: true });\n }\n // Rejection + Abort\n {\n this.addEventListener(NavigationTransitionError, this.#onError, {\n once: true,\n });\n this.addEventListener(NavigationTransitionAbort, () => {\n if (!this[NavigationTransitionIsFinished]) {\n return this[NavigationTransitionRejected](new AbortError());\n }\n });\n }\n // Proxy all events from this transition onto entry + the parent event target\n //\n // The parent could be another transition, or the Navigation, this allows us to\n // \"bubble up\" events layer by layer\n //\n // In this implementation, this allows individual transitions to \"intercept\" navigate and break the child\n // transition from happening\n //\n // TODO WARN this may not be desired behaviour vs standard spec'd Navigation\n {\n this.addEventListener(\"*\", this[NavigationTransitionEntry].dispatchEvent.bind(this[NavigationTransitionEntry]));\n this.addEventListener(\"*\", init[NavigationTransitionParentEventTarget].dispatchEvent.bind(init[NavigationTransitionParentEventTarget]));\n }\n }\n }\n rollback = (options) => {\n // console.log({ rolled: this.#rolledBack });\n if (this.#rolledBack) {\n // TODO\n throw new InvalidStateError(\"Rollback invoked multiple times: Please raise an issue at https://github.com/virtualstate/navigation with the use case where you want to use a rollback multiple times, this may have been unexpected behaviour\");\n }\n this.#rolledBack = true;\n return this.#options.rollback(options);\n };\n #onCommitSetProperty = () => {\n this[NavigationTransitionIsCommitted] = true;\n };\n #onFinishSetProperty = () => {\n this[NavigationTransitionIsFinished] = true;\n };\n #onFinishPromise = () => {\n // console.log(\"onFinishPromise\")\n this[NavigationTransitionFinishedDeferred].resolve(this[NavigationTransitionEntry]);\n };\n #onCommitPromise = () => {\n if (this.signal.aborted) ;\n else {\n this[NavigationTransitionCommittedDeferred].resolve(this[NavigationTransitionEntry]);\n }\n };\n #onError = (event) => {\n return this[NavigationTransitionRejected](event.error);\n };\n [NavigationTransitionPromiseResolved] = (...promises) => {\n for (const promise of promises) {\n this.#promises.delete(promise);\n }\n };\n [NavigationTransitionRejected] = async (reason) => {\n if (this[NavigationTransitionIsRejected])\n return;\n this[NavigationTransitionIsRejected] = true;\n this[NavigationTransitionAbort]();\n const navigationType = this[NavigationTransitionNavigationType];\n // console.log({ navigationType, reason, entry: this[NavigationTransitionEntry] });\n if (typeof navigationType === \"string\" || navigationType === Rollback) {\n // console.log(\"navigateerror\", { reason, z: isInvalidStateError(reason) });\n await this.dispatchEvent({\n type: \"navigateerror\",\n error: reason,\n get message() {\n if (reason instanceof Error) {\n return reason.message;\n }\n return `${reason}`;\n },\n });\n // console.log(\"navigateerror finished\");\n if (navigationType !== Rollback &&\n !(isInvalidStateError(reason) || isAbortError(reason))) {\n try {\n // console.log(\"Rollback\", navigationType);\n // console.warn(\"Rolling back immediately due to internal error\", error);\n await this.rollback()?.finished;\n // console.log(\"Rollback complete\", navigationType);\n }\n catch (error) {\n // console.error(\"Failed to rollback\", error);\n throw new InvalidStateError(\"Failed to rollback, please raise an issue at https://github.com/virtualstate/navigation/issues\");\n }\n }\n }\n this[NavigationTransitionCommittedDeferred].reject(reason);\n this[NavigationTransitionFinishedDeferred].reject(reason);\n };\n [NavigationIntercept] = (options) => {\n const transition = this;\n const promise = parseOptions();\n this[NavigationTransitionIsOngoing] = true;\n if (!promise)\n return;\n this[NavigationTransitionIsAsync] = true;\n const statusPromise = promise\n .then(() => ({\n status: \"fulfilled\",\n value: undefined,\n }))\n .catch(async (reason) => {\n await this[NavigationTransitionRejected](reason);\n return {\n status: \"rejected\",\n reason,\n };\n });\n this.#promises.add(statusPromise);\n function parseOptions() {\n if (!options)\n return undefined;\n if (isPromise(options)) {\n logWarning(\"EVENT_INTERCEPT_HANDLER\");\n return options;\n }\n if (typeof options === \"function\") {\n logWarning(\"EVENT_INTERCEPT_HANDLER\");\n return options();\n }\n const { handler, commit } = options;\n if (commit && typeof commit === \"string\") {\n transition[NavigationTransitionInterceptOptionsCommit].push(commit);\n }\n if (typeof handler !== \"function\") {\n return;\n }\n return handler();\n }\n };\n [NavigationTransitionWait] = async () => {\n if (!this.#promises.size)\n return this[NavigationTransitionEntry];\n try {\n const captured = [...this.#promises];\n const results = await Promise.all(captured);\n const rejected = results.filter((result) => result.status === \"rejected\");\n // console.log({ rejected, results, captured });\n if (rejected.length) {\n // TODO handle differently when there are failures, e.g. we could move navigateerror to here\n if (rejected.length === 1) {\n throw rejected[0].reason;\n }\n if (typeof AggregateError !== \"undefined\") {\n throw new AggregateError(rejected.map(({ reason }) => reason));\n }\n throw new Error();\n }\n this[NavigationTransitionPromiseResolved](...captured);\n if (this[NavigationTransitionIsPending]) {\n return this[NavigationTransitionWait]();\n }\n return this[NavigationTransitionEntry];\n }\n catch (error) {\n await this.#onError(error);\n throw await Promise.reject(error);\n }\n finally {\n await this[NavigationTransitionFinish]();\n }\n };\n [NavigationTransitionAbort]() {\n if (this.#abortController.signal.aborted)\n return;\n this.#abortController.abort();\n this.dispatchEvent({\n type: NavigationTransitionAbort,\n transition: this,\n entry: this[NavigationTransitionEntry],\n });\n }\n [NavigationTransitionFinish] = async () => {\n if (this[NavigationTransitionIsFinished]) {\n return;\n }\n await this.dispatchEvent({\n type: NavigationTransitionFinish,\n transition: this,\n entry: this[NavigationTransitionEntry],\n intercept: this[NavigationIntercept],\n });\n };\n}\n\nfunction getWindowBaseURL() {\n try {\n if (typeof window !== \"undefined\" && window.location) {\n return window.location.href;\n }\n }\n catch { }\n}\nfunction getBaseURL(url) {\n const baseURL = getWindowBaseURL() ?? \"https://html.spec.whatwg.org/\";\n return new URL(\n // Deno wants this to be always a string\n (url ?? \"\").toString(), baseURL);\n}\n\nfunction defer() {\n let resolve = undefined, reject = undefined, settled = false, status = \"pending\";\n const promise = new Promise((resolveFn, rejectFn) => {\n resolve = (value) => {\n status = \"fulfilled\";\n settled = true;\n resolveFn(value);\n };\n reject = (reason) => {\n status = \"rejected\";\n settled = true;\n rejectFn(reason);\n };\n });\n ok(resolve);\n ok(reject);\n return {\n get settled() {\n return settled;\n },\n get status() {\n return status;\n },\n resolve,\n reject,\n promise,\n };\n}\n\nclass NavigationCurrentEntryChangeEvent {\n type;\n from;\n navigationType;\n constructor(type, init) {\n this.type = type;\n if (!init) {\n throw new TypeError(\"init required\");\n }\n if (!init.from) {\n throw new TypeError(\"from required\");\n }\n this.from = init.from;\n this.navigationType = init.navigationType ?? undefined;\n }\n}\n\nclass NavigateEvent {\n type;\n canIntercept;\n /**\n * @deprecated\n */\n canTransition;\n destination;\n downloadRequest;\n formData;\n hashChange;\n info;\n signal;\n userInitiated;\n navigationType;\n constructor(type, init) {\n this.type = type;\n if (!init) {\n throw new TypeError(\"init required\");\n }\n if (!init.destination) {\n throw new TypeError(\"destination required\");\n }\n if (!init.signal) {\n throw new TypeError(\"signal required\");\n }\n this.canIntercept = init.canIntercept ?? false;\n this.canTransition = init.canIntercept ?? false;\n this.destination = init.destination;\n this.downloadRequest = init.downloadRequest;\n this.formData = init.formData;\n this.hashChange = init.hashChange ?? false;\n this.info = init.info;\n this.signal = init.signal;\n this.userInitiated = init.userInitiated ?? false;\n this.navigationType = init.navigationType ?? \"push\";\n }\n commit() {\n throw new Error(\"Not implemented\");\n }\n intercept(options) {\n throw new Error(\"Not implemented\");\n }\n preventDefault() {\n throw new Error(\"Not implemented\");\n }\n reportError(reason) {\n throw new Error(\"Not implemented\");\n }\n scroll() {\n throw new Error(\"Not implemented\");\n }\n /**\n * @deprecated\n */\n transitionWhile(options) {\n return this.intercept(options);\n }\n}\n\nconst NavigationFormData = Symbol.for(\"@virtualstate/navigation/formData\");\nconst NavigationDownloadRequest = Symbol.for(\"@virtualstate/navigation/downloadRequest\");\nconst NavigationCanIntercept = Symbol.for(\"@virtualstate/navigation/canIntercept\");\nconst NavigationUserInitiated = Symbol.for(\"@virtualstate/navigation/userInitiated\");\nconst NavigationOriginalEvent = Symbol.for(\"@virtualstate/navigation/originalEvent\");\nfunction noop() {\n return undefined;\n}\nfunction getEntryIndex(entries, entry) {\n const knownIndex = entry.index;\n if (knownIndex !== -1) {\n return knownIndex;\n }\n // TODO find an entry if it has changed id\n return -1;\n}\nfunction createNavigationTransition(context) {\n const { commit: transitionCommit, currentIndex, options, known: initialKnown, currentEntry, transition, transition: { [NavigationTransitionInitialEntries]: previousEntries, [NavigationTransitionEntry]: entry, [NavigationIntercept]: intercept, }, reportError } = context;\n let { transition: { [NavigationTransitionNavigationType]: navigationType }, } = context;\n let resolvedEntries = [...previousEntries];\n const known = new Set(initialKnown);\n let destinationIndex = -1, nextIndex = currentIndex;\n if (navigationType === Rollback) {\n const { index } = options ?? { index: undefined };\n if (typeof index !== \"number\")\n throw new InvalidStateError(\"Expected index to be provided for rollback\");\n destinationIndex = index;\n nextIndex = index;\n }\n else if (navigationType === \"traverse\" || navigationType === \"reload\") {\n destinationIndex = getEntryIndex(previousEntries, entry);\n nextIndex = destinationIndex;\n }\n else if (navigationType === \"replace\") {\n if (currentIndex === -1) {\n navigationType = \"push\";\n destinationIndex = currentIndex + 1;\n nextIndex = destinationIndex;\n }\n else {\n destinationIndex = currentIndex;\n nextIndex = currentIndex;\n }\n }\n else {\n destinationIndex = currentIndex + 1;\n nextIndex = destinationIndex;\n }\n if (typeof destinationIndex !== \"number\" || destinationIndex === -1) {\n throw new InvalidStateError(\"Could not resolve next index\");\n }\n // console.log({ navigationType, entry, options });\n if (!entry.url) {\n console.trace({ navigationType, entry, options });\n throw new InvalidStateError(\"Expected entry url\");\n }\n const destination = {\n url: entry.url,\n key: entry.key,\n id: entry.id,\n index: destinationIndex,\n sameDocument: entry.sameDocument,\n getState() {\n return entry.getState();\n },\n };\n let hashChange = false;\n const currentUrlInstance = getBaseURL(currentEntry?.url);\n const destinationUrlInstance = new URL(destination.url);\n const currentHash = currentUrlInstance.hash;\n const destinationHash = destinationUrlInstance.hash;\n // console.log({ currentHash, destinationHash });\n if (currentHash !== destinationHash) {\n const currentUrlInstanceWithoutHash = new URL(currentUrlInstance.toString());\n currentUrlInstanceWithoutHash.hash = \"\";\n const destinationUrlInstanceWithoutHash = new URL(destinationUrlInstance.toString());\n destinationUrlInstanceWithoutHash.hash = \"\";\n hashChange =\n currentUrlInstanceWithoutHash.toString() ===\n destinationUrlInstanceWithoutHash.toString();\n // console.log({ hashChange, currentUrlInstanceWithoutHash: currentUrlInstanceWithoutHash.toString(), before: destinationUrlInstanceWithoutHash.toString() })\n }\n let contextToCommit;\n const { resolve: resolveCommit, promise: waitForCommit } = defer();\n function commit() {\n ok(contextToCommit, \"Expected contextToCommit\");\n resolveCommit(transitionCommit(contextToCommit));\n }\n const abortController = new AbortController$1();\n const event = new NavigateEvent(\"navigate\", {\n signal: abortController.signal,\n info: undefined,\n ...options,\n canIntercept: options?.[NavigationCanIntercept] ?? true,\n formData: options?.[NavigationFormData] ?? undefined,\n downloadRequest: options?.[NavigationDownloadRequest] ?? undefined,\n hashChange,\n navigationType: options?.navigationType ??\n (typeof navigationType === \"string\" ? navigationType : \"replace\"),\n userInitiated: options?.[NavigationUserInitiated] ?? false,\n destination,\n });\n const originalEvent = options?.[NavigationOriginalEvent];\n const preventDefault = transition[NavigationTransitionAbort].bind(transition);\n if (originalEvent) {\n const definedEvent = originalEvent;\n event.intercept = function originalEventIntercept(options) {\n definedEvent.preventDefault();\n return intercept(options);\n };\n event.preventDefault = function originalEventPreventDefault() {\n definedEvent.preventDefault();\n return preventDefault();\n };\n }\n else {\n event.intercept = intercept;\n event.preventDefault = preventDefault;\n }\n // Enforce that transitionWhile and intercept match\n event.transitionWhile = event.intercept;\n event.commit = commit;\n if (reportError) {\n event.reportError = reportError;\n }\n event.scroll = noop;\n if (originalEvent) {\n event.originalEvent = originalEvent;\n }\n const currentEntryChange = new NavigationCurrentEntryChangeEvent(\"currententrychange\", {\n from: currentEntry,\n navigationType: event.navigationType,\n });\n let updatedEntries = [], removedEntries = [], addedEntries = [];\n const previousKeys = previousEntries.map(entry => entry.key);\n if (navigationType === Rollback) {\n const { entries } = options ?? { entries: undefined };\n if (!entries)\n throw new InvalidStateError(\"Expected entries to be provided for rollback\");\n resolvedEntries = entries;\n resolvedEntries.forEach((entry) => known.add(entry));\n const keys = resolvedEntries.map(entry => entry.key);\n removedEntries = previousEntries.filter(entry => !keys.includes(entry.key));\n addedEntries = resolvedEntries.filter(entry => !previousKeys.includes(entry.key));\n }\n // Default next index is current entries length, aka\n // console.log({ navigationType, givenNavigationType, index: this.#currentIndex, resolvedNextIndex });\n else if (navigationType === \"replace\" ||\n navigationType === \"traverse\" ||\n navigationType === \"reload\") {\n resolvedEntries[destination.index] = entry;\n if (navigationType !== \"traverse\") {\n updatedEntries.push(entry);\n }\n if (navigationType === \"replace\") {\n resolvedEntries = resolvedEntries.slice(0, destination.index + 1);\n }\n const keys = resolvedEntries.map(entry => entry.key);\n removedEntries = previousEntries.filter(entry => !keys.includes(entry.key));\n if (previousKeys.includes(entry.id)) {\n addedEntries = [entry];\n }\n }\n else if (navigationType === \"push\") {\n let removed = false;\n // Trim forward, we have reset our stack\n if (resolvedEntries[destination.index]) {\n // const before = [...this.#entries];\n resolvedEntries = resolvedEntries.slice(0, destination.index);\n // console.log({ before, after: [...this.#entries]})\n removed = true;\n }\n resolvedEntries.push(entry);\n addedEntries = [entry];\n if (removed) {\n const keys = resolvedEntries.map(entry => entry.key);\n removedEntries = previousEntries.filter(entry => !keys.includes(entry.key));\n }\n }\n known.add(entry);\n let entriesChange = undefined;\n if (updatedEntries.length || addedEntries.length || removedEntries.length) {\n entriesChange = {\n updatedEntries,\n addedEntries,\n removedEntries\n };\n }\n contextToCommit = {\n entries: resolvedEntries,\n index: nextIndex,\n known,\n entriesChange\n };\n return {\n entries: resolvedEntries,\n known,\n index: nextIndex,\n currentEntryChange,\n destination,\n navigate: event,\n navigationType,\n waitForCommit,\n commit,\n abortController\n };\n}\n\nfunction createEvent(event) {\n if (typeof CustomEvent !== \"undefined\" && typeof event.type === \"string\") {\n if (event instanceof CustomEvent) {\n return event;\n }\n const { type, detail, ...rest } = event;\n const customEvent = new CustomEvent(type, {\n detail: detail ?? rest,\n });\n Object.assign(customEvent, rest);\n assertEvent(customEvent, event.type);\n return customEvent;\n }\n return event;\n}\n\nconst NavigationSetOptions = Symbol.for(\"@virtualstate/navigation/setOptions\");\nconst NavigationSetEntries = Symbol.for(\"@virtualstate/navigation/setEntries\");\nconst NavigationSetCurrentIndex = Symbol.for(\"@virtualstate/navigation/setCurrentIndex\");\nconst NavigationSetCurrentKey = Symbol.for(\"@virtualstate/navigation/setCurrentKey\");\nconst NavigationGetState = Symbol.for(\"@virtualstate/navigation/getState\");\nconst NavigationSetState = Symbol.for(\"@virtualstate/navigation/setState\");\nconst NavigationDisposeState = Symbol.for(\"@virtualstate/navigation/disposeState\");\nfunction isNavigationNavigationType(value) {\n return (value === \"reload\" ||\n value === \"push\" ||\n value === \"replace\" ||\n value === \"traverse\");\n}\nclass Navigation extends NavigationEventTarget {\n // Should be always 0 or 1\n #transitionInProgressCount = 0;\n // #activePromise?: Promise<void> = undefined;\n #entries = [];\n #known = new Set();\n #currentIndex = -1;\n #activeTransition;\n #knownTransitions = new WeakSet();\n #baseURL = \"\";\n #initialEntry = undefined;\n #options = undefined;\n get canGoBack() {\n return !!this.#entries[this.#currentIndex - 1];\n }\n get canGoForward() {\n return !!this.#entries[this.#currentIndex + 1];\n }\n get currentEntry() {\n if (this.#currentIndex === -1) {\n if (!this.#initialEntry) {\n this.#initialEntry = new NavigationHistoryEntry({\n getState: this[NavigationGetState],\n navigationType: \"push\",\n index: -1,\n sameDocument: false,\n url: this.#baseURL.toString()\n });\n }\n return this.#initialEntry;\n }\n return this.#entries[this.#currentIndex];\n }\n get transition() {\n const transition = this.#activeTransition;\n // Never let an aborted transition leak, it doesn't need to be accessed any more\n return transition?.signal.aborted ? undefined : transition;\n }\n constructor(options = {}) {\n super();\n this[NavigationSetOptions](options);\n }\n [NavigationSetOptions](options) {\n this.#options = options;\n this.#baseURL = getBaseURL(options?.baseURL);\n this.#entries = [];\n if (options.entries) {\n this[NavigationSetEntries](options.entries);\n }\n if (options.currentKey) {\n this[NavigationSetCurrentKey](options.currentKey);\n }\n else if (typeof options.currentIndex === \"number\") {\n this[NavigationSetCurrentIndex](options.currentIndex);\n }\n }\n /**\n * Set the current entry key without any lifecycle eventing\n *\n * This would be more exact than providing an index\n * @param key\n */\n [NavigationSetCurrentKey](key) {\n const index = this.#entries.findIndex(entry => entry.key === key);\n // If the key can't be found, becomes a no-op\n if (index === -1)\n return;\n this.#currentIndex = index;\n }\n /**\n * Set the current entry index without any lifecycle eventing\n * @param index\n */\n [NavigationSetCurrentIndex](index) {\n if (index <= -1)\n return;\n if (index >= this.#entries.length)\n return;\n this.#currentIndex = index;\n }\n /**\n * Set the entries available without any lifecycle eventing\n * @param entries\n */\n [NavigationSetEntries](entries) {\n this.#entries = entries.map(({ key, url, navigationType, state, sameDocument }, index) => new NavigationHistoryEntry({\n getState: this[NavigationGetState],\n navigationType: isNavigationNavigationType(navigationType) ? navigationType : \"push\",\n sameDocument: sameDocument ?? true,\n index,\n url,\n key,\n state\n }));\n if (this.#currentIndex === -1 && this.#entries.length) {\n // Initialise, even if its not the one that was expected\n this.#currentIndex = 0;\n }\n }\n [NavigationGetState] = (entry) => {\n return this.#options?.getState?.(entry) ?? undefined;\n };\n [NavigationSetState] = (entry) => {\n return this.#options?.setState?.(entry);\n };\n [NavigationDisposeState] = (entry) => {\n return this.#options?.disposeState?.(entry);\n };\n back(options) {\n if (!this.canGoBack)\n throw new InvalidStateError(\"Cannot go back\");\n const entry = this.#entries[this.#currentIndex - 1];\n