UNPKG

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