kdu-router 4.0.16 → 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
3
- * (c) 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;
@@ -2487,7 +2592,6 @@ function addDevtools(app, router, matcher) {
2487
2592
  title: 'Error during Navigation',
2488
2593
  subtitle: to.fullPath,
2489
2594
  logType: 'error',
2490
- // @ts-ignore
2491
2595
  time: api.now(),
2492
2596
  data: { error },
2493
2597
  groupId: to.meta.__navigationId,
@@ -2509,7 +2613,6 @@ function addDevtools(app, router, matcher) {
2509
2613
  api.addTimelineEvent({
2510
2614
  layerId: navigationsLayerId,
2511
2615
  event: {
2512
- // @ts-ignore
2513
2616
  time: api.now(),
2514
2617
  title: 'Start of navigation',
2515
2618
  subtitle: to.fullPath,
@@ -2545,7 +2648,6 @@ function addDevtools(app, router, matcher) {
2545
2648
  event: {
2546
2649
  title: 'End of navigation',
2547
2650
  subtitle: to.fullPath,
2548
- // @ts-ignore
2549
2651
  time: api.now(),
2550
2652
  data,
2551
2653
  logType: failure ? 'warning' : 'default',
@@ -2595,7 +2697,7 @@ function addDevtools(app, router, matcher) {
2595
2697
  api.on.getInspectorState(payload => {
2596
2698
  if (payload.app === app && payload.inspectorId === routerInspectorId) {
2597
2699
  const routes = matcher.getRoutes();
2598
- const route = routes.find(route => route.record.__vd_id === payload.nodeId);
2700
+ const route = routes.find(route => route.record.__kd_id === payload.nodeId);
2599
2701
  if (route) {
2600
2702
  payload.state = {
2601
2703
  options: formatRouteRecordMatcherForStateInspector(route),
@@ -2659,6 +2761,13 @@ function formatRouteRecordMatcherForStateInspector(route) {
2659
2761
  value: route.alias.map(alias => alias.record.path),
2660
2762
  });
2661
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
+ }
2662
2771
  fields.push({
2663
2772
  key: 'score',
2664
2773
  editable: false,
@@ -2701,21 +2810,21 @@ function formatRouteRecordForInspector(route) {
2701
2810
  backgroundColor: ORANGE_400,
2702
2811
  });
2703
2812
  }
2704
- if (route.__vd_match) {
2813
+ if (route.__kd_match) {
2705
2814
  tags.push({
2706
2815
  label: 'matches',
2707
2816
  textColor: 0,
2708
2817
  backgroundColor: PINK_500,
2709
2818
  });
2710
2819
  }
2711
- if (route.__vd_exactActive) {
2820
+ if (route.__kd_exactActive) {
2712
2821
  tags.push({
2713
2822
  label: 'exact',
2714
2823
  textColor: 0,
2715
2824
  backgroundColor: LIME_500,
2716
2825
  });
2717
2826
  }
2718
- if (route.__vd_active) {
2827
+ if (route.__kd_active) {
2719
2828
  tags.push({
2720
2829
  label: 'active',
2721
2830
  textColor: 0,
@@ -2724,18 +2833,19 @@ function formatRouteRecordForInspector(route) {
2724
2833
  }
2725
2834
  if (record.redirect) {
2726
2835
  tags.push({
2727
- label: 'redirect: ' +
2728
- (typeof record.redirect === 'string' ? record.redirect : 'Object'),
2836
+ label: typeof record.redirect === 'string'
2837
+ ? `redirect: ${record.redirect}`
2838
+ : 'redirects',
2729
2839
  textColor: 0xffffff,
2730
2840
  backgroundColor: DARK,
2731
2841
  });
2732
2842
  }
2733
2843
  // add an id to be able to select it. Using the `path` is not possible because
2734
2844
  // empty path children would collide with their parents
2735
- let id = record.__vd_id;
2845
+ let id = record.__kd_id;
2736
2846
  if (id == null) {
2737
2847
  id = String(routeRecordId++);
2738
- record.__vd_id = id;
2848
+ record.__kd_id = id;
2739
2849
  }
2740
2850
  return {
2741
2851
  id,
@@ -2752,19 +2862,19 @@ function markRouteRecordActive(route, currentRoute) {
2752
2862
  // reset the matching state
2753
2863
  const isExactActive = currentRoute.matched.length &&
2754
2864
  isSameRouteRecord(currentRoute.matched[currentRoute.matched.length - 1], route.record);
2755
- route.__vd_exactActive = route.__vd_active = isExactActive;
2865
+ route.__kd_exactActive = route.__kd_active = isExactActive;
2756
2866
  if (!isExactActive) {
2757
- route.__vd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record));
2867
+ route.__kd_active = currentRoute.matched.some(match => isSameRouteRecord(match, route.record));
2758
2868
  }
2759
2869
  route.children.forEach(childRoute => markRouteRecordActive(childRoute, currentRoute));
2760
2870
  }
2761
2871
  function resetMatchStateOnRouteRecord(route) {
2762
- route.__vd_match = false;
2872
+ route.__kd_match = false;
2763
2873
  route.children.forEach(resetMatchStateOnRouteRecord);
2764
2874
  }
2765
2875
  function isRouteMatching(route, filter) {
2766
2876
  const found = String(route.re).match(EXTRACT_REGEXP_RE);
2767
- route.__vd_match = false;
2877
+ route.__kd_match = false;
2768
2878
  if (!found || found.length < 3) {
2769
2879
  return false;
2770
2880
  }
@@ -2775,7 +2885,7 @@ function isRouteMatching(route, filter) {
2775
2885
  route.children.forEach(child => isRouteMatching(child, filter));
2776
2886
  // exception case: `/`
2777
2887
  if (route.record.path !== '/' || filter === '/') {
2778
- route.__vd_match = route.re.test(filter);
2888
+ route.__kd_match = route.re.test(filter);
2779
2889
  return true;
2780
2890
  }
2781
2891
  // hide the / route
@@ -2904,7 +3014,7 @@ function createRouter(options) {
2904
3014
  delete targetParams[key];
2905
3015
  }
2906
3016
  }
2907
- // 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
2908
3018
  matcherLocation = assign({}, rawLocation, {
2909
3019
  params: encodeParams(rawLocation.params),
2910
3020
  });
@@ -2917,7 +3027,7 @@ function createRouter(options) {
2917
3027
  if (hash && !hash.startsWith('#')) {
2918
3028
  warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`);
2919
3029
  }
2920
- // decoding them) the matcher might have merged current location params so
3030
+ // the matcher might have merged current location params, so
2921
3031
  // we need to run the decoding again
2922
3032
  matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
2923
3033
  const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
@@ -2958,7 +3068,7 @@ function createRouter(options) {
2958
3068
  }
2959
3069
  function checkCanceledNavigation(to, from) {
2960
3070
  if (pendingLocation !== to) {
2961
- return createRouterError(8 /* NAVIGATION_CANCELLED */, {
3071
+ return createRouterError(8 /* ErrorTypes.NAVIGATION_CANCELLED */, {
2962
3072
  from,
2963
3073
  to,
2964
3074
  });
@@ -2993,7 +3103,8 @@ function createRouter(options) {
2993
3103
  return assign({
2994
3104
  query: to.query,
2995
3105
  hash: to.hash,
2996
- params: to.params,
3106
+ // avoid transferring params if the redirect has a path
3107
+ params: 'path' in newTargetLocation ? {} : to.params,
2997
3108
  }, newTargetLocation);
2998
3109
  }
2999
3110
  }
@@ -3007,7 +3118,9 @@ function createRouter(options) {
3007
3118
  const shouldRedirect = handleRedirectRecord(targetLocation);
3008
3119
  if (shouldRedirect)
3009
3120
  return pushWithRedirect(assign(locationAsObject(shouldRedirect), {
3010
- state: data,
3121
+ state: typeof shouldRedirect === 'object'
3122
+ ? assign({}, data, shouldRedirect.state)
3123
+ : data,
3011
3124
  force,
3012
3125
  replace,
3013
3126
  }),
@@ -3018,7 +3131,7 @@ function createRouter(options) {
3018
3131
  toLocation.redirectedFrom = redirectedFrom;
3019
3132
  let failure;
3020
3133
  if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
3021
- failure = createRouterError(16 /* NAVIGATION_DUPLICATED */, { to: toLocation, from });
3134
+ failure = createRouterError(16 /* ErrorTypes.NAVIGATION_DUPLICATED */, { to: toLocation, from });
3022
3135
  // trigger scroll to allow scrolling to the same anchor
3023
3136
  handleScroll(from, from,
3024
3137
  // this is a push, the only way for it to be triggered from a
@@ -3031,14 +3144,14 @@ function createRouter(options) {
3031
3144
  return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
3032
3145
  .catch((error) => isNavigationFailure(error)
3033
3146
  ? // navigation redirects still mark the router as ready
3034
- isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)
3147
+ isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)
3035
3148
  ? error
3036
3149
  : markAsReady(error) // also returns the error
3037
3150
  : // reject any unknown error
3038
3151
  triggerError(error, toLocation, from))
3039
3152
  .then((failure) => {
3040
3153
  if (failure) {
3041
- if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
3154
+ if (isNavigationFailure(failure, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
3042
3155
  if (// we are redirecting to the same location we were already at
3043
3156
  isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) &&
3044
3157
  // and we have done it a couple of times
@@ -3053,10 +3166,14 @@ function createRouter(options) {
3053
3166
  }
3054
3167
  return pushWithRedirect(
3055
3168
  // keep options
3056
- assign(locationAsObject(failure.to), {
3057
- state: data,
3058
- force,
3169
+ assign({
3170
+ // preserve an existing replacement but allow the redirect to override it
3059
3171
  replace,
3172
+ }, locationAsObject(failure.to), {
3173
+ state: typeof failure.to === 'object'
3174
+ ? assign({}, data, failure.to.state)
3175
+ : data,
3176
+ force,
3060
3177
  }),
3061
3178
  // preserve the original redirectedFrom if any
3062
3179
  redirectedFrom || toLocation);
@@ -3122,7 +3239,7 @@ function createRouter(options) {
3122
3239
  for (const record of to.matched) {
3123
3240
  // do not trigger beforeEnter on reused views
3124
3241
  if (record.beforeEnter && !from.matched.includes(record)) {
3125
- if (Array.isArray(record.beforeEnter)) {
3242
+ if (isArray(record.beforeEnter)) {
3126
3243
  for (const beforeEnter of record.beforeEnter)
3127
3244
  guards.push(guardToPromiseFn(beforeEnter, to, from));
3128
3245
  }
@@ -3155,7 +3272,7 @@ function createRouter(options) {
3155
3272
  return runGuardQueue(guards);
3156
3273
  })
3157
3274
  // catch any navigation canceled
3158
- .catch(err => isNavigationFailure(err, 8 /* NAVIGATION_CANCELLED */)
3275
+ .catch(err => isNavigationFailure(err, 8 /* ErrorTypes.NAVIGATION_CANCELLED */)
3159
3276
  ? err
3160
3277
  : Promise.reject(err)));
3161
3278
  }
@@ -3202,6 +3319,8 @@ function createRouter(options) {
3202
3319
  if (removeHistoryListener)
3203
3320
  return;
3204
3321
  removeHistoryListener = routerHistory.listen((to, _from, info) => {
3322
+ if (!router.listening)
3323
+ return;
3205
3324
  // cannot be a redirect route because it was in history
3206
3325
  const toLocation = resolve(to);
3207
3326
  // due to dynamic routing, and to hash history with manual navigation
@@ -3220,15 +3339,15 @@ function createRouter(options) {
3220
3339
  }
3221
3340
  navigate(toLocation, from)
3222
3341
  .catch((error) => {
3223
- if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ | 8 /* NAVIGATION_CANCELLED */)) {
3342
+ if (isNavigationFailure(error, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 8 /* ErrorTypes.NAVIGATION_CANCELLED */)) {
3224
3343
  return error;
3225
3344
  }
3226
- if (isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
3345
+ if (isNavigationFailure(error, 2 /* ErrorTypes.NAVIGATION_GUARD_REDIRECT */)) {
3227
3346
  // Here we could call if (info.delta) routerHistory.go(-info.delta,
3228
3347
  // false) but this is bug prone as we have no way to wait the
3229
3348
  // navigation to be finished before calling pushWithRedirect. Using
3230
- // a setTimeout of 16ms seems to work but there is not guarantee for
3231
- // 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
3232
3351
  // history entry and trigger a new navigation as requested by the
3233
3352
  // navigation guard.
3234
3353
  // the error is already handled by router.push we just want to avoid
@@ -3238,10 +3357,10 @@ function createRouter(options) {
3238
3357
  )
3239
3358
  .then(failure => {
3240
3359
  // 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
3360
+ // changing, but it was changed by the manual url change, so we
3242
3361
  // need to manually change it ourselves
3243
- if (isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ |
3244
- 16 /* NAVIGATION_DUPLICATED */) &&
3362
+ if (isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ |
3363
+ 16 /* ErrorTypes.NAVIGATION_DUPLICATED */) &&
3245
3364
  !info.delta &&
3246
3365
  info.type === NavigationType.pop) {
3247
3366
  routerHistory.go(-1, false);
@@ -3252,8 +3371,9 @@ function createRouter(options) {
3252
3371
  return Promise.reject();
3253
3372
  }
3254
3373
  // do not restore history on unknown direction
3255
- if (info.delta)
3374
+ if (info.delta) {
3256
3375
  routerHistory.go(-info.delta, false);
3376
+ }
3257
3377
  // unrecognized error, transfer to the global handler
3258
3378
  return triggerError(error, toLocation, from);
3259
3379
  })
@@ -3265,11 +3385,14 @@ function createRouter(options) {
3265
3385
  toLocation, from, false);
3266
3386
  // revert the navigation
3267
3387
  if (failure) {
3268
- 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 */)) {
3269
3392
  routerHistory.go(-info.delta, false);
3270
3393
  }
3271
3394
  else if (info.type === NavigationType.pop &&
3272
- isNavigationFailure(failure, 4 /* NAVIGATION_ABORTED */ | 16 /* NAVIGATION_DUPLICATED */)) {
3395
+ isNavigationFailure(failure, 4 /* ErrorTypes.NAVIGATION_ABORTED */ | 16 /* ErrorTypes.NAVIGATION_DUPLICATED */)) {
3273
3396
  // manual change in hash history #916
3274
3397
  // it's like a push but lacks the information of the direction
3275
3398
  routerHistory.go(-1, false);
@@ -3345,6 +3468,7 @@ function createRouter(options) {
3345
3468
  const installedApps = new Set();
3346
3469
  const router = {
3347
3470
  currentRoute,
3471
+ listening: true,
3348
3472
  addRoute,
3349
3473
  removeRoute,
3350
3474
  hasRoute,
@@ -3408,6 +3532,7 @@ function createRouter(options) {
3408
3532
  }
3409
3533
  unmountApp();
3410
3534
  };
3535
+ // TODO: this probably needs to be updated so it can be used by kdu-termui
3411
3536
  if (isBrowser) {
3412
3537
  addDevtools(app, router, matcher);
3413
3538
  }
@@ -3457,4 +3582,4 @@ function useRoute() {
3457
3582
  return inject(routeLocationKey);
3458
3583
  }
3459
3584
 
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 };
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 };