2971 lines
		
	
	
		
			112 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			2971 lines
		
	
	
		
			112 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*!
 | |
|   * vue-router v4.5.1
 | |
|   * (c) 2025 Eduardo San Martin Morote
 | |
|   * @license MIT
 | |
|   */
 | |
| 'use strict';
 | |
| 
 | |
| var vue = require('vue');
 | |
| 
 | |
| const isBrowser = typeof document !== 'undefined';
 | |
| 
 | |
| /**
 | |
|  * 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' ||
 | |
|         // support CF with dynamic imports that do not
 | |
|         // add the Module string tag
 | |
|         (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
 | |
|  */
 | |
| const isArray = Array.isArray;
 | |
| 
 | |
| /**
 | |
|  * 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 EXTRA_RESERVED_RE = /[!'()*]/g
 | |
| // const encodeReservedReplacer = (c: string) => '%' + c.charCodeAt(0).toString(16)
 | |
| const HASH_RE = /#/g; // %23
 | |
| const AMPERSAND_RE = /&/g; // %26
 | |
| const SLASH_RE = /\//g; // %2F
 | |
| const EQUAL_RE = /=/g; // %3D
 | |
| const IM_RE = /\?/g; // %3F
 | |
| const PLUS_RE = /\+/g; // %2B
 | |
| /**
 | |
|  * 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 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)
 | |
|         // Encode the space as +, encode the + to differentiate it from the space
 | |
|         .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 text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F');
 | |
| }
 | |
| /**
 | |
|  * Decode text using `decodeURIComponent`. Returns the original text if it
 | |
|  * fails.
 | |
|  *
 | |
|  * @param text - string to decode
 | |
|  * @returns decoded string
 | |
|  */
 | |
| function decode(text) {
 | |
|     try {
 | |
|         return decodeURIComponent('' + text);
 | |
|     }
 | |
|     catch (err) {
 | |
|     }
 | |
|     return '' + text;
 | |
| }
 | |
| 
 | |
| 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 = '';
 | |
|     // Could use URL and URLSearchParams but IE 11 doesn't support it
 | |
|     // TODO: move to new URL()
 | |
|     const hashPos = location.indexOf('#');
 | |
|     let searchPos = location.indexOf('?');
 | |
|     // the hash appears before the search, so it's not part of the search string
 | |
|     if (hashPos < searchPos && hashPos >= 0) {
 | |
|         searchPos = -1;
 | |
|     }
 | |
|     if (searchPos > -1) {
 | |
|         path = location.slice(0, searchPos);
 | |
|         searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length);
 | |
|         query = parseQuery(searchString);
 | |
|     }
 | |
|     if (hashPos > -1) {
 | |
|         path = path || location.slice(0, hashPos);
 | |
|         // keep the # character
 | |
|         hash = location.slice(hashPos, location.length);
 | |
|     }
 | |
|     // no search and no query
 | |
|     path = resolveRelativePath(path != null ? path : location, currentLocation);
 | |
|     // empty path means a relative query or hash `?foo=f`, `#thing`
 | |
|     return {
 | |
|         fullPath: path + (searchString && '?') + 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) {
 | |
|     // no base or base is not found at the beginning
 | |
|     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) {
 | |
|     // since the original record has an undefined value for aliasOf
 | |
|     // but all aliases point to the original record, this will always compare
 | |
|     // the original record
 | |
|     return (a.aliasOf || a) === (b.aliasOf || b);
 | |
| }
 | |
| function isSameRouteLocationParams(a, b) {
 | |
|     if (Object.keys(a).length !== Object.keys(b).length)
 | |
|         return false;
 | |
|     for (const 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 === b;
 | |
| }
 | |
| /**
 | |
|  * 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 (!to)
 | |
|         return from;
 | |
|     const fromSegments = from.split('/');
 | |
|     const toSegments = to.split('/');
 | |
|     const lastToSegment = toSegments[toSegments.length - 1];
 | |
|     // make . and ./ the same (../ === .., ../../ === ../..)
 | |
|     // this is the same behavior as new URL()
 | |
|     if (lastToSegment === '..' || lastToSegment === '.') {
 | |
|         toSegments.push('');
 | |
|     }
 | |
|     let position = fromSegments.length - 1;
 | |
|     let toPosition;
 | |
|     let segment;
 | |
|     for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
 | |
|         segment = toSegments[toPosition];
 | |
|         // we stay on the same position
 | |
|         if (segment === '.')
 | |
|             continue;
 | |
|         // go up in the from array
 | |
|         if (segment === '..') {
 | |
|             // we can't go below zero, but we still need to increment toPosition
 | |
|             if (position > 1)
 | |
|                 position--;
 | |
|             // continue
 | |
|         }
 | |
|         // we reached a non-relative path, we stop here
 | |
|         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: '/',
 | |
|     // TODO: could we use a symbol in the future?
 | |
|     name: undefined,
 | |
|     params: {},
 | |
|     query: {},
 | |
|     hash: '',
 | |
|     fullPath: '/',
 | |
|     matched: [],
 | |
|     meta: {},
 | |
|     redirectedFrom: undefined,
 | |
| };
 | |
| 
 | |
| var NavigationType;
 | |
| (function (NavigationType) {
 | |
|     NavigationType["pop"] = "pop";
 | |
|     NavigationType["push"] = "push";
 | |
| })(NavigationType || (NavigationType = {}));
 | |
| var NavigationDirection;
 | |
| (function (NavigationDirection) {
 | |
|     NavigationDirection["back"] = "back";
 | |
|     NavigationDirection["forward"] = "forward";
 | |
|     NavigationDirection["unknown"] = "";
 | |
| })(NavigationDirection || (NavigationDirection = {}));
 | |
| /**
 | |
|  * Starting location for Histories
 | |
|  */
 | |
| const START = '';
 | |
| // Generic utils
 | |
| /**
 | |
|  * 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) {
 | |
|             // respect <base> tag
 | |
|             const baseEl = document.querySelector('base');
 | |
|             base = (baseEl && baseEl.getAttribute('href')) || '/';
 | |
|             // strip full URL origin
 | |
|             base = base.replace(/^\w+:\/\/[^\/]+/, '');
 | |
|         }
 | |
|         else {
 | |
|             base = '/';
 | |
|         }
 | |
|     }
 | |
|     // ensure leading slash when it was removed by the regex above avoid leading
 | |
|     // slash with hash because the file could be read from the disk like file://
 | |
|     // and the leading slash would cause problems
 | |
|     if (base[0] !== '/' && base[0] !== '#')
 | |
|         base = '/' + base;
 | |
|     // remove the trailing slash so all other method can just do `base + fullPath`
 | |
|     // to build an href
 | |
|     return removeTrailingSlash(base);
 | |
| }
 | |
| // remove any character before the hash
 | |
| const BEFORE_HASH_RE = /^[^#]+#/;
 | |
| function createHref(base, location) {
 | |
|     return base.replace(BEFORE_HASH_RE, '#') + location;
 | |
| }
 | |
| 
 | |
| 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('#');
 | |
|         const el = typeof positionEl === 'string'
 | |
|             ? isIdSelector
 | |
|                 ? document.getElementById(positionEl.slice(1))
 | |
|                 : document.querySelector(positionEl)
 | |
|             : positionEl;
 | |
|         if (!el) {
 | |
|             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) {
 | |
|     const position = history.state ? history.state.position - delta : -1;
 | |
|     return position + path;
 | |
| }
 | |
| const scrollPositions = new Map();
 | |
| function saveScrollPosition(key, scrollPosition) {
 | |
|     scrollPositions.set(key, scrollPosition);
 | |
| }
 | |
| function getSavedScrollPosition(key) {
 | |
|     const scroll = scrollPositions.get(key);
 | |
|     // consume it so it's not used again
 | |
|     scrollPositions.delete(key);
 | |
|     return scroll;
 | |
| }
 | |
| // TODO: RFC about how to save scroll position
 | |
| /**
 | |
|  * ScrollBehavior instance used by the router to compute and restore the scroll
 | |
|  * position when navigating.
 | |
|  */
 | |
| // export interface ScrollHandler<ScrollPositionEntry extends HistoryStateValue, ScrollPosition extends ScrollPositionEntry> {
 | |
| //   // returns a scroll position that can be saved in history
 | |
| //   compute(): ScrollPositionEntry
 | |
| //   // can take an extended ScrollPositionEntry
 | |
| //   scroll(position: ScrollPosition): void
 | |
| // }
 | |
| // export const scrollHandler: ScrollHandler<ScrollPosition> = {
 | |
| //   compute: computeScroll,
 | |
| //   scroll: scrollToPosition,
 | |
| // }
 | |
| 
 | |
| 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;
 | |
|     // allows hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end
 | |
|     const hashPos = base.indexOf('#');
 | |
|     if (hashPos > -1) {
 | |
|         let slicePos = hash.includes(base.slice(hashPos))
 | |
|             ? base.slice(hashPos).length
 | |
|             : 1;
 | |
|         let pathFromHash = hash.slice(slicePos);
 | |
|         // prepend the starting slash to hash so the url starts with /#
 | |
|         if (pathFromHash[0] !== '/')
 | |
|             pathFromHash = '/' + pathFromHash;
 | |
|         return stripBase(pathFromHash, '');
 | |
|     }
 | |
|     const path = stripBase(pathname, base);
 | |
|     return path + search + hash;
 | |
| }
 | |
| function useHistoryListeners(base, historyState, currentLocation, replace) {
 | |
|     let listeners = [];
 | |
|     let teardowns = [];
 | |
|     // TODO: should it be a stack? a Dict. Check if the popstate listener
 | |
|     // can trigger twice
 | |
|     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;
 | |
|             // ignore the popstate and reset the pauseState
 | |
|             if (pauseState && pauseState === from) {
 | |
|                 pauseState = null;
 | |
|                 return;
 | |
|             }
 | |
|             delta = fromState ? state.position - fromState.position : 0;
 | |
|         }
 | |
|         else {
 | |
|             replace(to);
 | |
|         }
 | |
|         // Here we could also revert the navigation by calling history.go(-delta)
 | |
