kdu-router 3.4.0-beta.0 → 3.5.4

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,12 +1,12 @@
1
1
  /*!
2
- * kdu-router v3.4.0-beta.0
2
+ * kdu-router v3.5.4
3
3
  * (c) 2022 NKDuy
4
4
  * @license MIT
5
5
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
8
  typeof define === 'function' && define.amd ? define(factory) :
9
- (global = global || self, global.KduRouter = factory());
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.KduRouter = factory());
10
10
  }(this, (function () { 'use strict';
11
11
 
12
12
  /* */
@@ -18,7 +18,7 @@
18
18
  }
19
19
 
20
20
  function warn (condition, message) {
21
- if ( !condition) {
21
+ if (!condition) {
22
22
  typeof console !== 'undefined' && console.warn(("[kdu-router] " + message));
23
23
  }
24
24
  }
@@ -30,158 +30,6 @@
30
30
  return a
31
31
  }
32
32
 
33
- var View = {
34
- name: 'RouterView',
35
- functional: true,
36
- props: {
37
- name: {
38
- type: String,
39
- default: 'default'
40
- }
41
- },
42
- render: function render (_, ref) {
43
- var props = ref.props;
44
- var children = ref.children;
45
- var parent = ref.parent;
46
- var data = ref.data;
47
-
48
- // used by devtools to display a router-view badge
49
- data.routerView = true;
50
-
51
- // directly use parent context's createElement() function
52
- // so that components rendered by router-view can resolve named slots
53
- var h = parent.$createElement;
54
- var name = props.name;
55
- var route = parent.$route;
56
- var cache = parent._routerViewCache || (parent._routerViewCache = {});
57
-
58
- // determine current view depth, also check to see if the tree
59
- // has been toggled inactive but kept-alive.
60
- var depth = 0;
61
- var inactive = false;
62
- while (parent && parent._routerRoot !== parent) {
63
- var knodeData = parent.$knode ? parent.$knode.data : {};
64
- if (knodeData.routerView) {
65
- depth++;
66
- }
67
- if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
68
- inactive = true;
69
- }
70
- parent = parent.$parent;
71
- }
72
- data.routerViewDepth = depth;
73
-
74
- // render previous view if the tree is inactive and kept-alive
75
- if (inactive) {
76
- var cachedData = cache[name];
77
- var cachedComponent = cachedData && cachedData.component;
78
- if (cachedComponent) {
79
- // #2301
80
- // pass props
81
- if (cachedData.configProps) {
82
- fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
83
- }
84
- return h(cachedComponent, data, children)
85
- } else {
86
- // render previous empty view
87
- return h()
88
- }
89
- }
90
-
91
- var matched = route.matched[depth];
92
- var component = matched && matched.components[name];
93
-
94
- // render empty node if no matched route or no config component
95
- if (!matched || !component) {
96
- cache[name] = null;
97
- return h()
98
- }
99
-
100
- // cache component
101
- cache[name] = { component: component };
102
-
103
- // attach instance registration hook
104
- // this will be called in the instance's injected lifecycle hooks
105
- data.registerRouteInstance = function (vm, val) {
106
- // val could be undefined for unregistration
107
- var current = matched.instances[name];
108
- if (
109
- (val && current !== vm) ||
110
- (!val && current === vm)
111
- ) {
112
- matched.instances[name] = val;
113
- }
114
- }
115
-
116
- // also register instance in prepatch hook
117
- // in case the same component instance is reused across different routes
118
- ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
119
- matched.instances[name] = knode.componentInstance;
120
- };
121
-
122
- // register instance in init hook
123
- // in case kept-alive component be actived when routes changed
124
- data.hook.init = function (knode) {
125
- if (knode.data.keepAlive &&
126
- knode.componentInstance &&
127
- knode.componentInstance !== matched.instances[name]
128
- ) {
129
- matched.instances[name] = knode.componentInstance;
130
- }
131
- };
132
-
133
- var configProps = matched.props && matched.props[name];
134
- // save route and configProps in cache
135
- if (configProps) {
136
- extend(cache[name], {
137
- route: route,
138
- configProps: configProps
139
- });
140
- fillPropsinData(component, data, route, configProps);
141
- }
142
-
143
- return h(component, data, children)
144
- }
145
- };
146
-
147
- function fillPropsinData (component, data, route, configProps) {
148
- // resolve props
149
- var propsToPass = data.props = resolveProps(route, configProps);
150
- if (propsToPass) {
151
- // clone to prevent mutation
152
- propsToPass = data.props = extend({}, propsToPass);
153
- // pass non-declared props as attrs
154
- var attrs = data.attrs = data.attrs || {};
155
- for (var key in propsToPass) {
156
- if (!component.props || !(key in component.props)) {
157
- attrs[key] = propsToPass[key];
158
- delete propsToPass[key];
159
- }
160
- }
161
- }
162
- }
163
-
164
- function resolveProps (route, config) {
165
- switch (typeof config) {
166
- case 'undefined':
167
- return
168
- case 'object':
169
- return config
170
- case 'function':
171
- return config(route)
172
- case 'boolean':
173
- return config ? route.params : undefined
174
- default:
175
- {
176
- warn(
177
- false,
178
- "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
179
- "expecting an object, function or boolean."
180
- );
181
- }
182
- }
183
- }
184
-
185
33
  /* */
