kdu-router 4.0.16 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/kdu-router.cjs +3631 -0
- package/dist/kdu-router.cjs.js +1 -3484
- package/dist/kdu-router.cjs.prod.js +1 -2759
- package/dist/kdu-router.esm-browser.js +367 -219
- package/dist/kdu-router.esm-bundler.js +1 -3478
- package/dist/kdu-router.global.js +398 -223
- package/dist/kdu-router.global.prod.js +3 -3
- package/dist/kdu-router.mjs +3626 -0
- package/dist/kdu-router.node.mjs +2 -0
- package/dist/kdu-router.prod.cjs +2861 -0
- package/index.js +7 -0
- package/package.json +54 -57
- package/LICENSE +0 -21
- package/README.md +0 -17
- package/dist/kdu-router.d.ts +0 -1349
|
@@ -1,70 +1,33 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* kdu-router v4.0
|
|
3
|
-
* (c)
|
|
2
|
+
* kdu-router v4.2.0
|
|
3
|
+
* (c) 2024 NKDuy
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
6
|
import { getCurrentInstance, inject, onUnmounted, onDeactivated, onActivated, computed, unref, watchEffect, defineComponent, reactive, h, provide, ref, watch, shallowRef, nextTick } from 'kdu';
|
|
7
7
|
import { setupDevtoolsPlugin } from '@kdujs/devtools-api';
|
|
8
8
|
|
|
9
|
-
const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol';
|
|
10
|
-
const PolySymbol = (name) =>
|
|
11
|
-
// kr = kdu router
|
|
12
|
-
hasSymbol
|
|
13
|
-
? Symbol('[kdu-router]: ' + name )
|
|
14
|
-
: ('[kdu-router]: ' ) + name;
|
|
15
|
-
// rvlm = Router View Location Matched
|
|
16
|
-
/**
|
|
17
|
-
* RouteRecord being rendered by the closest ancestor Router View. Used for
|
|
18
|
-
* `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View
|
|
19
|
-
* Location Matched
|
|
20
|
-
*
|
|
21
|
-
* @internal
|
|
22
|
-
*/
|
|
23
|
-
const matchedRouteKey = /*#__PURE__*/ PolySymbol('router view location matched' );
|
|
24
|
-
/**
|
|
25
|
-
* Allows overriding the router view depth to control which component in
|
|
26
|
-
* `matched` is rendered. rvd stands for Router View Depth
|
|
27
|
-
*
|
|
28
|
-
* @internal
|
|
29
|
-
*/
|
|
30
|
-
const viewDepthKey = /*#__PURE__*/ PolySymbol('router view depth' );
|
|
31
|
-
/**
|
|
32
|
-
* Allows overriding the router instance returned by `useRouter` in tests. r
|
|
33
|
-
* stands for router
|
|
34
|
-
*
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
37
|
-
const routerKey = /*#__PURE__*/ PolySymbol('router' );
|
|
38
|
-
/**
|
|
39
|
-
* Allows overriding the current route returned by `useRoute` in tests. rl
|
|
40
|
-
* stands for route location
|
|
41
|
-
*
|
|
42
|
-
* @internal
|
|
43
|
-
*/
|
|
44
|
-
const routeLocationKey = /*#__PURE__*/ PolySymbol('route location' );
|
|
45
|
-
/**
|
|
46
|
-
* Allows overriding the current route used by router-view. Internally this is
|
|
47
|
-
* used when the `route` prop is passed.
|
|
48
|
-
*
|
|
49
|
-
* @internal
|
|
50
|
-
*/
|
|
51
|
-
const routerViewLocationKey = /*#__PURE__*/ PolySymbol('router view location' );
|
|
52
|
-
|
|
53
9
|
const isBrowser = typeof window !== 'undefined';
|
|
54
10
|
|
|
55
11
|
function isESModule(obj) {
|
|
56
|
-
return obj.__esModule ||
|
|
12
|
+
return obj.__esModule || obj[Symbol.toStringTag] === 'Module';
|
|
57
13
|
}
|
|
58
14
|
const assign = Object.assign;
|
|
59
15
|
function applyToParams(fn, params) {
|
|
60
16
|
const newParams = {};
|
|
61
17
|
for (const key in params) {
|
|
62
18
|
const value = params[key];
|
|
63
|
-
newParams[key] =
|
|
19
|
+
newParams[key] = isArray(value)
|
|
20
|
+
? value.map(fn)
|
|
21
|
+
: fn(value);
|
|
64
22
|
}
|
|
65
23
|
return newParams;
|
|
66
24
|
}
|
|
67
|
-
const noop = () => { };
|
|
25
|
+
const noop = () => { };
|
|
26
|
+
/**
|
|
27
|
+
* Typesafe alternative to Array.isArray
|
|
28
|
+
* https://github.com/microsoft/TypeScript/pull/48228
|
|
29
|
+
*/
|
|
30
|
+
const isArray = Array.isArray;
|
|
68
31
|
|
|
69
32
|
function warn(msg) {
|
|
70
33
|
// avoid using ...args as it breaks in older Edge builds
|
|
@@ -75,7 +38,7 @@ function warn(msg) {
|
|
|
75
38
|
const TRAILING_SLASH_RE = /\/$/;
|
|
76
39
|
const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, '');
|
|
77
40
|
/**
|
|
78
|
-
* Transforms
|
|
41
|
+
* Transforms a URI into a normalized history location
|
|
79
42
|
*
|
|
80
43
|
* @param parseQuery
|
|
81
44
|
* @param location - URI to normalize
|
|
@@ -86,8 +49,13 @@ const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, '');
|
|
|
86
49
|
function parseURL(parseQuery, location, currentLocation = '/') {
|
|
87
50
|
let path, query = {}, searchString = '', hash = '';
|
|
88
51
|
// Could use URL and URLSearchParams but IE 11 doesn't support it
|
|
89
|
-
|
|
90
|
-
const hashPos = location.indexOf('#'
|
|
52
|
+
// TODO: move to new URL()
|
|
53
|
+
const hashPos = location.indexOf('#');
|
|
54
|
+
let searchPos = location.indexOf('?');
|
|
55
|
+
// the hash appears before the search, so it's not part of the search string
|
|
56
|
+
if (hashPos < searchPos && hashPos >= 0) {
|
|
57
|
+
searchPos = -1;
|
|
58
|
+
}
|
|
91
59
|
if (searchPos > -1) {
|
|
92
60
|
path = location.slice(0, searchPos);
|
|
93
61
|
searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length);
|
|
@@ -119,8 +87,7 @@ function stringifyURL(stringifyQuery, location) {
|
|
|
119
87
|
return location.path + (query && '?') + query + (location.hash || '');
|
|
120
88
|
}
|
|
121
89
|
/**
|
|
122
|
-
* Strips off the base from the beginning of a location.pathname in a non
|
|
123
|
-
* case-sensitive way.
|
|
90
|
+
* Strips off the base from the beginning of a location.pathname in a non-case-sensitive way.
|
|
124
91
|
*
|
|
125
92
|
* @param pathname - location.pathname
|
|
126
93
|
* @param base - base to strip off
|
|
@@ -136,6 +103,7 @@ function stripBase(pathname, base) {
|
|
|
136
103
|
* pointing towards the same {@link RouteRecord} and that all `params`, `query`
|
|
137
104
|
* parameters and `hash` are the same
|
|
138
105
|
*
|
|
106
|
+
* @param stringifyQuery - A function that takes a query object of type LocationQueryRaw and returns a string representation of it.
|
|
139
107
|
* @param a - first {@link RouteLocation}
|
|
140
108
|
* @param b - second {@link RouteLocation}
|
|
141
109
|
*/
|
|
@@ -172,9 +140,9 @@ function isSameRouteLocationParams(a, b) {
|
|
|
172
140
|
return true;
|
|
173
141
|
}
|
|
174
142
|
function isSameRouteLocationParamsValue(a, b) {
|
|
175
|
-
return
|
|
143
|
+
return isArray(a)
|
|
176
144
|
? isEquivalentArray(a, b)
|
|
177
|
-
:
|
|
145
|
+
: isArray(b)
|
|
178
146
|
? isEquivalentArray(b, a)
|
|
179
147
|
: a === b;
|
|
180
148
|
}
|
|
@@ -186,7 +154,7 @@ function isSameRouteLocationParamsValue(a, b) {
|
|
|
186
154
|
* @param b - array of values or a single value
|
|
187
155
|
*/
|
|
188
156
|
function isEquivalentArray(a, b) {
|
|
189
|
-
return
|
|
157
|
+
return isArray(b)
|
|
190
158
|
? a.length === b.length && a.every((value, i) => value === b[i])
|
|
191
159
|
: a.length === 1 && a[0] === b;
|
|
192
160
|
}
|
|
@@ -207,23 +175,35 @@ function resolveRelativePath(to, from) {
|
|
|
207
175
|
return from;
|
|
208
176
|
const fromSegments = from.split('/');
|
|
209
177
|
const toSegments = to.split('/');
|
|
178
|
+
const lastToSegment = toSegments[toSegments.length - 1];
|
|
179
|
+
// make . and ./ the same (../ === .., ../../ === ../..)
|
|
180
|
+
// this is the same behavior as new URL()
|
|
181
|
+
if (lastToSegment === '..' || lastToSegment === '.') {
|
|
182
|
+
toSegments.push('');
|
|
183
|
+
}
|
|
210
184
|
let position = fromSegments.length - 1;
|
|
211
185
|
let toPosition;
|
|
212
186
|
let segment;
|
|
213
187
|
for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
|
|
214
188
|
segment = toSegments[toPosition];
|
|
215
|
-
//
|
|
216
|
-
if (
|
|
189
|
+
// we stay on the same position
|
|
190
|
+
if (segment === '.')
|
|
217
191
|
continue;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
192
|
+
// go up in the from array
|
|
193
|
+
if (segment === '..') {
|
|
194
|
+
// we can't go below zero, but we still need to increment toPosition
|
|
195
|
+
if (position > 1)
|
|
196
|
+
position--;
|
|
197
|
+
// continue
|
|
198
|
+
}
|
|
199
|
+
// we reached a non-relative path, we stop here
|
|
221
200
|
else
|
|
222
201
|
break;
|
|
223
202
|
}
|
|
224
203
|
return (fromSegments.slice(0, position).join('/') +
|
|
225
204
|
'/' +
|
|
226
205
|
toSegments
|
|
206
|
+
// ensure we use at least the last element in the toSegments
|
|
227
207
|
.slice(toPosition - (toPosition === toSegments.length ? 1 : 0))
|
|
228
208
|
.join('/'));
|
|
229
209
|
}
|
|
@@ -387,7 +367,8 @@ function getSavedScrollPosition(key) {
|
|
|
387
367
|
let createBaseLocation = () => location.protocol + '//' + location.host;
|
|
388
368
|
/**
|
|
389
369
|
* Creates a normalized history location from a window.location object
|
|
390
|
-
* @param
|
|
370
|
+
* @param base - The base path
|
|
371
|
+
* @param location - The window.location object
|
|
391
372
|
*/
|
|
392
373
|
function createCurrentLocation(base, location) {
|
|
393
374
|
const { pathname, search, hash } = location;
|
|
@@ -452,7 +433,7 @@ function useHistoryListeners(base, historyState, currentLocation, replace) {
|
|
|
452
433
|
pauseState = currentLocation.value;
|
|
453
434
|
}
|
|
454
435
|
function listen(callback) {
|
|
455
|
-
//
|
|
436
|
+
// set up the listener and prepare teardown callbacks
|
|
456
437
|
listeners.push(callback);
|
|
457
438
|
const teardown = () => {
|
|
458
439
|
const index = listeners.indexOf(callback);
|
|
@@ -475,9 +456,13 @@ function useHistoryListeners(base, historyState, currentLocation, replace) {
|
|
|
475
456
|
window.removeEventListener('popstate', popStateHandler);
|
|
476
457
|
window.removeEventListener('beforeunload', beforeUnloadListener);
|
|
477
458
|
}
|
|
478
|
-
//
|
|
459
|
+
// set up the listeners and prepare teardown callbacks
|
|
479
460
|
window.addEventListener('popstate', popStateHandler);
|
|
480
|
-
|
|
461
|
+
// TODO: could we use 'pagehide' or 'visibilitychange' instead?
|
|
462
|
+
// https://developer.chrome.com/blog/page-lifecycle-api/
|
|
463
|
+
window.addEventListener('beforeunload', beforeUnloadListener, {
|
|
464
|
+
passive: true,
|
|
465
|
+
});
|
|
481
466
|
return {
|
|
482
467
|
pauseListeners,
|
|
483
468
|
listen,
|
|
@@ -513,14 +498,14 @@ function useHistoryStateNavigation(base) {
|
|
|
513
498
|
// the length is off by one, we need to decrease it
|
|
514
499
|
position: history.length - 1,
|
|
515
500
|
replaced: true,
|
|
516
|
-
// don't add a scroll as the user may have an anchor and we want
|
|
501
|
+
// don't add a scroll as the user may have an anchor, and we want
|
|
517
502
|
// scrollBehavior to be triggered without a saved position
|
|
518
503
|
scroll: null,
|
|
519
504
|
}, true);
|
|
520
505
|
}
|
|
521
506
|
function changeLocation(to, state, replace) {
|
|
522
507
|
/**
|
|
523
|
-
* if a base tag is provided and we are on a normal domain, we have to
|
|
508
|
+
* if a base tag is provided, and we are on a normal domain, we have to
|
|
524
509
|
* respect the provided `base` attribute because pushState() will use it and
|
|
525
510
|
* potentially erase anything before the `#` where a base of
|
|
526
511
|
* `/folder/#` but a base of `/` would erase the `/folder/` section. If
|
|
@@ -614,7 +599,7 @@ function createWebHistory(base) {
|
|
|
614
599
|
}
|
|
615
600
|
|
|
616
601
|
/**
|
|
617
|
-
* Creates
|
|
602
|
+
* Creates an in-memory based history. The main purpose of this history is to handle SSR. It starts in a special location that is nowhere.
|
|
618
603
|
* It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`.
|
|
619
604
|
*
|
|
620
605
|
* @param base - Base applied to all urls, defaults to '/'
|
|
@@ -699,15 +684,13 @@ function createMemoryHistory(base = '') {
|
|
|
699
684
|
}
|
|
700
685
|
|
|
701
686
|
/**
|
|
702
|
-
* Creates a hash history. Useful for web applications with no host (e.g.
|
|
703
|
-
*
|
|
687
|
+
* Creates a hash history. Useful for web applications with no host (e.g. `file://`) or when configuring a server to
|
|
688
|
+
* handle any URL is not possible.
|
|
704
689
|
*
|
|
705
|
-
* @param base - optional base to provide. Defaults to `location.pathname +
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
* `href` value **has to match this parameter** (ignoring anything after the
|
|
710
|
-
* `#`).
|
|
690
|
+
* @param base - optional base to provide. Defaults to `location.pathname + location.search` If there is a `<base>` tag
|
|
691
|
+
* in the `head`, its value will be ignored in favor of this parameter **but note it affects all the history.pushState()
|
|
692
|
+
* calls**, meaning that if you use a `<base>` tag, it's `href` value **has to match this parameter** (ignoring anything
|
|
693
|
+
* after the `#`).
|
|
711
694
|
*
|
|
712
695
|
* @example
|
|
713
696
|
* ```js
|
|
@@ -772,7 +755,7 @@ const START_LOCATION_NORMALIZED = {
|
|
|
772
755
|
redirectedFrom: undefined,
|
|
773
756
|
};
|
|
774
757
|
|
|
775
|
-
const NavigationFailureSymbol =
|
|
758
|
+
const NavigationFailureSymbol = Symbol('navigation failure' );
|
|
776
759
|
/**
|
|
777
760
|
* Enumeration with all possible types for navigation failures. Can be passed to
|
|
778
761
|
* {@link isNavigationFailure} to check for specific failures.
|
|
@@ -797,21 +780,21 @@ var NavigationFailureType;
|
|
|
797
780
|
})(NavigationFailureType || (NavigationFailureType = {}));
|
|
798
781
|
// DEV only debug messages
|
|
799
782
|
const ErrorTypeMessages = {
|
|
800
|
-
[1 /* MATCHER_NOT_FOUND */]({ location, currentLocation }) {
|
|
783
|
+
[1 /* ErrorTypes.MATCHER_NOT_FOUND */]({ location, currentLocation }) {
|
|
801
784
|
return `No match for\n ${JSON.stringify(location)}${currentLocation
|
|
802
785
|
? '\nwhile being at\n' + JSON.stringify(currentLocation)
|
|
803
786
|
: ''}`;
|
|
804
787
|
},
|
|
805
|
-
[2 /* NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
|
|
788
|
+
[2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
|
|
806
789
|
return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
|
|
807
790
|
},
|
|
808
|
-
[4 /* NAVIGATION_ABORTED */]({ from, to }) {
|
|
791
|
+
[4 /* ErrorTypes.NAVIGATION_ABORTED */]({ from, to }) {
|
|
809
792
|
return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
|
|
810
793
|
},
|
|
811
|
-
[8 /* NAVIGATION_CANCELLED */]({ from, to }) {
|
|
794
|
+
[8 /* ErrorTypes.NAVIGATION_CANCELLED */]({ from, to }) {
|
|
812
795
|
return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
|
|
813
796
|
},
|
|
814
|
-
[16 /* NAVIGATION_DUPLICATED */]({ from, to }) {
|
|
797
|
+
[16 /* ErrorTypes.NAVIGATION_DUPLICATED */]({ from, to }) {
|
|
815
798
|
return `Avoided redundant navigation to current location: "${from.fullPath}".`;
|
|
816
799
|
},
|
|
817
800
|
};
|
|
@@ -843,7 +826,7 @@ function stringifyRoute(to) {
|
|
|
843
826
|
return JSON.stringify(location, null, 2);
|
|
844
827
|
}
|
|
845
828
|
|
|
846
|
-
// default pattern for a param: non
|
|
829
|
+
// default pattern for a param: non-greedy everything but /
|
|
847
830
|
const BASE_PARAM_PATTERN = '[^/]+?';
|
|
848
831
|
const BASE_PATH_PARSER_OPTIONS = {
|
|
849
832
|
sensitive: false,
|
|
@@ -870,23 +853,23 @@ function tokensToParser(segments, extraOptions) {
|
|
|
870
853
|
const keys = [];
|
|
871
854
|
for (const segment of segments) {
|
|
872
855
|
// the root segment needs special treatment
|
|
873
|
-
const segmentScores = segment.length ? [] : [90 /* Root */];
|
|
856
|
+
const segmentScores = segment.length ? [] : [90 /* PathScore.Root */];
|
|
874
857
|
// allow trailing slash
|
|
875
858
|
if (options.strict && !segment.length)
|
|
876
859
|
pattern += '/';
|
|
877
860
|
for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
|
|
878
861
|
const token = segment[tokenIndex];
|
|
879
|
-
// resets the score if we are inside a sub
|
|
880
|
-
let subSegmentScore = 40 /* Segment */ +
|
|
881
|
-
(options.sensitive ? 0.25 /* BonusCaseSensitive */ : 0);
|
|
882
|
-
if (token.type === 0 /* Static */) {
|
|
862
|
+
// resets the score if we are inside a sub-segment /:a-other-:b
|
|
863
|
+
let subSegmentScore = 40 /* PathScore.Segment */ +
|
|
864
|
+
(options.sensitive ? 0.25 /* PathScore.BonusCaseSensitive */ : 0);
|
|
865
|
+
if (token.type === 0 /* TokenType.Static */) {
|
|
883
866
|
// prepend the slash if we are starting a new segment
|
|
884
867
|
if (!tokenIndex)
|
|
885
868
|
pattern += '/';
|
|
886
869
|
pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
|
|
887
|
-
subSegmentScore += 40 /* Static */;
|
|
870
|
+
subSegmentScore += 40 /* PathScore.Static */;
|
|
888
871
|
}
|
|
889
|
-
else if (token.type === 1 /* Param */) {
|
|
872
|
+
else if (token.type === 1 /* TokenType.Param */) {
|
|
890
873
|
const { value, repeatable, optional, regexp } = token;
|
|
891
874
|
keys.push({
|
|
892
875
|
name: value,
|
|
@@ -896,7 +879,7 @@ function tokensToParser(segments, extraOptions) {
|
|
|
896
879
|
const re = regexp ? regexp : BASE_PARAM_PATTERN;
|
|
897
880
|
// the user provided a custom regexp /:id(\\d+)
|
|
898
881
|
if (re !== BASE_PARAM_PATTERN) {
|
|
899
|
-
subSegmentScore += 10 /* BonusCustomRegExp */;
|
|
882
|
+
subSegmentScore += 10 /* PathScore.BonusCustomRegExp */;
|
|
900
883
|
// make sure the regexp is valid before using it
|
|
901
884
|
try {
|
|
902
885
|
new RegExp(`(${re})`);
|
|
@@ -919,13 +902,13 @@ function tokensToParser(segments, extraOptions) {
|
|
|
919
902
|
if (optional)
|
|
920
903
|
subPattern += '?';
|
|
921
904
|
pattern += subPattern;
|
|
922
|
-
subSegmentScore += 20 /* Dynamic */;
|
|
905
|
+
subSegmentScore += 20 /* PathScore.Dynamic */;
|
|
923
906
|
if (optional)
|
|
924
|
-
subSegmentScore += -8 /* BonusOptional */;
|
|
907
|
+
subSegmentScore += -8 /* PathScore.BonusOptional */;
|
|
925
908
|
if (repeatable)
|
|
926
|
-
subSegmentScore += -20 /* BonusRepeatable */;
|
|
909
|
+
subSegmentScore += -20 /* PathScore.BonusRepeatable */;
|
|
927
910
|
if (re === '.*')
|
|
928
|
-
subSegmentScore += -50 /* BonusWildcard */;
|
|
911
|
+
subSegmentScore += -50 /* PathScore.BonusWildcard */;
|
|
929
912
|
}
|
|
930
913
|
segmentScores.push(subSegmentScore);
|
|
931
914
|
}
|
|
@@ -936,7 +919,7 @@ function tokensToParser(segments, extraOptions) {
|
|
|
936
919
|
// only apply the strict bonus to the last score
|
|
937
920
|
if (options.strict && options.end) {
|
|
938
921
|
const i = score.length - 1;
|
|
939
|
-
score[i][score[i].length - 1] += 0.7000000000000001 /* BonusStrict */;
|
|
922
|
+
score[i][score[i].length - 1] += 0.7000000000000001 /* PathScore.BonusStrict */;
|
|
940
923
|
}
|
|
941
924
|
// TODO: dev only warn double trailing slash
|
|
942
925
|
if (!options.strict)
|
|
@@ -968,20 +951,22 @@ function tokensToParser(segments, extraOptions) {
|
|
|
968
951
|
path += '/';
|
|
969
952
|
avoidDuplicatedSlash = false;
|
|
970
953
|
for (const token of segment) {
|
|
971
|
-
if (token.type === 0 /* Static */) {
|
|
954
|
+
if (token.type === 0 /* TokenType.Static */) {
|
|
972
955
|
path += token.value;
|
|
973
956
|
}
|
|
974
|
-
else if (token.type === 1 /* Param */) {
|
|
957
|
+
else if (token.type === 1 /* TokenType.Param */) {
|
|
975
958
|
const { value, repeatable, optional } = token;
|
|
976
959
|
const param = value in params ? params[value] : '';
|
|
977
|
-
if (
|
|
960
|
+
if (isArray(param) && !repeatable) {
|
|
978
961
|
throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);
|
|
979
|
-
|
|
962
|
+
}
|
|
963
|
+
const text = isArray(param)
|
|
964
|
+
? param.join('/')
|
|
965
|
+
: param;
|
|
980
966
|
if (!text) {
|
|
981
967
|
if (optional) {
|
|
982
|
-
// if we have more than one optional param like /:a?-static
|
|
983
|
-
|
|
984
|
-
if (segment.length < 2 && segments.length > 1) {
|
|
968
|
+
// if we have more than one optional param like /:a?-static we don't need to care about the optional param
|
|
969
|
+
if (segment.length < 2) {
|
|
985
970
|
// remove the last slash as we could be at the end
|
|
986
971
|
if (path.endsWith('/'))
|
|
987
972
|
path = path.slice(0, -1);
|
|
@@ -997,7 +982,8 @@ function tokensToParser(segments, extraOptions) {
|
|
|
997
982
|
}
|
|
998
983
|
}
|
|
999
984
|
}
|
|
1000
|
-
|
|
985
|
+
// avoid empty path when we have multiple optional params
|
|
986
|
+
return path || '/';
|
|
1001
987
|
}
|
|
1002
988
|
return {
|
|
1003
989
|
re,
|
|
@@ -1028,12 +1014,12 @@ function compareScoreArray(a, b) {
|
|
|
1028
1014
|
// if the last subsegment was Static, the shorter segments should be sorted first
|
|
1029
1015
|
// otherwise sort the longest segment first
|
|
1030
1016
|
if (a.length < b.length) {
|
|
1031
|
-
return a.length === 1 && a[0] === 40 /* Static */ + 40 /* Segment */
|
|
1017
|
+
return a.length === 1 && a[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
|
|
1032
1018
|
? -1
|
|
1033
1019
|
: 1;
|
|
1034
1020
|
}
|
|
1035
1021
|
else if (a.length > b.length) {
|
|
1036
|
-
return b.length === 1 && b[0] === 40 /* Static */ + 40 /* Segment */
|
|
1022
|
+
return b.length === 1 && b[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
|
|
1037
1023
|
? 1
|
|
1038
1024
|
: -1;
|
|
1039
1025
|
}
|
|
@@ -1084,7 +1070,7 @@ function isLastScoreNegative(score) {
|
|
|
1084
1070
|
}
|
|
1085
1071
|
|
|
1086
1072
|
const ROOT_TOKEN = {
|
|
1087
|
-
type: 0 /* Static */,
|
|
1073
|
+
type: 0 /* TokenType.Static */,
|
|
1088
1074
|
value: '',
|
|
1089
1075
|
};
|
|
1090
1076
|
const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
|
|
@@ -1104,7 +1090,7 @@ function tokenizePath(path) {
|
|
|
1104
1090
|
function crash(message) {
|
|
1105
1091
|
throw new Error(`ERR (${state})/"${buffer}": ${message}`);
|
|
1106
1092
|
}
|
|
1107
|
-
let state = 0 /* Static */;
|
|
1093
|
+
let state = 0 /* TokenizerState.Static */;
|
|
1108
1094
|
let previousState = state;
|
|
1109
1095
|
const tokens = [];
|
|
1110
1096
|
// the segment will always be valid because we get into the initial state
|
|
@@ -1126,19 +1112,19 @@ function tokenizePath(path) {
|
|
|
1126
1112
|
function consumeBuffer() {
|
|
1127
1113
|
if (!buffer)
|
|
1128
1114
|
return;
|
|
1129
|
-
if (state === 0 /* Static */) {
|
|
1115
|
+
if (state === 0 /* TokenizerState.Static */) {
|
|
1130
1116
|
segment.push({
|
|
1131
|
-
type: 0 /* Static */,
|
|
1117
|
+
type: 0 /* TokenType.Static */,
|
|
1132
1118
|
value: buffer,
|
|
1133
1119
|
});
|
|
1134
1120
|
}
|
|
1135
|
-
else if (state === 1 /* Param */ ||
|
|
1136
|
-
state === 2 /* ParamRegExp */ ||
|
|
1137
|
-
state === 3 /* ParamRegExpEnd */) {
|
|
1121
|
+
else if (state === 1 /* TokenizerState.Param */ ||
|
|
1122
|
+
state === 2 /* TokenizerState.ParamRegExp */ ||
|
|
1123
|
+
state === 3 /* TokenizerState.ParamRegExpEnd */) {
|
|
1138
1124
|
if (segment.length > 1 && (char === '*' || char === '+'))
|
|
1139
1125
|
crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
|
|
1140
1126
|
segment.push({
|
|
1141
|
-
type: 1 /* Param */,
|
|
1127
|
+
type: 1 /* TokenType.Param */,
|
|
1142
1128
|
value: buffer,
|
|
1143
1129
|
regexp: customRe,
|
|
1144
1130
|
repeatable: char === '*' || char === '+',
|
|
@@ -1155,13 +1141,13 @@ function tokenizePath(path) {
|
|
|
1155
1141
|
}
|
|
1156
1142
|
while (i < path.length) {
|
|
1157
1143
|
char = path[i++];
|
|
1158
|
-
if (char === '\\' && state !== 2 /* ParamRegExp */) {
|
|
1144
|
+
if (char === '\\' && state !== 2 /* TokenizerState.ParamRegExp */) {
|
|
1159
1145
|
previousState = state;
|
|
1160
|
-
state = 4 /* EscapeNext */;
|
|
1146
|
+
state = 4 /* TokenizerState.EscapeNext */;
|
|
1161
1147
|
continue;
|
|
1162
1148
|
}
|
|
1163
1149
|
switch (state) {
|
|
1164
|
-
case 0 /* Static */:
|
|
1150
|
+
case 0 /* TokenizerState.Static */:
|
|
1165
1151
|
if (char === '/') {
|
|
1166
1152
|
if (buffer) {
|
|
1167
1153
|
consumeBuffer();
|
|
@@ -1170,34 +1156,35 @@ function tokenizePath(path) {
|
|
|
1170
1156
|
}
|
|
1171
1157
|
else if (char === ':') {
|
|
1172
1158
|
consumeBuffer();
|
|
1173
|
-
state = 1 /* Param */;
|
|
1159
|
+
state = 1 /* TokenizerState.Param */;
|
|
1174
1160
|
}
|
|
1175
1161
|
else {
|
|
1176
1162
|
addCharToBuffer();
|
|
1177
1163
|
}
|
|
1178
1164
|
break;
|
|
1179
|
-
case 4 /* EscapeNext */:
|
|
1165
|
+
case 4 /* TokenizerState.EscapeNext */:
|
|
1180
1166
|
addCharToBuffer();
|
|
1181
1167
|
state = previousState;
|
|
1182
1168
|
break;
|
|
1183
|
-
case 1 /* Param */:
|
|
1169
|
+
case 1 /* TokenizerState.Param */:
|
|
1184
1170
|
if (char === '(') {
|
|
1185
|
-
state = 2 /* ParamRegExp */;
|
|
1171
|
+
state = 2 /* TokenizerState.ParamRegExp */;
|
|
1186
1172
|
}
|
|
1187
1173
|
else if (VALID_PARAM_RE.test(char)) {
|
|
1188
1174
|
addCharToBuffer();
|
|
1189
1175
|
}
|
|
1190
1176
|
else {
|
|
1191
1177
|
consumeBuffer();
|
|
1192
|
-
state = 0 /* Static */;
|
|
1178
|
+
state = 0 /* TokenizerState.Static */;
|
|
1193
1179
|
// go back one character if we were not modifying
|
|
1194
1180
|
if (char !== '*' && char !== '?' && char !== '+')
|
|
1195
1181
|
i--;
|
|
1196
1182
|
}
|
|
1197
1183
|
break;
|
|
1198
|
-
case 2 /* ParamRegExp */:
|
|
1184
|
+
case 2 /* TokenizerState.ParamRegExp */:
|
|
1199
1185
|
// TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
|
|
1200
1186
|
// it already works by escaping the closing )
|
|
1187
|
+
// https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
|
|
1201
1188
|
// is this really something people need since you can also write
|
|
1202
1189
|
// /prefix_:p()_suffix
|
|
1203
1190
|
if (char === ')') {
|
|
@@ -1205,16 +1192,16 @@ function tokenizePath(path) {
|
|
|
1205
1192
|
if (customRe[customRe.length - 1] == '\\')
|
|
1206
1193
|
customRe = customRe.slice(0, -1) + char;
|
|
1207
1194
|
else
|
|
1208
|
-
state = 3 /* ParamRegExpEnd */;
|
|
1195
|
+
state = 3 /* TokenizerState.ParamRegExpEnd */;
|
|
1209
1196
|
}
|
|
1210
1197
|
else {
|
|
1211
1198
|
customRe += char;
|
|
1212
1199
|
}
|
|
1213
1200
|
break;
|
|
1214
|
-
case 3 /* ParamRegExpEnd */:
|
|
1201
|
+
case 3 /* TokenizerState.ParamRegExpEnd */:
|
|
1215
1202
|
// same as finalizing a param
|
|
1216
1203
|
consumeBuffer();
|
|
1217
|
-
state = 0 /* Static */;
|
|
1204
|
+
state = 0 /* TokenizerState.Static */;
|
|
1218
1205
|
// go back one character if we were not modifying
|
|
1219
1206
|
if (char !== '*' && char !== '?' && char !== '+')
|
|
1220
1207
|
i--;
|
|
@@ -1225,7 +1212,7 @@ function tokenizePath(path) {
|
|
|
1225
1212
|
break;
|
|
1226
1213
|
}
|
|
1227
1214
|
}
|
|
1228
|
-
if (state === 2 /* ParamRegExp */)
|
|
1215
|
+
if (state === 2 /* TokenizerState.ParamRegExp */)
|
|
1229
1216
|
crash(`Unfinished custom RegExp for param "${buffer}"`);
|
|
1230
1217
|
consumeBuffer();
|
|
1231
1218
|
finalizeSegment();
|
|
@@ -1280,6 +1267,9 @@ function createRouterMatcher(routes, globalOptions) {
|
|
|
1280
1267
|
// used later on to remove by name
|
|
1281
1268
|
const isRootAdd = !originalRecord;
|
|
1282
1269
|
const mainNormalizedRecord = normalizeRouteRecord(record);
|
|
1270
|
+
{
|
|
1271
|
+
checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent);
|
|
1272
|
+
}
|
|
1283
1273
|
// we might be the child of an alias
|
|
1284
1274
|
mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;
|
|
1285
1275
|
const options = mergeOptions(globalOptions, record);
|
|
@@ -1323,11 +1313,11 @@ function createRouterMatcher(routes, globalOptions) {
|
|
|
1323
1313
|
throw new Error('Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +
|
|
1324
1314
|
'See more at https://kdujs-router.web.app/guide/migration/#removed-star-or-catch-all-routes.');
|
|
1325
1315
|
}
|
|
1326
|
-
// create the object
|
|
1316
|
+
// create the object beforehand, so it can be passed to children
|
|
1327
1317
|
matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
|
|
1328
1318
|
if (parent && path[0] === '/')
|
|
1329
1319
|
checkMissingParamsInAbsolutePath(matcher, parent);
|
|
1330
|
-
// if we are an alias we must tell the original record that we exist
|
|
1320
|
+
// if we are an alias we must tell the original record that we exist,
|
|
1331
1321
|
// so we can be removed
|
|
1332
1322
|
if (originalRecord) {
|
|
1333
1323
|
originalRecord.alias.push(matcher);
|
|
@@ -1345,20 +1335,27 @@ function createRouterMatcher(routes, globalOptions) {
|
|
|
1345
1335
|
if (isRootAdd && record.name && !isAliasRecord(matcher))
|
|
1346
1336
|
removeRoute(record.name);
|
|
1347
1337
|
}
|
|
1348
|
-
if (
|
|
1338
|
+
if (mainNormalizedRecord.children) {
|
|
1349
1339
|
const children = mainNormalizedRecord.children;
|
|
1350
1340
|
for (let i = 0; i < children.length; i++) {
|
|
1351
1341
|
addRoute(children[i], matcher, originalRecord && originalRecord.children[i]);
|
|
1352
1342
|
}
|
|
1353
1343
|
}
|
|
1354
1344
|
// if there was no original record, then the first one was not an alias and all
|
|
1355
|
-
// other
|
|
1345
|
+
// other aliases (if any) need to reference this record when adding children
|
|
1356
1346
|
originalRecord = originalRecord || matcher;
|
|
1357
1347
|
// TODO: add normalized records for more flexibility
|
|
1358
1348
|
// if (parent && isAliasRecord(originalRecord)) {
|
|
1359
1349
|
// parent.children.push(originalRecord)
|
|
1360
1350
|
// }
|
|
1361
|
-
|
|
1351
|
+
// Avoid adding a record that doesn't display anything. This allows passing through records without a component to
|
|
1352
|
+
// not be reached and pass through the catch all route
|
|
1353
|
+
if ((matcher.record.components &&
|
|
1354
|
+
Object.keys(matcher.record.components).length) ||
|
|
1355
|
+
matcher.record.name ||
|
|
1356
|
+
matcher.record.redirect) {
|
|
1357
|
+
insertMatcher(matcher);
|
|
1358
|
+
}
|
|
1362
1359
|
}
|
|
1363
1360
|
return originalMatcher
|
|
1364
1361
|
? () => {
|
|
@@ -1412,16 +1409,27 @@ function createRouterMatcher(routes, globalOptions) {
|
|
|
1412
1409
|
if ('name' in location && location.name) {
|
|
1413
1410
|
matcher = matcherMap.get(location.name);
|
|
1414
1411
|
if (!matcher)
|
|
1415
|
-
throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
|
|
1412
|
+
throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
|
|
1416
1413
|
location,
|
|
1417
1414
|
});
|
|
1415
|
+
// warn if the user is passing invalid params so they can debug it better when they get removed
|
|
1416
|
+
{
|
|
1417
|
+
const invalidParams = Object.keys(location.params || {}).filter(paramName => !matcher.keys.find(k => k.name === paramName));
|
|
1418
|
+
if (invalidParams.length) {
|
|
1419
|
+
warn(`Discarded invalid param(s) "${invalidParams.join('", "')}" when navigating.`);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1418
1422
|
name = matcher.record.name;
|
|
1419
1423
|
params = assign(
|
|
1420
1424
|
// paramsFromLocation is a new object
|
|
1421
1425
|
paramsFromLocation(currentLocation.params,
|
|
1422
1426
|
// only keep params that exist in the resolved location
|
|
1423
1427
|
// TODO: only keep optional params coming from a parent record
|
|
1424
|
-
matcher.keys.filter(k => !k.optional).map(k => k.name)),
|
|
1428
|
+
matcher.keys.filter(k => !k.optional).map(k => k.name)),
|
|
1429
|
+
// discard any existing params in the current location that do not exist here
|
|
1430
|
+
// #1497 this ensures better active/exact matching
|
|
1431
|
+
location.params &&
|
|
1432
|
+
paramsFromLocation(location.params, matcher.keys.map(k => k.name)));
|
|
1425
1433
|
// throws if cannot be stringified
|
|
1426
1434
|
path = matcher.stringify(params);
|
|
1427
1435
|
}
|
|
@@ -1435,7 +1443,6 @@ function createRouterMatcher(routes, globalOptions) {
|
|
|
1435
1443
|
matcher = matchers.find(m => m.re.test(path));
|
|
1436
1444
|
// matcher should have a value after the loop
|
|
1437
1445
|
if (matcher) {
|
|
1438
|
-
// TODO: dev warning of unused params if provided
|
|
1439
1446
|
// we know the matcher works because we tested the regexp
|
|
1440
1447
|
params = matcher.parse(path);
|
|
1441
1448
|
name = matcher.record.name;
|
|
@@ -1448,7 +1455,7 @@ function createRouterMatcher(routes, globalOptions) {
|
|
|
1448
1455
|
? matcherMap.get(currentLocation.name)
|
|
1449
1456
|
: matchers.find(m => m.re.test(currentLocation.path));
|
|
1450
1457
|
if (!matcher)
|
|
1451
|
-
throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
|
|
1458
|
+
throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
|
|
1452
1459
|
location,
|
|
1453
1460
|
currentLocation,
|
|
1454
1461
|
});
|
|
@@ -1506,8 +1513,8 @@ function normalizeRouteRecord(record) {
|
|
|
1506
1513
|
updateGuards: new Set(),
|
|
1507
1514
|
enterCallbacks: {},
|
|
1508
1515
|
components: 'components' in record
|
|
1509
|
-
? record.components ||
|
|
1510
|
-
: { default: record.component },
|
|
1516
|
+
? record.components || null
|
|
1517
|
+
: record.component && { default: record.component },
|
|
1511
1518
|
};
|
|
1512
1519
|
}
|
|
1513
1520
|
/**
|
|
@@ -1517,7 +1524,7 @@ function normalizeRouteRecord(record) {
|
|
|
1517
1524
|
*/
|
|
1518
1525
|
function normalizeRecordProps(record) {
|
|
1519
1526
|
const propsObject = {};
|
|
1520
|
-
// props does not exist on redirect records but we can set false directly
|
|
1527
|
+
// props does not exist on redirect records, but we can set false directly
|
|
1521
1528
|
const props = record.props || false;
|
|
1522
1529
|
if ('component' in record) {
|
|
1523
1530
|
propsObject.default = props;
|
|
@@ -1571,17 +1578,31 @@ function isSameParam(a, b) {
|
|
|
1571
1578
|
function checkSameParams(a, b) {
|
|
1572
1579
|
for (const key of a.keys) {
|
|
1573
1580
|
if (!key.optional && !b.keys.find(isSameParam.bind(null, key)))
|
|
1574
|
-
return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}"
|
|
1581
|
+
return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`);
|
|
1575
1582
|
}
|
|
1576
1583
|
for (const key of b.keys) {
|
|
1577
1584
|
if (!key.optional && !a.keys.find(isSameParam.bind(null, key)))
|
|
1578
|
-
return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}"
|
|
1585
|
+
return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* A route with a name and a child with an empty path without a name should warn when adding the route
|
|
1590
|
+
*
|
|
1591
|
+
* @param mainNormalizedRecord - RouteRecordNormalized
|
|
1592
|
+
* @param parent - RouteRecordMatcher
|
|
1593
|
+
*/
|
|
1594
|
+
function checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent) {
|
|
1595
|
+
if (parent &&
|
|
1596
|
+
parent.record.name &&
|
|
1597
|
+
!mainNormalizedRecord.name &&
|
|
1598
|
+
!mainNormalizedRecord.path) {
|
|
1599
|
+
warn(`The route named "${String(parent.record.name)}" has a child without a name and an empty path. Using that name won't render the empty path child so you probably want to move the name to the child instead. If this is intentional, add a name to the child route to remove the warning.`);
|
|
1579
1600
|
}
|
|
1580
1601
|
}
|
|
1581
1602
|
function checkMissingParamsInAbsolutePath(record, parent) {
|
|
1582
1603
|
for (const key of parent.keys) {
|
|
1583
1604
|
if (!record.keys.find(isSameParam.bind(null, key)))
|
|
1584
|
-
return warn(`Absolute path "${record.record.path}"
|
|
1605
|
+
return warn(`Absolute path "${record.record.path}" must have the exact same param named "${key.name}" as its parent "${parent.record.path}".`);
|
|
1585
1606
|
}
|
|
1586
1607
|
}
|
|
1587
1608
|
function isRecordChildOf(record, parent) {
|
|
@@ -1595,7 +1616,7 @@ function isRecordChildOf(record, parent) {
|
|
|
1595
1616
|
* On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2)
|
|
1596
1617
|
* defines some extra characters to be encoded. Most browsers do not encode them
|
|
1597
1618
|
* in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to
|
|
1598
|
-
* also encode `!'()*`. Leaving
|
|
1619
|
+
* also encode `!'()*`. Leaving un-encoded only ASCII alphanumeric(`a-zA-Z0-9`)
|
|
1599
1620
|
* plus `-._~`. This extra safety should be applied to query by patching the
|
|
1600
1621
|
* string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\`
|
|
1601
1622
|
* should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\`
|
|
@@ -1619,7 +1640,7 @@ const PLUS_RE = /\+/g; // %2B
|
|
|
1619
1640
|
* application/x-www-form-urlencoded
|
|
1620
1641
|
* (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo
|
|
1621
1642
|
* leave the plus character as is in queries. To be more flexible, we allow the
|
|
1622
|
-
* plus character on the query but it can also be manually encoded by the user.
|
|
1643
|
+
* plus character on the query, but it can also be manually encoded by the user.
|
|
1623
1644
|
*
|
|
1624
1645
|
* Resources:
|
|
1625
1646
|
* - https://url.spec.whatwg.org/#urlencoded-parsing
|
|
@@ -1751,7 +1772,7 @@ function parseQuery(search) {
|
|
|
1751
1772
|
if (key in query) {
|
|
1752
1773
|
// an extra variable for ts types
|
|
1753
1774
|
let currentValue = query[key];
|
|
1754
|
-
if (!
|
|
1775
|
+
if (!isArray(currentValue)) {
|
|
1755
1776
|
currentValue = query[key] = [currentValue];
|
|
1756
1777
|
}
|
|
1757
1778
|
currentValue.push(value);
|
|
@@ -1784,7 +1805,7 @@ function stringifyQuery(query) {
|
|
|
1784
1805
|
continue;
|
|
1785
1806
|
}
|
|
1786
1807
|
// keep null values
|
|
1787
|
-
const values =
|
|
1808
|
+
const values = isArray(value)
|
|
1788
1809
|
? value.map(v => v && encodeQueryValue(v))
|
|
1789
1810
|
: [value && encodeQueryValue(value)];
|
|
1790
1811
|
values.forEach(value => {
|
|
@@ -1813,7 +1834,7 @@ function normalizeQuery(query) {
|
|
|
1813
1834
|
for (const key in query) {
|
|
1814
1835
|
const value = query[key];
|
|
1815
1836
|
if (value !== undefined) {
|
|
1816
|
-
normalizedQuery[key] =
|
|
1837
|
+
normalizedQuery[key] = isArray(value)
|
|
1817
1838
|
? value.map(v => (v == null ? null : '' + v))
|
|
1818
1839
|
: value == null
|
|
1819
1840
|
? value
|
|
@@ -1823,6 +1844,43 @@ function normalizeQuery(query) {
|
|
|
1823
1844
|
return normalizedQuery;
|
|
1824
1845
|
}
|
|
1825
1846
|
|
|
1847
|
+
/**
|
|
1848
|
+
* RouteRecord being rendered by the closest ancestor Router View. Used for
|
|
1849
|
+
* `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View
|
|
1850
|
+
* Location Matched
|
|
1851
|
+
*
|
|
1852
|
+
* @internal
|
|
1853
|
+
*/
|
|
1854
|
+
const matchedRouteKey = Symbol('router view location matched' );
|
|
1855
|
+
/**
|
|
1856
|
+
* Allows overriding the router view depth to control which component in
|
|
1857
|
+
* `matched` is rendered. rvd stands for Router View Depth
|
|
1858
|
+
*
|
|
1859
|
+
* @internal
|
|
1860
|
+
*/
|
|
1861
|
+
const viewDepthKey = Symbol('router view depth' );
|
|
1862
|
+
/**
|
|
1863
|
+
* Allows overriding the router instance returned by `useRouter` in tests. r
|
|
1864
|
+
* stands for router
|
|
1865
|
+
*
|
|
1866
|
+
* @internal
|
|
1867
|
+
*/
|
|
1868
|
+
const routerKey = Symbol('router' );
|
|
1869
|
+
/**
|
|
1870
|
+
* Allows overriding the current route returned by `useRoute` in tests. rl
|
|
1871
|
+
* stands for route location
|
|
1872
|
+
*
|
|
1873
|
+
* @internal
|
|
1874
|
+
*/
|
|
1875
|
+
const routeLocationKey = Symbol('route location' );
|
|
1876
|
+
/**
|
|
1877
|
+
* Allows overriding the current route used by router-view. Internally this is
|
|
1878
|
+
* used when the `route` prop is passed.
|
|
1879
|
+
*
|
|
1880
|
+
* @internal
|
|
1881
|
+
*/
|
|
1882
|
+
const routerViewLocationKey = Symbol('router view location' );
|
|
1883
|
+
|
|
1826
1884
|
/**
|
|
1827
1885
|
* Create a list of callbacks that can be reset. Used to create before and after navigation guards list
|
|
1828
1886
|
*/
|
|
@@ -1873,7 +1931,7 @@ function onBeforeRouteLeave(leaveGuard) {
|
|
|
1873
1931
|
// to avoid warning
|
|
1874
1932
|
{}).value;
|
|
1875
1933
|
if (!activeRecord) {
|
|
1876
|
-
warn('No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside
|
|
1934
|
+
warn('No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside a component child of <router-view>. Maybe you called it inside of App.kdu?');
|
|
1877
1935
|
return;
|
|
1878
1936
|
}
|
|
1879
1937
|
registerGuard(activeRecord, 'leaveGuards', leaveGuard);
|
|
@@ -1894,7 +1952,7 @@ function onBeforeRouteUpdate(updateGuard) {
|
|
|
1894
1952
|
// to avoid warning
|
|
1895
1953
|
{}).value;
|
|
1896
1954
|
if (!activeRecord) {
|
|
1897
|
-
warn('No active route record was found when calling `onBeforeRouteUpdate()`. Make sure you call this function inside
|
|
1955
|
+
warn('No active route record was found when calling `onBeforeRouteUpdate()`. Make sure you call this function inside a component child of <router-view>. Maybe you called it inside of App.kdu?');
|
|
1898
1956
|
return;
|
|
1899
1957
|
}
|
|
1900
1958
|
registerGuard(activeRecord, 'updateGuards', updateGuard);
|
|
@@ -1906,16 +1964,17 @@ function guardToPromiseFn(guard, to, from, record, name) {
|
|
|
1906
1964
|
(record.enterCallbacks[name] = record.enterCallbacks[name] || []);
|
|
1907
1965
|
return () => new Promise((resolve, reject) => {
|
|
1908
1966
|
const next = (valid) => {
|
|
1909
|
-
if (valid === false)
|
|
1910
|
-
reject(createRouterError(4 /* NAVIGATION_ABORTED */, {
|
|
1967
|
+
if (valid === false) {
|
|
1968
|
+
reject(createRouterError(4 /* ErrorTypes.NAVIGATION_ABORTED */, {
|
|
1911
1969
|
from,
|
|
1912
1970
|
to,
|
|
1913
1971
|
}));
|
|
1972
|
+
}
|
|
1914
1973
|
else if (valid instanceof Error) {
|
|
1915
1974
|
reject(valid);
|
|
1916
1975
|
}
|
|
1917
1976
|
else if (isRouteLocation(valid)) {
|
|
1918
|
-
reject(createRouterError(2 /* NAVIGATION_GUARD_REDIRECT */, {
|
|
1977
|
+
reject(createRouterError(2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */, {
|
|
1919
1978
|
from: to,
|
|
1920
1979
|
to: valid,
|
|
1921
1980
|
}));
|
|
@@ -1924,8 +1983,9 @@ function guardToPromiseFn(guard, to, from, record, name) {
|
|
|
1924
1983
|
if (enterCallbackArray &&
|
|
1925
1984
|
// since enterCallbackArray is truthy, both record and name also are
|
|
1926
1985
|
record.enterCallbacks[name] === enterCallbackArray &&
|
|
1927
|
-
typeof valid === 'function')
|
|
1986
|
+
typeof valid === 'function') {
|
|
1928
1987
|
enterCallbackArray.push(valid);
|
|
1988
|
+
}
|
|
1929
1989
|
resolve();
|
|
1930
1990
|
}
|
|
1931
1991
|
};
|
|
@@ -1945,7 +2005,6 @@ function guardToPromiseFn(guard, to, from, record, name) {
|
|
|
1945
2005
|
}
|
|
1946
2006
|
return resolvedValue;
|
|
1947
2007
|
});
|
|
1948
|
-
// TODO: test me!
|
|
1949
2008
|
}
|
|
1950
2009
|
else if (guardReturn !== undefined) {
|
|
1951
2010
|
// @ts-expect-error: _called is added at canOnlyBeCalledOnce
|
|
@@ -1973,6 +2032,10 @@ function canOnlyBeCalledOnce(next, to, from) {
|
|
|
1973
2032
|
function extractComponentsGuards(matched, guardType, to, from) {
|
|
1974
2033
|
const guards = [];
|
|
1975
2034
|
for (const record of matched) {
|
|
2035
|
+
if (!record.components && !record.children.length) {
|
|
2036
|
+
warn(`Record with path "${record.path}" is either missing a "component(s)"` +
|
|
2037
|
+
` or "children" property.`);
|
|
2038
|
+
}
|
|
1976
2039
|
for (const name in record.components) {
|
|
1977
2040
|
let rawComponent = record.components[name];
|
|
1978
2041
|
{
|
|
@@ -2029,6 +2092,7 @@ function extractComponentsGuards(matched, guardType, to, from) {
|
|
|
2029
2092
|
? resolved.default
|
|
2030
2093
|
: resolved;
|
|
2031
2094
|
// replace the function with the resolved component
|
|
2095
|
+
// cannot be null or undefined because we went into the for loop
|
|
2032
2096
|
record.components[name] = resolvedComponent;
|
|
2033
2097
|
// __kccOpts is added by kdu-class-component and contain the regular options
|
|
2034
2098
|
const options = resolvedComponent.__kccOpts || resolvedComponent;
|
|
@@ -2042,6 +2106,7 @@ function extractComponentsGuards(matched, guardType, to, from) {
|
|
|
2042
2106
|
}
|
|
2043
2107
|
/**
|
|
2044
2108
|
* Allows differentiating lazy components from functional components and kdu-class-component
|
|
2109
|
+
* @internal
|
|
2045
2110
|
*
|
|
2046
2111
|
* @param component
|
|
2047
2112
|
*/
|
|
@@ -2050,6 +2115,34 @@ function isRouteComponent(component) {
|
|
|
2050
2115
|
'displayName' in component ||
|
|
2051
2116
|
'props' in component ||
|
|
2052
2117
|
'__kccOpts' in component);
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Ensures a route is loaded, so it can be passed as o prop to `<RouterView>`.
|
|
2121
|
+
*
|
|
2122
|
+
* @param route - resolved route to load
|
|
2123
|
+
*/
|
|
2124
|
+
function loadRouteLocation(route) {
|
|
2125
|
+
return route.matched.every(record => record.redirect)
|
|
2126
|
+
? Promise.reject(new Error('Cannot load a route that redirects.'))
|
|
2127
|
+
: Promise.all(route.matched.map(record => record.components &&
|
|
2128
|
+
Promise.all(Object.keys(record.components).reduce((promises, name) => {
|
|
2129
|
+
const rawComponent = record.components[name];
|
|
2130
|
+
if (typeof rawComponent === 'function' &&
|
|
2131
|
+
!('displayName' in rawComponent)) {
|
|
2132
|
+
promises.push(rawComponent().then(resolved => {
|
|
2133
|
+
if (!resolved)
|
|
2134
|
+
return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}". Ensure you passed a function that returns a promise.`));
|
|
2135
|
+
const resolvedComponent = isESModule(resolved)
|
|
2136
|
+
? resolved.default
|
|
2137
|
+
: resolved;
|
|
2138
|
+
// replace the function with the resolved component
|
|
2139
|
+
// cannot be null or undefined because we went into the for loop
|
|
2140
|
+
record.components[name] = resolvedComponent;
|
|
2141
|
+
return;
|
|
2142
|
+
}));
|
|
2143
|
+
}
|
|
2144
|
+
return promises;
|
|
2145
|
+
}, [])))).then(() => route);
|
|
2053
2146
|
}
|
|
2054
2147
|
|
|
2055
2148
|
// TODO: we could allow currentRoute as a prop to expose `isActive` and
|
|
@@ -2115,6 +2208,9 @@ function useLink(props) {
|
|
|
2115
2208
|
}, { flush: 'post' });
|
|
2116
2209
|
}
|
|
2117
2210
|
}
|
|
2211
|
+
/**
|
|
2212
|
+
* NOTE: update {@link _RouterLinkI}'s `$slots` type when updating this
|
|
2213
|
+
*/
|
|
2118
2214
|
return {
|
|
2119
2215
|
route,
|
|
2120
2216
|
href: computed(() => route.value.href),
|
|
@@ -2164,7 +2260,7 @@ const RouterLinkImpl = /*#__PURE__*/ defineComponent({
|
|
|
2164
2260
|
: null,
|
|
2165
2261
|
href: link.href,
|
|
2166
2262
|
// this would override user added attrs but Kdu will still add
|
|
2167
|
-
// the listener so we end up triggering both
|
|
2263
|
+
// the listener, so we end up triggering both
|
|
2168
2264
|
onClick: link.navigate,
|
|
2169
2265
|
class: elClass.value,
|
|
2170
2266
|
}, children);
|
|
@@ -2209,7 +2305,7 @@ function includesParams(outer, inner) {
|
|
|
2209
2305
|
return false;
|
|
2210
2306
|
}
|
|
2211
2307
|
else {
|
|
2212
|
-
if (!
|
|
2308
|
+
if (!isArray(outerValue) ||
|
|
2213
2309
|
outerValue.length !== innerValue.length ||
|
|
2214
2310
|
innerValue.some((value, i) => value !== outerValue[i]))
|
|
2215
2311
|
return false;
|
|
@@ -2253,9 +2349,21 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
|
|
|
2253
2349
|
warnDeprecatedUsage();
|
|
2254
2350
|
const injectedRoute = inject(routerViewLocationKey);
|
|
2255
2351
|
const routeToDisplay = computed(() => props.route || injectedRoute.value);
|
|
2256
|
-
const
|
|
2257
|
-
|
|
2258
|
-
|
|
2352
|
+
const injectedDepth = inject(viewDepthKey, 0);
|
|
2353
|
+
// The depth changes based on empty components option, which allows passthrough routes e.g. routes with children
|
|
2354
|
+
// that are used to reuse the `path` property
|
|
2355
|
+
const depth = computed(() => {
|
|
2356
|
+
let initialDepth = unref(injectedDepth);
|
|
2357
|
+
const { matched } = routeToDisplay.value;
|
|
2358
|
+
let matchedRoute;
|
|
2359
|
+
while ((matchedRoute = matched[initialDepth]) &&
|
|
2360
|
+
!matchedRoute.components) {
|
|
2361
|
+
initialDepth++;
|
|
2362
|
+
}
|
|
2363
|
+
return initialDepth;
|
|
2364
|
+
});
|
|
2365
|
+
const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth.value]);
|
|
2366
|
+
provide(viewDepthKey, computed(() => depth.value + 1));
|
|
2259
2367
|
provide(matchedRouteKey, matchedRouteRef);
|
|
2260
2368
|
provide(routerViewLocationKey, routeToDisplay);
|
|
2261
2369
|
const viewRef = ref();
|
|
@@ -2267,7 +2375,7 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
|
|
|
2267
2375
|
// this will update the instance for new instances as well as reused
|
|
2268
2376
|
// instances when navigating to a new route
|
|
2269
2377
|
to.instances[name] = instance;
|
|
2270
|
-
// the component instance is reused for a different route or name so
|
|
2378
|
+
// the component instance is reused for a different route or name, so
|
|
2271
2379
|
// we copy any saved update or leave guards. With async setup, the
|
|
2272
2380
|
// mounting component will mount before the matchedRoute changes,
|
|
2273
2381
|
// making instance === oldInstance, so we check if guards have been
|
|
@@ -2293,16 +2401,16 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
|
|
|
2293
2401
|
}, { flush: 'post' });
|
|
2294
2402
|
return () => {
|
|
2295
2403
|
const route = routeToDisplay.value;
|
|
2296
|
-
const matchedRoute = matchedRouteRef.value;
|
|
2297
|
-
const ViewComponent = matchedRoute && matchedRoute.components[props.name];
|
|
2298
2404
|
// we need the value at the time we render because when we unmount, we
|
|
2299
2405
|
// navigated to a different location so the value is different
|
|
2300
2406
|
const currentName = props.name;
|
|
2407
|
+
const matchedRoute = matchedRouteRef.value;
|
|
2408
|
+
const ViewComponent = matchedRoute && matchedRoute.components[currentName];
|
|
2301
2409
|
if (!ViewComponent) {
|
|
2302
2410
|
return normalizeSlot(slots.default, { Component: ViewComponent, route });
|
|
2303
2411
|
}
|
|
2304
2412
|
// props from route configuration
|
|
2305
|
-
const routePropsOption = matchedRoute.props[
|
|
2413
|
+
const routePropsOption = matchedRoute.props[currentName];
|
|
2306
2414
|
const routeProps = routePropsOption
|
|
2307
2415
|
? routePropsOption === true
|
|
2308
2416
|
? route.params
|
|
@@ -2324,12 +2432,12 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
|
|
|
2324
2432
|
component.ref) {
|
|
2325
2433
|
// TODO: can display if it's an alias, its props
|
|
2326
2434
|
const info = {
|
|
2327
|
-
depth,
|
|
2435
|
+
depth: depth.value,
|
|
2328
2436
|
name: matchedRoute.name,
|
|
2329
2437
|
path: matchedRoute.path,
|
|
2330
2438
|
meta: matchedRoute.meta,
|
|
2331
2439
|
};
|
|
2332
|
-
const internalInstances =
|
|
2440
|
+
const internalInstances = isArray(component.ref)
|
|
2333
2441
|
? component.ref.map(r => r.i)
|
|
2334
2442
|
: [component.ref.i];
|
|
2335
2443
|
internalInstances.forEach(instance => {
|
|
@@ -2362,8 +2470,11 @@ const RouterView = RouterViewImpl;
|
|
|
2362
2470
|
function warnDeprecatedUsage() {
|
|
2363
2471
|
const instance = getCurrentInstance();
|
|
2364
2472
|
const parentName = instance.parent && instance.parent.type.name;
|
|
2473
|
+
const parentSubTreeType = instance.parent && instance.parent.subTree && instance.parent.subTree.type;
|
|
2365
2474
|
if (parentName &&
|
|
2366
|
-
(parentName === 'KeepAlive' || parentName.includes('Transition'))
|
|
2475
|
+
(parentName === 'KeepAlive' || parentName.includes('Transition')) &&
|
|
2476
|
+
typeof parentSubTreeType === 'object' &&
|
|
2477
|
+
parentSubTreeType.name === 'RouterView') {
|
|
2367
2478
|
const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition';
|
|
2368
2479
|
warn(`<router-view> can no longer be used directly inside <transition> or <keep-alive>.\n` +
|
|
2369
2480
|
`Use slot props instead:\n\n` +
|
|
@@ -2375,6 +2486,13 @@ function warnDeprecatedUsage() {
|
|
|
2375
2486
|
}
|
|
2376
2487
|
}
|
|
2377
2488
|
|
|
2489
|
+
/**
|
|
2490
|
+
* Copies a route location and removes any problematic properties that cannot be shown in devtools (e.g. Kdu instances).
|
|
2491
|
+
*
|
|
2492
|
+
* @param routeLocation - routeLocation to format
|
|
2493
|
+
* @param tooltip - optional tooltip
|
|
2494
|
+
* @returns a copy of the routeLocation
|
|
2495
|
+
*/
|
|
2378
2496
|
function formatRouteLocation(routeLocation, tooltip) {
|
|
2379
2497
|
const copy = assign({}, routeLocation, {
|
|
2380
2498
|
// remove variables that can contain kdu instances
|
|
@@ -2416,6 +2534,9 @@ function addDevtools(app, router, matcher) {
|
|
|
2416
2534
|
componentStateTypes: ['Routing'],
|
|
2417
2535
|
app,
|
|
2418
2536
|
}, api => {
|
|
2537
|
+
if (typeof api.now !== 'function') {
|
|
2538
|
+
console.warn('[Kdu Router]: You seem to be using an outdated version of Kdu Devtools. Are you still using the Beta release instead of the stable one? You can find the links at https://kdujs-devtools.web.app/guide/installation.html.');
|
|
2539
|
+
}
|
|
2419
2540
|
// display state added by the router
|
|
2420
2541
|
api.on.inspectComponent((payload, ctx) => {
|
|
2421
2542
|
if (payload.instanceData) {
|
|
@@ -2439,7 +2560,7 @@ function addDevtools(app, router, matcher) {
|
|
|
2439
2560
|
});
|
|
2440
2561
|
}
|
|
2441
2562
|
// if multiple useLink are used
|
|
2442
|
-
if (
|
|
2563
|
+
if (isArray(componentInstance.__krl_devtools)) {
|
|
2443
2564
|
componentInstance.__devtoolsApi = api;
|
|
2444
2565
|
componentInstance.__krl_devtools.forEach(devtoolsData => {
|
|
2445
2566
|
let backgroundColor = ORANGE_400;
|
|
@@ -2487,7 +2608,6 @@ function addDevtools(app, router, matcher) {
|
|
|
2487
2608
|
title: 'Error during Navigation',
|
|
2488
2609
|
subtitle: to.fullPath,
|
|
2489
2610
|
logType: 'error',
|
|
2490
|
-
// @ts-ignore
|
|
2491
2611
|
time: api.now(),
|
|
2492
2612
|
data: { error },
|
|
2493
2613
|
groupId: to.meta.__navigationId,
|
|
@@ -2509,7 +2629,6 @@ function addDevtools(app, router, matcher) {
|
|
|
2509
2629
|
api.addTimelineEvent({
|
|
2510
2630
|
layerId: navigationsLayerId,
|
|
2511
2631
|
event: {
|
|
2512
|
-
// @ts-ignore
|
|
2513
2632
|
time: api.now(),
|
|
2514
2633
|
title: 'Start of navigation',
|
|
2515
2634
|
subtitle: to.fullPath,
|
|
@@ -2545,7 +2664,6 @@ function addDevtools(app, router, matcher) {
|
|
|
2545
2664
|
event: {
|
|
2546
2665
|
title: 'End of navigation',
|
|
2547
2666
|
subtitle: to.fullPath,
|
|
2548
|
-
// @ts-ignore
|
|
2549
2667
|
time: api.now(),
|
|
2550
2668
|
data,
|
|
2551
2669
|
logType: failure ? 'warning' : 'default',
|
|
@@ -2595,7 +2713,7 @@ function addDevtools(app, router, matcher) {
|
|
|
2595
2713
|
api.on.getInspectorState(payload => {
|
|
2596
2714
|
if (payload.app === app && payload.inspectorId === routerInspectorId) {
|
|
2597
2715
|
const routes = matcher.getRoutes();
|
|
2598
|
-
const route = routes.find(route => route.record.
|
|
2716
|
+
const route = routes.find(route => route.record.__kd_id === payload.nodeId);
|
|
2599
2717
|
if (route) {
|
|
2600
2718
|
payload.state = {
|
|
2601
2719
|
options: formatRouteRecordMatcherForStateInspector(route),
|
|
@@ -2659,6 +2777,13 @@ function formatRouteRecordMatcherForStateInspector(route) {
|
|
|
2659
2777
|
value: route.alias.map(alias => alias.record.path),
|
|
2660
2778
|
});
|
|
2661
2779
|
}
|
|
2780
|
+
if (Object.keys(route.record.meta).length) {
|
|
2781
|
+
fields.push({
|
|
2782
|
+
editable: false,
|
|
2783
|
+
key: 'meta',
|
|
2784
|
+
value: route.record.meta,
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2662
2787
|
fields.push({
|
|
2663
2788
|
key: 'score',
|
|
2664
2789
|
editable: false,
|
|
@@ -2701,21 +2826,21 @@ function formatRouteRecordForInspector(route) {
|
|
|
2701
2826
|
backgroundColor: ORANGE_400,
|
|
2702
2827
|
});
|
|
2703
2828
|
}
|
|
2704
|
-
if (route.
|
|
2829
|
+
if (route.__kd_match) {
|
|
2705
2830
|
tags.push({
|
|
2706
2831
|
label: 'matches',
|
|
2707
2832
|
textColor: 0,
|
|
2708
2833
|
backgroundColor: PINK_500,
|
|
2709
2834
|
});
|
|
2710
2835
|
}
|
|
2711
|
-
if (route.
|
|
2836
|
+
if (route.__kd_exactActive) {
|
|
2712
2837
|
tags.push({
|
|
2713
2838
|
label: 'exact',
|
|
2714
2839
|
textColor: 0,
|
|
2715
2840
|
backgroundColor: LIME_500,
|
|
2716
2841
|
});
|
|
2717
2842
|
}
|
|
2718
|
-
if (route.
|
|
2843
|
+
if (route.__kd_active) {
|
|
2719
2844
|
tags.push({
|
|
2720
2845
|
label: 'active',
|
|
2721
2846
|
textColor: 0,
|
|
@@ -2724,18 +2849,19 @@ function formatRouteRecordForInspector(route) {
|
|
|
2724
2849
|
}
|
|
2725
2850
|
if (record.redirect) {
|
|
2726
2851
|
tags.push({
|
|
2727
|
-
label:
|
|
2728
|
-
|
|
2852
|
+
label: typeof record.redirect === 'string'
|
|
2853
|
+
? `redirect: ${record.redirect}`
|
|
2854
|
+
: 'redirects',
|
|
2729
2855
|
textColor: 0xffffff,
|
|
2730
2856
|
backgroundColor: DARK,
|
|
2731
2857
|
});
|
|
2732
2858
|
}
|
|
2733
2859
|
// add an id to be able to select it. Using the `path` is not possible because
|
|
2734
2860
|
// empty path children would collide with their parents
|
|
2735
|
-
let id = record.
|
|
2861
|
+
let id = record.__kd_id;
|
|
2736
2862
|
if (id == null) {
|
|
2737
2863
|
id = String(routeRecordId++);
|
|
2738
|
-
record.
|
|
2864
|
+
record.__kd_id = id;
|
|
2739
2865
|
}
|
|
2740
2866
|
return {
|
|
2741
2867
|
id,
|
|
@@ -2752,19 +2878,19 @@ function markRouteRecordActive(route, currentRoute) {
|
|
|
2752
2878
|
// reset the matching state
|
|
2753
2879
|
const isExactActive = currentRoute.matched.length &&
|
|
2754
2880
|
isSameRouteRecord(currentRoute.matched[currentRoute.matched.length - 1], route.record);
|
|
2755
|
-
route.
|
|
2881
|
+
route.__kd_exactActive = route.__kd_active = isExactActive;
|
|
2756
2882
|
if (!isExactActive) {
|
|
2757
|
-
route.
|
|
2883
|
+
route.__kd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record));
|
|
2758
2884
|
}
|
|
2759
2885
|
route.children.forEach(childRoute => markRouteRecordActive(childRoute, currentRoute));
|
|
2760
2886
|
}
|
|
2761
2887
|
function resetMatchStateOnRouteRecord(route) {
|
|
2762
|
-
route.
|
|
2888
|
+
route.__kd_match = false;
|
|
2763
2889
|
route.children.forEach(resetMatchStateOnRouteRecord);
|
|
2764
2890
|
}
|
|
2765
2891
|
function isRouteMatching(route, filter) {
|
|
2766
2892
|
const found = String(route.re).match(EXTRACT_REGEXP_RE);
|
|
2767
|
-
route.
|
|
2893
|
+
route.__kd_match = false;
|
|
2768
2894
|
if (!found || found.length < 3) {
|
|
2769
2895
|
return false;
|
|
2770
2896
|
}
|
|
@@ -2775,7 +2901,7 @@ function isRouteMatching(route, filter) {
|
|
|
2775
2901
|
route.children.forEach(child => isRouteMatching(child, filter));
|
|
2776
2902
|
// exception case: `/`
|
|
2777
2903
|
if (route.record.path !== '/' || filter === '/') {
|
|
2778
|
-
route.
|
|
2904
|
+
route.__kd_match = route.re.test(filter);
|
|
2779
2905
|
return true;
|
|
2780
2906
|
}
|
|
2781
2907
|
// hide the / route
|
|
@@ -2888,9 +3014,7 @@ function createRouter(options) {
|
|
|
2888
3014
|
!('name' in rawLocation) &&
|
|
2889
3015
|
// @ts-expect-error: the type is never
|
|
2890
3016
|
Object.keys(rawLocation.params).length) {
|
|
2891
|
-
warn(`Path "${
|
|
2892
|
-
// @ts-expect-error: the type is never
|
|
2893
|
-
rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`);
|
|
3017
|
+
warn(`Path "${rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`);
|
|
2894
3018
|
}
|
|
2895
3019
|
matcherLocation = assign({}, rawLocation, {
|
|
2896
3020
|
path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path,
|
|
@@ -2904,9 +3028,9 @@ function createRouter(options) {
|
|
|
2904
3028
|
delete targetParams[key];
|
|
2905
3029
|
}
|
|
2906
3030
|
}
|
|
2907
|
-
// pass encoded values to the matcher so it can produce encoded path and fullPath
|
|
3031
|
+
// pass encoded values to the matcher, so it can produce encoded path and fullPath
|
|
2908
3032
|
matcherLocation = assign({}, rawLocation, {
|
|
2909
|
-
params: encodeParams(
|
|
3033
|
+
params: encodeParams(targetParams),
|
|
2910
3034
|
});
|
|
2911
3035
|
// current location params are decoded, we need to encode them in case the
|
|
2912
3036
|
// matcher merges the params
|
|
@@ -2917,7 +3041,7 @@ function createRouter(options) {
|
|
|
2917
3041
|
if (hash && !hash.startsWith('#')) {
|
|
2918
3042
|
warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`);
|
|
2919
3043
|
}
|
|
2920
|
-
//
|
|
3044
|
+
// the matcher might have merged current location params, so
|
|
2921
3045
|
// we need to run the decoding again
|
|
2922
3046
|
matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
|
|
2923
3047
|
const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
|
|
@@ -2958,7 +3082,7 @@ function createRouter(options) {
|
|
|
2958
3082
|
}
|
|
2959
3083
|
function checkCanceledNavigation(to, from) {
|
|
2960
3084
|
if (pendingLocation !== to) {
|
|
2961
|
-
return createRouterError(8 /* NAVIGATION_CANCELLED */, {
|
|
3085
|
+
return createRouterError(8 /* ErrorTypes.NAVIGATION_CANCELLED */, {
|
|
2962
3086
|
from,
|
|
2963
3087
|
to,
|
|
2964
3088
|
});
|
|
@@ -2993,7 +3117,8 @@ function createRouter(options) {
|
|
|
2993
3117
|
return assign({
|
|
2994
3118
|
query: to.query,
|
|
2995
3119
|
hash: to.hash,
|
|
2996
|
-
params
|
|
3120
|
+
// avoid transferring params if the redirect has a path
|
|
3121
|
+
params: 'path' in newTargetLocation ? {} : to.params,
|
|
2997
3122
|
}, newTargetLocation);
|
|
2998
3123
|
}
|
|
2999
3124
|
}
|
|
@@ -3007,7 +3132,9 @@ function createRouter(options) {
|
|
|
3007
3132
|
const shouldRedirect = handleRedirectRecord(targetLocation);
|
|
3008
3133
|
if (shouldRedirect)
|
|
3009
3134
|
return pushWithRedirect(assign(locationAsObject(shouldRedirect), {
|
|
3010
|
-
state:
|
|
3135
|
+
state: typeof shouldRedirect === 'object'
|
|
3136
|
+
? assign({}, data, shouldRedirect.state)
|
|
3137
|
+
: data,
|
|
3011
3138
|
force,
|
|
3012
3139
|
replace,
|
|
3013
3140
|
}),
|
|
@@ -3018,7 +3145,7 @@ function createRouter(options) {
|
|
|
3018
3145
|
toLocation.redirectedFrom = redirectedFrom;
|
|
3019
3146
|
let failure;
|
|
3020
3147
|
if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
|
|
3021
|
-
failure = createRouterError(16 /* NAVIGATION_DUPLICATED */, { to: toLocation, from });
|
|
3148
|
+
failure = createRouterError(16 /* ErrorTypes.NAVIGATION_DUPLICATED */, { to: toLocation, from });
|
|
3022
3149
|
// trigger scroll to allow scrolling to the same anchor
|
|
3023
3150
|
handleScroll(from, from,
|
|
3024
3151
|
// this is a push, the only way for it to be triggered from a
|
|
@@ -3031,14 +3158,14 @@ function createRouter(options) {
|
|
|
3031
3158
|
return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
|
|
3032
3159
|
.catch((error) => isNavigationFailure(error)
|
|
3033
3160
|
? // navigation redirects still mark the router as ready
|
|
3034
|
-
isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)
|
|
3161
|
+
isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)
|
|
3035
3162
|
? error
|
|
3036
3163
|
: markAsReady(error) // also returns the error
|
|
3037
3164
|
: // reject any unknown error
|
|
3038
3165
|
triggerError(error, toLocation, from))
|
|
3039
3166
|
.then((failure) => {
|
|
3040
3167
|
if (failure) {
|
|
3041
|
-
if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
|
|
3168
|
+
if (isNavigationFailure(failure, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
|
|
3042
3169
|
if (// we are redirecting to the same location we were already at
|
|
3043
3170
|
isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) &&
|
|
3044
3171
|
// and we have done it a couple of times
|
|
@@ -3047,16 +3174,20 @@ function createRouter(options) {
|
|
|
3047
3174
|
(redirectedFrom._count = redirectedFrom._count
|
|
3048
3175
|
? // @ts-expect-error
|
|
3049
3176
|
redirectedFrom._count + 1
|
|
3050
|
-
: 1) >
|
|
3051
|
-
warn(`Detected
|
|
3177
|
+
: 1) > 30) {
|
|
3178
|
+
warn(`Detected a possibly infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow.\n Are you always returning a new location within a navigation guard? That would lead to this error. Only return when redirecting or aborting, that should fix this. This might break in production if not fixed.`);
|
|
3052
3179
|
return Promise.reject(new Error('Infinite redirect in navigation guard'));
|
|
3053
3180
|
}
|
|
3054
3181
|
return pushWithRedirect(
|
|
3055
3182
|
// keep options
|
|
3056
|
-
assign(
|
|
3057
|
-
|
|
3058
|
-
force,
|
|
3183
|
+
assign({
|
|
3184
|
+
// preserve an existing replacement but allow the redirect to override it
|
|
3059
3185
|
replace,
|
|
3186
|
+
}, locationAsObject(failure.to), {
|
|
3187
|
+
state: typeof failure.to === 'object'
|
|
3188
|
+
? assign({}, data, failure.to.state)
|
|
3189
|
+
: data,
|
|
3190
|
+
force,
|
|
3060
3191
|
}),
|
|
3061
3192
|
// preserve the original redirectedFrom if any
|
|
3062
3193
|
redirectedFrom || toLocation);
|
|
@@ -3079,6 +3210,13 @@ function createRouter(options) {
|
|
|
3079
3210
|
const error = checkCanceledNavigation(to, from);
|
|
3080
3211
|
return error ? Promise.reject(error) : Promise.resolve();
|
|
3081
3212
|
}
|
|
3213
|
+
function runWithContext(fn) {
|
|
3214
|
+
const app = installedApps.values().next().value;
|
|
3215
|
+
// support Kdu < 3.3
|
|
3216
|
+
return app && typeof app.runWithContext === 'function'
|
|
3217
|
+
? app.runWithContext(fn)
|
|
3218
|
+
: fn();
|
|
3219
|
+
}
|
|
3082
3220
|
// TODO: refactor the whole before guards by internally using router.beforeEach
|
|
3083
3221
|
function navigate(to, from) {
|
|
3084
3222
|
let guards;
|
|
@@ -3122,7 +3260,7 @@ function createRouter(options) {
|
|
|
3122
3260
|
for (const record of to.matched) {
|
|
3123
3261
|
// do not trigger beforeEnter on reused views
|
|
3124
3262
|
if (record.beforeEnter && !from.matched.includes(record)) {
|
|
3125
|
-
if (
|
|
3263
|
+
if (isArray(record.beforeEnter)) {
|
|
3126
3264
|
for (const beforeEnter of record.beforeEnter)
|
|
3127
3265
|
guards.push(guardToPromiseFn(beforeEnter, to, from));
|
|
3128
3266
|
}
|
|
@@ -3155,15 +3293,16 @@ function createRouter(options) {
|
|
|
3155
3293
|
return runGuardQueue(guards);
|
|
3156
3294
|
})
|
|
3157
3295
|
// catch any navigation canceled
|
|
3158
|
-
.catch(err => isNavigationFailure(err, 8 /* NAVIGATION_CANCELLED */)
|
|
3296
|
+
.catch(err => isNavigationFailure(err, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)
|
|
3159
3297
|
? err
|
|
3160
3298
|
: Promise.reject(err)));
|
|
3161
3299
|
}
|
|
3162
3300
|
function triggerAfterEach(to, from, failure) {
|
|
3163
3301
|
// navigation is confirmed, call afterGuards
|
|
3164
3302
|
// TODO: wrap with error handlers
|
|
3165
|
-
for (const guard of afterGuards.list())
|
|
3166
|
-
guard(to, from, failure);
|
|
3303
|
+
for (const guard of afterGuards.list()) {
|
|
3304
|
+
runWithContext(() => guard(to, from, failure));
|
|
3305
|
+
}
|
|
3167
3306
|
}
|
|
3168
3307
|
/**
|
|
3169
3308
|
* - Cleans up any navigation guards
|
|
@@ -3202,6 +3341,8 @@ function createRouter(options) {
|
|
|
3202
3341
|
if (removeHistoryListener)
|
|
3203
3342
|
return;
|
|
3204
3343
|
removeHistoryListener = routerHistory.listen((to, _from, info) => {
|
|
3344
|
+
if (!router.listening)
|
|
3345
|
+
return;
|
|
3205
3346
|
// cannot be a redirect route because it was in history
|
|
3206
3347
|
const toLocation = resolve(to);
|
|
3207
3348
|
// due to dynamic routing, and to hash history with manual navigation
|
|
@@ -3220,15 +3361,15 @@ function createRouter(options) {
|
|
|
3220
3361
|
}
|
|
3221
3362
|
navigate(toLocation, from)
|
|
3222
3363
|
.catch((error) => {
|
|
3223
|
-
if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ | 8 /* NAVIGATION_CANCELLED */)) {
|
|
3364
|
+
if (isNavigationFailure(error, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
|
|
3224
3365
|
return error;
|
|
3225
3366
|
}
|
|
3226
|
-
if (isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
|
|
3367
|
+
if (isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
|
|
3227
3368
|
// Here we could call if (info.delta) routerHistory.go(-info.delta,
|
|
3228
3369
|
// false) but this is bug prone as we have no way to wait the
|
|
3229
3370
|
// navigation to be finished before calling pushWithRedirect. Using
|
|
3230
|
-
// a setTimeout of 16ms seems to work but there is
|
|
3231
|
-
// it to work on every browser. So
|
|
3371
|
+
// a setTimeout of 16ms seems to work but there is no guarantee for
|
|
3372
|
+
// it to work on every browser. So instead we do not restore the
|
|
3232
3373
|
// history entry and trigger a new navigation as requested by the
|
|
3233
3374
|
// navigation guard.
|
|
3234
3375
|
// the error is already handled by router.push we just want to avoid
|
|
@@ -3238,10 +3379,10 @@ function createRouter(options) {
|
|
|
3238
3379
|
)
|
|
3239
3380
|
.then(failure => {
|
|
3240
3381
|
// manual change in hash history #916 ending up in the URL not
|
|
3241
|
-
// changing but it was changed by the manual url change, so we
|
|
3382
|
+
// changing, but it was changed by the manual url change, so we
|
|
3242
3383
|
// need to manually change it ourselves
|
|
3243
|
-
if (isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ |
|
|
3244
|
-
16 /* NAVIGATION_DUPLICATED */) &&
|
|
3384
|
+
if (isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ |
|
|
3385
|
+
16 /* ErrorTypes.NAVIGATION_DUPLICATED */) &&
|
|
3245
3386
|
!info.delta &&
|
|
3246
3387
|
info.type === NavigationType.pop) {
|
|
3247
3388
|
routerHistory.go(-1, false);
|
|
@@ -3252,8 +3393,9 @@ function createRouter(options) {
|
|
|
3252
3393
|
return Promise.reject();
|
|
3253
3394
|
}
|
|
3254
3395
|
// do not restore history on unknown direction
|
|
3255
|
-
if (info.delta)
|
|
3396
|
+
if (info.delta) {
|
|
3256
3397
|
routerHistory.go(-info.delta, false);
|
|
3398
|
+
}
|
|
3257
3399
|
// unrecognized error, transfer to the global handler
|
|
3258
3400
|
return triggerError(error, toLocation, from);
|
|
3259
3401
|
})
|
|
@@ -3265,11 +3407,14 @@ function createRouter(options) {
|
|
|
3265
3407
|
toLocation, from, false);
|
|
3266
3408
|
// revert the navigation
|
|
3267
3409
|
if (failure) {
|
|
3268
|
-
if (info.delta
|
|
3410
|
+
if (info.delta &&
|
|
3411
|
+
// a new navigation has been triggered, so we do not want to revert, that will change the current history
|
|
3412
|
+
// entry while a different route is displayed
|
|
3413
|
+
!isNavigationFailure(failure, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
|
|
3269
3414
|
routerHistory.go(-info.delta, false);
|
|
3270
3415
|
}
|
|
3271
3416
|
else if (info.type === NavigationType.pop &&
|
|
3272
|
-
isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ | 16 /* NAVIGATION_DUPLICATED */)) {
|
|
3417
|
+
isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 16 /* ErrorTypes.NAVIGATION_DUPLICATED */)) {
|
|
3273
3418
|
// manual change in hash history #916
|
|
3274
3419
|
// it's like a push but lacks the information of the direction
|
|
3275
3420
|
routerHistory.go(-1, false);
|
|
@@ -3345,6 +3490,7 @@ function createRouter(options) {
|
|
|
3345
3490
|
const installedApps = new Set();
|
|
3346
3491
|
const router = {
|
|
3347
3492
|
currentRoute,
|
|
3493
|
+
listening: true,
|
|
3348
3494
|
addRoute,
|
|
3349
3495
|
removeRoute,
|
|
3350
3496
|
hasRoute,
|
|
@@ -3408,16 +3554,18 @@ function createRouter(options) {
|
|
|
3408
3554
|
}
|
|
3409
3555
|
unmountApp();
|
|
3410
3556
|
};
|
|
3557
|
+
// TODO: this probably needs to be updated so it can be used by kdu-termui
|
|
3411
3558
|
if (isBrowser) {
|
|
3412
3559
|
addDevtools(app, router, matcher);
|
|
3413
3560
|
}
|
|
3414
3561
|
},
|
|
3415
3562
|
};
|
|
3563
|
+
// TODO: type this as NavigationGuardReturn or similar instead of any
|
|
3564
|
+
function runGuardQueue(guards) {
|
|
3565
|
+
return guards.reduce((promise, guard) => promise.then(() => runWithContext(guard)), Promise.resolve());
|
|
3566
|
+
}
|
|
3416
3567
|
return router;
|
|
3417
3568
|
}
|
|
3418
|
-
function runGuardQueue(guards) {
|
|
3419
|
-
return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
|
|
3420
|
-
}
|
|
3421
3569
|
function extractChangingRecords(to, from) {
|
|
3422
3570
|
const leavingRecords = [];
|
|
3423
3571
|
const updatingRecords = [];
|
|
@@ -3457,4 +3605,4 @@ function useRoute() {
|
|
|
3457
3605
|
return inject(routeLocationKey);
|
|
3458
3606
|
}
|
|
3459
3607
|
|
|
3460
|
-
export { NavigationFailureType, RouterLink, RouterView, START_LOCATION_NORMALIZED as START_LOCATION, createMemoryHistory, createRouter, createRouterMatcher, createWebHashHistory, createWebHistory, isNavigationFailure, matchedRouteKey, onBeforeRouteLeave, onBeforeRouteUpdate, parseQuery, routeLocationKey, routerKey, routerViewLocationKey, stringifyQuery, useLink, useRoute, useRouter, viewDepthKey };
|
|
3608
|
+
export { NavigationFailureType, RouterLink, RouterView, START_LOCATION_NORMALIZED as START_LOCATION, createMemoryHistory, createRouter, createRouterMatcher, createWebHashHistory, createWebHistory, isNavigationFailure, loadRouteLocation, matchedRouteKey, onBeforeRouteLeave, onBeforeRouteUpdate, parseQuery, routeLocationKey, routerKey, routerViewLocationKey, stringifyQuery, useLink, useRoute, useRouter, viewDepthKey };
|