|         // this listener will have to be adapted to not trigger again and to wait for the url
 | |
|         // to be updated before triggering the listeners. Some kind of validation function would also
 | |
|         // need to be passed to the listeners so the navigation can be accepted
 | |
|         // call all listeners
 | |
|         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) {
 | |
|         // set up the listener and prepare teardown callbacks
 | |
|         listeners.push(callback);
 | |
|         const teardown = () => {
 | |
|             const index = listeners.indexOf(callback);
 | |
|             if (index > -1)
 | |
|                 listeners.splice(index, 1);
 | |
|         };
 | |
|         teardowns.push(teardown);
 | |
|         return teardown;
 | |
|     }
 | |
|     function beforeUnloadListener() {
 | |
|         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('beforeunload', beforeUnloadListener);
 | |
|     }
 | |
|     // set up the listeners and prepare teardown callbacks
 | |
|     window.addEventListener('popstate', popStateHandler);
 | |
|     // TODO: could we use 'pagehide' or 'visibilitychange' instead?
 | |
|     // https://developer.chrome.com/blog/page-lifecycle-api/
 | |
|     window.addEventListener('beforeunload', beforeUnloadListener, {
 | |
|         passive: true,
 | |
|     });
 | |
|     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;
 | |
|     // private variables
 | |
|     const currentLocation = {
 | |
|         value: createCurrentLocation(base, location),
 | |
|     };
 | |
|     const historyState = { value: history.state };
 | |
|     // build current history entry as this is a fresh navigation
 | |