186
34
 
187
35
  var encodeReserveRE = /[!'()*]/g;
@@ -192,10 +40,19 @@
192
40
  // - escapes [!'()*]
193
41
  // - preserve commas
194
42
  var encode = function (str) { return encodeURIComponent(str)
195
- .replace(encodeReserveRE, encodeReserveReplacer)
196
- .replace(commaRE, ','); };
43
+ .replace(encodeReserveRE, encodeReserveReplacer)
44
+ .replace(commaRE, ','); };
197
45
 
198
- var decode = decodeURIComponent;
46
+ function decode (str) {
47
+ try {
48
+ return decodeURIComponent(str)
49
+ } catch (err) {
50
+ {
51
+ warn(false, ("Error decoding \"" + str + "\". Leaving it intact."));
52
+ }
53
+ }
54
+ return str
55
+ }
199
56
 
200
57
  function resolveQuery (
201
58
  query,
@@ -209,16 +66,20 @@
209
66
  try {
210
67
  parsedQuery = parse(query || '');
211
68
  } catch (e) {
212
- warn(false, e.message);
69
+ warn(false, e.message);
213
70
  parsedQuery = {};
214
71
  }
215
72
  for (var key in extraQuery) {
216
73
  var value = extraQuery[key];
217
- parsedQuery[key] = Array.isArray(value) ? value.map(function (v) { return '' + v; }) : '' + value;
74
+ parsedQuery[key] = Array.isArray(value)
75
+ ? value.map(castQueryParamValue)
76
+ : castQueryParamValue(value);
218
77
  }
219
78
  return parsedQuery
220
79
  }
221
80
 
