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,5 +1,5 @@
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
  */
@@ -12,7 +12,7 @@ function assert (condition, message) {
12
12
  }
13
13
 
14
14
  function warn (condition, message) {
15
- if (process.env.NODE_ENV !== 'production' && !condition) {
15
+ if (!condition) {
16
16
  typeof console !== 'undefined' && console.warn(("[kdu-router] " + message));
17
17
  }
18
18
  }
@@ -24,158 +24,6 @@ function extend (a, b) {
24
24
  return a
25
25
  }
26
26
 
27
- var View = {
28
- name: 'RouterView',
29
- functional: true,
30
- props: {
31
- name: {
32
- type: String,
33
- default: 'default'
34
- }
35
- },
36
- render: function render (_, ref) {
37
- var props = ref.props;
38
- var children = ref.children;
39
- var parent = ref.parent;
40
- var data = ref.data;
41
-
42
- // used by devtools to display a router-view badge
43
- data.routerView = true;
44
-
45
- // directly use parent context's createElement() function
46
- // so that components rendered by router-view can resolve named slots
47
- var h = parent.$createElement;
48
- var name = props.name;
49
- var route = parent.$route;
50
- var cache = parent._routerViewCache || (parent._routerViewCache = {});
51
-
52
- // determine current view depth, also check to see if the tree
53
- // has been toggled inactive but kept-alive.
54
- var depth = 0;
55
- var inactive = false;
56
- while (parent && parent._routerRoot !== parent) {
57
- var knodeData = parent.$knode ? parent.$knode.data : {};
58
- if (knodeData.routerView) {
59
- depth++;
60
- }
61
- if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
62
- inactive = true;
63
- }
64
- parent = parent.$parent;
65
- }
66
- data.routerViewDepth = depth;
67
-
68
- // render previous view if the tree is inactive and kept-alive
69
- if (inactive) {
70
- var cachedData = cache[name];
71
- var cachedComponent = cachedData && cachedData.component;
72
- if (cachedComponent) {
73
- // #2301
74
- // pass props
75
- if (cachedData.configProps) {
76
- fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
77
- }
78
- return h(cachedComponent, data, children)
79
- } else {
80
- // render previous empty view
81
- return h()
82
- }
83
- }
84
-
85
- var matched = route.matched[depth];
86
- var component = matched && matched.components[name];
87
-
88
- // render empty node if no matched route or no config component
89
- if (!matched || !component) {
90
- cache[name] = null;
91
- return h()
92
- }
93
-
94
- // cache component
95
- cache[name] = { component: component };
96
-
97
- // attach instance registration hook
98
- // this will be called in the instance's injected lifecycle hooks
99
- data.registerRouteInstance = function (vm, val) {
100
- // val could be undefined for unregistration
101
- var current = matched.instances[name];
102
- if (
103
- (val && current !== vm) ||
104
- (!val && current === vm)
105
- ) {
106
- matched.instances[name] = val;
107
- }
108
- }
109
-
110
- // also register instance in prepatch hook
111
- // in case the same component instance is reused across different routes
112
- ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
113
- matched.instances[name] = knode.componentInstance;
114
- };
115
-
116
- // register instance in init hook
117
- // in case kept-alive component be actived when routes changed
118
- data.hook.init = function (knode) {
119
- if (knode.data.keepAlive &&
120
- knode.componentInstance &&
121
- knode.componentInstance !== matched.instances[name]
122
- ) {
123
- matched.instances[name] = knode.componentInstance;
124
- }
125
- };
126
-
127
- var configProps = matched.props && matched.props[name];
128
- // save route and configProps in cache
129
- if (configProps) {
130
- extend(cache[name], {
131
- route: route,
132
- configProps: configProps
133
- });
134
- fillPropsinData(component, data, route, configProps);
135
- }
136
-
137
- return h(component, data, children)
138
- }
139
- };
140
-
141
- function fillPropsinData (component, data, route, configProps) {
142
- // resolve props
143
- var propsToPass = data.props = resolveProps(route, configProps);
144
- if (propsToPass) {
145
- // clone to prevent mutation
146
- propsToPass = data.props = extend({}, propsToPass);
147
- // pass non-declared props as attrs
148
- var attrs = data.attrs = data.attrs || {};
149
- for (var key in propsToPass) {
150
- if (!component.props || !(key in component.props)) {
151
- attrs[key] = propsToPass[key];
152
- delete propsToPass[key];
153
- }
154
- }
155
- }
156
- }
157
-
158
- function resolveProps (route, config) {
159
- switch (typeof config) {
160
- case 'undefined':
161
- return
162
- case 'object':
163
- return config
164
- case 'function':
165
- return config(route)
166
- case 'boolean':
167
- return config ? route.params : undefined
168
- default:
169
- if (process.env.NODE_ENV !== 'production') {
170
- warn(
171
- false,
172
- "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
173
- "expecting an object, function or boolean."
174
- );
175
- }
176
- }
177
- }
178
-
179
27
  /* */