|     if (!historyState.value) {
 | |
|         changeLocation(currentLocation.value, {
 | |
|             back: null,
 | |
|             current: currentLocation.value,
 | |
|             forward: null,
 | |
|             // the length is off by one, we need to decrease it
 | |
|             position: history.length - 1,
 | |
|             replaced: true,
 | |
|             // don't add a scroll as the user may have an anchor, and we want
 | |
|             // scrollBehavior to be triggered without a saved position
 | |
|             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 `<base>` 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 {
 | |
|             // BROWSER QUIRK
 | |
|             // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds
 | |
|             history[replace ? 'replaceState' : 'pushState'](state, '', url);
 | |
|             historyState.value = state;
 | |
|         }
 | |
|         catch (err) {
 | |
|             {
 | |
|                 console.error(err);
 | |
|             }
 | |
|             // Force the navigation, this also resets the call count
 | |
|             location[replace ? 'replace' : 'assign'](url);
 | |
|         }
 | |
|     }
 | |
|     function replace(to, data) {
 | |
|         const state = assign({}, history.state, buildState(historyState.value.back, 
 | |
|         // keep back and forward entries but override current position
 | |
|         to, historyState.value.forward, true), data, { position: historyState.value.position });
 | |
|         changeLocation(to, state, true);
 | |
|         currentLocation.value = to;
 | |
|     }
 | |
|     function push(to, data) {
 | |
|         // Add to current entry the information of where we are going
 | |
|         // as well as saving the current position
 | |
|         const currentState = assign({}, 
 | |
|         // use current history state to gracefully handle a wrong call to
 | |
|         // history.replaceState
 | |
|         // https://github.com/vuejs/router/issues/366
 | |
|         historyState.value, history.state, {
 | |
|             forward: to,
 | |
|             scroll: computeScrollPosition(),
 | |
|         });
 | |
|         changeLocation(currentState.current, currentState, true);
 | |
|         const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data);
 | |
|         changeLocation(to, state, 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({
 | |
|         // it's overridden right after
 | |
|         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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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) {
 | |
|             // we are in the middle, we remove everything from here in the queue
 | |
|             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 = {
 | |
|         // rewritten by Object.defineProperty
 | |
|         location: START,
 | |
|         // rewritten by Object.defineProperty
 | |
|         state: {},
 | |
|         base,
 | |
|         createHref: createHref.bind(null, base),
 | |
|         replace(to, state) {
 | |
|             // remove current entry and decrement position
 | |
|             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 = 
 | |
|             // we are considering delta === 0 going forward, but in abstract mode
 | |
|             // using 0 for the delta doesn't make sense like it does in html5 where
 | |
|             // it reloads the page
 | |
|             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;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 `<base>` 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 `<base>` 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) {
 | |
|     // Make sure this implementation is fine in terms of encoding, specially for IE11
 | |
|     // for `file://`, directly use the pathname and ignore the base
 | |
|     // location.pathname contains an initial `/` even at the root: `https://example.com`
 | |
|     base = location.host ? base || location.pathname + location.search : '';
 | |
|     // allow the user to provide a `#` in the middle: `/base/#/app`
 | |
|     if (!base.includes('#'))
 | |
|         base += '#';
 | |
|     return createWebHistory(base);
 | |
| }
 | |
| 
 | |
| function isRouteLocation(route) {
 | |
|     return typeof route === 'string' || (route && typeof route === 'object');
 | |
| }
 | |
| function isRouteName(name) {
 | |
|     return typeof name === 'string' || typeof name === 'symbol';
 | |
| }
 | |
| 
 | |
| const NavigationFailureSymbol = Symbol('');
 | |
| /**
 | |
|  * Enumeration with all possible types for navigation failures. Can be passed to
 | |
|  * {@link isNavigationFailure} to check for specific failures.
 | |
|  */
 | |
| exports.NavigationFailureType = void 0;
 | |
| (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";
 | |
| })(exports.NavigationFailureType || (exports.NavigationFailureType = {}));
 | |
| // DEV only debug messages
 | |
| const ErrorTypeMessages = {
 | |
|     [1 /* ErrorTypes.MATCHER_NOT_FOUND */]({ location, currentLocation }) {
 | |
|         return `No match for\n ${JSON.stringify(location)}${currentLocation
 | |
|             ? '\nwhile being at\n' + JSON.stringify(currentLocation)
 | |
|             : ''}`;
 | |
|     },
 | |
|     [2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
 | |
|         return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
 | |
|     },
 | |
|     [4 /* ErrorTypes.NAVIGATION_ABORTED */]({ from, to }) {
 | |
|         return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
 | |
|     },
 | |
|     [8 /* ErrorTypes.NAVIGATION_CANCELLED */]({ from, to }) {
 | |
|         return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
 | |
|     },
 | |
|     [16 /* 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) {
 | |
|     // keep full error messages in cjs versions
 | |
|     {
 | |
|         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);
 | |
| }
 | |
| 
 | |
| // default pattern for a param: non-greedy everything but /
 | |
| const BASE_PARAM_PATTERN = '[^/]+?';
 | |
| const BASE_PATH_PARSER_OPTIONS = {
 | |
|     sensitive: false,
 | |
|     strict: false,
 | |
|     start: true,
 | |
|     end: true,
 | |
| };
 | |
| // Special Regex characters that must be escaped in static tokens
 | |
| 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);
 | |
|     // the amount of scores is the same as the length of segments except for the root segment "/"
 | |
|     const score = [];
 | |
|     // the regexp as a string
 | |
|     let pattern = options.start ? '^' : '';
 | |
|     // extracted keys
 | |
|     const keys = [];
 | |
|     for (const segment of segments) {
 | |
|         // the root segment needs special treatment
 | |
|         const segmentScores = segment.length ? [] : [90 /* PathScore.Root */];
 | |
|         // allow trailing slash
 | |
|         if (options.strict && !segment.length)
 | |
|             pattern += '/';
 | |
|         for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
 | |
|             const token = segment[tokenIndex];
 | |
|             // resets the score if we are inside a sub-segment /:a-other-:b
 | |
|             let subSegmentScore = 40 /* PathScore.Segment */ +
 | |
|                 (options.sensitive ? 0.25 /* PathScore.BonusCaseSensitive */ : 0);
 | |
|             if (token.type === 0 /* TokenType.Static */) {
 | |
|                 // prepend the slash if we are starting a new segment
 | |
|                 if (!tokenIndex)
 | |
|                     pattern += '/';
 | |
|                 pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
 | |
|                 subSegmentScore += 40 /* PathScore.Static */;
 | |
|             }
 | |
|             else if (token.type === 1 /* TokenType.Param */) {
 | |
|                 const { value, repeatable, optional, regexp } = token;
 | |
|                 keys.push({
 | |
|                     name: value,
 | |
|                     repeatable,
 | |
|                     optional,
 | |
|                 });
 | |
|                 const re = regexp ? regexp : BASE_PARAM_PATTERN;
 | |
|                 // the user provided a custom regexp /:id(\\d+)
 | |
|                 if (re !== BASE_PARAM_PATTERN) {
 | |
|                     subSegmentScore += 10 /* PathScore.BonusCustomRegExp */;
 | |
|                     // make sure the regexp is valid before using it
 | |
|                     try {
 | |
|                         new RegExp(`(${re})`);
 | |
|                     }
 | |
|                     catch (err) {
 | |
|                         throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` +
 | |
|                             err.message);
 | |
|                     }
 | |
|                 }
 | |
|                 // when we repeat we must take care of the repeating leading slash
 | |
|                 let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
 | |
|                 // prepend the slash if we are starting a new segment
 | |
|                 if (!tokenIndex)
 | |
|                     subPattern =
 | |
|                         // avoid an optional / if there are more segments e.g. /:p?-static
 | |
|                         // or /:p?-:p2
 | |
|                         optional && segment.length < 2
 | |
|                             ? `(?:/${subPattern})`
 | |
|                             : '/' + subPattern;
 | |
|                 if (optional)
 | |
|                     subPattern += '?';
 | |
|                 pattern += subPattern;
 | |
|                 subSegmentScore += 20 /* PathScore.Dynamic */;
 | |
|                 if (optional)
 | |
|                     subSegmentScore += -8 /* PathScore.BonusOptional */;
 | |
|                 if (repeatable)
 | |
|                     subSegmentScore += -20 /* PathScore.BonusRepeatable */;
 | |
|                 if (re === '.*')
 | |
|                     subSegmentScore += -50 /* PathScore.BonusWildcard */;
 | |
|             }
 | |
|             segmentScores.push(subSegmentScore);
 | |
|         }
 | |
|         // an empty array like /home/ -> [[{home}], []]
 | |
|         // if (!segment.length) pattern += '/'
 | |
|         score.push(segmentScores);
 | |
|     }
 | |
|     // only apply the strict bonus to the last score
 | |
|     if (options.strict && options.end) {
 | |
|         const i = score.length - 1;
 | |
|         score[i][score[i].length - 1] += 0.7000000000000001 /* PathScore.BonusStrict */;
 | |
|     }
 | |
|     // TODO: dev only warn double trailing slash
 | |
|     if (!options.strict)
 | |
|         pattern += '/?';
 | |
|     if (options.end)
 | |
|         pattern += '$';
 | |
|     // allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else
 | |
|     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 = '';
 | |
|         // for optional parameters to allow to be empty
 | |
|         let avoidDuplicatedSlash = false;
 | |
|         for (const segment of segments) {
 | |
|             if (!avoidDuplicatedSlash || !path.endsWith('/'))
 | |
|                 path += '/';
 | |
|             avoidDuplicatedSlash = false;
 | |
|             for (const token of segment) {
 | |
|                 if (token.type === 0 /* TokenType.Static */) {
 | |
|                     path += token.value;
 | |
|                 }
 | |
|                 else if (token.type === 1 /* 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 we have more than one optional param like /:a?-static we don't need to care about the optional param
 | |
|                             if (segment.length < 2) {
 | |
|                                 // remove the last slash as we could be at the end
 | |
|                                 if (path.endsWith('/'))
 | |
|                                     path = path.slice(0, -1);
 | |
|                                 // do not append a slash on the next iteration
 | |
|                                 else
 | |
|                                     avoidDuplicatedSlash = true;
 | |
|                             }
 | |
|                         }
 | |
|                         else
 | |
|                             throw new Error(`Missing required param "${value}"`);
 | |
|                     }
 | |
|                     path += text;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         // avoid empty path when we have multiple optional params
 | |
|         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];
 | |
|         // only keep going if diff === 0
 | |
|         if (diff)
 | |
|             return diff;
 | |
|         i++;
 | |
|     }
 | |
|     // if the last subsegment was Static, the shorter segments should be sorted first
 | |
|     // otherwise sort the longest segment first
 | |
|     if (a.length < b.length) {
 | |
|         return a.length === 1 && a[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
 | |
|             ? -1
 | |
|             : 1;
 | |
|     }
 | |
|     else if (a.length > b.length) {
 | |
|         return b.length === 1 && b[0] === 40 /* PathScore.Static */ + 40 /* 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]);
 | |
|         // do not return if both are equal
 | |
|         if (comp)
 | |
|             return comp;
 | |
|         i++;
 | |
|     }
 | |
|     if (Math.abs(bScore.length - aScore.length) === 1) {
 | |
|         if (isLastScoreNegative(aScore))
 | |
|             return 1;
 | |
|         if (isLastScoreNegative(bScore))
 | |
|             return -1;
 | |
|     }
 | |
|     // if a and b share the same score entries but b has more, sort b first
 | |
|     return bScore.length - aScore.length;
 | |
|     // this is the ternary version
 | |
|     // return aScore.length < bScore.length
 | |
|     //   ? 1
 | |
|     //   : aScore.length > bScore.length
 | |
|     //   ? -1
 | |
|     //   : 0
 | |
| }
 | |
| /**
 | |
|  * 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 ROOT_TOKEN = {
 | |
|     type: 0 /* TokenType.Static */,
 | |
|     value: '',
 | |
| };
 | |
| const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
 | |
| // After some profiling, the cache seems to be unnecessary because tokenizePath
 | |
| // (the slowest part of adding a route) is very fast
 | |
| // const tokenCache = new Map<string, Token[][]>()
 | |
| function tokenizePath(path) {
 | |
|     if (!path)
 | |
|         return [[]];
 | |
|     if (path === '/')
 | |
|         return [[ROOT_TOKEN]];
 | |
|     if (!path.startsWith('/')) {
 | |
|         throw new Error(`Invalid path "${path}"`);
 | |
|     }
 | |
|     // if (tokenCache.has(path)) return tokenCache.get(path)!
 | |
|     function crash(message) {
 | |
|         throw new Error(`ERR (${state})/"${buffer}": ${message}`);
 | |
|     }
 | |
|     let state = 0 /* TokenizerState.Static */;
 | |
|     let previousState = state;
 | |
|     const tokens = [];
 | |
|     // the segment will always be valid because we get into the initial state
 | |
|     // with the leading /
 | |
|     let segment;
 | |
|     function finalizeSegment() {
 | |
|         if (segment)
 | |
|             tokens.push(segment);
 | |
|         segment = [];
 | |
|     }
 | |
|     // index on the path
 | |
|     let i = 0;
 | |
|     // char at index
 | |
|     let char;
 | |
|     // buffer of the value read
 | |
|     let buffer = '';
 | |
|     // custom regexp for a param
 | |
|     let customRe = '';
 | |
|     function consumeBuffer() {
 | |
|         if (!buffer)
 | |
|             return;
 | |
|         if (state === 0 /* TokenizerState.Static */) {
 | |
|             segment.push({
 | |
|                 type: 0 /* TokenType.Static */,
 | |
|                 value: buffer,
 | |
|             });
 | |
|         }
 | |
|         else if (state === 1 /* TokenizerState.Param */ ||
 | |
|             state === 2 /* TokenizerState.ParamRegExp */ ||
 | |
|             state === 3 /* TokenizerState.ParamRegExpEnd */) {
 | |
|             if (segment.length > 1 && (char === '*' || char === '+'))
 | |
|                 crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
 | |
|             segment.push({
 | |
|                 type: 1 /* 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 !== 2 /* TokenizerState.ParamRegExp */) {
 | |
|             previousState = state;
 | |
|             state = 4 /* TokenizerState.EscapeNext */;
 | |
|             continue;
 | |
|         }
 | |
|         switch (state) {
 | |
|             case 0 /* TokenizerState.Static */:
 | |
|                 if (char === '/') {
 | |
|                     if (buffer) {
 | |
|                         consumeBuffer();
 | |
|                     }
 | |
|                     finalizeSegment();
 | |
|                 }
 | |
|                 else if (char === ':') {
 | |
|                     consumeBuffer();
 | |
|                     state = 1 /* TokenizerState.Param */;
 | |
|                 }
 | |
|                 else {
 | |
|                     addCharToBuffer();
 | |
|                 }
 | |
|                 break;
 | |
|             case 4 /* TokenizerState.EscapeNext */:
 | |
|                 addCharToBuffer();
 | |
|                 state = previousState;
 | |
|                 break;
 | |
|             case 1 /* TokenizerState.Param */:
 | |
|                 if (char === '(') {
 | |
|                     state = 2 /* TokenizerState.ParamRegExp */;
 | |
|                 }
 | |
|                 else if (VALID_PARAM_RE.test(char)) {
 | |
|                     addCharToBuffer();
 | |
|                 }
 | |
|                 else {
 | |
|                     consumeBuffer();
 | |
|                     state = 0 /* TokenizerState.Static */;
 | |
|                     // go back one character if we were not modifying
 | |
|                     if (char !== '*' && char !== '?' && char !== '+')
 | |
|                         i--;
 | |
|                 }
 | |
|                 break;
 | |
|             case 2 /* TokenizerState.ParamRegExp */:
 | |
|                 // TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
 | |
|                 // it already works by escaping the closing )
 | |
|                 // https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
 | |
|                 // is this really something people need since you can also write
 | |
|                 // /prefix_:p()_suffix
 | |
|                 if (char === ')') {
 | |
|                     // handle the escaped )
 | |
|                     if (customRe[customRe.length - 1] == '\\')
 | |
|                         customRe = customRe.slice(0, -1) + char;
 | |
|                     else
 | |
|                         state = 3 /* TokenizerState.ParamRegExpEnd */;
 | |
|                 }
 | |
|                 else {
 | |
|                     customRe += char;
 | |
|                 }
 | |
|                 break;
 | |
|             case 3 /* TokenizerState.ParamRegExpEnd */:
 | |
|                 // same as finalizing a param
 | |
|                 consumeBuffer();
 | |
|                 state = 0 /* TokenizerState.Static */;
 | |
|                 // go back one character if we were not modifying
 | |
|                 if (char !== '*' && char !== '?' && char !== '+')
 | |
|                     i--;
 | |
|                 customRe = '';
 | |
|                 break;
 | |
|             default:
 | |
|                 crash('Unknown state');
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
|     if (state === 2 /* TokenizerState.ParamRegExp */)
 | |
|         crash(`Unfinished custom RegExp for param "${buffer}"`);
 | |
|     consumeBuffer();
 | |
|     finalizeSegment();
 | |
|     // tokenCache.set(path, tokens)
 | |
|     return tokens;
 | |
| }
 | |
| 
 | |
| function createRouteRecordMatcher(record, parent, options) {
 | |
|     const parser = tokensToParser(tokenizePath(record.path), options);
 | |
|     const matcher = assign(parser, {
 | |
|         record,
 | |
|         parent,
 | |
|         // these needs to be populated by the parent
 | |
|         children: [],
 | |
|         alias: [],
 | |
|     });
 | |
|     if (parent) {
 | |
|         // both are aliases or both are not aliases
 | |
|         // we don't want to mix them because the order is used when
 | |
|         // passing originalRecord in Matcher.addRoute
 | |
|         if (!matcher.record.aliasOf === !parent.record.aliasOf)
 | |
|             parent.children.push(matcher);
 | |
|     }
 | |
|     return matcher;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Creates a Router Matcher.
 | |
|  *
 | |
|  * @internal
 | |
|  * @param routes - array of initial routes
 | |
|  * @param globalOptions - global route options
 | |
|  */
 | |
| function createRouterMatcher(routes, globalOptions) {
 | |
|     // normalized ordered array of matchers
 | |
|     const matchers = [];
 | |
|     const matcherMap = new Map();
 | |
|     globalOptions = mergeOptions({ strict: false, end: true, sensitive: false }, globalOptions);
 | |
|     function getRecordMatcher(name) {
 | |
|         return matcherMap.get(name);
 | |
|     }
 | |
|     function addRoute(record, parent, originalRecord) {
 | |
|         // used later on to remove by name
 | |
|         const isRootAdd = !originalRecord;
 | |
|         const mainNormalizedRecord = normalizeRouteRecord(record);
 | |
|         // we might be the child of an alias
 | |
|         mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;
 | |
|         const options = mergeOptions(globalOptions, record);
 | |
|         // generate an array of records to correctly handle aliases
 | |
|         const normalizedRecords = [mainNormalizedRecord];
 | |
|         if ('alias' in record) {
 | |
|             const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias;
 | |
|             for (const alias of aliases) {
 | |
|                 normalizedRecords.push(
 | |
|                 // we need to normalize again to ensure the `mods` property
 | |
|                 // being non enumerable
 | |
|                 normalizeRouteRecord(assign({}, mainNormalizedRecord, {
 | |
|                     // this allows us to hold a copy of the `components` option
 | |
|                     // so that async components cache is hold on the original record
 | |
|                     components: originalRecord
 | |
|                         ? originalRecord.record.components
 | |
|                         : mainNormalizedRecord.components,
 | |
|                     path: alias,
 | |
|                     // we might be the child of an alias
 | |
|                     aliasOf: originalRecord
 | |
|                         ? originalRecord.record
 | |
|                         : mainNormalizedRecord,
 | |
|                     // the aliases are always of the same kind as the original since they
 | |
|                     // are defined on the same record
 | |
|                 })));
 | |
|             }
 | |
|         }
 | |
|         let matcher;
 | |
|         let originalMatcher;
 | |
|         for (const normalizedRecord of normalizedRecords) {
 | |
|             const { path } = normalizedRecord;
 | |
|             // Build up the path for nested routes if the child isn't an absolute
 | |
|             // route. Only add the / delimiter if the child path isn't empty and if the
 | |
|             // parent path doesn't have a trailing slash
 | |
|             if (parent && path[0] !== '/') {
 | |
|                 const parentPath = parent.record.path;
 | |
|                 const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
 | |
|                 normalizedRecord.path =
 | |
|                     parent.record.path + (path && connectingSlash + path);
 | |
|             }
 | |
|             // create the object beforehand, so it can be passed to children
 | |
|             matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
 | |
|             // if we are an alias we must tell the original record that we exist,
 | |
|             // so we can be removed
 | |
|             if (originalRecord) {
 | |
|                 originalRecord.alias.push(matcher);
 | |
|             }
 | |
|             else {
 | |
|                 // otherwise, the first record is the original and others are aliases
 | |
|                 originalMatcher = originalMatcher || matcher;
 | |
|                 if (originalMatcher !== matcher)
 | |
|                     originalMatcher.alias.push(matcher);
 | |
|                 // remove the route if named and only for the top record (avoid in nested calls)
 | |
|                 // this works because the original record is the first one
 | |
|                 if (isRootAdd && record.name && !isAliasRecord(matcher)) {
 | |
|                     removeRoute(record.name);
 | |
|                 }
 | |
|             }
 | |
|             // Avoid adding a record that doesn't display anything. This allows passing through records without a component to
 | |
|             // not be reached and pass through the catch all route
 | |
|             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]);
 | |
|                 }
 | |
|             }
 | |
|             // if there was no original record, then the first one was not an alias and all
 | |
|             // other aliases (if any) need to reference this record when adding children
 | |
|             originalRecord = originalRecord || matcher;
 | |
|             // TODO: add normalized records for more flexibility
 | |
|             // if (parent && isAliasRecord(originalRecord)) {
 | |
|             //   parent.children.push(originalRecord)
 | |
|             // }
 | |
|         }
 | |
|         return originalMatcher
 | |
|             ? () => {
 | |
|                 // since other matchers are aliases, they should be removed by the original matcher
 | |
|                 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);
 | |
|         // only add the original record to the name map
 | |
|         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(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
 | |
|                     location,
 | |
|                 });
 | |
|             name = matcher.record.name;
 | |
|             params = assign(
 | |
|             // paramsFromLocation is a new object
 | |
|             paramsFromLocation(currentLocation.params, 
 | |
|             // only keep params that exist in the resolved location
 | |
|             // only keep optional params coming from a parent record
 | |
|             matcher.keys
 | |
|                 .filter(k => !k.optional)
 | |
|                 .concat(matcher.parent ? matcher.parent.keys.filter(k => k.optional) : [])
 | |
|                 .map(k => k.name)), 
 | |
|             // discard any existing params in the current location that do not exist here
 | |
|             // #1497 this ensures better active/exact matching
 | |
|             location.params &&
 | |
|                 paramsFromLocation(location.params, matcher.keys.map(k => k.name)));
 | |
|             // throws if cannot be stringified
 | |
|             path = matcher.stringify(params);
 | |
|         }
 | |
|         else if (location.path != null) {
 | |
|             // no need to resolve the path with the matcher as it was provided
 | |
|             // this also allows the user to control the encoding
 | |
|             path = location.path;
 | |
|             matcher = matchers.find(m => m.re.test(path));
 | |
|             // matcher should have a value after the loop
 | |
|             if (matcher) {
 | |
|                 // we know the matcher works because we tested the regexp
 | |
|                 params = matcher.parse(path);
 | |
|                 name = matcher.record.name;
 | |
|             }
 | |
|             // location is a relative path
 | |
|         }
 | |
|         else {
 | |
|             // match by name or path of current route
 | |
|             matcher = currentLocation.name
 | |
|                 ? matcherMap.get(currentLocation.name)
 | |
|                 : matchers.find(m => m.re.test(currentLocation.path));
 | |
|             if (!matcher)
 | |
|                 throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
 | |
|                     location,
 | |
|                     currentLocation,
 | |
|                 });
 | |
|             name = matcher.record.name;
 | |
|             // since we are navigating to the same location, we don't need to pick the
 | |
|             // params like when `name` is provided
 | |
|             params = assign({}, currentLocation.params, location.params);
 | |
|             path = matcher.stringify(params);
 | |
|         }
 | |
|         const matched = [];
 | |
|         let parentMatcher = matcher;
 | |
|         while (parentMatcher) {
 | |
|             // reversed order so parents are at the beginning
 | |
|             matched.unshift(parentMatcher.record);
 | |
|             parentMatcher = parentMatcher.parent;
 | |
|         }
 | |
|         return {
 | |
|             name,
 | |
|             path,
 | |
|             params,
 | |
|             matched,
 | |
|             meta: mergeMetaFields(matched),
 | |
|         };
 | |
|     }
 | |
|     // add initial routes
 | |
|     routes.forEach(route => addRoute(route));
 | |
|     function clearRoutes() {
 | |
|         matchers.length = 0;
 | |
|         matcherMap.clear();
 | |
|     }
 | |
|     return {
 | |
|         addRoute,
 | |
|         resolve,
 | |
|         removeRoute,
 | |
|         clearRoutes,
 | |
|         getRoutes,
 | |
|         getRecordMatcher,
 | |
|     };
 | |
| }
 | |
| function paramsFromLocation(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: new Set(),
 | |
|         updateGuards: new Set(),
 | |
|         enterCallbacks: {},
 | |
|         // must be declared afterwards
 | |
|         // mods: {},
 | |
|         components: 'components' in record
 | |
|             ? record.components || null
 | |
|             : record.component && { default: record.component },
 | |
|     };
 | |
|     // mods contain modules and shouldn't be copied,
 | |
|     // logged or anything. It's just used for internal
 | |
|     // advanced use cases like data loaders
 | |
|     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 = {};
 | |
|     // props does not exist on redirect records, but we can set false directly
 | |
|     const props = record.props || false;
 | |
|     if ('component' in record) {
 | |
|         propsObject.default = props;
 | |
|     }
 | |
|     else {
 | |
|         // NOTE: we could also allow a function to be applied to every component.
 | |
|         // Would need user feedback for use cases
 | |
|         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 mergeOptions(defaults, partialOptions) {
 | |
|     const options = {};
 | |
|     for (const key in defaults) {
 | |
|         options[key] = key in partialOptions ? partialOptions[key] : defaults[key];
 | |
|     }
 | |
|     return options;
 | |
| }
 | |
| /**
 | |
|  * 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) {
 | |
|     // First phase: binary search based on score
 | |
|     let lower = 0;
 | |
|     let upper = matchers.length;
 | |
|     while (lower !== upper) {
 | |
|         const mid = (lower + upper) >> 1;
 | |
|         const sortOrder = comparePathParserScore(matcher, matchers[mid]);
 | |
|         if (sortOrder < 0) {
 | |
|             upper = mid;
 | |
|         }
 | |
|         else {
 | |
|             lower = mid + 1;
 | |
|         }
 | |
|     }
 | |
|     // Second phase: check for an ancestor with the same score
 | |
|     const insertionAncestor = getInsertionAncestor(matcher);
 | |
|     if (insertionAncestor) {
 | |
|         upper = matchers.lastIndexOf(insertionAncestor, upper - 1);
 | |
|     }
 | |
|     return upper;
 | |
| }
 | |
| function getInsertionAncestor(matcher) {
 | |
|     let ancestor = matcher;
 | |
|     while ((ancestor = ancestor.parent)) {
 | |
|         if (isMatchable(ancestor) &&
 | |
|             comparePathParserScore(matcher, ancestor) === 0) {
 | |
|             return ancestor;
 | |
|         }
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| /**
 | |
|  * 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);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 = {};
 | |
|     // avoid creating an object with an empty key and empty value
 | |
|     // because of split('&')
 | |
|     if (search === '' || search === '?')
 | |
|         return query;
 | |
|     const hasLeadingIM = search[0] === '?';
 | |
|     const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&');
 | |
|     for (let i = 0; i < searchParams.length; ++i) {
 | |
|         // pre decode the + into space
 | |
|         const searchParam = searchParams[i].replace(PLUS_RE, ' ');
 | |
|         // allow the = character
 | |
|         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) {
 | |
|             // an extra variable for ts types
 | |
|             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) {
 | |
|             // only null adds the value
 | |
|             if (value !== undefined) {
 | |
|                 search += (search.length ? '&' : '') + key;
 | |
|             }
 | |
|             continue;
 | |
|         }
 | |
|         // keep null values
 | |
|         const values = isArray(value)
 | |
|             ? value.map(v => v && encodeQueryValue(v))
 | |
|             : [value && encodeQueryValue(value)];
 | |
|         values.forEach(value => {
 | |
|             // skip undefined values in arrays as if they were not present
 | |
|             // smaller code than using filter
 | |
|             if (value !== undefined) {
 | |
|                 // only append & with non-empty search
 | |
|                 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 !== undefined) {
 | |
|             normalizedQuery[key] = isArray(value)
 | |
|                 ? value.map(v => (v == null ? null : '' + v))
 | |
|                 : value == null
 | |
|                     ? value
 | |
|                     : '' + value;
 | |
|         }
 | |
|     }
 | |
|     return normalizedQuery;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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('');
 | |
| /**
 | |
|  * Allows overriding the router view depth to control which component in
 | |
|  * `matched` is rendered. rvd stands for Router View Depth
 | |
|  *
 | |
|  * @internal
 | |
|  */
 | |
| const viewDepthKey = Symbol('');
 | |
| /**
 | |
|  * Allows overriding the router instance returned by `useRouter` in tests. r
 | |
|  * stands for router
 | |
|  *
 | |
|  * @internal
 | |
|  */
 | |
| const routerKey = Symbol('');
 | |
| /**
 | |
|  * Allows overriding the current route returned by `useRoute` in tests. rl
 | |
|  * stands for route location
 | |
|  *
 | |
|  * @internal
 | |
|  */
 | |
| const routeLocationKey = Symbol('');
 | |
| /**
 | |
|  * Allows overriding the current route used by router-view. Internally this is
 | |
|  * used when the `route` prop is passed.
 | |
|  *
 | |
|  * @internal
 | |
|  */
 | |
| const routerViewLocationKey = Symbol('');
 | |
| 
 | |
| /**
 | |
|  * 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,
 | |
|     };
 | |
| }
 | |
| 
 | |
| function registerGuard(record, name, guard) {
 | |
|     const removeFromList = () => {
 | |
|         record[name].delete(guard);
 | |
|     };
 | |
|     vue.onUnmounted(removeFromList);
 | |
|     vue.onDeactivated(removeFromList);
 | |
|     vue.onActivated(() => {
 | |
|         record[name].add(guard);
 | |
|     });
 | |
|     record[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) {
 | |
|     const activeRecord = vue.inject(matchedRouteKey, 
 | |
|     // to avoid warning
 | |
|     {}).value;
 | |
|     if (!activeRecord) {
 | |
|         return;
 | |
|     }
 | |
|     registerGuard(activeRecord, '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) {
 | |
|     const activeRecord = vue.inject(matchedRouteKey, 
 | |
|     // to avoid warning
 | |
|     {}).value;
 | |
|     if (!activeRecord) {
 | |
|         return;
 | |
|     }
 | |
|     registerGuard(activeRecord, 'updateGuards', updateGuard);
 | |
| }
 | |
| function guardToPromiseFn(guard, to, from, record, name, runWithContext = fn => fn()) {
 | |
|     // keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place
 | |
|     const enterCallbackArray = record &&
 | |
|         // name is defined if record is because of the function overload
 | |
|         (record.enterCallbacks[name] = record.enterCallbacks[name] || []);
 | |
|     return () => new Promise((resolve, reject) => {
 | |
|         const next = (valid) => {
 | |
|             if (valid === false) {
 | |
|                 reject(createRouterError(4 /* ErrorTypes.NAVIGATION_ABORTED */, {
 | |
|                     from,
 | |
|                     to,
 | |
|                 }));
 | |
|             }
 | |
|             else if (valid instanceof Error) {
 | |
|                 reject(valid);
 | |
|             }
 | |
|             else if (isRouteLocation(valid)) {
 | |
|                 reject(createRouterError(2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */, {
 | |
|                     from: to,
 | |
|                     to: valid,
 | |
|                 }));
 | |
|             }
 | |
|             else {
 | |
|                 if (enterCallbackArray &&
 | |
|                     // since enterCallbackArray is truthy, both record and name also are
 | |
|                     record.enterCallbacks[name] === enterCallbackArray &&
 | |
|                     typeof valid === 'function') {
 | |
|                     enterCallbackArray.push(valid);
 | |
|                 }
 | |
|                 resolve();
 | |
|             }
 | |
|         };
 | |
|         // wrapping with Promise.resolve allows it to work with both async and sync guards
 | |
|         const guardReturn = runWithContext(() => guard.call(record && record.instances[name], to, from, next));
 | |
|         let guardCall = Promise.resolve(guardReturn);
 | |
|         if (guard.length < 3)
 | |
|             guardCall = guardCall.then(next);
 | |
|         guardCall.catch(err => reject(err));
 | |
|     });
 | |
| }
 | |
| function extractComponentsGuards(matched, guardType, to, from, runWithContext = fn => fn()) {
 | |
|     const guards = [];
 | |
|     for (const record of matched) {
 | |
|         for (const name in record.components) {
 | |
|             let rawComponent = record.components[name];
 | |
|             // skip update and leave guards if the route component is not mounted
 | |
|             if (guardType !== 'beforeRouteEnter' && !record.instances[name])
 | |
|                 continue;
 | |
|             if (isRouteComponent(rawComponent)) {
 | |
|                 // __vccOpts is added by vue-class-component and contain the regular options
 | |
|                 const options = rawComponent.__vccOpts || rawComponent;
 | |
|                 const guard = options[guardType];
 | |
|                 guard &&
 | |
|                     guards.push(guardToPromiseFn(guard, to, from, record, name, runWithContext));
 | |
|             }
 | |
|             else {
 | |
|                 // start requesting the chunk already
 | |
|                 let componentPromise = rawComponent();
 | |
|                 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;
 | |
|                     // keep the resolved module for plugins like data loaders
 | |
|                     record.mods[name] = resolved;
 | |
|                     // replace the function with the resolved component
 | |
|                     // cannot be null or undefined because we went into the for loop
 | |
|                     record.components[name] = resolvedComponent;
 | |
|                     // __vccOpts is added by vue-class-component and contain the regular options
 | |
|                     const options = resolvedComponent.__vccOpts || resolvedComponent;
 | |
|                     const guard = options[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 `<RouterView>`.
 | |
|  *
 | |
|  * @param route - resolved route to load
 | |
|  */
 | |
| function loadRouteLocation(route) {
 | |
|     return route.matched.every(record => record.redirect)
 | |
|         ? Promise.reject(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(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;
 | |
|                         // keep the resolved module for plugins like data loaders
 | |
|                         record.mods[name] = resolved;
 | |
|                         // replace the function with the resolved component
 | |
|                         // cannot be null or undefined because we went into the for loop
 | |
|                         record.components[name] = resolvedComponent;
 | |
|                         return;
 | |
|                     }));
 | |
|                 }
 | |
|                 return promises;
 | |
|             }, [])))).then(() => route);
 | |
| }
 | |
| 
 | |
| // TODO: we could allow currentRoute as a prop to expose `isActive` and
 | |
| // `isExactActive` behavior should go through an RFC
 | |
| /**
 | |
|  * 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 = vue.inject(routerKey);
 | |
|     const currentRoute = vue.inject(routeLocationKey);
 | |
|     const route = vue.computed(() => {
 | |
|         const to = vue.unref(props.to);
 | |
|         return router.resolve(to);
 | |
|     });
 | |
|     const activeRecordIndex = vue.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;
 | |
|         // possible parent record
 | |
|         const parentRecordPath = getOriginalPath(matched[length - 2]);
 | |
|         return (
 | |
|         // we are dealing with nested routes
 | |
|         length > 1 &&
 | |
|             // if the parent and matched route have the same path, this link is
 | |
|             // referring to the empty child. Or we currently are on a different
 | |
|             // child of the same parent
 | |
|             getOriginalPath(routeMatched) === parentRecordPath &&
 | |
|             // avoid comparing the child with its parent
 | |
|             currentMatched[currentMatched.length - 1].path !== parentRecordPath
 | |
|             ? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2]))
 | |
|             : index);
 | |
|     });
 | |
|     const isActive = vue.computed(() => activeRecordIndex.value > -1 &&
 | |
|         includesParams(currentRoute.params, route.value.params));
 | |
|     const isExactActive = vue.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[vue.unref(props.replace) ? 'replace' : 'push'](vue.unref(props.to)
 | |
|             // avoid uncaught errors are they are logged anyway
 | |
|             ).catch(noop);
 | |
|             if (props.viewTransition &&
 | |
|                 typeof document !== 'undefined' &&
 | |
|                 'startViewTransition' in document) {
 | |
|                 document.startViewTransition(() => p);
 | |
|             }
 | |
|             return p;
 | |
|         }
 | |
|         return Promise.resolve();
 | |
|     }
 | |
|     /**
 | |
|      * NOTE: update {@link _RouterLinkI}'s `$slots` type when updating this
 | |
|      */
 | |
|     return {
 | |
|         route,
 | |
|         href: vue.computed(() => route.value.href),
 | |
|         isActive,
 | |
|         isExactActive,
 | |
|         navigate,
 | |
|     };
 | |
| }
 | |
| function preferSingleVNode(vnodes) {
 | |
|     return vnodes.length === 1 ? vnodes[0] : vnodes;
 | |
| }
 | |
| const RouterLinkImpl = /*#__PURE__*/ vue.defineComponent({
 | |
|     name: 'RouterLink',
 | |
|     compatConfig: { MODE: 3 },
 | |
|     props: {
 | |
|         to: {
 | |
|             type: [String, Object],
 | |
|             required: true,
 | |
|         },
 | |
|         replace: Boolean,
 | |
|         activeClass: String,
 | |
|         // inactiveClass: String,
 | |
|         exactActiveClass: String,
 | |
|         custom: Boolean,
 | |
|         ariaCurrentValue: {
 | |
|             type: String,
 | |
|             default: 'page',
 | |
|         },
 | |
|         viewTransition: Boolean,
 | |
|     },
 | |
|     useLink,
 | |
|     setup(props, { slots }) {
 | |
|         const link = vue.reactive(useLink(props));
 | |
|         const { options } = vue.inject(routerKey);
 | |
|         const elClass = vue.computed(() => ({
 | |
|             [getLinkClass(props.activeClass, options.linkActiveClass, 'router-link-active')]: link.isActive,
 | |
|             // [getLinkClass(
 | |
|             //   props.inactiveClass,
 | |
|             //   options.linkInactiveClass,
 | |
|             //   'router-link-inactive'
 | |
|             // )]: !link.isExactActive,
 | |
|             [getLinkClass(props.exactActiveClass, options.linkExactActiveClass, 'router-link-exact-active')]: link.isExactActive,
 | |
|         }));
 | |
|         return () => {
 | |
|             const children = slots.default && preferSingleVNode(slots.default(link));
 | |
|             return props.custom
 | |
|                 ? children
 | |
|                 : vue.h('a', {
 | |
|                     'aria-current': link.isExactActive
 | |
|                         ? props.ariaCurrentValue
 | |
|                         : null,
 | |
|                     href: link.href,
 | |
|                     // this would override user added attrs but Vue will still add
 | |
|                     // the listener, so we end up triggering both
 | |
|                     onClick: link.navigate,
 | |
|                     class: elClass.value,
 | |
|                 }, children);
 | |
|         };
 | |
|     },
 | |
| });
 | |
| // export the public type for h/tsx inference
 | |
| // also to avoid inline import() in generated d.ts files
 | |
| /**
 | |
|  * Component to render a link that triggers a navigation on click.
 | |
|  */
 | |
| const RouterLink = RouterLinkImpl;
 | |
| function guardEvent(e) {
 | |
|     // don't redirect with control keys
 | |
|     if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
 | |
|         return;
 | |
|     // don't redirect when preventDefault called
 | |
|     if (e.defaultPrevented)
 | |
|         return;
 | |
|     // don't redirect on right click
 | |
|     if (e.button !== undefined && e.button !== 0)
 | |
|         return;
 | |
|     // don't redirect if `target="_blank"`
 | |
|     // @ts-expect-error getAttribute does exist
 | |
|     if (e.currentTarget && e.currentTarget.getAttribute) {
 | |
|         // @ts-expect-error getAttribute exists
 | |
|         const target = e.currentTarget.getAttribute('target');
 | |
|         if (/\b_blank\b/i.test(target))
 | |
|             return;
 | |
|     }
 | |
|     // this may be a Weex event which doesn't have this method
 | |
|     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 !== outerValue[i]))
 | |
|                 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;
 | |
| 
 | |
| const RouterViewImpl = /*#__PURE__*/ vue.defineComponent({
 | |
|     name: 'RouterView',
 | |
|     // #674 we manually inherit them
 | |
|     inheritAttrs: false,
 | |
|     props: {
 | |
|         name: {
 | |
|             type: String,
 | |
|             default: 'default',
 | |
|         },
 | |
|         route: Object,
 | |
|     },
 | |
|     // Better compat for @vue/compat users
 | |
|     // https://github.com/vuejs/router/issues/1315
 | |
|     compatConfig: { MODE: 3 },
 | |
|     setup(props, { attrs, slots }) {
 | |
|         const injectedRoute = vue.inject(routerViewLocationKey);
 | |
|         const routeToDisplay = vue.computed(() => props.route || injectedRoute.value);
 | |
|         const injectedDepth = vue.inject(viewDepthKey, 0);
 | |
|         // The depth changes based on empty components option, which allows passthrough routes e.g. routes with children
 | |
|         // that are used to reuse the `path` property
 | |
|         const depth = vue.computed(() => {
 | |
|             let initialDepth = vue.unref(injectedDepth);
 | |
|             const { matched } = routeToDisplay.value;
 | |
|             let matchedRoute;
 | |
|             while ((matchedRoute = matched[initialDepth]) &&
 | |
|                 !matchedRoute.components) {
 | |
|                 initialDepth++;
 | |
|             }
 | |
|             return initialDepth;
 | |
|         });
 | |
|         const matchedRouteRef = vue.computed(() => routeToDisplay.value.matched[depth.value]);
 | |
|         vue.provide(viewDepthKey, vue.computed(() => depth.value + 1));
 | |
|         vue.provide(matchedRouteKey, matchedRouteRef);
 | |
|         vue.provide(routerViewLocationKey, routeToDisplay);
 | |
|         const viewRef = vue.ref();
 | |
|         // watch at the same time the component instance, the route record we are
 | |
|         // rendering, and the name
 | |
|         vue.watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {
 | |
|             // copy reused instances
 | |
|             if (to) {
 | |
|                 // this will update the instance for new instances as well as reused
 | |
|                 // instances when navigating to a new route
 | |
|                 to.instances[name] = instance;
 | |
|                 // the component instance is reused for a different route or name, so
 | |
|                 // we copy any saved update or leave guards. With async setup, the
 | |
|                 // mounting component will mount before the matchedRoute changes,
 | |
|                 // making instance === oldInstance, so we check if guards have been
 | |
|                 // added before. This works because we remove guards when
 | |
|                 // unmounting/deactivating components
 | |
|                 if (from && from !== to && instance && instance === oldInstance) {
 | |
|                     if (!to.leaveGuards.size) {
 | |
|                         to.leaveGuards = from.leaveGuards;
 | |
|                     }
 | |
|                     if (!to.updateGuards.size) {
 | |
|                         to.updateGuards = from.updateGuards;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             // trigger beforeRouteEnter next callbacks
 | |
|             if (instance &&
 | |
|                 to &&
 | |
|                 // if there is no instance but to and from are the same this might be
 | |
|                 // the first visit
 | |
|                 (!from || !isSameRouteRecord(to, from) || !oldInstance)) {
 | |
|                 (to.enterCallbacks[name] || []).forEach(callback => callback(instance));
 | |
|             }
 | |
|         }, { flush: 'post' });
 | |
|         return () => {
 | |
|             const route = routeToDisplay.value;
 | |
|             // we need the value at the time we render because when we unmount, we
 | |
|             // navigated to a different location so the value is different
 | |
|             const currentName = props.name;
 | |
|             const matchedRoute = matchedRouteRef.value;
 | |
|             const ViewComponent = matchedRoute && matchedRoute.components[currentName];
 | |
|             if (!ViewComponent) {
 | |
|                 return normalizeSlot(slots.default, { Component: ViewComponent, route });
 | |
|             }
 | |
|             // props from route configuration
 | |
|             const routePropsOption = matchedRoute.props[currentName];
 | |
|             const routeProps = routePropsOption
 | |
|                 ? routePropsOption === true
 | |
|                     ? route.params
 | |
|                     : typeof routePropsOption === 'function'
 | |
|                         ? routePropsOption(route)
 | |
|                         : routePropsOption
 | |
|                 : null;
 | |
|             const onVnodeUnmounted = vnode => {
 | |
|                 // remove the instance reference to prevent leak
 | |
|                 if (vnode.component.isUnmounted) {
 | |
|                     matchedRoute.instances[currentName] = null;
 | |
|                 }
 | |
|             };
 | |
|             const component = vue.h(ViewComponent, assign({}, routeProps, attrs, {
 | |
|                 onVnodeUnmounted,
 | |
|                 ref: viewRef,
 | |
|             }));
 | |
|             return (
 | |
|             // pass the vnode to the slot as a prop.
 | |
|             // h and <component :is="..."> both accept vnodes
 | |
|             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;
 | |
| }
 | |
| // export the public type for h/tsx inference
 | |
| // also to avoid inline import() in generated d.ts files
 | |
| /**
 | |
|  * Component to display the current route the user is at.
 | |
|  */
 | |
| const RouterView = RouterViewImpl;
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
|     const beforeGuards = useCallbacks();
 | |
|     const beforeResolveGuards = useCallbacks();
 | |
|     const afterGuards = useCallbacks();
 | |
|     const currentRoute = vue.shallowRef(START_LOCATION_NORMALIZED);
 | |
|     let pendingLocation = START_LOCATION_NORMALIZED;
 | |
|     // leave the scrollRestoration if no scrollBehavior is provided
 | |
|     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 = 
 | |
|     // @ts-expect-error: intentionally avoid the type check
 | |
|     applyToParams.bind(null, decode);
 | |
|     function addRoute(parentOrRoute, route) {
 | |
|         let parent;
 | |
|         let record;
 | |
|         if (isRouteName(parentOrRoute)) {
 | |
|             parent = matcher.getRecordMatcher(parentOrRoute);
 | |
|             record = route;
 | |
|         }
 | |
|         else {
 | |
|             record = parentOrRoute;
 | |
|         }
 | |
|         return matcher.addRoute(record, parent);
 | |
|     }
 | |
|     function removeRoute(name) {
 | |
|         const recordMatcher = matcher.getRecordMatcher(name);
 | |
|         if (recordMatcher) {
 | |
|             matcher.removeRoute(recordMatcher);
 | |
|         }
 | |
|     }
 | |
|     function getRoutes() {
 | |
|         return matcher.getRoutes().map(routeMatcher => routeMatcher.record);
 | |
|     }
 | |
|     function hasRoute(name) {
 | |
|         return !!matcher.getRecordMatcher(name);
 | |
|     }
 | |
|     function resolve(rawLocation, currentLocation) {
 | |
|         // const resolve: Router['resolve'] = (rawLocation: RouteLocationRaw, currentLocation) => {
 | |
|         // const objectLocation = routerLocationAsObject(rawLocation)
 | |
|         // we create a copy to modify it later
 | |
|         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);
 | |
|             // locationNormalized is always a new object
 | |
|             return assign(locationNormalized, matchedRoute, {
 | |
|                 params: decodeParams(matchedRoute.params),
 | |
|                 hash: decode(locationNormalized.hash),
 | |
|                 redirectedFrom: undefined,
 | |
|                 href,
 | |
|             });
 | |
|         }
 | |
|         let matcherLocation;
 | |
|         // path could be relative in object as well
 | |
|         if (rawLocation.path != null) {
 | |
|             matcherLocation = assign({}, rawLocation, {
 | |
|                 path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path,
 | |
|             });
 | |
|         }
 | |
|         else {
 | |
|             // remove any nullish param
 | |
|             const targetParams = assign({}, rawLocation.params);
 | |
|             for (const key in targetParams) {
 | |
|                 if (targetParams[key] == null) {
 | |
|                     delete targetParams[key];
 | |
|                 }
 | |
|             }
 | |
|             // pass encoded values to the matcher, so it can produce encoded path and fullPath
 | |
|             matcherLocation = assign({}, rawLocation, {
 | |
|                 params: encodeParams(targetParams),
 | |
|             });
 | |
|             // current location params are decoded, we need to encode them in case the
 | |
|             // matcher merges the params
 | |
|             currentLocation.params = encodeParams(currentLocation.params);
 | |
|         }
 | |
|         const matchedRoute = matcher.resolve(matcherLocation, currentLocation);
 | |
|         const hash = rawLocation.hash || '';
 | |
|         // the matcher might have merged current location params, so
 | |
|         // we need to run the decoding again
 | |
|         matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
 | |
|         const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
 | |
|             hash: encodeHash(hash),
 | |
|             path: matchedRoute.path,
 | |
|         }));
 | |
|         const href = routerHistory.createHref(fullPath);
 | |
|         return assign({
 | |
|             fullPath,
 | |
|             // keep the hash encoded so fullPath is effectively path + encodedQuery +
 | |
|             // hash
 | |
|             hash,
 | |
|             query: 
 | |
|             // if the user is using a custom query lib like qs, we might have
 | |
|             // nested objects, so we keep the query as is, meaning it can contain
 | |
|             // numbers at `$route.query`, but at the point, the user will have to
 | |
|             // use their own type anyway.
 | |
|             // https://github.com/vuejs/router/issues/328#issuecomment-649481567
 | |
|             stringifyQuery$1 === stringifyQuery
 | |
|                 ? normalizeQuery(rawLocation.query)
 | |
|                 : (rawLocation.query || {}),
 | |
|         }, matchedRoute, {
 | |
|             redirectedFrom: undefined,
 | |
|             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(8 /* ErrorTypes.NAVIGATION_CANCELLED */, {
 | |
|                 from,
 | |
|                 to,
 | |
|             });
 | |
|         }
 | |
|     }
 | |
|     function push(to) {
 | |
|         return pushWithRedirect(to);
 | |
|     }
 | |
|     function replace(to) {
 | |
|         return push(assign(locationAsObject(to), { replace: true }));
 | |
|     }
 | |
|     function handleRedirectRecord(to) {
 | |
|         const lastMatched = to.matched[to.matched.length - 1];
 | |
|         if (lastMatched && lastMatched.redirect) {
 | |
|             const { redirect } = lastMatched;
 | |
|             let newTargetLocation = typeof redirect === 'function' ? redirect(to) : redirect;
 | |
|             if (typeof newTargetLocation === 'string') {
 | |
|                 newTargetLocation =
 | |
|                     newTargetLocation.includes('?') || newTargetLocation.includes('#')
 | |
|                         ? (newTargetLocation = locationAsObject(newTargetLocation))
 | |
|                         : // force empty params
 | |
|                             { path: newTargetLocation };
 | |
|                 // @ts-expect-error: force empty params when a string is passed to let
 | |
|                 // the router parse them again
 | |
|                 newTargetLocation.params = {};
 | |
|             }
 | |
|             return assign({
 | |
|                 query: to.query,
 | |
|                 hash: to.hash,
 | |
|                 // avoid transferring params if the redirect has a path
 | |
|                 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;
 | |
|         // to could be a string where `replace` is a function
 | |
|         const replace = to.replace === true;
 | |
|         const shouldRedirect = handleRedirectRecord(targetLocation);
 | |
|         if (shouldRedirect)
 | |
|             return pushWithRedirect(assign(locationAsObject(shouldRedirect), {
 | |
|                 state: typeof shouldRedirect === 'object'
 | |
|                     ? assign({}, data, shouldRedirect.state)
 | |
|                     : data,
 | |
|                 force,
 | |
|                 replace,
 | |
|             }), 
 | |
|             // keep original redirectedFrom if it exists
 | |
|             redirectedFrom || targetLocation);
 | |
|         // if it was a redirect we already called `pushWithRedirect` above
 | |
|         const toLocation = targetLocation;
 | |
|         toLocation.redirectedFrom = redirectedFrom;
 | |
|         let failure;
 | |
|         if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
 | |
|             failure = createRouterError(16 /* ErrorTypes.NAVIGATION_DUPLICATED */, { to: toLocation, from });
 | |
|             // trigger scroll to allow scrolling to the same anchor
 | |
|             handleScroll(from, from, 
 | |
|             // this is a push, the only way for it to be triggered from a
 | |
|             // history.listen is with a redirect, which makes it become a push
 | |
|             true, 
 | |
|             // This cannot be the first navigation because the initial location
 | |
|             // cannot be manually navigated to
 | |
|             false);
 | |
|         }
 | |
|         return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
 | |
|             .catch((error) => isNavigationFailure(error)
 | |
|             ? // navigation redirects still mark the router as ready
 | |
|                 isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)
 | |
|                     ? error
 | |
|                     : markAsReady(error) // also returns the error
 | |
|             : // reject any unknown error
 | |
|                 triggerError(error, toLocation, from))
 | |
|             .then((failure) => {
 | |
|             if (failure) {
 | |
|                 if (isNavigationFailure(failure, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
 | |
|                     return pushWithRedirect(
 | |
|                     // keep options
 | |
|                     assign({
 | |
|                         // preserve an existing replacement but allow the redirect to override it
 | |
|                         replace,
 | |
|                     }, locationAsObject(failure.to), {
 | |
|                         state: typeof failure.to === 'object'
 | |
|                             ? assign({}, data, failure.to.state)
 | |
|                             : data,
 | |
|                         force,
 | |
|                     }), 
 | |
|                     // preserve the original redirectedFrom if any
 | |
|                     redirectedFrom || toLocation);
 | |
|                 }
 | |
|             }
 | |
|             else {
 | |
|                 // if we fail we don't finalize the navigation
 | |
|                 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;
 | |
|         // support Vue < 3.3
 | |
|         return app && typeof app.runWithContext === 'function'
 | |
|             ? app.runWithContext(fn)
 | |
|             : fn();
 | |
|     }
 | |
|     // TODO: refactor the whole before guards by internally using router.beforeEach
 | |
|     function navigate(to, from) {
 | |
|         let guards;
 | |
|         const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from);
 | |
|         // all components here have been resolved once because we are leaving
 | |
|         guards = extractComponentsGuards(leavingRecords.reverse(), 'beforeRouteLeave', to, from);
 | |
|         // leavingRecords is already reversed
 | |
|         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);
 | |
|         // run the queue of per route beforeRouteLeave guards
 | |
|         return (runGuardQueue(guards)
 | |
|             .then(() => {
 | |
|             // check global guards beforeEach
 | |
|             guards = [];
 | |
|             for (const guard of beforeGuards.list()) {
 | |
|                 guards.push(guardToPromiseFn(guard, to, from));
 | |
|             }
 | |
|             guards.push(canceledNavigationCheck);
 | |
|             return runGuardQueue(guards);
 | |
|         })
 | |
|             .then(() => {
 | |
|             // check in components beforeRouteUpdate
 | |
|             guards = extractComponentsGuards(updatingRecords, 'beforeRouteUpdate', to, from);
 | |
|             for (const record of updatingRecords) {
 | |
|                 record.updateGuards.forEach(guard => {
 | |
|                     guards.push(guardToPromiseFn(guard, to, from));
 | |
|                 });
 | |
|             }
 | |
|             guards.push(canceledNavigationCheck);
 | |
|             // run the queue of per route beforeEnter guards
 | |
|             return runGuardQueue(guards);
 | |
|         })
 | |
|             .then(() => {
 | |
|             // check the route beforeEnter
 | |
|             guards = [];
 | |
|             for (const record of enteringRecords) {
 | |
|                 // do not trigger beforeEnter on reused views
 | |
|                 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);
 | |
|             // run the queue of per route beforeEnter guards
 | |
|             return runGuardQueue(guards);
 | |
|         })
 | |
|             .then(() => {
 | |
|             // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
 | |
|             // clear existing enterCallbacks, these are added by extractComponentsGuards
 | |
|             to.matched.forEach(record => (record.enterCallbacks = {}));
 | |
|             // check in-component beforeRouteEnter
 | |
|             guards = extractComponentsGuards(enteringRecords, 'beforeRouteEnter', to, from, runWithContext);
 | |
|             guards.push(canceledNavigationCheck);
 | |
|             // run the queue of per route beforeEnter guards
 | |
|             return runGuardQueue(guards);
 | |
|         })
 | |
|             .then(() => {
 | |
|             // check global guards beforeResolve
 | |
|             guards = [];
 | |
|             for (const guard of beforeResolveGuards.list()) {
 | |
|                 guards.push(guardToPromiseFn(guard, to, from));
 | |
|             }
 | |
|             guards.push(canceledNavigationCheck);
 | |
|             return runGuardQueue(guards);
 | |
|         })
 | |
|             // catch any navigation canceled
 | |
|             .catch(err => isNavigationFailure(err, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)
 | |
|             ? err
 | |
|             : Promise.reject(err)));
 | |
|     }
 | |
|     function triggerAfterEach(to, from, failure) {
 | |
|         // navigation is confirmed, call afterGuards
 | |
|         // TODO: wrap with error handlers
 | |
|         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) {
 | |
|         // a more recent navigation took place
 | |
|         const error = checkCanceledNavigation(toLocation, from);
 | |
|         if (error)
 | |
|             return error;
 | |
|         // only consider as push if it's not the first navigation
 | |
|         const isFirstNavigation = from === START_LOCATION_NORMALIZED;
 | |
|         const state = !isBrowser ? {} : history.state;
 | |
|         // change URL only if the user did a push/replace and if it's not the initial navigation because
 | |
|         // it's just reflecting the url
 | |
|         if (isPush) {
 | |
|             // on the initial navigation, we want to reuse the scroll position from
 | |
|             // history state if it exists
 | |
|             if (replace || isFirstNavigation)
 | |
|                 routerHistory.replace(toLocation.fullPath, assign({
 | |
|                     scroll: isFirstNavigation && state && state.scroll,
 | |
|                 }, data));
 | |
|             else
 | |
|                 routerHistory.push(toLocation.fullPath, data);
 | |
|         }
 | |
|         // accept current navigation
 | |
|         currentRoute.value = toLocation;
 | |
|         handleScroll(toLocation, from, isPush, isFirstNavigation);
 | |
|         markAsReady();
 | |
|     }
 | |
|     let removeHistoryListener;
 | |
|     // attach listener to history to trigger navigations
 | |
|     function setupListeners() {
 | |
|         // avoid setting up listeners twice due to an invalid first navigation
 | |
|         if (removeHistoryListener)
 | |
|             return;
 | |
|         removeHistoryListener = routerHistory.listen((to, _from, info) => {
 | |
|             if (!router.listening)
 | |
|                 return;
 | |
|             // cannot be a redirect route because it was in history
 | |
|             const toLocation = resolve(to);
 | |
|             // due to dynamic routing, and to hash history with manual navigation
 | |
|             // (manually changing the url or calling history.hash = '#/somewhere'),
 | |
|             // there could be a redirect record in history
 | |
|             const shouldRedirect = handleRedirectRecord(toLocation);
 | |
|             if (shouldRedirect) {
 | |
|                 pushWithRedirect(assign(shouldRedirect, { replace: true, force: true }), toLocation).catch(noop);
 | |
|                 return;
 | |
|             }
 | |
|             pendingLocation = toLocation;
 | |
|             const from = currentRoute.value;
 | |
|             // TODO: should be moved to web history?
 | |
|             if (isBrowser) {
 | |
|                 saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
 | |
|             }
 | |
|             navigate(toLocation, from)
 | |
|                 .catch((error) => {
 | |
|                 if (isNavigationFailure(error, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
 | |
|                     return error;
 | |
|                 }
 | |
|                 if (isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
 | |
|                     // Here we could call if (info.delta) routerHistory.go(-info.delta,
 | |
|                     // false) but this is bug prone as we have no way to wait the
 | |
|                     // navigation to be finished before calling pushWithRedirect. Using
 | |
|                     // a setTimeout of 16ms seems to work but there is no guarantee for
 | |
|                     // it to work on every browser. So instead we do not restore the
 | |
|                     // history entry and trigger a new navigation as requested by the
 | |
|                     // navigation guard.
 | |
|                     // the error is already handled by router.push we just want to avoid
 | |
|                     // logging the error
 | |
|                     pushWithRedirect(assign(locationAsObject(error.to), {
 | |
|                         force: true,
 | |
|                     }), toLocation
 | |
|                     // avoid an uncaught rejection, let push call triggerError
 | |
|                     )
 | |
|                         .then(failure => {
 | |
|                         // manual change in hash history #916 ending up in the URL not
 | |
|                         // changing, but it was changed by the manual url change, so we
 | |
|                         // need to manually change it ourselves
 | |
|                         if (isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ |
 | |
|                             16 /* ErrorTypes.NAVIGATION_DUPLICATED */) &&
 | |
|                             !info.delta &&
 | |
|                             info.type === NavigationType.pop) {
 | |
|                             routerHistory.go(-1, false);
 | |
|                         }
 | |
|                     })
 | |
|                         .catch(noop);
 | |
|                     // avoid the then branch
 | |
|                     return Promise.reject();
 | |
|                 }
 | |
|                 // do not restore history on unknown direction
 | |
|                 if (info.delta) {
 | |
|                     routerHistory.go(-info.delta, false);
 | |
|                 }
 | |
|                 // unrecognized error, transfer to the global handler
 | |
|                 return triggerError(error, toLocation, from);
 | |
|             })
 | |
|                 .then((failure) => {
 | |
|                 failure =
 | |
|                     failure ||
 | |
|                         finalizeNavigation(
 | |
|                         // after navigation, all matched components are resolved
 | |
|                         toLocation, from, false);
 | |
|                 // revert the navigation
 | |
|                 if (failure) {
 | |
|                     if (info.delta &&
 | |
|                         // a new navigation has been triggered, so we do not want to revert, that will change the current history
 | |
|                         // entry while a different route is displayed
 | |
|                         !isNavigationFailure(failure, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
 | |
|                         routerHistory.go(-info.delta, false);
 | |
|                     }
 | |
|                     else if (info.type === NavigationType.pop &&
 | |
|                         isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 16 /* ErrorTypes.NAVIGATION_DUPLICATED */)) {
 | |
|                         // manual change in hash history #916
 | |
|                         // it's like a push but lacks the information of the direction
 | |
|                         routerHistory.go(-1, false);
 | |
|                     }
 | |
|                 }
 | |
|                 triggerAfterEach(toLocation, from, failure);
 | |
|             })
 | |
|                 // avoid warnings in the console about uncaught rejections, they are logged by triggerErrors
 | |
|                 .catch(noop);
 | |
|         });
 | |
|     }
 | |
|     // Initialization and Errors
 | |
|     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 {
 | |
|             console.error(error);
 | |
|         }
 | |
|         // reject the error no matter there were error listeners or not
 | |
|         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) {
 | |
|             // still not ready if an error happened
 | |
|             ready = !err;
 | |
|             setupListeners();
 | |
|             readyHandlers
 | |
|                 .list()
 | |
|                 .forEach(([resolve, reject]) => (err ? reject(err) : resolve()));
 | |
|             readyHandlers.reset();
 | |
|         }
 | |
|         return err;
 | |
|     }
 | |
|     // Scroll behavior
 | |
|     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 vue.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 = 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) {
 | |
|             const router = this;
 | |
|             app.component('RouterLink', RouterLink);
 | |
|             app.component('RouterView', RouterView);
 | |
|             app.config.globalProperties.$router = router;
 | |
|             Object.defineProperty(app.config.globalProperties, '$route', {
 | |
|                 enumerable: true,
 | |
|                 get: () => vue.unref(currentRoute),
 | |
|             });
 | |
|             // this initial navigation is only necessary on client, on server it doesn't
 | |
|             // make sense because it will create an extra unnecessary navigation and could
 | |
|             // lead to problems
 | |
|             if (isBrowser &&
 | |
|                 // used for the initial navigation client side to avoid pushing
 | |
|                 // multiple times when the router is used in multiple apps
 | |
|                 !started &&
 | |
|                 currentRoute.value === START_LOCATION_NORMALIZED) {
 | |
|                 // see above
 | |
|                 started = true;
 | |
|                 push(routerHistory.location).catch(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, vue.shallowReactive(reactiveRoute));
 | |
|             app.provide(routerViewLocationKey, currentRoute);
 | |
|             const unmountApp = app.unmount;
 | |
|             installedApps.add(app);
 | |
|             app.unmount = function () {
 | |
|                 installedApps.delete(app);
 | |
|                 // the router is not attached to an app anymore
 | |
|                 if (installedApps.size < 1) {
 | |
|                     // invalidate the current navigation
 | |
|                     pendingLocation = START_LOCATION_NORMALIZED;
 | |
|                     removeHistoryListener && removeHistoryListener();
 | |
|                     removeHistoryListener = null;
 | |
|                     currentRoute.value = START_LOCATION_NORMALIZED;
 | |
|                     started = false;
 | |
|                     ready = false;
 | |
|                 }
 | |
|                 unmountApp();
 | |
|             };
 | |
|         },
 | |
|     };
 | |
|     // TODO: type this as NavigationGuardReturn or similar instead of any
 | |
|     function runGuardQueue(guards) {
 | |
|         return guards.reduce((promise, guard) => promise.then(() => runWithContext(guard)), Promise.resolve());
 | |
|     }
 | |
|     return router;
 | |
| }
 | |
| 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) {
 | |
|             // the type doesn't matter because we are comparing per reference
 | |
|             if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {
 | |
|                 enteringRecords.push(recordTo);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return [leavingRecords, updatingRecords, enteringRecords];
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Returns the router instance. Equivalent to using `$router` inside
 | |
|  * templates.
 | |
|  */
 | |
| function useRouter() {
 | |
|     return vue.inject(routerKey);
 | |
| }
 | |
| /**
 | |
|  * Returns the current route location. Equivalent to using `$route` inside
 | |
|  * templates.
 | |
|  */
 | |
| function useRoute(_name) {
 | |
|     return vue.inject(routeLocationKey);
 | |
| }
 | |
| 
 | |
| exports.RouterLink = RouterLink;
 | |
| exports.RouterView = RouterView;
 | |
| exports.START_LOCATION = START_LOCATION_NORMALIZED;
 | |
| exports.createMemoryHistory = createMemoryHistory;
 | |
| exports.createRouter = createRouter;
 | |
| exports.createRouterMatcher = createRouterMatcher;
 | |
| exports.createWebHashHistory = createWebHashHistory;
 | |
| exports.createWebHistory = createWebHistory;
 | |
| exports.isNavigationFailure = isNavigationFailure;
 | |
| exports.loadRouteLocation = loadRouteLocation;
 | |
| exports.matchedRouteKey = matchedRouteKey;
 | |
| exports.onBeforeRouteLeave = onBeforeRouteLeave;
 | |
| exports.onBeforeRouteUpdate = onBeforeRouteUpdate;
 | |
| exports.parseQuery = parseQuery;
 | |
| exports.routeLocationKey = routeLocationKey;
 | |
| exports.routerKey = routerKey;
 | |
| exports.routerViewLocationKey = routerViewLocationKey;
 | |
| exports.stringifyQuery = stringifyQuery;
 | |
| exports.useLink = useLink;
 | |
| exports.useRoute = useRoute;
 | |
| exports.useRouter = useRouter;
 | |
| exports.viewDepthKey = viewDepthKey;
 |