UNPKG

153 kBJavaScriptView Raw
1/*!
2 * vue-router v4.5.0
3 * (c) 2024 Eduardo San Martin Morote
4 * @license MIT
5 */
6import { getCurrentInstance, inject, onUnmounted, onDeactivated, onActivated, computed, unref, watchEffect, defineComponent, reactive, h, provide, ref, watch, shallowRef, shallowReactive, nextTick } from 'vue';
7import { setupDevtoolsPlugin } from '@vue/devtools-api';
8
9const isBrowser = typeof document !== 'undefined';
10
11/**
12 * Allows differentiating lazy components from functional components and vue-class-component
13 * @internal
14 *
15 * @param component
16 */
17function isRouteComponent(component) {
18 return (typeof component === 'object' ||
19 'displayName' in component ||
20 'props' in component ||
21 '__vccOpts' in component);
22}
23function isESModule(obj) {
24 return (obj.__esModule ||
25 obj[Symbol.toStringTag] === 'Module' ||
26 // support CF with dynamic imports that do not
27 // add the Module string tag
28 (obj.default && isRouteComponent(obj.default)));
29}
30const assign = Object.assign;
31function applyToParams(fn, params) {
32 const newParams = {};
33 for (const key in params) {
34 const value = params[key];
35 newParams[key] = isArray(value)
36 ? value.map(fn)
37 : fn(value);
38 }
39 return newParams;
40}
41const noop = () => { };
42/**
43 * Typesafe alternative to Array.isArray
44 * https://github.com/microsoft/TypeScript/pull/48228
45 */
46const isArray = Array.isArray;
47
48function warn(msg) {
49 // avoid using ...args as it breaks in older Edge builds
50 const args = Array.from(arguments).slice(1);
51 console.warn.apply(console, ['[Vue Router warn]: ' + msg].concat(args));
52}
53
54/**
55 * Encoding Rules (␣ = Space)
56 * - Path: ␣ " < > # ? { }
57 * - Query: ␣ " < > # & =
58 * - Hash: ␣ " < > `
59 *
60 * On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2)
61 * defines some extra characters to be encoded. Most browsers do not encode them
62 * in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to
63 * also encode `!'()*`. Leaving un-encoded only ASCII alphanumeric(`a-zA-Z0-9`)
64 * plus `-._~`. This extra safety should be applied to query by patching the
65 * string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\`
66 * should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\`
67 * into a `/` if directly typed in. The _backtick_ (`````) should also be
68 * encoded everywhere because some browsers like FF encode it when directly
69 * written while others don't. Safari and IE don't encode ``"<>{}``` in hash.
70 */
71// const EXTRA_RESERVED_RE = /[!'()*]/g
72// const encodeReservedReplacer = (c: string) => '%' + c.charCodeAt(0).toString(16)
73const HASH_RE = /#/g; // %23
74const AMPERSAND_RE = /&/g; // %26
75const SLASH_RE = /\//g; // %2F
76const EQUAL_RE = /=/g; // %3D
77const IM_RE = /\?/g; // %3F
78const PLUS_RE = /\+/g; // %2B
79/**
80 * NOTE: It's not clear to me if we should encode the + symbol in queries, it
81 * seems to be less flexible than not doing so and I can't find out the legacy
82 * systems requiring this for regular requests like text/html. In the standard,
83 * the encoding of the plus character is only mentioned for
84 * application/x-www-form-urlencoded
85 * (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo
86 * leave the plus character as is in queries. To be more flexible, we allow the
87 * plus character on the query, but it can also be manually encoded by the user.
88 *
89 * Resources:
90 * - https://url.spec.whatwg.org/#urlencoded-parsing
91 * - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20
92 */
93const ENC_BRACKET_OPEN_RE = /%5B/g; // [
94const ENC_BRACKET_CLOSE_RE = /%5D/g; // ]
95const ENC_CARET_RE = /%5E/g; // ^
96const ENC_BACKTICK_RE = /%60/g; // `
97const ENC_CURLY_OPEN_RE = /%7B/g; // {
98const ENC_PIPE_RE = /%7C/g; // |
99const ENC_CURLY_CLOSE_RE = /%7D/g; // }
100const ENC_SPACE_RE = /%20/g; // }
101/**
102 * Encode characters that need to be encoded on the path, search and hash
103 * sections of the URL.
104 *
105 * @internal
106 * @param text - string to encode
107 * @returns encoded string
108 */
109function commonEncode(text) {
110 return encodeURI('' + text)
111 .replace(ENC_PIPE_RE, '|')
112 .replace(ENC_BRACKET_OPEN_RE, '[')
113 .replace(ENC_BRACKET_CLOSE_RE, ']');
114}
115/**
116 * Encode characters that need to be encoded on the hash section of the URL.
117 *
118 * @param text - string to encode
119 * @returns encoded string
120 */
121function encodeHash(text) {
122 return commonEncode(text)
123 .replace(ENC_CURLY_OPEN_RE, '{')
124 .replace(ENC_CURLY_CLOSE_RE, '}')
125 .replace(ENC_CARET_RE, '^');
126}
127/**
128 * Encode characters that need to be encoded query values on the query
129 * section of the URL.
130 *
131 * @param text - string to encode
132 * @returns encoded string
133 */
134function encodeQueryValue(text) {
135 return (commonEncode(text)
136 // Encode the space as +, encode the + to differentiate it from the space
137 .replace(PLUS_RE, '%2B')
138 .replace(ENC_SPACE_RE, '+')
139 .replace(HASH_RE, '%23')
140 .replace(AMPERSAND_RE, '%26')
141 .replace(ENC_BACKTICK_RE, '`')
142 .replace(ENC_CURLY_OPEN_RE, '{')
143 .replace(ENC_CURLY_CLOSE_RE, '}')
144 .replace(ENC_CARET_RE, '^'));
145}
146/**
147 * Like `encodeQueryValue` but also encodes the `=` character.
148 *
149 * @param text - string to encode
150 */
151function encodeQueryKey(text) {
152 return encodeQueryValue(text).replace(EQUAL_RE, '%3D');
153}
154/**
155 * Encode characters that need to be encoded on the path section of the URL.
156 *
157 * @param text - string to encode
158 * @returns encoded string
159 */
160function encodePath(text) {
161 return commonEncode(text).replace(HASH_RE, '%23').replace(IM_RE, '%3F');
162}
163/**
164 * Encode characters that need to be encoded on the path section of the URL as a
165 * param. This function encodes everything {@link encodePath} does plus the
166 * slash (`/`) character. If `text` is `null` or `undefined`, returns an empty
167 * string instead.
168 *
169 * @param text - string to encode
170 * @returns encoded string
171 */
172function encodeParam(text) {
173 return text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F');
174}
175/**
176 * Decode text using `decodeURIComponent`. Returns the original text if it
177 * fails.
178 *
179 * @param text - string to decode
180 * @returns decoded string
181 */
182function decode(text) {
183 try {
184 return decodeURIComponent('' + text);
185 }
186 catch (err) {
187 (process.env.NODE_ENV !== 'production') && warn(`Error decoding "${text}". Using original value`);
188 }
189 return '' + text;
190}
191
192const TRAILING_SLASH_RE = /\/$/;
193const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, '');
194/**
195 * Transforms a URI into a normalized history location
196 *
197 * @param parseQuery
198 * @param location - URI to normalize
199 * @param currentLocation - current absolute location. Allows resolving relative
200 * paths. Must start with `/`. Defaults to `/`
201 * @returns a normalized history location
202 */
203function parseURL(parseQuery, location, currentLocation = '/') {
204 let path, query = {}, searchString = '', hash = '';
205 // Could use URL and URLSearchParams but IE 11 doesn't support it
206 // TODO: move to new URL()
207 const hashPos = location.indexOf('#');
208 let searchPos = location.indexOf('?');
209 // the hash appears before the search, so it's not part of the search string
210 if (hashPos < searchPos && hashPos >= 0) {
211 searchPos = -1;
212 }
213 if (searchPos > -1) {
214 path = location.slice(0, searchPos);
215 searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length);
216 query = parseQuery(searchString);
217 }
218 if (hashPos > -1) {
219 path = path || location.slice(0, hashPos);
220 // keep the # character
221 hash = location.slice(hashPos, location.length);
222 }
223 // no search and no query
224 path = resolveRelativePath(path != null ? path : location, currentLocation);
225 // empty path means a relative query or hash `?foo=f`, `#thing`
226 return {
227 fullPath: path + (searchString && '?') + searchString + hash,
228 path,
229 query,
230 hash: decode(hash),
231 };
232}
233/**
234 * Stringifies a URL object
235 *
236 * @param stringifyQuery
237 * @param location
238 */
239function stringifyURL(stringifyQuery, location) {
240 const query = location.query ? stringifyQuery(location.query) : '';
241 return location.path + (query && '?') + query + (location.hash || '');
242}
243/**
244 * Strips off the base from the beginning of a location.pathname in a non-case-sensitive way.
245 *
246 * @param pathname - location.pathname
247 * @param base - base to strip off
248 */
249function stripBase(pathname, base) {
250 // no base or base is not found at the beginning
251 if (!base || !pathname.toLowerCase().startsWith(base.toLowerCase()))
252 return pathname;
253 return pathname.slice(base.length) || '/';
254}
255/**
256 * Checks if two RouteLocation are equal. This means that both locations are
257 * pointing towards the same {@link RouteRecord} and that all `params`, `query`
258 * parameters and `hash` are the same
259 *
260 * @param stringifyQuery - A function that takes a query object of type LocationQueryRaw and returns a string representation of it.
261 * @param a - first {@link RouteLocation}
262 * @param b - second {@link RouteLocation}
263 */
264function isSameRouteLocation(stringifyQuery, a, b) {
265 const aLastIndex = a.matched.length - 1;
266 const bLastIndex = b.matched.length - 1;
267 return (aLastIndex > -1 &&
268 aLastIndex === bLastIndex &&
269 isSameRouteRecord(a.matched[aLastIndex], b.matched[bLastIndex]) &&
270 isSameRouteLocationParams(a.params, b.params) &&
271 stringifyQuery(a.query) === stringifyQuery(b.query) &&
272 a.hash === b.hash);
273}
274/**
275 * Check if two `RouteRecords` are equal. Takes into account aliases: they are
276 * considered equal to the `RouteRecord` they are aliasing.
277 *
278 * @param a - first {@link RouteRecord}
279 * @param b - second {@link RouteRecord}
280 */
281function isSameRouteRecord(a, b) {
282 // since the original record has an undefined value for aliasOf
283 // but all aliases point to the original record, this will always compare
284 // the original record
285 return (a.aliasOf || a) === (b.aliasOf || b);
286}
287function isSameRouteLocationParams(a, b) {
288 if (Object.keys(a).length !== Object.keys(b).length)
289 return false;
290 for (const key in a) {
291 if (!isSameRouteLocationParamsValue(a[key], b[key]))
292 return false;
293 }
294 return true;
295}
296function isSameRouteLocationParamsValue(a, b) {
297 return isArray(a)
298 ? isEquivalentArray(a, b)
299 : isArray(b)
300 ? isEquivalentArray(b, a)
301 : a === b;
302}
303/**
304 * Check if two arrays are the same or if an array with one single entry is the
305 * same as another primitive value. Used to check query and parameters
306 *
307 * @param a - array of values
308 * @param b - array of values or a single value
309 */
310function isEquivalentArray(a, b) {
311 return isArray(b)
312 ? a.length === b.length && a.every((value, i) => value === b[i])
313 : a.length === 1 && a[0] === b;
314}
315/**
316 * Resolves a relative path that starts with `.`.
317 *
318 * @param to - path location we are resolving
319 * @param from - currentLocation.path, should start with `/`
320 */
321function resolveRelativePath(to, from) {
322 if (to.startsWith('/'))
323 return to;
324 if ((process.env.NODE_ENV !== 'production') && !from.startsWith('/')) {
325 warn(`Cannot resolve a relative location without an absolute path. Trying to resolve "${to}" from "${from}". It should look like "/${from}".`);
326 return to;
327 }
328 if (!to)
329 return from;
330 const fromSegments = from.split('/');
331 const toSegments = to.split('/');
332 const lastToSegment = toSegments[toSegments.length - 1];
333 // make . and ./ the same (../ === .., ../../ === ../..)
334 // this is the same behavior as new URL()
335 if (lastToSegment === '..' || lastToSegment === '.') {
336 toSegments.push('');
337 }
338 let position = fromSegments.length - 1;
339 let toPosition;
340 let segment;
341 for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
342 segment = toSegments[toPosition];
343 // we stay on the same position
344 if (segment === '.')
345 continue;
346 // go up in the from array
347 if (segment === '..') {
348 // we can't go below zero, but we still need to increment toPosition
349 if (position > 1)
350 position--;
351 // continue
352 }
353 // we reached a non-relative path, we stop here
354 else
355 break;
356 }
357 return (fromSegments.slice(0, position).join('/') +
358 '/' +
359 toSegments.slice(toPosition).join('/'));
360}
361/**
362 * Initial route location where the router is. Can be used in navigation guards
363 * to differentiate the initial navigation.
364 *
365 * @example
366 * ```js
367 * import { START_LOCATION } from 'vue-router'
368 *
369 * router.beforeEach((to, from) => {
370 * if (from === START_LOCATION) {
371 * // initial navigation
372 * }
373 * })
374 * ```
375 */
376const START_LOCATION_NORMALIZED = {
377 path: '/',
378 // TODO: could we use a symbol in the future?
379 name: undefined,
380 params: {},
381 query: {},
382 hash: '',
383 fullPath: '/',
384 matched: [],
385 meta: {},
386 redirectedFrom: undefined,
387};
388
389var NavigationType;
390(function (NavigationType) {
391 NavigationType["pop"] = "pop";
392 NavigationType["push"] = "push";
393})(NavigationType || (NavigationType = {}));
394var NavigationDirection;
395(function (NavigationDirection) {
396 NavigationDirection["back"] = "back";
397 NavigationDirection["forward"] = "forward";
398 NavigationDirection["unknown"] = "";
399})(NavigationDirection || (NavigationDirection = {}));
400/**
401 * Starting location for Histories
402 */
403const START = '';
404// Generic utils
405/**
406 * Normalizes a base by removing any trailing slash and reading the base tag if
407 * present.
408 *
409 * @param base - base to normalize
410 */
411function normalizeBase(base) {
412 if (!base) {
413 if (isBrowser) {
414 // respect <base> tag
415 const baseEl = document.querySelector('base');
416 base = (baseEl && baseEl.getAttribute('href')) || '/';
417 // strip full URL origin
418 base = base.replace(/^\w+:\/\/[^\/]+/, '');
419 }
420 else {
421 base = '/';
422 }
423 }
424 // ensure leading slash when it was removed by the regex above avoid leading
425 // slash with hash because the file could be read from the disk like file://
426 // and the leading slash would cause problems
427 if (base[0] !== '/' && base[0] !== '#')
428 base = '/' + base;
429 // remove the trailing slash so all other method can just do `base + fullPath`
430 // to build an href
431 return removeTrailingSlash(base);
432}
433// remove any character before the hash
434const BEFORE_HASH_RE = /^[^#]+#/;
435function createHref(base, location) {
436 return base.replace(BEFORE_HASH_RE, '#') + location;
437}
438
439function getElementPosition(el, offset) {
440 const docRect = document.documentElement.getBoundingClientRect();
441 const elRect = el.getBoundingClientRect();
442 return {
443 behavior: offset.behavior,
444 left: elRect.left - docRect.left - (offset.left || 0),
445 top: elRect.top - docRect.top - (offset.top || 0),
446 };
447}
448const computeScrollPosition = () => ({
449 left: window.scrollX,
450 top: window.scrollY,
451});
452function scrollToPosition(position) {
453 let scrollToOptions;
454 if ('el' in position) {
455 const positionEl = position.el;
456 const isIdSelector = typeof positionEl === 'string' && positionEl.startsWith('#');
457 /**
458 * `id`s can accept pretty much any characters, including CSS combinators
459 * like `>` or `~`. It's still possible to retrieve elements using
460 * `document.getElementById('~')` but it needs to be escaped when using
461 * `document.querySelector('#\\~')` for it to be valid. The only
462 * requirements for `id`s are them to be unique on the page and to not be
463 * empty (`id=""`). Because of that, when passing an id selector, it should
464 * be properly escaped for it to work with `querySelector`. We could check
465 * for the id selector to be simple (no CSS combinators `+ >~`) but that
466 * would make things inconsistent since they are valid characters for an
467 * `id` but would need to be escaped when using `querySelector`, breaking
468 * their usage and ending up in no selector returned. Selectors need to be
469 * escaped:
470 *
471 * - `#1-thing` becomes `#\31 -thing`
472 * - `#with~symbols` becomes `#with\\~symbols`
473 *
474 * - More information about the topic can be found at
475 * https://mathiasbynens.be/notes/html5-id-class.
476 * - Practical example: https://mathiasbynens.be/demo/html5-id
477 */
478 if ((process.env.NODE_ENV !== 'production') && typeof position.el === 'string') {
479 if (!isIdSelector || !document.getElementById(position.el.slice(1))) {
480 try {
481 const foundEl = document.querySelector(position.el);
482 if (isIdSelector && foundEl) {
483 warn(`The selector "${position.el}" should be passed as "el: document.querySelector('${position.el}')" because it starts with "#".`);
484 // return to avoid other warnings
485 return;
486 }
487 }
488 catch (err) {
489 warn(`The selector "${position.el}" is invalid. If you are using an id selector, make sure to escape it. You can find more information about escaping characters in selectors at https://mathiasbynens.be/notes/css-escapes or use CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape).`);
490 // return to avoid other warnings
491 return;
492 }
493 }
494 }
495 const el = typeof positionEl === 'string'
496 ? isIdSelector
497 ? document.getElementById(positionEl.slice(1))
498 : document.querySelector(positionEl)
499 : positionEl;
500 if (!el) {
501 (process.env.NODE_ENV !== 'production') &&
502 warn(`Couldn't find element using selector "${position.el}" returned by scrollBehavior.`);
503 return;
504 }
505 scrollToOptions = getElementPosition(el, position);
506 }
507 else {
508 scrollToOptions = position;
509 }
510 if ('scrollBehavior' in document.documentElement.style)
511 window.scrollTo(scrollToOptions);
512 else {
513 window.scrollTo(scrollToOptions.left != null ? scrollToOptions.left : window.scrollX, scrollToOptions.top != null ? scrollToOptions.top : window.scrollY);
514 }
515}
516function getScrollKey(path, delta) {
517 const position = history.state ? history.state.position - delta : -1;
518 return position + path;
519}
520const scrollPositions = new Map();
521function saveScrollPosition(key, scrollPosition) {
522 scrollPositions.set(key, scrollPosition);
523}
524function getSavedScrollPosition(key) {
525 const scroll = scrollPositions.get(key);
526 // consume it so it's not used again
527 scrollPositions.delete(key);
528 return scroll;
529}
530// TODO: RFC about how to save scroll position
531/**
532 * ScrollBehavior instance used by the router to compute and restore the scroll
533 * position when navigating.
534 */
535// export interface ScrollHandler<ScrollPositionEntry extends HistoryStateValue, ScrollPosition extends ScrollPositionEntry> {
536// // returns a scroll position that can be saved in history
537// compute(): ScrollPositionEntry
538// // can take an extended ScrollPositionEntry
539// scroll(position: ScrollPosition): void
540// }
541// export const scrollHandler: ScrollHandler<ScrollPosition> = {
542// compute: computeScroll,
543// scroll: scrollToPosition,
544// }
545
546let createBaseLocation = () => location.protocol + '//' + location.host;
547/**
548 * Creates a normalized history location from a window.location object
549 * @param base - The base path
550 * @param location - The window.location object
551 */
552function createCurrentLocation(base, location) {
553 const { pathname, search, hash } = location;
554 // allows hash bases like #, /#, #/, #!, #!/, /#!/, or even /folder#end
555 const hashPos = base.indexOf('#');
556 if (hashPos > -1) {
557 let slicePos = hash.includes(base.slice(hashPos))
558 ? base.slice(hashPos).length
559 : 1;
560 let pathFromHash = hash.slice(slicePos);
561 // prepend the starting slash to hash so the url starts with /#
562 if (pathFromHash[0] !== '/')
563 pathFromHash = '/' + pathFromHash;
564 return stripBase(pathFromHash, '');
565 }
566 const path = stripBase(pathname, base);
567 return path + search + hash;
568}
569function useHistoryListeners(base, historyState, currentLocation, replace) {
570 let listeners = [];
571 let teardowns = [];
572 // TODO: should it be a stack? a Dict. Check if the popstate listener
573 // can trigger twice
574 let pauseState = null;
575 const popStateHandler = ({ state, }) => {
576 const to = createCurrentLocation(base, location);
577 const from = currentLocation.value;
578 const fromState = historyState.value;
579 let delta = 0;
580 if (state) {
581 currentLocation.value = to;
582 historyState.value = state;
583 // ignore the popstate and reset the pauseState
584 if (pauseState && pauseState === from) {
585 pauseState = null;
586 return;
587 }
588 delta = fromState ? state.position - fromState.position : 0;
589 }
590 else {
591 replace(to);
592 }
593 // Here we could also revert the navigation by calling history.go(-delta)
594 // this listener will have to be adapted to not trigger again and to wait for the url
595 // to be updated before triggering the listeners. Some kind of validation function would also
596 // need to be passed to the listeners so the navigation can be accepted
597 // call all listeners
598 listeners.forEach(listener => {
599 listener(currentLocation.value, from, {
600 delta,
601 type: NavigationType.pop,
602 direction: delta
603 ? delta > 0
604 ? NavigationDirection.forward
605 : NavigationDirection.back
606 : NavigationDirection.unknown,
607 });
608 });
609 };
610 function pauseListeners() {
611 pauseState = currentLocation.value;
612 }
613 function listen(callback) {
614 // set up the listener and prepare teardown callbacks
615 listeners.push(callback);
616 const teardown = () => {
617 const index = listeners.indexOf(callback);
618 if (index > -1)
619 listeners.splice(index, 1);
620 };
621 teardowns.push(teardown);
622 return teardown;
623 }
624 function beforeUnloadListener() {
625 const { history } = window;
626 if (!history.state)
627 return;
628 history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), '');
629 }
630 function destroy() {
631 for (const teardown of teardowns)
632 teardown();
633 teardowns = [];
634 window.removeEventListener('popstate', popStateHandler);
635 window.removeEventListener('beforeunload', beforeUnloadListener);
636 }
637 // set up the listeners and prepare teardown callbacks
638 window.addEventListener('popstate', popStateHandler);
639 // TODO: could we use 'pagehide' or 'visibilitychange' instead?
640 // https://developer.chrome.com/blog/page-lifecycle-api/
641 window.addEventListener('beforeunload', beforeUnloadListener, {
642 passive: true,
643 });
644 return {
645 pauseListeners,
646 listen,
647 destroy,
648 };
649}
650/**
651 * Creates a state object
652 */
653function buildState(back, current, forward, replaced = false, computeScroll = false) {
654 return {
655 back,
656 current,
657 forward,
658 replaced,
659 position: window.history.length,
660 scroll: computeScroll ? computeScrollPosition() : null,
661 };
662}
663function useHistoryStateNavigation(base) {
664 const { history, location } = window;
665 // private variables
666 const currentLocation = {
667 value: createCurrentLocation(base, location),
668 };
669 const historyState = { value: history.state };
670 // build current history entry as this is a fresh navigation
671 if (!historyState.value) {
672 changeLocation(currentLocation.value, {
673 back: null,
674 current: currentLocation.value,
675 forward: null,
676 // the length is off by one, we need to decrease it
677 position: history.length - 1,
678 replaced: true,
679 // don't add a scroll as the user may have an anchor, and we want
680 // scrollBehavior to be triggered without a saved position
681 scroll: null,
682 }, true);
683 }
684 function changeLocation(to, state, replace) {
685 /**
686 * if a base tag is provided, and we are on a normal domain, we have to
687 * respect the provided `base` attribute because pushState() will use it and
688 * potentially erase anything before the `#` like at
689 * https://github.com/vuejs/router/issues/685 where a base of
690 * `/folder/#` but a base of `/` would erase the `/folder/` section. If
691 * there is no host, the `<base>` tag makes no sense and if there isn't a
692 * base tag we can just use everything after the `#`.
693 */
694 const hashIndex = base.indexOf('#');
695 const url = hashIndex > -1
696 ? (location.host && document.querySelector('base')
697 ? base
698 : base.slice(hashIndex)) + to
699 : createBaseLocation() + base + to;
700 try {
701 // BROWSER QUIRK
702 // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds
703 history[replace ? 'replaceState' : 'pushState'](state, '', url);
704 historyState.value = state;
705 }
706 catch (err) {
707 if ((process.env.NODE_ENV !== 'production')) {
708 warn('Error with push/replace State', err);
709 }
710 else {
711 console.error(err);
712 }
713 // Force the navigation, this also resets the call count
714 location[replace ? 'replace' : 'assign'](url);
715 }
716 }
717 function replace(to, data) {
718 const state = assign({}, history.state, buildState(historyState.value.back,
719 // keep back and forward entries but override current position
720 to, historyState.value.forward, true), data, { position: historyState.value.position });
721 changeLocation(to, state, true);
722 currentLocation.value = to;
723 }
724 function push(to, data) {
725 // Add to current entry the information of where we are going
726 // as well as saving the current position
727 const currentState = assign({},
728 // use current history state to gracefully handle a wrong call to
729 // history.replaceState
730 // https://github.com/vuejs/router/issues/366
731 historyState.value, history.state, {
732 forward: to,
733 scroll: computeScrollPosition(),
734 });
735 if ((process.env.NODE_ENV !== 'production') && !history.state) {
736 warn(`history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\n` +
737 `history.replaceState(history.state, '', url)\n\n` +
738 `You can find more information at https://router.vuejs.org/guide/migration/#Usage-of-history-state`);
739 }
740 changeLocation(currentState.current, currentState, true);
741 const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data);
742 changeLocation(to, state, false);
743 currentLocation.value = to;
744 }
745 return {
746 location: currentLocation,
747 state: historyState,
748 push,
749 replace,
750 };
751}
752/**
753 * Creates an HTML5 history. Most common history for single page applications.
754 *
755 * @param base -
756 */
757function createWebHistory(base) {
758 base = normalizeBase(base);
759 const historyNavigation = useHistoryStateNavigation(base);
760 const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace);
761 function go(delta, triggerListeners = true) {
762 if (!triggerListeners)
763 historyListeners.pauseListeners();
764 history.go(delta);
765 }
766 const routerHistory = assign({
767 // it's overridden right after
768 location: '',
769 base,
770 go,
771 createHref: createHref.bind(null, base),
772 }, historyNavigation, historyListeners);
773 Object.defineProperty(routerHistory, 'location', {
774 enumerable: true,
775 get: () => historyNavigation.location.value,
776 });
777 Object.defineProperty(routerHistory, 'state', {
778 enumerable: true,
779 get: () => historyNavigation.state.value,
780 });
781 return routerHistory;
782}
783
784/**
785 * 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.
786 * It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`.
787 *
788 * @param base - Base applied to all urls, defaults to '/'
789 * @returns a history object that can be passed to the router constructor
790 */
791function createMemoryHistory(base = '') {
792 let listeners = [];
793 let queue = [START];
794 let position = 0;
795 base = normalizeBase(base);
796 function setLocation(location) {
797 position++;
798 if (position !== queue.length) {
799 // we are in the middle, we remove everything from here in the queue
800 queue.splice(position);
801 }
802 queue.push(location);
803 }
804 function triggerListeners(to, from, { direction, delta }) {
805 const info = {
806 direction,
807 delta,
808 type: NavigationType.pop,
809 };
810 for (const callback of listeners) {
811 callback(to, from, info);
812 }
813 }
814 const routerHistory = {
815 // rewritten by Object.defineProperty
816 location: START,
817 // TODO: should be kept in queue
818 state: {},
819 base,
820 createHref: createHref.bind(null, base),
821 replace(to) {
822 // remove current entry and decrement position
823 queue.splice(position--, 1);
824 setLocation(to);
825 },
826 push(to, data) {
827 setLocation(to);
828 },
829 listen(callback) {
830 listeners.push(callback);
831 return () => {
832 const index = listeners.indexOf(callback);
833 if (index > -1)
834 listeners.splice(index, 1);
835 };
836 },
837 destroy() {
838 listeners = [];
839 queue = [START];
840 position = 0;
841 },
842 go(delta, shouldTrigger = true) {
843 const from = this.location;
844 const direction =
845 // we are considering delta === 0 going forward, but in abstract mode
846 // using 0 for the delta doesn't make sense like it does in html5 where
847 // it reloads the page
848 delta < 0 ? NavigationDirection.back : NavigationDirection.forward;
849 position = Math.max(0, Math.min(position + delta, queue.length - 1));
850 if (shouldTrigger) {
851 triggerListeners(this.location, from, {
852 direction,
853 delta,
854 });
855 }
856 },
857 };
858 Object.defineProperty(routerHistory, 'location', {
859 enumerable: true,
860 get: () => queue[position],
861 });
862 return routerHistory;
863}
864
865/**
866 * Creates a hash history. Useful for web applications with no host (e.g. `file://`) or when configuring a server to
867 * handle any URL is not possible.
868 *
869 * @param base - optional base to provide. Defaults to `location.pathname + location.search` If there is a `<base>` tag
870 * in the `head`, its value will be ignored in favor of this parameter **but note it affects all the history.pushState()
871 * calls**, meaning that if you use a `<base>` tag, it's `href` value **has to match this parameter** (ignoring anything
872 * after the `#`).
873 *
874 * @example
875 * ```js
876 * // at https://example.com/folder
877 * createWebHashHistory() // gives a url of `https://example.com/folder#`
878 * createWebHashHistory('/folder/') // gives a url of `https://example.com/folder/#`
879 * // if the `#` is provided in the base, it won't be added by `createWebHashHistory`
880 * createWebHashHistory('/folder/#/app/') // gives a url of `https://example.com/folder/#/app/`
881 * // you should avoid doing this because it changes the original url and breaks copying urls
882 * createWebHashHistory('/other-folder/') // gives a url of `https://example.com/other-folder/#`
883 *
884 * // at file:///usr/etc/folder/index.html
885 * // for locations with no `host`, the base is ignored
886 * createWebHashHistory('/iAmIgnored') // gives a url of `file:///usr/etc/folder/index.html#`
887 * ```
888 */
889function createWebHashHistory(base) {
890 // Make sure this implementation is fine in terms of encoding, specially for IE11
891 // for `file://`, directly use the pathname and ignore the base
892 // location.pathname contains an initial `/` even at the root: `https://example.com`
893 base = location.host ? base || location.pathname + location.search : '';
894 // allow the user to provide a `#` in the middle: `/base/#/app`
895 if (!base.includes('#'))
896 base += '#';
897 if ((process.env.NODE_ENV !== 'production') && !base.endsWith('#/') && !base.endsWith('#')) {
898 warn(`A hash base must end with a "#":\n"${base}" should be "${base.replace(/#.*$/, '#')}".`);
899 }
900 return createWebHistory(base);
901}
902
903function isRouteLocation(route) {
904 return typeof route === 'string' || (route && typeof route === 'object');
905}
906function isRouteName(name) {
907 return typeof name === 'string' || typeof name === 'symbol';
908}
909
910const NavigationFailureSymbol = Symbol((process.env.NODE_ENV !== 'production') ? 'navigation failure' : '');
911/**
912 * Enumeration with all possible types for navigation failures. Can be passed to
913 * {@link isNavigationFailure} to check for specific failures.
914 */
915var NavigationFailureType;
916(function (NavigationFailureType) {
917 /**
918 * An aborted navigation is a navigation that failed because a navigation
919 * guard returned `false` or called `next(false)`
920 */
921 NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted";
922 /**
923 * A cancelled navigation is a navigation that failed because a more recent
924 * navigation finished started (not necessarily finished).
925 */
926 NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled";
927 /**
928 * A duplicated navigation is a navigation that failed because it was
929 * initiated while already being at the exact same location.
930 */
931 NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated";
932})(NavigationFailureType || (NavigationFailureType = {}));
933// DEV only debug messages
934const ErrorTypeMessages = {
935 [1 /* ErrorTypes.MATCHER_NOT_FOUND */]({ location, currentLocation }) {
936 return `No match for\n ${JSON.stringify(location)}${currentLocation
937 ? '\nwhile being at\n' + JSON.stringify(currentLocation)
938 : ''}`;
939 },
940 [2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
941 return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
942 },
943 [4 /* ErrorTypes.NAVIGATION_ABORTED */]({ from, to }) {
944 return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
945 },
946 [8 /* ErrorTypes.NAVIGATION_CANCELLED */]({ from, to }) {
947 return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
948 },
949 [16 /* ErrorTypes.NAVIGATION_DUPLICATED */]({ from, to }) {
950 return `Avoided redundant navigation to current location: "${from.fullPath}".`;
951 },
952};
953/**
954 * Creates a typed NavigationFailure object.
955 * @internal
956 * @param type - NavigationFailureType
957 * @param params - { from, to }
958 */
959function createRouterError(type, params) {
960 // keep full error messages in cjs versions
961 if ((process.env.NODE_ENV !== 'production') || !true) {
962 return assign(new Error(ErrorTypeMessages[type](params)), {
963 type,
964 [NavigationFailureSymbol]: true,
965 }, params);
966 }
967 else {
968 return assign(new Error(), {
969 type,
970 [NavigationFailureSymbol]: true,
971 }, params);
972 }
973}
974function isNavigationFailure(error, type) {
975 return (error instanceof Error &&
976 NavigationFailureSymbol in error &&
977 (type == null || !!(error.type & type)));
978}
979const propertiesToLog = ['params', 'query', 'hash'];
980function stringifyRoute(to) {
981 if (typeof to === 'string')
982 return to;
983 if (to.path != null)
984 return to.path;
985 const location = {};
986 for (const key of propertiesToLog) {
987 if (key in to)
988 location[key] = to[key];
989 }
990 return JSON.stringify(location, null, 2);
991}
992
993// default pattern for a param: non-greedy everything but /
994const BASE_PARAM_PATTERN = '[^/]+?';
995const BASE_PATH_PARSER_OPTIONS = {
996 sensitive: false,
997 strict: false,
998 start: true,
999 end: true,
1000};
1001// Special Regex characters that must be escaped in static tokens
1002const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g;
1003/**
1004 * Creates a path parser from an array of Segments (a segment is an array of Tokens)
1005 *
1006 * @param segments - array of segments returned by tokenizePath
1007 * @param extraOptions - optional options for the regexp
1008 * @returns a PathParser
1009 */
1010function tokensToParser(segments, extraOptions) {
1011 const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
1012 // the amount of scores is the same as the length of segments except for the root segment "/"
1013 const score = [];
1014 // the regexp as a string
1015 let pattern = options.start ? '^' : '';
1016 // extracted keys
1017 const keys = [];
1018 for (const segment of segments) {
1019 // the root segment needs special treatment
1020 const segmentScores = segment.length ? [] : [90 /* PathScore.Root */];
1021 // allow trailing slash
1022 if (options.strict && !segment.length)
1023 pattern += '/';
1024 for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
1025 const token = segment[tokenIndex];
1026 // resets the score if we are inside a sub-segment /:a-other-:b
1027 let subSegmentScore = 40 /* PathScore.Segment */ +
1028 (options.sensitive ? 0.25 /* PathScore.BonusCaseSensitive */ : 0);
1029 if (token.type === 0 /* TokenType.Static */) {
1030 // prepend the slash if we are starting a new segment
1031 if (!tokenIndex)
1032 pattern += '/';
1033 pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
1034 subSegmentScore += 40 /* PathScore.Static */;
1035 }
1036 else if (token.type === 1 /* TokenType.Param */) {
1037 const { value, repeatable, optional, regexp } = token;
1038 keys.push({
1039 name: value,
1040 repeatable,
1041 optional,
1042 });
1043 const re = regexp ? regexp : BASE_PARAM_PATTERN;
1044 // the user provided a custom regexp /:id(\\d+)
1045 if (re !== BASE_PARAM_PATTERN) {
1046 subSegmentScore += 10 /* PathScore.BonusCustomRegExp */;
1047 // make sure the regexp is valid before using it
1048 try {
1049 new RegExp(`(${re})`);
1050 }
1051 catch (err) {
1052 throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` +
1053 err.message);
1054 }
1055 }
1056 // when we repeat we must take care of the repeating leading slash
1057 let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
1058 // prepend the slash if we are starting a new segment
1059 if (!tokenIndex)
1060 subPattern =
1061 // avoid an optional / if there are more segments e.g. /:p?-static
1062 // or /:p?-:p2
1063 optional && segment.length < 2
1064 ? `(?:/${subPattern})`
1065 : '/' + subPattern;
1066 if (optional)
1067 subPattern += '?';
1068 pattern += subPattern;
1069 subSegmentScore += 20 /* PathScore.Dynamic */;
1070 if (optional)
1071 subSegmentScore += -8 /* PathScore.BonusOptional */;
1072 if (repeatable)
1073 subSegmentScore += -20 /* PathScore.BonusRepeatable */;
1074 if (re === '.*')
1075 subSegmentScore += -50 /* PathScore.BonusWildcard */;
1076 }
1077 segmentScores.push(subSegmentScore);
1078 }
1079 // an empty array like /home/ -> [[{home}], []]
1080 // if (!segment.length) pattern += '/'
1081 score.push(segmentScores);
1082 }
1083 // only apply the strict bonus to the last score
1084 if (options.strict && options.end) {
1085 const i = score.length - 1;
1086 score[i][score[i].length - 1] += 0.7000000000000001 /* PathScore.BonusStrict */;
1087 }
1088 // TODO: dev only warn double trailing slash
1089 if (!options.strict)
1090 pattern += '/?';
1091 if (options.end)
1092 pattern += '$';
1093 // allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else
1094 else if (options.strict && !pattern.endsWith('/'))
1095 pattern += '(?:/|$)';
1096 const re = new RegExp(pattern, options.sensitive ? '' : 'i');
1097 function parse(path) {
1098 const match = path.match(re);
1099 const params = {};
1100 if (!match)
1101 return null;
1102 for (let i = 1; i < match.length; i++) {
1103 const value = match[i] || '';
1104 const key = keys[i - 1];
1105 params[key.name] = value && key.repeatable ? value.split('/') : value;
1106 }
1107 return params;
1108 }
1109 function stringify(params) {
1110 let path = '';
1111 // for optional parameters to allow to be empty
1112 let avoidDuplicatedSlash = false;
1113 for (const segment of segments) {
1114 if (!avoidDuplicatedSlash || !path.endsWith('/'))
1115 path += '/';
1116 avoidDuplicatedSlash = false;
1117 for (const token of segment) {
1118 if (token.type === 0 /* TokenType.Static */) {
1119 path += token.value;
1120 }
1121 else if (token.type === 1 /* TokenType.Param */) {
1122 const { value, repeatable, optional } = token;
1123 const param = value in params ? params[value] : '';
1124 if (isArray(param) && !repeatable) {
1125 throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);
1126 }
1127 const text = isArray(param)
1128 ? param.join('/')
1129 : param;
1130 if (!text) {
1131 if (optional) {
1132 // if we have more than one optional param like /:a?-static we don't need to care about the optional param
1133 if (segment.length < 2) {
1134 // remove the last slash as we could be at the end
1135 if (path.endsWith('/'))
1136 path = path.slice(0, -1);
1137 // do not append a slash on the next iteration
1138 else
1139 avoidDuplicatedSlash = true;
1140 }
1141 }
1142 else
1143 throw new Error(`Missing required param "${value}"`);
1144 }
1145 path += text;
1146 }
1147 }
1148 }
1149 // avoid empty path when we have multiple optional params
1150 return path || '/';
1151 }
1152 return {
1153 re,
1154 score,
1155 keys,
1156 parse,
1157 stringify,
1158 };
1159}
1160/**
1161 * Compares an array of numbers as used in PathParser.score and returns a
1162 * number. This function can be used to `sort` an array
1163 *
1164 * @param a - first array of numbers
1165 * @param b - second array of numbers
1166 * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
1167 * should be sorted first
1168 */
1169function compareScoreArray(a, b) {
1170 let i = 0;
1171 while (i < a.length && i < b.length) {
1172 const diff = b[i] - a[i];
1173 // only keep going if diff === 0
1174 if (diff)
1175 return diff;
1176 i++;
1177 }
1178 // if the last subsegment was Static, the shorter segments should be sorted first
1179 // otherwise sort the longest segment first
1180 if (a.length < b.length) {
1181 return a.length === 1 && a[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
1182 ? -1
1183 : 1;
1184 }
1185 else if (a.length > b.length) {
1186 return b.length === 1 && b[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
1187 ? 1
1188 : -1;
1189 }
1190 return 0;
1191}
1192/**
1193 * Compare function that can be used with `sort` to sort an array of PathParser
1194 *
1195 * @param a - first PathParser
1196 * @param b - second PathParser
1197 * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
1198 */
1199function comparePathParserScore(a, b) {
1200 let i = 0;
1201 const aScore = a.score;
1202 const bScore = b.score;
1203 while (i < aScore.length && i < bScore.length) {
1204 const comp = compareScoreArray(aScore[i], bScore[i]);
1205 // do not return if both are equal
1206 if (comp)
1207 return comp;
1208 i++;
1209 }
1210 if (Math.abs(bScore.length - aScore.length) === 1) {
1211 if (isLastScoreNegative(aScore))
1212 return 1;
1213 if (isLastScoreNegative(bScore))
1214 return -1;
1215 }
1216 // if a and b share the same score entries but b has more, sort b first
1217 return bScore.length - aScore.length;
1218 // this is the ternary version
1219 // return aScore.length < bScore.length
1220 // ? 1
1221 // : aScore.length > bScore.length
1222 // ? -1
1223 // : 0
1224}
1225/**
1226 * This allows detecting splats at the end of a path: /home/:id(.*)*
1227 *
1228 * @param score - score to check
1229 * @returns true if the last entry is negative
1230 */
1231function isLastScoreNegative(score) {
1232 const last = score[score.length - 1];
1233 return score.length > 0 && last[last.length - 1] < 0;
1234}
1235
1236const ROOT_TOKEN = {
1237 type: 0 /* TokenType.Static */,
1238 value: '',
1239};
1240const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
1241// After some profiling, the cache seems to be unnecessary because tokenizePath
1242// (the slowest part of adding a route) is very fast
1243// const tokenCache = new Map<string, Token[][]>()
1244function tokenizePath(path) {
1245 if (!path)
1246 return [[]];
1247 if (path === '/')
1248 return [[ROOT_TOKEN]];
1249 if (!path.startsWith('/')) {
1250 throw new Error((process.env.NODE_ENV !== 'production')
1251 ? `Route paths should start with a "/": "${path}" should be "/${path}".`
1252 : `Invalid path "${path}"`);
1253 }
1254 // if (tokenCache.has(path)) return tokenCache.get(path)!
1255 function crash(message) {
1256 throw new Error(`ERR (${state})/"${buffer}": ${message}`);
1257 }
1258 let state = 0 /* TokenizerState.Static */;
1259 let previousState = state;
1260 const tokens = [];
1261 // the segment will always be valid because we get into the initial state
1262 // with the leading /
1263 let segment;
1264 function finalizeSegment() {
1265 if (segment)
1266 tokens.push(segment);
1267 segment = [];
1268 }
1269 // index on the path
1270 let i = 0;
1271 // char at index
1272 let char;
1273 // buffer of the value read
1274 let buffer = '';
1275 // custom regexp for a param
1276 let customRe = '';
1277 function consumeBuffer() {
1278 if (!buffer)
1279 return;
1280 if (state === 0 /* TokenizerState.Static */) {
1281 segment.push({
1282 type: 0 /* TokenType.Static */,
1283 value: buffer,
1284 });
1285 }
1286 else if (state === 1 /* TokenizerState.Param */ ||
1287 state === 2 /* TokenizerState.ParamRegExp */ ||
1288 state === 3 /* TokenizerState.ParamRegExpEnd */) {
1289 if (segment.length > 1 && (char === '*' || char === '+'))
1290 crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
1291 segment.push({
1292 type: 1 /* TokenType.Param */,
1293 value: buffer,
1294 regexp: customRe,
1295 repeatable: char === '*' || char === '+',
1296 optional: char === '*' || char === '?',
1297 });
1298 }
1299 else {
1300 crash('Invalid state to consume buffer');
1301 }
1302 buffer = '';
1303 }
1304 function addCharToBuffer() {
1305 buffer += char;
1306 }
1307 while (i < path.length) {
1308 char = path[i++];
1309 if (char === '\\' && state !== 2 /* TokenizerState.ParamRegExp */) {
1310 previousState = state;
1311 state = 4 /* TokenizerState.EscapeNext */;
1312 continue;
1313 }
1314 switch (state) {
1315 case 0 /* TokenizerState.Static */:
1316 if (char === '/') {
1317 if (buffer) {
1318 consumeBuffer();
1319 }
1320 finalizeSegment();
1321 }
1322 else if (char === ':') {
1323 consumeBuffer();
1324 state = 1 /* TokenizerState.Param */;
1325 }
1326 else {
1327 addCharToBuffer();
1328 }
1329 break;
1330 case 4 /* TokenizerState.EscapeNext */:
1331 addCharToBuffer();
1332 state = previousState;
1333 break;
1334 case 1 /* TokenizerState.Param */:
1335 if (char === '(') {
1336 state = 2 /* TokenizerState.ParamRegExp */;
1337 }
1338 else if (VALID_PARAM_RE.test(char)) {
1339 addCharToBuffer();
1340 }
1341 else {
1342 consumeBuffer();
1343 state = 0 /* TokenizerState.Static */;
1344 // go back one character if we were not modifying
1345 if (char !== '*' && char !== '?' && char !== '+')
1346 i--;
1347 }
1348 break;
1349 case 2 /* TokenizerState.ParamRegExp */:
1350 // TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
1351 // it already works by escaping the closing )
1352 // https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
1353 // is this really something people need since you can also write
1354 // /prefix_:p()_suffix
1355 if (char === ')') {
1356 // handle the escaped )
1357 if (customRe[customRe.length - 1] == '\\')
1358 customRe = customRe.slice(0, -1) + char;
1359 else
1360 state = 3 /* TokenizerState.ParamRegExpEnd */;
1361 }
1362 else {
1363 customRe += char;
1364 }
1365 break;
1366 case 3 /* TokenizerState.ParamRegExpEnd */:
1367 // same as finalizing a param
1368 consumeBuffer();
1369 state = 0 /* TokenizerState.Static */;
1370 // go back one character if we were not modifying
1371 if (char !== '*' && char !== '?' && char !== '+')
1372 i--;
1373 customRe = '';
1374 break;
1375 default:
1376 crash('Unknown state');
1377 break;
1378 }
1379 }
1380 if (state === 2 /* TokenizerState.ParamRegExp */)
1381 crash(`Unfinished custom RegExp for param "${buffer}"`);
1382 consumeBuffer();
1383 finalizeSegment();
1384 // tokenCache.set(path, tokens)
1385 return tokens;
1386}
1387
1388function createRouteRecordMatcher(record, parent, options) {
1389 const parser = tokensToParser(tokenizePath(record.path), options);
1390 // warn against params with the same name
1391 if ((process.env.NODE_ENV !== 'production')) {
1392 const existingKeys = new Set();
1393 for (const key of parser.keys) {
1394 if (existingKeys.has(key.name))
1395 warn(`Found duplicated params with name "${key.name}" for path "${record.path}". Only the last one will be available on "$route.params".`);
1396 existingKeys.add(key.name);
1397 }
1398 }
1399 const matcher = assign(parser, {
1400 record,
1401 parent,
1402 // these needs to be populated by the parent
1403 children: [],
1404 alias: [],
1405 });
1406 if (parent) {
1407 // both are aliases or both are not aliases
1408 // we don't want to mix them because the order is used when
1409 // passing originalRecord in Matcher.addRoute
1410 if (!matcher.record.aliasOf === !parent.record.aliasOf)
1411 parent.children.push(matcher);
1412 }
1413 return matcher;
1414}
1415
1416/**
1417 * Creates a Router Matcher.
1418 *
1419 * @internal
1420 * @param routes - array of initial routes
1421 * @param globalOptions - global route options
1422 */
1423function createRouterMatcher(routes, globalOptions) {
1424 // normalized ordered array of matchers
1425 const matchers = [];
1426 const matcherMap = new Map();
1427 globalOptions = mergeOptions({ strict: false, end: true, sensitive: false }, globalOptions);
1428 function getRecordMatcher(name) {
1429 return matcherMap.get(name);
1430 }
1431 function addRoute(record, parent, originalRecord) {
1432 // used later on to remove by name
1433 const isRootAdd = !originalRecord;
1434 const mainNormalizedRecord = normalizeRouteRecord(record);
1435 if ((process.env.NODE_ENV !== 'production')) {
1436 checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent);
1437 }
1438 // we might be the child of an alias
1439 mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;
1440 const options = mergeOptions(globalOptions, record);
1441 // generate an array of records to correctly handle aliases
1442 const normalizedRecords = [mainNormalizedRecord];
1443 if ('alias' in record) {
1444 const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias;
1445 for (const alias of aliases) {
1446 normalizedRecords.push(
1447 // we need to normalize again to ensure the `mods` property
1448 // being non enumerable
1449 normalizeRouteRecord(assign({}, mainNormalizedRecord, {
1450 // this allows us to hold a copy of the `components` option
1451 // so that async components cache is hold on the original record
1452 components: originalRecord
1453 ? originalRecord.record.components
1454 : mainNormalizedRecord.components,
1455 path: alias,
1456 // we might be the child of an alias
1457 aliasOf: originalRecord
1458 ? originalRecord.record
1459 : mainNormalizedRecord,
1460 // the aliases are always of the same kind as the original since they
1461 // are defined on the same record
1462 })));
1463 }
1464 }
1465 let matcher;
1466 let originalMatcher;
1467 for (const normalizedRecord of normalizedRecords) {
1468 const { path } = normalizedRecord;
1469 // Build up the path for nested routes if the child isn't an absolute
1470 // route. Only add the / delimiter if the child path isn't empty and if the
1471 // parent path doesn't have a trailing slash
1472 if (parent && path[0] !== '/') {
1473 const parentPath = parent.record.path;
1474 const connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
1475 normalizedRecord.path =
1476 parent.record.path + (path && connectingSlash + path);
1477 }
1478 if ((process.env.NODE_ENV !== 'production') && normalizedRecord.path === '*') {
1479 throw new Error('Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +
1480 'See more at https://router.vuejs.org/guide/migration/#Removed-star-or-catch-all-routes.');
1481 }
1482 // create the object beforehand, so it can be passed to children
1483 matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
1484 if ((process.env.NODE_ENV !== 'production') && parent && path[0] === '/')
1485 checkMissingParamsInAbsolutePath(matcher, parent);
1486 // if we are an alias we must tell the original record that we exist,
1487 // so we can be removed
1488 if (originalRecord) {
1489 originalRecord.alias.push(matcher);
1490 if ((process.env.NODE_ENV !== 'production')) {
1491 checkSameParams(originalRecord, matcher);
1492 }
1493 }
1494 else {
1495 // otherwise, the first record is the original and others are aliases
1496 originalMatcher = originalMatcher || matcher;
1497 if (originalMatcher !== matcher)
1498 originalMatcher.alias.push(matcher);
1499 // remove the route if named and only for the top record (avoid in nested calls)
1500 // this works because the original record is the first one
1501 if (isRootAdd && record.name && !isAliasRecord(matcher)) {
1502 if ((process.env.NODE_ENV !== 'production')) {
1503 checkSameNameAsAncestor(record, parent);
1504 }
1505 removeRoute(record.name);
1506 }
1507 }
1508 // Avoid adding a record that doesn't display anything. This allows passing through records without a component to
1509 // not be reached and pass through the catch all route
1510 if (isMatchable(matcher)) {
1511 insertMatcher(matcher);
1512 }
1513 if (mainNormalizedRecord.children) {
1514 const children = mainNormalizedRecord.children;
1515 for (let i = 0; i < children.length; i++) {
1516 addRoute(children[i], matcher, originalRecord && originalRecord.children[i]);
1517 }
1518 }
1519 // if there was no original record, then the first one was not an alias and all
1520 // other aliases (if any) need to reference this record when adding children
1521 originalRecord = originalRecord || matcher;
1522 // TODO: add normalized records for more flexibility
1523 // if (parent && isAliasRecord(originalRecord)) {
1524 // parent.children.push(originalRecord)
1525 // }
1526 }
1527 return originalMatcher
1528 ? () => {
1529 // since other matchers are aliases, they should be removed by the original matcher
1530 removeRoute(originalMatcher);
1531 }
1532 : noop;
1533 }
1534 function removeRoute(matcherRef) {
1535 if (isRouteName(matcherRef)) {
1536 const matcher = matcherMap.get(matcherRef);
1537 if (matcher) {
1538 matcherMap.delete(matcherRef);
1539 matchers.splice(matchers.indexOf(matcher), 1);
1540 matcher.children.forEach(removeRoute);
1541 matcher.alias.forEach(removeRoute);
1542 }
1543 }
1544 else {
1545 const index = matchers.indexOf(matcherRef);
1546 if (index > -1) {
1547 matchers.splice(index, 1);
1548 if (matcherRef.record.name)
1549 matcherMap.delete(matcherRef.record.name);
1550 matcherRef.children.forEach(removeRoute);
1551 matcherRef.alias.forEach(removeRoute);
1552 }
1553 }
1554 }
1555 function getRoutes() {
1556 return matchers;
1557 }
1558 function insertMatcher(matcher) {
1559 const index = findInsertionIndex(matcher, matchers);
1560 matchers.splice(index, 0, matcher);
1561 // only add the original record to the name map
1562 if (matcher.record.name && !isAliasRecord(matcher))
1563 matcherMap.set(matcher.record.name, matcher);
1564 }
1565 function resolve(location, currentLocation) {
1566 let matcher;
1567 let params = {};
1568 let path;
1569 let name;
1570 if ('name' in location && location.name) {
1571 matcher = matcherMap.get(location.name);
1572 if (!matcher)
1573 throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
1574 location,
1575 });
1576 // warn if the user is passing invalid params so they can debug it better when they get removed
1577 if ((process.env.NODE_ENV !== 'production')) {
1578 const invalidParams = Object.keys(location.params || {}).filter(paramName => !matcher.keys.find(k => k.name === paramName));
1579 if (invalidParams.length) {
1580 warn(`Discarded invalid param(s) "${invalidParams.join('", "')}" when navigating. See https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#414-2022-08-22 for more details.`);
1581 }
1582 }
1583 name = matcher.record.name;
1584 params = assign(
1585 // paramsFromLocation is a new object
1586 paramsFromLocation(currentLocation.params,
1587 // only keep params that exist in the resolved location
1588 // only keep optional params coming from a parent record
1589 matcher.keys
1590 .filter(k => !k.optional)
1591 .concat(matcher.parent ? matcher.parent.keys.filter(k => k.optional) : [])
1592 .map(k => k.name)),
1593 // discard any existing params in the current location that do not exist here
1594 // #1497 this ensures better active/exact matching
1595 location.params &&
1596 paramsFromLocation(location.params, matcher.keys.map(k => k.name)));
1597 // throws if cannot be stringified
1598 path = matcher.stringify(params);
1599 }
1600 else if (location.path != null) {
1601 // no need to resolve the path with the matcher as it was provided
1602 // this also allows the user to control the encoding
1603 path = location.path;
1604 if ((process.env.NODE_ENV !== 'production') && !path.startsWith('/')) {
1605 warn(`The Matcher cannot resolve relative paths but received "${path}". Unless you directly called \`matcher.resolve("${path}")\`, this is probably a bug in vue-router. Please open an issue at https://github.com/vuejs/router/issues/new/choose.`);
1606 }
1607 matcher = matchers.find(m => m.re.test(path));
1608 // matcher should have a value after the loop
1609 if (matcher) {
1610 // we know the matcher works because we tested the regexp
1611 params = matcher.parse(path);
1612 name = matcher.record.name;
1613 }
1614 // location is a relative path
1615 }
1616 else {
1617 // match by name or path of current route
1618 matcher = currentLocation.name
1619 ? matcherMap.get(currentLocation.name)
1620 : matchers.find(m => m.re.test(currentLocation.path));
1621 if (!matcher)
1622 throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
1623 location,
1624 currentLocation,
1625 });
1626 name = matcher.record.name;
1627 // since we are navigating to the same location, we don't need to pick the
1628 // params like when `name` is provided
1629 params = assign({}, currentLocation.params, location.params);
1630 path = matcher.stringify(params);
1631 }
1632 const matched = [];
1633 let parentMatcher = matcher;
1634 while (parentMatcher) {
1635 // reversed order so parents are at the beginning
1636 matched.unshift(parentMatcher.record);
1637 parentMatcher = parentMatcher.parent;
1638 }
1639 return {
1640 name,
1641 path,
1642 params,
1643 matched,
1644 meta: mergeMetaFields(matched),
1645 };
1646 }
1647 // add initial routes
1648 routes.forEach(route => addRoute(route));
1649 function clearRoutes() {
1650 matchers.length = 0;
1651 matcherMap.clear();
1652 }
1653 return {
1654 addRoute,
1655 resolve,
1656 removeRoute,
1657 clearRoutes,
1658 getRoutes,
1659 getRecordMatcher,
1660 };
1661}
1662function paramsFromLocation(params, keys) {
1663 const newParams = {};
1664 for (const key of keys) {
1665 if (key in params)
1666 newParams[key] = params[key];
1667 }
1668 return newParams;
1669}
1670/**
1671 * Normalizes a RouteRecordRaw. Creates a copy
1672 *
1673 * @param record
1674 * @returns the normalized version
1675 */
1676function normalizeRouteRecord(record) {
1677 const normalized = {
1678 path: record.path,
1679 redirect: record.redirect,
1680 name: record.name,
1681 meta: record.meta || {},
1682 aliasOf: record.aliasOf,
1683 beforeEnter: record.beforeEnter,
1684 props: normalizeRecordProps(record),
1685 children: record.children || [],
1686 instances: {},
1687 leaveGuards: new Set(),
1688 updateGuards: new Set(),
1689 enterCallbacks: {},
1690 // must be declared afterwards
1691 // mods: {},
1692 components: 'components' in record
1693 ? record.components || null
1694 : record.component && { default: record.component },
1695 };
1696 // mods contain modules and shouldn't be copied,
1697 // logged or anything. It's just used for internal
1698 // advanced use cases like data loaders
1699 Object.defineProperty(normalized, 'mods', {
1700 value: {},
1701 });
1702 return normalized;
1703}
1704/**
1705 * Normalize the optional `props` in a record to always be an object similar to
1706 * components. Also accept a boolean for components.
1707 * @param record
1708 */
1709function normalizeRecordProps(record) {
1710 const propsObject = {};
1711 // props does not exist on redirect records, but we can set false directly
1712 const props = record.props || false;
1713 if ('component' in record) {
1714 propsObject.default = props;
1715 }
1716 else {
1717 // NOTE: we could also allow a function to be applied to every component.
1718 // Would need user feedback for use cases
1719 for (const name in record.components)
1720 propsObject[name] = typeof props === 'object' ? props[name] : props;
1721 }
1722 return propsObject;
1723}
1724/**
1725 * Checks if a record or any of its parent is an alias
1726 * @param record
1727 */
1728function isAliasRecord(record) {
1729 while (record) {
1730 if (record.record.aliasOf)
1731 return true;
1732 record = record.parent;
1733 }
1734 return false;
1735}
1736/**
1737 * Merge meta fields of an array of records
1738 *
1739 * @param matched - array of matched records
1740 */
1741function mergeMetaFields(matched) {
1742 return matched.reduce((meta, record) => assign(meta, record.meta), {});
1743}
1744function mergeOptions(defaults, partialOptions) {
1745 const options = {};
1746 for (const key in defaults) {
1747 options[key] = key in partialOptions ? partialOptions[key] : defaults[key];
1748 }
1749 return options;
1750}
1751function isSameParam(a, b) {
1752 return (a.name === b.name &&
1753 a.optional === b.optional &&
1754 a.repeatable === b.repeatable);
1755}
1756/**
1757 * Check if a path and its alias have the same required params
1758 *
1759 * @param a - original record
1760 * @param b - alias record
1761 */
1762function checkSameParams(a, b) {
1763 for (const key of a.keys) {
1764 if (!key.optional && !b.keys.find(isSameParam.bind(null, key)))
1765 return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`);
1766 }
1767 for (const key of b.keys) {
1768 if (!key.optional && !a.keys.find(isSameParam.bind(null, key)))
1769 return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`);
1770 }
1771}
1772/**
1773 * A route with a name and a child with an empty path without a name should warn when adding the route
1774 *
1775 * @param mainNormalizedRecord - RouteRecordNormalized
1776 * @param parent - RouteRecordMatcher
1777 */
1778function checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent) {
1779 if (parent &&
1780 parent.record.name &&
1781 !mainNormalizedRecord.name &&
1782 !mainNormalizedRecord.path) {
1783 warn(`The route named "${String(parent.record.name)}" has a child without a name and an empty path. Using that name won't render the empty path child so you probably want to move the name to the child instead. If this is intentional, add a name to the child route to remove the warning.`);
1784 }
1785}
1786function checkSameNameAsAncestor(record, parent) {
1787 for (let ancestor = parent; ancestor; ancestor = ancestor.parent) {
1788 if (ancestor.record.name === record.name) {
1789 throw new Error(`A route named "${String(record.name)}" has been added as a ${parent === ancestor ? 'child' : 'descendant'} of a route with the same name. Route names must be unique and a nested route cannot use the same name as an ancestor.`);
1790 }
1791 }
1792}
1793function checkMissingParamsInAbsolutePath(record, parent) {
1794 for (const key of parent.keys) {
1795 if (!record.keys.find(isSameParam.bind(null, key)))
1796 return warn(`Absolute path "${record.record.path}" must have the exact same param named "${key.name}" as its parent "${parent.record.path}".`);
1797 }
1798}
1799/**
1800 * Performs a binary search to find the correct insertion index for a new matcher.
1801 *
1802 * Matchers are primarily sorted by their score. If scores are tied then we also consider parent/child relationships,
1803 * with descendants coming before ancestors. If there's still a tie, new routes are inserted after existing routes.
1804 *
1805 * @param matcher - new matcher to be inserted
1806 * @param matchers - existing matchers
1807 */
1808function findInsertionIndex(matcher, matchers) {
1809 // First phase: binary search based on score
1810 let lower = 0;
1811 let upper = matchers.length;
1812 while (lower !== upper) {
1813 const mid = (lower + upper) >> 1;
1814 const sortOrder = comparePathParserScore(matcher, matchers[mid]);
1815 if (sortOrder < 0) {
1816 upper = mid;
1817 }
1818 else {
1819 lower = mid + 1;
1820 }
1821 }
1822 // Second phase: check for an ancestor with the same score
1823 const insertionAncestor = getInsertionAncestor(matcher);
1824 if (insertionAncestor) {
1825 upper = matchers.lastIndexOf(insertionAncestor, upper - 1);
1826 if ((process.env.NODE_ENV !== 'production') && upper < 0) {
1827 // This should never happen
1828 warn(`Finding ancestor route "${insertionAncestor.record.path}" failed for "${matcher.record.path}"`);
1829 }
1830 }
1831 return upper;
1832}
1833function getInsertionAncestor(matcher) {
1834 let ancestor = matcher;
1835 while ((ancestor = ancestor.parent)) {
1836 if (isMatchable(ancestor) &&
1837 comparePathParserScore(matcher, ancestor) === 0) {
1838 return ancestor;
1839 }
1840 }
1841 return;
1842}
1843/**
1844 * Checks if a matcher can be reachable. This means if it's possible to reach it as a route. For example, routes without
1845 * a component, or name, or redirect, are just used to group other routes.
1846 * @param matcher
1847 * @param matcher.record record of the matcher
1848 * @returns
1849 */
1850function isMatchable({ record }) {
1851 return !!(record.name ||
1852 (record.components && Object.keys(record.components).length) ||
1853 record.redirect);
1854}
1855
1856/**
1857 * Transforms a queryString into a {@link LocationQuery} object. Accept both, a
1858 * version with the leading `?` and without Should work as URLSearchParams
1859
1860 * @internal
1861 *
1862 * @param search - search string to parse
1863 * @returns a query object
1864 */
1865function parseQuery(search) {
1866 const query = {};
1867 // avoid creating an object with an empty key and empty value
1868 // because of split('&')
1869 if (search === '' || search === '?')
1870 return query;
1871 const hasLeadingIM = search[0] === '?';
1872 const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&');
1873 for (let i = 0; i < searchParams.length; ++i) {
1874 // pre decode the + into space
1875 const searchParam = searchParams[i].replace(PLUS_RE, ' ');
1876 // allow the = character
1877 const eqPos = searchParam.indexOf('=');
1878 const key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));
1879 const value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1));
1880 if (key in query) {
1881 // an extra variable for ts types
1882 let currentValue = query[key];
1883 if (!isArray(currentValue)) {
1884 currentValue = query[key] = [currentValue];
1885 }
1886 currentValue.push(value);
1887 }
1888 else {
1889 query[key] = value;
1890 }
1891 }
1892 return query;
1893}
1894/**
1895 * Stringifies a {@link LocationQueryRaw} object. Like `URLSearchParams`, it
1896 * doesn't prepend a `?`
1897 *
1898 * @internal
1899 *
1900 * @param query - query object to stringify
1901 * @returns string version of the query without the leading `?`
1902 */
1903function stringifyQuery(query) {
1904 let search = '';
1905 for (let key in query) {
1906 const value = query[key];
1907 key = encodeQueryKey(key);
1908 if (value == null) {
1909 // only null adds the value
1910 if (value !== undefined) {
1911 search += (search.length ? '&' : '') + key;
1912 }
1913 continue;
1914 }
1915 // keep null values
1916 const values = isArray(value)
1917 ? value.map(v => v && encodeQueryValue(v))
1918 : [value && encodeQueryValue(value)];
1919 values.forEach(value => {
1920 // skip undefined values in arrays as if they were not present
1921 // smaller code than using filter
1922 if (value !== undefined) {
1923 // only append & with non-empty search
1924 search += (search.length ? '&' : '') + key;
1925 if (value != null)
1926 search += '=' + value;
1927 }
1928 });
1929 }
1930 return search;
1931}
1932/**
1933 * Transforms a {@link LocationQueryRaw} into a {@link LocationQuery} by casting
1934 * numbers into strings, removing keys with an undefined value and replacing
1935 * undefined with null in arrays
1936 *
1937 * @param query - query object to normalize
1938 * @returns a normalized query object
1939 */
1940function normalizeQuery(query) {
1941 const normalizedQuery = {};
1942 for (const key in query) {
1943 const value = query[key];
1944 if (value !== undefined) {
1945 normalizedQuery[key] = isArray(value)
1946 ? value.map(v => (v == null ? null : '' + v))
1947 : value == null
1948 ? value
1949 : '' + value;
1950 }
1951 }
1952 return normalizedQuery;
1953}
1954
1955/**
1956 * RouteRecord being rendered by the closest ancestor Router View. Used for
1957 * `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View
1958 * Location Matched
1959 *
1960 * @internal
1961 */
1962const matchedRouteKey = Symbol((process.env.NODE_ENV !== 'production') ? 'router view location matched' : '');
1963/**
1964 * Allows overriding the router view depth to control which component in
1965 * `matched` is rendered. rvd stands for Router View Depth
1966 *
1967 * @internal
1968 */
1969const viewDepthKey = Symbol((process.env.NODE_ENV !== 'production') ? 'router view depth' : '');
1970/**
1971 * Allows overriding the router instance returned by `useRouter` in tests. r
1972 * stands for router
1973 *
1974 * @internal
1975 */
1976const routerKey = Symbol((process.env.NODE_ENV !== 'production') ? 'router' : '');
1977/**
1978 * Allows overriding the current route returned by `useRoute` in tests. rl
1979 * stands for route location
1980 *
1981 * @internal
1982 */
1983const routeLocationKey = Symbol((process.env.NODE_ENV !== 'production') ? 'route location' : '');
1984/**
1985 * Allows overriding the current route used by router-view. Internally this is
1986 * used when the `route` prop is passed.
1987 *
1988 * @internal
1989 */
1990const routerViewLocationKey = Symbol((process.env.NODE_ENV !== 'production') ? 'router view location' : '');
1991
1992/**
1993 * Create a list of callbacks that can be reset. Used to create before and after navigation guards list
1994 */
1995function useCallbacks() {
1996 let handlers = [];
1997 function add(handler) {
1998 handlers.push(handler);
1999 return () => {
2000 const i = handlers.indexOf(handler);
2001 if (i > -1)
2002 handlers.splice(i, 1);
2003 };
2004 }
2005 function reset() {
2006 handlers = [];
2007 }
2008 return {
2009 add,
2010 list: () => handlers.slice(),
2011 reset,
2012 };
2013}
2014
2015function registerGuard(record, name, guard) {
2016 const removeFromList = () => {
2017 record[name].delete(guard);
2018 };
2019 onUnmounted(removeFromList);
2020 onDeactivated(removeFromList);
2021 onActivated(() => {
2022 record[name].add(guard);
2023 });
2024 record[name].add(guard);
2025}
2026/**
2027 * Add a navigation guard that triggers whenever the component for the current
2028 * location is about to be left. Similar to {@link beforeRouteLeave} but can be
2029 * used in any component. The guard is removed when the component is unmounted.
2030 *
2031 * @param leaveGuard - {@link NavigationGuard}
2032 */
2033function onBeforeRouteLeave(leaveGuard) {
2034 if ((process.env.NODE_ENV !== 'production') && !getCurrentInstance()) {
2035 warn('getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function');
2036 return;
2037 }
2038 const activeRecord = inject(matchedRouteKey,
2039 // to avoid warning
2040 {}).value;
2041 if (!activeRecord) {
2042 (process.env.NODE_ENV !== 'production') &&
2043 warn('No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside a component child of <router-view>. Maybe you called it inside of App.vue?');
2044 return;
2045 }
2046 registerGuard(activeRecord, 'leaveGuards', leaveGuard);
2047}
2048/**
2049 * Add a navigation guard that triggers whenever the current location is about
2050 * to be updated. Similar to {@link beforeRouteUpdate} but can be used in any
2051 * component. The guard is removed when the component is unmounted.
2052 *
2053 * @param updateGuard - {@link NavigationGuard}
2054 */
2055function onBeforeRouteUpdate(updateGuard) {
2056 if ((process.env.NODE_ENV !== 'production') && !getCurrentInstance()) {
2057 warn('getCurrentInstance() returned null. onBeforeRouteUpdate() must be called at the top of a setup function');
2058 return;
2059 }
2060 const activeRecord = inject(matchedRouteKey,
2061 // to avoid warning
2062 {}).value;
2063 if (!activeRecord) {
2064 (process.env.NODE_ENV !== 'production') &&
2065 warn('No active route record was found when calling `onBeforeRouteUpdate()`. Make sure you call this function inside a component child of <router-view>. Maybe you called it inside of App.vue?');
2066 return;
2067 }
2068 registerGuard(activeRecord, 'updateGuards', updateGuard);
2069}
2070function guardToPromiseFn(guard, to, from, record, name, runWithContext = fn => fn()) {
2071 // keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place
2072 const enterCallbackArray = record &&
2073 // name is defined if record is because of the function overload
2074 (record.enterCallbacks[name] = record.enterCallbacks[name] || []);
2075 return () => new Promise((resolve, reject) => {
2076 const next = (valid) => {
2077 if (valid === false) {
2078 reject(createRouterError(4 /* ErrorTypes.NAVIGATION_ABORTED */, {
2079 from,
2080 to,
2081 }));
2082 }
2083 else if (valid instanceof Error) {
2084 reject(valid);
2085 }
2086 else if (isRouteLocation(valid)) {
2087 reject(createRouterError(2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */, {
2088 from: to,
2089 to: valid,
2090 }));
2091 }
2092 else {
2093 if (enterCallbackArray &&
2094 // since enterCallbackArray is truthy, both record and name also are
2095 record.enterCallbacks[name] === enterCallbackArray &&
2096 typeof valid === 'function') {
2097 enterCallbackArray.push(valid);
2098 }
2099 resolve();
2100 }
2101 };
2102 // wrapping with Promise.resolve allows it to work with both async and sync guards
2103 const guardReturn = runWithContext(() => guard.call(record && record.instances[name], to, from, (process.env.NODE_ENV !== 'production') ? canOnlyBeCalledOnce(next, to, from) : next));
2104 let guardCall = Promise.resolve(guardReturn);
2105 if (guard.length < 3)
2106 guardCall = guardCall.then(next);
2107 if ((process.env.NODE_ENV !== 'production') && guard.length > 2) {
2108 const message = `The "next" callback was never called inside of ${guard.name ? '"' + guard.name + '"' : ''}:\n${guard.toString()}\n. If you are returning a value instead of calling "next", make sure to remove the "next" parameter from your function.`;
2109 if (typeof guardReturn === 'object' && 'then' in guardReturn) {
2110 guardCall = guardCall.then(resolvedValue => {
2111 // @ts-expect-error: _called is added at canOnlyBeCalledOnce
2112 if (!next._called) {
2113 warn(message);
2114 return Promise.reject(new Error('Invalid navigation guard'));
2115 }
2116 return resolvedValue;
2117 });
2118 }
2119 else if (guardReturn !== undefined) {
2120 // @ts-expect-error: _called is added at canOnlyBeCalledOnce
2121 if (!next._called) {
2122 warn(message);
2123 reject(new Error('Invalid navigation guard'));
2124 return;
2125 }
2126 }
2127 }
2128 guardCall.catch(err => reject(err));
2129 });
2130}
2131function canOnlyBeCalledOnce(next, to, from) {
2132 let called = 0;
2133 return function () {
2134 if (called++ === 1)
2135 warn(`The "next" callback was called more than once in one navigation guard when going from "${from.fullPath}" to "${to.fullPath}". It should be called exactly one time in each navigation guard. This will fail in production.`);
2136 // @ts-expect-error: we put it in the original one because it's easier to check
2137 next._called = true;
2138 if (called === 1)
2139 next.apply(null, arguments);
2140 };
2141}
2142function extractComponentsGuards(matched, guardType, to, from, runWithContext = fn => fn()) {
2143 const guards = [];
2144 for (const record of matched) {
2145 if ((process.env.NODE_ENV !== 'production') && !record.components && !record.children.length) {
2146 warn(`Record with path "${record.path}" is either missing a "component(s)"` +
2147 ` or "children" property.`);
2148 }
2149 for (const name in record.components) {
2150 let rawComponent = record.components[name];
2151 if ((process.env.NODE_ENV !== 'production')) {
2152 if (!rawComponent ||
2153 (typeof rawComponent !== 'object' &&
2154 typeof rawComponent !== 'function')) {
2155 warn(`Component "${name}" in record with path "${record.path}" is not` +
2156 ` a valid component. Received "${String(rawComponent)}".`);
2157 // throw to ensure we stop here but warn to ensure the message isn't
2158 // missed by the user
2159 throw new Error('Invalid route component');
2160 }
2161 else if ('then' in rawComponent) {
2162 // warn if user wrote import('/component.vue') instead of () =>
2163 // import('./component.vue')
2164 warn(`Component "${name}" in record with path "${record.path}" is a ` +
2165 `Promise instead of a function that returns a Promise. Did you ` +
2166 `write "import('./MyPage.vue')" instead of ` +
2167 `"() => import('./MyPage.vue')" ? This will break in ` +
2168 `production if not fixed.`);
2169 const promise = rawComponent;
2170 rawComponent = () => promise;
2171 }
2172 else if (rawComponent.__asyncLoader &&
2173 // warn only once per component
2174 !rawComponent.__warnedDefineAsync) {
2175 rawComponent.__warnedDefineAsync = true;
2176 warn(`Component "${name}" in record with path "${record.path}" is defined ` +
2177 `using "defineAsyncComponent()". ` +
2178 `Write "() => import('./MyPage.vue')" instead of ` +
2179 `"defineAsyncComponent(() => import('./MyPage.vue'))".`);
2180 }
2181 }
2182 // skip update and leave guards if the route component is not mounted
2183 if (guardType !== 'beforeRouteEnter' && !record.instances[name])
2184 continue;
2185 if (isRouteComponent(rawComponent)) {
2186 // __vccOpts is added by vue-class-component and contain the regular options
2187 const options = rawComponent.__vccOpts || rawComponent;
2188 const guard = options[guardType];
2189 guard &&
2190 guards.push(guardToPromiseFn(guard, to, from, record, name, runWithContext));
2191 }
2192 else {
2193 // start requesting the chunk already
2194 let componentPromise = rawComponent();
2195 if ((process.env.NODE_ENV !== 'production') && !('catch' in componentPromise)) {
2196 warn(`Component "${name}" in record with path "${record.path}" is a function that does not return a Promise. If you were passing a functional component, make sure to add a "displayName" to the component. This will break in production if not fixed.`);
2197 componentPromise = Promise.resolve(componentPromise);
2198 }
2199 guards.push(() => componentPromise.then(resolved => {
2200 if (!resolved)
2201 throw new Error(`Couldn't resolve component "${name}" at "${record.path}"`);
2202 const resolvedComponent = isESModule(resolved)
2203 ? resolved.default
2204 : resolved;
2205 // keep the resolved module for plugins like data loaders
2206 record.mods[name] = resolved;
2207 // replace the function with the resolved component
2208 // cannot be null or undefined because we went into the for loop
2209 record.components[name] = resolvedComponent;
2210 // __vccOpts is added by vue-class-component and contain the regular options
2211 const options = resolvedComponent.__vccOpts || resolvedComponent;
2212 const guard = options[guardType];
2213 return (guard &&
2214 guardToPromiseFn(guard, to, from, record, name, runWithContext)());
2215 }));
2216 }
2217 }
2218 }
2219 return guards;
2220}
2221/**
2222 * Ensures a route is loaded, so it can be passed as o prop to `<RouterView>`.
2223 *
2224 * @param route - resolved route to load
2225 */
2226function loadRouteLocation(route) {
2227 return route.matched.every(record => record.redirect)
2228 ? Promise.reject(new Error('Cannot load a route that redirects.'))
2229 : Promise.all(route.matched.map(record => record.components &&
2230 Promise.all(Object.keys(record.components).reduce((promises, name) => {
2231 const rawComponent = record.components[name];
2232 if (typeof rawComponent === 'function' &&
2233 !('displayName' in rawComponent)) {
2234 promises.push(rawComponent().then(resolved => {
2235 if (!resolved)
2236 return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}". Ensure you passed a function that returns a promise.`));
2237 const resolvedComponent = isESModule(resolved)
2238 ? resolved.default
2239 : resolved;
2240 // keep the resolved module for plugins like data loaders
2241 record.mods[name] = resolved;
2242 // replace the function with the resolved component
2243 // cannot be null or undefined because we went into the for loop
2244 record.components[name] = resolvedComponent;
2245 return;
2246 }));
2247 }
2248 return promises;
2249 }, [])))).then(() => route);
2250}
2251
2252// TODO: we could allow currentRoute as a prop to expose `isActive` and
2253// `isExactActive` behavior should go through an RFC
2254/**
2255 * Returns the internal behavior of a {@link RouterLink} without the rendering part.
2256 *
2257 * @param props - a `to` location and an optional `replace` flag
2258 */
2259function useLink(props) {
2260 const router = inject(routerKey);
2261 const currentRoute = inject(routeLocationKey);
2262 let hasPrevious = false;
2263 let previousTo = null;
2264 const route = computed(() => {
2265 const to = unref(props.to);
2266 if ((process.env.NODE_ENV !== 'production') && (!hasPrevious || to !== previousTo)) {
2267 if (!isRouteLocation(to)) {
2268 if (hasPrevious) {
2269 warn(`Invalid value for prop "to" in useLink()\n- to:`, to, `\n- previous to:`, previousTo, `\n- props:`, props);
2270 }
2271 else {
2272 warn(`Invalid value for prop "to" in useLink()\n- to:`, to, `\n- props:`, props);
2273 }
2274 }
2275 previousTo = to;
2276 hasPrevious = true;
2277 }
2278 return router.resolve(to);
2279 });
2280 const activeRecordIndex = computed(() => {
2281 const { matched } = route.value;
2282 const { length } = matched;
2283 const routeMatched = matched[length - 1];
2284 const currentMatched = currentRoute.matched;
2285 if (!routeMatched || !currentMatched.length)
2286 return -1;
2287 const index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched));
2288 if (index > -1)
2289 return index;
2290 // possible parent record
2291 const parentRecordPath = getOriginalPath(matched[length - 2]);
2292 return (
2293 // we are dealing with nested routes
2294 length > 1 &&
2295 // if the parent and matched route have the same path, this link is
2296 // referring to the empty child. Or we currently are on a different
2297 // child of the same parent
2298 getOriginalPath(routeMatched) === parentRecordPath &&
2299 // avoid comparing the child with its parent
2300 currentMatched[currentMatched.length - 1].path !== parentRecordPath
2301 ? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2]))
2302 : index);
2303 });
2304 const isActive = computed(() => activeRecordIndex.value > -1 &&
2305 includesParams(currentRoute.params, route.value.params));
2306 const isExactActive = computed(() => activeRecordIndex.value > -1 &&
2307 activeRecordIndex.value === currentRoute.matched.length - 1 &&
2308 isSameRouteLocationParams(currentRoute.params, route.value.params));
2309 function navigate(e = {}) {
2310 if (guardEvent(e)) {
2311 const p = router[unref(props.replace) ? 'replace' : 'push'](unref(props.to)
2312 // avoid uncaught errors are they are logged anyway
2313 ).catch(noop);
2314 if (props.viewTransition &&
2315 typeof document !== 'undefined' &&
2316 'startViewTransition' in document) {
2317 document.startViewTransition(() => p);
2318 }
2319 return p;
2320 }
2321 return Promise.resolve();
2322 }
2323 // devtools only
2324 if (((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) && isBrowser) {
2325 const instance = getCurrentInstance();
2326 if (instance) {
2327 const linkContextDevtools = {
2328 route: route.value,
2329 isActive: isActive.value,
2330 isExactActive: isExactActive.value,
2331 error: null,
2332 };
2333 // @ts-expect-error: this is internal
2334 instance.__vrl_devtools = instance.__vrl_devtools || [];
2335 // @ts-expect-error: this is internal
2336 instance.__vrl_devtools.push(linkContextDevtools);
2337 watchEffect(() => {
2338 linkContextDevtools.route = route.value;
2339 linkContextDevtools.isActive = isActive.value;
2340 linkContextDevtools.isExactActive = isExactActive.value;
2341 linkContextDevtools.error = isRouteLocation(unref(props.to))
2342 ? null
2343 : 'Invalid "to" value';
2344 }, { flush: 'post' });
2345 }
2346 }
2347 /**
2348 * NOTE: update {@link _RouterLinkI}'s `$slots` type when updating this
2349 */
2350 return {
2351 route,
2352 href: computed(() => route.value.href),
2353 isActive,
2354 isExactActive,
2355 navigate,
2356 };
2357}
2358function preferSingleVNode(vnodes) {
2359 return vnodes.length === 1 ? vnodes[0] : vnodes;
2360}
2361const RouterLinkImpl = /*#__PURE__*/ defineComponent({
2362 name: 'RouterLink',
2363 compatConfig: { MODE: 3 },
2364 props: {
2365 to: {
2366 type: [String, Object],
2367 required: true,
2368 },
2369 replace: Boolean,
2370 activeClass: String,
2371 // inactiveClass: String,
2372 exactActiveClass: String,
2373 custom: Boolean,
2374 ariaCurrentValue: {
2375 type: String,
2376 default: 'page',
2377 },
2378 },
2379 useLink,
2380 setup(props, { slots }) {
2381 const link = reactive(useLink(props));
2382 const { options } = inject(routerKey);
2383 const elClass = computed(() => ({
2384 [getLinkClass(props.activeClass, options.linkActiveClass, 'router-link-active')]: link.isActive,
2385 // [getLinkClass(
2386 // props.inactiveClass,
2387 // options.linkInactiveClass,
2388 // 'router-link-inactive'
2389 // )]: !link.isExactActive,
2390 [getLinkClass(props.exactActiveClass, options.linkExactActiveClass, 'router-link-exact-active')]: link.isExactActive,
2391 }));
2392 return () => {
2393 const children = slots.default && preferSingleVNode(slots.default(link));
2394 return props.custom
2395 ? children
2396 : h('a', {
2397 'aria-current': link.isExactActive
2398 ? props.ariaCurrentValue
2399 : null,
2400 href: link.href,
2401 // this would override user added attrs but Vue will still add
2402 // the listener, so we end up triggering both
2403 onClick: link.navigate,
2404 class: elClass.value,
2405 }, children);
2406 };
2407 },
2408});
2409// export the public type for h/tsx inference
2410// also to avoid inline import() in generated d.ts files
2411/**
2412 * Component to render a link that triggers a navigation on click.
2413 */
2414const RouterLink = RouterLinkImpl;
2415function guardEvent(e) {
2416 // don't redirect with control keys
2417 if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
2418 return;
2419 // don't redirect when preventDefault called
2420 if (e.defaultPrevented)
2421 return;
2422 // don't redirect on right click
2423 if (e.button !== undefined && e.button !== 0)
2424 return;
2425 // don't redirect if `target="_blank"`
2426 // @ts-expect-error getAttribute does exist
2427 if (e.currentTarget && e.currentTarget.getAttribute) {
2428 // @ts-expect-error getAttribute exists
2429 const target = e.currentTarget.getAttribute('target');
2430 if (/\b_blank\b/i.test(target))
2431 return;
2432 }
2433 // this may be a Weex event which doesn't have this method
2434 if (e.preventDefault)
2435 e.preventDefault();
2436 return true;
2437}
2438function includesParams(outer, inner) {
2439 for (const key in inner) {
2440 const innerValue = inner[key];
2441 const outerValue = outer[key];
2442 if (typeof innerValue === 'string') {
2443 if (innerValue !== outerValue)
2444 return false;
2445 }
2446 else {
2447 if (!isArray(outerValue) ||
2448 outerValue.length !== innerValue.length ||
2449 innerValue.some((value, i) => value !== outerValue[i]))
2450 return false;
2451 }
2452 }
2453 return true;
2454}
2455/**
2456 * Get the original path value of a record by following its aliasOf
2457 * @param record
2458 */
2459function getOriginalPath(record) {
2460 return record ? (record.aliasOf ? record.aliasOf.path : record.path) : '';
2461}
2462/**
2463 * Utility class to get the active class based on defaults.
2464 * @param propClass
2465 * @param globalClass
2466 * @param defaultClass
2467 */
2468const getLinkClass = (propClass, globalClass, defaultClass) => propClass != null
2469 ? propClass
2470 : globalClass != null
2471 ? globalClass
2472 : defaultClass;
2473
2474const RouterViewImpl = /*#__PURE__*/ defineComponent({
2475 name: 'RouterView',
2476 // #674 we manually inherit them
2477 inheritAttrs: false,
2478 props: {
2479 name: {
2480 type: String,
2481 default: 'default',
2482 },
2483 route: Object,
2484 },
2485 // Better compat for @vue/compat users
2486 // https://github.com/vuejs/router/issues/1315
2487 compatConfig: { MODE: 3 },
2488 setup(props, { attrs, slots }) {
2489 (process.env.NODE_ENV !== 'production') && warnDeprecatedUsage();
2490 const injectedRoute = inject(routerViewLocationKey);
2491 const routeToDisplay = computed(() => props.route || injectedRoute.value);
2492 const injectedDepth = inject(viewDepthKey, 0);
2493 // The depth changes based on empty components option, which allows passthrough routes e.g. routes with children
2494 // that are used to reuse the `path` property
2495 const depth = computed(() => {
2496 let initialDepth = unref(injectedDepth);
2497 const { matched } = routeToDisplay.value;
2498 let matchedRoute;
2499 while ((matchedRoute = matched[initialDepth]) &&
2500 !matchedRoute.components) {
2501 initialDepth++;
2502 }
2503 return initialDepth;
2504 });
2505 const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth.value]);
2506 provide(viewDepthKey, computed(() => depth.value + 1));
2507 provide(matchedRouteKey, matchedRouteRef);
2508 provide(routerViewLocationKey, routeToDisplay);
2509 const viewRef = ref();
2510 // watch at the same time the component instance, the route record we are
2511 // rendering, and the name
2512 watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {
2513 // copy reused instances
2514 if (to) {
2515 // this will update the instance for new instances as well as reused
2516 // instances when navigating to a new route
2517 to.instances[name] = instance;
2518 // the component instance is reused for a different route or name, so
2519 // we copy any saved update or leave guards. With async setup, the
2520 // mounting component will mount before the matchedRoute changes,
2521 // making instance === oldInstance, so we check if guards have been
2522 // added before. This works because we remove guards when
2523 // unmounting/deactivating components
2524 if (from && from !== to && instance && instance === oldInstance) {
2525 if (!to.leaveGuards.size) {
2526 to.leaveGuards = from.leaveGuards;
2527 }
2528 if (!to.updateGuards.size) {
2529 to.updateGuards = from.updateGuards;
2530 }
2531 }
2532 }
2533 // trigger beforeRouteEnter next callbacks
2534 if (instance &&
2535 to &&
2536 // if there is no instance but to and from are the same this might be
2537 // the first visit
2538 (!from || !isSameRouteRecord(to, from) || !oldInstance)) {
2539 (to.enterCallbacks[name] || []).forEach(callback => callback(instance));
2540 }
2541 }, { flush: 'post' });
2542 return () => {
2543 const route = routeToDisplay.value;
2544 // we need the value at the time we render because when we unmount, we
2545 // navigated to a different location so the value is different
2546 const currentName = props.name;
2547 const matchedRoute = matchedRouteRef.value;
2548 const ViewComponent = matchedRoute && matchedRoute.components[currentName];
2549 if (!ViewComponent) {
2550 return normalizeSlot(slots.default, { Component: ViewComponent, route });
2551 }
2552 // props from route configuration
2553 const routePropsOption = matchedRoute.props[currentName];
2554 const routeProps = routePropsOption
2555 ? routePropsOption === true
2556 ? route.params
2557 : typeof routePropsOption === 'function'
2558 ? routePropsOption(route)
2559 : routePropsOption
2560 : null;
2561 const onVnodeUnmounted = vnode => {
2562 // remove the instance reference to prevent leak
2563 if (vnode.component.isUnmounted) {
2564 matchedRoute.instances[currentName] = null;
2565 }
2566 };
2567 const component = h(ViewComponent, assign({}, routeProps, attrs, {
2568 onVnodeUnmounted,
2569 ref: viewRef,
2570 }));
2571 if (((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) &&
2572 isBrowser &&
2573 component.ref) {
2574 // TODO: can display if it's an alias, its props
2575 const info = {
2576 depth: depth.value,
2577 name: matchedRoute.name,
2578 path: matchedRoute.path,
2579 meta: matchedRoute.meta,
2580 };
2581 const internalInstances = isArray(component.ref)
2582 ? component.ref.map(r => r.i)
2583 : [component.ref.i];
2584 internalInstances.forEach(instance => {
2585 // @ts-expect-error
2586 instance.__vrv_devtools = info;
2587 });
2588 }
2589 return (
2590 // pass the vnode to the slot as a prop.
2591 // h and <component :is="..."> both accept vnodes
2592 normalizeSlot(slots.default, { Component: component, route }) ||
2593 component);
2594 };
2595 },
2596});
2597function normalizeSlot(slot, data) {
2598 if (!slot)
2599 return null;
2600 const slotContent = slot(data);
2601 return slotContent.length === 1 ? slotContent[0] : slotContent;
2602}
2603// export the public type for h/tsx inference
2604// also to avoid inline import() in generated d.ts files
2605/**
2606 * Component to display the current route the user is at.
2607 */
2608const RouterView = RouterViewImpl;
2609// warn against deprecated usage with <transition> & <keep-alive>
2610// due to functional component being no longer eager in Vue 3
2611function warnDeprecatedUsage() {
2612 const instance = getCurrentInstance();
2613 const parentName = instance.parent && instance.parent.type.name;
2614 const parentSubTreeType = instance.parent && instance.parent.subTree && instance.parent.subTree.type;
2615 if (parentName &&
2616 (parentName === 'KeepAlive' || parentName.includes('Transition')) &&
2617 typeof parentSubTreeType === 'object' &&
2618 parentSubTreeType.name === 'RouterView') {
2619 const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition';
2620 warn(`<router-view> can no longer be used directly inside <transition> or <keep-alive>.\n` +
2621 `Use slot props instead:\n\n` +
2622 `<router-view v-slot="{ Component }">\n` +
2623 ` <${comp}>\n` +
2624 ` <component :is="Component" />\n` +
2625 ` </${comp}>\n` +
2626 `</router-view>`);
2627 }
2628}
2629
2630/**
2631 * Copies a route location and removes any problematic properties that cannot be shown in devtools (e.g. Vue instances).
2632 *
2633 * @param routeLocation - routeLocation to format
2634 * @param tooltip - optional tooltip
2635 * @returns a copy of the routeLocation
2636 */
2637function formatRouteLocation(routeLocation, tooltip) {
2638 const copy = assign({}, routeLocation, {
2639 // remove variables that can contain vue instances
2640 matched: routeLocation.matched.map(matched => omit(matched, ['instances', 'children', 'aliasOf'])),
2641 });
2642 return {
2643 _custom: {
2644 type: null,
2645 readOnly: true,
2646 display: routeLocation.fullPath,
2647 tooltip,
2648 value: copy,
2649 },
2650 };
2651}
2652function formatDisplay(display) {
2653 return {
2654 _custom: {
2655 display,
2656 },
2657 };
2658}
2659// to support multiple router instances
2660let routerId = 0;
2661function addDevtools(app, router, matcher) {
2662 // Take over router.beforeEach and afterEach
2663 // make sure we are not registering the devtool twice
2664 if (router.__hasDevtools)
2665 return;
2666 router.__hasDevtools = true;
2667 // increment to support multiple router instances
2668 const id = routerId++;
2669 setupDevtoolsPlugin({
2670 id: 'org.vuejs.router' + (id ? '.' + id : ''),
2671 label: 'Vue Router',
2672 packageName: 'vue-router',
2673 homepage: 'https://router.vuejs.org',
2674 logo: 'https://router.vuejs.org/logo.png',
2675 componentStateTypes: ['Routing'],
2676 app,
2677 }, api => {
2678 if (typeof api.now !== 'function') {
2679 console.warn('[Vue Router]: You seem to be using an outdated version of Vue Devtools. Are you still using the Beta release instead of the stable one? You can find the links at https://devtools.vuejs.org/guide/installation.html.');
2680 }
2681 // display state added by the router
2682 api.on.inspectComponent((payload, ctx) => {
2683 if (payload.instanceData) {
2684 payload.instanceData.state.push({
2685 type: 'Routing',
2686 key: '$route',
2687 editable: false,
2688 value: formatRouteLocation(router.currentRoute.value, 'Current Route'),
2689 });
2690 }
2691 });
2692 // mark router-link as active and display tags on router views
2693 api.on.visitComponentTree(({ treeNode: node, componentInstance }) => {
2694 if (componentInstance.__vrv_devtools) {
2695 const info = componentInstance.__vrv_devtools;
2696 node.tags.push({
2697 label: (info.name ? `${info.name.toString()}: ` : '') + info.path,
2698 textColor: 0,
2699 tooltip: 'This component is rendered by &lt;router-view&gt;',
2700 backgroundColor: PINK_500,
2701 });
2702 }
2703 // if multiple useLink are used
2704 if (isArray(componentInstance.__vrl_devtools)) {
2705 componentInstance.__devtoolsApi = api;
2706 componentInstance.__vrl_devtools.forEach(devtoolsData => {
2707 let label = devtoolsData.route.path;
2708 let backgroundColor = ORANGE_400;
2709 let tooltip = '';
2710 let textColor = 0;
2711 if (devtoolsData.error) {
2712 label = devtoolsData.error;
2713 backgroundColor = RED_100;
2714 textColor = RED_700;
2715 }
2716 else if (devtoolsData.isExactActive) {
2717 backgroundColor = LIME_500;
2718 tooltip = 'This is exactly active';
2719 }
2720 else if (devtoolsData.isActive) {
2721 backgroundColor = BLUE_600;
2722 tooltip = 'This link is active';
2723 }
2724 node.tags.push({
2725 label,
2726 textColor,
2727 tooltip,
2728 backgroundColor,
2729 });
2730 });
2731 }
2732 });
2733 watch(router.currentRoute, () => {
2734 // refresh active state
2735 refreshRoutesView();
2736 api.notifyComponentUpdate();
2737 api.sendInspectorTree(routerInspectorId);
2738 api.sendInspectorState(routerInspectorId);
2739 });
2740 const navigationsLayerId = 'router:navigations:' + id;
2741 api.addTimelineLayer({
2742 id: navigationsLayerId,
2743 label: `Router${id ? ' ' + id : ''} Navigations`,
2744 color: 0x40a8c4,
2745 });
2746 // const errorsLayerId = 'router:errors'
2747 // api.addTimelineLayer({
2748 // id: errorsLayerId,
2749 // label: 'Router Errors',
2750 // color: 0xea5455,
2751 // })
2752 router.onError((error, to) => {
2753 api.addTimelineEvent({
2754 layerId: navigationsLayerId,
2755 event: {
2756 title: 'Error during Navigation',
2757 subtitle: to.fullPath,
2758 logType: 'error',
2759 time: api.now(),
2760 data: { error },
2761 groupId: to.meta.__navigationId,
2762 },
2763 });
2764 });
2765 // attached to `meta` and used to group events
2766 let navigationId = 0;
2767 router.beforeEach((to, from) => {
2768 const data = {
2769 guard: formatDisplay('beforeEach'),
2770 from: formatRouteLocation(from, 'Current Location during this navigation'),
2771 to: formatRouteLocation(to, 'Target location'),
2772 };
2773 // Used to group navigations together, hide from devtools
2774 Object.defineProperty(to.meta, '__navigationId', {
2775 value: navigationId++,
2776 });
2777 api.addTimelineEvent({
2778 layerId: navigationsLayerId,
2779 event: {
2780 time: api.now(),
2781 title: 'Start of navigation',
2782 subtitle: to.fullPath,
2783 data,
2784 groupId: to.meta.__navigationId,
2785 },
2786 });
2787 });
2788 router.afterEach((to, from, failure) => {
2789 const data = {
2790 guard: formatDisplay('afterEach'),
2791 };
2792 if (failure) {
2793 data.failure = {
2794 _custom: {
2795 type: Error,
2796 readOnly: true,
2797 display: failure ? failure.message : '',
2798 tooltip: 'Navigation Failure',
2799 value: failure,
2800 },
2801 };
2802 data.status = formatDisplay('❌');
2803 }
2804 else {
2805 data.status = formatDisplay('✅');
2806 }
2807 // we set here to have the right order
2808 data.from = formatRouteLocation(from, 'Current Location during this navigation');
2809 data.to = formatRouteLocation(to, 'Target location');
2810 api.addTimelineEvent({
2811 layerId: navigationsLayerId,
2812 event: {
2813 title: 'End of navigation',
2814 subtitle: to.fullPath,
2815 time: api.now(),
2816 data,
2817 logType: failure ? 'warning' : 'default',
2818 groupId: to.meta.__navigationId,
2819 },
2820 });
2821 });
2822 /**
2823 * Inspector of Existing routes
2824 */
2825 const routerInspectorId = 'router-inspector:' + id;
2826 api.addInspector({
2827 id: routerInspectorId,
2828 label: 'Routes' + (id ? ' ' + id : ''),
2829 icon: 'book',
2830 treeFilterPlaceholder: 'Search routes',
2831 });
2832 function refreshRoutesView() {
2833 // the routes view isn't active
2834 if (!activeRoutesPayload)
2835 return;
2836 const payload = activeRoutesPayload;
2837 // children routes will appear as nested
2838 let routes = matcher.getRoutes().filter(route => !route.parent ||
2839 // these routes have a parent with no component which will not appear in the view
2840 // therefore we still need to include them
2841 !route.parent.record.components);
2842 // reset match state to false
2843 routes.forEach(resetMatchStateOnRouteRecord);
2844 // apply a match state if there is a payload
2845 if (payload.filter) {
2846 routes = routes.filter(route =>
2847 // save matches state based on the payload
2848 isRouteMatching(route, payload.filter.toLowerCase()));
2849 }
2850 // mark active routes
2851 routes.forEach(route => markRouteRecordActive(route, router.currentRoute.value));
2852 payload.rootNodes = routes.map(formatRouteRecordForInspector);
2853 }
2854 let activeRoutesPayload;
2855 api.on.getInspectorTree(payload => {
2856 activeRoutesPayload = payload;
2857 if (payload.app === app && payload.inspectorId === routerInspectorId) {
2858 refreshRoutesView();
2859 }
2860 });
2861 /**
2862 * Display information about the currently selected route record
2863 */
2864 api.on.getInspectorState(payload => {
2865 if (payload.app === app && payload.inspectorId === routerInspectorId) {
2866 const routes = matcher.getRoutes();
2867 const route = routes.find(route => route.record.__vd_id === payload.nodeId);
2868 if (route) {
2869 payload.state = {
2870 options: formatRouteRecordMatcherForStateInspector(route),
2871 };
2872 }
2873 }
2874 });
2875 api.sendInspectorTree(routerInspectorId);
2876 api.sendInspectorState(routerInspectorId);
2877 });
2878}
2879function modifierForKey(key) {
2880 if (key.optional) {
2881 return key.repeatable ? '*' : '?';
2882 }
2883 else {
2884 return key.repeatable ? '+' : '';
2885 }
2886}
2887function formatRouteRecordMatcherForStateInspector(route) {
2888 const { record } = route;
2889 const fields = [
2890 { editable: false, key: 'path', value: record.path },
2891 ];
2892 if (record.name != null) {
2893 fields.push({
2894 editable: false,
2895 key: 'name',
2896 value: record.name,
2897 });
2898 }
2899 fields.push({ editable: false, key: 'regexp', value: route.re });
2900 if (route.keys.length) {
2901 fields.push({
2902 editable: false,
2903 key: 'keys',
2904 value: {
2905 _custom: {
2906 type: null,
2907 readOnly: true,
2908 display: route.keys
2909 .map(key => `${key.name}${modifierForKey(key)}`)
2910 .join(' '),
2911 tooltip: 'Param keys',
2912 value: route.keys,
2913 },
2914 },
2915 });
2916 }
2917 if (record.redirect != null) {
2918 fields.push({
2919 editable: false,
2920 key: 'redirect',
2921 value: record.redirect,
2922 });
2923 }
2924 if (route.alias.length) {
2925 fields.push({
2926 editable: false,
2927 key: 'aliases',
2928 value: route.alias.map(alias => alias.record.path),
2929 });
2930 }
2931 if (Object.keys(route.record.meta).length) {
2932 fields.push({
2933 editable: false,
2934 key: 'meta',
2935 value: route.record.meta,
2936 });
2937 }
2938 fields.push({
2939 key: 'score',
2940 editable: false,
2941 value: {
2942 _custom: {
2943 type: null,
2944 readOnly: true,
2945 display: route.score.map(score => score.join(', ')).join(' | '),
2946 tooltip: 'Score used to sort routes',
2947 value: route.score,
2948 },
2949 },
2950 });
2951 return fields;
2952}
2953/**
2954 * Extracted from tailwind palette
2955 */
2956const PINK_500 = 0xec4899;
2957const BLUE_600 = 0x2563eb;
2958const LIME_500 = 0x84cc16;
2959const CYAN_400 = 0x22d3ee;
2960const ORANGE_400 = 0xfb923c;
2961// const GRAY_100 = 0xf4f4f5
2962const DARK = 0x666666;
2963const RED_100 = 0xfee2e2;
2964const RED_700 = 0xb91c1c;
2965function formatRouteRecordForInspector(route) {
2966 const tags = [];
2967 const { record } = route;
2968 if (record.name != null) {
2969 tags.push({
2970 label: String(record.name),
2971 textColor: 0,
2972 backgroundColor: CYAN_400,
2973 });
2974 }
2975 if (record.aliasOf) {
2976 tags.push({
2977 label: 'alias',
2978 textColor: 0,
2979 backgroundColor: ORANGE_400,
2980 });
2981 }
2982 if (route.__vd_match) {
2983 tags.push({
2984 label: 'matches',
2985 textColor: 0,
2986 backgroundColor: PINK_500,
2987 });
2988 }
2989 if (route.__vd_exactActive) {
2990 tags.push({
2991 label: 'exact',
2992 textColor: 0,
2993 backgroundColor: LIME_500,
2994 });
2995 }
2996 if (route.__vd_active) {
2997 tags.push({
2998 label: 'active',
2999 textColor: 0,
3000 backgroundColor: BLUE_600,
3001 });
3002 }
3003 if (record.redirect) {
3004 tags.push({
3005 label: typeof record.redirect === 'string'
3006 ? `redirect: ${record.redirect}`
3007 : 'redirects',
3008 textColor: 0xffffff,
3009 backgroundColor: DARK,
3010 });
3011 }
3012 // add an id to be able to select it. Using the `path` is not possible because
3013 // empty path children would collide with their parents
3014 let id = record.__vd_id;
3015 if (id == null) {
3016 id = String(routeRecordId++);
3017 record.__vd_id = id;
3018 }
3019 return {
3020 id,
3021 label: record.path,
3022 tags,
3023 children: route.children.map(formatRouteRecordForInspector),
3024 };
3025}
3026// incremental id for route records and inspector state
3027let routeRecordId = 0;
3028const EXTRACT_REGEXP_RE = /^\/(.*)\/([a-z]*)$/;
3029function markRouteRecordActive(route, currentRoute) {
3030 // no route will be active if matched is empty
3031 // reset the matching state
3032 const isExactActive = currentRoute.matched.length &&
3033 isSameRouteRecord(currentRoute.matched[currentRoute.matched.length - 1], route.record);
3034 route.__vd_exactActive = route.__vd_active = isExactActive;
3035 if (!isExactActive) {
3036 route.__vd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record));
3037 }
3038 route.children.forEach(childRoute => markRouteRecordActive(childRoute, currentRoute));
3039}
3040function resetMatchStateOnRouteRecord(route) {
3041 route.__vd_match = false;
3042 route.children.forEach(resetMatchStateOnRouteRecord);
3043}
3044function isRouteMatching(route, filter) {
3045 const found = String(route.re).match(EXTRACT_REGEXP_RE);
3046 route.__vd_match = false;
3047 if (!found || found.length < 3) {
3048 return false;
3049 }
3050 // use a regexp without $ at the end to match nested routes better
3051 const nonEndingRE = new RegExp(found[1].replace(/\$$/, ''), found[2]);
3052 if (nonEndingRE.test(filter)) {
3053 // mark children as matches
3054 route.children.forEach(child => isRouteMatching(child, filter));
3055 // exception case: `/`
3056 if (route.record.path !== '/' || filter === '/') {
3057 route.__vd_match = route.re.test(filter);
3058 return true;
3059 }
3060 // hide the / route
3061 return false;
3062 }
3063 const path = route.record.path.toLowerCase();
3064 const decodedPath = decode(path);
3065 // also allow partial matching on the path
3066 if (!filter.startsWith('/') &&
3067 (decodedPath.includes(filter) || path.includes(filter)))
3068 return true;
3069 if (decodedPath.startsWith(filter) || path.startsWith(filter))
3070 return true;
3071 if (route.record.name && String(route.record.name).includes(filter))
3072 return true;
3073 return route.children.some(child => isRouteMatching(child, filter));
3074}
3075function omit(obj, keys) {
3076 const ret = {};
3077 for (const key in obj) {
3078 if (!keys.includes(key)) {
3079 // @ts-expect-error
3080 ret[key] = obj[key];
3081 }
3082 }
3083 return ret;
3084}
3085
3086/**
3087 * Creates a Router instance that can be used by a Vue app.
3088 *
3089 * @param options - {@link RouterOptions}
3090 */
3091function createRouter(options) {
3092 const matcher = createRouterMatcher(options.routes, options);
3093 const parseQuery$1 = options.parseQuery || parseQuery;
3094 const stringifyQuery$1 = options.stringifyQuery || stringifyQuery;
3095 const routerHistory = options.history;
3096 if ((process.env.NODE_ENV !== 'production') && !routerHistory)
3097 throw new Error('Provide the "history" option when calling "createRouter()":' +
3098 ' https://router.vuejs.org/api/interfaces/RouterOptions.html#history');
3099 const beforeGuards = useCallbacks();
3100 const beforeResolveGuards = useCallbacks();
3101 const afterGuards = useCallbacks();
3102 const currentRoute = shallowRef(START_LOCATION_NORMALIZED);
3103 let pendingLocation = START_LOCATION_NORMALIZED;
3104 // leave the scrollRestoration if no scrollBehavior is provided
3105 if (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) {
3106 history.scrollRestoration = 'manual';
3107 }
3108 const normalizeParams = applyToParams.bind(null, paramValue => '' + paramValue);
3109 const encodeParams = applyToParams.bind(null, encodeParam);
3110 const decodeParams =
3111 // @ts-expect-error: intentionally avoid the type check
3112 applyToParams.bind(null, decode);
3113 function addRoute(parentOrRoute, route) {
3114 let parent;
3115 let record;
3116 if (isRouteName(parentOrRoute)) {
3117 parent = matcher.getRecordMatcher(parentOrRoute);
3118 if ((process.env.NODE_ENV !== 'production') && !parent) {
3119 warn(`Parent route "${String(parentOrRoute)}" not found when adding child route`, route);
3120 }
3121 record = route;
3122 }
3123 else {
3124 record = parentOrRoute;
3125 }
3126 return matcher.addRoute(record, parent);
3127 }
3128 function removeRoute(name) {
3129 const recordMatcher = matcher.getRecordMatcher(name);
3130 if (recordMatcher) {
3131 matcher.removeRoute(recordMatcher);
3132 }
3133 else if ((process.env.NODE_ENV !== 'production')) {
3134 warn(`Cannot remove non-existent route "${String(name)}"`);
3135 }
3136 }
3137 function getRoutes() {
3138 return matcher.getRoutes().map(routeMatcher => routeMatcher.record);
3139 }
3140 function hasRoute(name) {
3141 return !!matcher.getRecordMatcher(name);
3142 }
3143 function resolve(rawLocation, currentLocation) {
3144 // const resolve: Router['resolve'] = (rawLocation: RouteLocationRaw, currentLocation) => {
3145 // const objectLocation = routerLocationAsObject(rawLocation)
3146 // we create a copy to modify it later
3147 currentLocation = assign({}, currentLocation || currentRoute.value);
3148 if (typeof rawLocation === 'string') {
3149 const locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path);
3150 const matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation);
3151 const href = routerHistory.createHref(locationNormalized.fullPath);
3152 if ((process.env.NODE_ENV !== 'production')) {
3153 if (href.startsWith('//'))
3154 warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);
3155 else if (!matchedRoute.matched.length) {
3156 warn(`No match found for location with path "${rawLocation}"`);
3157 }
3158 }
3159 // locationNormalized is always a new object
3160 return assign(locationNormalized, matchedRoute, {
3161 params: decodeParams(matchedRoute.params),
3162 hash: decode(locationNormalized.hash),
3163 redirectedFrom: undefined,
3164 href,
3165 });
3166 }
3167 if ((process.env.NODE_ENV !== 'production') && !isRouteLocation(rawLocation)) {
3168 warn(`router.resolve() was passed an invalid location. This will fail in production.\n- Location:`, rawLocation);
3169 return resolve({});
3170 }
3171 let matcherLocation;
3172 // path could be relative in object as well
3173 if (rawLocation.path != null) {
3174 if ((process.env.NODE_ENV !== 'production') &&
3175 'params' in rawLocation &&
3176 !('name' in rawLocation) &&
3177 // @ts-expect-error: the type is never
3178 Object.keys(rawLocation.params).length) {
3179 warn(`Path "${rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`);
3180 }
3181 matcherLocation = assign({}, rawLocation, {
3182 path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path,
3183 });
3184 }
3185 else {
3186 // remove any nullish param
3187 const targetParams = assign({}, rawLocation.params);
3188 for (const key in targetParams) {
3189 if (targetParams[key] == null) {
3190 delete targetParams[key];
3191 }
3192 }
3193 // pass encoded values to the matcher, so it can produce encoded path and fullPath
3194 matcherLocation = assign({}, rawLocation, {
3195 params: encodeParams(targetParams),
3196 });
3197 // current location params are decoded, we need to encode them in case the
3198 // matcher merges the params
3199 currentLocation.params = encodeParams(currentLocation.params);
3200 }
3201 const matchedRoute = matcher.resolve(matcherLocation, currentLocation);
3202 const hash = rawLocation.hash || '';
3203 if ((process.env.NODE_ENV !== 'production') && hash && !hash.startsWith('#')) {
3204 warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`);
3205 }
3206 // the matcher might have merged current location params, so
3207 // we need to run the decoding again
3208 matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
3209 const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
3210 hash: encodeHash(hash),
3211 path: matchedRoute.path,
3212 }));
3213 const href = routerHistory.createHref(fullPath);
3214 if ((process.env.NODE_ENV !== 'production')) {
3215 if (href.startsWith('//')) {
3216 warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);
3217 }
3218 else if (!matchedRoute.matched.length) {
3219 warn(`No match found for location with path "${rawLocation.path != null ? rawLocation.path : rawLocation}"`);
3220 }
3221 }
3222 return assign({
3223 fullPath,
3224 // keep the hash encoded so fullPath is effectively path + encodedQuery +
3225 // hash
3226 hash,
3227 query:
3228 // if the user is using a custom query lib like qs, we might have
3229 // nested objects, so we keep the query as is, meaning it can contain
3230 // numbers at `$route.query`, but at the point, the user will have to
3231 // use their own type anyway.
3232 // https://github.com/vuejs/router/issues/328#issuecomment-649481567
3233 stringifyQuery$1 === stringifyQuery
3234 ? normalizeQuery(rawLocation.query)
3235 : (rawLocation.query || {}),
3236 }, matchedRoute, {
3237 redirectedFrom: undefined,
3238 href,
3239 });
3240 }
3241 function locationAsObject(to) {
3242 return typeof to === 'string'
3243 ? parseURL(parseQuery$1, to, currentRoute.value.path)
3244 : assign({}, to);
3245 }
3246 function checkCanceledNavigation(to, from) {
3247 if (pendingLocation !== to) {
3248 return createRouterError(8 /* ErrorTypes.NAVIGATION_CANCELLED */, {
3249 from,
3250 to,
3251 });
3252 }
3253 }
3254 function push(to) {
3255 return pushWithRedirect(to);
3256 }
3257 function replace(to) {
3258 return push(assign(locationAsObject(to), { replace: true }));
3259 }
3260 function handleRedirectRecord(to) {
3261 const lastMatched = to.matched[to.matched.length - 1];
3262 if (lastMatched && lastMatched.redirect) {
3263 const { redirect } = lastMatched;
3264 let newTargetLocation = typeof redirect === 'function' ? redirect(to) : redirect;
3265 if (typeof newTargetLocation === 'string') {
3266 newTargetLocation =
3267 newTargetLocation.includes('?') || newTargetLocation.includes('#')
3268 ? (newTargetLocation = locationAsObject(newTargetLocation))
3269 : // force empty params
3270 { path: newTargetLocation };
3271 // @ts-expect-error: force empty params when a string is passed to let
3272 // the router parse them again
3273 newTargetLocation.params = {};
3274 }
3275 if ((process.env.NODE_ENV !== 'production') &&
3276 newTargetLocation.path == null &&
3277 !('name' in newTargetLocation)) {
3278 warn(`Invalid redirect found:\n${JSON.stringify(newTargetLocation, null, 2)}\n when navigating to "${to.fullPath}". A redirect must contain a name or path. This will break in production.`);
3279 throw new Error('Invalid redirect');
3280 }
3281 return assign({
3282 query: to.query,
3283 hash: to.hash,
3284 // avoid transferring params if the redirect has a path
3285 params: newTargetLocation.path != null ? {} : to.params,
3286 }, newTargetLocation);
3287 }
3288 }
3289 function pushWithRedirect(to, redirectedFrom) {
3290 const targetLocation = (pendingLocation = resolve(to));
3291 const from = currentRoute.value;
3292 const data = to.state;
3293 const force = to.force;
3294 // to could be a string where `replace` is a function
3295 const replace = to.replace === true;
3296 const shouldRedirect = handleRedirectRecord(targetLocation);
3297 if (shouldRedirect)
3298 return pushWithRedirect(assign(locationAsObject(shouldRedirect), {
3299 state: typeof shouldRedirect === 'object'
3300 ? assign({}, data, shouldRedirect.state)
3301 : data,
3302 force,
3303 replace,
3304 }),
3305 // keep original redirectedFrom if it exists
3306 redirectedFrom || targetLocation);
3307 // if it was a redirect we already called `pushWithRedirect` above
3308 const toLocation = targetLocation;
3309 toLocation.redirectedFrom = redirectedFrom;
3310 let failure;
3311 if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
3312 failure = createRouterError(16 /* ErrorTypes.NAVIGATION_DUPLICATED */, { to: toLocation, from });
3313 // trigger scroll to allow scrolling to the same anchor
3314 handleScroll(from, from,
3315 // this is a push, the only way for it to be triggered from a
3316 // history.listen is with a redirect, which makes it become a push
3317 true,
3318 // This cannot be the first navigation because the initial location
3319 // cannot be manually navigated to
3320 false);
3321 }
3322 return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
3323 .catch((error) => isNavigationFailure(error)
3324 ? // navigation redirects still mark the router as ready
3325 isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)
3326 ? error
3327 : markAsReady(error) // also returns the error
3328 : // reject any unknown error
3329 triggerError(error, toLocation, from))
3330 .then((failure) => {
3331 if (failure) {
3332 if (isNavigationFailure(failure, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
3333 if ((process.env.NODE_ENV !== 'production') &&
3334 // we are redirecting to the same location we were already at
3335 isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) &&
3336 // and we have done it a couple of times
3337 redirectedFrom &&
3338 // @ts-expect-error: added only in dev
3339 (redirectedFrom._count = redirectedFrom._count
3340 ? // @ts-expect-error
3341 redirectedFrom._count + 1
3342 : 1) > 30) {
3343 warn(`Detected a possibly infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow.\n Are you always returning a new location within a navigation guard? That would lead to this error. Only return when redirecting or aborting, that should fix this. This might break in production if not fixed.`);
3344 return Promise.reject(new Error('Infinite redirect in navigation guard'));
3345 }
3346 return pushWithRedirect(
3347 // keep options
3348 assign({
3349 // preserve an existing replacement but allow the redirect to override it
3350 replace,
3351 }, locationAsObject(failure.to), {
3352 state: typeof failure.to === 'object'
3353 ? assign({}, data, failure.to.state)
3354 : data,
3355 force,
3356 }),
3357 // preserve the original redirectedFrom if any
3358 redirectedFrom || toLocation);
3359 }
3360 }
3361 else {
3362 // if we fail we don't finalize the navigation
3363 failure = finalizeNavigation(toLocation, from, true, replace, data);
3364 }
3365 triggerAfterEach(toLocation, from, failure);
3366 return failure;
3367 });
3368 }
3369 /**
3370 * Helper to reject and skip all navigation guards if a new navigation happened
3371 * @param to
3372 * @param from
3373 */
3374 function checkCanceledNavigationAndReject(to, from) {
3375 const error = checkCanceledNavigation(to, from);
3376 return error ? Promise.reject(error) : Promise.resolve();
3377 }
3378 function runWithContext(fn) {
3379 const app = installedApps.values().next().value;
3380 // support Vue < 3.3
3381 return app && typeof app.runWithContext === 'function'
3382 ? app.runWithContext(fn)
3383 : fn();
3384 }
3385 // TODO: refactor the whole before guards by internally using router.beforeEach
3386 function navigate(to, from) {
3387 let guards;
3388 const [leavingRecords, updatingRecords, enteringRecords] = extractChangingRecords(to, from);
3389 // all components here have been resolved once because we are leaving
3390 guards = extractComponentsGuards(leavingRecords.reverse(), 'beforeRouteLeave', to, from);
3391 // leavingRecords is already reversed
3392 for (const record of leavingRecords) {
3393 record.leaveGuards.forEach(guard => {
3394 guards.push(guardToPromiseFn(guard, to, from));
3395 });
3396 }
3397 const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from);
3398 guards.push(canceledNavigationCheck);
3399 // run the queue of per route beforeRouteLeave guards
3400 return (runGuardQueue(guards)
3401 .then(() => {
3402 // check global guards beforeEach
3403 guards = [];
3404 for (const guard of beforeGuards.list()) {
3405 guards.push(guardToPromiseFn(guard, to, from));
3406 }
3407 guards.push(canceledNavigationCheck);
3408 return runGuardQueue(guards);
3409 })
3410 .then(() => {
3411 // check in components beforeRouteUpdate
3412 guards = extractComponentsGuards(updatingRecords, 'beforeRouteUpdate', to, from);
3413 for (const record of updatingRecords) {
3414 record.updateGuards.forEach(guard => {
3415 guards.push(guardToPromiseFn(guard, to, from));
3416 });
3417 }
3418 guards.push(canceledNavigationCheck);
3419 // run the queue of per route beforeEnter guards
3420 return runGuardQueue(guards);
3421 })
3422 .then(() => {
3423 // check the route beforeEnter
3424 guards = [];
3425 for (const record of enteringRecords) {
3426 // do not trigger beforeEnter on reused views
3427 if (record.beforeEnter) {
3428 if (isArray(record.beforeEnter)) {
3429 for (const beforeEnter of record.beforeEnter)
3430 guards.push(guardToPromiseFn(beforeEnter, to, from));
3431 }
3432 else {
3433 guards.push(guardToPromiseFn(record.beforeEnter, to, from));
3434 }
3435 }
3436 }
3437 guards.push(canceledNavigationCheck);
3438 // run the queue of per route beforeEnter guards
3439 return runGuardQueue(guards);
3440 })
3441 .then(() => {
3442 // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
3443 // clear existing enterCallbacks, these are added by extractComponentsGuards
3444 to.matched.forEach(record => (record.enterCallbacks = {}));
3445 // check in-component beforeRouteEnter
3446 guards = extractComponentsGuards(enteringRecords, 'beforeRouteEnter', to, from, runWithContext);
3447 guards.push(canceledNavigationCheck);
3448 // run the queue of per route beforeEnter guards
3449 return runGuardQueue(guards);
3450 })
3451 .then(() => {
3452 // check global guards beforeResolve
3453 guards = [];
3454 for (const guard of beforeResolveGuards.list()) {
3455 guards.push(guardToPromiseFn(guard, to, from));
3456 }
3457 guards.push(canceledNavigationCheck);
3458 return runGuardQueue(guards);
3459 })
3460 // catch any navigation canceled
3461 .catch(err => isNavigationFailure(err, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)
3462 ? err
3463 : Promise.reject(err)));
3464 }
3465 function triggerAfterEach(to, from, failure) {
3466 // navigation is confirmed, call afterGuards
3467 // TODO: wrap with error handlers
3468 afterGuards
3469 .list()
3470 .forEach(guard => runWithContext(() => guard(to, from, failure)));
3471 }
3472 /**
3473 * - Cleans up any navigation guards
3474 * - Changes the url if necessary
3475 * - Calls the scrollBehavior
3476 */
3477 function finalizeNavigation(toLocation, from, isPush, replace, data) {
3478 // a more recent navigation took place
3479 const error = checkCanceledNavigation(toLocation, from);
3480 if (error)
3481 return error;
3482 // only consider as push if it's not the first navigation
3483 const isFirstNavigation = from === START_LOCATION_NORMALIZED;
3484 const state = !isBrowser ? {} : history.state;
3485 // change URL only if the user did a push/replace and if it's not the initial navigation because
3486 // it's just reflecting the url
3487 if (isPush) {
3488 // on the initial navigation, we want to reuse the scroll position from
3489 // history state if it exists
3490 if (replace || isFirstNavigation)
3491 routerHistory.replace(toLocation.fullPath, assign({
3492 scroll: isFirstNavigation && state && state.scroll,
3493 }, data));
3494 else
3495 routerHistory.push(toLocation.fullPath, data);
3496 }
3497 // accept current navigation
3498 currentRoute.value = toLocation;
3499 handleScroll(toLocation, from, isPush, isFirstNavigation);
3500 markAsReady();
3501 }
3502 let removeHistoryListener;
3503 // attach listener to history to trigger navigations
3504 function setupListeners() {
3505 // avoid setting up listeners twice due to an invalid first navigation
3506 if (removeHistoryListener)
3507 return;
3508 removeHistoryListener = routerHistory.listen((to, _from, info) => {
3509 if (!router.listening)
3510 return;
3511 // cannot be a redirect route because it was in history
3512 const toLocation = resolve(to);
3513 // due to dynamic routing, and to hash history with manual navigation
3514 // (manually changing the url or calling history.hash = '#/somewhere'),
3515 // there could be a redirect record in history
3516 const shouldRedirect = handleRedirectRecord(toLocation);
3517 if (shouldRedirect) {
3518 pushWithRedirect(assign(shouldRedirect, { replace: true, force: true }), toLocation).catch(noop);
3519 return;
3520 }
3521 pendingLocation = toLocation;
3522 const from = currentRoute.value;
3523 // TODO: should be moved to web history?
3524 if (isBrowser) {
3525 saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
3526 }
3527 navigate(toLocation, from)
3528 .catch((error) => {
3529 if (isNavigationFailure(error, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
3530 return error;
3531 }
3532 if (isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
3533 // Here we could call if (info.delta) routerHistory.go(-info.delta,
3534 // false) but this is bug prone as we have no way to wait the
3535 // navigation to be finished before calling pushWithRedirect. Using
3536 // a setTimeout of 16ms seems to work but there is no guarantee for
3537 // it to work on every browser. So instead we do not restore the
3538 // history entry and trigger a new navigation as requested by the
3539 // navigation guard.
3540 // the error is already handled by router.push we just want to avoid
3541 // logging the error
3542 pushWithRedirect(assign(locationAsObject(error.to), {
3543 force: true,
3544 }), toLocation
3545 // avoid an uncaught rejection, let push call triggerError
3546 )
3547 .then(failure => {
3548 // manual change in hash history #916 ending up in the URL not
3549 // changing, but it was changed by the manual url change, so we
3550 // need to manually change it ourselves
3551 if (isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ |
3552 16 /* ErrorTypes.NAVIGATION_DUPLICATED */) &&
3553 !info.delta &&
3554 info.type === NavigationType.pop) {
3555 routerHistory.go(-1, false);
3556 }
3557 })
3558 .catch(noop);
3559 // avoid the then branch
3560 return Promise.reject();
3561 }
3562 // do not restore history on unknown direction
3563 if (info.delta) {
3564 routerHistory.go(-info.delta, false);
3565 }
3566 // unrecognized error, transfer to the global handler
3567 return triggerError(error, toLocation, from);
3568 })
3569 .then((failure) => {
3570 failure =
3571 failure ||
3572 finalizeNavigation(
3573 // after navigation, all matched components are resolved
3574 toLocation, from, false);
3575 // revert the navigation
3576 if (failure) {
3577 if (info.delta &&
3578 // a new navigation has been triggered, so we do not want to revert, that will change the current history
3579 // entry while a different route is displayed
3580 !isNavigationFailure(failure, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
3581 routerHistory.go(-info.delta, false);
3582 }
3583 else if (info.type === NavigationType.pop &&
3584 isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 16 /* ErrorTypes.NAVIGATION_DUPLICATED */)) {
3585 // manual change in hash history #916
3586 // it's like a push but lacks the information of the direction
3587 routerHistory.go(-1, false);
3588 }
3589 }
3590 triggerAfterEach(toLocation, from, failure);
3591 })
3592 // avoid warnings in the console about uncaught rejections, they are logged by triggerErrors
3593 .catch(noop);
3594 });
3595 }
3596 // Initialization and Errors
3597 let readyHandlers = useCallbacks();
3598 let errorListeners = useCallbacks();
3599 let ready;
3600 /**
3601 * Trigger errorListeners added via onError and throws the error as well
3602 *
3603 * @param error - error to throw
3604 * @param to - location we were navigating to when the error happened
3605 * @param from - location we were navigating from when the error happened
3606 * @returns the error as a rejected promise
3607 */
3608 function triggerError(error, to, from) {
3609 markAsReady(error);
3610 const list = errorListeners.list();
3611 if (list.length) {
3612 list.forEach(handler => handler(error, to, from));
3613 }
3614 else {
3615 if ((process.env.NODE_ENV !== 'production')) {
3616 warn('uncaught error during route navigation:');
3617 }
3618 console.error(error);
3619 }
3620 // reject the error no matter there were error listeners or not
3621 return Promise.reject(error);
3622 }
3623 function isReady() {
3624 if (ready && currentRoute.value !== START_LOCATION_NORMALIZED)
3625 return Promise.resolve();
3626 return new Promise((resolve, reject) => {
3627 readyHandlers.add([resolve, reject]);
3628 });
3629 }
3630 function markAsReady(err) {
3631 if (!ready) {
3632 // still not ready if an error happened
3633 ready = !err;
3634 setupListeners();
3635 readyHandlers
3636 .list()
3637 .forEach(([resolve, reject]) => (err ? reject(err) : resolve()));
3638 readyHandlers.reset();
3639 }
3640 return err;
3641 }
3642 // Scroll behavior
3643 function handleScroll(to, from, isPush, isFirstNavigation) {
3644 const { scrollBehavior } = options;
3645 if (!isBrowser || !scrollBehavior)
3646 return Promise.resolve();
3647 const scrollPosition = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) ||
3648 ((isFirstNavigation || !isPush) &&
3649 history.state &&
3650 history.state.scroll) ||
3651 null;
3652 return nextTick()
3653 .then(() => scrollBehavior(to, from, scrollPosition))
3654 .then(position => position && scrollToPosition(position))
3655 .catch(err => triggerError(err, to, from));
3656 }
3657 const go = (delta) => routerHistory.go(delta);
3658 let started;
3659 const installedApps = new Set();
3660 const router = {
3661 currentRoute,
3662 listening: true,
3663 addRoute,
3664 removeRoute,
3665 clearRoutes: matcher.clearRoutes,
3666 hasRoute,
3667 getRoutes,
3668 resolve,
3669 options,
3670 push,
3671 replace,
3672 go,
3673 back: () => go(-1),
3674 forward: () => go(1),
3675 beforeEach: beforeGuards.add,
3676 beforeResolve: beforeResolveGuards.add,
3677 afterEach: afterGuards.add,
3678 onError: errorListeners.add,
3679 isReady,
3680 install(app) {
3681 const router = this;
3682 app.component('RouterLink', RouterLink);
3683 app.component('RouterView', RouterView);
3684 app.config.globalProperties.$router = router;
3685 Object.defineProperty(app.config.globalProperties, '$route', {
3686 enumerable: true,
3687 get: () => unref(currentRoute),
3688 });
3689 // this initial navigation is only necessary on client, on server it doesn't
3690 // make sense because it will create an extra unnecessary navigation and could
3691 // lead to problems
3692 if (isBrowser &&
3693 // used for the initial navigation client side to avoid pushing
3694 // multiple times when the router is used in multiple apps
3695 !started &&
3696 currentRoute.value === START_LOCATION_NORMALIZED) {
3697 // see above
3698 started = true;
3699 push(routerHistory.location).catch(err => {
3700 if ((process.env.NODE_ENV !== 'production'))
3701 warn('Unexpected error when starting the router:', err);
3702 });
3703 }
3704 const reactiveRoute = {};
3705 for (const key in START_LOCATION_NORMALIZED) {
3706 Object.defineProperty(reactiveRoute, key, {
3707 get: () => currentRoute.value[key],
3708 enumerable: true,
3709 });
3710 }
3711 app.provide(routerKey, router);
3712 app.provide(routeLocationKey, shallowReactive(reactiveRoute));
3713 app.provide(routerViewLocationKey, currentRoute);
3714 const unmountApp = app.unmount;
3715 installedApps.add(app);
3716 app.unmount = function () {
3717 installedApps.delete(app);
3718 // the router is not attached to an app anymore
3719 if (installedApps.size < 1) {
3720 // invalidate the current navigation
3721 pendingLocation = START_LOCATION_NORMALIZED;
3722 removeHistoryListener && removeHistoryListener();
3723 removeHistoryListener = null;
3724 currentRoute.value = START_LOCATION_NORMALIZED;
3725 started = false;
3726 ready = false;
3727 }
3728 unmountApp();
3729 };
3730 // TODO: this probably needs to be updated so it can be used by vue-termui
3731 if (((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) && isBrowser) {
3732 addDevtools(app, router, matcher);
3733 }
3734 },
3735 };
3736 // TODO: type this as NavigationGuardReturn or similar instead of any
3737 function runGuardQueue(guards) {
3738 return guards.reduce((promise, guard) => promise.then(() => runWithContext(guard)), Promise.resolve());
3739 }
3740 return router;
3741}
3742function extractChangingRecords(to, from) {
3743 const leavingRecords = [];
3744 const updatingRecords = [];
3745 const enteringRecords = [];
3746 const len = Math.max(from.matched.length, to.matched.length);
3747 for (let i = 0; i < len; i++) {
3748 const recordFrom = from.matched[i];
3749 if (recordFrom) {
3750 if (to.matched.find(record => isSameRouteRecord(record, recordFrom)))
3751 updatingRecords.push(recordFrom);
3752 else
3753 leavingRecords.push(recordFrom);
3754 }
3755 const recordTo = to.matched[i];
3756 if (recordTo) {
3757 // the type doesn't matter because we are comparing per reference
3758 if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {
3759 enteringRecords.push(recordTo);
3760 }
3761 }
3762 }
3763 return [leavingRecords, updatingRecords, enteringRecords];
3764}
3765
3766/**
3767 * Returns the router instance. Equivalent to using `$router` inside
3768 * templates.
3769 */
3770function useRouter() {
3771 return inject(routerKey);
3772}
3773/**
3774 * Returns the current route location. Equivalent to using `$route` inside
3775 * templates.
3776 */
3777function useRoute(_name) {
3778 return inject(routeLocationKey);
3779}
3780
3781export { NavigationFailureType, RouterLink, RouterView, START_LOCATION_NORMALIZED as START_LOCATION, createMemoryHistory, createRouter, createRouterMatcher, createWebHashHistory, createWebHistory, isNavigationFailure, loadRouteLocation, matchedRouteKey, onBeforeRouteLeave, onBeforeRouteUpdate, parseQuery, routeLocationKey, routerKey, routerViewLocationKey, stringifyQuery, useLink, useRoute, useRouter, viewDepthKey };