180
28
 
181
29
  var encodeReserveRE = /[!'()*]/g;
@@ -186,10 +34,19 @@ var commaRE = /%2C/g;
186
34
  // - escapes [!'()*]
187
35
  // - preserve commas
188
36
  var encode = function (str) { return encodeURIComponent(str)
189
- .replace(encodeReserveRE, encodeReserveReplacer)
190
- .replace(commaRE, ','); };
37
+ .replace(encodeReserveRE, encodeReserveReplacer)
38
+ .replace(commaRE, ','); };
191
39
 
192
- var decode = decodeURIComponent;
40
+ function decode (str) {
41
+ try {
42
+ return decodeURIComponent(str)
43
+ } catch (err) {
44
+ if (process.env.NODE_ENV !== 'production') {
45
+ warn(false, ("Error decoding \"" + str + "\". Leaving it intact."));
46
+ }
47
+ }
48
+ return str
49
+ }
193
50
 
194
51
  function resolveQuery (
195
52
  query,
@@ -208,11 +65,15 @@ function resolveQuery (
208
65
  }
209
66
  for (var key in extraQuery) {
210
67
  var value = extraQuery[key];
211
- parsedQuery[key] = Array.isArray(value) ? value.map(function (v) { return '' + v; }) : '' + value;
68
+ parsedQuery[key] = Array.isArray(value)
69
+ ? value.map(castQueryParamValue)
70
+ : castQueryParamValue(value);
212
71
  }
213
72
  return parsedQuery
214
73
  }
215
74
 
