kdu-router 4.0.16-rc.0 → 4.1.6

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.
@@ -1,70 +1,33 @@
1
1
  /*!
2
- * kdu-router v4.0.16-rc.0
3
- * (c) 2021-2022 NKDuy
2
+ * kdu-router v4.1.6
3
+ * (c) 2023 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 || (hasSymbol && obj[Symbol.toStringTag] === 'Module');
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] = Array.isArray(value) ? value.map(fn) : fn(value);
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 an URI into a normalized history location
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
- const searchPos = location.indexOf('?');
90
- const hashPos = location.indexOf('#', searchPos > -1 ? searchPos : 0);
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
@@ -172,9 +139,9 @@ function isSameRouteLocationParams(a, b) {
172
139
  return true;
173
140
  }
174
141
  function isSameRouteLocationParamsValue(a, b) {
175
- return Array.isArray(a)
142
+ return isArray(a)
176
143
  ? isEquivalentArray(a, b)
177
- : Array.isArray(b)
144
+ : isArray(b)
178
145
  ? isEquivalentArray(b, a)
179
146
  : a === b;
180
147
  }
@@ -186,7 +153,7 @@ function isSameRouteLocationParamsValue(a, b) {
186
153
  * @param b - array of values or a single value
187
154
  */
188
155
  function isEquivalentArray(a, b) {
189
- return Array.isArray(b)
156
+ return isArray(b)
190
157
  ? a.length === b.length && a.every((value, i) => value === b[i])
191
158
  : a.length === 1 && a[0] === b;
192
159
  }
@@ -212,18 +179,24 @@ function resolveRelativePath(to, from) {
212
179
  let segment;
213
180
  for (toPosition = 0; toPosition < toSegments.length; toPosition++) {
214
181
  segment = toSegments[toPosition];
215
- // can't go below zero
216
- if (position === 1 || segment === '.')
182
+ // we stay on the same position
183
+ if (segment === '.')
217
184
  continue;
218
- if (segment === '..')
219
- position--;
220
- // found something that is not relative path
185
+ // go up in the from array
186
+ if (segment === '..') {
187
+ // we can't go below zero, but we still need to increment toPosition
188
+ if (position > 1)
189
+ position--;
190
+ // continue
191
+ }
192
+ // we reached a non-relative path, we stop here
221
193
  else
222
194
  break;
223
195
  }
224
196
  return (fromSegments.slice(0, position).join('/') +
225
197
  '/' +
226
198
  toSegments
199
+ // ensure we use at least the last element in the toSegments
227
200
  .slice(toPosition - (toPosition === toSegments.length ? 1 : 0))
228
201
  .join('/'));
229
202
  }
@@ -452,7 +425,7 @@ function useHistoryListeners(base, historyState, currentLocation, replace) {
452
425
  pauseState = currentLocation.value;
453
426
  }
