/*! * vue-router v5.0.3 * (c) 2026 Eduardo San Martin Morote * @license MIT */ import { computed, defineComponent, getCurrentInstance, h, inject, nextTick, onActivated, onDeactivated, onUnmounted, provide, reactive, ref, shallowReactive, shallowRef, unref, watch, watchEffect } from "vue"; import { setupDevtoolsPlugin } from "@vue/devtools-api"; //#region src/utils/env.ts const isBrowser = typeof document !== "undefined"; //#endregion //#region src/utils/index.ts /** * Allows differentiating lazy components from functional components and vue-class-component * @internal * * @param component */ function isRouteComponent(component) { return typeof component === "object" || "displayName" in component || "props" in component || "__vccOpts" in component; } function isESModule(obj) { return obj.__esModule || obj[Symbol.toStringTag] === "Module" || obj.default && isRouteComponent(obj.default); } const assign = Object.assign; function applyToParams(fn, params) { const newParams = {}; for (const key in params) { const value = params[key]; newParams[key] = isArray(value) ? value.map(fn) : fn(value); } return newParams; } const noop = () => {}; /** * Typesafe alternative to Array.isArray * https://github.com/microsoft/TypeScript/pull/48228 * * @internal */ const isArray = Array.isArray; function mergeOptions(defaults, partialOptions) { const options = {}; for (const key in defaults) options[key] = key in partialOptions ? partialOptions[key] : defaults[key]; return options; } //#endregion //#region src/warning.ts function warn(msg) { const args = Array.from(arguments).slice(1); console.warn.apply(console, ["[Vue Router warn]: " + msg].concat(args)); } //#endregion //#region src/encoding.ts /** * Encoding Rules (␣ = Space) * - Path: ␣ " < > # ? { } * - Query: ␣ " < > # & = * - Hash: ␣ " < > ` * * On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2) * defines some extra characters to be encoded. Most browsers do not encode them * in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to * also encode `!'()*`. Leaving un-encoded only ASCII alphanumeric(`a-zA-Z0-9`) * plus `-._~`. This extra safety should be applied to query by patching the * string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\` * should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\` * into a `/` if directly typed in. The _backtick_ (`````) should also be * encoded everywhere because some browsers like FF encode it when directly * written while others don't. Safari and IE don't encode ``"<>{}``` in hash. */ const HASH_RE = /#/g; const AMPERSAND_RE = /&/g; const SLASH_RE = /\//g; const EQUAL_RE = /=/g; const IM_RE = /\?/g; const PLUS_RE = /\+/g; /** * NOTE: It's not clear to me if we should encode the + symbol in queries, it * seems to be less flexible than not doing so and I can't find out the legacy * systems requiring this for regular requests like text/html. In the standard, * the encoding of the plus character is only mentioned for * application/x-www-form-urlencoded * (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo * leave the plus character as is in queries. To be more flexible, we allow the * plus character on the query, but it can also be manually encoded by the user. * * Resources: * - https://url.spec.whatwg.org/#urlencoded-parsing * - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20 */ const ENC_BRACKET_OPEN_RE = /%5B/g; const ENC_BRACKET_CLOSE_RE = /%5D/g; const ENC_CARET_RE = /%5E/g; const ENC_BACKTICK_RE = /%60/g; const ENC_CURLY_OPEN_RE = /%7B/g; const ENC_PIPE_RE = /%7C/g; const ENC_CURLY_CLOSE_RE = /%7D/g; const ENC_SPACE_RE = /%20/g; /** * Encode characters that need to be encoded on the path, search and hash * sections of the URL. * * @internal * @param text - string to encode * @returns encoded string */ function commonEncode(text) { return text == null ? "" : encodeURI("" + text).replace(ENC_PIPE_RE, "|").replace(ENC_BRACKET_OPEN_RE, "[").replace(ENC_BRACKET_CLOSE_RE, "]"); } /** * Encode characters that need to be encoded on the hash section of the URL. * * @param text - string to encode * @returns encoded string */ function encodeHash(text) { return commonEncode(text).replace(ENC_CURLY_OPEN_RE, "{").replace(ENC_CURLY_CLOSE_RE, "}").replace(ENC_CARET_RE, "^"); } /** * Encode characters that need to be encoded query values on the query * section of the URL. * * @param text - string to encode * @returns encoded string */ function encodeQueryValue(text) { return commonEncode(text).replace(PLUS_RE, "%2B").replace(ENC_SPACE_RE, "+").replace(HASH_RE, "%23").replace(AMPERSAND_RE, "%26").replace(ENC_BACKTICK_RE, "`").replace(ENC_CURLY_OPEN_RE, "{").replace(ENC_CURLY_CLOSE_RE, "}").replace(ENC_CARET_RE, "^"); } /** * Like `encodeQueryValue` but also encodes the `=` character. * * @param text - string to encode */ function encodeQueryKey(text) { return encodeQueryValue(text).replace(EQUAL_RE, "%3D"); } /** * Encode characters that need to be encoded on the path section of the URL. * * @param text - string to encode * @returns encoded string */ function encodePath(text) { return commonEncode(text).replace(HASH_RE, "%23").replace(IM_RE, "%3F"); } /** * Encode characters that need to be encoded on the path section of the URL as a * param. This function encodes everything {@link encodePath} does plus the * slash (`/`) character. If `text` is `null` or `undefined`, returns an empty * string instead. * * @param text - string to encode * @returns encoded string */ function encodeParam(text) { return encodePath(text).replace(SLASH_RE, "%2F"); } function decode(text) { if (text == null) return null; try { return decodeURIComponent("" + text); } catch (err) { warn(`Error decoding "${text}". Using original value`); } return "" + text; } //#endregion //#region src/location.ts const TRAILING_SLASH_RE = /\/$/; const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, ""); /** * Transforms a URI into a normalized history location * * @param parseQuery * @param location - URI to normalize * @param currentLocation - current absolute location. Allows resolving relative * paths. Must start with `/`. Defaults to `/` * @returns a normalized history location */ function parseURL(parseQuery, location, currentLocation = "/") { let path, query = {}, searchString = "", hash = ""; const hashPos = location.indexOf("#"); let searchPos = location.indexOf("?"); searchPos = hashPos >= 0 && searchPos > hashPos ? -1 : searchPos; if (searchPos >= 0) { path = location.slice(0, searchPos); searchString = location.slice(searchPos, hashPos > 0 ? hashPos : location.length); query = parseQuery(searchString.slice(1)); } if (hashPos >= 0) { path = path || location.slice(0, hashPos); hash = location.slice(hashPos, location.length); } path = resolveRelativePath(path != null ? path : location, currentLocation); return { fullPath: path + searchString + hash, path, query, hash: decode(hash) }; } /** * Stringifies a URL object * * @param stringifyQuery * @param location */ function stringifyURL(stringifyQuery, location) { const query = location.query ? stringifyQuery(location.query) : ""; return location.path + (query && "?") + query + (location.hash || ""); } /** * Strips off the base from the beginning of a location.pathname in a non-case-sensitive way. * * @param pathname - location.pathname * @param base - base to strip off */ function stripBase(pathname, base) { if (!base || !pathname.toLowerCase().startsWith(base.toLowerCase())) return pathname; return pathname.slice(base.length) || "/"; } /** * Checks if two RouteLocation are equal. This means that both locations are * pointing towards the same {@link RouteRecord} and that all `params`, `query` * parameters and `hash` are the same * * @param stringifyQuery - A function that takes a query object of type LocationQueryRaw and returns a string representation of it. * @param a - first {@link RouteLocation} * @param b - second {@link RouteLocation} */ function isSameRouteLocation(stringifyQuery, a, b) { const aLastIndex = a.matched.length - 1; const bLastIndex = b.matched.length - 1; return aLastIndex > -1 && aLastIndex === bLastIndex && isSameRouteRecord(a.matched[aLastIndex], b.matched[bLastIndex]) && isSameRouteLocationParams(a.params, b.params) && stringifyQuery(a.query) === stringifyQuery(b.query) && a.hash === b.hash; } /** * Check if two `RouteRecords` are equal. Takes into account aliases: they are * considered equal to the `RouteRecord` they are aliasing. * * @param a - first {@link RouteRecord} * @param b - second {@link RouteRecord} */ function isSameRouteRecord(a, b) { return (a.aliasOf || a) === (b.aliasOf || b); } function isSameRouteLocationParams(a, b) { if (Object.keys(a).length !== Object.keys(b).length) return false; for (var key in a) if (!isSameRouteLocationParamsValue(a[key], b[key])) return false; return true; } function isSameRouteLocationParamsValue(a, b) { return isArray(a) ? isEquivalentArray(a, b) : isArray(b) ? isEquivalentArray(b, a) : (a && a.valueOf()) === (b && b.valueOf()); } /** * Check if two arrays are the same or if an array with one single entry is the * same as another primitive value. Used to check query and parameters * * @param a - array of values * @param b - array of values or a single value */ function isEquivalentArray(a, b) { return isArray(b) ? a.length === b.length && a.every((value, i) => value === b[i]) : a.length === 1 && a[0] === b; } /** * Resolves a relative path that starts with `.`. * * @param to - path location we are resolving * @param from - currentLocation.path, should start with `/` */ function resolveRelativePath(to, from) { if (to.startsWith("/")) return to; if (!from.startsWith("/")) { warn(`Cannot resolve a relative location without an absolute path. Trying to resolve "${to}" from "${from}". It should look like "/${from}".`); return to; } if (!to) return from; const fromSegments = from.split("/"); const toSegments = to.split("/"); const lastToSegment = toSegments[toSegments.length - 1]; if (lastToSegment === ".." || lastToSegment === ".") toSegments.push(""); let position = fromSegments.length - 1; let toPosition; let segment; for (toPosition = 0; toPosition < toSegments.length; toPosition++) { segment = toSegments[toPosition]; if (segment === ".") continue; if (segment === "..") { if (position > 1) position--; } else break; } return fromSegments.slice(0, position).join("/") + "/" + toSegments.slice(toPosition).join("/"); } /** * Initial route location where the router is. Can be used in navigation guards * to differentiate the initial navigation. * * @example * ```js * import { START_LOCATION } from 'vue-router' * * router.beforeEach((to, from) => { * if (from === START_LOCATION) { * // initial navigation * } * }) * ``` */ const START_LOCATION_NORMALIZED = { path: "/", name: void 0, params: {}, query: {}, hash: "", fullPath: "/", matched: [], meta: {}, redirectedFrom: void 0 }; //#endregion //#region src/history/common.ts let NavigationType = /* @__PURE__ */ function(NavigationType) { NavigationType["pop"] = "pop"; NavigationType["push"] = "push"; return NavigationType; }({}); let NavigationDirection = /* @__PURE__ */ function(NavigationDirection) { NavigationDirection["back"] = "back"; NavigationDirection["forward"] = "forward"; NavigationDirection["unknown"] = ""; return NavigationDirection; }({}); /** * Starting location for Histories */ const START = ""; /** * Normalizes a base by removing any trailing slash and reading the base tag if * present. * * @param base - base to normalize */ function normalizeBase(base) { if (!base) if (isBrowser) { const baseEl = document.querySelector("base"); base = baseEl && baseEl.getAttribute("href") || "/"; base = base.replace(/^\w+:\/\/[^\/]+/, ""); } else base = "/"; if (base[0] !== "/" && base[0] !== "#") base = "/" + base; return removeTrailingSlash(base); } const BEFORE_HASH_RE = /^[^#]+#/; function createHref(base, location) { return base.replace(BEFORE_HASH_RE, "#") + location; } //#endregion //#region src/scrollBehavior.ts function getElementPosition(el, offset) { const docRect = document.documentElement.getBoundingClientRect(); const elRect = el.getBoundingClientRect(); return { behavior: offset.behavior, left: elRect.left - docRect.left - (offset.left || 0), top: elRect.top - docRect.top - (offset.top || 0) }; } const computeScrollPosition = () => ({ left: window.scrollX, top: window.scrollY }); function scrollToPosition(position) { let scrollToOptions; if ("el" in position) { const positionEl = position.el; const isIdSelector = typeof positionEl === "string" && positionEl.startsWith("#"); /** * `id`s can accept pretty much any characters, including CSS combinators * like `>` or `~`. It's still possible to retrieve elements using * `document.getElementById('~')` but it needs to be escaped when using * `document.querySelector('#\\~')` for it to be valid. The only * requirements for `id`s are them to be unique on the page and to not be * empty (`id=""`). Because of that, when passing an id selector, it should * be properly escaped for it to work with `querySelector`. We could check * for the id selector to be simple (no CSS combinators `+ >~`) but that * would make things inconsistent since they are valid characters for an * `id` but would need to be escaped when using `querySelector`, breaking * their usage and ending up in no selector returned. Selectors need to be * escaped: * * - `#1-thing` becomes `#\31 -thing` * - `#with~symbols` becomes `#with\\~symbols` * * - More information about the topic can be found at * https://mathiasbynens.be/notes/html5-id-class. * - Practical example: https://mathiasbynens.be/demo/html5-id */ if (typeof position.el === "string") { if (!isIdSelector || !document.getElementById(position.el.slice(1))) try { const foundEl = document.querySelector(position.el); if (isIdSelector && foundEl) { warn(`The selector "${position.el}" should be passed as "el: document.querySelector('${position.el}')" because it starts with "#".`); return; } } catch (err) { warn(`The selector "${position.el}" is invalid. If you are using an id selector, make sure to escape it. You can find more information about escaping characters in selectors at https://mathiasbynens.be/notes/css-escapes or use CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape).`); return; } } const el = typeof positionEl === "string" ? isIdSelector ? document.getElementById(positionEl.slice(1)) : document.querySelector(positionEl) : positionEl; if (!el) { warn(`Couldn't find element using selector "${position.el}" returned by scrollBehavior.`); return; } scrollToOptions = getElementPosition(el, position); } else scrollToOptions = position; if ("scrollBehavior" in document.documentElement.style) window.scrollTo(scrollToOptions); else window.scrollTo(scrollToOptions.left != null ? scrollToOptions.left : window.scrollX, scrollToOptions.top != null ? scrollToOptions.top : window.scrollY); } function getScrollKey(path, delta) { return (history.state ? history.state.position - delta : -1) + path; } const scrollPositions = /* @__PURE__ */ new Map(); function saveScrollPosition(key, scrollPosition) { scrollPositions.set(key, scrollPosition); } function getSavedScrollPosition(key) { const scroll = scrollPositions.get(key); scrollPositions.delete(key); return scroll; } /** * ScrollBehavior instance used by the router to compute and restore the scroll * position when navigating. */ //#endregion //#region src/history/html5.ts let createBaseLocation = () => location.protocol + "//" + location.host; /** * Creates a normalized history location from a window.location object * @param base - The base path * @param location - The window.location object */ function createCurrentLocation(base, location) { const { pathname, search, hash } = location; const hashPos = base.indexOf("#"); if (hashPos > -1) { let slicePos = hash.includes(base.slice(hashPos)) ? base.slice(hashPos).length : 1; let pathFromHash = hash.slice(slicePos); if (pathFromHash[0] !== "/") pathFromHash = "/" + pathFromHash; return stripBase(pathFromHash, ""); } return stripBase(pathname, base) + search + hash; } function useHistoryListeners(base, historyState, currentLocation, replace) { let listeners = []; let teardowns = []; let pauseState = null; const popStateHandler = ({ state }) => { const to = createCurrentLocation(base, location); const from = currentLocation.value; const fromState = historyState.value; let delta = 0; if (state) { currentLocation.value = to; historyState.value = state; if (pauseState && pauseState === from) { pauseState = null; return; } delta = fromState ? state.position - fromState.position : 0; } else replace(to); listeners.forEach((listener) => { listener(currentLocation.value, from, { delta, type: NavigationType.pop, direction: delta ? delta > 0 ? NavigationDirection.forward : NavigationDirection.back : NavigationDirection.unknown }); }); }; function pauseListeners() { pauseState = currentLocation.value; } function listen(callback) { listeners.push(callback); const teardown = () => { const index = listeners.indexOf(callback); if (index > -1) listeners.splice(index, 1); }; teardowns.push(teardown); return teardown; } function beforeUnloadListener() { if (document.visibilityState === "hidden") { const { history } = window; if (!history.state) return; history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), ""); } } function destroy() { for (const teardown of teardowns) teardown(); teardowns = []; window.removeEventListener("popstate", popStateHandler); window.removeEventListener("pagehide", beforeUnloadListener); document.removeEventListener("visibilitychange", beforeUnloadListener); } window.addEventListener("popstate", popStateHandler); window.addEventListener("pagehide", beforeUnloadListener); document.addEventListener("visibilitychange", beforeUnloadListener); return { pauseListeners, listen, destroy }; } /** * Creates a state object */ function buildState(back, current, forward, replaced = false, computeScroll = false) { return { back, current, forward, replaced, position: window.history.length, scroll: computeScroll ? computeScrollPosition() : null }; } function useHistoryStateNavigation(base) { const { history, location } = window; const currentLocation = { value: createCurrentLocation(base, location) }; const historyState = { value: history.state }; if (!historyState.value) changeLocation(currentLocation.value, { back: null, current: currentLocation.value, forward: null, position: history.length - 1, replaced: true, scroll: null }, true); function changeLocation(to, state, replace) { /** * if a base tag is provided, and we are on a normal domain, we have to * respect the provided `base` attribute because pushState() will use it and * potentially erase anything before the `#` like at * https://github.com/vuejs/router/issues/685 where a base of * `/folder/#` but a base of `/` would erase the `/folder/` section. If * there is no host, the `` tag makes no sense and if there isn't a * base tag we can just use everything after the `#`. */ const hashIndex = base.indexOf("#"); const url = hashIndex > -1 ? (location.host && document.querySelector("base") ? base : base.slice(hashIndex)) + to : createBaseLocation() + base + to; try { history[replace ? "replaceState" : "pushState"](state, "", url); historyState.value = state; } catch (err) { warn("Error with push/replace State", err); location[replace ? "replace" : "assign"](url); } } function replace(to, data) { changeLocation(to, assign({}, history.state, buildState(historyState.value.back, to, historyState.value.forward, true), data, { position: historyState.value.position }), true); currentLocation.value = to; } function push(to, data) { const currentState = assign({}, historyState.value, history.state, { forward: to, scroll: computeScrollPosition() }); if (!history.state) warn("history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\nhistory.replaceState(history.state, '', url)\n\nYou can find more information at https://router.vuejs.org/guide/migration/#Usage-of-history-state"); changeLocation(currentState.current, currentState, true); changeLocation(to, assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data), false); currentLocation.value = to; } return { location: currentLocation, state: historyState, push, replace }; } /** * Creates an HTML5 history. Most common history for single page applications. * * @param base - */ function createWebHistory(base) { base = normalizeBase(base); const historyNavigation = useHistoryStateNavigation(base); const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace); function go(delta, triggerListeners = true) { if (!triggerListeners) historyListeners.pauseListeners(); history.go(delta); } const routerHistory = assign({ location: "", base, go, createHref: createHref.bind(null, base) }, historyNavigation, historyListeners); Object.defineProperty(routerHistory, "location", { enumerable: true, get: () => historyNavigation.location.value }); Object.defineProperty(routerHistory, "state", { enumerable: true, get: () => historyNavigation.state.value }); return routerHistory; } //#endregion //#region src/history/memory.ts /** * Creates an in-memory based history. The main purpose of this history is to handle SSR. It starts in a special location that is nowhere. * It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`. * * @param base - Base applied to all urls, defaults to '/' * @returns a history object that can be passed to the router constructor */ function createMemoryHistory(base = "") { let listeners = []; let queue = [[START, {}]]; let position = 0; base = normalizeBase(base); function setLocation(location, state = {}) { position++; if (position !== queue.length) queue.splice(position); queue.push([location, state]); } function triggerListeners(to, from, { direction, delta }) { const info = { direction, delta, type: NavigationType.pop }; for (const callback of listeners) callback(to, from, info); } const routerHistory = { location: START, state: {}, base, createHref: createHref.bind(null, base), replace(to, state) { queue.splice(position--, 1); setLocation(to, state); }, push(to, state) { setLocation(to, state); }, listen(callback) { listeners.push(callback); return () => { const index = listeners.indexOf(callback); if (index > -1) listeners.splice(index, 1); }; }, destroy() { listeners = []; queue = [[START, {}]]; position = 0; }, go(delta, shouldTrigger = true) { const from = this.location; const direction = delta < 0 ? NavigationDirection.back : NavigationDirection.forward; position = Math.max(0, Math.min(position + delta, queue.length - 1)); if (shouldTrigger) triggerListeners(this.location, from, { direction, delta }); } }; Object.defineProperty(routerHistory, "location", { enumerable: true, get: () => queue[position][0] }); Object.defineProperty(routerHistory, "state", { enumerable: true, get: () => queue[position][1] }); return routerHistory; } //#endregion //#region src/history/hash.ts /** * Creates a hash history. Useful for web applications with no host (e.g. `file://`) or when configuring a server to * handle any URL is not possible. * * @param base - optional base to provide. Defaults to `location.pathname + location.search` If there is a `` tag * in the `head`, its value will be ignored in favor of this parameter **but note it affects all the history.pushState() * calls**, meaning that if you use a `` tag, it's `href` value **has to match this parameter** (ignoring anything * after the `#`). * * @example * ```js * // at https://example.com/folder * createWebHashHistory() // gives a url of `https://example.com/folder#` * createWebHashHistory('/folder/') // gives a url of `https://example.com/folder/#` * // if the `#` is provided in the base, it won't be added by `createWebHashHistory` * createWebHashHistory('/folder/#/app/') // gives a url of `https://example.com/folder/#/app/` * // you should avoid doing this because it changes the original url and breaks copying urls * createWebHashHistory('/other-folder/') // gives a url of `https://example.com/other-folder/#` * * // at file:///usr/etc/folder/index.html * // for locations with no `host`, the base is ignored * createWebHashHistory('/iAmIgnored') // gives a url of `file:///usr/etc/folder/index.html#` * ``` */ function createWebHashHistory(base) { base = location.host ? base || location.pathname + location.search : ""; if (!base.includes("#")) base += "#"; if (!base.endsWith("#/") && !base.endsWith("#")) warn(`A hash base must end with a "#":\n"${base}" should be "${base.replace(/#.*$/, "#")}".`); return createWebHistory(base); } //#endregion //#region src/types/typeGuards.ts function isRouteLocation(route) { return typeof route === "string" || route && typeof route === "object"; } function isRouteName(name) { return typeof name === "string" || typeof name === "symbol"; } //#endregion //#region src/errors.ts /** * Flags so we can combine them when checking for multiple errors. This is the internal version of * {@link NavigationFailureType}. * * @internal */ let ErrorTypes = /* @__PURE__ */ function(ErrorTypes) { ErrorTypes[ErrorTypes["MATCHER_NOT_FOUND"] = 1] = "MATCHER_NOT_FOUND"; ErrorTypes[ErrorTypes["NAVIGATION_GUARD_REDIRECT"] = 2] = "NAVIGATION_GUARD_REDIRECT"; ErrorTypes[ErrorTypes["NAVIGATION_ABORTED"] = 4] = "NAVIGATION_ABORTED"; ErrorTypes[ErrorTypes["NAVIGATION_CANCELLED"] = 8] = "NAVIGATION_CANCELLED"; ErrorTypes[ErrorTypes["NAVIGATION_DUPLICATED"] = 16] = "NAVIGATION_DUPLICATED"; return ErrorTypes; }({}); const NavigationFailureSymbol = Symbol("navigation failure"); /** * Enumeration with all possible types for navigation failures. Can be passed to * {@link isNavigationFailure} to check for specific failures. */ let NavigationFailureType = /* @__PURE__ */ function(NavigationFailureType) { /** * An aborted navigation is a navigation that failed because a navigation * guard returned `false` or called `next(false)` */ NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted"; /** * A cancelled navigation is a navigation that failed because a more recent * navigation finished started (not necessarily finished). */ NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled"; /** * A duplicated navigation is a navigation that failed because it was * initiated while already being at the exact same location. */ NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated"; return NavigationFailureType; }({}); const ErrorTypeMessages = { [ErrorTypes.MATCHER_NOT_FOUND]({ location, currentLocation }) { return `No match for\n ${JSON.stringify(location)}${currentLocation ? "\nwhile being at\n" + JSON.stringify(currentLocation) : ""}`; }, [ErrorTypes.NAVIGATION_GUARD_REDIRECT]({ from, to }) { return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`; }, [ErrorTypes.NAVIGATION_ABORTED]({ from, to }) { return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`; }, [ErrorTypes.NAVIGATION_CANCELLED]({ from, to }) { return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`; }, [ErrorTypes.NAVIGATION_DUPLICATED]({ from, to }) { return `Avoided redundant navigation to current location: "${from.fullPath}".`; } }; /** * Creates a typed NavigationFailure object. * @internal * @param type - NavigationFailureType * @param params - { from, to } */ function createRouterError(type, params) { return assign(new Error(ErrorTypeMessages[type](params)), { type, [NavigationFailureSymbol]: true }, params); } function isNavigationFailure(error, type) { return error instanceof Error && NavigationFailureSymbol in error && (type == null || !!(error.type & type)); } const propertiesToLog = [ "params", "query", "hash" ]; function stringifyRoute(to) { if (typeof to === "string") return to; if (to.path != null) return to.path; const location = {}; for (const key of propertiesToLog) if (key in to) location[key] = to[key]; return JSON.stringify(location, null, 2); } //#endregion //#region src/matcher/pathTokenizer.ts let TokenType = /* @__PURE__ */ function(TokenType) { TokenType[TokenType["Static"] = 0] = "Static"; TokenType[TokenType["Param"] = 1] = "Param"; TokenType[TokenType["Group"] = 2] = "Group"; return TokenType; }({}); var TokenizerState = /* @__PURE__ */ function(TokenizerState) { TokenizerState[TokenizerState["Static"] = 0] = "Static"; TokenizerState[TokenizerState["Param"] = 1] = "Param"; TokenizerState[TokenizerState["ParamRegExp"] = 2] = "ParamRegExp"; TokenizerState[TokenizerState["ParamRegExpEnd"] = 3] = "ParamRegExpEnd"; TokenizerState[TokenizerState["EscapeNext"] = 4] = "EscapeNext"; return TokenizerState; }(TokenizerState || {}); const ROOT_TOKEN = { type: TokenType.Static, value: "" }; const VALID_PARAM_RE = /[a-zA-Z0-9_]/; function tokenizePath(path) { if (!path) return [[]]; if (path === "/") return [[ROOT_TOKEN]]; if (!path.startsWith("/")) throw new Error(`Route paths should start with a "/": "${path}" should be "/${path}".`); function crash(message) { throw new Error(`ERR (${state})/"${buffer}": ${message}`); } let state = TokenizerState.Static; let previousState = state; const tokens = []; let segment; function finalizeSegment() { if (segment) tokens.push(segment); segment = []; } let i = 0; let char; let buffer = ""; let customRe = ""; function consumeBuffer() { if (!buffer) return; if (state === TokenizerState.Static) segment.push({ type: TokenType.Static, value: buffer }); else if (state === TokenizerState.Param || state === TokenizerState.ParamRegExp || state === TokenizerState.ParamRegExpEnd) { if (segment.length > 1 && (char === "*" || char === "+")) crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`); segment.push({ type: TokenType.Param, value: buffer, regexp: customRe, repeatable: char === "*" || char === "+", optional: char === "*" || char === "?" }); } else crash("Invalid state to consume buffer"); buffer = ""; } function addCharToBuffer() { buffer += char; } while (i < path.length) { char = path[i++]; if (char === "\\" && state !== TokenizerState.ParamRegExp) { previousState = state; state = TokenizerState.EscapeNext; continue; } switch (state) { case TokenizerState.Static: if (char === "/") { if (buffer) consumeBuffer(); finalizeSegment(); } else if (char === ":") { consumeBuffer(); state = TokenizerState.Param; } else addCharToBuffer(); break; case TokenizerState.EscapeNext: addCharToBuffer(); state = previousState; break; case TokenizerState.Param: if (char === "(") state = TokenizerState.ParamRegExp; else if (VALID_PARAM_RE.test(char)) addCharToBuffer(); else { consumeBuffer(); state = TokenizerState.Static; if (char !== "*" && char !== "?" && char !== "+") i--; } break; case TokenizerState.ParamRegExp: if (char === ")") if (customRe[customRe.length - 1] == "\\") customRe = customRe.slice(0, -1) + char; else state = TokenizerState.ParamRegExpEnd; else customRe += char; break; case TokenizerState.ParamRegExpEnd: consumeBuffer(); state = TokenizerState.Static; if (char !== "*" && char !== "?" && char !== "+") i--; customRe = ""; break; default: crash("Unknown state"); break; } } if (state === TokenizerState.ParamRegExp) crash(`Unfinished custom RegExp for param "${buffer}"`); consumeBuffer(); finalizeSegment(); return tokens; } //#endregion //#region src/matcher/pathParserRanker.ts const BASE_PARAM_PATTERN = "[^/]+?"; const BASE_PATH_PARSER_OPTIONS = { sensitive: false, strict: false, start: true, end: true }; var PathScore = /* @__PURE__ */ function(PathScore) { PathScore[PathScore["_multiplier"] = 10] = "_multiplier"; PathScore[PathScore["Root"] = 90] = "Root"; PathScore[PathScore["Segment"] = 40] = "Segment"; PathScore[PathScore["SubSegment"] = 30] = "SubSegment"; PathScore[PathScore["Static"] = 40] = "Static"; PathScore[PathScore["Dynamic"] = 20] = "Dynamic"; PathScore[PathScore["BonusCustomRegExp"] = 10] = "BonusCustomRegExp"; PathScore[PathScore["BonusWildcard"] = -50] = "BonusWildcard"; PathScore[PathScore["BonusRepeatable"] = -20] = "BonusRepeatable"; PathScore[PathScore["BonusOptional"] = -8] = "BonusOptional"; PathScore[PathScore["BonusStrict"] = .7000000000000001] = "BonusStrict"; PathScore[PathScore["BonusCaseSensitive"] = .25] = "BonusCaseSensitive"; return PathScore; }(PathScore || {}); const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g; /** * Creates a path parser from an array of Segments (a segment is an array of Tokens) * * @param segments - array of segments returned by tokenizePath * @param extraOptions - optional options for the regexp * @returns a PathParser */ function tokensToParser(segments, extraOptions) { const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions); const score = []; let pattern = options.start ? "^" : ""; const keys = []; for (const segment of segments) { const segmentScores = segment.length ? [] : [PathScore.Root]; if (options.strict && !segment.length) pattern += "/"; for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) { const token = segment[tokenIndex]; let subSegmentScore = PathScore.Segment + (options.sensitive ? PathScore.BonusCaseSensitive : 0); if (token.type === TokenType.Static) { if (!tokenIndex) pattern += "/"; pattern += token.value.replace(REGEX_CHARS_RE, "\\$&"); subSegmentScore += PathScore.Static; } else if (token.type === TokenType.Param) { const { value, repeatable, optional, regexp } = token; keys.push({ name: value, repeatable, optional }); const re = regexp ? regexp : BASE_PARAM_PATTERN; if (re !== BASE_PARAM_PATTERN) { subSegmentScore += PathScore.BonusCustomRegExp; try { new RegExp(`(${re})`); } catch (err) { throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` + err.message); } } let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`; if (!tokenIndex) subPattern = optional && segment.length < 2 ? `(?:/${subPattern})` : "/" + subPattern; if (optional) subPattern += "?"; pattern += subPattern; subSegmentScore += PathScore.Dynamic; if (optional) subSegmentScore += PathScore.BonusOptional; if (repeatable) subSegmentScore += PathScore.BonusRepeatable; if (re === ".*") subSegmentScore += PathScore.BonusWildcard; } segmentScores.push(subSegmentScore); } score.push(segmentScores); } if (options.strict && options.end) { const i = score.length - 1; score[i][score[i].length - 1] += PathScore.BonusStrict; } if (!options.strict) pattern += "/?"; if (options.end) pattern += "$"; else if (options.strict && !pattern.endsWith("/")) pattern += "(?:/|$)"; const re = new RegExp(pattern, options.sensitive ? "" : "i"); function parse(path) { const match = path.match(re); const params = {}; if (!match) return null; for (let i = 1; i < match.length; i++) { const value = match[i] || ""; const key = keys[i - 1]; params[key.name] = value && key.repeatable ? value.split("/") : value; } return params; } function stringify(params) { let path = ""; let avoidDuplicatedSlash = false; for (const segment of segments) { if (!avoidDuplicatedSlash || !path.endsWith("/")) path += "/"; avoidDuplicatedSlash = false; for (const token of segment) if (token.type === TokenType.Static) path += token.value; else if (token.type === TokenType.Param) { const { value, repeatable, optional } = token; const param = value in params ? params[value] : ""; if (isArray(param) && !repeatable) throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`); const text = isArray(param) ? param.join("/") : param; if (!text) if (optional) { if (segment.length < 2) if (path.endsWith("/")) path = path.slice(0, -1); else avoidDuplicatedSlash = true; } else throw new Error(`Missing required param "${value}"`); path += text; } } return path || "/"; } return { re, score, keys, parse, stringify }; } /** * Compares an array of numbers as used in PathParser.score and returns a * number. This function can be used to `sort` an array * * @param a - first array of numbers * @param b - second array of numbers * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b * should be sorted first */ function compareScoreArray(a, b) { let i = 0; while (i < a.length && i < b.length) { const diff = b[i] - a[i]; if (diff) return diff; i++; } if (a.length < b.length) return a.length === 1 && a[0] === PathScore.Static + PathScore.Segment ? -1 : 1; else if (a.length > b.length) return b.length === 1 && b[0] === PathScore.Static + PathScore.Segment ? 1 : -1; return 0; } /** * Compare function that can be used with `sort` to sort an array of PathParser * * @param a - first PathParser * @param b - second PathParser * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b */ function comparePathParserScore(a, b) { let i = 0; const aScore = a.score; const bScore = b.score; while (i < aScore.length && i < bScore.length) { const comp = compareScoreArray(aScore[i], bScore[i]); if (comp) return comp; i++; } if (Math.abs(bScore.length - aScore.length) === 1) { if (isLastScoreNegative(aScore)) return 1; if (isLastScoreNegative(bScore)) return -1; } return bScore.length - aScore.length; } /** * This allows detecting splats at the end of a path: /home/:id(.*)* * * @param score - score to check * @returns true if the last entry is negative */ function isLastScoreNegative(score) { const last = score[score.length - 1]; return score.length > 0 && last[last.length - 1] < 0; } const PATH_PARSER_OPTIONS_DEFAULTS = { strict: false, end: true, sensitive: false }; //#endregion //#region src/matcher/pathMatcher.ts function createRouteRecordMatcher(record, parent, options) { const parser = tokensToParser(tokenizePath(record.path), options); { const existingKeys = /* @__PURE__ */ new Set(); for (const key of parser.keys) { if (existingKeys.has(key.name)) warn(`Found duplicated params with name "${key.name}" for path "${record.path}". Only the last one will be available on "$route.params".`); existingKeys.add(key.name); } } const matcher = assign(parser, { record, parent, children: [], alias: [] }); if (parent) { if (!matcher.record.aliasOf === !parent.record.aliasOf) parent.children.push(matcher); } return matcher; } //#endregion //#region src/matcher/index.ts /** * Creates a Router Matcher. * * @internal * @param routes - array of initial routes * @param globalOptions - global route options */ function createRouterMatcher(routes, globalOptions) { const matchers = []; const matcherMap = /* @__PURE__ */ new Map(); globalOptions = mergeOptions(PATH_PARSER_OPTIONS_DEFAULTS, globalOptions); function getRecordMatcher(name) { return matcherMap.get(name); } function addRoute(record, parent, originalRecord) { const isRootAdd = !originalRecord; const mainNormalizedRecord = normalizeRouteRecord(record); checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent); mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record; const options = mergeOptions(globalOptions, record); const normalizedRecords = [mainNormalizedRecord]; if ("alias" in record) { const aliases = typeof record.alias === "string" ? [record.alias] : record.alias; for (const alias of aliases) normalizedRecords.push(normalizeRouteRecord(assign({}, mainNormalizedRecord, { components: originalRecord ? originalRecord.record.components : mainNormalizedRecord.components, path: alias, aliasOf: originalRecord ? originalRecord.record : mainNormalizedRecord }))); } let matcher; let originalMatcher; for (const normalizedRecord of normalizedRecords) { const { path } = normalizedRecord; if (parent && path[0] !== "/") { const parentPath = parent.record.path; const connectingSlash = parentPath[parentPath.length - 1] === "/" ? "" : "/"; normalizedRecord.path = parent.record.path + (path && connectingSlash + path); } if (normalizedRecord.path === "*") throw new Error("Catch all routes (\"*\") must now be defined using a param with a custom regexp.\nSee more at https://router.vuejs.org/guide/migration/#Removed-star-or-catch-all-routes."); matcher = createRouteRecordMatcher(normalizedRecord, parent, options); if (parent && path[0] === "/") checkMissingParamsInAbsolutePath(matcher, parent); if (originalRecord) { originalRecord.alias.push(matcher); checkSameParams(originalRecord, matcher); } else { originalMatcher = originalMatcher || matcher; if (originalMatcher !== matcher) originalMatcher.alias.push(matcher); if (isRootAdd && record.name && !isAliasRecord(matcher)) { checkSameNameAsAncestor(record, parent); removeRoute(record.name); } } if (isMatchable(matcher)) insertMatcher(matcher); if (mainNormalizedRecord.children) { const children = mainNormalizedRecord.children; for (let i = 0; i < children.length; i++) addRoute(children[i], matcher, originalRecord && originalRecord.children[i]); } originalRecord = originalRecord || matcher; } return originalMatcher ? () => { removeRoute(originalMatcher); } : noop; } function removeRoute(matcherRef) { if (isRouteName(matcherRef)) { const matcher = matcherMap.get(matcherRef); if (matcher) { matcherMap.delete(matcherRef); matchers.splice(matchers.indexOf(matcher), 1); matcher.children.forEach(removeRoute); matcher.alias.forEach(removeRoute); } } else { const index = matchers.indexOf(matcherRef); if (index > -1) { matchers.splice(index, 1); if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name); matcherRef.children.forEach(removeRoute); matcherRef.alias.forEach(removeRoute); } } } function getRoutes() { return matchers; } function insertMatcher(matcher) { const index = findInsertionIndex(matcher, matchers); matchers.splice(index, 0, matcher); if (matcher.record.name && !isAliasRecord(matcher)) matcherMap.set(matcher.record.name, matcher); } function resolve(location, currentLocation) { let matcher; let params = {}; let path; let name; if ("name" in location && location.name) { matcher = matcherMap.get(location.name); if (!matcher) throw createRouterError(ErrorTypes.MATCHER_NOT_FOUND, { location }); { const invalidParams = Object.keys(location.params || {}).filter((paramName) => !matcher.keys.find((k) => k.name === paramName)); if (invalidParams.length) warn(`Discarded invalid param(s) "${invalidParams.join("\", \"")}" when navigating. See https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#414-2022-08-22 for more details.`); } name = matcher.record.name; params = assign(pickParams(currentLocation.params, matcher.keys.filter((k) => !k.optional).concat(matcher.parent ? matcher.parent.keys.filter((k) => k.optional) : []).map((k) => k.name)), location.params && pickParams(location.params, matcher.keys.map((k) => k.name))); path = matcher.stringify(params); } else if (location.path != null) { path = location.path; if (!path.startsWith("/")) warn(`The Matcher cannot resolve relative paths but received "${path}". Unless you directly called \`matcher.resolve("${path}")\`, this is probably a bug in vue-router. Please open an issue at https://github.com/vuejs/router/issues/new/choose.`); matcher = matchers.find((m) => m.re.test(path)); if (matcher) { params = matcher.parse(path); name = matcher.record.name; } } else { matcher = currentLocation.name ? matcherMap.get(currentLocation.name) : matchers.find((m) => m.re.test(currentLocation.path)); if (!matcher) throw createRouterError(ErrorTypes.MATCHER_NOT_FOUND, { location, currentLocation }); name = matcher.record.name; params = assign({}, currentLocation.params, location.params); path = matcher.stringify(params); } const matched = []; let parentMatcher = matcher; while (parentMatcher) { matched.unshift(parentMatcher.record); parentMatcher = parentMatcher.parent; } return { name, path, params, matched, meta: mergeMetaFields(matched) }; } routes.forEach((route) => addRoute(route)); function clearRoutes() { matchers.length = 0; matcherMap.clear(); } return { addRoute, resolve, removeRoute, clearRoutes, getRoutes, getRecordMatcher }; } /** * Picks an object param to contain only specified keys. * * @param params - params object to pick from * @param keys - keys to pick */ function pickParams(params, keys) { const newParams = {}; for (const key of keys) if (key in params) newParams[key] = params[key]; return newParams; } /** * Normalizes a RouteRecordRaw. Creates a copy * * @param record * @returns the normalized version */ function normalizeRouteRecord(record) { const normalized = { path: record.path, redirect: record.redirect, name: record.name, meta: record.meta || {}, aliasOf: record.aliasOf, beforeEnter: record.beforeEnter, props: normalizeRecordProps(record), children: record.children || [], instances: {}, leaveGuards: /* @__PURE__ */ new Set(), updateGuards: /* @__PURE__ */ new Set(), enterCallbacks: {}, components: "components" in record ? record.components || null : record.component && { default: record.component } }; Object.defineProperty(normalized, "mods", { value: {} }); return normalized; } /** * Normalize the optional `props` in a record to always be an object similar to * components. Also accept a boolean for components. * @param record */ function normalizeRecordProps(record) { const propsObject = {}; const props = record.props || false; if ("component" in record) propsObject.default = props; else for (const name in record.components) propsObject[name] = typeof props === "object" ? props[name] : props; return propsObject; } /** * Checks if a record or any of its parent is an alias * @param record */ function isAliasRecord(record) { while (record) { if (record.record.aliasOf) return true; record = record.parent; } return false; } /** * Merge meta fields of an array of records * * @param matched - array of matched records */ function mergeMetaFields(matched) { return matched.reduce((meta, record) => assign(meta, record.meta), {}); } function isSameParam(a, b) { return a.name === b.name && a.optional === b.optional && a.repeatable === b.repeatable; } /** * Check if a path and its alias have the same required params * * @param a - original record * @param b - alias record */ function checkSameParams(a, b) { for (const key of a.keys) if (!key.optional && !b.keys.find(isSameParam.bind(null, key))) return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`); for (const key of b.keys) if (!key.optional && !a.keys.find(isSameParam.bind(null, key))) return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`); } /** * A route with a name and a child with an empty path without a name should warn when adding the route * * @param mainNormalizedRecord - RouteRecordNormalized * @param parent - RouteRecordMatcher */ function checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent) { if (parent && parent.record.name && !mainNormalizedRecord.name && !mainNormalizedRecord.path && mainNormalizedRecord.children.length === 0) warn(`The route named "${String(parent.record.name)}" has a child without a name, an empty path, and no children. This is probably a mistake: using that name won't render the empty path child so you probably want to move the name to the child instead. If this is intentional, add a name to the child route to silence the warning.`); } function checkSameNameAsAncestor(record, parent) { for (let ancestor = parent; ancestor; ancestor = ancestor.parent) if (ancestor.record.name === record.name) throw new Error(`A route named "${String(record.name)}" has been added as a ${parent === ancestor ? "child" : "descendant"} of a route with the same name. Route names must be unique and a nested route cannot use the same name as an ancestor.`); } function checkMissingParamsInAbsolutePath(record, parent) { for (const key of parent.keys) if (!record.keys.find(isSameParam.bind(null, key))) return warn(`Absolute path "${record.record.path}" must have the exact same param named "${key.name}" as its parent "${parent.record.path}".`); } /** * Performs a binary search to find the correct insertion index for a new matcher. * * Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships, * with descendants coming before ancestors. If there's still a tie, new routes are inserted after existing routes. * * @param matcher - new matcher to be inserted * @param matchers - existing matchers */ function findInsertionIndex(matcher, matchers) { let lower = 0; let upper = matchers.length; while (lower !== upper) { const mid = lower + upper >> 1; if (comparePathParserScore(matcher, matchers[mid]) < 0) upper = mid; else lower = mid + 1; } const insertionAncestor = getInsertionAncestor(matcher); if (insertionAncestor) { upper = matchers.lastIndexOf(insertionAncestor, upper - 1); if (upper < 0) warn(`Finding ancestor route "${insertionAncestor.record.path}" failed for "${matcher.record.path}"`); } return upper; } function getInsertionAncestor(matcher) { let ancestor = matcher; while (ancestor = ancestor.parent) if (isMatchable(ancestor) && comparePathParserScore(matcher, ancestor) === 0) return ancestor; } /** * Checks if a matcher can be reachable. This means if it's possible to reach it as a route. For example, routes without * a component, or name, or redirect, are just used to group other routes. * @param matcher * @param matcher.record record of the matcher * @returns */ function isMatchable({ record }) { return !!(record.name || record.components && Object.keys(record.components).length || record.redirect); } //#endregion //#region src/query.ts /** * Transforms a queryString into a {@link LocationQuery} object. Accept both, a * version with the leading `?` and without Should work as URLSearchParams * @internal * * @param search - search string to parse * @returns a query object */ function parseQuery(search) { const query = {}; if (search === "" || search === "?") return query; const searchParams = (search[0] === "?" ? search.slice(1) : search).split("&"); for (let i = 0; i < searchParams.length; ++i) { const searchParam = searchParams[i].replace(PLUS_RE, " "); const eqPos = searchParam.indexOf("="); const key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos)); const value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1)); if (key in query) { let currentValue = query[key]; if (!isArray(currentValue)) currentValue = query[key] = [currentValue]; currentValue.push(value); } else query[key] = value; } return query; } /** * Stringifies a {@link LocationQueryRaw} object. Like `URLSearchParams`, it * doesn't prepend a `?` * * @internal * * @param query - query object to stringify * @returns string version of the query without the leading `?` */ function stringifyQuery(query) { let search = ""; for (let key in query) { const value = query[key]; key = encodeQueryKey(key); if (value == null) { if (value !== void 0) search += (search.length ? "&" : "") + key; continue; } (isArray(value) ? value.map((v) => v && encodeQueryValue(v)) : [value && encodeQueryValue(value)]).forEach((value) => { if (value !== void 0) { search += (search.length ? "&" : "") + key; if (value != null) search += "=" + value; } }); } return search; } /** * Transforms a {@link LocationQueryRaw} into a {@link LocationQuery} by casting * numbers into strings, removing keys with an undefined value and replacing * undefined with null in arrays * * @param query - query object to normalize * @returns a normalized query object */ function normalizeQuery(query) { const normalizedQuery = {}; for (const key in query) { const value = query[key]; if (value !== void 0) normalizedQuery[key] = isArray(value) ? value.map((v) => v == null ? null : "" + v) : value == null ? value : "" + value; } return normalizedQuery; } //#endregion //#region src/injectionSymbols.ts /** * RouteRecord being rendered by the closest ancestor Router View. Used for * `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View * Location Matched * * @internal */ const matchedRouteKey = Symbol("router view location matched"); /** * Allows overriding the router view depth to control which component in * `matched` is rendered. rvd stands for Router View Depth * * @internal */ const viewDepthKey = Symbol("router view depth"); /** * Allows overriding the router instance returned by `useRouter` in tests. r * stands for router * * @internal */ const routerKey = Symbol("router"); /** * Allows overriding the current route returned by `useRoute` in tests. rl * stands for route location * * @internal */ const routeLocationKey = Symbol("route location"); /** * Allows overriding the current route used by router-view. Internally this is * used when the `route` prop is passed. * * @internal */ const routerViewLocationKey = Symbol("router view location"); //#endregion //#region src/utils/callbacks.ts /** * Create a list of callbacks that can be reset. Used to create before and after navigation guards list */ function useCallbacks() { let handlers = []; function add(handler) { handlers.push(handler); return () => { const i = handlers.indexOf(handler); if (i > -1) handlers.splice(i, 1); }; } function reset() { handlers = []; } return { add, list: () => handlers.slice(), reset }; } //#endregion //#region src/navigationGuards.ts function registerGuard(activeRecordRef, name, guard) { const record = activeRecordRef.value; if (!record) { warn(`No active route record was found when calling \`${name === "updateGuards" ? "onBeforeRouteUpdate" : "onBeforeRouteLeave"}()\`. Make sure you call this function inside a component child of . Maybe you called it inside of App.vue?`); return; } let currentRecord = record; const removeFromList = () => { currentRecord[name].delete(guard); }; onUnmounted(removeFromList); onDeactivated(removeFromList); onActivated(() => { const newRecord = activeRecordRef.value; if (!newRecord) warn("No active route record was found when reactivating component with navigation guard. This is likely a bug in vue-router. Please report it."); if (newRecord) currentRecord = newRecord; currentRecord[name].add(guard); }); currentRecord[name].add(guard); } /** * Add a navigation guard that triggers whenever the component for the current * location is about to be left. Similar to {@link beforeRouteLeave} but can be * used in any component. The guard is removed when the component is unmounted. * * @param leaveGuard - {@link NavigationGuard} */ function onBeforeRouteLeave(leaveGuard) { if (!getCurrentInstance()) { warn("getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function"); return; } registerGuard(inject(matchedRouteKey, {}), "leaveGuards", leaveGuard); } /** * Add a navigation guard that triggers whenever the current location is about * to be updated. Similar to {@link beforeRouteUpdate} but can be used in any * component. The guard is removed when the component is unmounted. * * @param updateGuard - {@link NavigationGuard} */ function onBeforeRouteUpdate(updateGuard) { if (!getCurrentInstance()) { warn("getCurrentInstance() returned null. onBeforeRouteUpdate() must be called at the top of a setup function"); return; } registerGuard(inject(matchedRouteKey, {}), "updateGuards", updateGuard); } function guardToPromiseFn(guard, to, from, record, name, runWithContext = (fn) => fn()) { const enterCallbackArray = record && (record.enterCallbacks[name] = record.enterCallbacks[name] || []); return () => new Promise((resolve, reject) => { const next = (valid) => { if (valid === false) reject(createRouterError(ErrorTypes.NAVIGATION_ABORTED, { from, to })); else if (valid instanceof Error) reject(valid); else if (isRouteLocation(valid)) reject(createRouterError(ErrorTypes.NAVIGATION_GUARD_REDIRECT, { from: to, to: valid })); else { if (enterCallbackArray && record.enterCallbacks[name] === enterCallbackArray && typeof valid === "function") enterCallbackArray.push(valid); resolve(); } }; const guardReturn = runWithContext(() => guard.call(record && record.instances[name], to, from, withDeprecationWarning(canOnlyBeCalledOnce(next, to, from)))); let guardCall = Promise.resolve(guardReturn); if (guard.length < 3) guardCall = guardCall.then(next); if (guard.length > 2) { const message = `The "next" callback was never called inside of ${guard.name ? "\"" + guard.name + "\"" : ""}:\n${guard.toString()}\n. If you are returning a value instead of calling "next", make sure to remove the "next" parameter from your function.`; if (typeof guardReturn === "object" && "then" in guardReturn) guardCall = guardCall.then((resolvedValue) => { if (!next._called) { warn(message); return Promise.reject(/* @__PURE__ */ new Error("Invalid navigation guard")); } return resolvedValue; }); else if (guardReturn !== void 0) { if (!next._called) { warn(message); reject(/* @__PURE__ */ new Error("Invalid navigation guard")); return; } } } guardCall.catch((err) => reject(err)); }); } /** * Wraps the next callback to warn when it is used. Dev-only: when __DEV__ is * false (production builds), this branch is dead code and is stripped from the * bundle. * * @internal */ function withDeprecationWarning(next) { let warned = false; return function() { if (!warned) { warned = true; warn("The `next()` callback in navigation guards is deprecated. Return the value instead of calling `next(value)`."); } return next.apply(this, arguments); }; } function canOnlyBeCalledOnce(next, to, from) { let called = 0; return function() { if (called++ === 1) warn(`The "next" callback was called more than once in one navigation guard when going from "${from.fullPath}" to "${to.fullPath}". It should be called exactly one time in each navigation guard. This will fail in production.`); next._called = true; if (called === 1) next.apply(null, arguments); }; } function extractComponentsGuards(matched, guardType, to, from, runWithContext = (fn) => fn()) { const guards = []; for (const record of matched) { if (!record.components && record.children && !record.children.length) warn(`Record with path "${record.path}" is either missing a "component(s)" or "children" property.`); for (const name in record.components) { let rawComponent = record.components[name]; if (!rawComponent || typeof rawComponent !== "object" && typeof rawComponent !== "function") { warn(`Component "${name}" in record with path "${record.path}" is not a valid component. Received "${String(rawComponent)}".`); throw new Error("Invalid route component"); } else if ("then" in rawComponent) { warn(`Component "${name}" in record with path "${record.path}" is a Promise instead of a function that returns a Promise. Did you write "import('./MyPage.vue')" instead of "() => import('./MyPage.vue')" ? This will break in production if not fixed.`); const promise = rawComponent; rawComponent = () => promise; } else if (rawComponent.__asyncLoader && !rawComponent.__warnedDefineAsync) { rawComponent.__warnedDefineAsync = true; warn(`Component "${name}" in record with path "${record.path}" is defined using "defineAsyncComponent()". Write "() => import('./MyPage.vue')" instead of "defineAsyncComponent(() => import('./MyPage.vue'))".`); } if (guardType !== "beforeRouteEnter" && !record.instances[name]) continue; if (isRouteComponent(rawComponent)) { const guard = (rawComponent.__vccOpts || rawComponent)[guardType]; guard && guards.push(guardToPromiseFn(guard, to, from, record, name, runWithContext)); } else { let componentPromise = rawComponent(); if (!("catch" in componentPromise)) { warn(`Component "${name}" in record with path "${record.path}" is a function that does not return a Promise. If you were passing a functional component, make sure to add a "displayName" to the component. This will break in production if not fixed.`); componentPromise = Promise.resolve(componentPromise); } guards.push(() => componentPromise.then((resolved) => { if (!resolved) throw new Error(`Couldn't resolve component "${name}" at "${record.path}"`); const resolvedComponent = isESModule(resolved) ? resolved.default : resolved; record.mods[name] = resolved; record.components[name] = resolvedComponent; const guard = (resolvedComponent.__vccOpts || resolvedComponent)[guardType]; return guard && guardToPromiseFn(guard, to, from, record, name, runWithContext)(); })); } } } return guards; } /** * Ensures a route is loaded, so it can be passed as o prop to ``. * * @param route - resolved route to load */ function loadRouteLocation(route) { return route.matched.every((record) => record.redirect) ? Promise.reject(/* @__PURE__ */ new Error("Cannot load a route that redirects.")) : Promise.all(route.matched.map((record) => record.components && Promise.all(Object.keys(record.components).reduce((promises, name) => { const rawComponent = record.components[name]; if (typeof rawComponent === "function" && !("displayName" in rawComponent)) promises.push(rawComponent().then((resolved) => { if (!resolved) return Promise.reject(/* @__PURE__ */ new Error(`Couldn't resolve component "${name}" at "${record.path}". Ensure you passed a function that returns a promise.`)); const resolvedComponent = isESModule(resolved) ? resolved.default : resolved; record.mods[name] = resolved; record.components[name] = resolvedComponent; })); return promises; }, [])))).then(() => route); } /** * Split the leaving, updating, and entering records. * @internal * * @param to - Location we are navigating to * @param from - Location we are navigating from */ function extractChangingRecords(to, from) { const leavingRecords = []; const updatingRecords = []; const enteringRecords = []; const len = Math.max(from.matched.length, to.matched.length); for (let i = 0; i < len; i++) { const recordFrom = from.matched[i]; if (recordFrom) if (to.matched.find((record) => isSameRouteRecord(record, recordFrom))) updatingRecords.push(recordFrom); else leavingRecords.push(recordFrom); const recordTo = to.matched[i]; if (recordTo) { if (!from.matched.find((record) => isSameRouteRecord(record, recordTo))) enteringRecords.push(recordTo); } } return [ leavingRecords, updatingRecords, enteringRecords ]; } //#endregion //#region src/RouterLink.ts /** * Returns the internal behavior of a {@link RouterLink} without the rendering part. * * @param props - a `to` location and an optional `replace` flag */ function useLink(props) { const router = inject(routerKey); const currentRoute = inject(routeLocationKey); let hasPrevious = false; let previousTo = null; const route = computed(() => { const to = unref(props.to); if (!hasPrevious || to !== previousTo) { if (!isRouteLocation(to)) if (hasPrevious) warn(`Invalid value for prop "to" in useLink()\n- to:`, to, `\n- previous to:`, previousTo, `\n- props:`, props); else warn(`Invalid value for prop "to" in useLink()\n- to:`, to, `\n- props:`, props); previousTo = to; hasPrevious = true; } return router.resolve(to); }); const activeRecordIndex = computed(() => { const { matched } = route.value; const { length } = matched; const routeMatched = matched[length - 1]; const currentMatched = currentRoute.matched; if (!routeMatched || !currentMatched.length) return -1; const index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched)); if (index > -1) return index; const parentRecordPath = getOriginalPath(matched[length - 2]); return length > 1 && getOriginalPath(routeMatched) === parentRecordPath && currentMatched[currentMatched.length - 1].path !== parentRecordPath ? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2])) : index; }); const isActive = computed(() => activeRecordIndex.value > -1 && includesParams(currentRoute.params, route.value.params)); const isExactActive = computed(() => activeRecordIndex.value > -1 && activeRecordIndex.value === currentRoute.matched.length - 1 && isSameRouteLocationParams(currentRoute.params, route.value.params)); function navigate(e = {}) { if (guardEvent(e)) { const p = router[unref(props.replace) ? "replace" : "push"](unref(props.to)).catch(noop); if (props.viewTransition && typeof document !== "undefined" && "startViewTransition" in document) document.startViewTransition(() => p); return p; } return Promise.resolve(); } if (isBrowser) { const instance = getCurrentInstance(); if (instance) { const linkContextDevtools = { route: route.value, isActive: isActive.value, isExactActive: isExactActive.value, error: null }; instance.__vrl_devtools = instance.__vrl_devtools || []; instance.__vrl_devtools.push(linkContextDevtools); watchEffect(() => { linkContextDevtools.route = route.value; linkContextDevtools.isActive = isActive.value; linkContextDevtools.isExactActive = isExactActive.value; linkContextDevtools.error = isRouteLocation(unref(props.to)) ? null : "Invalid \"to\" value"; }, { flush: "post" }); } } /** * NOTE: update {@link _RouterLinkI}'s `$slots` type when updating this */ return { route, href: computed(() => route.value.href), isActive, isExactActive, navigate }; } function preferSingleVNode(vnodes) { return vnodes.length === 1 ? vnodes[0] : vnodes; } const RouterLinkImpl = /* @__PURE__ */ defineComponent({ name: "RouterLink", compatConfig: { MODE: 3 }, props: { to: { type: [String, Object], required: true }, replace: Boolean, activeClass: String, exactActiveClass: String, custom: Boolean, ariaCurrentValue: { type: String, default: "page" }, viewTransition: Boolean }, useLink, setup(props, { slots }) { const link = reactive(useLink(props)); const { options } = inject(routerKey); const elClass = computed(() => ({ [getLinkClass(props.activeClass, options.linkActiveClass, "router-link-active")]: link.isActive, [getLinkClass(props.exactActiveClass, options.linkExactActiveClass, "router-link-exact-active")]: link.isExactActive })); return () => { const children = slots.default && preferSingleVNode(slots.default(link)); return props.custom ? children : h("a", { "aria-current": link.isExactActive ? props.ariaCurrentValue : null, href: link.href, onClick: link.navigate, class: elClass.value }, children); }; } }); /** * Component to render a link that triggers a navigation on click. */ const RouterLink = RouterLinkImpl; function guardEvent(e) { if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return; if (e.defaultPrevented) return; if (e.button !== void 0 && e.button !== 0) return; if (e.currentTarget && e.currentTarget.getAttribute) { const target = e.currentTarget.getAttribute("target"); if (/\b_blank\b/i.test(target)) return; } if (e.preventDefault) e.preventDefault(); return true; } function includesParams(outer, inner) { for (const key in inner) { const innerValue = inner[key]; const outerValue = outer[key]; if (typeof innerValue === "string") { if (innerValue !== outerValue) return false; } else if (!isArray(outerValue) || outerValue.length !== innerValue.length || innerValue.some((value, i) => value.valueOf() !== outerValue[i].valueOf())) return false; } return true; } /** * Get the original path value of a record by following its aliasOf * @param record */ function getOriginalPath(record) { return record ? record.aliasOf ? record.aliasOf.path : record.path : ""; } /** * Utility class to get the active class based on defaults. * @param propClass * @param globalClass * @param defaultClass */ const getLinkClass = (propClass, globalClass, defaultClass) => propClass != null ? propClass : globalClass != null ? globalClass : defaultClass; //#endregion //#region src/RouterView.ts const RouterViewImpl = /* @__PURE__ */ defineComponent({ name: "RouterView", inheritAttrs: false, props: { name: { type: String, default: "default" }, route: Object }, compatConfig: { MODE: 3 }, setup(props, { attrs, slots }) { warnDeprecatedUsage(); const injectedRoute = inject(routerViewLocationKey); const routeToDisplay = computed(() => props.route || injectedRoute.value); const injectedDepth = inject(viewDepthKey, 0); const depth = computed(() => { let initialDepth = unref(injectedDepth); const { matched } = routeToDisplay.value; let matchedRoute; while ((matchedRoute = matched[initialDepth]) && !matchedRoute.components) initialDepth++; return initialDepth; }); const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth.value]); provide(viewDepthKey, computed(() => depth.value + 1)); provide(matchedRouteKey, matchedRouteRef); provide(routerViewLocationKey, routeToDisplay); const viewRef = ref(); watch(() => [ viewRef.value, matchedRouteRef.value, props.name ], ([instance, to, name], [oldInstance, from, oldName]) => { if (to) { to.instances[name] = instance; if (from && from !== to && instance && instance === oldInstance) { if (!to.leaveGuards.size) to.leaveGuards = from.leaveGuards; if (!to.updateGuards.size) to.updateGuards = from.updateGuards; } } if (instance && to && (!from || !isSameRouteRecord(to, from) || !oldInstance)) (to.enterCallbacks[name] || []).forEach((callback) => callback(instance)); }, { flush: "post" }); return () => { const route = routeToDisplay.value; const currentName = props.name; const matchedRoute = matchedRouteRef.value; const ViewComponent = matchedRoute && matchedRoute.components[currentName]; if (!ViewComponent) return normalizeSlot(slots.default, { Component: ViewComponent, route }); const routePropsOption = matchedRoute.props[currentName]; const routeProps = routePropsOption ? routePropsOption === true ? route.params : typeof routePropsOption === "function" ? routePropsOption(route) : routePropsOption : null; const onVnodeUnmounted = (vnode) => { if (vnode.component.isUnmounted) matchedRoute.instances[currentName] = null; }; const component = h(ViewComponent, assign({}, routeProps, attrs, { onVnodeUnmounted, ref: viewRef })); if (isBrowser && component.ref) { const info = { depth: depth.value, name: matchedRoute.name, path: matchedRoute.path, meta: matchedRoute.meta }; (isArray(component.ref) ? component.ref.map((r) => r.i) : [component.ref.i]).forEach((instance) => { instance.__vrv_devtools = info; }); } return normalizeSlot(slots.default, { Component: component, route }) || component; }; } }); function normalizeSlot(slot, data) { if (!slot) return null; const slotContent = slot(data); return slotContent.length === 1 ? slotContent[0] : slotContent; } /** * Component to display the current route the user is at. */ const RouterView = RouterViewImpl; function warnDeprecatedUsage() { const instance = getCurrentInstance(); const parentName = instance.parent && instance.parent.type.name; const parentSubTreeType = instance.parent && instance.parent.subTree && instance.parent.subTree.type; if (parentName && (parentName === "KeepAlive" || parentName.includes("Transition")) && typeof parentSubTreeType === "object" && parentSubTreeType.name === "RouterView") { const comp = parentName === "KeepAlive" ? "keep-alive" : "transition"; warn(` can no longer be used directly inside or . Use slot props instead: <${comp}>\n \n \n`); } } //#endregion //#region src/devtools.ts /** * Copies a route location and removes any problematic properties that cannot be shown in devtools (e.g. Vue instances). * * @param routeLocation - routeLocation to format * @param tooltip - optional tooltip * @returns a copy of the routeLocation */ function formatRouteLocation(routeLocation, tooltip) { const copy = assign({}, routeLocation, { matched: routeLocation.matched.map((matched) => omit(matched, [ "instances", "children", "aliasOf" ])) }); return { _custom: { type: null, readOnly: true, display: routeLocation.fullPath, tooltip, value: copy } }; } function formatDisplay(display) { return { _custom: { display } }; } let routerId = 0; function addDevtools(app, router, matcher) { if (router.__hasDevtools) return; router.__hasDevtools = true; const id = routerId++; setupDevtoolsPlugin({ id: "org.vuejs.router" + (id ? "." + id : ""), label: "Vue Router", packageName: "vue-router", homepage: "https://router.vuejs.org", logo: "https://router.vuejs.org/logo.png", componentStateTypes: ["Routing"], app }, (api) => { api.on.inspectComponent((payload) => { if (payload.instanceData) payload.instanceData.state.push({ type: "Routing", key: "$route", editable: false, value: formatRouteLocation(router.currentRoute.value, "Current Route") }); }); api.on.visitComponentTree(({ treeNode: node, componentInstance }) => { if (componentInstance.__vrv_devtools) { const info = componentInstance.__vrv_devtools; node.tags.push({ label: (info.name ? `${info.name.toString()}: ` : "") + info.path, textColor: 0, tooltip: "This component is rendered by <router-view>", backgroundColor: PINK_500 }); } if (isArray(componentInstance.__vrl_devtools)) { componentInstance.__devtoolsApi = api; componentInstance.__vrl_devtools.forEach((devtoolsData) => { let label = devtoolsData.route.path; let backgroundColor = ORANGE_400; let tooltip = ""; let textColor = 0; if (devtoolsData.error) { label = devtoolsData.error; backgroundColor = RED_100; textColor = RED_700; } else if (devtoolsData.isExactActive) { backgroundColor = LIME_500; tooltip = "This is exactly active"; } else if (devtoolsData.isActive) { backgroundColor = BLUE_600; tooltip = "This link is active"; } node.tags.push({ label, textColor, tooltip, backgroundColor }); }); } }); watch(router.currentRoute, () => { refreshRoutesView(); api.notifyComponentUpdate(); api.sendInspectorTree(routerInspectorId); api.sendInspectorState(routerInspectorId); }); const navigationsLayerId = "router:navigations:" + id; api.addTimelineLayer({ id: navigationsLayerId, label: `Router${id ? " " + id : ""} Navigations`, color: 4237508 }); router.onError((error, to) => { api.addTimelineEvent({ layerId: navigationsLayerId, event: { title: "Error during Navigation", subtitle: to.fullPath, logType: "error", time: api.now(), data: { error }, groupId: to.meta.__navigationId } }); }); let navigationId = 0; router.beforeEach((to, from) => { const data = { guard: formatDisplay("beforeEach"), from: formatRouteLocation(from, "Current Location during this navigation"), to: formatRouteLocation(to, "Target location") }; Object.defineProperty(to.meta, "__navigationId", { value: navigationId++ }); api.addTimelineEvent({ layerId: navigationsLayerId, event: { time: api.now(), title: "Start of navigation", subtitle: to.fullPath, data, groupId: to.meta.__navigationId } }); }); router.afterEach((to, from, failure) => { const data = { guard: formatDisplay("afterEach") }; if (failure) { data.failure = { _custom: { type: Error, readOnly: true, display: failure ? failure.message : "", tooltip: "Navigation Failure", value: failure } }; data.status = formatDisplay("❌"); } else data.status = formatDisplay("✅"); data.from = formatRouteLocation(from, "Current Location during this navigation"); data.to = formatRouteLocation(to, "Target location"); api.addTimelineEvent({ layerId: navigationsLayerId, event: { title: "End of navigation", subtitle: to.fullPath, time: api.now(), data, logType: failure ? "warning" : "default", groupId: to.meta.__navigationId } }); }); /** * Inspector of Existing routes */ const routerInspectorId = "router-inspector:" + id; api.addInspector({ id: routerInspectorId, label: "Routes" + (id ? " " + id : ""), icon: "book", treeFilterPlaceholder: "Search routes" }); function refreshRoutesView() { if (!activeRoutesPayload) return; const payload = activeRoutesPayload; let routes = matcher.getRoutes().filter((route) => !route.parent || !route.parent.record.components); routes.forEach(resetMatchStateOnRouteRecord); if (payload.filter) routes = routes.filter((route) => isRouteMatching(route, payload.filter.toLowerCase())); routes.forEach((route) => markRouteRecordActive(route, router.currentRoute.value)); payload.rootNodes = routes.map(formatRouteRecordForInspector); } let activeRoutesPayload; api.on.getInspectorTree((payload) => { activeRoutesPayload = payload; if (payload.app === app && payload.inspectorId === routerInspectorId) refreshRoutesView(); }); /** * Display information about the currently selected route record */ api.on.getInspectorState((payload) => { if (payload.app === app && payload.inspectorId === routerInspectorId) { const route = matcher.getRoutes().find((route) => route.record.__vd_id === payload.nodeId); if (route) payload.state = { options: formatRouteRecordMatcherForStateInspector(route) }; } }); api.sendInspectorTree(routerInspectorId); api.sendInspectorState(routerInspectorId); }); } function modifierForKey(key) { if (key.optional) return key.repeatable ? "*" : "?"; else return key.repeatable ? "+" : ""; } function formatRouteRecordMatcherForStateInspector(route) { const { record } = route; const fields = [{ editable: false, key: "path", value: record.path }]; if (record.name != null) fields.push({ editable: false, key: "name", value: record.name }); fields.push({ editable: false, key: "regexp", value: route.re }); if (route.keys.length) fields.push({ editable: false, key: "keys", value: { _custom: { type: null, readOnly: true, display: route.keys.map((key) => `${key.name}${modifierForKey(key)}`).join(" "), tooltip: "Param keys", value: route.keys } } }); if (record.redirect != null) fields.push({ editable: false, key: "redirect", value: record.redirect }); if (route.alias.length) fields.push({ editable: false, key: "aliases", value: route.alias.map((alias) => alias.record.path) }); if (Object.keys(route.record.meta).length) fields.push({ editable: false, key: "meta", value: route.record.meta }); fields.push({ key: "score", editable: false, value: { _custom: { type: null, readOnly: true, display: route.score.map((score) => score.join(", ")).join(" | "), tooltip: "Score used to sort routes", value: route.score } } }); return fields; } /** * Extracted from tailwind palette */ const PINK_500 = 15485081; const BLUE_600 = 2450411; const LIME_500 = 8702998; const CYAN_400 = 2282478; const ORANGE_400 = 16486972; const DARK = 6710886; const RED_100 = 16704226; const RED_700 = 12131356; function formatRouteRecordForInspector(route) { const tags = []; const { record } = route; if (record.name != null) tags.push({ label: String(record.name), textColor: 0, backgroundColor: CYAN_400 }); if (record.aliasOf) tags.push({ label: "alias", textColor: 0, backgroundColor: ORANGE_400 }); if (route.__vd_match) tags.push({ label: "matches", textColor: 0, backgroundColor: PINK_500 }); if (route.__vd_exactActive) tags.push({ label: "exact", textColor: 0, backgroundColor: LIME_500 }); if (route.__vd_active) tags.push({ label: "active", textColor: 0, backgroundColor: BLUE_600 }); if (record.redirect) tags.push({ label: typeof record.redirect === "string" ? `redirect: ${record.redirect}` : "redirects", textColor: 16777215, backgroundColor: DARK }); let id = record.__vd_id; if (id == null) { id = String(routeRecordId++); record.__vd_id = id; } return { id, label: record.path, tags, children: route.children.map(formatRouteRecordForInspector) }; } let routeRecordId = 0; const EXTRACT_REGEXP_RE = /^\/(.*)\/([a-z]*)$/; function markRouteRecordActive(route, currentRoute) { const isExactActive = currentRoute.matched.length && isSameRouteRecord(currentRoute.matched[currentRoute.matched.length - 1], route.record); route.__vd_exactActive = route.__vd_active = isExactActive; if (!isExactActive) route.__vd_active = currentRoute.matched.some((match) => isSameRouteRecord(match, route.record)); route.children.forEach((childRoute) => markRouteRecordActive(childRoute, currentRoute)); } function resetMatchStateOnRouteRecord(route) { route.__vd_match = false; route.children.forEach(resetMatchStateOnRouteRecord); } function isRouteMatching(route, filter) { const found = String(route.re).match(EXTRACT_REGEXP_RE); route.__vd_match = false; if (!found || found.length < 3) return false; if (new RegExp(found[1].replace(/\$$/, ""), found[2]).test(filter)) { route.children.forEach((child) => isRouteMatching(child, filter)); if (route.record.path !== "/" || filter === "/") { route.__vd_match = route.re.test(filter); return true; } return false; } const path = route.record.path.toLowerCase(); const decodedPath = decode(path); if (!filter.startsWith("/") && (decodedPath.includes(filter) || path.includes(filter))) return true; if (decodedPath.startsWith(filter) || path.startsWith(filter)) return true; if (route.record.name && String(route.record.name).includes(filter)) return true; return route.children.some((child) => isRouteMatching(child, filter)); } function omit(obj, keys) { const ret = {}; for (const key in obj) if (!keys.includes(key)) ret[key] = obj[key]; return ret; } //#endregion //#region src/router.ts /** * Creates a Router instance that can be used by a Vue app. * * @param options - {@link RouterOptions} */ function createRouter(options) { const matcher = createRouterMatcher(options.routes, options); const parseQuery$1 = options.parseQuery || parseQuery; const stringifyQuery$1 = options.stringifyQuery || stringifyQuery; const routerHistory = options.history; if (!routerHistory) throw new Error("Provide the \"history\" option when calling \"createRouter()\": https://router.vuejs.org/api/interfaces/RouterOptions.html#history"); const beforeGuards = useCallbacks(); const beforeResolveGuards = useCallbacks(); const afterGuards = useCallbacks(); const currentRoute = shallowRef(START_LOCATION_NORMALIZED); let pendingLocation = START_LOCATION_NORMALIZED; if (isBrowser && options.scrollBehavior && "scrollRestoration" in history) history.scrollRestoration = "manual"; const normalizeParams = applyToParams.bind(null, (paramValue) => "" + paramValue); const encodeParams = applyToParams.bind(null, encodeParam); const decodeParams = applyToParams.bind(null, decode); function addRoute(parentOrRoute, route) { let parent; let record; if (isRouteName(parentOrRoute)) { parent = matcher.getRecordMatcher(parentOrRoute); if (!parent) warn(`Parent route "${String(parentOrRoute)}" not found when adding child route`, route); record = route; } else record = parentOrRoute; return matcher.addRoute(record, parent); } function removeRoute(name) { const recordMatcher = matcher.getRecordMatcher(name); if (recordMatcher) matcher.removeRoute(recordMatcher); else warn(`Cannot remove non-existent route "${String(name)}"`); } function getRoutes() { return matcher.getRoutes().map((routeMatcher) => routeMatcher.record); } function hasRoute(name) { return !!matcher.getRecordMatcher(name); } function resolve(rawLocation, currentLocation) { currentLocation = assign({}, currentLocation || currentRoute.value); if (typeof rawLocation === "string") { const locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path); const matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation); const href = routerHistory.createHref(locationNormalized.fullPath); if (href.startsWith("//")) warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`); else if (!matchedRoute.matched.length) warn(`No match found for location with path "${rawLocation}"`); return assign(locationNormalized, matchedRoute, { params: decodeParams(matchedRoute.params), hash: decode(locationNormalized.hash), redirectedFrom: void 0, href }); } if (!isRouteLocation(rawLocation)) { warn(`router.resolve() was passed an invalid location. This will fail in production.\n- Location:`, rawLocation); return resolve({}); } let matcherLocation; if (rawLocation.path != null) { if ("params" in rawLocation && !("name" in rawLocation) && Object.keys(rawLocation.params).length) warn(`Path "${rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`); matcherLocation = assign({}, rawLocation, { path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path }); } else { const targetParams = assign({}, rawLocation.params); for (const key in targetParams) if (targetParams[key] == null) delete targetParams[key]; matcherLocation = assign({}, rawLocation, { params: encodeParams(targetParams) }); currentLocation.params = encodeParams(currentLocation.params); } const matchedRoute = matcher.resolve(matcherLocation, currentLocation); const hash = rawLocation.hash || ""; if (hash && !hash.startsWith("#")) warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`); matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params)); const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, { hash: encodeHash(hash), path: matchedRoute.path })); const href = routerHistory.createHref(fullPath); if (href.startsWith("//")) warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`); else if (!matchedRoute.matched.length) warn(`No match found for location with path "${rawLocation.path != null ? rawLocation.path : rawLocation}"`); return assign({ fullPath, hash, query: stringifyQuery$1 === stringifyQuery ? normalizeQuery(rawLocation.query) : rawLocation.query || {} }, matchedRoute, { redirectedFrom: void 0, href }); } function locationAsObject(to) { return typeof to === "string" ? parseURL(parseQuery$1, to, currentRoute.value.path) : assign({}, to); } function checkCanceledNavigation(to, from) { if (pendingLocation !== to) return createRouterError(ErrorTypes.NAVIGATION_CANCELLED, { from, to }); } function push(to) { return pushWithRedirect(to); } function replace(to) { return push(assign(locationAsObject(to), { replace: true })); } function handleRedirectRecord(to, from) { const lastMatched = to.matched[to.matched.length - 1]; if (lastMatched && lastMatched.redirect) { const { redirect } = lastMatched; let newTargetLocation = typeof redirect === "function" ? redirect(to, from) : redirect; if (typeof newTargetLocation === "string") { newTargetLocation = newTargetLocation.includes("?") || newTargetLocation.includes("#") ? newTargetLocation = locationAsObject(newTargetLocation) : { path: newTargetLocation }; newTargetLocation.params = {}; } if (newTargetLocation.path == null && !("name" in newTargetLocation)) { warn(`Invalid redirect found:\n${JSON.stringify(newTargetLocation, null, 2)}\n when navigating to "${to.fullPath}". A redirect must contain a name or path. This will break in production.`); throw new Error("Invalid redirect"); } return assign({ query: to.query, hash: to.hash, params: newTargetLocation.path != null ? {} : to.params }, newTargetLocation); } } function pushWithRedirect(to, redirectedFrom) { const targetLocation = pendingLocation = resolve(to); const from = currentRoute.value; const data = to.state; const force = to.force; const replace = to.replace === true; const shouldRedirect = handleRedirectRecord(targetLocation, from); if (shouldRedirect) return pushWithRedirect(assign(locationAsObject(shouldRedirect), { state: typeof shouldRedirect === "object" ? assign({}, data, shouldRedirect.state) : data, force, replace }), redirectedFrom || targetLocation); const toLocation = targetLocation; toLocation.redirectedFrom = redirectedFrom; let failure; if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) { failure = createRouterError(ErrorTypes.NAVIGATION_DUPLICATED, { to: toLocation, from }); handleScroll(from, from, true, false); } return (failure ? Promise.resolve(failure) : navigate(toLocation, from)).catch((error) => isNavigationFailure(error) ? isNavigationFailure(error, ErrorTypes.NAVIGATION_GUARD_REDIRECT) ? error : markAsReady(error) : triggerError(error, toLocation, from)).then((failure) => { if (failure) { if (isNavigationFailure(failure, ErrorTypes.NAVIGATION_GUARD_REDIRECT)) { if (isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) && redirectedFrom && (redirectedFrom._count = redirectedFrom._count ? redirectedFrom._count + 1 : 1) > 30) { warn(`Detected a possibly infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow.\n Are you always returning a new location within a navigation guard? That would lead to this error. Only return when redirecting or aborting, that should fix this. This might break in production if not fixed.`); return Promise.reject(/* @__PURE__ */ new Error("Infinite redirect in navigation guard")); } return pushWithRedirect(assign({ replace }, locationAsObject(failure.to), { state: typeof failure.to === "object" ? assign({}, data, failure.to.state) : data, force }), redirectedFrom || toLocation); } } else failure = finalizeNavigation(toLocation, from, true, replace, data); triggerAfterEach(toLocation, from, failure); return failure; }); } /** * Helper to reject and skip all navigation guards if a new navigation happened * @param to * @param from */ function checkCanceledNavigationAndReject(to, from) { const error = checkCanceledNavigation(to, from); return error ? Promise.reject(error) : Promise.resolve(); } function runWithContext(fn) { const app = installedApps.values().next().value; return app && typeof app.runWithContext === "function" ? app.runWithContext(fn) : fn(); } function navigate(to, from) { let guards; const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from); guards = extractComponentsGuards(leavingRecords.reverse(), "beforeRouteLeave", to, from); for (const record of leavingRecords) record.leaveGuards.forEach((guard) => { guards.push(guardToPromiseFn(guard, to, from)); }); const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from); guards.push(canceledNavigationCheck); return runGuardQueue(guards).then(() => { guards = []; for (const guard of beforeGuards.list()) guards.push(guardToPromiseFn(guard, to, from)); guards.push(canceledNavigationCheck); return runGuardQueue(guards); }).then(() => { guards = extractComponentsGuards(updatingRecords, "beforeRouteUpdate", to, from); for (const record of updatingRecords) record.updateGuards.forEach((guard) => { guards.push(guardToPromiseFn(guard, to, from)); }); guards.push(canceledNavigationCheck); return runGuardQueue(guards); }).then(() => { guards = []; for (const record of enteringRecords) if (record.beforeEnter) if (isArray(record.beforeEnter)) for (const beforeEnter of record.beforeEnter) guards.push(guardToPromiseFn(beforeEnter, to, from)); else guards.push(guardToPromiseFn(record.beforeEnter, to, from)); guards.push(canceledNavigationCheck); return runGuardQueue(guards); }).then(() => { to.matched.forEach((record) => record.enterCallbacks = {}); guards = extractComponentsGuards(enteringRecords, "beforeRouteEnter", to, from, runWithContext); guards.push(canceledNavigationCheck); return runGuardQueue(guards); }).then(() => { guards = []; for (const guard of beforeResolveGuards.list()) guards.push(guardToPromiseFn(guard, to, from)); guards.push(canceledNavigationCheck); return runGuardQueue(guards); }).catch((err) => isNavigationFailure(err, ErrorTypes.NAVIGATION_CANCELLED) ? err : Promise.reject(err)); } function triggerAfterEach(to, from, failure) { afterGuards.list().forEach((guard) => runWithContext(() => guard(to, from, failure))); } /** * - Cleans up any navigation guards * - Changes the url if necessary * - Calls the scrollBehavior */ function finalizeNavigation(toLocation, from, isPush, replace, data) { const error = checkCanceledNavigation(toLocation, from); if (error) return error; const isFirstNavigation = from === START_LOCATION_NORMALIZED; const state = !isBrowser ? {} : history.state; if (isPush) if (replace || isFirstNavigation) routerHistory.replace(toLocation.fullPath, assign({ scroll: isFirstNavigation && state && state.scroll }, data)); else routerHistory.push(toLocation.fullPath, data); currentRoute.value = toLocation; handleScroll(toLocation, from, isPush, isFirstNavigation); markAsReady(); } let removeHistoryListener; function setupListeners() { if (removeHistoryListener) return; removeHistoryListener = routerHistory.listen((to, _from, info) => { if (!router.listening) return; const toLocation = resolve(to); const shouldRedirect = handleRedirectRecord(toLocation, router.currentRoute.value); if (shouldRedirect) { pushWithRedirect(assign(shouldRedirect, { replace: true, force: true }), toLocation).catch(noop); return; } pendingLocation = toLocation; const from = currentRoute.value; if (isBrowser) saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition()); navigate(toLocation, from).catch((error) => { if (isNavigationFailure(error, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_CANCELLED)) return error; if (isNavigationFailure(error, ErrorTypes.NAVIGATION_GUARD_REDIRECT)) { pushWithRedirect(assign(locationAsObject(error.to), { force: true }), toLocation).then((failure) => { if (isNavigationFailure(failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED) && !info.delta && info.type === NavigationType.pop) routerHistory.go(-1, false); }).catch(noop); return Promise.reject(); } if (info.delta) routerHistory.go(-info.delta, false); return triggerError(error, toLocation, from); }).then((failure) => { failure = failure || finalizeNavigation(toLocation, from, false); if (failure) { if (info.delta && !isNavigationFailure(failure, ErrorTypes.NAVIGATION_CANCELLED)) routerHistory.go(-info.delta, false); else if (info.type === NavigationType.pop && isNavigationFailure(failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED)) routerHistory.go(-1, false); } triggerAfterEach(toLocation, from, failure); }).catch(noop); }); } let readyHandlers = useCallbacks(); let errorListeners = useCallbacks(); let ready; /** * Trigger errorListeners added via onError and throws the error as well * * @param error - error to throw * @param to - location we were navigating to when the error happened * @param from - location we were navigating from when the error happened * @returns the error as a rejected promise */ function triggerError(error, to, from) { markAsReady(error); const list = errorListeners.list(); if (list.length) list.forEach((handler) => handler(error, to, from)); else { warn("uncaught error during route navigation:"); console.error(error); } return Promise.reject(error); } function isReady() { if (ready && currentRoute.value !== START_LOCATION_NORMALIZED) return Promise.resolve(); return new Promise((resolve, reject) => { readyHandlers.add([resolve, reject]); }); } function markAsReady(err) { if (!ready) { ready = !err; setupListeners(); readyHandlers.list().forEach(([resolve, reject]) => err ? reject(err) : resolve()); readyHandlers.reset(); } return err; } function handleScroll(to, from, isPush, isFirstNavigation) { const { scrollBehavior } = options; if (!isBrowser || !scrollBehavior) return Promise.resolve(); const scrollPosition = !isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0)) || (isFirstNavigation || !isPush) && history.state && history.state.scroll || null; return nextTick().then(() => scrollBehavior(to, from, scrollPosition)).then((position) => position && scrollToPosition(position)).catch((err) => triggerError(err, to, from)); } const go = (delta) => routerHistory.go(delta); let started; const installedApps = /* @__PURE__ */ new Set(); const router = { currentRoute, listening: true, addRoute, removeRoute, clearRoutes: matcher.clearRoutes, hasRoute, getRoutes, resolve, options, push, replace, go, back: () => go(-1), forward: () => go(1), beforeEach: beforeGuards.add, beforeResolve: beforeResolveGuards.add, afterEach: afterGuards.add, onError: errorListeners.add, isReady, install(app) { app.component("RouterLink", RouterLink); app.component("RouterView", RouterView); app.config.globalProperties.$router = router; Object.defineProperty(app.config.globalProperties, "$route", { enumerable: true, get: () => unref(currentRoute) }); if (isBrowser && !started && currentRoute.value === START_LOCATION_NORMALIZED) { started = true; push(routerHistory.location).catch((err) => { warn("Unexpected error when starting the router:", err); }); } const reactiveRoute = {}; for (const key in START_LOCATION_NORMALIZED) Object.defineProperty(reactiveRoute, key, { get: () => currentRoute.value[key], enumerable: true }); app.provide(routerKey, router); app.provide(routeLocationKey, shallowReactive(reactiveRoute)); app.provide(routerViewLocationKey, currentRoute); const unmountApp = app.unmount; installedApps.add(app); app.unmount = function() { installedApps.delete(app); if (installedApps.size < 1) { pendingLocation = START_LOCATION_NORMALIZED; removeHistoryListener && removeHistoryListener(); removeHistoryListener = null; currentRoute.value = START_LOCATION_NORMALIZED; started = false; ready = false; } unmountApp(); }; if (isBrowser && true) addDevtools(app, router, matcher); } }; function runGuardQueue(guards) { return guards.reduce((promise, guard) => promise.then(() => runWithContext(guard)), Promise.resolve()); } return router; } //#endregion //#region src/useApi.ts /** * Returns the router instance. Equivalent to using `$router` inside * templates. */ function useRouter() { return inject(routerKey); } /** * Returns the current route location. Equivalent to using `$route` inside * templates. */ function useRoute(_name) { return inject(routeLocationKey); } //#endregion export { NavigationFailureType, RouterLink, RouterView, START_LOCATION_NORMALIZED as START_LOCATION, createMemoryHistory, createRouter, createRouterMatcher, createWebHashHistory, createWebHistory, isNavigationFailure, loadRouteLocation, matchedRouteKey, onBeforeRouteLeave, onBeforeRouteUpdate, parseQuery, routeLocationKey, routerKey, routerViewLocationKey, stringifyQuery, useLink, useRoute, useRouter, viewDepthKey };