75
+ var castQueryParamValue = function (value) { return (value == null || typeof value === 'object' ? value : String(value)); };
76
+
216
77
  function parseQuery (query) {
217
78
  var res = {};
218
79
 
@@ -225,9 +86,7 @@ function parseQuery (query) {
225
86
  query.split('&').forEach(function (param) {
226
87
  var parts = param.replace(/\+/g, ' ').split('=');
227
88
  var key = decode(parts.shift());
228
- var val = parts.length > 0
229
- ? decode(parts.join('='))
230
- : null;
89
+ var val = parts.length > 0 ? decode(parts.join('=')) : null;
231
90
 
232
91
  if (res[key] === undefined) {
233
92
  res[key] = val;
@@ -242,34 +101,39 @@ function parseQuery (query) {
242
101
  }
243
102
 
244
103
  function stringifyQuery (obj) {
245
- var res = obj ? Object.keys(obj).map(function (key) {
246
- var val = obj[key];
104
+ var res = obj
105
+ ? Object.keys(obj)
106
+ .map(function (key) {
107
+ var val = obj[key];
247
108
 
248
- if (val === undefined) {
249
- return ''
250
- }
251
-
252
- if (val === null) {
253
- return encode(key)
254
- }
109
+ if (val === undefined) {
110
+ return ''
111
+ }
255
112
 
256
- if (Array.isArray(val)) {
257
- var result = [];
258
- val.forEach(function (val2) {
259
- if (val2 === undefined) {
260
- return
113
+ if (val === null) {
114
+ return encode(key)
261
115
  }
262
- if (val2 === null) {
263
- result.push(encode(key));
264
- } else {
265
- result.push(encode(key) + '=' + encode(val2));
116
+
117
+ if (Array.isArray(val)) {
118
+ var result = [];
119
+ val.forEach(function (val2) {
120
+ if (val2 === undefined) {
121
+ return
122
+ }
123
+ if (val2 === null) {
124
+ result.push(encode(key));
125
+ } else {
126
+ result.push(encode(key) + '=' + encode(val2));
127
+ }
128
+ });
129
+ return result.join('&')
266
130
  }
267
- });
268
- return result.join('&')
269
- }
270
131
 
271
- return encode(key) + '=' + encode(val)
272
- }).filter(function (x) { return x.length > 0; }).join('&') : null;
132
+ return encode(key) + '=' + encode(val)
133
+ })
134
+ .filter(function (x) { return x.length > 0; })
135
+ .join('&')
136
+ : null;
273
137
  return res ? ("?" + res) : ''
274
138
  }
275
139
 
@@ -346,23 +210,23 @@ function getFullPath (
346
210
  return (path || '/') + stringify(query) + hash
347
211
  }
348
212
 
349
- function isSameRoute (a, b) {
213
+ function isSameRoute (a, b, onlyPath) {
350
214
  if (b === START) {
351
215
  return a === b
352
216
  } else if (!b) {
353
217
  return false
354
218
  } else if (a.path && b.path) {
355
- return (
356
- a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
219
+ return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
357
220
  a.hash === b.hash &&
358
- isObjectEqual(a.query, b.query)
359
- )
221
+ isObjectEqual(a.query, b.query))
360
222
  } else if (a.name && b.name) {
361
223
  return (
362
224
  a.name === b.name &&
363
- a.hash === b.hash &&
225
+ (onlyPath || (
226
+ a.hash === b.hash &&
364
227
  isObjectEqual(a.query, b.query) &&
365
- isObjectEqual(a.params, b.params)
228
+ isObjectEqual(a.params, b.params))
229
+ )
366
230
  )
367
231
  } else {
368
232
  return false
@@ -375,14 +239,18 @@ function isObjectEqual (a, b) {
375
239
 
376
240
  // handle null value #1566
377
241
  if (!a || !b) { return a === b }
378
- var aKeys = Object.keys(a);
379
- var bKeys = Object.keys(b);
242
+ var aKeys = Object.keys(a).sort();
243
+ var bKeys = Object.keys(b).sort();
380
244
  if (aKeys.length !== bKeys.length) {
381
245
  return false
382
246
  }
383
- return aKeys.every(function (key) {
247
+ return aKeys.every(function (key, i) {
384
248
  var aVal = a[key];
249
+ var bKey = bKeys[i];
250
+ if (bKey !== key) { return false }
385
251
  var bVal = b[key];
252
+ // query values can be null and undefined
253
+ if (aVal == null || bVal == null) { return aVal === bVal }
386
254
  // check nested equality
387
255
  if (typeof aVal === 'object' && typeof bVal === 'object') {
388
256
  return isObjectEqual(aVal, bVal)
@@ -410,6 +278,178 @@ function queryIncludes (current, target) {
410
278
  return true
411
279
  }
412
280
 
281
+ function handleRouteEntered (route) {
282
+ for (var i = 0; i < route.matched.length; i++) {
283
+ var record = route.matched[i];
284
+ for (var name in record.instances) {
285
+ var instance = record.instances[name];
286
+ var cbs = record.enteredCbs[name];
287
+ if (!instance || !cbs) { continue }
288
+ delete record.enteredCbs[name];
289
+ for (var i$1 = 0; i$1 < cbs.length; i$1++) {
290
+ if (!instance._isBeingDestroyed) { cbs[i$1](instance); }
291
+ }
292
+ }
293
+ }
294
+ }
295
+
296
+ var View = {
297
+ name: 'RouterView',
298
+ functional: true,
299
+ props: {
300
+ name: {
301
+ type: String,
302
+ default: 'default'
303
+ }
304
+ },
305
+ render: function render (_, ref) {
306
+ var props = ref.props;
307
+ var children = ref.children;
308
+ var parent = ref.parent;
309
+ var data = ref.data;
310
+
311
+ // used by devtools to display a router-view badge
312
+ data.routerView = true;
313
+
314
+ // directly use parent context's createElement() function
315
+ // so that components rendered by router-view can resolve named slots
316
+ var h = parent.$createElement;
317
+ var name = props.name;
318
+ var route = parent.$route;
319
+ var cache = parent._routerViewCache || (parent._routerViewCache = {});
320
+
321
+ // determine current view depth, also check to see if the tree
322
+ // has been toggled inactive but kept-alive.
323
+ var depth = 0;
324
+ var inactive = false;
325
+ while (parent && parent._routerRoot !== parent) {
326
+ var knodeData = parent.$knode ? parent.$knode.data : {};
327
+ if (knodeData.routerView) {
328
+ depth++;
329
+ }
330
+ if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
331
+ inactive = true;
332
+ }
333
+ parent = parent.$parent;
334
+ }
335
+ data.routerViewDepth = depth;
336
+
337
+ // render previous view if the tree is inactive and kept-alive
338
+ if (inactive) {
339
+ var cachedData = cache[name];
340
+ var cachedComponent = cachedData && cachedData.component;
341
+ if (cachedComponent) {
342
+ // #2301
343
+ // pass props
344
+ if (cachedData.configProps) {
345
+ fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
346
+ }
347
+ return h(cachedComponent, data, children)
348
+ } else {
349
+ // render previous empty view
350
+ return h()
351
+ }
352
+ }
353
+
354
+ var matched = route.matched[depth];
355
+ var component = matched && matched.components[name];
356
+
357
+ // render empty node if no matched route or no config component
358
+ if (!matched || !component) {
359
+ cache[name] = null;
360
+ return h()
361
+ }
362
+
363
+ // cache component
364
+ cache[name] = { component: component };
365
+
366
+ // attach instance registration hook
367
+ // this will be called in the instance's injected lifecycle hooks
368
+ data.registerRouteInstance = function (vm, val) {
369
+ // val could be undefined for unregistration
370
+ var current = matched.instances[name];
371
+ if (
372
+ (val && current !== vm) ||
373
+ (!val && current === vm)
374
+ ) {
375
+ matched.instances[name] = val;
376
+ }
377
+ }
378
+
379
+ // also register instance in prepatch hook
380
+ // in case the same component instance is reused across different routes
381
+ ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
382
+ matched.instances[name] = knode.componentInstance;
383
+ };
384
+
385
+ // register instance in init hook
386
+ // in case kept-alive component be actived when routes changed
387
+ data.hook.init = function (knode) {
388
+ if (knode.data.keepAlive &&
389
+ knode.componentInstance &&
390
+ knode.componentInstance !== matched.instances[name]
391
+ ) {
392
+ matched.instances[name] = knode.componentInstance;
393
+ }
394
+
395
+ // if the route transition has already been confirmed then we weren't
396
+ // able to call the cbs during confirmation as the component was not
397
+ // registered yet, so we call it here.
398
+ handleRouteEntered(route);
399
+ };
400
+
401
+ var configProps = matched.props && matched.props[name];
402
+ // save route and configProps in cache
403
+ if (configProps) {
404
+ extend(cache[name], {
405
+ route: route,
406
+ configProps: configProps
407
+ });
408
+ fillPropsinData(component, data, route, configProps);
409
+ }
410
+
411
+ return h(component, data, children)
412
+ }
413
+ };
414
+
415
+ function fillPropsinData (component, data, route, configProps) {
416
+ // resolve props
417
+ var propsToPass = data.props = resolveProps(route, configProps);
418
+ if (propsToPass) {
419
+ // clone to prevent mutation
420
+ propsToPass = data.props = extend({}, propsToPass);
421
+ // pass non-declared props as attrs
422
+ var attrs = data.attrs = data.attrs || {};
423
+ for (var key in propsToPass) {
424
+ if (!component.props || !(key in component.props)) {
425
+ attrs[key] = propsToPass[key];
426
+ delete propsToPass[key];
427
+ }
428
+ }
429
+ }
430
+ }
431
+
432
+ function resolveProps (route, config) {
433
+ switch (typeof config) {
434
+ case 'undefined':
435
+ return
436
+ case 'object':
437
+ return config
438
+ case 'function':
439
+ return config(route)
440
+ case 'boolean':
441
+ return config ? route.params : undefined
442
+ default:
443
+ if (process.env.NODE_ENV !== 'production') {
444
+ warn(
445
+ false,
446
+ "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
447
+ "expecting an object, function or boolean."
448
+ );
449
+ }
450
+ }
451
+ }
452
+
413
453
  /* */
414
454
 
415
455
  function resolvePath (
@@ -478,7 +518,7 @@ function parsePath (path) {
478
518
  }
479
519
 
480
520
  function cleanPath (path) {
481
- return path.replace(/\/\//g, '/')
521
+ return path.replace(/\/(?:\s*\/)+/g, '/')
482
522
  }
483
523
 
484
524
  var isarray = Array.isArray || function (arr) {
@@ -1018,6 +1058,10 @@ var eventTypes = [String, Array];
1018
1058
 
1019
1059
  var noop = function () {};
1020
1060
 
1061
+ var warnedCustomSlot;
1062
+ var warnedTagProp;
1063
+ var warnedEventProp;
1064
+
1021
1065
  var Link = {
1022
1066
  name: 'RouterLink',
1023
1067
  props: {
@@ -1029,7 +1073,9 @@ var Link = {
1029
1073
  type: String,
1030
1074
  default: 'a'
1031
1075
  },
1076
+ custom: Boolean,
1032
1077
  exact: Boolean,
1078
+ exactPath: Boolean,
1033
1079
  append: Boolean,
1034
1080
  replace: Boolean,
1035
1081
  activeClass: String,
@@ -1078,8 +1124,8 @@ var Link = {
1078
1124
  ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1079
1125
  : route;
1080
1126
 
1081
- classes[exactActiveClass] = isSameRoute(current, compareTarget);
1082
- classes[activeClass] = this.exact
1127
+ classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath);
1128
+ classes[activeClass] = this.exact || this.exactPath
1083
1129
  ? classes[exactActiveClass]
1084
1130
  : isIncludedRoute(current, compareTarget);
1085
1131
 
@@ -1118,19 +1164,40 @@ var Link = {
1118
1164
  });
1119
1165
 
1120
1166
  if (scopedSlot) {
1167
+ if (process.env.NODE_ENV !== 'production' && !this.custom) {
1168
+ !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');
1169
+ warnedCustomSlot = true;
1170
+ }
1121
1171
  if (scopedSlot.length === 1) {
1122
1172
  return scopedSlot[0]
1123
1173
  } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1124
1174
  if (process.env.NODE_ENV !== 'production') {
1125
1175
  warn(
1126
1176
  false,
1127
- ("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.")
1177
+ ("<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.")
1128
1178
  );
1129
1179
  }
1130
1180
  return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1131
1181
  }
1132
1182
  }
1133
1183
 
1184
+ if (process.env.NODE_ENV !== 'production') {
1185
+ if ('tag' in this.$options.propsData && !warnedTagProp) {
1186
+ warn(
1187
+ false,
1188
+ "<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."
1189
+ );
1190
+ warnedTagProp = true;
1191
+ }
1192
+ if ('event' in this.$options.propsData && !warnedEventProp) {
1193
+ warn(
1194
+ false,
1195
+ "<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."
1196
+ );
1197
+ warnedEventProp = true;
1198
+ }
1199
+ }
1200
+
1134
1201
  if (this.tag === 'a') {
1135
1202
  data.on = on;
1136
1203
  data.attrs = { href: href, 'aria-current': ariaCurrentValue };
@@ -1266,7 +1333,8 @@ function createRouteMap (
1266
1333
  routes,
1267
1334
  oldPathList,
1268
1335
  oldPathMap,
1269
- oldNameMap
1336
+ oldNameMap,
1337
+ parentRoute
1270
1338
  ) {
1271
1339
  // the path list is used to control path matching priority
1272
1340
  var pathList = oldPathList || [];
@@ -1276,7 +1344,7 @@ function createRouteMap (
1276
1344
  var nameMap = oldNameMap || Object.create(null);
1277
1345
 
1278
1346
  routes.forEach(function (route) {
1279
- addRouteRecord(pathList, pathMap, nameMap, route);
1347
+ addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
1280
1348
  });
1281
1349
 
1282
1350
  // ensure wildcard routes are always at the end
@@ -1325,6 +1393,14 @@ function addRouteRecord (
1325
1393
  path || name
1326
1394
  )) + " cannot be a " + "string id. Use an actual component instead."
1327
1395
  );
1396
+
1397
+ warn(
1398
+ // eslint-disable-next-line no-control-regex
1399
+ !/[^\u0000-\u007F]+/.test(path),
1400
+ "Route with path \"" + path + "\" contains unencoded characters, make sure " +
1401
+ "your path is correctly encoded before passing it to the router. Use " +
1402
+ "encodeURI to encode static segments of your path."
1403
+ );
1328
1404
  }
1329
1405
 
1330
1406
  var pathToRegexpOptions =
@@ -1339,7 +1415,13 @@ function addRouteRecord (
1339
1415
  path: normalizedPath,
1340
1416
  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1341
1417
  components: route.components || { default: route.component },
1418
+ alias: route.alias
1419
+ ? typeof route.alias === 'string'
1420
+ ? [route.alias]
1421
+ : route.alias
1422
+ : [],
1342
1423
  instances: {},
1424
+ enteredCbs: {},
1343
1425
  name: name,
1344
1426
  parent: parent,
1345
1427
  matchAs: matchAs,
@@ -1367,7 +1449,7 @@ function addRouteRecord (
1367
1449
  warn(
1368
1450
  false,
1369
1451
  "Named Route '" + (route.name) + "' has a default child route. " +
1370
- "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " +
1452
+ "When navigating to this named route (:to=\"{name: '" + (route.name) + "'}\"), " +
1371
1453
  "the default child route will not be rendered. Remove the name from " +
1372
1454
  "this route and use the name of the default child route for named " +
1373
1455
  "links instead."
@@ -1474,6 +1556,28 @@ function createMatcher (
1474
1556
  createRouteMap(routes, pathList, pathMap, nameMap);
1475
1557
  }
1476
1558
 
1559
+ function addRoute (parentOrRoute, route) {
1560
+ var parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined;
1561
+ // $flow-disable-line
1562
+ createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent);
1563
+
1564
+ // add aliases of parent
1565
+ if (parent && parent.alias.length) {
1566
+ createRouteMap(
1567
+ // $flow-disable-line route is defined if parent is
1568
+ parent.alias.map(function (alias) { return ({ path: alias, children: [route] }); }),
1569
+ pathList,
1570
+ pathMap,
1571
+ nameMap,
1572
+ parent
1573
+ );
1574
+ }
1575
+ }
1576
+
1577
+ function getRoutes () {
1578
+ return pathList.map(function (path) { return pathMap[path]; })
1579
+ }
1580
+
1477
1581
  function match (
1478
1582
  raw,
1479
1583
  currentRoute,
@@ -1620,6 +1724,8 @@ function createMatcher (
1620
1724
 
1621
1725
  return {
1622
1726
  match: match,
1727
+ addRoute: addRoute,
1728
+ getRoutes: getRoutes,
1623
1729
  addRoutes: addRoutes
1624
1730
  }
1625
1731
  }
@@ -1639,10 +1745,9 @@ function matchRoute (
1639
1745
 
1640
1746
  for (var i = 1, len = m.length; i < len; ++i) {
1641
1747
  var key = regex.keys[i - 1];
1642
- var val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i];
1643
1748
  if (key) {
1644
1749
  // Fix #1994: using * with props: true generates a param named 0
1645
- params[key.name || 'pathMatch'] = val;
1750
+ params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i];
1646
1751
  }
1647
1752
  }
1648
1753
 
@@ -1832,7 +1937,17 @@ function scrollToPosition (shouldScroll, position) {
1832
1937
  }
1833
1938
 
1834
1939
  if (position) {
1835
- window.scrollTo(position.x, position.y);
1940
+ // $flow-disable-line
1941
+ if ('scrollBehavior' in document.documentElement.style) {
1942
+ window.scrollTo({
1943
+ left: position.x,
1944
+ top: position.y,
1945
+ // $flow-disable-line
1946
+ behavior: shouldScroll.behavior
1947
+ });
1948
+ } else {
1949
+ window.scrollTo(position.x, position.y);
1950
+ }
1836
1951
  }
1837
1952
  }
1838
1953
 
@@ -1897,6 +2012,7 @@ function runQueue (queue, fn, cb) {
1897
2012
  step(0);
1898
2013
  }
1899
2014
 
2015
+ // When changing thing, also edit router.d.ts
1900
2016
  var NavigationFailureType = {
1901
2017
  redirected: 2,
1902
2018
  aborted: 4,
@@ -2130,6 +2246,7 @@ History.prototype.transitionTo = function transitionTo (
2130
2246
  var this$1 = this;
2131
2247
 
2132
2248
  var route;
2249
+ // catch redirect option
2133
2250
  try {
2134
2251
  route = this.router.match(location, this.current);
2135
2252
  } catch (e) {
@@ -2139,10 +2256,10 @@ History.prototype.transitionTo = function transitionTo (
2139
2256
  // Exception should still be thrown
2140
2257
  throw e
2141
2258
  }
2259
+ var prev = this.current;
2142
2260
  this.confirmTransition(
2143
2261
  route,
2144
2262
  function () {
2145
- var prev = this$1.current;
2146
2263
  this$1.updateRoute(route);
2147
2264
  onComplete && onComplete(route);
2148
2265
  this$1.ensureURL();
@@ -2163,16 +2280,13 @@ History.prototype.transitionTo = function transitionTo (
2163
2280
  onAbort(err);
2164
2281
  }
2165
2282
  if (err && !this$1.ready) {
2166
- this$1.ready = true;
2167
- // Initial redirection should still trigger the onReady onSuccess
2168
- if (!isNavigationFailure(err, NavigationFailureType.redirected)) {
2283
+ // Initial redirection should not mark the history as ready yet
2284
+ // because it's triggered by the redirection instead
2285
+ if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
2286
+ this$1.ready = true;
2169
2287
  this$1.readyErrorCbs.forEach(function (cb) {
2170
2288
  cb(err);
2171
2289
  });
2172
- } else {
2173
- this$1.readyCbs.forEach(function (cb) {
2174
- cb(route);
2175
- });
2176
2290
  }
2177
2291
  }
2178
2292
  }
@@ -2183,16 +2297,19 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2183
2297
  var this$1 = this;
2184
2298
 
2185
2299
  var current = this.current;
2300
+ this.pending = route;
2186
2301
  var abort = function (err) {
2187
- // changed after adding errors before that change,
2188
- // redirect and aborted navigation would produce an err == null
2302
+ // changed after adding errors
2303
+ // before that change, redirect and aborted navigation would produce an err == null
2189
2304
  if (!isNavigationFailure(err) && isError(err)) {
2190
2305
  if (this$1.errorCbs.length) {
2191
2306
  this$1.errorCbs.forEach(function (cb) {
2192
2307
  cb(err);
2193
2308
  });
2194
2309
  } else {
2195
- warn(false, 'uncaught error during route navigation:');
2310
+ if (process.env.NODE_ENV !== 'production') {
2311
+ warn(false, 'uncaught error during route navigation:');
2312
+ }
2196
2313
  console.error(err);
2197
2314
  }
2198
2315
  }
@@ -2207,6 +2324,9 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2207
2324
  route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
2208
2325
  ) {
2209
2326
  this.ensureURL();
2327
+ if (route.hash) {
2328
+ handleScroll(this.router, current, route, false);
2329
+ }
2210
2330
  return abort(createNavigationDuplicatedError(current, route))
2211
2331
  }
2212
2332
 
@@ -2231,7 +2351,6 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2231
2351
  resolveAsyncComponents(activated)
2232
2352
  );
2233
2353
 
2234
- this.pending = route;
2235
2354
  var iterator = function (hook, next) {
2236
2355
  if (this$1.pending !== route) {
2237
2356
  return abort(createNavigationCancelledError(current, route))
@@ -2268,11 +2387,9 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2268
2387
  };
2269
2388
 
2270
2389
  runQueue(queue, iterator, function () {
2271
- var postEnterCbs = [];
2272
- var isValid = function () { return this$1.current === route; };
2273
2390
  // wait until async components are resolved before
2274
2391
  // extracting in-component enter guards
2275
- var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
2392
+ var enterGuards = extractEnterGuards(activated);
2276
2393
  var queue = enterGuards.concat(this$1.router.resolveHooks);
2277
2394
  runQueue(queue, iterator, function () {
2278
2395
  if (this$1.pending !== route) {
@@ -2282,9 +2399,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2282
2399
  onComplete(route);
2283
2400
  if (this$1.router.app) {
2284
2401
  this$1.router.app.$nextTick(function () {
2285
- postEnterCbs.forEach(function (cb) {
2286
- cb();
2287
- });
2402
+ handleRouteEntered(route);
2288
2403
  });
2289
2404
  }
2290
2405
  });
@@ -2300,11 +2415,16 @@ History.prototype.setupListeners = function setupListeners () {
2300
2415
  // Default implementation is empty
2301
2416
  };
2302
2417
 
2303
- History.prototype.teardownListeners = function teardownListeners () {
2418
+ History.prototype.teardown = function teardown () {
2419
+ // clean up event listeners
2304
2420
  this.listeners.forEach(function (cleanupListener) {
2305
2421
  cleanupListener();
2306
2422
  });
2307
2423
  this.listeners = [];
2424
+
2425
+ // reset current history route
2426
+ this.current = START;
2427
+ this.pending = null;
2308
2428
  };
2309
2429
 
2310
2430
  function normalizeBase (base) {
@@ -2390,15 +2510,13 @@ function bindGuard (guard, instance) {
2390
2510
  }
2391
2511
 
2392
2512
  function extractEnterGuards (
2393
- activated,
2394
- cbs,
2395
- isValid
2513
+ activated
2396
2514
  ) {
2397
2515
  return extractGuards(
2398
2516
  activated,
2399
2517
  'beforeRouteEnter',
2400
2518
  function (guard, _, match, key) {
2401
- return bindEnterGuard(guard, match, key, cbs, isValid)
2519
+ return bindEnterGuard(guard, match, key)
2402
2520
  }
2403
2521
  )
2404
2522
  }
@@ -2406,45 +2524,21 @@ function extractEnterGuards (
2406
2524
  function bindEnterGuard (
2407
2525
  guard,
2408
2526
  match,
2409
- key,
2410
- cbs,
2411
- isValid
2527
+ key
2412
2528
  ) {
2413
2529
  return function routeEnterGuard (to, from, next) {
2414
2530
  return guard(to, from, function (cb) {
2415
2531
  if (typeof cb === 'function') {
2416
- cbs.push(function () {
2417
- // #750
2418
- // if a router-view is wrapped with an out-in transition,
2419
- // the instance may not have been registered at this time.
2420
- // we will need to poll for registration until current route
2421
- // is no longer valid.
2422
- poll(cb, match.instances, key, isValid);
2423
- });
2532
+ if (!match.enteredCbs[key]) {
2533
+ match.enteredCbs[key] = [];
2534
+ }
2535
+ match.enteredCbs[key].push(cb);
2424
2536
  }
2425
2537
  next(cb);
2426
2538
  })
2427
2539
  }
2428
2540
  }
2429
2541
 
2430
- function poll (
2431
- cb, // somehow flow cannot infer this is a function
2432
- instances,
2433
- key,
2434
- isValid
2435
- ) {
2436
- if (
2437
- instances[key] &&
2438
- !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
2439
- ) {
2440
- cb(instances[key]);
2441
- } else if (isValid()) {
2442
- setTimeout(function () {
2443
- poll(cb, instances, key, isValid);
2444
- }, 16);
2445
- }
2446
- }
2447
-
2448
2542
  /* */
2449
2543
 
2450
2544
  var HTML5History = /*@__PURE__*/(function (History) {
@@ -2538,8 +2632,13 @@ var HTML5History = /*@__PURE__*/(function (History) {
2538
2632
  }(History));
2539
2633
 
2540
2634
  function getLocation (base) {
2541
- var path = decodeURI(window.location.pathname);
2542
- if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
2635
+ var path = window.location.pathname;
2636
+ var pathLowerCase = path.toLowerCase();
2637
+ var baseLowerCase = base.toLowerCase();
2638
+ // base="/a" shouldn't turn path="/app" into "/a/pp"
2639
+ // so we ensure the trailing slash in the base
2640
+ if (base && ((pathLowerCase === baseLowerCase) ||
2641
+ (pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
2543
2642
  path = path.slice(base.length);
2544
2643
  }
2545
2644
  return (path || '/') + window.location.search + window.location.hash
@@ -2678,17 +2777,6 @@ function getHash () {
2678
2777
  if (index < 0) { return '' }
2679
2778
 
2680
2779
  href = href.slice(index + 1);
2681
- // decode the hash but not the search or hash
2682
- // as search(query) is already decoded
2683
- var searchIndex = href.indexOf('?');
2684
- if (searchIndex < 0) {
2685
- var hashIndex = href.indexOf('#');
2686
- if (hashIndex > -1) {
2687
- href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2688
- } else { href = decodeURI(href); }
2689
- } else {
2690
- href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2691
- }
2692
2780
 
2693
2781
  return href
2694
2782
  }
@@ -2767,8 +2855,12 @@ var AbstractHistory = /*@__PURE__*/(function (History) {
2767
2855
  this.confirmTransition(
2768
2856
  route,
2769
2857
  function () {
2858
+ var prev = this$1.current;
2770
2859
  this$1.index = targetIndex;
2771
2860
  this$1.updateRoute(route);
2861
+ this$1.router.afterHooks.forEach(function (hook) {
2862
+ hook && hook(route, prev);
2863
+ });
2772
2864
  },
2773
2865
  function (err) {
2774
2866
  if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
@@ -2795,6 +2887,9 @@ var AbstractHistory = /*@__PURE__*/(function (History) {
2795
2887
  var KduRouter = function KduRouter (options) {
2796
2888
  if ( options === void 0 ) options = {};
2797
2889
 
2890
+ if (process.env.NODE_ENV !== 'production') {
2891
+ warn(this instanceof KduRouter, "Router must be called with the new operator.");
2892
+ }
2798
2893
  this.app = null;
2799
2894
  this.apps = [];
2800
2895
  this.options = options;
@@ -2862,10 +2957,7 @@ KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2862
2957
  // we do not release the router so it can be reused
2863
2958
  if (this$1.app === app) { this$1.app = this$1.apps[0] || null; }
2864
2959
 
2865
- if (!this$1.app) {
2866
- // clean up event listeners
2867
- this$1.history.teardownListeners();
2868
- }
2960
+ if (!this$1.app) { this$1.history.teardown(); }
2869
2961
  });
2870
2962
 
2871
2963
  // main app previously initialized
@@ -3004,7 +3096,21 @@ KduRouter.prototype.resolve = function resolve (
3004
3096
  }
3005
3097
  };
3006
3098
 
3099
+ KduRouter.prototype.getRoutes = function getRoutes () {
3100
+ return this.matcher.getRoutes()
3101
+ };
3102
+
3103
+ KduRouter.prototype.addRoute = function addRoute (parentOrRoute, route) {
3104
+ this.matcher.addRoute(parentOrRoute, route);
3105
+ if (this.history.current !== START) {
3106
+ this.history.transitionTo(this.history.getCurrentLocation());
3107
+ }
3108
+ };
3109
+
3007
3110
  KduRouter.prototype.addRoutes = function addRoutes (routes) {
3111
+ if (process.env.NODE_ENV !== 'production') {
3112
+ warn(false, 'router.addRoutes() is deprecated and has been removed in Kdu Router 4. Use router.addRoute() instead.');
3113
+ }
3008
3114
  this.matcher.addRoutes(routes);
3009
3115
  if (this.history.current !== START) {
3010
3116
  this.history.transitionTo(this.history.getCurrentLocation());
@@ -3027,9 +3133,10 @@ function createHref (base, fullPath, mode) {
3027
3133
  }
3028
3134
 
3029
3135
  KduRouter.install = install;
3030
- KduRouter.version = '3.4.0-beta.0';
3136
+ KduRouter.version = '3.5.4';
3031
3137
  KduRouter.isNavigationFailure = isNavigationFailure;
3032
3138
  KduRouter.NavigationFailureType = NavigationFailureType;
3139
+ KduRouter.START_LOCATION = START;
3033
3140
 
3034
3141
  if (inBrowser && window.Kdu) {
3035
3142
  window.Kdu.use(KduRouter);