kdu-router 3.1.7 → 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.1.7
2
+ * kdu-router v3.5.4
3
3
  * (c) 2022 NKDuy
4
4
  * @license MIT
5
5
  */
@@ -14,23 +14,11 @@ function assert (condition, message) {
14
14
  }
15
15
 
16
16
  function warn (condition, message) {
17
- if (process.env.NODE_ENV !== 'production' && !condition) {
17
+ if (!condition) {
18
18
  typeof console !== 'undefined' && console.warn(("[kdu-router] " + message));
19
19
  }
20
20
  }
21
21
 
22
- function isError (err) {
23
- return Object.prototype.toString.call(err).indexOf('Error') > -1
24
- }
25
-
26
- function isExtendedError (constructor, err) {
27
- return (
28
- err instanceof constructor ||
29
- // _name is to support IE9 too
30
- (err && (err.name === constructor.name || err._name === constructor._name))
31
- )
32
- }
33
-
34
22
  function extend (a, b) {
35
23
  for (var key in b) {
36
24
  a[key] = b[key];
@@ -38,158 +26,6 @@ function extend (a, b) {
38
26
  return a
39
27
  }
40
28
 
41
- var View = {
42
- name: 'RouterView',
43
- functional: true,
44
- props: {
45
- name: {
46
- type: String,
47
- default: 'default'
48
- }
49
- },
50
- render: function render (_, ref) {
51
- var props = ref.props;
52
- var children = ref.children;
53
- var parent = ref.parent;
54
- var data = ref.data;
55
-
56
- // used by devtools to display a router-view badge
57
- data.routerView = true;
58
-
59
- // directly use parent context's createElement() function
60
- // so that components rendered by router-view can resolve named slots
61
- var h = parent.$createElement;
62
- var name = props.name;
63
- var route = parent.$route;
64
- var cache = parent._routerViewCache || (parent._routerViewCache = {});
65
-
66
- // determine current view depth, also check to see if the tree
67
- // has been toggled inactive but kept-alive.
68
- var depth = 0;
69
- var inactive = false;
70
- while (parent && parent._routerRoot !== parent) {
71
- var knodeData = parent.$knode ? parent.$knode.data : {};
72
- if (knodeData.routerView) {
73
- depth++;
74
- }
75
- if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
76
- inactive = true;
77
- }
78
- parent = parent.$parent;
79
- }
80
- data.routerViewDepth = depth;
81
-
82
- // render previous view if the tree is inactive and kept-alive
83
- if (inactive) {
84
- var cachedData = cache[name];
85
- var cachedComponent = cachedData && cachedData.component;
86
- if (cachedComponent) {
87
- // #2301
88
- // pass props
89
- if (cachedData.configProps) {
90
- fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
91
- }
92
- return h(cachedComponent, data, children)
93
- } else {
94
- // render previous empty view
95
- return h()
96
- }
97
- }
98
-
99
- var matched = route.matched[depth];
100
- var component = matched && matched.components[name];
101
-
102
- // render empty node if no matched route or no config component
103
- if (!matched || !component) {
104
- cache[name] = null;
105
- return h()
106
- }
107
-
108
- // cache component
109
- cache[name] = { component: component };
110
-
111
- // attach instance registration hook
112
- // this will be called in the instance's injected lifecycle hooks
113
- data.registerRouteInstance = function (vm, val) {
114
- // val could be undefined for unregistration
115
- var current = matched.instances[name];
116
- if (
117
- (val && current !== vm) ||
118
- (!val && current === vm)
119
- ) {
120
- matched.instances[name] = val;
121
- }
122
- }
123
-
124
- // also register instance in prepatch hook
125
- // in case the same component instance is reused across different routes
126
- ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
127
- matched.instances[name] = knode.componentInstance;
128
- };
129
-
130
- // register instance in init hook
131
- // in case kept-alive component be actived when routes changed
132
- data.hook.init = function (knode) {
133
- if (knode.data.keepAlive &&
134
- knode.componentInstance &&
135
- knode.componentInstance !== matched.instances[name]
136
- ) {
137
- matched.instances[name] = knode.componentInstance;
138
- }
139
- };
140
-
141
- var configProps = matched.props && matched.props[name];
142
- // save route and configProps in cachce
143
- if (configProps) {
144
- extend(cache[name], {
145
- route: route,
146
- configProps: configProps
147
- });
148
- fillPropsinData(component, data, route, configProps);
149
- }
150
-
151
- return h(component, data, children)
152
- }
153
- };
154
-
155
- function fillPropsinData (component, data, route, configProps) {
156
- // resolve props
157
- var propsToPass = data.props = resolveProps(route, configProps);
158
- if (propsToPass) {
159
- // clone to prevent mutation
160
- propsToPass = data.props = extend({}, propsToPass);
161
- // pass non-declared props as attrs
162
- var attrs = data.attrs = data.attrs || {};
163
- for (var key in propsToPass) {
164
- if (!component.props || !(key in component.props)) {
165
- attrs[key] = propsToPass[key];
166
- delete propsToPass[key];
167
- }
168
- }
169
- }
170
- }
171
-
172
- function resolveProps (route, config) {
173
- switch (typeof config) {
174
- case 'undefined':
175
- return
176
- case 'object':
177
- return config
178
- case 'function':
179
- return config(route)
180
- case 'boolean':
181
- return config ? route.params : undefined
182
- default:
183
- if (process.env.NODE_ENV !== 'production') {
184
- warn(
185
- false,
186
- "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
187
- "expecting an object, function or boolean."
188
- );
189
- }
190
- }
191
- }
192
-
193
29
  /* */
194
30
 
195
31
  var encodeReserveRE = /[!'()*]/g;
@@ -200,10 +36,19 @@ var commaRE = /%2C/g;
200
36
  // - escapes [!'()*]
201
37
  // - preserve commas
202
38
  var encode = function (str) { return encodeURIComponent(str)
203
- .replace(encodeReserveRE, encodeReserveReplacer)
204
- .replace(commaRE, ','); };
39
+ .replace(encodeReserveRE, encodeReserveReplacer)
40
+ .replace(commaRE, ','); };
205
41
 
206
- var decode = decodeURIComponent;
42
+ function decode (str) {
43
+ try {
44
+ return decodeURIComponent(str)
45
+ } catch (err) {
46
+ if (process.env.NODE_ENV !== 'production') {
47
+ warn(false, ("Error decoding \"" + str + "\". Leaving it intact."));
48
+ }
49
+ }
50
+ return str
51
+ }
207
52
 
208
53
  function resolveQuery (
209
54
  query,
@@ -221,11 +66,16 @@ function resolveQuery (
221
66
  parsedQuery = {};
222
67
  }
223
68
  for (var key in extraQuery) {
224
- parsedQuery[key] = extraQuery[key];
69
+ var value = extraQuery[key];
70
+ parsedQuery[key] = Array.isArray(value)
71
+ ? value.map(castQueryParamValue)
72
+ : castQueryParamValue(value);
225
73
  }
226
74
  return parsedQuery
227
75
  }
228
76
 
77
+ var castQueryParamValue = function (value) { return (value == null || typeof value === 'object' ? value : String(value)); };
78
+
229
79
  function parseQuery (query) {
230
80
  var res = {};
231
81
 
@@ -238,9 +88,7 @@ function parseQuery (query) {
238
88
  query.split('&').forEach(function (param) {
239
89
  var parts = param.replace(/\+/g, ' ').split('=');
240
90
  var key = decode(parts.shift());
241
- var val = parts.length > 0
242
- ? decode(parts.join('='))
243
- : null;
91
+ var val = parts.length > 0 ? decode(parts.join('=')) : null;
244
92
 
245
93
  if (res[key] === undefined) {
246
94
  res[key] = val;
@@ -255,34 +103,39 @@ function parseQuery (query) {
255
103
  }
256
104
 
257
105
  function stringifyQuery (obj) {
258
- var res = obj ? Object.keys(obj).map(function (key) {
259
- var val = obj[key];
106
+ var res = obj
107
+ ? Object.keys(obj)
108
+ .map(function (key) {
109
+ var val = obj[key];
260
110
 
261
- if (val === undefined) {
262
- return ''
263
- }
264
-
265
- if (val === null) {
266
- return encode(key)
267
- }
111
+ if (val === undefined) {
112
+ return ''
113
+ }
268
114
 
269
- if (Array.isArray(val)) {
270
- var result = [];
271
- val.forEach(function (val2) {
272
- if (val2 === undefined) {
273
- return
115
+ if (val === null) {
116
+ return encode(key)
274
117
  }
275
- if (val2 === null) {
276
- result.push(encode(key));
277
- } else {
278
- result.push(encode(key) + '=' + encode(val2));
118
+
119
+ if (Array.isArray(val)) {
120
+ var result = [];
121
+ val.forEach(function (val2) {
122
+ if (val2 === undefined) {
123
+ return
124
+ }
125
+ if (val2 === null) {
126
+ result.push(encode(key));
127
+ } else {
128
+ result.push(encode(key) + '=' + encode(val2));
129
+ }
130
+ });
131
+ return result.join('&')
279
132
  }
280
- });
281
- return result.join('&')
282
- }
283
133
 
284
- return encode(key) + '=' + encode(val)
285
- }).filter(function (x) { return x.length > 0; }).join('&') : null;
134
+ return encode(key) + '=' + encode(val)
135
+ })
136
+ .filter(function (x) { return x.length > 0; })
137
+ .join('&')
138
+ : null;
286
139
  return res ? ("?" + res) : ''
287
140
  }
288
141
 
@@ -359,68 +212,244 @@ function getFullPath (
359
212
  return (path || '/') + stringify(query) + hash
360
213
  }
361
214
 
362
- function isSameRoute (a, b) {
215
+ function isSameRoute (a, b, onlyPath) {
363
216
  if (b === START) {
364
217
  return a === b
365
218
  } else if (!b) {
366
219
  return false
367
220
  } else if (a.path && b.path) {
368
- return (
369
- a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
221
+ return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
370
222
  a.hash === b.hash &&
371
- isObjectEqual(a.query, b.query)
372
- )
223
+ isObjectEqual(a.query, b.query))
373
224
  } else if (a.name && b.name) {
374
225
  return (
375
226
  a.name === b.name &&
376
- a.hash === b.hash &&
227
+ (onlyPath || (
228
+ a.hash === b.hash &&
377
229
  isObjectEqual(a.query, b.query) &&
378
- isObjectEqual(a.params, b.params)
230
+ isObjectEqual(a.params, b.params))
231
+ )
379
232
  )
380
233
  } else {
381
234
  return false
382
235
  }
383
236
  }
384
237
 
385
- function isObjectEqual (a, b) {
386
- if ( a === void 0 ) a = {};
387
- if ( b === void 0 ) b = {};
238
+ function isObjectEqual (a, b) {
239
+ if ( a === void 0 ) a = {};
240
+ if ( b === void 0 ) b = {};
241
+
242
+ // handle null value #1566
243
+ if (!a || !b) { return a === b }
244
+ var aKeys = Object.keys(a).sort();
245
+ var bKeys = Object.keys(b).sort();
246
+ if (aKeys.length !== bKeys.length) {
247
+ return false
248
+ }
249
+ return aKeys.every(function (key, i) {
250
+ var aVal = a[key];
251
+ var bKey = bKeys[i];
252
+ if (bKey !== key) { return false }
253
+ var bVal = b[key];
254
+ // query values can be null and undefined
255
+ if (aVal == null || bVal == null) { return aVal === bVal }
256
+ // check nested equality
257
+ if (typeof aVal === 'object' && typeof bVal === 'object') {
258
+ return isObjectEqual(aVal, bVal)
259
+ }
260
+ return String(aVal) === String(bVal)
261
+ })
262
+ }
263
+
264
+ function isIncludedRoute (current, target) {
265
+ return (
266
+ current.path.replace(trailingSlashRE, '/').indexOf(
267
+ target.path.replace(trailingSlashRE, '/')
268
+ ) === 0 &&
269
+ (!target.hash || current.hash === target.hash) &&
270
+ queryIncludes(current.query, target.query)
271
+ )
272
+ }
273
+
274
+ function queryIncludes (current, target) {
275
+ for (var key in target) {
276
+ if (!(key in current)) {
277
+ return false
278
+ }
279
+ }
280
+ return true
281
+ }
282
+
283
+ function handleRouteEntered (route) {
284
+ for (var i = 0; i < route.matched.length; i++) {
285
+ var record = route.matched[i];
286
+ for (var name in record.instances) {
287
+ var instance = record.instances[name];
288
+ var cbs = record.enteredCbs[name];
289
+ if (!instance || !cbs) { continue }
290
+ delete record.enteredCbs[name];
291
+ for (var i$1 = 0; i$1 < cbs.length; i$1++) {
292
+ if (!instance._isBeingDestroyed) { cbs[i$1](instance); }
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ var View = {
299
+ name: 'RouterView',
300
+ functional: true,
301
+ props: {
302
+ name: {
303
+ type: String,
304
+ default: 'default'
305
+ }
306
+ },
307
+ render: function render (_, ref) {
308
+ var props = ref.props;
309
+ var children = ref.children;
310
+ var parent = ref.parent;
311
+ var data = ref.data;
312
+
313
+ // used by devtools to display a router-view badge
314
+ data.routerView = true;
315
+
316
+ // directly use parent context's createElement() function
317
+ // so that components rendered by router-view can resolve named slots
318
+ var h = parent.$createElement;
319
+ var name = props.name;
320
+ var route = parent.$route;
321
+ var cache = parent._routerViewCache || (parent._routerViewCache = {});
322
+
323
+ // determine current view depth, also check to see if the tree
324
+ // has been toggled inactive but kept-alive.
325
+ var depth = 0;
326
+ var inactive = false;
327
+ while (parent && parent._routerRoot !== parent) {
328
+ var knodeData = parent.$knode ? parent.$knode.data : {};
329
+ if (knodeData.routerView) {
330
+ depth++;
331
+ }
332
+ if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
333
+ inactive = true;
334
+ }
335
+ parent = parent.$parent;
336
+ }
337
+ data.routerViewDepth = depth;
338
+
339
+ // render previous view if the tree is inactive and kept-alive
340
+ if (inactive) {
341
+ var cachedData = cache[name];
342
+ var cachedComponent = cachedData && cachedData.component;
343
+ if (cachedComponent) {
344
+ // #2301
345
+ // pass props
346
+ if (cachedData.configProps) {
347
+ fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
348
+ }
349
+ return h(cachedComponent, data, children)
350
+ } else {
351
+ // render previous empty view
352
+ return h()
353
+ }
354
+ }
355
+
356
+ var matched = route.matched[depth];
357
+ var component = matched && matched.components[name];
358
+
359
+ // render empty node if no matched route or no config component
360
+ if (!matched || !component) {
361
+ cache[name] = null;
362
+ return h()
363
+ }
364
+
365
+ // cache component
366
+ cache[name] = { component: component };
367
+
368
+ // attach instance registration hook
369
+ // this will be called in the instance's injected lifecycle hooks
370
+ data.registerRouteInstance = function (vm, val) {
371
+ // val could be undefined for unregistration
372
+ var current = matched.instances[name];
373
+ if (
374
+ (val && current !== vm) ||
375
+ (!val && current === vm)
376
+ ) {
377
+ matched.instances[name] = val;
378
+ }
379
+ }
380
+
381
+ // also register instance in prepatch hook
382
+ // in case the same component instance is reused across different routes
383
+ ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
384
+ matched.instances[name] = knode.componentInstance;
385
+ };
386
+
387
+ // register instance in init hook
388
+ // in case kept-alive component be actived when routes changed
389
+ data.hook.init = function (knode) {
390
+ if (knode.data.keepAlive &&
391
+ knode.componentInstance &&
392
+ knode.componentInstance !== matched.instances[name]
393
+ ) {
394
+ matched.instances[name] = knode.componentInstance;
395
+ }
396
+
397
+ // if the route transition has already been confirmed then we weren't
398
+ // able to call the cbs during confirmation as the component was not
399
+ // registered yet, so we call it here.
400
+ handleRouteEntered(route);
401
+ };
388
402
 
389
- // handle null value #1566
390
- if (!a || !b) { return a === b }
391
- var aKeys = Object.keys(a);
392
- var bKeys = Object.keys(b);
393
- if (aKeys.length !== bKeys.length) {
394
- return false
395
- }
396
- return aKeys.every(function (key) {
397
- var aVal = a[key];
398
- var bVal = b[key];
399
- // check nested equality
400
- if (typeof aVal === 'object' && typeof bVal === 'object') {
401
- return isObjectEqual(aVal, bVal)
403
+ var configProps = matched.props && matched.props[name];
404
+ // save route and configProps in cache
405
+ if (configProps) {
406
+ extend(cache[name], {
407
+ route: route,
408
+ configProps: configProps
409
+ });
410
+ fillPropsinData(component, data, route, configProps);
402
411
  }
403
- return String(aVal) === String(bVal)
404
- })
405
- }
406
412
 
407
- function isIncludedRoute (current, target) {
408
- return (
409
- current.path.replace(trailingSlashRE, '/').indexOf(
410
- target.path.replace(trailingSlashRE, '/')
411
- ) === 0 &&
412
- (!target.hash || current.hash === target.hash) &&
413
- queryIncludes(current.query, target.query)
414
- )
415
- }
413
+ return h(component, data, children)
414
+ }
415
+ };
416
416
 
417
- function queryIncludes (current, target) {
418
- for (var key in target) {
419
- if (!(key in current)) {
420
- return false
417
+ function fillPropsinData (component, data, route, configProps) {
418
+ // resolve props
419
+ var propsToPass = data.props = resolveProps(route, configProps);
420
+ if (propsToPass) {
421
+ // clone to prevent mutation
422
+ propsToPass = data.props = extend({}, propsToPass);
423
+ // pass non-declared props as attrs
424
+ var attrs = data.attrs = data.attrs || {};
425
+ for (var key in propsToPass) {
426
+ if (!component.props || !(key in component.props)) {
427
+ attrs[key] = propsToPass[key];
428
+ delete propsToPass[key];
429
+ }
421
430
  }
422
431
  }
423
- return true
432
+ }
433
+
434
+ function resolveProps (route, config) {
435
+ switch (typeof config) {
436
+ case 'undefined':
437
+ return
438
+ case 'object':
439
+ return config
440
+ case 'function':
441
+ return config(route)
442
+ case 'boolean':
443
+ return config ? route.params : undefined
444
+ default:
445
+ if (process.env.NODE_ENV !== 'production') {
446
+ warn(
447
+ false,
448
+ "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
449
+ "expecting an object, function or boolean."
450
+ );
451
+ }
452
+ }
424
453
  }
425
454
 
426
455
  /* */
@@ -491,7 +520,7 @@ function parsePath (path) {
491
520
  }
492
521
 
493
522
  function cleanPath (path) {
494
- return path.replace(/\/\//g, '/')
523
+ return path.replace(/\/(?:\s*\/)+/g, '/')
495
524
  }
496
525
 
497
526
  var isarray = Array.isArray || function (arr) {
@@ -1031,6 +1060,10 @@ var eventTypes = [String, Array];
1031
1060
 
1032
1061
  var noop = function () {};
1033
1062
 
1063
+ var warnedCustomSlot;
1064
+ var warnedTagProp;
1065
+ var warnedEventProp;
1066
+
1034
1067
  var Link = {
1035
1068
  name: 'RouterLink',
1036
1069
  props: {
@@ -1042,11 +1075,17 @@ var Link = {
1042
1075
  type: String,
1043
1076
  default: 'a'
1044
1077
  },
1078
+ custom: Boolean,
1045
1079
  exact: Boolean,
1080
+ exactPath: Boolean,
1046
1081
  append: Boolean,
1047
1082
  replace: Boolean,
1048
1083
  activeClass: String,
1049
1084
  exactActiveClass: String,
1085
+ ariaCurrentValue: {
1086
+ type: String,
1087
+ default: 'page'
1088
+ },
1050
1089
  event: {
1051
1090
  type: eventTypes,
1052
1091
  default: 'click'
@@ -1087,11 +1126,13 @@ var Link = {
1087
1126
  ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1088
1127
  : route;
1089
1128
 
1090
- classes[exactActiveClass] = isSameRoute(current, compareTarget);
1091
- classes[activeClass] = this.exact
1129
+ classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath);
1130
+ classes[activeClass] = this.exact || this.exactPath
1092
1131
  ? classes[exactActiveClass]
1093
1132
  : isIncludedRoute(current, compareTarget);
1094
1133
 
1134
+ var ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null;
1135
+
1095
1136
  var handler = function (e) {
1096
1137
  if (guardEvent(e)) {
1097
1138
  if (this$1.replace) {
@@ -1125,22 +1166,43 @@ var Link = {
1125
1166
  });
1126
1167
 
1127
1168
  if (scopedSlot) {
1169
+ if (process.env.NODE_ENV !== 'production' && !this.custom) {
1170
+ !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');
1171
+ warnedCustomSlot = true;
1172
+ }
1128
1173
  if (scopedSlot.length === 1) {
1129
1174
  return scopedSlot[0]
1130
1175
  } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1131
1176
  if (process.env.NODE_ENV !== 'production') {
1132
1177
  warn(
1133
1178
  false,
1134
- ("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.")
1179
+ ("<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.")
1135
1180
  );
1136
1181
  }
1137
1182
  return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1138
1183
  }
1139
1184
  }
1140
1185
 
1186
+ if (process.env.NODE_ENV !== 'production') {
1187
+ if ('tag' in this.$options.propsData && !warnedTagProp) {
1188
+ warn(
1189
+ false,
1190
+ "<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."
1191
+ );
1192
+ warnedTagProp = true;
1193
+ }
1194
+ if ('event' in this.$options.propsData && !warnedEventProp) {
1195
+ warn(
1196
+ false,
1197
+ "<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."
1198
+ );
1199
+ warnedEventProp = true;
1200
+ }
1201
+ }
1202
+
1141
1203
  if (this.tag === 'a') {
1142
1204
  data.on = on;
1143
- data.attrs = { href: href };
1205
+ data.attrs = { href: href, 'aria-current': ariaCurrentValue };
1144
1206
  } else {
1145
1207
  // find the first <a> child and apply listener and href
1146
1208
  var a = findAnchor(this.$slots.default);
@@ -1168,6 +1230,7 @@ var Link = {
1168
1230
 
1169
1231
  var aAttrs = (a.data.attrs = extend({}, a.data.attrs));
1170
1232
  aAttrs.href = href;
1233
+ aAttrs['aria-current'] = ariaCurrentValue;
1171
1234
  } else {
1172
1235
  // doesn't have <a> child, apply listener to self
1173
1236
  data.on = on;
@@ -1272,7 +1335,8 @@ function createRouteMap (
1272
1335
  routes,
1273
1336
  oldPathList,
1274
1337
  oldPathMap,
1275
- oldNameMap
1338
+ oldNameMap,
1339
+ parentRoute
1276
1340
  ) {
1277
1341
  // the path list is used to control path matching priority
1278
1342
  var pathList = oldPathList || [];
@@ -1282,7 +1346,7 @@ function createRouteMap (
1282
1346
  var nameMap = oldNameMap || Object.create(null);
1283
1347
 
1284
1348
  routes.forEach(function (route) {
1285
- addRouteRecord(pathList, pathMap, nameMap, route);
1349
+ addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
1286
1350
  });
1287
1351
 
1288
1352
  // ensure wildcard routes are always at the end
@@ -1331,6 +1395,14 @@ function addRouteRecord (
1331
1395
  path || name
1332
1396
  )) + " cannot be a " + "string id. Use an actual component instead."
1333
1397
  );
1398
+
1399
+ warn(
1400
+ // eslint-disable-next-line no-control-regex
1401
+ !/[^\u0000-\u007F]+/.test(path),
1402
+ "Route with path \"" + path + "\" contains unencoded characters, make sure " +
1403
+ "your path is correctly encoded before passing it to the router. Use " +
1404
+ "encodeURI to encode static segments of your path."
1405
+ );
1334
1406
  }
1335
1407
 
1336
1408
  var pathToRegexpOptions =
@@ -1345,7 +1417,13 @@ function addRouteRecord (
1345
1417
  path: normalizedPath,
1346
1418
  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1347
1419
  components: route.components || { default: route.component },
1420
+ alias: route.alias
1421
+ ? typeof route.alias === 'string'
1422
+ ? [route.alias]
1423
+ : route.alias
1424
+ : [],
1348
1425
  instances: {},
1426
+ enteredCbs: {},
1349
1427
  name: name,
1350
1428
  parent: parent,
1351
1429
  matchAs: matchAs,
@@ -1373,7 +1451,7 @@ function addRouteRecord (
1373
1451
  warn(
1374
1452
  false,
1375
1453
  "Named Route '" + (route.name) + "' has a default child route. " +
1376
- "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " +
1454
+ "When navigating to this named route (:to=\"{name: '" + (route.name) + "'}\"), " +
1377
1455
  "the default child route will not be rendered. Remove the name from " +
1378
1456
  "this route and use the name of the default child route for named " +
1379
1457
  "links instead."
@@ -1480,6 +1558,28 @@ function createMatcher (
1480
1558
  createRouteMap(routes, pathList, pathMap, nameMap);
1481
1559
  }
1482
1560
 
1561
+ function addRoute (parentOrRoute, route) {
1562
+ var parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined;
1563
+ // $flow-disable-line
1564
+ createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent);
1565
+
1566
+ // add aliases of parent
1567
+ if (parent && parent.alias.length) {
1568
+ createRouteMap(
1569
+ // $flow-disable-line route is defined if parent is
1570
+ parent.alias.map(function (alias) { return ({ path: alias, children: [route] }); }),
1571
+ pathList,
1572
+ pathMap,
1573
+ nameMap,
1574
+ parent
1575
+ );
1576
+ }
1577
+ }
1578
+
1579
+ function getRoutes () {
1580
+ return pathList.map(function (path) { return pathMap[path]; })
1581
+ }
1582
+
1483
1583
  function match (
1484
1584
  raw,
1485
1585
  currentRoute,
@@ -1626,6 +1726,8 @@ function createMatcher (
1626
1726
 
1627
1727
  return {
1628
1728
  match: match,
1729
+ addRoute: addRoute,
1730
+ getRoutes: getRoutes,
1629
1731
  addRoutes: addRoutes
1630
1732
  }
1631
1733
  }
@@ -1645,10 +1747,9 @@ function matchRoute (
1645
1747
 
1646
1748
  for (var i = 1, len = m.length; i < len; ++i) {
1647
1749
  var key = regex.keys[i - 1];
1648
- var val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i];
1649
1750
  if (key) {
1650
1751
  // Fix #1994: using * with props: true generates a param named 0
1651
- params[key.name || 'pathMatch'] = val;
1752
+ params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i];
1652
1753
  }
1653
1754
  }
1654
1755
 
@@ -1686,6 +1787,10 @@ function setStateKey (key) {
1686
1787
  var positionStore = Object.create(null);
1687
1788
 
1688
1789
  function setupScroll () {
1790
+ // Prevent browser scroll behavior on History popstate
1791
+ if ('scrollRestoration' in window.history) {
1792
+ window.history.scrollRestoration = 'manual';
1793
+ }
1689
1794
  // Fix for #1585 for Firefox
1690
1795
  // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
1691
1796
  // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
@@ -1697,12 +1802,10 @@ function setupScroll () {
1697
1802
  var stateCopy = extend({}, window.history.state);
1698
1803
  stateCopy.key = getStateKey();
1699
1804
  window.history.replaceState(stateCopy, '', absolutePath);
1700
- window.addEventListener('popstate', function (e) {
1701
- saveScrollPosition();
1702
- if (e.state && e.state.key) {
1703
- setStateKey(e.state.key);
1704
- }
1705
- });
1805
+ window.addEventListener('popstate', handlePopState);
1806
+ return function () {
1807
+ window.removeEventListener('popstate', handlePopState);
1808
+ }
1706
1809
  }
1707
1810
 
1708
1811
  function handleScroll (
@@ -1764,6 +1867,13 @@ function saveScrollPosition () {
1764
1867
  }
1765
1868
  }
1766
1869
 
1870
+ function handlePopState (e) {
1871
+ saveScrollPosition();
1872
+ if (e.state && e.state.key) {
1873
+ setStateKey(e.state.key);
1874
+ }
1875
+ }
1876
+
1767
1877
  function getScrollPosition () {
1768
1878
  var key = getStateKey();
1769
1879
  if (key) {
@@ -1829,7 +1939,17 @@ function scrollToPosition (shouldScroll, position) {
1829
1939
  }
1830
1940
 
1831
1941
  if (position) {
1832
- window.scrollTo(position.x, position.y);
1942
+ // $flow-disable-line
1943
+ if ('scrollBehavior' in document.documentElement.style) {
1944
+ window.scrollTo({
1945
+ left: position.x,
1946
+ top: position.y,
1947
+ // $flow-disable-line
1948
+ behavior: shouldScroll.behavior
1949
+ });
1950
+ } else {
1951
+ window.scrollTo(position.x, position.y);
1952
+ }
1833
1953
  }
1834
1954
  }
1835
1955
 
@@ -1849,7 +1969,7 @@ var supportsPushState =
1849
1969
  return false
1850
1970
  }
1851
1971
 
1852
- return window.history && 'pushState' in window.history
1972
+ return window.history && typeof window.history.pushState === 'function'
1853
1973
  })();
1854
1974
 
1855
1975
  function pushState (url, replace) {
@@ -1894,6 +2014,89 @@ function runQueue (queue, fn, cb) {
1894
2014
  step(0);
1895
2015
  }
1896
2016
 
2017
+ // When changing thing, also edit router.d.ts
2018
+ var NavigationFailureType = {
2019
+ redirected: 2,
2020
+ aborted: 4,
2021
+ cancelled: 8,
2022
+ duplicated: 16
2023
+ };
2024
+
2025
+ function createNavigationRedirectedError (from, to) {
2026
+ return createRouterError(
2027
+ from,
2028
+ to,
2029
+ NavigationFailureType.redirected,
2030
+ ("Redirected when going from \"" + (from.fullPath) + "\" to \"" + (stringifyRoute(
2031
+ to
2032
+ )) + "\" via a navigation guard.")
2033
+ )
2034
+ }
2035
+
2036
+ function createNavigationDuplicatedError (from, to) {
2037
+ var error = createRouterError(
2038
+ from,
2039
+ to,
2040
+ NavigationFailureType.duplicated,
2041
+ ("Avoided redundant navigation to current location: \"" + (from.fullPath) + "\".")
2042
+ );
2043
+ // backwards compatible with the first introduction of Errors
2044
+ error.name = 'NavigationDuplicated';
2045
+ return error
2046
+ }
2047
+
2048
+ function createNavigationCancelledError (from, to) {
2049
+ return createRouterError(
2050
+ from,
2051
+ to,
2052
+ NavigationFailureType.cancelled,
2053
+ ("Navigation cancelled from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" with a new navigation.")
2054
+ )
2055
+ }
2056
+
2057
+ function createNavigationAbortedError (from, to) {
2058
+ return createRouterError(
2059
+ from,
2060
+ to,
2061
+ NavigationFailureType.aborted,
2062
+ ("Navigation aborted from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" via a navigation guard.")
2063
+ )
2064
+ }
2065
+
2066
+ function createRouterError (from, to, type, message) {
2067
+ var error = new Error(message);
2068
+ error._isRouter = true;
2069
+ error.from = from;
2070
+ error.to = to;
2071
+ error.type = type;
2072
+
2073
+ return error
2074
+ }
2075
+
2076
+ var propertiesToLog = ['params', 'query', 'hash'];
2077
+
2078
+ function stringifyRoute (to) {
2079
+ if (typeof to === 'string') { return to }
2080
+ if ('path' in to) { return to.path }
2081
+ var location = {};
2082
+ propertiesToLog.forEach(function (key) {
2083
+ if (key in to) { location[key] = to[key]; }
2084
+ });
2085
+ return JSON.stringify(location, null, 2)
2086
+ }
2087
+
2088
+ function isError (err) {
2089
+ return Object.prototype.toString.call(err).indexOf('Error') > -1
2090
+ }
2091
+
2092
+ function isNavigationFailure (err, errorType) {
2093
+ return (
2094
+ isError(err) &&
2095
+ err._isRouter &&
2096
+ (errorType == null || err.type === errorType)
2097
+ )
2098
+ }
2099
+
1897
2100
  /* */
1898
2101
 
1899
2102
  function resolveAsyncComponents (matched) {
@@ -2003,33 +2206,6 @@ function once (fn) {
2003
2206
  }
2004
2207
  }
2005
2208
 
2006
- var NavigationDuplicated = /*@__PURE__*/(function (Error) {
2007
- function NavigationDuplicated (normalizedLocation) {
2008
- Error.call(this);
2009
- this.name = this._name = 'NavigationDuplicated';
2010
- // passing the message to super() doesn't seem to work in the transpiled version
2011
- this.message = "Navigating to current location (\"" + (normalizedLocation.fullPath) + "\") is not allowed";
2012
- // add a stack property so services like Sentry can correctly display it
2013
- Object.defineProperty(this, 'stack', {
2014
- value: new Error().stack,
2015
- writable: true,
2016
- configurable: true
2017
- });
2018
- // we could also have used
2019
- // Error.captureStackTrace(this, this.constructor)
2020
- // but it only exists on node and chrome
2021
- }
2022
-
2023
- if ( Error ) NavigationDuplicated.__proto__ = Error;
2024
- NavigationDuplicated.prototype = Object.create( Error && Error.prototype );
2025
- NavigationDuplicated.prototype.constructor = NavigationDuplicated;
2026
-
2027
- return NavigationDuplicated;
2028
- }(Error));
2029
-
2030
- // support IE9
2031
- NavigationDuplicated._name = 'NavigationDuplicated';
2032
-
2033
2209
  /* */
2034
2210
 
2035
2211
  var History = function History (router, base) {
@@ -2042,6 +2218,7 @@ var History = function History (router, base) {
2042
2218
  this.readyCbs = [];
2043
2219
  this.readyErrorCbs = [];
2044
2220
  this.errorCbs = [];
2221
+ this.listeners = [];
2045
2222
  };
2046
2223
 
2047
2224
  History.prototype.listen = function listen (cb) {
@@ -2070,13 +2247,27 @@ History.prototype.transitionTo = function transitionTo (
2070
2247
  ) {
2071
2248
  var this$1 = this;
2072
2249
 
2073
- var route = this.router.match(location, this.current);
2250
+ var route;
2251
+ // catch redirect option
2252
+ try {
2253
+ route = this.router.match(location, this.current);
2254
+ } catch (e) {
2255
+ this.errorCbs.forEach(function (cb) {
2256
+ cb(e);
2257
+ });
2258
+ // Exception should still be thrown
2259
+ throw e
2260
+ }
2261
+ var prev = this.current;
2074
2262
  this.confirmTransition(
2075
2263
  route,
2076
2264
  function () {
2077
2265
  this$1.updateRoute(route);
2078
2266
  onComplete && onComplete(route);
2079
2267
  this$1.ensureURL();
2268
+ this$1.router.afterHooks.forEach(function (hook) {
2269
+ hook && hook(route, prev);
2270
+ });
2080
2271
 
2081
2272
  // fire ready cbs once
2082
2273
  if (!this$1.ready) {
@@ -2091,10 +2282,14 @@ History.prototype.transitionTo = function transitionTo (
2091
2282
  onAbort(err);
2092
2283
  }
2093
2284
  if (err && !this$1.ready) {
2094
- this$1.ready = true;
2095
- this$1.readyErrorCbs.forEach(function (cb) {
2096
- cb(err);
2097
- });
2285
+ // Initial redirection should not mark the history as ready yet
2286
+ // because it's triggered by the redirection instead
2287
+ if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
2288
+ this$1.ready = true;
2289
+ this$1.readyErrorCbs.forEach(function (cb) {
2290
+ cb(err);
2291
+ });
2292
+ }
2098
2293
  }
2099
2294
  }
2100
2295
  );
@@ -2104,29 +2299,37 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2104
2299
  var this$1 = this;
2105
2300
 
2106
2301
  var current = this.current;
2302
+ this.pending = route;
2107
2303
  var abort = function (err) {
2108
- // When the user navigates through history through back/forward buttons
2109
- // we do not want to throw the error. We only throw it if directly calling
2110
- // push/replace. That's why it's not included in isError
2111
- if (!isExtendedError(NavigationDuplicated, err) && isError(err)) {
2304
+ // changed after adding errors
2305
+ // before that change, redirect and aborted navigation would produce an err == null
2306
+ if (!isNavigationFailure(err) && isError(err)) {
2112
2307
  if (this$1.errorCbs.length) {
2113
2308
  this$1.errorCbs.forEach(function (cb) {
2114
2309
  cb(err);
2115
2310
  });
2116
2311
  } else {
2117
- warn(false, 'uncaught error during route navigation:');
2312
+ if (process.env.NODE_ENV !== 'production') {
2313
+ warn(false, 'uncaught error during route navigation:');
2314
+ }
2118
2315
  console.error(err);
2119
2316
  }
2120
2317
  }
2121
2318
  onAbort && onAbort(err);
2122
2319
  };
2320
+ var lastRouteIndex = route.matched.length - 1;
2321
+ var lastCurrentIndex = current.matched.length - 1;
2123
2322
  if (
2124
2323
  isSameRoute(route, current) &&
2125
2324
  // in the case the route map has been dynamically appended to
2126
- route.matched.length === current.matched.length
2325
+ lastRouteIndex === lastCurrentIndex &&
2326
+ route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
2127
2327
  ) {
2128
2328
  this.ensureURL();
2129
- return abort(new NavigationDuplicated(route))
2329
+ if (route.hash) {
2330
+ handleScroll(this.router, current, route, false);
2331
+ }
2332
+ return abort(createNavigationDuplicatedError(current, route))
2130
2333
  }
2131
2334
 
2132
2335
  var ref = resolveQueue(
@@ -2150,15 +2353,17 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2150
2353
  resolveAsyncComponents(activated)
2151
2354
  );
2152
2355
 
2153
- this.pending = route;
2154
2356
  var iterator = function (hook, next) {
2155
2357
  if (this$1.pending !== route) {
2156
- return abort()
2358
+ return abort(createNavigationCancelledError(current, route))
2157
2359
  }
2158
2360
  try {
2159
2361
  hook(route, current, function (to) {
2160
- if (to === false || isError(to)) {
2362
+ if (to === false) {
2161
2363
  // next(false) -> abort navigation, ensure current URL
2364
+ this$1.ensureURL(true);
2365
+ abort(createNavigationAbortedError(current, route));
2366
+ } else if (isError(to)) {
2162
2367
  this$1.ensureURL(true);
2163
2368
  abort(to);
2164
2369
  } else if (
@@ -2167,7 +2372,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2167
2372
  (typeof to.path === 'string' || typeof to.name === 'string'))
2168
2373
  ) {
2169
2374
  // next('/') or next({ path: '/' }) -> redirect
2170
- abort();
2375
+ abort(createNavigationRedirectedError(current, route));
2171
2376
  if (typeof to === 'object' && to.replace) {
2172
2377
  this$1.replace(to);
2173
2378
  } else {
@@ -2184,23 +2389,19 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2184
2389
  };
2185
2390
 
2186
2391
  runQueue(queue, iterator, function () {
2187
- var postEnterCbs = [];
2188
- var isValid = function () { return this$1.current === route; };
2189
2392
  // wait until async components are resolved before
2190
2393
  // extracting in-component enter guards
2191
- var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
2394
+ var enterGuards = extractEnterGuards(activated);
2192
2395
  var queue = enterGuards.concat(this$1.router.resolveHooks);
2193
2396
  runQueue(queue, iterator, function () {
2194
2397
  if (this$1.pending !== route) {
2195
- return abort()
2398
+ return abort(createNavigationCancelledError(current, route))
2196
2399
  }
2197
2400
  this$1.pending = null;
2198
2401
  onComplete(route);
2199
2402
  if (this$1.router.app) {
2200
2403
  this$1.router.app.$nextTick(function () {
2201
- postEnterCbs.forEach(function (cb) {
2202
- cb();
2203
- });
2404
+ handleRouteEntered(route);
2204
2405
  });
2205
2406
  }
2206
2407
  });
@@ -2208,12 +2409,24 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2208
2409
  };
2209
2410
 
2210
2411
  History.prototype.updateRoute = function updateRoute (route) {
2211
- var prev = this.current;
2212
2412
  this.current = route;
2213
2413
  this.cb && this.cb(route);
2214
- this.router.afterHooks.forEach(function (hook) {
2215
- hook && hook(route, prev);
2414
+ };
2415
+
2416
+ History.prototype.setupListeners = function setupListeners () {
2417
+ // Default implementation is empty
2418
+ };
2419
+
2420
+ History.prototype.teardown = function teardown () {
2421
+ // clean up event listeners
2422
+ this.listeners.forEach(function (cleanupListener) {
2423
+ cleanupListener();
2216
2424
  });
2425
+ this.listeners = [];
2426
+
2427
+ // reset current history route
2428
+ this.current = START;
2429
+ this.pending = null;
2217
2430
  };
2218
2431
 
2219
2432
  function normalizeBase (base) {
@@ -2299,15 +2512,13 @@ function bindGuard (guard, instance) {
2299
2512
  }
2300
2513
 
2301
2514
  function extractEnterGuards (
2302
- activated,
2303
- cbs,
2304
- isValid
2515
+ activated
2305
2516
  ) {
2306
2517
  return extractGuards(
2307
2518
  activated,
2308
2519
  'beforeRouteEnter',
2309
2520
  function (guard, _, match, key) {
2310
- return bindEnterGuard(guard, match, key, cbs, isValid)
2521
+ return bindEnterGuard(guard, match, key)
2311
2522
  }
2312
2523
  )
2313
2524
  }
@@ -2315,68 +2526,56 @@ function extractEnterGuards (
2315
2526
  function bindEnterGuard (
2316
2527
  guard,
2317
2528
  match,
2318
- key,
2319
- cbs,
2320
- isValid
2529
+ key
2321
2530
  ) {
2322
2531
  return function routeEnterGuard (to, from, next) {
2323
2532
  return guard(to, from, function (cb) {
2324
2533
  if (typeof cb === 'function') {
2325
- cbs.push(function () {
2326
- // #750
2327
- // if a router-view is wrapped with an out-in transition,
2328
- // the instance may not have been registered at this time.
2329
- // we will need to poll for registration until current route
2330
- // is no longer valid.
2331
- poll(cb, match.instances, key, isValid);
2332
- });
2534
+ if (!match.enteredCbs[key]) {
2535
+ match.enteredCbs[key] = [];
2536
+ }
2537
+ match.enteredCbs[key].push(cb);
2333
2538
  }
2334
2539
  next(cb);
2335
2540
  })
2336
2541
  }
2337
2542
  }
2338
2543
 
2339
- function poll (
2340
- cb, // somehow flow cannot infer this is a function
2341
- instances,
2342
- key,
2343
- isValid
2344
- ) {
2345
- if (
2346
- instances[key] &&
2347
- !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
2348
- ) {
2349
- cb(instances[key]);
2350
- } else if (isValid()) {
2351
- setTimeout(function () {
2352
- poll(cb, instances, key, isValid);
2353
- }, 16);
2354
- }
2355
- }
2356
-
2357
2544
  /* */
2358
2545
 
2359
2546
  var HTML5History = /*@__PURE__*/(function (History) {
2360
2547
  function HTML5History (router, base) {
2548
+ History.call(this, router, base);
2549
+
2550
+ this._startLocation = getLocation(this.base);
2551
+ }
2552
+
2553
+ if ( History ) HTML5History.__proto__ = History;
2554
+ HTML5History.prototype = Object.create( History && History.prototype );
2555
+ HTML5History.prototype.constructor = HTML5History;
2556
+
2557
+ HTML5History.prototype.setupListeners = function setupListeners () {
2361
2558
  var this$1 = this;
2362
2559
 
2363
- History.call(this, router, base);
2560
+ if (this.listeners.length > 0) {
2561
+ return
2562
+ }
2364
2563
 
2564
+ var router = this.router;
2365
2565
  var expectScroll = router.options.scrollBehavior;
2366
2566
  var supportsScroll = supportsPushState && expectScroll;
2367
2567
 
2368
2568
  if (supportsScroll) {
2369
- setupScroll();
2569
+ this.listeners.push(setupScroll());
2370
2570
  }
2371
2571
 
2372
- var initLocation = getLocation(this.base);
2373
- window.addEventListener('popstate', function (e) {
2572
+ var handleRoutingEvent = function () {
2374
2573
  var current = this$1.current;
2375
2574
 
2376
2575
  // Avoiding first `popstate` event dispatched in some browsers but first
2377
2576
  // history route not updated since async guard at the same time.
2378
2577
  var location = getLocation(this$1.base);
2379
- if (this$1.current === START && location === initLocation) {
2578
+ if (this$1.current === START && location === this$1._startLocation) {
2380
2579
  return
2381
2580
  }
2382
2581
 
@@ -2385,12 +2584,12 @@ var HTML5History = /*@__PURE__*/(function (History) {
2385
2584
  handleScroll(router, route, current, true);
2386
2585
  }
2387
2586
  });
2587
+ };
2588
+ window.addEventListener('popstate', handleRoutingEvent);
2589
+ this.listeners.push(function () {
2590
+ window.removeEventListener('popstate', handleRoutingEvent);
2388
2591
  });
2389
- }
2390
-
2391
- if ( History ) HTML5History.__proto__ = History;
2392
- HTML5History.prototype = Object.create( History && History.prototype );
2393
- HTML5History.prototype.constructor = HTML5History;
2592
+ };
2394
2593
 
2395
2594
  HTML5History.prototype.go = function go (n) {
2396
2595
  window.history.go(n);
@@ -2435,8 +2634,13 @@ var HTML5History = /*@__PURE__*/(function (History) {
2435
2634
  }(History));
2436
2635
 
2437
2636
  function getLocation (base) {
2438
- var path = decodeURI(window.location.pathname);
2439
- if (base && path.indexOf(base) === 0) {
2637
+ var path = window.location.pathname;
2638
+ var pathLowerCase = path.toLowerCase();
2639
+ var baseLowerCase = base.toLowerCase();
2640
+ // base="/a" shouldn't turn path="/app" into "/a/pp"
2641
+ // so we ensure the trailing slash in the base
2642
+ if (base && ((pathLowerCase === baseLowerCase) ||
2643
+ (pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
2440
2644
  path = path.slice(base.length);
2441
2645
  }
2442
2646
  return (path || '/') + window.location.search + window.location.hash
@@ -2463,31 +2667,40 @@ var HashHistory = /*@__PURE__*/(function (History) {
2463
2667
  HashHistory.prototype.setupListeners = function setupListeners () {
2464
2668
  var this$1 = this;
2465
2669
 
2670
+ if (this.listeners.length > 0) {
2671
+ return
2672
+ }
2673
+
2466
2674
  var router = this.router;
2467
2675
  var expectScroll = router.options.scrollBehavior;
2468
2676
  var supportsScroll = supportsPushState && expectScroll;
2469
2677
 
2470
2678
  if (supportsScroll) {
2471
- setupScroll();
2679
+ this.listeners.push(setupScroll());
2472
2680
  }
2473
2681
 
2474
- window.addEventListener(
2475
- supportsPushState ? 'popstate' : 'hashchange',
2476
- function () {
2477
- var current = this$1.current;
2478
- if (!ensureSlash()) {
2479
- return
2480
- }
2481
- this$1.transitionTo(getHash(), function (route) {
2482
- if (supportsScroll) {
2483
- handleScroll(this$1.router, route, current, true);
2484
- }
2485
- if (!supportsPushState) {
2486
- replaceHash(route.fullPath);
2487
- }
2488
- });
2682
+ var handleRoutingEvent = function () {
2683
+ var current = this$1.current;
2684
+ if (!ensureSlash()) {
2685
+ return
2489
2686
  }
2687
+ this$1.transitionTo(getHash(), function (route) {
2688
+ if (supportsScroll) {
2689
+ handleScroll(this$1.router, route, current, true);
2690
+ }
2691
+ if (!supportsPushState) {
2692
+ replaceHash(route.fullPath);
2693
+ }
2694
+ });
2695
+ };
2696
+ var eventType = supportsPushState ? 'popstate' : 'hashchange';
2697
+ window.addEventListener(
2698
+ eventType,
2699
+ handleRoutingEvent
2490
2700
  );
2701
+ this.listeners.push(function () {
2702
+ window.removeEventListener(eventType, handleRoutingEvent);
2703
+ });
2491
2704
  };
2492
2705
 
2493
2706
  HashHistory.prototype.push = function push (location, onComplete, onAbort) {
@@ -2566,17 +2779,6 @@ function getHash () {
2566
2779
  if (index < 0) { return '' }
2567
2780
 
2568
2781
  href = href.slice(index + 1);
2569
- // decode the hash but not the search or hash
2570
- // as search(query) is already decoded
2571
- var searchIndex = href.indexOf('?');
2572
- if (searchIndex < 0) {
2573
- var hashIndex = href.indexOf('#');
2574
- if (hashIndex > -1) {
2575
- href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2576
- } else { href = decodeURI(href); }
2577
- } else {
2578
- href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2579
- }
2580
2782
 
2581
2783
  return href
2582
2784
  }
@@ -2655,11 +2857,15 @@ var AbstractHistory = /*@__PURE__*/(function (History) {
2655
2857
  this.confirmTransition(
2656
2858
  route,
2657
2859
  function () {
2860
+ var prev = this$1.current;
2658
2861
  this$1.index = targetIndex;
2659
2862
  this$1.updateRoute(route);
2863
+ this$1.router.afterHooks.forEach(function (hook) {
2864
+ hook && hook(route, prev);
2865
+ });
2660
2866
  },
2661
2867
  function (err) {
2662
- if (isExtendedError(NavigationDuplicated, err)) {
2868
+ if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
2663
2869
  this$1.index = targetIndex;
2664
2870
  }
2665
2871
  }
@@ -2680,11 +2886,12 @@ var AbstractHistory = /*@__PURE__*/(function (History) {
2680
2886
 
2681
2887
  /* */
2682
2888
 
2683
-
2684
-
2685
2889
  var KduRouter = function KduRouter (options) {
2686
2890
  if ( options === void 0 ) options = {};
2687
2891
 
2892
+ if (process.env.NODE_ENV !== 'production') {
2893
+ warn(this instanceof KduRouter, "Router must be called with the new operator.");
2894
+ }
2688
2895
  this.app = null;
2689
2896
  this.apps = [];
2690
2897
  this.options = options;
@@ -2694,7 +2901,8 @@ var KduRouter = function KduRouter (options) {
2694
2901
  this.matcher = createMatcher(options.routes || [], this);
2695
2902
 
2696
2903
  var mode = options.mode || 'hash';
2697
- this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false;
2904
+ this.fallback =
2905
+ mode === 'history' && !supportsPushState && options.fallback !== false;
2698
2906
  if (this.fallback) {
2699
2907
  mode = 'hash';
2700
2908
  }
@@ -2722,11 +2930,7 @@ var KduRouter = function KduRouter (options) {
2722
2930
 
2723
2931
  var prototypeAccessors = { currentRoute: { configurable: true } };
2724
2932
 
2725
- KduRouter.prototype.match = function match (
2726
- raw,
2727
- current,
2728
- redirectedFrom
2729
- ) {
2933
+ KduRouter.prototype.match = function match (raw, current, redirectedFrom) {
2730
2934
  return this.matcher.match(raw, current, redirectedFrom)
2731
2935
  };
2732
2936
 
@@ -2737,11 +2941,12 @@ prototypeAccessors.currentRoute.get = function () {
2737
2941
  KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2738
2942
  var this$1 = this;
2739
2943
 
2740
- process.env.NODE_ENV !== 'production' && assert(
2741
- install.installed,
2742
- "not installed. Make sure to call `Kdu.use(KduRouter)` " +
2743
- "before creating root instance."
2744
- );
2944
+ process.env.NODE_ENV !== 'production' &&
2945
+ assert(
2946
+ install.installed,
2947
+ "not installed. Make sure to call `Kdu.use(KduRouter)` " +
2948
+ "before creating root instance."
2949
+ );
2745
2950
 
2746
2951
  this.apps.push(app);
2747
2952
 
@@ -2753,6 +2958,8 @@ KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2753
2958
  // ensure we still have a main app or null if no apps
2754
2959
  // we do not release the router so it can be reused
2755
2960
  if (this$1.app === app) { this$1.app = this$1.apps[0] || null; }
2961
+
2962
+ if (!this$1.app) { this$1.history.teardown(); }
2756
2963
  });
2757
2964
 
2758
2965
  // main app previously initialized
@@ -2765,16 +2972,24 @@ KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2765
2972
 
2766
2973
  var history = this.history;
2767
2974
 
2768
- if (history instanceof HTML5History) {
2769
- history.transitionTo(history.getCurrentLocation());
2770
- } else if (history instanceof HashHistory) {
2771
- var setupHashListener = function () {
2975
+ if (history instanceof HTML5History || history instanceof HashHistory) {
2976
+ var handleInitialScroll = function (routeOrError) {
2977
+ var from = history.current;
2978
+ var expectScroll = this$1.options.scrollBehavior;
2979
+ var supportsScroll = supportsPushState && expectScroll;
2980
+
2981
+ if (supportsScroll && 'fullPath' in routeOrError) {
2982
+ handleScroll(this$1, routeOrError, from, false);
2983
+ }
2984
+ };
2985
+ var setupListeners = function (routeOrError) {
2772
2986
  history.setupListeners();
2987
+ handleInitialScroll(routeOrError);
2773
2988
  };
2774
2989
  history.transitionTo(
2775
2990
  history.getCurrentLocation(),
2776
- setupHashListener,
2777
- setupHashListener
2991
+ setupListeners,
2992
+ setupListeners
2778
2993
  );
2779
2994
  }
2780
2995
 
@@ -2852,11 +3067,14 @@ KduRouter.prototype.getMatchedComponents = function getMatchedComponents (to) {
2852
3067
  if (!route) {
2853
3068
  return []
2854
3069
  }
2855
- return [].concat.apply([], route.matched.map(function (m) {
2856
- return Object.keys(m.components).map(function (key) {
2857
- return m.components[key]
3070
+ return [].concat.apply(
3071
+ [],
3072
+ route.matched.map(function (m) {
3073
+ return Object.keys(m.components).map(function (key) {
3074
+ return m.components[key]
3075
+ })
2858
3076
  })
2859
- }))
3077
+ )
2860
3078
  };
2861
3079
 
2862
3080
  KduRouter.prototype.resolve = function resolve (
@@ -2865,12 +3083,7 @@ KduRouter.prototype.resolve = function resolve (
2865
3083
  append
2866
3084
  ) {
2867
3085
  current = current || this.history.current;
2868
- var location = normalizeLocation(
2869
- to,
2870
- current,
2871
- append,
2872
- this
2873
- );
3086
+ var location = normalizeLocation(to, current, append, this);
2874
3087
  var route = this.match(location, current);
2875
3088
  var fullPath = route.redirectedFrom || route.fullPath;
2876
3089
  var base = this.history.base;
@@ -2885,7 +3098,21 @@ KduRouter.prototype.resolve = function resolve (
2885
3098
  }
2886
3099
  };
2887
3100
 
3101
+ KduRouter.prototype.getRoutes = function getRoutes () {
3102
+ return this.matcher.getRoutes()
3103
+ };
3104
+
3105
+ KduRouter.prototype.addRoute = function addRoute (parentOrRoute, route) {
3106
+ this.matcher.addRoute(parentOrRoute, route);
3107
+ if (this.history.current !== START) {
3108
+ this.history.transitionTo(this.history.getCurrentLocation());
3109
+ }
3110
+ };
3111
+
2888
3112
  KduRouter.prototype.addRoutes = function addRoutes (routes) {
3113
+ if (process.env.NODE_ENV !== 'production') {
3114
+ warn(false, 'router.addRoutes() is deprecated and has been removed in Kdu Router 4. Use router.addRoute() instead.');
3115
+ }
2889
3116
  this.matcher.addRoutes(routes);
2890
3117
  if (this.history.current !== START) {
2891
3118
  this.history.transitionTo(this.history.getCurrentLocation());
@@ -2908,7 +3135,10 @@ function createHref (base, fullPath, mode) {
2908
3135
  }
2909
3136
 
2910
3137
  KduRouter.install = install;
2911
- KduRouter.version = '3.1.7';
3138
+ KduRouter.version = '3.5.4';
3139
+ KduRouter.isNavigationFailure = isNavigationFailure;
3140
+ KduRouter.NavigationFailureType = NavigationFailureType;
3141
+ KduRouter.START_LOCATION = START;
2912
3142
 
2913
3143
  if (inBrowser && window.Kdu) {
2914
3144
  window.Kdu.use(KduRouter);