UNPKG

118 kBJavaScriptView Raw
1/*!
2 * vue-router v4.0.0-rc.1
3 * (c) 2020 Eduardo San Martin Morote
4 * @license MIT
5 */
6var VueRouter = (function (exports, vue) {
7 'use strict';
8
9 const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
10 const PolySymbol = (name) =>
11 // vr = vue router
12 hasSymbol
13 ? Symbol( '[vue-router]: ' + name )
14 : ( '[vue-router]: ' ) + name;
15 // rvlm = Router View Location Matched
16 const matchedRouteKey = /*#__PURE__*/ PolySymbol( 'router view location matched' );
17 // rvd = Router View Depth
18 const viewDepthKey = /*#__PURE__*/ PolySymbol( 'router view depth' );
19 // r = router
20 const routerKey = /*#__PURE__*/ PolySymbol( 'router' );
21 // rt = route location
22 const routeLocationKey = /*#__PURE__*/ PolySymbol( 'route location' );
23
24 const isBrowser = typeof window !== 'undefined';
25
26 function isESModule(obj) {
27 return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module');
28 }
29 const assign = Object.assign;
30 function applyToParams(fn, params) {
31 const newParams = {};
32 for (const key in params) {
33 const value = params[key];
34 newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value);
35 }
36 return newParams;
37 }
38 let noop = () => { };
39
40 function warn(msg) {
41 // avoid using ...args as it breaks in older Edge builds
42 const args = Array.from(arguments).slice(1);
43 console.warn.apply(console, ['[Vue Router warn]: ' + msg].concat(args));
44 }
45
46 const TRAILING_SLASH_RE = /\/$/;
47 const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, '');
48 /**
49 * Transforms an URI into a normalized history location
50 *
51 * @param parseQuery
52 * @param location - URI to normalize
53 * @param currentLocation - current absolute location. Allows resolving relative
54 * paths. Must start with `/`. Defaults to `/`
55 * @returns a normalized history location
56 */
57 function parseURL(parseQuery, location, currentLocation = '/') {
58 let path, query = {}, searchString = '', hash = '';
59 // Could use URL and URLSearchParams but IE 11 doesn't support it
60 const searchPos = location.indexOf('?');
61 const hashPos = location.indexOf('#', searchPos > -1 ? searchPos : 0);
62 if (searchPos > -1) {
63 path = location.slice(0, searchPos);
64 searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length);
65 query = parseQuery(searchString);
66 }
67 if (hashPos > -1) {
68 path = path || location.slice(0, hashPos);
69 // keep the # character
70 hash = location.slice(hashPos, location.length);
71 }
72 // no search and no query
73 path = resolveRelativePath(path != null ? path : location, currentLocation);
74 // empty path means a relative query or hash `?foo=f`, `#thing`
75 return {
76 fullPath: path + (searchString && '?') + searchString + hash,
77 path,
78 query,
79 hash,
80 };
81 }
82 /**
83 * Stringifies a URL object
84 *
85 * @param stringifyQuery
86 * @param location
87 */
88 function stringifyURL(stringifyQuery, location) {
89 let query = location.query ? stringifyQuery(location.query) : '';
90 return location.path + (query && '?') + query + (location.hash || '');
91 }
92 /**
93 * Strips off the base from the beginning of a location.pathname in a non
94 * case-sensitive way.
95 *
96 * @param pathname - location.pathname
97 * @param base - base to strip off
98 */
99 function stripBase(pathname, base) {
100 // no base or base is not found at the beginning
101 if (!base || pathname.toLowerCase().indexOf(base.toLowerCase()))
102 return pathname;
103 return pathname.slice(base.length) || '/';
104 }
105 /**
106 * Checks if two RouteLocation are equal. This means that both locations are
107 * pointing towards the same {@link RouteRecord} and that all `params`, `query`
108 * parameters and `hash` are the same
109 *
110 * @param a - first {@link RouteLocation}
111 * @param b - second {@link RouteLocation}
112 */
113 function isSameRouteLocation(stringifyQuery, a, b) {
114 let aLastIndex = a.matched.length - 1;
115 let bLastIndex = b.matched.length - 1;
116 return (aLastIndex > -1 &&
117 aLastIndex === bLastIndex &&
118 isSameRouteRecord(a.matched[aLastIndex], b.matched[bLastIndex]) &&
119 isSameRouteLocationParams(a.params, b.params) &&
120 stringifyQuery(a.query) === stringifyQuery(b.query) &&
121 a.hash === b.hash);
122 }
123 /**
124 * Check if two `RouteRecords` are equal. Takes into account aliases: they are
125 * considered equal to the `RouteRecord` they are aliasing.
126 *
127 * @param a - first {@link RouteRecord}
128 * @param b - second {@link RouteRecord}
129 */
130 function isSameRouteRecord(a, b) {
131 // since the original record has an undefined value for aliasOf
132 // but all aliases point to the original record, this will always compare
133 // the original record
134 return (a.aliasOf || a) === (b.aliasOf || b);
135 }
136 function isSameRouteLocationParams(a, b) {
137 if (Object.keys(a).length !== Object.keys(b).length)
138 return false;
139 for (let key in a) {
140 if (!isSameRouteLocationParamsValue(a[key], b[key]))
141 return false;
142 }
143 return true;
144 }
145 function isSameRouteLocationParamsValue(a, b) {
146 return Array.isArray(a)
147 ? isEquivalentArray(a, b)
148 : Array.isArray(b)
149 ? isEquivalentArray(b, a)
150 : a === b;
151 }
152 /**
153 * Check if two arrays are the same or if an array with one single entry is the
154 * same as another primitive value. Used to check query and parameters
155 *
156 * @param a - array of values
157 * @param b - array of values or a single value
158 */
159 function isEquivalentArray(a, b) {
160 return Array.isArray(b)
161 ? a.length === b.length && a.every((value, i) => value === b[i])
162 : a.length === 1 && a[0] === b;
163 }
164 /**
165 * Resolves a relative path that starts with `.`.
166 *
167 * @param to - path location we are resolving
168 * @param from - currentLocation.path, should start with `/`
169 */
170 function resolveRelativePath(to, from) {
171 if (to.startsWith('/'))
172 return to;
173 if ( !from.startsWith('/')) {
174 warn(`Cannot resolve a relative location without an absolute path. Trying to resolve "${to}" from "${from}". It should look like "/${from}".`);
175 return to;
176 }
177 if (!to)
178 return from;
179 const fromSegments = from.split('/');
180 const toSegments = to.split('/');
181 let position = fromSegments.length - 1;
182 let toPosition;
183 let segment;
184 for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
185 segment = toSegments[toPosition];
186 // can't go below zero
187 if (position === 1 || segment === '.')
188 continue;
189 if (segment === '..')
190 position--;
191 // found something that is not relative path
192 else
193 break;
194 }
195 return (fromSegments.slice(0, position).join('/') +
196 '/' +
197 toSegments
198 .slice(toPosition - (toPosition === toSegments.length ? 1 : 0))
199 .join('/'));
200 }
201
202 var NavigationType;
203 (function (NavigationType) {
204 NavigationType["pop"] = "pop";
205 NavigationType["push"] = "push";
206 })(NavigationType || (NavigationType = {}));
207 var NavigationDirection;
208 (function (NavigationDirection) {
209 NavigationDirection["back"] = "back";
210 NavigationDirection["forward"] = "forward";
211 NavigationDirection["unknown"] = "";
212 })(NavigationDirection || (NavigationDirection = {}));
213 /**
214 * Starting location for Histories
215 */
216 const START = '';
217 // Generic utils
218 /**
219 * Normalizes a base by removing any trailing slash and reading the base tag if
220 * present.
221 *
222 * @param base - base to normalize
223 */
224 function normalizeBase(base) {
225 if (!base) {
226 if (isBrowser) {
227 // respect <base> tag
228 const baseEl = document.querySelector('base');
229 base = (baseEl && baseEl.getAttribute('href')) || '/';
230 // strip full URL origin
231 base = base.replace(/^\w+:\/\/[^\/]+/, '');
232 }
233 else {
234 base = '/';
235 }
236 }
237 // ensure leading slash when it was removed by the regex above avoid leading
238 // slash with hash because the file could be read from the disk like file://
239 // and the leading slash would cause problems
240 if (base[0] !== '/' && base[0] !== '#')
241 base = '/' + base;
242 // remove the trailing slash so all other method can just do `base + fullPath`
243 // to build an href
244 return removeTrailingSlash(base);
245 }
246 // remove any character before the hash
247 const BEFORE_HASH_RE = /^[^#]+#/;
248 function createHref(base, location) {
249 return base.replace(BEFORE_HASH_RE, '#') + location;
250 }
251
252 function getElementPosition(el, offset) {
253 const docRect = document.documentElement.getBoundingClientRect();
254 const elRect = el.getBoundingClientRect();
255 return {
256 behavior: offset.behavior,
257 left: elRect.left - docRect.left - (offset.left || 0),
258 top: elRect.top - docRect.top - (offset.top || 0),
259 };
260 }
261 const computeScrollPosition = () => ({
262 left: window.pageXOffset,
263 top: window.pageYOffset,
264 });
265 function scrollToPosition(position) {
266 let scrollToOptions;
267 if ('el' in position) {
268 let positionEl = position.el;
269 const isIdSelector = typeof positionEl === 'string' && positionEl.startsWith('#');
270 /**
271 * `id`s can accept pretty much any characters, including CSS combinators
272 * like `>` or `~`. It's still possible to retrieve elements using
273 * `document.getElementById('~')` but it needs to be escaped when using
274 * `document.querySelector('#\\~')` for it to be valid. The only
275 * requirements for `id`s are them to be unique on the page and to not be
276 * empty (`id=""`). Because of that, when passing an id selector, it should
277 * be properly escaped for it to work with `querySelector`. We could check
278 * for the id selector to be simple (no CSS combinators `+ >~`) but that
279 * would make things inconsistent since they are valid characters for an
280 * `id` but would need to be escaped when using `querySelector`, breaking
281 * their usage and ending up in no selector returned. Selectors need to be
282 * escaped:
283 *
284 * - `#1-thing` becomes `#\31 -thing`
285 * - `#with~symbols` becomes `#with\\~symbols`
286 *
287 * - More information about the topic can be found at
288 * https://mathiasbynens.be/notes/html5-id-class.
289 * - Practical example: https://mathiasbynens.be/demo/html5-id
290 */
291 if ( typeof position.el === 'string') {
292 if (!isIdSelector || !document.getElementById(position.el.slice(1))) {
293 try {
294 let foundEl = document.querySelector(position.el);
295 if (isIdSelector && foundEl) {
296 warn(`The selector "${position.el}" should be passed as "el: document.querySelector('${position.el}')" because it starts with "#".`);
297 // return to avoid other warnings
298 return;
299 }
300 }
301 catch (err) {
302 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).`);
303 // return to avoid other warnings
304 return;
305 }
306 }
307 }
308 const el = typeof positionEl === 'string'
309 ? isIdSelector
310 ? document.getElementById(positionEl.slice(1))
311 : document.querySelector(positionEl)
312 : positionEl;
313 if (!el) {
314
315 warn(`Couldn't find element using selector "${position.el}" returned by scrollBehavior.`);
316 return;
317 }
318 scrollToOptions = getElementPosition(el, position);
319 }
320 else {
321 scrollToOptions = position;
322 }
323 if ('scrollBehavior' in document.documentElement.style)
324 window.scrollTo(scrollToOptions);
325 else {
326 window.scrollTo(scrollToOptions.left != null ? scrollToOptions.left : window.pageXOffset, scrollToOptions.top != null ? scrollToOptions.top : window.pageYOffset);
327 }
328 }
329 function getScrollKey(path, delta) {
330 const position = history.state ? history.state.position - delta : -1;
331 return position + path;
332 }
333 const scrollPositions = new Map();
334 function saveScrollPosition(key, scrollPosition) {
335 scrollPositions.set(key, scrollPosition);
336 }
337 function getSavedScrollPosition(key) {
338 const scroll = scrollPositions.get(key);
339 // consume it so it's not used again
340 scrollPositions.delete(key);
341 return scroll;
342 }
343 // TODO: RFC about how to save scroll position
344 /**
345 * ScrollBehavior instance used by the router to compute and restore the scroll
346 * position when navigating.
347 */
348 // export interface ScrollHandler<ScrollPositionEntry extends HistoryStateValue, ScrollPosition extends ScrollPositionEntry> {
349 // // returns a scroll position that can be saved in history
350 // compute(): ScrollPositionEntry
351 // // can take an extended ScrollPositionEntry
352 // scroll(position: ScrollPosition): void
353 // }
354 // export const scrollHandler: ScrollHandler<ScrollPosition> = {
355 // compute: computeScroll,
356 // scroll: scrollToPosition,
357 // }
358
359 let createBaseLocation = () => location.protocol + '//' + location.host;
360 /**
361 * Creates a normalized history location from a window.location object
362 * @param location -
363 */
364 function createCurrentLocation(base, location) {
365 const { pathname, search, hash } = location;
366 // allows hash based url
367 const hashPos = base.indexOf('#');
368 if (hashPos > -1) {
369 // prepend the starting slash to hash so the url starts with /#
370 let pathFromHash = hash.slice(1);
371 if (pathFromHash[0] !== '/')
372 pathFromHash = '/' + pathFromHash;
373 return stripBase(pathFromHash, '');
374 }
375 const path = stripBase(pathname, base);
376 return path + search + hash;
377 }
378 function useHistoryListeners(base, historyState, currentLocation, replace) {
379 let listeners = [];
380 let teardowns = [];
381 // TODO: should it be a stack? a Dict. Check if the popstate listener
382 // can trigger twice
383 let pauseState = null;
384 const popStateHandler = ({ state, }) => {
385 const to = createCurrentLocation(base, location);
386 const from = currentLocation.value;
387 const fromState = historyState.value;
388 let delta = 0;
389 if (state) {
390 currentLocation.value = to;
391 historyState.value = state;
392 // ignore the popstate and reset the pauseState
393 if (pauseState && pauseState === from) {
394 pauseState = null;
395 return;
396 }
397 delta = fromState ? state.position - fromState.position : 0;
398 }
399 else {
400 replace(to);
401 }
402 // console.log({ deltaFromCurrent })
403 // Here we could also revert the navigation by calling history.go(-delta)
404 // this listener will have to be adapted to not trigger again and to wait for the url
405 // to be updated before triggering the listeners. Some kind of validation function would also
406 // need to be passed to the listeners so the navigation can be accepted
407 // call all listeners
408 listeners.forEach(listener => {
409 listener(currentLocation.value, from, {
410 delta,
411 type: NavigationType.pop,
412 direction: delta
413 ? delta > 0
414 ? NavigationDirection.forward
415 : NavigationDirection.back
416 : NavigationDirection.unknown,
417 });
418 });
419 };
420 function pauseListeners() {
421 pauseState = currentLocation.value;
422 }
423 function listen(callback) {
424 // setup the listener and prepare teardown callbacks
425 listeners.push(callback);
426 const teardown = () => {
427 const index = listeners.indexOf(callback);
428 if (index > -1)
429 listeners.splice(index, 1);
430 };
431 teardowns.push(teardown);
432 return teardown;
433 }
434 function beforeUnloadListener() {
435 const { history } = window;
436 if (!history.state)
437 return;
438 history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), '');
439 }
440 function destroy() {
441 for (const teardown of teardowns)
442 teardown();
443 teardowns = [];
444 window.removeEventListener('popstate', popStateHandler);
445 window.removeEventListener('beforeunload', beforeUnloadListener);
446 }
447 // setup the listeners and prepare teardown callbacks
448 window.addEventListener('popstate', popStateHandler);
449 window.addEventListener('beforeunload', beforeUnloadListener);
450 return {
451 pauseListeners,
452 listen,
453 destroy,
454 };
455 }
456 /**
457 * Creates a state object
458 */
459 function buildState(back, current, forward, replaced = false, computeScroll = false) {
460 return {
461 back,
462 current,
463 forward,
464 replaced,
465 position: window.history.length,
466 scroll: computeScroll ? computeScrollPosition() : null,
467 };
468 }
469 function useHistoryStateNavigation(base) {
470 const { history, location } = window;
471 // private variables
472 let currentLocation = {
473 value: createCurrentLocation(base, location),
474 };
475 let historyState = { value: history.state };
476 // build current history entry as this is a fresh navigation
477 if (!historyState.value) {
478 changeLocation(currentLocation.value, {
479 back: null,
480 current: currentLocation.value,
481 forward: null,
482 // the length is off by one, we need to decrease it
483 position: history.length - 1,
484 replaced: true,
485 // don't add a scroll as the user may have an anchor and we want
486 // scrollBehavior to be triggered without a saved position
487 scroll: null,
488 }, true);
489 }
490 function changeLocation(to, state, replace) {
491 // when the base has a `#`, only use that for the URL
492 const hashIndex = base.indexOf('#');
493 const url = hashIndex > -1
494 ? base.slice(hashIndex) + to
495 : createBaseLocation() + base + to;
496 try {
497 // BROWSER QUIRK
498 // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds
499 history[replace ? 'replaceState' : 'pushState'](state, '', url);
500 historyState.value = state;
501 }
502 catch (err) {
503 {
504 warn('Error with push/replace State', err);
505 }
506 // Force the navigation, this also resets the call count
507 location[replace ? 'replace' : 'assign'](url);
508 }
509 }
510 function replace(to, data) {
511 const state = assign({}, history.state, buildState(historyState.value.back,
512 // keep back and forward entries but override current position
513 to, historyState.value.forward, true), data, { position: historyState.value.position });
514 changeLocation(to, state, true);
515 currentLocation.value = to;
516 }
517 function push(to, data) {
518 // Add to current entry the information of where we are going
519 // as well as saving the current position
520 const currentState = assign({},
521 // use current history state to gracefully handle a wrong call to
522 // history.replaceState
523 // https://github.com/vuejs/vue-router-next/issues/366
524 historyState.value, history.state, {
525 forward: to,
526 scroll: computeScrollPosition(),
527 });
528 if ( !history.state) {
529 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` +
530 `history.replaceState(history.state, '', url)\n\n` +
531 `You can find more information at https://next.router.vuejs.org/guide/migration/#usage-of-history-state.`);
532 }
533 changeLocation(currentState.current, currentState, true);
534 const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data);
535 changeLocation(to, state, false);
536 currentLocation.value = to;
537 }
538 return {
539 location: currentLocation,
540 state: historyState,
541 push,
542 replace,
543 };
544 }
545 /**
546 * Creates an HTML5 history. Most common history for single page applications.
547 *
548 * @param base -
549 */
550 function createWebHistory(base) {
551 base = normalizeBase(base);
552 const historyNavigation = useHistoryStateNavigation(base);
553 const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace);
554 function go(delta, triggerListeners = true) {
555 if (!triggerListeners)
556 historyListeners.pauseListeners();
557 history.go(delta);
558 }
559 const routerHistory = assign({
560 // it's overridden right after
561 location: '',
562 base,
563 go,
564 createHref: createHref.bind(null, base),
565 }, historyNavigation, historyListeners);
566 Object.defineProperty(routerHistory, 'location', {
567 get: () => historyNavigation.location.value,
568 });
569 Object.defineProperty(routerHistory, 'state', {
570 get: () => historyNavigation.state.value,
571 });
572 return routerHistory;
573 }
574
575 /**
576 * Creates a in-memory based history. The main purpose of this history is to handle SSR. It starts in a special location that is nowhere.
577 * It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`.
578 *
579 * @param base - Base applied to all urls, defaults to '/'
580 * @returns a history object that can be passed to the router constructor
581 */
582 function createMemoryHistory(base = '') {
583 let listeners = [];
584 let queue = [START];
585 let position = 0;
586 function setLocation(location) {
587 position++;
588 if (position === queue.length) {
589 // we are at the end, we can simply append a new entry
590 queue.push(location);
591 }
592 else {
593 // we are in the middle, we remove everything from here in the queue
594 queue.splice(position);
595 queue.push(location);
596 }
597 }
598 function triggerListeners(to, from, { direction, delta }) {
599 const info = {
600 direction,
601 delta,
602 type: NavigationType.pop,
603 };
604 for (let callback of listeners) {
605 callback(to, from, info);
606 }
607 }
608 const routerHistory = {
609 // rewritten by Object.defineProperty
610 location: START,
611 state: {},
612 base,
613 createHref: createHref.bind(null, base),
614 replace(to) {
615 // remove current entry and decrement position
616 queue.splice(position--, 1);
617 setLocation(to);
618 },
619 push(to, data) {
620 setLocation(to);
621 },
622 listen(callback) {
623 listeners.push(callback);
624 return () => {
625 const index = listeners.indexOf(callback);
626 if (index > -1)
627 listeners.splice(index, 1);
628 };
629 },
630 destroy() {
631 listeners = [];
632 },
633 go(delta, shouldTrigger = true) {
634 const from = this.location;
635 const direction =
636 // we are considering delta === 0 going forward, but in abstract mode
637 // using 0 for the delta doesn't make sense like it does in html5 where
638 // it reloads the page
639 delta < 0 ? NavigationDirection.back : NavigationDirection.forward;
640 position = Math.max(0, Math.min(position + delta, queue.length - 1));
641 if (shouldTrigger) {
642 triggerListeners(this.location, from, {
643 direction,
644 delta,
645 });
646 }
647 },
648 };
649 Object.defineProperty(routerHistory, 'location', {
650 get: () => queue[position],
651 });
652 return routerHistory;
653 }
654
655 /**
656 * Creates a hash history. Useful for web applications with no host (e.g.
657 * `file://`) or when configuring a server to handle any URL.
658 *
659 * @param base - optional base to provide. Defaults to `location.pathname` or
660 * `/` if at root. If there is a `base` tag in the `head`, its value will be
661 * **ignored**.
662 *
663 * @example
664 * ```js
665 * // at https://example.com/folder
666 * createWebHashHistory() // gives a url of `https://example.com/folder#`
667 * createWebHashHistory('/folder/') // gives a url of `https://example.com/folder/#`
668 * // if the `#` is provided in the base, it won't be added by `createWebHashHistory`
669 * createWebHashHistory('/folder/#/app/') // gives a url of `https://example.com/folder/#/app/`
670 * // you should avoid doing this because it changes the original url and breaks copying urls
671 * createWebHashHistory('/other-folder/') // gives a url of `https://example.com/other-folder/#`
672 *
673 * // at file:///usr/etc/folder/index.html
674 * // for locations with no `host`, the base is ignored
675 * createWebHashHistory('/iAmIgnored') // gives a url of `file:///usr/etc/folder/index.html#`
676 * ```
677 */
678 function createWebHashHistory(base) {
679 // Make sure this implementation is fine in terms of encoding, specially for IE11
680 // for `file://`, directly use the pathname and ignore the base
681 // location.pathname contains an initial `/` even at the root: `https://example.com`
682 base = location.host ? base || location.pathname : '';
683 // allow the user to provide a `#` in the middle: `/base/#/app`
684 if (base.indexOf('#') < 0)
685 base += '#';
686 if ( !base.endsWith('#/') && !base.endsWith('#')) {
687 warn(`A hash base must end with a "#":\n"${base}" should be "${base.replace(/#.*$/, '#')}".`);
688 }
689 return createWebHistory(base);
690 }
691
692 function isRouteLocation(route) {
693 return typeof route === 'string' || (route && typeof route === 'object');
694 }
695 function isRouteName(name) {
696 return typeof name === 'string' || typeof name === 'symbol';
697 }
698
699 /**
700 * Initial route location where the router is. Can be used in navigation guards
701 * to differentiate the initial navigation.
702 *
703 * @example
704 * ```js
705 * import { START_LOCATION } from 'vue-router'
706 *
707 * router.beforeEach((to, from) => {
708 * if (from === START_LOCATION) {
709 * // initial navigation
710 * }
711 * })
712 * ```
713 */
714 const START_LOCATION_NORMALIZED = {
715 path: '/',
716 name: undefined,
717 params: {},
718 query: {},
719 hash: '',
720 fullPath: '/',
721 matched: [],
722 meta: {},
723 redirectedFrom: undefined,
724 };
725
726 const NavigationFailureSymbol = /*#__PURE__*/ PolySymbol( 'navigation failure' );
727 (function (NavigationFailureType) {
728 /**
729 * An aborted navigation is a navigation that failed because a navigation
730 * guard returned `false` or called `next(false)`
731 */
732 NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted";
733 /**
734 * A cancelled navigation is a navigation that failed because a more recent
735 * navigation finished started (not necessarily finished).
736 */
737 NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled";
738 /**
739 * A duplicated navigation is a navigation that failed because it was
740 * initiated while already being at the exact same location.
741 */
742 NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated";
743 })(exports.NavigationFailureType || (exports.NavigationFailureType = {}));
744 // DEV only debug messages
745 const ErrorTypeMessages = {
746 [1 /* MATCHER_NOT_FOUND */]({ location, currentLocation }) {
747 return `No match for\n ${JSON.stringify(location)}${currentLocation
748 ? '\nwhile being at\n' + JSON.stringify(currentLocation)
749 : ''}`;
750 },
751 [2 /* NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
752 return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
753 },
754 [4 /* NAVIGATION_ABORTED */]({ from, to }) {
755 return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
756 },
757 [8 /* NAVIGATION_CANCELLED */]({ from, to }) {
758 return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
759 },
760 [16 /* NAVIGATION_DUPLICATED */]({ from, to }) {
761 return `Avoided redundant navigation to current location: "${from.fullPath}".`;
762 },
763 };
764 function createRouterError(type, params) {
765 {
766 return assign(new Error(ErrorTypeMessages[type](params)), {
767 type,
768 [NavigationFailureSymbol]: true,
769 }, params);
770 }
771 }
772 function isNavigationFailure(error, type) {
773 return (error instanceof Error &&
774 NavigationFailureSymbol in error &&
775 (type == null || !!(error.type & type)));
776 }
777 const propertiesToLog = ['params', 'query', 'hash'];
778 function stringifyRoute(to) {
779 if (typeof to === 'string')
780 return to;
781 if ('path' in to)
782 return to.path;
783 const location = {};
784 for (const key of propertiesToLog) {
785 if (key in to)
786 location[key] = to[key];
787 }
788 return JSON.stringify(location, null, 2);
789 }
790
791 // default pattern for a param: non greedy everything but /
792 const BASE_PARAM_PATTERN = '[^/]+?';
793 const BASE_PATH_PARSER_OPTIONS = {
794 sensitive: false,
795 strict: false,
796 start: true,
797 end: true,
798 };
799 // Special Regex characters that must be escaped in static tokens
800 const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g;
801 /**
802 * Creates a path parser from an array of Segments (a segment is an array of Tokens)
803 *
804 * @param segments - array of segments returned by tokenizePath
805 * @param extraOptions - optional options for the regexp
806 * @returns a PathParser
807 */
808 function tokensToParser(segments, extraOptions) {
809 const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
810 // the amount of scores is the same as the length of segments except for the root segment "/"
811 let score = [];
812 // the regexp as a string
813 let pattern = options.start ? '^' : '';
814 // extracted keys
815 const keys = [];
816 for (const segment of segments) {
817 // the root segment needs special treatment
818 const segmentScores = segment.length ? [] : [90 /* Root */];
819 // allow trailing slash
820 if (options.strict && !segment.length)
821 pattern += '/';
822 for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
823 const token = segment[tokenIndex];
824 // resets the score if we are inside a sub segment /:a-other-:b
825 let subSegmentScore = 40 /* Segment */ +
826 (options.sensitive ? 0.25 /* BonusCaseSensitive */ : 0);
827 if (token.type === 0 /* Static */) {
828 // prepend the slash if we are starting a new segment
829 if (!tokenIndex)
830 pattern += '/';
831 pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
832 subSegmentScore += 40 /* Static */;
833 }
834 else if (token.type === 1 /* Param */) {
835 const { value, repeatable, optional, regexp } = token;
836 keys.push({
837 name: value,
838 repeatable,
839 optional,
840 });
841 const re = regexp ? regexp : BASE_PARAM_PATTERN;
842 // the user provided a custom regexp /:id(\\d+)
843 if (re !== BASE_PARAM_PATTERN) {
844 subSegmentScore += 10 /* BonusCustomRegExp */;
845 // make sure the regexp is valid before using it
846 try {
847 new RegExp(`(${re})`);
848 }
849 catch (err) {
850 throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` +
851 err.message);
852 }
853 }
854 // when we repeat we must take care of the repeating leading slash
855 let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
856 // prepend the slash if we are starting a new segment
857 if (!tokenIndex)
858 subPattern = optional ? `(?:/${subPattern})` : '/' + subPattern;
859 if (optional)
860 subPattern += '?';
861 pattern += subPattern;
862 subSegmentScore += 20 /* Dynamic */;
863 if (optional)
864 subSegmentScore += -8 /* BonusOptional */;
865 if (repeatable)
866 subSegmentScore += -20 /* BonusRepeatable */;
867 if (re === '.*')
868 subSegmentScore += -50 /* BonusWildcard */;
869 }
870 segmentScores.push(subSegmentScore);
871 }
872 // an empty array like /home/ -> [[{home}], []]
873 // if (!segment.length) pattern += '/'
874 score.push(segmentScores);
875 }
876 // only apply the strict bonus to the last score
877 if (options.strict && options.end) {
878 const i = score.length - 1;
879 score[i][score[i].length - 1] += 0.7000000000000001 /* BonusStrict */;
880 }
881 // TODO: dev only warn double trailing slash
882 if (!options.strict)
883 pattern += '/?';
884 if (options.end)
885 pattern += '$';
886 // allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else
887 else if (options.strict)
888 pattern += '(?:/|$)';
889 const re = new RegExp(pattern, options.sensitive ? '' : 'i');
890 function parse(path) {
891 const match = path.match(re);
892 const params = {};
893 if (!match)
894 return null;
895 for (let i = 1; i < match.length; i++) {
896 const value = match[i] || '';
897 const key = keys[i - 1];
898 params[key.name] = value && key.repeatable ? value.split('/') : value;
899 }
900 return params;
901 }
902 function stringify(params) {
903 let path = '';
904 // for optional parameters to allow to be empty
905 let avoidDuplicatedSlash = false;
906 for (const segment of segments) {
907 if (!avoidDuplicatedSlash || !path.endsWith('/'))
908 path += '/';
909 avoidDuplicatedSlash = false;
910 for (const token of segment) {
911 if (token.type === 0 /* Static */) {
912 path += token.value;
913 }
914 else if (token.type === 1 /* Param */) {
915 const { value, repeatable, optional } = token;
916 const param = value in params ? params[value] : '';
917 if (Array.isArray(param) && !repeatable)
918 throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);
919 const text = Array.isArray(param) ? param.join('/') : param;
920 if (!text) {
921 if (optional) {
922 // remove the last slash as we could be at the end
923 if (path.endsWith('/'))
924 path = path.slice(0, -1);
925 // do not append a slash on the next iteration
926 else
927 avoidDuplicatedSlash = true;
928 }
929 else
930 throw new Error(`Missing required param "${value}"`);
931 }
932 path += text;
933 }
934 }
935 }
936 return path;
937 }
938 return {
939 re,
940 score,
941 keys,
942 parse,
943 stringify,
944 };
945 }
946 /**
947 * Compares an array of numbers as used in PathParser.score and returns a
948 * number. This function can be used to `sort` an array
949 * @param a - first array of numbers
950 * @param b - second array of numbers
951 * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
952 * should be sorted first
953 */
954 function compareScoreArray(a, b) {
955 let i = 0;
956 while (i < a.length && i < b.length) {
957 const diff = b[i] - a[i];
958 // only keep going if diff === 0
959 if (diff)
960 return diff;
961 i++;
962 }
963 // if the last subsegment was Static, the shorter segments should be sorted first
964 // otherwise sort the longest segment first
965 if (a.length < b.length) {
966 return a.length === 1 && a[0] === 40 /* Static */ + 40 /* Segment */
967 ? -1
968 : 1;
969 }
970 else if (a.length > b.length) {
971 return b.length === 1 && b[0] === 40 /* Static */ + 40 /* Segment */
972 ? 1
973 : -1;
974 }
975 return 0;
976 }
977 /**
978 * Compare function that can be used with `sort` to sort an array of PathParser
979 * @param a - first PathParser
980 * @param b - second PathParser
981 * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
982 */
983 function comparePathParserScore(a, b) {
984 let i = 0;
985 const aScore = a.score;
986 const bScore = b.score;
987 while (i < aScore.length && i < bScore.length) {
988 const comp = compareScoreArray(aScore[i], bScore[i]);
989 // do not return if both are equal
990 if (comp)
991 return comp;
992 i++;
993 }
994 // if a and b share the same score entries but b has more, sort b first
995 return bScore.length - aScore.length;
996 // this is the ternary version
997 // return aScore.length < bScore.length
998 // ? 1
999 // : aScore.length > bScore.length
1000 // ? -1
1001 // : 0
1002 }
1003
1004 const ROOT_TOKEN = {
1005 type: 0 /* Static */,
1006 value: '',
1007 };
1008 const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
1009 // After some profiling, the cache seems to be unnecessary because tokenizePath
1010 // (the slowest part of adding a route) is very fast
1011 // const tokenCache = new Map<string, Token[][]>()
1012 function tokenizePath(path) {
1013 if (!path)
1014 return [[]];
1015 if (path === '/')
1016 return [[ROOT_TOKEN]];
1017 if (!path.startsWith('/')) {
1018 throw new Error( `Route paths should start with a "/": "${path}" should be "/${path}".`
1019 );
1020 }
1021 // if (tokenCache.has(path)) return tokenCache.get(path)!
1022 function crash(message) {
1023 throw new Error(`ERR (${state})/"${buffer}": ${message}`);
1024 }
1025 let state = 0 /* Static */;
1026 let previousState = state;
1027 const tokens = [];
1028 // the segment will always be valid because we get into the initial state
1029 // with the leading /
1030 let segment;
1031 function finalizeSegment() {
1032 if (segment)
1033 tokens.push(segment);
1034 segment = [];
1035 }
1036 // index on the path
1037 let i = 0;
1038 // char at index
1039 let char;
1040 // buffer of the value read
1041 let buffer = '';
1042 // custom regexp for a param
1043 let customRe = '';
1044 function consumeBuffer() {
1045 if (!buffer)
1046 return;
1047 if (state === 0 /* Static */) {
1048 segment.push({
1049 type: 0 /* Static */,
1050 value: buffer,
1051 });
1052 }
1053 else if (state === 1 /* Param */ ||
1054 state === 2 /* ParamRegExp */ ||
1055 state === 3 /* ParamRegExpEnd */) {
1056 if (segment.length > 1 && (char === '*' || char === '+'))
1057 crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
1058 segment.push({
1059 type: 1 /* Param */,
1060 value: buffer,
1061 regexp: customRe,
1062 repeatable: char === '*' || char === '+',
1063 optional: char === '*' || char === '?',
1064 });
1065 }
1066 else {
1067 crash('Invalid state to consume buffer');
1068 }
1069 buffer = '';
1070 }
1071 function addCharToBuffer() {
1072 buffer += char;
1073 }
1074 while (i < path.length) {
1075 char = path[i++];
1076 if (char === '\\' && state !== 2 /* ParamRegExp */) {
1077 previousState = state;
1078 state = 4 /* EscapeNext */;
1079 continue;
1080 }
1081 switch (state) {
1082 case 0 /* Static */:
1083 if (char === '/') {
1084 if (buffer) {
1085 consumeBuffer();
1086 }
1087 finalizeSegment();
1088 }
1089 else if (char === ':') {
1090 consumeBuffer();
1091 state = 1 /* Param */;
1092 }
1093 else {
1094 addCharToBuffer();
1095 }
1096 break;
1097 case 4 /* EscapeNext */:
1098 addCharToBuffer();
1099 state = previousState;
1100 break;
1101 case 1 /* Param */:
1102 if (char === '(') {
1103 state = 2 /* ParamRegExp */;
1104 customRe = '';
1105 }
1106 else if (VALID_PARAM_RE.test(char)) {
1107 addCharToBuffer();
1108 }
1109 else {
1110 consumeBuffer();
1111 state = 0 /* Static */;
1112 // go back one character if we were not modifying
1113 if (char !== '*' && char !== '?' && char !== '+')
1114 i--;
1115 }
1116 break;
1117 case 2 /* ParamRegExp */:
1118 // TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
1119 // it already works by escaping the closing )
1120 // https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
1121 // is this really something people need since you can also write
1122 // /prefix_:p()_suffix
1123 if (char === ')') {
1124 // handle the escaped )
1125 if (customRe[customRe.length - 1] == '\\')
1126 customRe = customRe.slice(0, -1) + char;
1127 else
1128 state = 3 /* ParamRegExpEnd */;
1129 }
1130 else {
1131 customRe += char;
1132 }
1133 break;
1134 case 3 /* ParamRegExpEnd */:
1135 // same as finalizing a param
1136 consumeBuffer();
1137 state = 0 /* Static */;
1138 // go back one character if we were not modifying
1139 if (char !== '*' && char !== '?' && char !== '+')
1140 i--;
1141 break;
1142 default:
1143 crash('Unknown state');
1144 break;
1145 }
1146 }
1147 if (state === 2 /* ParamRegExp */)
1148 crash(`Unfinished custom RegExp for param "${buffer}"`);
1149 consumeBuffer();
1150 finalizeSegment();
1151 // tokenCache.set(path, tokens)
1152 return tokens;
1153 }
1154
1155 function createRouteRecordMatcher(record, parent, options) {
1156 const parser = tokensToParser(tokenizePath(record.path), options);
1157 // warn against params with the same name
1158 {
1159 const existingKeys = new Set();
1160 for (const key of parser.keys) {
1161 if (existingKeys.has(key.name))
1162 warn(`Found duplicated params with name "${key.name}" for path "${record.path}". Only the last one will be available on "$route.params".`);
1163 existingKeys.add(key.name);
1164 }
1165 }
1166 const matcher = assign(parser, {
1167 record,
1168 parent,
1169 // these needs to be populated by the parent
1170 children: [],
1171 alias: [],
1172 });
1173 if (parent) {
1174 // both are aliases or both are not aliases
1175 // we don't want to mix them because the order is used when
1176 // passing originalRecord in Matcher.addRoute
1177 if (!matcher.record.aliasOf === !parent.record.aliasOf)
1178 parent.children.push(matcher);
1179 }
1180 return matcher;
1181 }
1182
1183 /**
1184 * Creates a Router Matcher.
1185 *
1186 * @internal
1187 * @param routes - array of initial routes
1188 * @param globalOptions - global route options
1189 */
1190 function createRouterMatcher(routes, globalOptions) {
1191 // normalized ordered array of matchers
1192 const matchers = [];
1193 const matcherMap = new Map();
1194 globalOptions = mergeOptions({ strict: false, end: true, sensitive: false }, globalOptions);
1195 function getRecordMatcher(name) {
1196 return matcherMap.get(name);
1197 }
1198 function addRoute(record, parent, originalRecord) {
1199 // used later on to remove by name
1200 let isRootAdd = !originalRecord;
1201 let mainNormalizedRecord = normalizeRouteRecord(record);
1202 // we might be the child of an alias
1203 mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;
1204 const options = mergeOptions(globalOptions, record);
1205 // generate an array of records to correctly handle aliases
1206 const normalizedRecords = [
1207 mainNormalizedRecord,
1208 ];
1209 if ('alias' in record) {
1210 const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias;
1211 for (const alias of aliases) {
1212 normalizedRecords.push(assign({}, mainNormalizedRecord, {
1213 // this allows us to hold a copy of the `components` option
1214 // so that async components cache is hold on the original record
1215 components: originalRecord
1216 ? originalRecord.record.components
1217 : mainNormalizedRecord.components,
1218 path: alias,
1219 // we might be the child of an alias
1220 aliasOf: originalRecord
1221 ? originalRecord.record
1222 : mainNormalizedRecord,
1223 }));
1224 }
1225 }
1226 let matcher;
1227 let originalMatcher;
1228 for (const normalizedRecord of normalizedRecords) {
1229 let { path } = normalizedRecord;
1230 // Build up the path for nested routes if the child isn't an absolute
1231 // route. Only add the / delimiter if the child path isn't empty and if the
1232 // parent path doesn't have a trailing slash
1233 if (parent && path[0] !== '/') {
1234 let parentPath = parent.record.path;
1235 let connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
1236 normalizedRecord.path =
1237 parent.record.path + (path && connectingSlash + path);
1238 }
1239 if ( normalizedRecord.path === '*') {
1240 throw new Error('Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +
1241 'See more at https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes.');
1242 }
1243 // create the object before hand so it can be passed to children
1244 matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
1245 if ( parent && path[0] === '/')
1246 checkMissingParamsInAbsolutePath(matcher, parent);
1247 // if we are an alias we must tell the original record that we exist
1248 // so we can be removed
1249 if (originalRecord) {
1250 originalRecord.alias.push(matcher);
1251 {
1252 checkSameParams(originalRecord, matcher);
1253 }
1254 }
1255 else {
1256 // otherwise, the first record is the original and others are aliases
1257 originalMatcher = originalMatcher || matcher;
1258 if (originalMatcher !== matcher)
1259 originalMatcher.alias.push(matcher);
1260 // remove the route if named and only for the top record (avoid in nested calls)
1261 // this works because the original record is the first one
1262 if (isRootAdd && record.name && !isAliasRecord(matcher))
1263 removeRoute(record.name);
1264 }
1265 if ('children' in mainNormalizedRecord) {
1266 let children = mainNormalizedRecord.children;
1267 for (let i = 0; i < children.length; i++) {
1268 addRoute(children[i], matcher, originalRecord && originalRecord.children[i]);
1269 }
1270 }
1271 // if there was no original record, then the first one was not an alias and all
1272 // other alias (if any) need to reference this record when adding children
1273 originalRecord = originalRecord || matcher;
1274 insertMatcher(matcher);
1275 }
1276 return originalMatcher
1277 ? () => {
1278 // since other matchers are aliases, they should be removed by the original matcher
1279 removeRoute(originalMatcher);
1280 }
1281 : noop;
1282 }
1283 function removeRoute(matcherRef) {
1284 if (isRouteName(matcherRef)) {
1285 const matcher = matcherMap.get(matcherRef);
1286 if (matcher) {
1287 matcherMap.delete(matcherRef);
1288 matchers.splice(matchers.indexOf(matcher), 1);
1289 matcher.children.forEach(removeRoute);
1290 matcher.alias.forEach(removeRoute);
1291 }
1292 }
1293 else {
1294 let index = matchers.indexOf(matcherRef);
1295 if (index > -1) {
1296 matchers.splice(index, 1);
1297 if (matcherRef.record.name)
1298 matcherMap.delete(matcherRef.record.name);
1299 matcherRef.children.forEach(removeRoute);
1300 matcherRef.alias.forEach(removeRoute);
1301 }
1302 }
1303 }
1304 function getRoutes() {
1305 return matchers;
1306 }
1307 function insertMatcher(matcher) {
1308 let i = 0;
1309 // console.log('i is', { i })
1310 while (i < matchers.length &&
1311 comparePathParserScore(matcher, matchers[i]) >= 0)
1312 i++;
1313 // console.log('END i is', { i })
1314 // while (i < matchers.length && matcher.score <= matchers[i].score) i++
1315 matchers.splice(i, 0, matcher);
1316 // only add the original record to the name map
1317 if (matcher.record.name && !isAliasRecord(matcher))
1318 matcherMap.set(matcher.record.name, matcher);
1319 }
1320 function resolve(location, currentLocation) {
1321 let matcher;
1322 let params = {};
1323 let path;
1324 let name;
1325 if ('name' in location && location.name) {
1326 matcher = matcherMap.get(location.name);
1327 if (!matcher)
1328 throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
1329 location,
1330 });
1331 name = matcher.record.name;
1332 params = assign(
1333 // paramsFromLocation is a new object
1334 paramsFromLocation(currentLocation.params,
1335 // only keep params that exist in the resolved location
1336 // TODO: only keep optional params coming from a parent record
1337 matcher.keys.filter(k => !k.optional).map(k => k.name)), location.params);
1338 // throws if cannot be stringified
1339 path = matcher.stringify(params);
1340 }
1341 else if ('path' in location) {
1342 // no need to resolve the path with the matcher as it was provided
1343 // this also allows the user to control the encoding
1344 path = location.path;
1345 if ( !path.startsWith('/')) {
1346 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://new-issue.vuejs.org/?repo=vuejs/vue-router-next.`);
1347 }
1348 matcher = matchers.find(m => m.re.test(path));
1349 // matcher should have a value after the loop
1350 if (matcher) {
1351 // TODO: dev warning of unused params if provided
1352 // we know the matcher works because we tested the regexp
1353 params = matcher.parse(path);
1354 name = matcher.record.name;
1355 }
1356 // location is a relative path
1357 }
1358 else {
1359 // match by name or path of current route
1360 matcher = currentLocation.name
1361 ? matcherMap.get(currentLocation.name)
1362 : matchers.find(m => m.re.test(currentLocation.path));
1363 if (!matcher)
1364 throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
1365 location,
1366 currentLocation,
1367 });
1368 name = matcher.record.name;
1369 // since we are navigating to the same location, we don't need to pick the
1370 // params like when `name` is provided
1371 params = assign({}, currentLocation.params, location.params);
1372 path = matcher.stringify(params);
1373 }
1374 const matched = [];
1375 let parentMatcher = matcher;
1376 while (parentMatcher) {
1377 // reversed order so parents are at the beginning
1378 matched.unshift(parentMatcher.record);
1379 parentMatcher = parentMatcher.parent;
1380 }
1381 return {
1382 name,
1383 path,
1384 params,
1385 matched,
1386 meta: mergeMetaFields(matched),
1387 };
1388 }
1389 // add initial routes
1390 routes.forEach(route => addRoute(route));
1391 return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher };
1392 }
1393 function paramsFromLocation(params, keys) {
1394 let newParams = {};
1395 for (let key of keys) {
1396 if (key in params)
1397 newParams[key] = params[key];
1398 }
1399 return newParams;
1400 }
1401 /**
1402 * Normalizes a RouteRecordRaw. Creates a copy
1403 *
1404 * @param record
1405 * @returns the normalized version
1406 */
1407 function normalizeRouteRecord(record) {
1408 return {
1409 path: record.path,
1410 redirect: record.redirect,
1411 name: record.name,
1412 meta: record.meta || {},
1413 aliasOf: undefined,
1414 beforeEnter: record.beforeEnter,
1415 props: normalizeRecordProps(record),
1416 children: record.children || [],
1417 instances: {},
1418 leaveGuards: [],
1419 updateGuards: [],
1420 enterCallbacks: {},
1421 components: 'components' in record
1422 ? record.components || {}
1423 : { default: record.component },
1424 };
1425 }
1426 /**
1427 * Normalize the optional `props` in a record to always be an object similar to
1428 * components. Also accept a boolean for components.
1429 * @param record
1430 */
1431 function normalizeRecordProps(record) {
1432 const propsObject = {};
1433 // props does not exist on redirect records but we can set false directly
1434 const props = record.props || false;
1435 if ('component' in record) {
1436 propsObject.default = props;
1437 }
1438 else {
1439 // NOTE: we could also allow a function to be applied to every component.
1440 // Would need user feedback for use cases
1441 for (let name in record.components)
1442 propsObject[name] = typeof props === 'boolean' ? props : props[name];
1443 }
1444 return propsObject;
1445 }
1446 /**
1447 * Checks if a record or any of its parent is an alias
1448 * @param record
1449 */
1450 function isAliasRecord(record) {
1451 while (record) {
1452 if (record.record.aliasOf)
1453 return true;
1454 record = record.parent;
1455 }
1456 return false;
1457 }
1458 /**
1459 * Merge meta fields of an array of records
1460 *
1461 * @param matched - array of matched records
1462 */
1463 function mergeMetaFields(matched) {
1464 return matched.reduce((meta, record) => assign(meta, record.meta), {});
1465 }
1466 function mergeOptions(defaults, partialOptions) {
1467 let options = {};
1468 for (let key in defaults) {
1469 options[key] =
1470 key in partialOptions ? partialOptions[key] : defaults[key];
1471 }
1472 return options;
1473 }
1474 function isSameParam(a, b) {
1475 return (a.name === b.name &&
1476 a.optional === b.optional &&
1477 a.repeatable === b.repeatable);
1478 }
1479 function checkSameParams(a, b) {
1480 for (let key of a.keys) {
1481 if (!b.keys.find(isSameParam.bind(null, key)))
1482 return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);
1483 }
1484 for (let key of b.keys) {
1485 if (!a.keys.find(isSameParam.bind(null, key)))
1486 return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);
1487 }
1488 }
1489 function checkMissingParamsInAbsolutePath(record, parent) {
1490 for (let key of parent.keys) {
1491 if (!record.keys.find(isSameParam.bind(null, key)))
1492 return warn(`Absolute path "${record.record.path}" should have the exact same param named "${key.name}" as its parent "${parent.record.path}".`);
1493 }
1494 }
1495
1496 /**
1497 * Encoding Rules ␣ = Space Path: ␣ " < > # ? { } Query: ␣ " < > # & = Hash: ␣ "
1498 * < > `
1499 *
1500 * On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2)
1501 * defines some extra characters to be encoded. Most browsers do not encode them
1502 * in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to
1503 * also encode `!'()*`. Leaving unencoded only ASCII alphanumeric(`a-zA-Z0-9`)
1504 * plus `-._~`. This extra safety should be applied to query by patching the
1505 * string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\`
1506 * should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\`
1507 * into a `/` if directly typed in. The _backtick_ (`````) should also be
1508 * encoded everywhere because some browsers like FF encode it when directly
1509 * written while others don't. Safari and IE don't encode ``"<>{}``` in hash.
1510 */
1511 // const EXTRA_RESERVED_RE = /[!'()*]/g
1512 // const encodeReservedReplacer = (c: string) => '%' + c.charCodeAt(0).toString(16)
1513 const HASH_RE = /#/g; // %23
1514 const AMPERSAND_RE = /&/g; // %26
1515 const SLASH_RE = /\//g; // %2F
1516 const EQUAL_RE = /=/g; // %3D
1517 const IM_RE = /\?/g; // %3F
1518 /**
1519 * NOTE: It's not clear to me if we should encode the + symbol in queries, it
1520 * seems to be less flexible than not doing so and I can't find out the legacy
1521 * systems requiring this for regular requests like text/html. In the standard,
1522 * the encoding of the plus character is only mentioned for
1523 * application/x-www-form-urlencoded
1524 * (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo
1525 * leave the plus character as is in queries. To be more flexible, we allow the
1526 * plus character on the query but it can also be manually encoded by the user.
1527 *
1528 * Resources:
1529 * - https://url.spec.whatwg.org/#urlencoded-parsing
1530 * - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20
1531 */
1532 // const PLUS_RE = /\+/g // %3F
1533 const ENC_BRACKET_OPEN_RE = /%5B/g; // [
1534 const ENC_BRACKET_CLOSE_RE = /%5D/g; // ]
1535 const ENC_CARET_RE = /%5E/g; // ^
1536 const ENC_BACKTICK_RE = /%60/g; // `
1537 const ENC_CURLY_OPEN_RE = /%7B/g; // {
1538 const ENC_PIPE_RE = /%7C/g; // |
1539 const ENC_CURLY_CLOSE_RE = /%7D/g; // }
1540 /**
1541 * Encode characters that need to be encoded on the path, search and hash
1542 * sections of the URL.
1543 *
1544 * @internal
1545 * @param text - string to encode
1546 * @returns encoded string
1547 */
1548 function commonEncode(text) {
1549 return encodeURI('' + text)
1550 .replace(ENC_PIPE_RE, '|')
1551 .replace(ENC_BRACKET_OPEN_RE, '[')
1552 .replace(ENC_BRACKET_CLOSE_RE, ']');
1553 }
1554 /**
1555 * Encode characters that need to be encoded on the hash section of the URL.
1556 *
1557 * @param text - string to encode
1558 * @returns encoded string
1559 */
1560 function encodeHash(text) {
1561 return commonEncode(text)
1562 .replace(ENC_CURLY_OPEN_RE, '{')
1563 .replace(ENC_CURLY_CLOSE_RE, '}')
1564 .replace(ENC_CARET_RE, '^');
1565 }
1566 /**
1567 * Encode characters that need to be encoded query values on the query
1568 * section of the URL.
1569 *
1570 * @param text - string to encode
1571 * @returns encoded string
1572 */
1573 function encodeQueryValue(text) {
1574 return commonEncode(text)
1575 .replace(HASH_RE, '%23')
1576 .replace(AMPERSAND_RE, '%26')
1577 .replace(ENC_BACKTICK_RE, '`')
1578 .replace(ENC_CURLY_OPEN_RE, '{')
1579 .replace(ENC_CURLY_CLOSE_RE, '}')
1580 .replace(ENC_CARET_RE, '^');
1581 }
1582 /**
1583 * Like `encodeQueryValue` but also encodes the `=` character.
1584 *
1585 * @param text - string to encode
1586 */
1587 function encodeQueryKey(text) {
1588 return encodeQueryValue(text).replace(EQUAL_RE, '%3D');
1589 }
1590 /**
1591 * Encode characters that need to be encoded on the path section of the URL.
1592 *
1593 * @param text - string to encode
1594 * @returns encoded string
1595 */
1596 function encodePath(text) {
1597 return commonEncode(text).replace(HASH_RE, '%23').replace(IM_RE, '%3F');
1598 }
1599 /**
1600 * Encode characters that need to be encoded on the path section of the URL as a
1601 * param. This function encodes everything {@link encodePath} does plus the
1602 * slash (`/`) character.
1603 *
1604 * @param text - string to encode
1605 * @returns encoded string
1606 */
1607 function encodeParam(text) {
1608 return encodePath(text).replace(SLASH_RE, '%2F');
1609 }
1610 /**
1611 * Decode text using `decodeURIComponent`. Returns the original text if it
1612 * fails.
1613 *
1614 * @param text - string to decode
1615 * @returns decoded string
1616 */
1617 function decode(text) {
1618 try {
1619 return decodeURIComponent('' + text);
1620 }
1621 catch (err) {
1622 warn(`Error decoding "${text}". Using original value`);
1623 }
1624 return '' + text;
1625 }
1626
1627 /**
1628 * Transforms a queryString into a {@link LocationQuery} object. Accept both, a
1629 * version with the leading `?` and without Should work as URLSearchParams
1630
1631 * @internal
1632 *
1633 * @param search - search string to parse
1634 * @returns a query object
1635 */
1636 function parseQuery(search) {
1637 const query = {};
1638 // avoid creating an object with an empty key and empty value
1639 // because of split('&')
1640 if (search === '' || search === '?')
1641 return query;
1642 const hasLeadingIM = search[0] === '?';
1643 const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&');
1644 for (let i = 0; i < searchParams.length; ++i) {
1645 const searchParam = searchParams[i];
1646 // allow the = character
1647 let eqPos = searchParam.indexOf('=');
1648 let key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));
1649 let value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1));
1650 if (key in query) {
1651 // an extra variable for ts types
1652 let currentValue = query[key];
1653 if (!Array.isArray(currentValue)) {
1654 currentValue = query[key] = [currentValue];
1655 }
1656 currentValue.push(value);
1657 }
1658 else {
1659 query[key] = value;
1660 }
1661 }
1662 return query;
1663 }
1664 /**
1665 * Stringifies a {@link LocationQueryRaw} object. Like `URLSearchParams`, it
1666 * doesn't prepend a `?`
1667 *
1668 * @internal
1669 *
1670 * @param query - query object to stringify
1671 * @returns string version of the query without the leading `?`
1672 */
1673 function stringifyQuery(query) {
1674 let search = '';
1675 for (let key in query) {
1676 if (search.length)
1677 search += '&';
1678 const value = query[key];
1679 key = encodeQueryKey(key);
1680 if (value == null) {
1681 // only null adds the value
1682 if (value !== undefined)
1683 search += key;
1684 continue;
1685 }
1686 // keep null values
1687 let values = Array.isArray(value)
1688 ? value.map(v => v && encodeQueryValue(v))
1689 : [value && encodeQueryValue(value)];
1690 for (let i = 0; i < values.length; i++) {
1691 // only append & with i > 0
1692 search += (i ? '&' : '') + key;
1693 if (values[i] != null)
1694 search += ('=' + values[i]);
1695 }
1696 }
1697 return search;
1698 }
1699 /**
1700 * Transforms a {@link LocationQueryRaw} into a {@link LocationQuery} by casting
1701 * numbers into strings, removing keys with an undefined value and replacing
1702 * undefined with null in arrays
1703 *
1704 * @param query - query object to normalize
1705 * @returns a normalized query object
1706 */
1707 function normalizeQuery(query) {
1708 const normalizedQuery = {};
1709 for (let key in query) {
1710 let value = query[key];
1711 if (value !== undefined) {
1712 normalizedQuery[key] = Array.isArray(value)
1713 ? value.map(v => (v == null ? null : '' + v))
1714 : value == null
1715 ? value
1716 : '' + value;
1717 }
1718 }
1719 return normalizedQuery;
1720 }
1721
1722 /**
1723 * Create a list of callbacks that can be reset. Used to create before and after navigation guards list
1724 */
1725 function useCallbacks() {
1726 let handlers = [];
1727 function add(handler) {
1728 handlers.push(handler);
1729 return () => {
1730 const i = handlers.indexOf(handler);
1731 if (i > -1)
1732 handlers.splice(i, 1);
1733 };
1734 }
1735 function reset() {
1736 handlers = [];
1737 }
1738 return {
1739 add,
1740 list: () => handlers,
1741 reset,
1742 };
1743 }
1744
1745 function registerGuard(list, guard) {
1746 const removeFromList = () => {
1747 const index = list.indexOf(guard);
1748 if (index > -1)
1749 list.splice(index, 1);
1750 };
1751 vue.onUnmounted(removeFromList);
1752 vue.onDeactivated(removeFromList);
1753 vue.onActivated(() => {
1754 const index = list.indexOf(guard);
1755 if (index < 0)
1756 list.push(guard);
1757 });
1758 list.push(guard);
1759 }
1760 /**
1761 * Add a navigation guard that triggers whenever the component for the current
1762 * location is about to be left. Similar to {@link beforeRouteLeave} but can be
1763 * used in any component. The guard is removed when the component is unmounted.
1764 *
1765 * @param leaveGuard - {@link NavigationGuard}
1766 */
1767 function onBeforeRouteLeave(leaveGuard) {
1768 if ( !vue.getCurrentInstance()) {
1769 warn('onBeforeRouteLeave must be called at the top of a setup function');
1770 return;
1771 }
1772 const activeRecord = vue.inject(matchedRouteKey, {}).value;
1773 if (!activeRecord) {
1774
1775 warn('onBeforeRouteLeave must be called at the top of a setup function');
1776 return;
1777 }
1778 registerGuard(activeRecord.leaveGuards, leaveGuard);
1779 }
1780 /**
1781 * Add a navigation guard that triggers whenever the current location is about
1782 * to be updated. Similar to {@link beforeRouteUpdate} but can be used in any
1783 * component. The guard is removed when the component is unmounted.
1784 *
1785 * @param updateGuard - {@link NavigationGuard}
1786 */
1787 function onBeforeRouteUpdate(updateGuard) {
1788 if ( !vue.getCurrentInstance()) {
1789 warn('onBeforeRouteUpdate must be called at the top of a setup function');
1790 return;
1791 }
1792 const activeRecord = vue.inject(matchedRouteKey, {}).value;
1793 if (!activeRecord) {
1794
1795 warn('onBeforeRouteUpdate must be called at the top of a setup function');
1796 return;
1797 }
1798 registerGuard(activeRecord.updateGuards, updateGuard);
1799 }
1800 function guardToPromiseFn(guard, to, from, record, name) {
1801 // keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place
1802 const enterCallbackArray = record &&
1803 // name is defined if record is because of the function overload
1804 (record.enterCallbacks[name] = record.enterCallbacks[name] || []);
1805 return () => new Promise((resolve, reject) => {
1806 const next = (valid) => {
1807 if (valid === false)
1808 reject(createRouterError(4 /* NAVIGATION_ABORTED */, {
1809 from,
1810 to,
1811 }));
1812 else if (valid instanceof Error) {
1813 reject(valid);
1814 }
1815 else if (isRouteLocation(valid)) {
1816 reject(createRouterError(2 /* NAVIGATION_GUARD_REDIRECT */, {
1817 from: to,
1818 to: valid,
1819 }));
1820 }
1821 else {
1822 if (enterCallbackArray &&
1823 // since enterCallbackArray is truthy, both record and name also are
1824 record.enterCallbacks[name] === enterCallbackArray &&
1825 typeof valid === 'function')
1826 enterCallbackArray.push(valid);
1827 resolve();
1828 }
1829 };
1830 // wrapping with Promise.resolve allows it to work with both async and sync guards
1831 const guardReturn = guard.call(record && record.instances[name], to, from, canOnlyBeCalledOnce(next, to, from) );
1832 let guardCall = Promise.resolve(guardReturn);
1833 if (guard.length < 3)
1834 guardCall = guardCall.then(next);
1835 if ( guard.length > 2) {
1836 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.`;
1837 if (typeof guardReturn === 'object' && 'then' in guardReturn) {
1838 guardCall = guardCall.then(resolvedValue => {
1839 // @ts-ignore: _called is added at canOnlyBeCalledOnce
1840 if (!next._called) {
1841 warn(message);
1842 return Promise.reject(new Error('Invalid navigation guard'));
1843 }
1844 return resolvedValue;
1845 });
1846 // TODO: test me!
1847 }
1848 else if (guardReturn !== undefined) {
1849 // @ts-ignore: _called is added at canOnlyBeCalledOnce
1850 if (!next._called) {
1851 warn(message);
1852 reject(new Error('Invalid navigation guard'));
1853 return;
1854 }
1855 }
1856 }
1857 guardCall.catch(err => reject(err));
1858 });
1859 }
1860 function canOnlyBeCalledOnce(next, to, from) {
1861 let called = 0;
1862 return function () {
1863 if (called++ === 1)
1864 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.`);
1865 // @ts-ignore: we put it in the original one because it's easier to check
1866 next._called = true;
1867 if (called === 1)
1868 next.apply(null, arguments);
1869 };
1870 }
1871 function extractComponentsGuards(matched, guardType, to, from) {
1872 const guards = [];
1873 for (const record of matched) {
1874 for (const name in record.components) {
1875 let rawComponent = record.components[name];
1876 {
1877 if (!rawComponent ||
1878 (typeof rawComponent !== 'object' &&
1879 typeof rawComponent !== 'function')) {
1880 warn(`Component "${name}" in record with path "${record.path}" is not` +
1881 ` a valid component. Received "${String(rawComponent)}".`);
1882 // throw to ensure we stop here but warn to ensure the message isn't
1883 // missed by the user
1884 throw new Error('Invalid route component');
1885 }
1886 else if ('then' in rawComponent) {
1887 // warn if user wrote import('/component.vue') instead of () =>
1888 // import('./component.vue')
1889 warn(`Component "${name}" in record with path "${record.path}" is a ` +
1890 `Promise instead of a function that returns a Promise. Did you ` +
1891 `write "import('./MyPage.vue')" instead of ` +
1892 `"() => import('./MyPage.vue')" ? This will break in ` +
1893 `production if not fixed.`);
1894 let promise = rawComponent;
1895 rawComponent = () => promise;
1896 }
1897 }
1898 // skip update and leave guards if the route component is not mounted
1899 if (guardType !== 'beforeRouteEnter' && !record.instances[name])
1900 continue;
1901 if (isRouteComponent(rawComponent)) {
1902 // __vccOpts is added by vue-class-component and contain the regular options
1903 let options = rawComponent.__vccOpts || rawComponent;
1904 const guard = options[guardType];
1905 guard && guards.push(guardToPromiseFn(guard, to, from, record, name));
1906 }
1907 else {
1908 // start requesting the chunk already
1909 let componentPromise = rawComponent();
1910 if ( !('catch' in componentPromise)) {
1911 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.`);
1912 componentPromise = Promise.resolve(componentPromise);
1913 }
1914 else {
1915 // display the error if any
1916 componentPromise = componentPromise.catch( err => err && warn(err) );
1917 }
1918 guards.push(() => componentPromise.then(resolved => {
1919 if (!resolved)
1920 return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}"`));
1921 const resolvedComponent = isESModule(resolved)
1922 ? resolved.default
1923 : resolved;
1924 // replace the function with the resolved component
1925 record.components[name] = resolvedComponent;
1926 // @ts-ignore: the options types are not propagated to Component
1927 const guard = resolvedComponent[guardType];
1928 return guard && guardToPromiseFn(guard, to, from, record, name)();
1929 }));
1930 }
1931 }
1932 }
1933 return guards;
1934 }
1935 /**
1936 * Allows differentiating lazy components from functional components and vue-class-component
1937 * @param component
1938 */
1939 function isRouteComponent(component) {
1940 return (typeof component === 'object' ||
1941 'displayName' in component ||
1942 'props' in component ||
1943 '__vccOpts' in component);
1944 }
1945
1946 // TODO: we could allow currentRoute as a prop to expose `isActive` and
1947 // `isExactActive` behavior should go through an RFC
1948 function useLink(props) {
1949 const router = vue.inject(routerKey);
1950 const currentRoute = vue.inject(routeLocationKey);
1951 const route = vue.computed(() => router.resolve(vue.unref(props.to)));
1952 const activeRecordIndex = vue.computed(() => {
1953 let { matched } = route.value;
1954 let { length } = matched;
1955 const routeMatched = matched[length - 1];
1956 let currentMatched = currentRoute.matched;
1957 if (!routeMatched || !currentMatched.length)
1958 return -1;
1959 let index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched));
1960 if (index > -1)
1961 return index;
1962 // possible parent record
1963 let parentRecordPath = getOriginalPath(matched[length - 2]);
1964 return (
1965 // we are dealing with nested routes
1966 length > 1 &&
1967 // if the parent and matched route have the same path, this link is
1968 // referring to the empty child. Or we currently are on a different
1969 // child of the same parent
1970 getOriginalPath(routeMatched) === parentRecordPath &&
1971 // avoid comparing the child with its parent
1972 currentMatched[currentMatched.length - 1].path !== parentRecordPath
1973 ? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2]))
1974 : index);
1975 });
1976 const isActive = vue.computed(() => activeRecordIndex.value > -1 &&
1977 includesParams(currentRoute.params, route.value.params));
1978 const isExactActive = vue.computed(() => activeRecordIndex.value > -1 &&
1979 activeRecordIndex.value === currentRoute.matched.length - 1 &&
1980 isSameRouteLocationParams(currentRoute.params, route.value.params));
1981 function navigate(e = {}) {
1982 if (guardEvent(e))
1983 return router[vue.unref(props.replace) ? 'replace' : 'push'](vue.unref(props.to));
1984 return Promise.resolve();
1985 }
1986 return {
1987 route,
1988 href: vue.computed(() => route.value.href),
1989 isActive,
1990 isExactActive,
1991 navigate,
1992 };
1993 }
1994 const RouterLinkImpl = /*#__PURE__*/ vue.defineComponent({
1995 name: 'RouterLink',
1996 props: {
1997 to: {
1998 type: [String, Object],
1999 required: true,
2000 },
2001 activeClass: String,
2002 // inactiveClass: String,
2003 exactActiveClass: String,
2004 custom: Boolean,
2005 ariaCurrentValue: {
2006 type: String,
2007 default: 'page',
2008 },
2009 },
2010 setup(props, { slots, attrs }) {
2011 const link = vue.reactive(useLink(props));
2012 const { options } = vue.inject(routerKey);
2013 const elClass = vue.computed(() => ({
2014 [getLinkClass(props.activeClass, options.linkActiveClass, 'router-link-active')]: link.isActive,
2015 // [getLinkClass(
2016 // props.inactiveClass,
2017 // options.linkInactiveClass,
2018 // 'router-link-inactive'
2019 // )]: !link.isExactActive,
2020 [getLinkClass(props.exactActiveClass, options.linkExactActiveClass, 'router-link-exact-active')]: link.isExactActive,
2021 }));
2022 return () => {
2023 const children = slots.default && slots.default(link);
2024 return props.custom
2025 ? children
2026 : vue.h('a', assign({
2027 'aria-current': link.isExactActive
2028 ? props.ariaCurrentValue
2029 : null,
2030 onClick: link.navigate,
2031 href: link.href,
2032 }, attrs, {
2033 class: elClass.value,
2034 }), children);
2035 };
2036 },
2037 });
2038 // export the public type for h/tsx inference
2039 // also to avoid inline import() in generated d.ts files
2040 /**
2041 * Component to render a link that triggers a navigation on click.
2042 */
2043 const RouterLink = RouterLinkImpl;
2044 function guardEvent(e) {
2045 // don't redirect with control keys
2046 if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
2047 return;
2048 // don't redirect when preventDefault called
2049 if (e.defaultPrevented)
2050 return;
2051 // don't redirect on right click
2052 if (e.button !== undefined && e.button !== 0)
2053 return;
2054 // don't redirect if `target="_blank"`
2055 // @ts-ignore getAttribute does exist
2056 if (e.currentTarget && e.currentTarget.getAttribute) {
2057 // @ts-ignore getAttribute exists
2058 const target = e.currentTarget.getAttribute('target');
2059 if (/\b_blank\b/i.test(target))
2060 return;
2061 }
2062 // this may be a Weex event which doesn't have this method
2063 if (e.preventDefault)
2064 e.preventDefault();
2065 return true;
2066 }
2067 function includesParams(outer, inner) {
2068 for (let key in inner) {
2069 let innerValue = inner[key];
2070 let outerValue = outer[key];
2071 if (typeof innerValue === 'string') {
2072 if (innerValue !== outerValue)
2073 return false;
2074 }
2075 else {
2076 if (!Array.isArray(outerValue) ||
2077 outerValue.length !== innerValue.length ||
2078 innerValue.some((value, i) => value !== outerValue[i]))
2079 return false;
2080 }
2081 }
2082 return true;
2083 }
2084 /**
2085 * Get the original path value of a record by following its aliasOf
2086 * @param record
2087 */
2088 function getOriginalPath(record) {
2089 return record ? (record.aliasOf ? record.aliasOf.path : record.path) : '';
2090 }
2091 /**
2092 * Utility class to get the active class based on defaults.
2093 * @param propClass
2094 * @param globalClass
2095 * @param defaultClass
2096 */
2097 const getLinkClass = (propClass, globalClass, defaultClass) => propClass != null
2098 ? propClass
2099 : globalClass != null
2100 ? globalClass
2101 : defaultClass;
2102
2103 const RouterViewImpl = /*#__PURE__*/ vue.defineComponent({
2104 name: 'RouterView',
2105 props: {
2106 name: {
2107 type: String,
2108 default: 'default',
2109 },
2110 route: Object,
2111 },
2112 setup(props, { attrs, slots }) {
2113 warnDeprecatedUsage();
2114 const injectedRoute = vue.inject(routeLocationKey);
2115 const depth = vue.inject(viewDepthKey, 0);
2116 const matchedRouteRef = vue.computed(() => (props.route || injectedRoute).matched[depth]);
2117 vue.provide(viewDepthKey, depth + 1);
2118 vue.provide(matchedRouteKey, matchedRouteRef);
2119 const viewRef = vue.ref();
2120 // watch at the same time the component instance, the route record we are
2121 // rendering, and the name
2122 vue.watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {
2123 // copy reused instances
2124 if (to) {
2125 // this will update the instance for new instances as well as reused
2126 // instances when navigating to a new route
2127 to.instances[name] = instance;
2128 // the component instance is reused for a different route or name so
2129 // we copy any saved update or leave guards
2130 if (from && instance === oldInstance) {
2131 to.leaveGuards = from.leaveGuards;
2132 to.updateGuards = from.updateGuards;
2133 }
2134 }
2135 // trigger beforeRouteEnter next callbacks
2136 if (instance &&
2137 to &&
2138 // if there is no instance but to and from are the same this might be
2139 // the first visit
2140 (!from || !isSameRouteRecord(to, from) || !oldInstance)) {
2141 (to.enterCallbacks[name] || []).forEach(callback => callback(instance));
2142 }
2143 }, { flush: 'post' });
2144 return () => {
2145 const route = props.route || injectedRoute;
2146 const matchedRoute = matchedRouteRef.value;
2147 const ViewComponent = matchedRoute && matchedRoute.components[props.name];
2148 // we need the value at the time we render because when we unmount, we
2149 // navigated to a different location so the value is different
2150 const currentName = props.name;
2151 if (!ViewComponent) {
2152 return slots.default
2153 ? slots.default({ Component: ViewComponent, route })
2154 : null;
2155 }
2156 // props from route configuration
2157 const routePropsOption = matchedRoute.props[props.name];
2158 const routeProps = routePropsOption
2159 ? routePropsOption === true
2160 ? route.params
2161 : typeof routePropsOption === 'function'
2162 ? routePropsOption(route)
2163 : routePropsOption
2164 : null;
2165 const onVnodeUnmounted = vnode => {
2166 // remove the instance reference to prevent leak
2167 if (vnode.component.isUnmounted) {
2168 matchedRoute.instances[currentName] = null;
2169 }
2170 };
2171 const component = vue.h(ViewComponent, assign({}, routeProps, attrs, {
2172 onVnodeUnmounted,
2173 ref: viewRef,
2174 }));
2175 return (
2176 // pass the vnode to the slot as a prop.
2177 // h and <component :is="..."> both accept vnodes
2178 slots.default
2179 ? slots.default({ Component: component, route })
2180 : component);
2181 };
2182 },
2183 });
2184 // export the public type for h/tsx inference
2185 // also to avoid inline import() in generated d.ts files
2186 /**
2187 * Component to display the current route the user is at.
2188 */
2189 const RouterView = RouterViewImpl;
2190 // warn against deprecated usage with <transition> & <keep-alive>
2191 // due to functional component being no longer eager in Vue 3
2192 function warnDeprecatedUsage() {
2193 const instance = vue.getCurrentInstance();
2194 const parentName = instance.parent && instance.parent.type.name;
2195 if (parentName &&
2196 (parentName === 'KeepAlive' || parentName.includes('Transition'))) {
2197 const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition';
2198 warn(`<router-view> can no longer be used directly inside <transition> or <keep-alive>.\n` +
2199 `Use slot props instead:\n\n` +
2200 `<router-view v-slot="{ Component }">\n` +
2201 ` <${comp}>\n` +
2202 ` <component :is="Component" />\n` +
2203 ` </${comp}>\n` +
2204 `</router-view>`);
2205 }
2206 }
2207
2208 /**
2209 * Creates a Router instance that can be used by a Vue app.
2210 *
2211 * @param options - {@link RouterOptions}
2212 */
2213 function createRouter(options) {
2214 const matcher = createRouterMatcher(options.routes, options);
2215 let parseQuery$1 = options.parseQuery || parseQuery;
2216 let stringifyQuery$1 = options.stringifyQuery || stringifyQuery;
2217 let { scrollBehavior } = options;
2218 let routerHistory = options.history;
2219 const beforeGuards = useCallbacks();
2220 const beforeResolveGuards = useCallbacks();
2221 const afterGuards = useCallbacks();
2222 const currentRoute = vue.shallowRef(START_LOCATION_NORMALIZED);
2223 let pendingLocation = START_LOCATION_NORMALIZED;
2224 // leave the scrollRestoration if no scrollBehavior is provided
2225 if (isBrowser && scrollBehavior && 'scrollRestoration' in history) {
2226 history.scrollRestoration = 'manual';
2227 }
2228 const normalizeParams = applyToParams.bind(null, paramValue => '' + paramValue);
2229 const encodeParams = applyToParams.bind(null, encodeParam);
2230 const decodeParams = applyToParams.bind(null, decode);
2231 function addRoute(parentOrRoute, route) {
2232 let parent;
2233 let record;
2234 if (isRouteName(parentOrRoute)) {
2235 parent = matcher.getRecordMatcher(parentOrRoute);
2236 record = route;
2237 }
2238 else {
2239 record = parentOrRoute;
2240 }
2241 return matcher.addRoute(record, parent);
2242 }
2243 function removeRoute(name) {
2244 let recordMatcher = matcher.getRecordMatcher(name);
2245 if (recordMatcher) {
2246 matcher.removeRoute(recordMatcher);
2247 }
2248 else {
2249 warn(`Cannot remove non-existent route "${String(name)}"`);
2250 }
2251 }
2252 function getRoutes() {
2253 return matcher.getRoutes().map(routeMatcher => routeMatcher.record);
2254 }
2255 function hasRoute(name) {
2256 return !!matcher.getRecordMatcher(name);
2257 }
2258 function resolve(rawLocation, currentLocation) {
2259 // const objectLocation = routerLocationAsObject(rawLocation)
2260 // we create a copy to modify it later
2261 currentLocation = assign({}, currentLocation || currentRoute.value);
2262 if (typeof rawLocation === 'string') {
2263 let locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path);
2264 let matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation);
2265 let href = routerHistory.createHref(locationNormalized.fullPath);
2266 {
2267 if (href.startsWith('//'))
2268 warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);
2269 else if (!matchedRoute.matched.length) {
2270 warn(`No match found for location with path "${rawLocation}"`);
2271 }
2272 }
2273 // locationNormalized is always a new object
2274 return assign(locationNormalized, matchedRoute, {
2275 params: decodeParams(matchedRoute.params),
2276 hash: decode(locationNormalized.hash),
2277 redirectedFrom: undefined,
2278 href,
2279 });
2280 }
2281 let matcherLocation;
2282 // path could be relative in object as well
2283 if ('path' in rawLocation) {
2284 if (
2285 'params' in rawLocation &&
2286 !('name' in rawLocation) &&
2287 Object.keys(rawLocation.params).length) {
2288 warn(`Path "${rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`);
2289 }
2290 matcherLocation = assign({}, rawLocation, {
2291 path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path,
2292 });
2293 }
2294 else {
2295 // pass encoded values to the matcher so it can produce encoded path and fullPath
2296 matcherLocation = assign({}, rawLocation, {
2297 params: encodeParams(rawLocation.params),
2298 });
2299 // current location params are decoded, we need to encode them in case the
2300 // matcher merges the params
2301 currentLocation.params = encodeParams(currentLocation.params);
2302 }
2303 let matchedRoute = matcher.resolve(matcherLocation, currentLocation);
2304 const hash = rawLocation.hash || '';
2305 if ( hash && !hash.startsWith('#')) {
2306 warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`);
2307 }
2308 // decoding them) the matcher might have merged current location params so
2309 // we need to run the decoding again
2310 matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
2311 const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
2312 hash: encodeHash(hash),
2313 path: matchedRoute.path,
2314 }));
2315 let href = routerHistory.createHref(fullPath);
2316 {
2317 if (href.startsWith('//')) {
2318 warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);
2319 }
2320 else if (!matchedRoute.matched.length) {
2321 warn(`No match found for location with path "${'path' in rawLocation ? rawLocation.path : rawLocation}"`);
2322 }
2323 }
2324 return assign({
2325 fullPath,
2326 // keep the hash encoded so fullPath is effectively path + encodedQuery +
2327 // hash
2328 hash,
2329 query:
2330 // if the user is using a custom query lib like qs, we might have
2331 // nested objects, so we keep the query as is, meaning it can contain
2332 // numbers at `$route.query`, but at the point, the user will have to
2333 // use their own type anyway.
2334 // https://github.com/vuejs/vue-router-next/issues/328#issuecomment-649481567
2335 stringifyQuery$1 === stringifyQuery
2336 ? normalizeQuery(rawLocation.query)
2337 : rawLocation.query,
2338 }, matchedRoute, {
2339 redirectedFrom: undefined,
2340 href,
2341 });
2342 }
2343 function locationAsObject(to) {
2344 return typeof to === 'string' ? { path: to } : assign({}, to);
2345 }
2346 function checkCanceledNavigation(to, from) {
2347 if (pendingLocation !== to) {
2348 return createRouterError(8 /* NAVIGATION_CANCELLED */, {
2349 from,
2350 to,
2351 });
2352 }
2353 }
2354 function push(to) {
2355 return pushWithRedirect(to);
2356 }
2357 function replace(to) {
2358 return push(assign(locationAsObject(to), { replace: true }));
2359 }
2360 function pushWithRedirect(to, redirectedFrom) {
2361 const targetLocation = (pendingLocation = resolve(to));
2362 const from = currentRoute.value;
2363 const data = to.state;
2364 const force = to.force;
2365 // to could be a string where `replace` is a function
2366 const replace = to.replace === true;
2367 const lastMatched = targetLocation.matched[targetLocation.matched.length - 1];
2368 if (lastMatched && lastMatched.redirect) {
2369 const { redirect } = lastMatched;
2370 // transform it into an object to pass the original RouteLocaleOptions
2371 let newTargetLocation = locationAsObject(typeof redirect === 'function' ? redirect(targetLocation) : redirect);
2372 if (
2373 !('path' in newTargetLocation) &&
2374 !('name' in newTargetLocation)) {
2375 warn(`Invalid redirect found:\n${JSON.stringify(newTargetLocation, null, 2)}\n when navigating to "${targetLocation.fullPath}". A redirect must contain a name or path. This will break in production.`);
2376 return Promise.reject(new Error('Invalid redirect'));
2377 }
2378 return pushWithRedirect(assign({
2379 query: targetLocation.query,
2380 hash: targetLocation.hash,
2381 params: targetLocation.params,
2382 }, newTargetLocation, {
2383 state: data,
2384 force,
2385 replace,
2386 }),
2387 // keep original redirectedFrom if it exists
2388 redirectedFrom || targetLocation);
2389 }
2390 // if it was a redirect we already called `pushWithRedirect` above
2391 const toLocation = targetLocation;
2392 toLocation.redirectedFrom = redirectedFrom;
2393 let failure;
2394 if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
2395 failure = createRouterError(16 /* NAVIGATION_DUPLICATED */, { to: toLocation, from });
2396 // trigger scroll to allow scrolling to the same anchor
2397 handleScroll(from, from,
2398 // this is a push, the only way for it to be triggered from a
2399 // history.listen is with a redirect, which makes it become a pus
2400 true,
2401 // This cannot be the first navigation because the initial location
2402 // cannot be manually navigated to
2403 false);
2404 }
2405 return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
2406 .catch((error) => {
2407 if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ |
2408 8 /* NAVIGATION_CANCELLED */ |
2409 2 /* NAVIGATION_GUARD_REDIRECT */)) {
2410 return error;
2411 }
2412 // unknown error, rejects
2413 return triggerError(error);
2414 })
2415 .then((failure) => {
2416 if (failure) {
2417 if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
2418 if (
2419 // we are redirecting to the same location we were already at
2420 isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) &&
2421 // and we have done it a couple of times
2422 redirectedFrom &&
2423 // @ts-ignore
2424 (redirectedFrom._count = redirectedFrom._count
2425 ? // @ts-ignore
2426 redirectedFrom._count + 1
2427 : 1) > 10) {
2428 warn(`Detected an infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow. This will break in production if not fixed.`);
2429 return Promise.reject(new Error('Infinite redirect in navigation guard'));
2430 }
2431 return pushWithRedirect(
2432 // keep options
2433 assign(locationAsObject(failure.to), {
2434 state: data,
2435 force,
2436 replace,
2437 }),
2438 // preserve the original redirectedFrom if any
2439 redirectedFrom || toLocation);
2440 }
2441 }
2442 else {
2443 // if we fail we don't finalize the navigation
2444 failure = finalizeNavigation(toLocation, from, true, replace, data);
2445 }
2446 triggerAfterEach(toLocation, from, failure);
2447 return failure;
2448 });
2449 }
2450 /**
2451 * Helper to reject and skip all navigation guards if a new navigation happened
2452 * @param to
2453 * @param from
2454 */
2455 function checkCanceledNavigationAndReject(to, from) {
2456 const error = checkCanceledNavigation(to, from);
2457 return error ? Promise.reject(error) : Promise.resolve();
2458 }
2459 // TODO: refactor the whole before guards by internally using router.beforeEach
2460 function navigate(to, from) {
2461 let guards;
2462 const [leavingRecords, updatingRecords, enteringRecords,] = extractChangingRecords(to, from);
2463 // all components here have been resolved once because we are leaving
2464 guards = extractComponentsGuards(leavingRecords.reverse(), 'beforeRouteLeave', to, from);
2465 // leavingRecords is already reversed
2466 for (const record of leavingRecords) {
2467 for (const guard of record.leaveGuards) {
2468 guards.push(guardToPromiseFn(guard, to, from));
2469 }
2470 }
2471 const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from);
2472 guards.push(canceledNavigationCheck);
2473 // run the queue of per route beforeRouteLeave guards
2474 return (runGuardQueue(guards)
2475 .then(() => {
2476 // check global guards beforeEach
2477 guards = [];
2478 for (const guard of beforeGuards.list()) {
2479 guards.push(guardToPromiseFn(guard, to, from));
2480 }
2481 guards.push(canceledNavigationCheck);
2482 return runGuardQueue(guards);
2483 })
2484 .then(() => {
2485 // check in components beforeRouteUpdate
2486 guards = extractComponentsGuards(updatingRecords, 'beforeRouteUpdate', to, from);
2487 for (const record of updatingRecords) {
2488 for (const guard of record.updateGuards) {
2489 guards.push(guardToPromiseFn(guard, to, from));
2490 }
2491 }
2492 guards.push(canceledNavigationCheck);
2493 // run the queue of per route beforeEnter guards
2494 return runGuardQueue(guards);
2495 })
2496 .then(() => {
2497 // check the route beforeEnter
2498 guards = [];
2499 for (const record of to.matched) {
2500 // do not trigger beforeEnter on reused views
2501 if (record.beforeEnter && from.matched.indexOf(record) < 0) {
2502 if (Array.isArray(record.beforeEnter)) {
2503 for (const beforeEnter of record.beforeEnter)
2504 guards.push(guardToPromiseFn(beforeEnter, to, from));
2505 }
2506 else {
2507 guards.push(guardToPromiseFn(record.beforeEnter, to, from));
2508 }
2509 }
2510 }
2511 guards.push(canceledNavigationCheck);
2512 // run the queue of per route beforeEnter guards
2513 return runGuardQueue(guards);
2514 })
2515 .then(() => {
2516 // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
2517 // clear existing enterCallbacks, these are added by extractComponentsGuards
2518 to.matched.forEach(record => (record.enterCallbacks = {}));
2519 // check in-component beforeRouteEnter
2520 guards = extractComponentsGuards(enteringRecords, 'beforeRouteEnter', to, from);
2521 guards.push(canceledNavigationCheck);
2522 // run the queue of per route beforeEnter guards
2523 return runGuardQueue(guards);
2524 })
2525 .then(() => {
2526 // check global guards beforeResolve
2527 guards = [];
2528 for (const guard of beforeResolveGuards.list()) {
2529 guards.push(guardToPromiseFn(guard, to, from));
2530 }
2531 guards.push(canceledNavigationCheck);
2532 return runGuardQueue(guards);
2533 })
2534 // catch any navigation canceled
2535 .catch(err => isNavigationFailure(err, 8 /* NAVIGATION_CANCELLED */)
2536 ? err
2537 : Promise.reject(err)));
2538 }
2539 function triggerAfterEach(to, from, failure) {
2540 // navigation is confirmed, call afterGuards
2541 // TODO: wrap with error handlers
2542 for (const guard of afterGuards.list())
2543 guard(to, from, failure);
2544 }
2545 /**
2546 * - Cleans up any navigation guards
2547 * - Changes the url if necessary
2548 * - Calls the scrollBehavior
2549 */
2550 function finalizeNavigation(toLocation, from, isPush, replace, data) {
2551 // a more recent navigation took place
2552 const error = checkCanceledNavigation(toLocation, from);
2553 if (error)
2554 return error;
2555 // only consider as push if it's not the first navigation
2556 const isFirstNavigation = from === START_LOCATION_NORMALIZED;
2557 const state = !isBrowser ? {} : history.state;
2558 // change URL only if the user did a push/replace and if it's not the initial navigation because
2559 // it's just reflecting the url
2560 if (isPush) {
2561 // on the initial navigation, we want to reuse the scroll position from
2562 // history state if it exists
2563 if (replace || isFirstNavigation)
2564 routerHistory.replace(toLocation.fullPath, assign({
2565 scroll: isFirstNavigation && state && state.scroll,
2566 }, data));
2567 else
2568 routerHistory.push(toLocation.fullPath, data);
2569 }
2570 // accept current navigation
2571 currentRoute.value = toLocation;
2572 handleScroll(toLocation, from, isPush, isFirstNavigation);
2573 markAsReady();
2574 }
2575 let removeHistoryListener;
2576 // attach listener to history to trigger navigations
2577 function setupListeners() {
2578 removeHistoryListener = routerHistory.listen((to, _from, info) => {
2579 // cannot be a redirect route because it was in history
2580 const toLocation = resolve(to);
2581 pendingLocation = toLocation;
2582 const from = currentRoute.value;
2583 // TODO: should be moved to web history?
2584 if (isBrowser) {
2585 saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
2586 }
2587 navigate(toLocation, from)
2588 .catch((error) => {
2589 if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ | 8 /* NAVIGATION_CANCELLED */)) {
2590 return error;
2591 }
2592 if (isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
2593 // do not restore history on unknown direction
2594 if (info.delta)
2595 routerHistory.go(-info.delta, false);
2596 // the error is already handled by router.push we just want to avoid
2597 // logging the error
2598 pushWithRedirect(error.to, toLocation
2599 // avoid an uncaught rejection
2600 ).catch(noop);
2601 // avoid the then branch
2602 return Promise.reject();
2603 }
2604 // do not restore history on unknown direction
2605 if (info.delta)
2606 routerHistory.go(-info.delta, false);
2607 // unrecognized error, transfer to the global handler
2608 return triggerError(error);
2609 })
2610 .then((failure) => {
2611 failure =
2612 failure ||
2613 finalizeNavigation(
2614 // after navigation, all matched components are resolved
2615 toLocation, from, false);
2616 // revert the navigation
2617 if (failure && info.delta)
2618 routerHistory.go(-info.delta, false);
2619 triggerAfterEach(toLocation, from, failure);
2620 })
2621 .catch(noop);
2622 });
2623 }
2624 // Initialization and Errors
2625 let readyHandlers = useCallbacks();
2626 let errorHandlers = useCallbacks();
2627 let ready;
2628 /**
2629 * Trigger errorHandlers added via onError and throws the error as well
2630 * @param error - error to throw
2631 * @returns the error as a rejected promise
2632 */
2633 function triggerError(error) {
2634 markAsReady(error);
2635 errorHandlers.list().forEach(handler => handler(error));
2636 return Promise.reject(error);
2637 }
2638 function isReady() {
2639 if (ready && currentRoute.value !== START_LOCATION_NORMALIZED)
2640 return Promise.resolve();
2641 return new Promise((resolve, reject) => {
2642 readyHandlers.add([resolve, reject]);
2643 });
2644 }
2645 /**
2646 * Mark the router as ready, resolving the promised returned by isReady(). Can
2647 * only be called once, otherwise does nothing.
2648 * @param err - optional error
2649 */
2650 function markAsReady(err) {
2651 if (ready)
2652 return;
2653 ready = true;
2654 setupListeners();
2655 readyHandlers
2656 .list()
2657 .forEach(([resolve, reject]) => (err ? reject(err) : resolve()));
2658 readyHandlers.reset();
2659 }
2660 // Scroll behavior
2661 function handleScroll(to, from, isPush, isFirstNavigation) {
2662 if (!isBrowser || !scrollBehavior)
2663 return Promise.resolve();
2664 let scrollPosition = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) ||
2665 ((isFirstNavigation || !isPush) &&
2666 history.state &&
2667 history.state.scroll) ||
2668 null;
2669 return vue.nextTick()
2670 .then(() => scrollBehavior(to, from, scrollPosition))
2671 .then(position => position && scrollToPosition(position))
2672 .catch(triggerError);
2673 }
2674 const go = (delta) => routerHistory.go(delta);
2675 let started;
2676 const installedApps = new Set();
2677 const router = {
2678 currentRoute,
2679 addRoute,
2680 removeRoute,
2681 hasRoute,
2682 getRoutes,
2683 resolve,
2684 options,
2685 push,
2686 replace,
2687 go,
2688 back: () => go(-1),
2689 forward: () => go(1),
2690 beforeEach: beforeGuards.add,
2691 beforeResolve: beforeResolveGuards.add,
2692 afterEach: afterGuards.add,
2693 onError: errorHandlers.add,
2694 isReady,
2695 install(app) {
2696 const router = this;
2697 app.component('RouterLink', RouterLink);
2698 app.component('RouterView', RouterView);
2699 app.config.globalProperties.$router = router;
2700 Object.defineProperty(app.config.globalProperties, '$route', {
2701 get: () => vue.unref(currentRoute),
2702 });
2703 // this initial navigation is only necessary on client, on server it doesn't
2704 // make sense because it will create an extra unnecessary navigation and could
2705 // lead to problems
2706 if (isBrowser &&
2707 // used for the initial navigation client side to avoid pushing
2708 // multiple times when the router is used in multiple apps
2709 !started &&
2710 currentRoute.value === START_LOCATION_NORMALIZED) {
2711 // see above
2712 started = true;
2713 push(routerHistory.location).catch(err => {
2714 warn('Unexpected error when starting the router:', err);
2715 });
2716 }
2717 const reactiveRoute = {};
2718 for (let key in START_LOCATION_NORMALIZED) {
2719 // @ts-ignore: the key matches
2720 reactiveRoute[key] = vue.computed(() => currentRoute.value[key]);
2721 }
2722 app.provide(routerKey, router);
2723 app.provide(routeLocationKey, vue.reactive(reactiveRoute));
2724 let unmountApp = app.unmount;
2725 installedApps.add(app);
2726 app.unmount = function () {
2727 installedApps.delete(app);
2728 if (installedApps.size < 1) {
2729 removeHistoryListener();
2730 currentRoute.value = START_LOCATION_NORMALIZED;
2731 started = false;
2732 ready = false;
2733 }
2734 unmountApp.call(this, arguments);
2735 };
2736 },
2737 };
2738 return router;
2739 }
2740 function runGuardQueue(guards) {
2741 return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
2742 }
2743 function extractChangingRecords(to, from) {
2744 const leavingRecords = [];
2745 const updatingRecords = [];
2746 const enteringRecords = [];
2747 const len = Math.max(from.matched.length, to.matched.length);
2748 for (let i = 0; i < len; i++) {
2749 const recordFrom = from.matched[i];
2750 if (recordFrom) {
2751 if (to.matched.indexOf(recordFrom) < 0)
2752 leavingRecords.push(recordFrom);
2753 else
2754 updatingRecords.push(recordFrom);
2755 }
2756 const recordTo = to.matched[i];
2757 if (recordTo) {
2758 // the type doesn't matter because we are comparing per reference
2759 if (from.matched.indexOf(recordTo) < 0)
2760 enteringRecords.push(recordTo);
2761 }
2762 }
2763 return [leavingRecords, updatingRecords, enteringRecords];
2764 }
2765
2766 /**
2767 * Returns the router instance. Equivalent to using `$router` inside
2768 * templates.
2769 */
2770 function useRouter() {
2771 return vue.inject(routerKey);
2772 }
2773 /**
2774 * Returns the current route location. Equivalent to using `$route` inside
2775 * templates.
2776 */
2777 function useRoute() {
2778 return vue.inject(routeLocationKey);
2779 }
2780
2781 exports.RouterLink = RouterLink;
2782 exports.RouterView = RouterView;
2783 exports.START_LOCATION = START_LOCATION_NORMALIZED;
2784 exports.createMemoryHistory = createMemoryHistory;
2785 exports.createRouter = createRouter;
2786 exports.createRouterMatcher = createRouterMatcher;
2787 exports.createWebHashHistory = createWebHashHistory;
2788 exports.createWebHistory = createWebHistory;
2789 exports.isNavigationFailure = isNavigationFailure;
2790 exports.onBeforeRouteLeave = onBeforeRouteLeave;
2791 exports.onBeforeRouteUpdate = onBeforeRouteUpdate;
2792 exports.parseQuery = parseQuery;
2793 exports.stringifyQuery = stringifyQuery;
2794 exports.useLink = useLink;
2795 exports.useRoute = useRoute;
2796 exports.useRouter = useRouter;
2797
2798 Object.defineProperty(exports, '__esModule', { value: true });
2799
2800 return exports;
2801
2802}({}, Vue));