454
427
  function listen(callback) {
455
- // setup the listener and prepare teardown callbacks
428
+ // set up the listener and prepare teardown callbacks
456
429
  listeners.push(callback);
457
430
  const teardown = () => {
458
431
  const index = listeners.indexOf(callback);
@@ -475,7 +448,7 @@ function useHistoryListeners(base, historyState, currentLocation, replace) {
475
448
  window.removeEventListener('popstate', popStateHandler);
476
449
  window.removeEventListener('beforeunload', beforeUnloadListener);
477
450
  }
478
- // setup the listeners and prepare teardown callbacks
451
+ // set up the listeners and prepare teardown callbacks
479
452
  window.addEventListener('popstate', popStateHandler);
480
453
  window.addEventListener('beforeunload', beforeUnloadListener);
481
454
  return {
@@ -513,14 +486,14 @@ function useHistoryStateNavigation(base) {
513
486
  // the length is off by one, we need to decrease it
514
487
  position: history.length - 1,
515
488
  replaced: true,
516
- // don't add a scroll as the user may have an anchor and we want
489
+ // don't add a scroll as the user may have an anchor, and we want
517
490
  // scrollBehavior to be triggered without a saved position
518
491
  scroll: null,
519
492
  }, true);
520
493
  }
521
494
  function changeLocation(to, state, replace) {
522
495
  /**
523
- * if a base tag is provided and we are on a normal domain, we have to
496
+ * if a base tag is provided, and we are on a normal domain, we have to
524
497
  * respect the provided `base` attribute because pushState() will use it and
525
498
  * potentially erase anything before the `#` where a base of
526
499
  * `/folder/#` but a base of `/` would erase the `/folder/` section. If
@@ -614,7 +587,7 @@ function createWebHistory(base) {
614
587
  }
615
588
 
616
589
  /**
617
- * 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.
590
+ * 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
591
  * It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`.
619
592
  *
620
593
  * @param base - Base applied to all urls, defaults to '/'
@@ -699,15 +672,13 @@ function createMemoryHistory(base = '') {
699
672
  }
700
673
 
701
674
  /**
702
- * Creates a hash history. Useful for web applications with no host (e.g.
703
- * `file://`) or when configuring a server to handle any URL is not possible.
675
+ * Creates a hash history. Useful for web applications with no host (e.g. `file://`) or when configuring a server to
676
+ * handle any URL is not possible.
704
677
  *
705
- * @param base - optional base to provide. Defaults to `location.pathname +
706
- * location.search` If there is a `<base>` tag in the `head`, its value will be
707
- * ignored in favor of this parameter **but note it affects all the
708
- * history.pushState() calls**, meaning that if you use a `<base>` tag, it's
709
- * `href` value **has to match this parameter** (ignoring anything after the
710
- * `#`).
678
+ * @param base - optional base to provide. Defaults to `location.pathname + location.search` If there is a `<base>` tag
679
+ * in the `head`, its value will be ignored in favor of this parameter **but note it affects all the history.pushState()
680
+ * calls**, meaning that if you use a `<base>` tag, it's `href` value **has to match this parameter** (ignoring anything
681
+ * after the `#`).
711
682
  *
712
683
  * @example
713
684
  * ```js
@@ -772,7 +743,7 @@ const START_LOCATION_NORMALIZED = {
772
743
  redirectedFrom: undefined,
773
744
  };
774
745
 
775
- const NavigationFailureSymbol = /*#__PURE__*/ PolySymbol('navigation failure' );
746
+ const NavigationFailureSymbol = Symbol('navigation failure' );
776
747
  /**
777
748
  * Enumeration with all possible types for navigation failures. Can be passed to
778
749
  * {@link isNavigationFailure} to check for specific failures.
@@ -797,21 +768,21 @@ var NavigationFailureType;
797
768
  })(NavigationFailureType || (NavigationFailureType = {}));
798
769
  // DEV only debug messages
799
770
  const ErrorTypeMessages = {
800
- [1 /* MATCHER_NOT_FOUND */]({ location, currentLocation }) {
771
+ [1 /* ErrorTypes.MATCHER_NOT_FOUND */]({ location, currentLocation }) {
801
772
  return `No match for\n ${JSON.stringify(location)}${currentLocation
802
773
  ? '\nwhile being at\n' + JSON.stringify(currentLocation)
803
774
  : ''}`;
804
775
  },
805
- [2 /* NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
776
+ [2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
806
777
  return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
807
778
  },
808
- [4 /* NAVIGATION_ABORTED */]({ from, to }) {
779
+ [4 /* ErrorTypes.NAVIGATION_ABORTED */]({ from, to }) {
809
780
  return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
810
781
  },
811
- [8 /* NAVIGATION_CANCELLED */]({ from, to }) {
782
+ [8 /* ErrorTypes.NAVIGATION_CANCELLED */]({ from, to }) {
812
783
  return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
813
784
  },
814
- [16 /* NAVIGATION_DUPLICATED */]({ from, to }) {
785
+ [16 /* ErrorTypes.NAVIGATION_DUPLICATED */]({ from, to }) {
815
786
  return `Avoided redundant navigation to current location: "${from.fullPath}".`;
816
787
  },
817
788
  };
@@ -843,7 +814,7 @@ function stringifyRoute(to) {
843
814
  return JSON.stringify(location, null, 2);
844
815
  }
845
816
 
846
- // default pattern for a param: non greedy everything but /
817
+ // default pattern for a param: non-greedy everything but /
847
818
  const BASE_PARAM_PATTERN = '[^/]+?';
848
819
  const BASE_PATH_PARSER_OPTIONS = {
849
820
  sensitive: false,
@@ -870,23 +841,23 @@ function tokensToParser(segments, extraOptions) {
870
841
  const keys = [];
871
842
  for (const segment of segments) {
872
843
  // the root segment needs special treatment
873
- const segmentScores = segment.length ? [] : [90 /* Root */];
844
+ const segmentScores = segment.length ? [] : [90 /* PathScore.Root */];
874
845
  // allow trailing slash
875
846
  if (options.strict && !segment.length)
876
847
  pattern += '/';
877
848
  for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
878
849
  const token = segment[tokenIndex];
879
- // resets the score if we are inside a sub segment /:a-other-:b
880
- let subSegmentScore = 40 /* Segment */ +
881
- (options.sensitive ? 0.25 /* BonusCaseSensitive */ : 0);
882
- if (token.type === 0 /* Static */) {
850
+ // resets the score if we are inside a sub-segment /:a-other-:b
851
+ let subSegmentScore = 40 /* PathScore.Segment */ +
852
+ (options.sensitive ? 0.25 /* PathScore.BonusCaseSensitive */ : 0);
853
+ if (token.type === 0 /* TokenType.Static */) {
883
854
  // prepend the slash if we are starting a new segment
884
855
  if (!tokenIndex)
885
856
  pattern += '/';
886
857
  pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
887
- subSegmentScore += 40 /* Static */;
858
+ subSegmentScore += 40 /* PathScore.Static */;
888
859
  }
889
- else if (token.type === 1 /* Param */) {
860
+ else if (token.type === 1 /* TokenType.Param */) {
890
861
  const { value, repeatable, optional, regexp } = token;
891
862
  keys.push({
892
863
  name: value,
@@ -896,7 +867,7 @@ function tokensToParser(segments, extraOptions) {
896
867
  const re = regexp ? regexp : BASE_PARAM_PATTERN;
897
868
  // the user provided a custom regexp /:id(\\d+)
898
869
  if (re !== BASE_PARAM_PATTERN) {
899
- subSegmentScore += 10 /* BonusCustomRegExp */;
870
+ subSegmentScore += 10 /* PathScore.BonusCustomRegExp */;
900
871
  // make sure the regexp is valid before using it
901
872
  try {
902
873
  new RegExp(`(${re})`);
@@ -919,13 +890,13 @@ function tokensToParser(segments, extraOptions) {
919
890
  if (optional)
920
891
  subPattern += '?';
921
892
  pattern += subPattern;
922
- subSegmentScore += 20 /* Dynamic */;
893
+ subSegmentScore += 20 /* PathScore.Dynamic */;
923
894
  if (optional)
924
- subSegmentScore += -8 /* BonusOptional */;
895
+ subSegmentScore += -8 /* PathScore.BonusOptional */;
925
896
  if (repeatable)
926
- subSegmentScore += -20 /* BonusRepeatable */;
897
+ subSegmentScore += -20 /* PathScore.BonusRepeatable */;
927
898
  if (re === '.*')
928
- subSegmentScore += -50 /* BonusWildcard */;
899
+ subSegmentScore += -50 /* PathScore.BonusWildcard */;
929
900
  }
930
901
  segmentScores.push(subSegmentScore);
931
902
  }
@@ -936,7 +907,7 @@ function tokensToParser(segments, extraOptions) {
936
907
  // only apply the strict bonus to the last score
937
908
  if (options.strict && options.end) {
938
909
  const i = score.length - 1;
939
- score[i][score[i].length - 1] += 0.7000000000000001 /* BonusStrict */;
910
+ score[i][score[i].length - 1] += 0.7000000000000001 /* PathScore.BonusStrict */;
940
911
  }
941
912
  // TODO: dev only warn double trailing slash
942
913
  if (!options.strict)
@@ -968,20 +939,22 @@ function tokensToParser(segments, extraOptions) {
968
939
  path += '/';
969
940
  avoidDuplicatedSlash = false;
970
941
  for (const token of segment) {
971
- if (token.type === 0 /* Static */) {
942
+ if (token.type === 0 /* TokenType.Static */) {
972
943
  path += token.value;
973
944
  }
974
- else if (token.type === 1 /* Param */) {
945
+ else if (token.type === 1 /* TokenType.Param */) {
975
946
  const { value, repeatable, optional } = token;
976
947
  const param = value in params ? params[value] : '';
977
- if (Array.isArray(param) && !repeatable)
948
+ if (isArray(param) && !repeatable) {
978
949
  throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);
979
- const text = Array.isArray(param) ? param.join('/') : param;
950
+ }
951
+ const text = isArray(param)
952
+ ? param.join('/')
953
+ : param;
980
954
  if (!text) {
981
955
  if (optional) {
982
- // if we have more than one optional param like /:a?-static and there are more segments, we don't need to
983
- // care about the optional param
984
- if (segment.length < 2 && segments.length > 1) {
956
+ // if we have more than one optional param like /:a?-static we don't need to care about the optional param
957
+ if (segment.length < 2) {
985
958
  // remove the last slash as we could be at the end
986
959
  if (path.endsWith('/'))
987
960
  path = path.slice(0, -1);
@@ -997,7 +970,8 @@ function tokensToParser(segments, extraOptions) {
997
970
  }
998
971
  }
999
972
  }
1000
- return path;
973
+ // avoid empty path when we have multiple optional params
974
+ return path || '/';
1001
975
  }
1002
976
  return {
1003
977
  re,
@@ -1028,12 +1002,12 @@ function compareScoreArray(a, b) {
1028
1002
  // if the last subsegment was Static, the shorter segments should be sorted first
1029
1003
  // otherwise sort the longest segment first
1030
1004
  if (a.length < b.length) {
1031
- return a.length === 1 && a[0] === 40 /* Static */ + 40 /* Segment */
1005
+ return a.length === 1 && a[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
1032
1006
  ? -1
1033
1007
  : 1;
1034
1008
  }
1035
1009
  else if (a.length > b.length) {
1036
- return b.length === 1 && b[0] === 40 /* Static */ + 40 /* Segment */
1010
+ return b.length === 1 && b[0] === 40 /* PathScore.Static */ + 40 /* PathScore.Segment */
1037
1011
  ? 1
1038
1012
  : -1;
1039
1013
  }
@@ -1084,7 +1058,7 @@ function isLastScoreNegative(score) {
1084
1058
  }
1085
1059
 
1086
1060
  const ROOT_TOKEN = {
1087
- type: 0 /* Static */,
1061
+ type: 0 /* TokenType.Static */,
1088
1062
  value: '',
1089
1063
  };
1090
1064
  const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
@@ -1104,7 +1078,7 @@ function tokenizePath(path) {
1104
1078
  function crash(message) {
1105
1079
  throw new Error(`ERR (${state})/"${buffer}": ${message}`);
1106
1080
  }
1107
- let state = 0 /* Static */;
1081
+ let state = 0 /* TokenizerState.Static */;
1108
1082
  let previousState = state;
1109
1083
  const tokens = [];
1110
1084
  // the segment will always be valid because we get into the initial state
@@ -1126,19 +1100,19 @@ function tokenizePath(path) {
1126
1100
  function consumeBuffer() {
1127
1101
  if (!buffer)
1128
1102
  return;
1129
- if (state === 0 /* Static */) {
1103
+ if (state === 0 /* TokenizerState.Static */) {
1130
1104
  segment.push({
1131
- type: 0 /* Static */,
1105
+ type: 0 /* TokenType.Static */,
1132
1106
  value: buffer,
1133
1107
  });
1134
1108
  }
1135
- else if (state === 1 /* Param */ ||
1136
- state === 2 /* ParamRegExp */ ||
1137
- state === 3 /* ParamRegExpEnd */) {
1109
+ else if (state === 1 /* TokenizerState.Param */ ||
1110
+ state === 2 /* TokenizerState.ParamRegExp */ ||
1111
+ state === 3 /* TokenizerState.ParamRegExpEnd */) {
1138
1112
  if (segment.length > 1 && (char === '*' || char === '+'))
1139
1113
  crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
1140
1114
  segment.push({
1141
- type: 1 /* Param */,
1115
+ type: 1 /* TokenType.Param */,
1142
1116
  value: buffer,
1143
1117
  regexp: customRe,
1144
1118
  repeatable: char === '*' || char === '+',
@@ -1155,13 +1129,13 @@ function tokenizePath(path) {
1155
1129
  }
1156
1130
  while (i < path.length) {
1157
1131
  char = path[i++];
1158
- if (char === '\\' && state !== 2 /* ParamRegExp */) {
1132
+ if (char === '\\' && state !== 2 /* TokenizerState.ParamRegExp */) {
1159
1133
  previousState = state;
1160
- state = 4 /* EscapeNext */;
1134
+ state = 4 /* TokenizerState.EscapeNext */;
1161
1135
  continue;
1162
1136
  }
1163
1137
  switch (state) {
1164
- case 0 /* Static */:
1138
+ case 0 /* TokenizerState.Static */:
1165
1139
  if (char === '/') {
1166
1140
  if (buffer) {
1167
1141
  consumeBuffer();
@@ -1170,32 +1144,32 @@ function tokenizePath(path) {
1170
1144
  }
1171
1145
  else if (char === ':') {
1172
1146
  consumeBuffer();
1173
- state = 1 /* Param */;
1147
+ state = 1 /* TokenizerState.Param */;
1174
1148
  }
1175
1149
  else {
1176
1150
  addCharToBuffer();
1177
1151
  }
1178
1152
  break;
1179
- case 4 /* EscapeNext */:
1153
+ case 4 /* TokenizerState.EscapeNext */:
1180
1154
  addCharToBuffer();
1181
1155
  state = previousState;
1182
1156
  break;
1183
- case 1 /* Param */:
1157
+ case 1 /* TokenizerState.Param */:
1184
1158
  if (char === '(') {
1185
- state = 2 /* ParamRegExp */;
1159
+ state = 2 /* TokenizerState.ParamRegExp */;
1186
1160
  }
1187
1161
  else if (VALID_PARAM_RE.test(char)) {
1188
1162
  addCharToBuffer();
1189
1163
  }
1190
1164
  else {
1191
1165
  consumeBuffer();
1192
- state = 0 /* Static */;
1166
+ state = 0 /* TokenizerState.Static */;
1193
1167
  // go back one character if we were not modifying
1194
1168
  if (char !== '*' && char !== '?' && char !== '+')
1195
1169
  i--;
1196
1170
  }
1197
1171
  break;
1198
- case 2 /* ParamRegExp */:
1172
+ case 2 /* TokenizerState.ParamRegExp */:
1199
1173
  // TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
1200
1174
  // it already works by escaping the closing )
1201
1175
  // is this really something people need since you can also write
@@ -1205,16 +1179,16 @@ function tokenizePath(path) {
1205
1179
  if (customRe[customRe.length - 1] == '\\')
1206
1180
  customRe = customRe.slice(0, -1) + char;
1207
1181
  else
1208
- state = 3 /* ParamRegExpEnd */;
1182
+ state = 3 /* TokenizerState.ParamRegExpEnd */;
1209
1183
  }
1210
1184
  else {
1211
1185
  customRe += char;
1212
1186
  }
1213
1187
  break;
1214
- case 3 /* ParamRegExpEnd */:
1188
+ case 3 /* TokenizerState.ParamRegExpEnd */:
1215
1189
  // same as finalizing a param
1216
1190
  consumeBuffer();
1217
- state = 0 /* Static */;
1191
+ state = 0 /* TokenizerState.Static */;
1218
1192
  // go back one character if we were not modifying
1219
1193
  if (char !== '*' && char !== '?' && char !== '+')
1220
1194
  i--;
@@ -1225,7 +1199,7 @@ function tokenizePath(path) {
1225
1199
  break;
1226
1200
  }
1227
1201
  }
1228
- if (state === 2 /* ParamRegExp */)
1202
+ if (state === 2 /* TokenizerState.ParamRegExp */)
1229
1203
  crash(`Unfinished custom RegExp for param "${buffer}"`);
1230
1204
  consumeBuffer();
1231
1205
  finalizeSegment();
@@ -1280,6 +1254,9 @@ function createRouterMatcher(routes, globalOptions) {
1280
1254
  // used later on to remove by name
1281
1255
  const isRootAdd = !originalRecord;
1282
1256
  const mainNormalizedRecord = normalizeRouteRecord(record);
1257
+ {
1258
+ checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent);
1259
+ }
1283
1260
  // we might be the child of an alias
1284
1261
  mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;
1285
1262
  const options = mergeOptions(globalOptions, record);
@@ -1323,11 +1300,11 @@ function createRouterMatcher(routes, globalOptions) {
1323
1300
  throw new Error('Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +
1324
1301
  'See more at https://kdujs-router.web.app/guide/migration/#removed-star-or-catch-all-routes.');
1325
1302
  }
1326
- // create the object before hand so it can be passed to children
1303
+ // create the object beforehand, so it can be passed to children
1327
1304
  matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
1328
1305
  if (parent && path[0] === '/')
1329
1306
  checkMissingParamsInAbsolutePath(matcher, parent);
1330
- // if we are an alias we must tell the original record that we exist
1307
+ // if we are an alias we must tell the original record that we exist,
1331
1308
  // so we can be removed
1332
1309
  if (originalRecord) {
1333
1310
  originalRecord.alias.push(matcher);
@@ -1345,20 +1322,27 @@ function createRouterMatcher(routes, globalOptions) {
1345
1322
  if (isRootAdd && record.name && !isAliasRecord(matcher))
1346
1323
  removeRoute(record.name);
1347
1324
  }
1348
- if ('children' in mainNormalizedRecord) {
1325
+ if (mainNormalizedRecord.children) {
1349
1326
  const children = mainNormalizedRecord.children;
1350
1327
  for (let i = 0; i < children.length; i++) {
1351
1328
  addRoute(children[i], matcher, originalRecord && originalRecord.children[i]);
1352
1329
  }
1353
1330
  }
1354
1331
  // if there was no original record, then the first one was not an alias and all
1355
- // other alias (if any) need to reference this record when adding children
1332
+ // other aliases (if any) need to reference this record when adding children
1356
1333
  originalRecord = originalRecord || matcher;
1357
1334
  // TODO: add normalized records for more flexibility
1358
1335
  // if (parent && isAliasRecord(originalRecord)) {
1359
1336
  // parent.children.push(originalRecord)
1360
1337
  // }
1361
- insertMatcher(matcher);
1338
+ // Avoid adding a record that doesn't display anything. This allows passing through records without a component to
1339
+ // not be reached and pass through the catch all route
1340
+ if ((matcher.record.components &&
1341
+ Object.keys(matcher.record.components).length) ||
1342
+ matcher.record.name ||
1343
+ matcher.record.redirect) {
1344
+ insertMatcher(matcher);
1345
+ }
1362
1346
  }
1363
1347
  return originalMatcher
1364
1348
  ? () => {
@@ -1412,16 +1396,27 @@ function createRouterMatcher(routes, globalOptions) {
1412
1396
  if ('name' in location && location.name) {
1413
1397
  matcher = matcherMap.get(location.name);
1414
1398
  if (!matcher)
1415
- throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
1399
+ throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
1416
1400
  location,
1417
1401
  });
1402
+ // warn if the user is passing invalid params so they can debug it better when they get removed
1403
+ {
1404
+ const invalidParams = Object.keys(location.params || {}).filter(paramName => !matcher.keys.find(k => k.name === paramName));
1405
+ if (invalidParams.length) {
1406
+ warn(`Discarded invalid param(s) "${invalidParams.join('", "')}" when navigating.`);
1407
+ }
1408
+ }
1418
1409
  name = matcher.record.name;
1419
1410
  params = assign(
1420
1411
  // paramsFromLocation is a new object
1421
1412
  paramsFromLocation(currentLocation.params,
1422
1413
  // only keep params that exist in the resolved location
1423
1414
  // TODO: only keep optional params coming from a parent record
1424
- matcher.keys.filter(k => !k.optional).map(k => k.name)), location.params);
1415
+ matcher.keys.filter(k => !k.optional).map(k => k.name)),
1416
+ // discard any existing params in the current location that do not exist here
1417
+ // #1497 this ensures better active/exact matching
1418
+ location.params &&
1419
+ paramsFromLocation(location.params, matcher.keys.map(k => k.name)));
1425
1420
  // throws if cannot be stringified
1426
1421
  path = matcher.stringify(params);
1427
1422
  }
@@ -1435,7 +1430,6 @@ function createRouterMatcher(routes, globalOptions) {
1435
1430
  matcher = matchers.find(m => m.re.test(path));
1436
1431
  // matcher should have a value after the loop
1437
1432
  if (matcher) {
1438
- // TODO: dev warning of unused params if provided
1439
1433
  // we know the matcher works because we tested the regexp
1440
1434
  params = matcher.parse(path);
1441
1435
  name = matcher.record.name;
@@ -1448,7 +1442,7 @@ function createRouterMatcher(routes, globalOptions) {
1448
1442
  ? matcherMap.get(currentLocation.name)
1449
1443
  : matchers.find(m => m.re.test(currentLocation.path));
1450
1444
  if (!matcher)
1451
- throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
1445
+ throw createRouterError(1 /* ErrorTypes.MATCHER_NOT_FOUND */, {
1452
1446
  location,
1453
1447
  currentLocation,
1454
1448
  });
@@ -1506,8 +1500,8 @@ function normalizeRouteRecord(record) {
1506
1500
  updateGuards: new Set(),
1507
1501
  enterCallbacks: {},
1508
1502
  components: 'components' in record
1509
- ? record.components || {}
1510
- : { default: record.component },
1503
+ ? record.components || null
1504
+ : record.component && { default: record.component },
1511
1505
  };
1512
1506
  }
1513
1507
  /**
@@ -1517,7 +1511,7 @@ function normalizeRouteRecord(record) {
1517
1511
  */
1518
1512
  function normalizeRecordProps(record) {
1519
1513
  const propsObject = {};
1520
- // props does not exist on redirect records but we can set false directly
1514
+ // props does not exist on redirect records, but we can set false directly
1521
1515
  const props = record.props || false;
1522
1516
  if ('component' in record) {
1523
1517
  propsObject.default = props;
@@ -1571,17 +1565,31 @@ function isSameParam(a, b) {
1571
1565
  function checkSameParams(a, b) {
1572
1566
  for (const key of a.keys) {
1573
1567
  if (!key.optional && !b.keys.find(isSameParam.bind(null, key)))
1574
- return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);
1568
+ return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`);
1575
1569
  }
1576
1570
  for (const key of b.keys) {
1577
1571
  if (!key.optional && !a.keys.find(isSameParam.bind(null, key)))
1578
- return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);
1572
+ return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" must have the exact same param named "${key.name}"`);
1573
+ }
1574
+ }
1575
+ /**
1576
+ * A route with a name and a child with an empty path without a name should warn when adding the route
1577
+ *
1578
+ * @param mainNormalizedRecord - RouteRecordNormalized
1579
+ * @param parent - RouteRecordMatcher
1580
+ */
1581
+ function checkChildMissingNameWithEmptyPath(mainNormalizedRecord, parent) {
1582
+ if (parent &&
1583
+ parent.record.name &&
1584
+ !mainNormalizedRecord.name &&
1585
+ !mainNormalizedRecord.path) {
1586
+ 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
1587
  }
1580
1588
  }
1581
1589
  function checkMissingParamsInAbsolutePath(record, parent) {
1582
1590
  for (const key of parent.keys) {
1583
1591
  if (!record.keys.find(isSameParam.bind(null, key)))
1584
- return warn(`Absolute path "${record.record.path}" should have the exact same param named "${key.name}" as its parent "${parent.record.path}".`);
1592
+ return warn(`Absolute path "${record.record.path}" must have the exact same param named "${key.name}" as its parent "${parent.record.path}".`);
1585
1593
  }
1586
1594
  }
1587
1595
  function isRecordChildOf(record, parent) {
@@ -1595,7 +1603,7 @@ function isRecordChildOf(record, parent) {
1595
1603
  * On top of that, the RFC3986 (https://tools.ietf.org/html/rfc3986#section-2.2)
1596
1604
  * defines some extra characters to be encoded. Most browsers do not encode them
1597
1605
  * in encodeURI https://github.com/whatwg/url/issues/369, so it may be safer to
1598
- * also encode `!'()*`. Leaving unencoded only ASCII alphanumeric(`a-zA-Z0-9`)
1606
+ * also encode `!'()*`. Leaving un-encoded only ASCII alphanumeric(`a-zA-Z0-9`)
1599
1607
  * plus `-._~`. This extra safety should be applied to query by patching the
1600
1608
  * string returned by encodeURIComponent encodeURI also encodes `[\]^`. `\`
1601
1609
  * should be encoded to avoid ambiguity. Browsers (IE, FF, C) transform a `\`
@@ -1619,7 +1627,7 @@ const PLUS_RE = /\+/g; // %2B
1619
1627
  * application/x-www-form-urlencoded
1620
1628
  * (https://url.spec.whatwg.org/#urlencoded-parsing) and most browsers seems lo
1621
1629
  * 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.
1630
+ * plus character on the query, but it can also be manually encoded by the user.
1623
1631
  *
1624
1632
  * Resources:
1625
1633
  * - https://url.spec.whatwg.org/#urlencoded-parsing
@@ -1751,7 +1759,7 @@ function parseQuery(search) {
1751
1759
  if (key in query) {
1752
1760
  // an extra variable for ts types
1753
1761
  let currentValue = query[key];
1754
- if (!Array.isArray(currentValue)) {
1762
+ if (!isArray(currentValue)) {
1755
1763
  currentValue = query[key] = [currentValue];
1756
1764
  }
1757
1765
  currentValue.push(value);
@@ -1784,7 +1792,7 @@ function stringifyQuery(query) {
1784
1792
  continue;
1785
1793
  }
1786
1794
  // keep null values
1787
- const values = Array.isArray(value)
1795
+ const values = isArray(value)
1788
1796
  ? value.map(v => v && encodeQueryValue(v))
1789
1797
  : [value && encodeQueryValue(value)];
1790
1798
  values.forEach(value => {
@@ -1813,7 +1821,7 @@ function normalizeQuery(query) {
1813
1821
  for (const key in query) {
1814
1822
  const value = query[key];
1815
1823
  if (value !== undefined) {
1816
- normalizedQuery[key] = Array.isArray(value)
1824
+ normalizedQuery[key] = isArray(value)
1817
1825
  ? value.map(v => (v == null ? null : '' + v))
1818
1826
  : value == null
1819
1827
  ? value
@@ -1823,6 +1831,43 @@ function normalizeQuery(query) {
1823
1831
  return normalizedQuery;
1824
1832
  }
1825
1833
 
1834
+ /**
1835
+ * RouteRecord being rendered by the closest ancestor Router View. Used for
1836
+ * `onBeforeRouteUpdate` and `onBeforeRouteLeave`. rvlm stands for Router View
1837
+ * Location Matched
1838
+ *
1839
+ * @internal
1840
+ */
1841
+ const matchedRouteKey = Symbol('router view location matched' );
1842
+ /**
1843
+ * Allows overriding the router view depth to control which component in
1844
+ * `matched` is rendered. rvd stands for Router View Depth
1845
+ *
1846
+ * @internal
1847
+ */
1848
+ const viewDepthKey = Symbol('router view depth' );
1849
+ /**
1850
+ * Allows overriding the router instance returned by `useRouter` in tests. r
1851
+ * stands for router
1852
+ *
1853
+ * @internal
1854
+ */
1855
+ const routerKey = Symbol('router' );
1856
+ /**
1857
+ * Allows overriding the current route returned by `useRoute` in tests. rl
1858
+ * stands for route location
1859
+ *
1860
+ * @internal
1861
+ */
1862
+ const routeLocationKey = Symbol('route location' );
1863
+ /**
1864
+ * Allows overriding the current route used by router-view. Internally this is
1865
+ * used when the `route` prop is passed.
1866
+ *
1867
+ * @internal
1868
+ */
1869
+ const routerViewLocationKey = Symbol('router view location' );
1870
+
1826
1871
  /**
1827
1872
  * Create a list of callbacks that can be reset. Used to create before and after navigation guards list
1828
1873
  */
@@ -1873,7 +1918,7 @@ function onBeforeRouteLeave(leaveGuard) {
1873
1918
  // to avoid warning
1874
1919
  {}).value;
1875
1920
  if (!activeRecord) {
1876
- warn('No active route record was found when calling `onBeforeRouteLeave()`. Make sure you call this function inside of a component child of <router-view>. Maybe you called it inside of App.kdu?');
1921
+ 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
1922
  return;
1878
1923
  }
1879
1924
  registerGuard(activeRecord, 'leaveGuards', leaveGuard);
@@ -1894,7 +1939,7 @@ function onBeforeRouteUpdate(updateGuard) {
1894
1939
  // to avoid warning
1895
1940
  {}).value;
1896
1941
  if (!activeRecord) {
1897
- warn('No active route record was found when calling `onBeforeRouteUpdate()`. Make sure you call this function inside of a component child of <router-view>. Maybe you called it inside of App.kdu?');
1942
+ 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
1943
  return;
1899
1944
  }
1900
1945
  registerGuard(activeRecord, 'updateGuards', updateGuard);
@@ -1906,16 +1951,17 @@ function guardToPromiseFn(guard, to, from, record, name) {
1906
1951
  (record.enterCallbacks[name] = record.enterCallbacks[name] || []);
1907
1952
  return () => new Promise((resolve, reject) => {
1908
1953
  const next = (valid) => {
1909
- if (valid === false)
1910
- reject(createRouterError(4 /* NAVIGATION_ABORTED */, {
1954
+ if (valid === false) {
1955
+ reject(createRouterError(4 /* ErrorTypes.NAVIGATION_ABORTED */, {
1911
1956
  from,
1912
1957
  to,
1913
1958
  }));
1959
+ }
1914
1960
  else if (valid instanceof Error) {
1915
1961
  reject(valid);
1916
1962
  }
1917
1963
  else if (isRouteLocation(valid)) {
1918
- reject(createRouterError(2 /* NAVIGATION_GUARD_REDIRECT */, {
1964
+ reject(createRouterError(2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */, {
1919
1965
  from: to,
1920
1966
  to: valid,
1921
1967
  }));
@@ -1924,8 +1970,9 @@ function guardToPromiseFn(guard, to, from, record, name) {
1924
1970
  if (enterCallbackArray &&
1925
1971
  // since enterCallbackArray is truthy, both record and name also are
1926
1972
  record.enterCallbacks[name] === enterCallbackArray &&
1927
- typeof valid === 'function')
1973
+ typeof valid === 'function') {
1928
1974
  enterCallbackArray.push(valid);
1975
+ }
1929
1976
  resolve();
1930
1977
  }
1931
1978
  };
@@ -1945,7 +1992,6 @@ function guardToPromiseFn(guard, to, from, record, name) {
1945
1992
  }
1946
1993
  return resolvedValue;
1947
1994
  });
1948
- // TODO: test me!
1949
1995
  }
1950
1996
  else if (guardReturn !== undefined) {
1951
1997
  // @ts-expect-error: _called is added at canOnlyBeCalledOnce
@@ -1973,6 +2019,10 @@ function canOnlyBeCalledOnce(next, to, from) {
1973
2019
  function extractComponentsGuards(matched, guardType, to, from) {
1974
2020
  const guards = [];
1975
2021
  for (const record of matched) {
2022
+ if (!record.components && !record.children.length) {
2023
+ warn(`Record with path "${record.path}" is either missing a "component(s)"` +
2024
+ ` or "children" property.`);
2025
+ }
1976
2026
  for (const name in record.components) {
1977
2027
  let rawComponent = record.components[name];
1978
2028
  {
@@ -2029,6 +2079,7 @@ function extractComponentsGuards(matched, guardType, to, from) {
2029
2079
  ? resolved.default
2030
2080
  : resolved;
2031
2081
  // replace the function with the resolved component
2082
+ // cannot be null or undefined because we went into the for loop
2032
2083
  record.components[name] = resolvedComponent;
2033
2084
  // __kccOpts is added by kdu-class-component and contain the regular options
2034
2085
  const options = resolvedComponent.__kccOpts || resolvedComponent;
@@ -2042,6 +2093,7 @@ function extractComponentsGuards(matched, guardType, to, from) {
2042
2093
  }
2043
2094
  /**
2044
2095
  * Allows differentiating lazy components from functional components and kdu-class-component
2096
+ * @internal
2045
2097
  *
2046
2098
  * @param component
2047
2099
  */
@@ -2050,6 +2102,34 @@ function isRouteComponent(component) {
2050
2102
  'displayName' in component ||
2051
2103
  'props' in component ||
2052
2104
  '__kccOpts' in component);
2105
+ }
2106
+ /**
2107
+ * Ensures a route is loaded, so it can be passed as o prop to `<RouterView>`.
2108
+ *
2109
+ * @param route - resolved route to load
2110
+ */
2111
+ function loadRouteLocation(route) {
2112
+ return route.matched.every(record => record.redirect)
2113
+ ? Promise.reject(new Error('Cannot load a route that redirects.'))
2114
+ : Promise.all(route.matched.map(record => record.components &&
2115
+ Promise.all(Object.keys(record.components).reduce((promises, name) => {
2116
+ const rawComponent = record.components[name];
2117
+ if (typeof rawComponent === 'function' &&
2118
+ !('displayName' in rawComponent)) {
2119
+ promises.push(rawComponent().then(resolved => {
2120
+ if (!resolved)
2121
+ return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}". Ensure you passed a function that returns a promise.`));
2122
+ const resolvedComponent = isESModule(resolved)
2123
+ ? resolved.default
2124
+ : resolved;
2125
+ // replace the function with the resolved component
2126
+ // cannot be null or undefined because we went into the for loop
2127
+ record.components[name] = resolvedComponent;
2128
+ return;
2129
+ }));
2130
+ }
2131
+ return promises;
2132
+ }, [])))).then(() => route);
2053
2133
  }
2054
2134
 
2055
2135
  // TODO: we could allow currentRoute as a prop to expose `isActive` and
@@ -2115,6 +2195,9 @@ function useLink(props) {
2115
2195
  }, { flush: 'post' });
2116
2196
  }
2117
2197
  }
2198
+ /**
2199
+ * NOTE: update {@link _RouterLinkI}'s `$slots` type when updating this
2200
+ */
2118
2201
  return {
2119
2202
  route,
2120
2203
  href: computed(() => route.value.href),
@@ -2164,7 +2247,7 @@ const RouterLinkImpl = /*#__PURE__*/ defineComponent({
2164
2247
  : null,
2165
2248
  href: link.href,
2166
2249
  // this would override user added attrs but Kdu will still add
2167
- // the listener so we end up triggering both
2250
+ // the listener, so we end up triggering both
2168
2251
  onClick: link.navigate,
2169
2252
  class: elClass.value,
2170
2253
  }, children);
@@ -2209,7 +2292,7 @@ function includesParams(outer, inner) {
2209
2292
  return false;
2210
2293
  }
2211
2294
  else {
2212
- if (!Array.isArray(outerValue) ||
2295
+ if (!isArray(outerValue) ||
2213
2296
  outerValue.length !== innerValue.length ||
2214
2297
  innerValue.some((value, i) => value !== outerValue[i]))
2215
2298
  return false;
@@ -2253,9 +2336,21 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
2253
2336
  warnDeprecatedUsage();
2254
2337
  const injectedRoute = inject(routerViewLocationKey);
2255
2338
  const routeToDisplay = computed(() => props.route || injectedRoute.value);
2256
- const depth = inject(viewDepthKey, 0);
2257
- const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth]);
2258
- provide(viewDepthKey, depth + 1);
2339
+ const injectedDepth = inject(viewDepthKey, 0);
2340
+ // The depth changes based on empty components option, which allows passthrough routes e.g. routes with children
2341
+ // that are used to reuse the `path` property
2342
+ const depth = computed(() => {
2343
+ let initialDepth = unref(injectedDepth);
2344
+ const { matched } = routeToDisplay.value;
2345
+ let matchedRoute;
2346
+ while ((matchedRoute = matched[initialDepth]) &&
2347
+ !matchedRoute.components) {
2348
+ initialDepth++;
2349
+ }
2350
+ return initialDepth;
2351
+ });
2352
+ const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth.value]);
2353
+ provide(viewDepthKey, computed(() => depth.value + 1));
2259
2354
  provide(matchedRouteKey, matchedRouteRef);
2260
2355
  provide(routerViewLocationKey, routeToDisplay);
2261
2356
  const viewRef = ref();
@@ -2267,7 +2362,7 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
2267
2362
  // this will update the instance for new instances as well as reused
2268
2363
  // instances when navigating to a new route
2269
2364
  to.instances[name] = instance;
2270
- // the component instance is reused for a different route or name so
2365
+ // the component instance is reused for a different route or name, so
2271
2366
  // we copy any saved update or leave guards. With async setup, the
2272
2367
  // mounting component will mount before the matchedRoute changes,
2273
2368
  // making instance === oldInstance, so we check if guards have been
@@ -2293,16 +2388,16 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
2293
2388
  }, { flush: 'post' });
2294
2389
  return () => {
2295
2390
  const route = routeToDisplay.value;
2296
- const matchedRoute = matchedRouteRef.value;
2297
- const ViewComponent = matchedRoute && matchedRoute.components[props.name];
2298
2391
  // we need the value at the time we render because when we unmount, we
2299
2392
  // navigated to a different location so the value is different
2300
2393
  const currentName = props.name;
2394
+ const matchedRoute = matchedRouteRef.value;
2395
+ const ViewComponent = matchedRoute && matchedRoute.components[currentName];
2301
2396
  if (!ViewComponent) {
2302
2397
  return normalizeSlot(slots.default, { Component: ViewComponent, route });
2303
2398
  }
2304
2399
  // props from route configuration
2305
- const routePropsOption = matchedRoute.props[props.name];
2400
+ const routePropsOption = matchedRoute.props[currentName];
2306
2401
  const routeProps = routePropsOption
2307
2402
  ? routePropsOption === true
2308
2403
  ? route.params
@@ -2324,12 +2419,12 @@ const RouterViewImpl = /*#__PURE__*/ defineComponent({
2324
2419
  component.ref) {
2325
2420
  // TODO: can display if it's an alias, its props
2326
2421
  const info = {
2327
- depth,
2422
+ depth: depth.value,
2328
2423
  name: matchedRoute.name,
2329
2424
  path: matchedRoute.path,
2330
2425
  meta: matchedRoute.meta,
2331
2426
  };
2332
- const internalInstances = Array.isArray(component.ref)
2427
+ const internalInstances = isArray(component.ref)
2333
2428
  ? component.ref.map(r => r.i)
2334
2429
  : [component.ref.i];
2335
2430
  internalInstances.forEach(instance => {
@@ -2375,6 +2470,13 @@ function warnDeprecatedUsage() {
2375
2470
  }
2376
2471
  }
2377
2472
 
2473
+ /**
2474
+ * Copies a route location and removes any problematic properties that cannot be shown in devtools (e.g. Kdu instances).
2475
+ *
2476
+ * @param routeLocation - routeLocation to format
2477
+ * @param tooltip - optional tooltip
2478
+ * @returns a copy of the routeLocation
2479
+ */
2378
2480
  function formatRouteLocation(routeLocation, tooltip) {
2379
2481
  const copy = assign({}, routeLocation, {
2380
2482
  // remove variables that can contain kdu instances
@@ -2416,6 +2518,9 @@ function addDevtools(app, router, matcher) {
2416
2518
  componentStateTypes: ['Routing'],
2417
2519
  app,
2418
2520
  }, api => {
2521
+ if (typeof api.now !== 'function') {
2522
+ 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.');
2523
+ }
2419
2524
  // display state added by the router
2420
2525
  api.on.inspectComponent((payload, ctx) => {
2421
2526
  if (payload.instanceData) {
@@ -2439,7 +2544,7 @@ function addDevtools(app, router, matcher) {
2439
2544
  });
2440
2545
  }
2441
2546
  // if multiple useLink are used
2442
- if (Array.isArray(componentInstance.__krl_devtools)) {
2547
+ if (isArray(componentInstance.__krl_devtools)) {
2443
2548
  componentInstance.__devtoolsApi = api;
2444
2549
  componentInstance.__krl_devtools.forEach(devtoolsData => {
2445
2550
  let backgroundColor = ORANGE_400;
@@ -2592,7 +2697,7 @@ function addDevtools(app, router, matcher) {
2592
2697
  api.on.getInspectorState(payload => {
2593
2698
  if (payload.app === app && payload.inspectorId === routerInspectorId) {
2594
2699
  const routes = matcher.getRoutes();
2595
- const route = routes.find(route => route.record.__vd_id === payload.nodeId);
2700
+ const route = routes.find(route => route.record.__kd_id === payload.nodeId);
2596
2701
  if (route) {
2597
2702
  payload.state = {
2598
2703
  options: formatRouteRecordMatcherForStateInspector(route),
@@ -2656,6 +2761,13 @@ function formatRouteRecordMatcherForStateInspector(route) {
2656
2761
  value: route.alias.map(alias => alias.record.path),
2657
2762
  });
2658
2763
  }
2764
+ if (Object.keys(route.record.meta).length) {
2765
+ fields.push({
2766
+ editable: false,
2767
+ key: 'meta',
2768
+ value: route.record.meta,
2769
+ });
2770
+ }
2659
2771
  fields.push({
2660
2772
  key: 'score',
2661
2773
  editable: false,
@@ -2698,21 +2810,21 @@ function formatRouteRecordForInspector(route) {
2698
2810
  backgroundColor: ORANGE_400,
2699
2811
  });
2700
2812
  }
2701
- if (route.__vd_match) {
2813
+ if (route.__kd_match) {
2702
2814
  tags.push({
2703
2815
  label: 'matches',
2704
2816
  textColor: 0,
2705
2817
  backgroundColor: PINK_500,
2706
2818
  });
2707
2819
  }
2708
- if (route.__vd_exactActive) {
2820
+ if (route.__kd_exactActive) {
2709
2821
  tags.push({
2710
2822
  label: 'exact',
2711
2823
  textColor: 0,
2712
2824
  backgroundColor: LIME_500,
2713
2825
  });
2714
2826
  }
2715
- if (route.__vd_active) {
2827
+ if (route.__kd_active) {
2716
2828
  tags.push({
2717
2829
  label: 'active',
2718
2830
  textColor: 0,
@@ -2721,18 +2833,19 @@ function formatRouteRecordForInspector(route) {
2721
2833
  }
2722
2834
  if (record.redirect) {
2723
2835
  tags.push({
2724
- label: 'redirect: ' +
2725
- (typeof record.redirect === 'string' ? record.redirect : 'Object'),
2836
+ label: typeof record.redirect === 'string'
2837
+ ? `redirect: ${record.redirect}`
2838
+ : 'redirects',
2726
2839
  textColor: 0xffffff,
2727
2840
  backgroundColor: DARK,
2728
2841
  });
2729
2842
  }
2730
2843
  // add an id to be able to select it. Using the `path` is not possible because
2731
2844
  // empty path children would collide with their parents
2732
- let id = record.__vd_id;
2845
+ let id = record.__kd_id;
2733
2846
  if (id == null) {
2734
2847
  id = String(routeRecordId++);
2735
- record.__vd_id = id;
2848
+ record.__kd_id = id;
2736
2849
  }
2737
2850
  return {
2738
2851
  id,
@@ -2749,19 +2862,19 @@ function markRouteRecordActive(route, currentRoute) {
2749
2862
  // reset the matching state
2750
2863
  const isExactActive = currentRoute.matched.length &&
2751
2864
  isSameRouteRecord(currentRoute.matched[currentRoute.matched.length - 1], route.record);
2752
- route.__vd_exactActive = route.__vd_active = isExactActive;
2865
+ route.__kd_exactActive = route.__kd_active = isExactActive;
2753
2866
  if (!isExactActive) {
2754
- route.__vd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record));
2867
+ route.__kd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record));
2755
2868
  }
2756
2869
  route.children.forEach(childRoute => markRouteRecordActive(childRoute, currentRoute));
2757
2870
  }
2758
2871
  function resetMatchStateOnRouteRecord(route) {
2759
- route.__vd_match = false;
2872
+ route.__kd_match = false;
2760
2873
  route.children.forEach(resetMatchStateOnRouteRecord);
2761
2874
  }
2762
2875
  function isRouteMatching(route, filter) {
2763
2876
  const found = String(route.re).match(EXTRACT_REGEXP_RE);
2764
- route.__vd_match = false;
2877
+ route.__kd_match = false;
2765
2878
  if (!found || found.length < 3) {
2766
2879
  return false;
2767
2880
  }
@@ -2772,7 +2885,7 @@ function isRouteMatching(route, filter) {
2772
2885
  route.children.forEach(child => isRouteMatching(child, filter));
2773
2886
  // exception case: `/`
2774
2887
  if (route.record.path !== '/' || filter === '/') {
2775
- route.__vd_match = route.re.test(filter);
2888
+ route.__kd_match = route.re.test(filter);
2776
2889
  return true;
2777
2890
  }
2778
2891
  // hide the / route
@@ -2901,7 +3014,7 @@ function createRouter(options) {
2901
3014
  delete targetParams[key];
2902
3015
  }
2903
3016
  }
2904
- // pass encoded values to the matcher so it can produce encoded path and fullPath
3017
+ // pass encoded values to the matcher, so it can produce encoded path and fullPath
2905
3018
  matcherLocation = assign({}, rawLocation, {
2906
3019
  params: encodeParams(rawLocation.params),
2907
3020
  });
@@ -2914,7 +3027,7 @@ function createRouter(options) {
2914
3027
  if (hash && !hash.startsWith('#')) {
2915
3028
  warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`);
2916
3029
  }
2917
- // decoding them) the matcher might have merged current location params so
3030
+ // the matcher might have merged current location params, so
2918
3031
  // we need to run the decoding again
2919
3032
  matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
2920
3033
  const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
@@ -2955,7 +3068,7 @@ function createRouter(options) {
2955
3068
  }
2956
3069
  function checkCanceledNavigation(to, from) {
2957
3070
  if (pendingLocation !== to) {
2958
- return createRouterError(8 /* NAVIGATION_CANCELLED */, {
3071
+ return createRouterError(8 /* ErrorTypes.NAVIGATION_CANCELLED */, {
2959
3072
  from,
2960
3073
  to,
2961
3074
  });
@@ -2990,7 +3103,8 @@ function createRouter(options) {
2990
3103
  return assign({
2991
3104
  query: to.query,
2992
3105
  hash: to.hash,
2993
- params: to.params,
3106
+ // avoid transferring params if the redirect has a path
3107
+ params: 'path' in newTargetLocation ? {} : to.params,
2994
3108
  }, newTargetLocation);
2995
3109
  }
2996
3110
  }
@@ -3004,7 +3118,9 @@ function createRouter(options) {
3004
3118
  const shouldRedirect = handleRedirectRecord(targetLocation);
3005
3119
  if (shouldRedirect)
3006
3120
  return pushWithRedirect(assign(locationAsObject(shouldRedirect), {
3007
- state: data,
3121
+ state: typeof shouldRedirect === 'object'
3122
+ ? assign({}, data, shouldRedirect.state)
3123
+ : data,
3008
3124
  force,
3009
3125
  replace,
3010
3126
  }),
@@ -3015,7 +3131,7 @@ function createRouter(options) {
3015
3131
  toLocation.redirectedFrom = redirectedFrom;
3016
3132
  let failure;
3017
3133
  if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
3018
- failure = createRouterError(16 /* NAVIGATION_DUPLICATED */, { to: toLocation, from });
3134
+ failure = createRouterError(16 /* ErrorTypes.NAVIGATION_DUPLICATED */, { to: toLocation, from });
3019
3135
  // trigger scroll to allow scrolling to the same anchor
3020
3136
  handleScroll(from, from,
3021
3137
  // this is a push, the only way for it to be triggered from a
@@ -3028,14 +3144,14 @@ function createRouter(options) {
3028
3144
  return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
3029
3145
  .catch((error) => isNavigationFailure(error)
3030
3146
  ? // navigation redirects still mark the router as ready
3031
- isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)
3147
+ isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)
3032
3148
  ? error
3033
3149
  : markAsReady(error) // also returns the error
3034
3150
  : // reject any unknown error
3035
3151
  triggerError(error, toLocation, from))
3036
3152
  .then((failure) => {
3037
3153
  if (failure) {
3038
- if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
3154
+ if (isNavigationFailure(failure, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
3039
3155
  if (// we are redirecting to the same location we were already at
3040
3156
  isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) &&
3041
3157
  // and we have done it a couple of times
@@ -3050,10 +3166,14 @@ function createRouter(options) {
3050
3166
  }
3051
3167
  return pushWithRedirect(
3052
3168
  // keep options
3053
- assign(locationAsObject(failure.to), {
3054
- state: data,
3055
- force,
3169
+ assign({
3170
+ // preserve an existing replacement but allow the redirect to override it
3056
3171
  replace,
3172
+ }, locationAsObject(failure.to), {
3173
+ state: typeof failure.to === 'object'
3174
+ ? assign({}, data, failure.to.state)
3175
+ : data,
3176
+ force,
3057
3177
  }),
3058
3178
  // preserve the original redirectedFrom if any
3059
3179
  redirectedFrom || toLocation);
@@ -3119,7 +3239,7 @@ function createRouter(options) {
3119
3239
  for (const record of to.matched) {
3120
3240
  // do not trigger beforeEnter on reused views
3121
3241
  if (record.beforeEnter && !from.matched.includes(record)) {
3122
- if (Array.isArray(record.beforeEnter)) {
3242
+ if (isArray(record.beforeEnter)) {
3123
3243
  for (const beforeEnter of record.beforeEnter)
3124
3244
  guards.push(guardToPromiseFn(beforeEnter, to, from));
3125
3245
  }
@@ -3152,7 +3272,7 @@ function createRouter(options) {
3152
3272
  return runGuardQueue(guards);
3153
3273
  })
3154
3274
  // catch any navigation canceled
3155
- .catch(err => isNavigationFailure(err, 8 /* NAVIGATION_CANCELLED */)
3275
+ .catch(err => isNavigationFailure(err, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)
3156
3276
  ? err
3157
3277
  : Promise.reject(err)));
3158
3278
  }
@@ -3199,6 +3319,8 @@ function createRouter(options) {
3199
3319
  if (removeHistoryListener)
3200
3320
  return;
3201
3321
  removeHistoryListener = routerHistory.listen((to, _from, info) => {
3322
+ if (!router.listening)
3323
+ return;
3202
3324
  // cannot be a redirect route because it was in history
3203
3325
  const toLocation = resolve(to);
3204
3326
  // due to dynamic routing, and to hash history with manual navigation
@@ -3217,15 +3339,15 @@ function createRouter(options) {
3217
3339
  }
3218
3340
  navigate(toLocation, from)
3219
3341
  .catch((error) => {
3220
- if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ | 8 /* NAVIGATION_CANCELLED */)) {
3342
+ if (isNavigationFailure(error, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
3221
3343
  return error;
3222
3344
  }
3223
- if (isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
3345
+ if (isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
3224
3346
  // Here we could call if (info.delta) routerHistory.go(-info.delta,
3225
3347
  // false) but this is bug prone as we have no way to wait the
3226
3348
  // navigation to be finished before calling pushWithRedirect. Using
3227
- // a setTimeout of 16ms seems to work but there is not guarantee for
3228
- // it to work on every browser. So Instead we do not restore the
3349
+ // a setTimeout of 16ms seems to work but there is no guarantee for
3350
+ // it to work on every browser. So instead we do not restore the
3229
3351
  // history entry and trigger a new navigation as requested by the
3230
3352
  // navigation guard.
3231
3353
  // the error is already handled by router.push we just want to avoid
@@ -3235,10 +3357,10 @@ function createRouter(options) {
3235
3357
  )
3236
3358
  .then(failure => {
3237
3359
  // manual change in hash history #916 ending up in the URL not
3238
- // changing but it was changed by the manual url change, so we
3360
+ // changing, but it was changed by the manual url change, so we
3239
3361
  // need to manually change it ourselves
3240
- if (isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ |
3241
- 16 /* NAVIGATION_DUPLICATED */) &&
3362
+ if (isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ |
3363
+ 16 /* ErrorTypes.NAVIGATION_DUPLICATED */) &&
3242
3364
  !info.delta &&
3243
3365
  info.type === NavigationType.pop) {
3244
3366
  routerHistory.go(-1, false);
@@ -3249,8 +3371,9 @@ function createRouter(options) {
3249
3371
  return Promise.reject();
3250
3372
  }
3251
3373
  // do not restore history on unknown direction
3252
- if (info.delta)
3374
+ if (info.delta) {
3253
3375
  routerHistory.go(-info.delta, false);
3376
+ }
3254
3377
  // unrecognized error, transfer to the global handler
3255
3378
  return triggerError(error, toLocation, from);
3256
3379
  })
@@ -3262,11 +3385,14 @@ function createRouter(options) {
3262
3385
  toLocation, from, false);
3263
3386
  // revert the navigation
3264
3387
  if (failure) {
3265
- if (info.delta) {
3388
+ if (info.delta &&
3389
+ // a new navigation has been triggered, so we do not want to revert, that will change the current history
3390
+ // entry while a different route is displayed
3391
+ !isNavigationFailure(failure, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
3266
3392
  routerHistory.go(-info.delta, false);
3267
3393
  }
3268
3394
  else if (info.type === NavigationType.pop &&
3269
- isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ | 16 /* NAVIGATION_DUPLICATED */)) {
3395
+ isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 16 /* ErrorTypes.NAVIGATION_DUPLICATED */)) {
3270
3396
  // manual change in hash history #916
3271
3397
  // it's like a push but lacks the information of the direction
3272
3398
  routerHistory.go(-1, false);
@@ -3342,6 +3468,7 @@ function createRouter(options) {
3342
3468
  const installedApps = new Set();
3343
3469
  const router = {
3344
3470
  currentRoute,
3471
+ listening: true,
3345
3472
  addRoute,
3346
3473
  removeRoute,
3347
3474
  hasRoute,
@@ -3405,6 +3532,7 @@ function createRouter(options) {
3405
3532
  }
3406
3533
  unmountApp();
3407
3534
  };
3535
+ // TODO: this probably needs to be updated so it can be used by kdu-termui
3408
3536
  if (isBrowser) {
3409
3537
  addDevtools(app, router, matcher);
3410
3538
  }
@@ -3454,4 +3582,4 @@ function useRoute() {
3454
3582
  return inject(routeLocationKey);
3455
3583
  }
3456
3584
 
3457
- 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 };
3585
+ 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 };