81
+ var castQueryParamValue = function (value) { return (value == null || typeof value === 'object' ? value : String(value)); };
82
+
222
83
  function parseQuery (query) {
223
84
  var res = {};
224
85
 
@@ -231,9 +92,7 @@
231
92
  query.split('&').forEach(function (param) {
232
93
  var parts = param.replace(/\+/g, ' ').split('=');
233
94
  var key = decode(parts.shift());
234
- var val = parts.length > 0
235
- ? decode(parts.join('='))
236
- : null;
95
+ var val = parts.length > 0 ? decode(parts.join('=')) : null;
237
96
 
238
97
  if (res[key] === undefined) {
239
98
  res[key] = val;
@@ -248,34 +107,39 @@
248
107
  }
249
108
 
250
109
  function stringifyQuery (obj) {
251
- var res = obj ? Object.keys(obj).map(function (key) {
252
- var val = obj[key];
253
-
254
- if (val === undefined) {
255
- return ''
256
- }
110
+ var res = obj
111
+ ? Object.keys(obj)
112
+ .map(function (key) {
113
+ var val = obj[key];
257
114
 
258
- if (val === null) {
259
- return encode(key)
260
- }
115
+ if (val === undefined) {
116
+ return ''
117
+ }
261
118
 
262
- if (Array.isArray(val)) {
263
- var result = [];
264
- val.forEach(function (val2) {
265
- if (val2 === undefined) {
266
- return
119
+ if (val === null) {
120
+ return encode(key)
267
121
  }
268
- if (val2 === null) {
269
- result.push(encode(key));
270
- } else {
271
- result.push(encode(key) + '=' + encode(val2));
122
+
123
+ if (Array.isArray(val)) {
124
+ var result = [];
125
+ val.forEach(function (val2) {
126
+ if (val2 === undefined) {
127
+ return
128
+ }
129
+ if (val2 === null) {
130
+ result.push(encode(key));
131
+ } else {
132
+ result.push(encode(key) + '=' + encode(val2));
133
+ }
134
+ });
135
+ return result.join('&')
272
136
  }
273
- });
274
- return result.join('&')
275
- }
276
137
 
277
- return encode(key) + '=' + encode(val)
278
- }).filter(function (x) { return x.length > 0; }).join('&') : null;
138
+ return encode(key) + '=' + encode(val)
139
+ })
140
+ .filter(function (x) { return x.length > 0; })
141
+ .join('&')
142
+ : null;
279
143
  return res ? ("?" + res) : ''
280
144
  }
281
145
 
@@ -352,23 +216,23 @@
352
216
  return (path || '/') + stringify(query) + hash
353
217
  }
354
218
 
355
- function isSameRoute (a, b) {
219
+ function isSameRoute (a, b, onlyPath) {
356
220
  if (b === START) {
357
221
  return a === b
358
222
  } else if (!b) {
359
223
  return false
360
224
  } else if (a.path && b.path) {
361
- return (
362
- a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
225
+ return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
363
226
  a.hash === b.hash &&
364
- isObjectEqual(a.query, b.query)
365
- )
227
+ isObjectEqual(a.query, b.query))
366
228
  } else if (a.name && b.name) {
367
229
  return (
368
230
  a.name === b.name &&
369
- a.hash === b.hash &&
231
+ (onlyPath || (
232
+ a.hash === b.hash &&
370
233
  isObjectEqual(a.query, b.query) &&
371
- isObjectEqual(a.params, b.params)
234
+ isObjectEqual(a.params, b.params))
235
+ )
372
236
  )
373
237
  } else {
374
238
  return false
@@ -381,14 +245,18 @@
381
245
 
382
246
  // handle null value #1566
383
247
  if (!a || !b) { return a === b }
384
- var aKeys = Object.keys(a);
385
- var bKeys = Object.keys(b);
248
+ var aKeys = Object.keys(a).sort();
249
+ var bKeys = Object.keys(b).sort();
386
250
  if (aKeys.length !== bKeys.length) {
387
251
  return false
388
252
  }
389
- return aKeys.every(function (key) {
253
+ return aKeys.every(function (key, i) {
390
254
  var aVal = a[key];
255
+ var bKey = bKeys[i];
256
+ if (bKey !== key) { return false }
391
257
  var bVal = b[key];
258
+ // query values can be null and undefined
259
+ if (aVal == null || bVal == null) { return aVal === bVal }
392
260
  // check nested equality
393
261
  if (typeof aVal === 'object' && typeof bVal === 'object') {
394
262
  return isObjectEqual(aVal, bVal)
@@ -416,6 +284,178 @@
416
284
  return true
417
285
  }
418
286
 
287
+ function handleRouteEntered (route) {
288
+ for (var i = 0; i < route.matched.length; i++) {
289
+ var record = route.matched[i];
290
+ for (var name in record.instances) {
291
+ var instance = record.instances[name];
292
+ var cbs = record.enteredCbs[name];
293
+ if (!instance || !cbs) { continue }
294
+ delete record.enteredCbs[name];
295
+ for (var i$1 = 0; i$1 < cbs.length; i$1++) {
296
+ if (!instance._isBeingDestroyed) { cbs[i$1](instance); }
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ var View = {
303
+ name: 'RouterView',
304
+ functional: true,
305
+ props: {
306
+ name: {
307
+ type: String,
308
+ default: 'default'
309
+ }
310
+ },
311
+ render: function render (_, ref) {
312
+ var props = ref.props;
313
+ var children = ref.children;
314
+ var parent = ref.parent;
315
+ var data = ref.data;
316
+
317
+ // used by devtools to display a router-view badge
318
+ data.routerView = true;
319
+
320
+ // directly use parent context's createElement() function
321
+ // so that components rendered by router-view can resolve named slots
322
+ var h = parent.$createElement;
323
+ var name = props.name;
324
+ var route = parent.$route;
325
+ var cache = parent._routerViewCache || (parent._routerViewCache = {});
326
+
327
+ // determine current view depth, also check to see if the tree
328
+ // has been toggled inactive but kept-alive.
329
+ var depth = 0;
330
+ var inactive = false;
331
+ while (parent && parent._routerRoot !== parent) {
332
+ var knodeData = parent.$knode ? parent.$knode.data : {};
333
+ if (knodeData.routerView) {
334
+ depth++;
335
+ }
336
+ if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
337
+ inactive = true;
338
+ }
339
+ parent = parent.$parent;
340
+ }
341
+ data.routerViewDepth = depth;
342
+
343
+ // render previous view if the tree is inactive and kept-alive
344
+ if (inactive) {
345
+ var cachedData = cache[name];
346
+ var cachedComponent = cachedData && cachedData.component;
347
+ if (cachedComponent) {
348
+ // #2301
349
+ // pass props
350
+ if (cachedData.configProps) {
351
+ fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
352
+ }
353
+ return h(cachedComponent, data, children)
354
+ } else {
355
+ // render previous empty view
356
+ return h()
357
+ }
358
+ }
359
+
360
+ var matched = route.matched[depth];
361
+ var component = matched && matched.components[name];
362
+
363
+ // render empty node if no matched route or no config component
364
+ if (!matched || !component) {
365
+ cache[name] = null;
366
+ return h()
367
+ }
368
+
369
+ // cache component
370
+ cache[name] = { component: component };
371
+
372
+ // attach instance registration hook
373
+ // this will be called in the instance's injected lifecycle hooks
374
+ data.registerRouteInstance = function (vm, val) {
375
+ // val could be undefined for unregistration
376
+ var current = matched.instances[name];
377
+ if (
378
+ (val && current !== vm) ||
379
+ (!val && current === vm)
380
+ ) {
381
+ matched.instances[name] = val;
382
+ }
383
+ }
384
+
385
+ // also register instance in prepatch hook
386
+ // in case the same component instance is reused across different routes
387
+ ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
388
+ matched.instances[name] = knode.componentInstance;
389
+ };
390
+
391
+ // register instance in init hook
392
+ // in case kept-alive component be actived when routes changed
393
+ data.hook.init = function (knode) {
394
+ if (knode.data.keepAlive &&
395
+ knode.componentInstance &&
396
+ knode.componentInstance !== matched.instances[name]
397
+ ) {
398
+ matched.instances[name] = knode.componentInstance;
399
+ }
400
+
401
+ // if the route transition has already been confirmed then we weren't
402
+ // able to call the cbs during confirmation as the component was not
403
+ // registered yet, so we call it here.
404
+ handleRouteEntered(route);
405
+ };
406
+
407
+ var configProps = matched.props && matched.props[name];
408
+ // save route and configProps in cache
409
+ if (configProps) {
410
+ extend(cache[name], {
411
+ route: route,
412
+ configProps: configProps
413
+ });
414
+ fillPropsinData(component, data, route, configProps);
415
+ }
416
+
417
+ return h(component, data, children)
418
+ }
419
+ };
420
+
421
+ function fillPropsinData (component, data, route, configProps) {
422
+ // resolve props
423
+ var propsToPass = data.props = resolveProps(route, configProps);
424
+ if (propsToPass) {
425
+ // clone to prevent mutation
426
+ propsToPass = data.props = extend({}, propsToPass);
427
+ // pass non-declared props as attrs
428
+ var attrs = data.attrs = data.attrs || {};
429
+ for (var key in propsToPass) {
430
+ if (!component.props || !(key in component.props)) {
431
+ attrs[key] = propsToPass[key];
432
+ delete propsToPass[key];
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ function resolveProps (route, config) {
439
+ switch (typeof config) {
440
+ case 'undefined':
441
+ return
442
+ case 'object':
443
+ return config
444
+ case 'function':
445
+ return config(route)
446
+ case 'boolean':
447
+ return config ? route.params : undefined
448
+ default:
449
+ {
450
+ warn(
451
+ false,
452
+ "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
453
+ "expecting an object, function or boolean."
454
+ );
455
+ }
456
+ }
457
+ }
458
+
419
459
  /* */
420
460
 
421
461
  function resolvePath (
@@ -484,7 +524,7 @@
484
524
  }
485
525
 
486
526
  function cleanPath (path) {
487
- return path.replace(/\/\//g, '/')
527
+ return path.replace(/\/(?:\s*\/)+/g, '/')
488
528
  }
489
529
 
490
530
  var isarray = Array.isArray || function (arr) {
@@ -1024,6 +1064,10 @@
1024
1064
 
1025
1065
  var noop = function () {};
1026
1066
 
1067
+ var warnedCustomSlot;
1068
+ var warnedTagProp;
1069
+ var warnedEventProp;
1070
+
1027
1071
  var Link = {
1028
1072
  name: 'RouterLink',
1029
1073
  props: {
@@ -1035,7 +1079,9 @@
1035
1079
  type: String,
1036
1080
  default: 'a'
1037
1081
  },
1082
+ custom: Boolean,
1038
1083
  exact: Boolean,
1084
+ exactPath: Boolean,
1039
1085
  append: Boolean,
1040
1086
  replace: Boolean,
1041
1087
  activeClass: String,
@@ -1084,8 +1130,8 @@
1084
1130
  ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1085
1131
  : route;
1086
1132
 
1087
- classes[exactActiveClass] = isSameRoute(current, compareTarget);
1088
- classes[activeClass] = this.exact
1133
+ classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath);
1134
+ classes[activeClass] = this.exact || this.exactPath
1089
1135
  ? classes[exactActiveClass]
1090
1136
  : isIncludedRoute(current, compareTarget);
1091
1137
 
@@ -1124,19 +1170,40 @@
1124
1170
  });
1125
1171
 
1126
1172
  if (scopedSlot) {
1173
+ if (!this.custom) {
1174
+ !warnedCustomSlot && warn(false, 'In Kdu Router 4, the k-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link k-slot="{ navigate, href }" custom></router-link>\n');
1175
+ warnedCustomSlot = true;
1176
+ }
1127
1177
  if (scopedSlot.length === 1) {
1128
1178
  return scopedSlot[0]
1129
1179
  } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1130
1180
  {
1131
1181
  warn(
1132
1182
  false,
1133
- ("RouterLink with to=\"" + (this.to) + "\" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.")
1183
+ ("<router-link> with to=\"" + (this.to) + "\" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.")
1134
1184
  );
1135
1185
  }
1136
1186
  return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1137
1187
  }
1138
1188
  }
1139
1189
 
1190
+ {
1191
+ if ('tag' in this.$options.propsData && !warnedTagProp) {
1192
+ warn(
1193
+ false,
1194
+ "<router-link>'s tag prop is deprecated and has been removed in Kdu Router 4. Use the k-slot API to remove this warning: https://kdujs-router.web.app/guide/migration/#removal-of-event-and-tag-props-in-router-link."
1195
+ );
1196
+ warnedTagProp = true;
1197
+ }
1198
+ if ('event' in this.$options.propsData && !warnedEventProp) {
1199
+ warn(
1200
+ false,
1201
+ "<router-link>'s event prop is deprecated and has been removed in Kdu Router 4. Use the k-slot API to remove this warning: https://kdujs-router.web.app/guide/migration/#removal-of-event-and-tag-props-in-router-link."
1202
+ );
1203
+ warnedEventProp = true;
1204
+ }
1205
+ }
1206
+
1140
1207
  if (this.tag === 'a') {
1141
1208
  data.on = on;
1142
1209
  data.attrs = { href: href, 'aria-current': ariaCurrentValue };
@@ -1272,7 +1339,8 @@
1272
1339
  routes,
1273
1340
  oldPathList,
1274
1341
  oldPathMap,
1275
- oldNameMap
1342
+ oldNameMap,
1343
+ parentRoute
1276
1344
  ) {
1277
1345
  // the path list is used to control path matching priority
1278
1346
  var pathList = oldPathList || [];
@@ -1282,7 +1350,7 @@
1282
1350
  var nameMap = oldNameMap || Object.create(null);
1283
1351
 
1284
1352
  routes.forEach(function (route) {
1285
- addRouteRecord(pathList, pathMap, nameMap, route);
1353
+ addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
1286
1354
  });
1287
1355
 
1288
1356
  // ensure wildcard routes are always at the end
@@ -1331,6 +1399,14 @@
1331
1399
  path || name
1332
1400
  )) + " cannot be a " + "string id. Use an actual component instead."
1333
1401
  );
1402
+
1403
+ warn(
1404
+ // eslint-disable-next-line no-control-regex
1405
+ !/[^\u0000-\u007F]+/.test(path),
1406
+ "Route with path \"" + path + "\" contains unencoded characters, make sure " +
1407
+ "your path is correctly encoded before passing it to the router. Use " +
1408
+ "encodeURI to encode static segments of your path."
1409
+ );
1334
1410
  }
1335
1411
 
1336
1412
  var pathToRegexpOptions =
@@ -1345,7 +1421,13 @@
1345
1421
  path: normalizedPath,
1346
1422
  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1347
1423
  components: route.components || { default: route.component },
1424
+ alias: route.alias
1425
+ ? typeof route.alias === 'string'
1426
+ ? [route.alias]
1427
+ : route.alias
1428
+ : [],
1348
1429
  instances: {},
1430
+ enteredCbs: {},
1349
1431
  name: name,
1350
1432
  parent: parent,
1351
1433
  matchAs: matchAs,
@@ -1373,7 +1455,7 @@
1373
1455
  warn(
1374
1456
  false,
1375
1457
  "Named Route '" + (route.name) + "' has a default child route. " +
1376
- "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " +
1458
+ "When navigating to this named route (:to=\"{name: '" + (route.name) + "'}\"), " +
1377
1459
  "the default child route will not be rendered. Remove the name from " +
1378
1460
  "this route and use the name of the default child route for named " +
1379
1461
  "links instead."
@@ -1397,7 +1479,7 @@
1397
1479
  var aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
1398
1480
  for (var i = 0; i < aliases.length; ++i) {
1399
1481
  var alias = aliases[i];
1400
- if ( alias === path) {
1482
+ if (alias === path) {
1401
1483
  warn(
1402
1484
  false,
1403
1485
  ("Found an alias with the same value as the path: \"" + path + "\". You have to remove that alias. It will be ignored in development.")
@@ -1424,7 +1506,7 @@
1424
1506
  if (name) {
1425
1507
  if (!nameMap[name]) {
1426
1508
  nameMap[name] = record;
1427
- } else if ( !matchAs) {
1509
+ } else if (!matchAs) {
1428
1510
  warn(
1429
1511
  false,
1430
1512
  "Duplicate named routes definition: " +
@@ -1480,6 +1562,28 @@
1480
1562
  createRouteMap(routes, pathList, pathMap, nameMap);
1481
1563
  }
1482
1564
 
1565
+ function addRoute (parentOrRoute, route) {
1566
+ var parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined;
1567
+ // $flow-disable-line
1568
+ createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent);
1569
+
1570
+ // add aliases of parent
1571
+ if (parent && parent.alias.length) {
1572
+ createRouteMap(
1573
+ // $flow-disable-line route is defined if parent is
1574
+ parent.alias.map(function (alias) { return ({ path: alias, children: [route] }); }),
1575
+ pathList,
1576
+ pathMap,
1577
+ nameMap,
1578
+ parent
1579
+ );
1580
+ }
1581
+ }
1582
+
1583
+ function getRoutes () {
1584
+ return pathList.map(function (path) { return pathMap[path]; })
1585
+ }
1586
+
1483
1587
  function match (
1484
1588
  raw,
1485
1589
  currentRoute,
@@ -1626,6 +1730,8 @@
1626
1730
 
1627
1731
  return {
1628
1732
  match: match,
1733
+ addRoute: addRoute,
1734
+ getRoutes: getRoutes,
1629
1735
  addRoutes: addRoutes
1630
1736
  }
1631
1737
  }
@@ -1645,10 +1751,9 @@
1645
1751
 
1646
1752
  for (var i = 1, len = m.length; i < len; ++i) {
1647
1753
  var key = regex.keys[i - 1];
1648
- var val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i];
1649
1754
  if (key) {
1650
1755
  // Fix #1994: using * with props: true generates a param named 0
1651
- params[key.name || 'pathMatch'] = val;
1756
+ params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i];
1652
1757
  }
1653
1758
  }
1654
1759
 
@@ -1838,7 +1943,17 @@
1838
1943
  }
1839
1944
 
1840
1945
  if (position) {
1841
- window.scrollTo(position.x, position.y);
1946
+ // $flow-disable-line
1947
+ if ('scrollBehavior' in document.documentElement.style) {
1948
+ window.scrollTo({
1949
+ left: position.x,
1950
+ top: position.y,
1951
+ // $flow-disable-line
1952
+ behavior: shouldScroll.behavior
1953
+ });
1954
+ } else {
1955
+ window.scrollTo(position.x, position.y);
1956
+ }
1842
1957
  }
1843
1958
  }
1844
1959
 
@@ -1903,6 +2018,7 @@
1903
2018
  step(0);
1904
2019
  }
1905
2020
 
2021
+ // When changing thing, also edit router.d.ts
1906
2022
  var NavigationFailureType = {
1907
2023
  redirected: 2,
1908
2024
  aborted: 4,
@@ -2020,7 +2136,7 @@
2020
2136
 
2021
2137
  var reject = once(function (reason) {
2022
2138
  var msg = "Failed to resolve async component " + key + ": " + reason;
2023
- warn(false, msg);
2139
+ warn(false, msg);
2024
2140
  if (!error) {
2025
2141
  error = isError(reason)
2026
2142
  ? reason
@@ -2136,6 +2252,7 @@
2136
2252
  var this$1 = this;
2137
2253
 
2138
2254
  var route;
2255
+ // catch redirect option
2139
2256
  try {
2140
2257
  route = this.router.match(location, this.current);
2141
2258
  } catch (e) {
@@ -2145,10 +2262,10 @@
2145
2262
  // Exception should still be thrown
2146
2263
  throw e
2147
2264
  }
2265
+ var prev = this.current;
2148
2266
  this.confirmTransition(
2149
2267
  route,
2150
2268
  function () {
2151
- var prev = this$1.current;
2152
2269
  this$1.updateRoute(route);
2153
2270
  onComplete && onComplete(route);
2154
2271
  this$1.ensureURL();
@@ -2169,16 +2286,13 @@
2169
2286
  onAbort(err);
2170
2287
  }
2171
2288
  if (err && !this$1.ready) {
2172
- this$1.ready = true;
2173
- // Initial redirection should still trigger the onReady onSuccess
2174
- if (!isNavigationFailure(err, NavigationFailureType.redirected)) {
2289
+ // Initial redirection should not mark the history as ready yet
2290
+ // because it's triggered by the redirection instead
2291
+ if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
2292
+ this$1.ready = true;
2175
2293
  this$1.readyErrorCbs.forEach(function (cb) {
2176
2294
  cb(err);
2177
2295
  });
2178
- } else {
2179
- this$1.readyCbs.forEach(function (cb) {
2180
- cb(route);
2181
- });
2182
2296
  }
2183
2297
  }
2184
2298
  }
@@ -2189,16 +2303,19 @@
2189
2303
  var this$1 = this;
2190
2304
 
2191
2305
  var current = this.current;
2306
+ this.pending = route;
2192
2307
  var abort = function (err) {
2193
- // changed after adding errors before that change,
2194
- // redirect and aborted navigation would produce an err == null
2308
+ // changed after adding errors
2309
+ // before that change, redirect and aborted navigation would produce an err == null
2195
2310
  if (!isNavigationFailure(err) && isError(err)) {
2196
2311
  if (this$1.errorCbs.length) {
2197
2312
  this$1.errorCbs.forEach(function (cb) {
2198
2313
  cb(err);
2199
2314
  });
2200
2315
  } else {
2201
- warn(false, 'uncaught error during route navigation:');
2316
+ {
2317
+ warn(false, 'uncaught error during route navigation:');
2318
+ }
2202
2319
  console.error(err);
2203
2320
  }
2204
2321
  }
@@ -2213,6 +2330,9 @@
2213
2330
  route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
2214
2331
  ) {
2215
2332
  this.ensureURL();
2333
+ if (route.hash) {
2334
+ handleScroll(this.router, current, route, false);
2335
+ }
2216
2336
  return abort(createNavigationDuplicatedError(current, route))
2217
2337
  }
2218
2338
 
@@ -2237,7 +2357,6 @@
2237
2357
  resolveAsyncComponents(activated)
2238
2358
  );
2239
2359
 
2240
- this.pending = route;
2241
2360
  var iterator = function (hook, next) {
2242
2361
  if (this$1.pending !== route) {
2243
2362
  return abort(createNavigationCancelledError(current, route))
@@ -2274,11 +2393,9 @@
2274
2393
  };
2275
2394
 
2276
2395
  runQueue(queue, iterator, function () {
2277
- var postEnterCbs = [];
2278
- var isValid = function () { return this$1.current === route; };
2279
2396
  // wait until async components are resolved before
2280
2397
  // extracting in-component enter guards
2281
- var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
2398
+ var enterGuards = extractEnterGuards(activated);
2282
2399
  var queue = enterGuards.concat(this$1.router.resolveHooks);
2283
2400
  runQueue(queue, iterator, function () {
2284
2401
  if (this$1.pending !== route) {
@@ -2288,9 +2405,7 @@
2288
2405
  onComplete(route);
2289
2406
  if (this$1.router.app) {
2290
2407
  this$1.router.app.$nextTick(function () {
2291
- postEnterCbs.forEach(function (cb) {
2292
- cb();
2293
- });
2408
+ handleRouteEntered(route);
2294
2409
  });
2295
2410
  }
2296
2411
  });
@@ -2306,11 +2421,16 @@
2306
2421
  // Default implementation is empty
2307
2422
  };
2308
2423
 
2309
- History.prototype.teardownListeners = function teardownListeners () {
2424
+ History.prototype.teardown = function teardown () {
2425
+ // clean up event listeners
2310
2426
  this.listeners.forEach(function (cleanupListener) {
2311
2427
  cleanupListener();
2312
2428
  });
2313
2429
  this.listeners = [];
2430
+
2431
+ // reset current history route
2432
+ this.current = START;
2433
+ this.pending = null;
2314
2434
  };
2315
2435
 
2316
2436
  function normalizeBase (base) {
@@ -2396,15 +2516,13 @@
2396
2516
  }
2397
2517
 
2398
2518
  function extractEnterGuards (
2399
- activated,
2400
- cbs,
2401
- isValid
2519
+ activated
2402
2520
  ) {
2403
2521
  return extractGuards(
2404
2522
  activated,
2405
2523
  'beforeRouteEnter',
2406
2524
  function (guard, _, match, key) {
2407
- return bindEnterGuard(guard, match, key, cbs, isValid)
2525
+ return bindEnterGuard(guard, match, key)
2408
2526
  }
2409
2527
  )
2410
2528
  }
@@ -2412,45 +2530,21 @@
2412
2530
  function bindEnterGuard (
2413
2531
  guard,
2414
2532
  match,
2415
- key,
2416
- cbs,
2417
- isValid
2533
+ key
2418
2534
  ) {
2419
2535
  return function routeEnterGuard (to, from, next) {
2420
2536
  return guard(to, from, function (cb) {
2421
2537
  if (typeof cb === 'function') {
2422
- cbs.push(function () {
2423
- // #750
2424
- // if a router-view is wrapped with an out-in transition,
2425
- // the instance may not have been registered at this time.
2426
- // we will need to poll for registration until current route
2427
- // is no longer valid.
2428
- poll(cb, match.instances, key, isValid);
2429
- });
2538
+ if (!match.enteredCbs[key]) {
2539
+ match.enteredCbs[key] = [];
2540
+ }
2541
+ match.enteredCbs[key].push(cb);
2430
2542
  }
2431
2543
  next(cb);
2432
2544
  })
2433
2545
  }
2434
2546
  }
2435
2547
 
2436
- function poll (
2437
- cb, // somehow flow cannot infer this is a function
2438
- instances,
2439
- key,
2440
- isValid
2441
- ) {
2442
- if (
2443
- instances[key] &&
2444
- !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
2445
- ) {
2446
- cb(instances[key]);
2447
- } else if (isValid()) {
2448
- setTimeout(function () {
2449
- poll(cb, instances, key, isValid);
2450
- }, 16);
2451
- }
2452
- }
2453
-
2454
2548
  /* */
2455
2549
 
2456
2550
  var HTML5History = /*@__PURE__*/(function (History) {
@@ -2544,8 +2638,13 @@
2544
2638
  }(History));
2545
2639
 
2546
2640
  function getLocation (base) {
2547
- var path = decodeURI(window.location.pathname);
2548
- if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
2641
+ var path = window.location.pathname;
2642
+ var pathLowerCase = path.toLowerCase();
2643
+ var baseLowerCase = base.toLowerCase();
2644
+ // base="/a" shouldn't turn path="/app" into "/a/pp"
2645
+ // so we ensure the trailing slash in the base
2646
+ if (base && ((pathLowerCase === baseLowerCase) ||
2647
+ (pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
2549
2648
  path = path.slice(base.length);
2550
2649
  }
2551
2650
  return (path || '/') + window.location.search + window.location.hash
@@ -2684,17 +2783,6 @@
2684
2783
  if (index < 0) { return '' }
2685
2784
 
2686
2785
  href = href.slice(index + 1);
2687
- // decode the hash but not the search or hash
2688
- // as search(query) is already decoded
2689
- var searchIndex = href.indexOf('?');
2690
- if (searchIndex < 0) {
2691
- var hashIndex = href.indexOf('#');
2692
- if (hashIndex > -1) {
2693
- href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2694
- } else { href = decodeURI(href); }
2695
- } else {
2696
- href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2697
- }
2698
2786
 
2699
2787
  return href
2700
2788
  }
@@ -2773,8 +2861,12 @@
2773
2861
  this.confirmTransition(
2774
2862
  route,
2775
2863
  function () {
2864
+ var prev = this$1.current;
2776
2865
  this$1.index = targetIndex;
2777
2866
  this$1.updateRoute(route);
2867
+ this$1.router.afterHooks.forEach(function (hook) {
2868
+ hook && hook(route, prev);
2869
+ });
2778
2870
  },
2779
2871
  function (err) {
2780
2872
  if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
@@ -2801,6 +2893,9 @@
2801
2893
  var KduRouter = function KduRouter (options) {
2802
2894
  if ( options === void 0 ) options = {};
2803
2895
 
2896
+ {
2897
+ warn(this instanceof KduRouter, "Router must be called with the new operator.");
2898
+ }
2804
2899
  this.app = null;
2805
2900
  this.apps = [];
2806
2901
  this.options = options;
@@ -2850,8 +2945,7 @@
2850
2945
  KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2851
2946
  var this$1 = this;
2852
2947
 
2853
-
2854
- assert(
2948
+ assert(
2855
2949
  install.installed,
2856
2950
  "not installed. Make sure to call `Kdu.use(KduRouter)` " +
2857
2951
  "before creating root instance."
@@ -2868,10 +2962,7 @@
2868
2962
  // we do not release the router so it can be reused
2869
2963
  if (this$1.app === app) { this$1.app = this$1.apps[0] || null; }
2870
2964
 
2871
- if (!this$1.app) {
2872
- // clean up event listeners
2873
- this$1.history.teardownListeners();
2874
- }
2965
+ if (!this$1.app) { this$1.history.teardown(); }
2875
2966
  });
2876
2967
 
2877
2968
  // main app previously initialized
@@ -3010,7 +3101,21 @@
3010
3101
  }
3011
3102
  };
3012
3103
 
3104
+ KduRouter.prototype.getRoutes = function getRoutes () {
3105
+ return this.matcher.getRoutes()
3106
+ };
3107
+
3108
+ KduRouter.prototype.addRoute = function addRoute (parentOrRoute, route) {
3109
+ this.matcher.addRoute(parentOrRoute, route);
3110
+ if (this.history.current !== START) {
3111
+ this.history.transitionTo(this.history.getCurrentLocation());
3112
+ }
3113
+ };
3114
+
3013
3115
  KduRouter.prototype.addRoutes = function addRoutes (routes) {
3116
+ {
3117
+ warn(false, 'router.addRoutes() is deprecated and has been removed in Kdu Router 4. Use router.addRoute() instead.');
3118
+ }
3014
3119
  this.matcher.addRoutes(routes);
3015
3120
  if (this.history.current !== START) {
3016
3121
  this.history.transitionTo(this.history.getCurrentLocation());
@@ -3033,9 +3138,10 @@
3033
3138
  }
3034
3139
 
3035
3140
  KduRouter.install = install;
3036
- KduRouter.version = '3.4.0-beta.0';
3141
+ KduRouter.version = '3.5.4';
3037
3142
  KduRouter.isNavigationFailure = isNavigationFailure;
3038
3143
  KduRouter.NavigationFailureType = NavigationFailureType;
3144
+ KduRouter.START_LOCATION = START;
3039
3145
 
3040
3146
  if (inBrowser && window.Kdu) {
3041
3147
  window.Kdu.use(KduRouter);