kdu-router 4.0.16 → 4.2.0

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