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
  */
@@ -12,23 +12,11 @@ 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
  }
19
19
 
20
- function isError (err) {
21
- return Object.prototype.toString.call(err).indexOf('Error') > -1
22
- }
23
-
24
- function isExtendedError (constructor, err) {
25
- return (
26
- err instanceof constructor ||
27
- // _name is to support IE9 too
28
- (err && (err.name === constructor.name || err._name === constructor._name))
29
- )
30
- }
31
-
32
20
  function extend (a, b) {
33
21
  for (var key in b) {
34
22
  a[key] = b[key];
@@ -36,158 +24,6 @@ function extend (a, b) {
36
24
  return a
37
25
  }
38
26
 
39
- var View = {
40
- name: 'RouterView',
41
- functional: true,
42
- props: {
43
- name: {
44
- type: String,
45
- default: 'default'
46
- }
47
- },
48
- render: function render (_, ref) {
49
- var props = ref.props;
50
- var children = ref.children;
51
- var parent = ref.parent;
52
- var data = ref.data;
53
-
54
- // used by devtools to display a router-view badge
55
- data.routerView = true;
56
-
57
- // directly use parent context's createElement() function
58
- // so that components rendered by router-view can resolve named slots
59
- var h = parent.$createElement;
60
- var name = props.name;
61
- var route = parent.$route;
62
- var cache = parent._routerViewCache || (parent._routerViewCache = {});
63
-
64
- // determine current view depth, also check to see if the tree
65
- // has been toggled inactive but kept-alive.
66
- var depth = 0;
67
- var inactive = false;
68
- while (parent && parent._routerRoot !== parent) {
69
- var knodeData = parent.$knode ? parent.$knode.data : {};
70
- if (knodeData.routerView) {
71
- depth++;
72
- }
73
- if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
74
- inactive = true;
75
- }
76
- parent = parent.$parent;
77
- }
78
- data.routerViewDepth = depth;
79
-
80
- // render previous view if the tree is inactive and kept-alive
81
- if (inactive) {
82
- var cachedData = cache[name];
83
- var cachedComponent = cachedData && cachedData.component;
84
- if (cachedComponent) {
85
- // #2301
86
- // pass props
87
- if (cachedData.configProps) {
88
- fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
89
- }
90
- return h(cachedComponent, data, children)
91
- } else {
92
- // render previous empty view
93
- return h()
94
- }
95
- }
96
-
97
- var matched = route.matched[depth];
98
- var component = matched && matched.components[name];
99
-
100
- // render empty node if no matched route or no config component
101
- if (!matched || !component) {
102
- cache[name] = null;
103
- return h()
104
- }
105
-
106
- // cache component
107
- cache[name] = { component: component };
108
-
109
- // attach instance registration hook
110
- // this will be called in the instance's injected lifecycle hooks
111
- data.registerRouteInstance = function (vm, val) {
112
- // val could be undefined for unregistration
113
- var current = matched.instances[name];
114
- if (
115
- (val && current !== vm) ||
116
- (!val && current === vm)
117
- ) {
118
- matched.instances[name] = val;
119
- }
120
- }
121
-
122
- // also register instance in prepatch hook
123
- // in case the same component instance is reused across different routes
124
- ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
125
- matched.instances[name] = knode.componentInstance;
126
- };
127
-
128
- // register instance in init hook
129
- // in case kept-alive component be actived when routes changed
130
- data.hook.init = function (knode) {
131
- if (knode.data.keepAlive &&
132
- knode.componentInstance &&
133
- knode.componentInstance !== matched.instances[name]
134
- ) {
135
- matched.instances[name] = knode.componentInstance;
136
- }
137
- };
138
-
139
- var configProps = matched.props && matched.props[name];
140
- // save route and configProps in cachce
141
- if (configProps) {
142
- extend(cache[name], {
143
- route: route,
144
- configProps: configProps
145
- });
146
- fillPropsinData(component, data, route, configProps);
147
- }
148
-
149
- return h(component, data, children)
150
- }
151
- };
152
-
153
- function fillPropsinData (component, data, route, configProps) {
154
- // resolve props
155
- var propsToPass = data.props = resolveProps(route, configProps);
156
- if (propsToPass) {
157
- // clone to prevent mutation
158
- propsToPass = data.props = extend({}, propsToPass);
159
- // pass non-declared props as attrs
160
- var attrs = data.attrs = data.attrs || {};
161
- for (var key in propsToPass) {
162
- if (!component.props || !(key in component.props)) {
163
- attrs[key] = propsToPass[key];
164
- delete propsToPass[key];
165
- }
166
- }
167
- }
168
- }
169
-
170
- function resolveProps (route, config) {
171
- switch (typeof config) {
172
- case 'undefined':
173
- return
174
- case 'object':
175
- return config
176
- case 'function':
177
- return config(route)
178
- case 'boolean':
179
- return config ? route.params : undefined
180
- default:
181
- if (process.env.NODE_ENV !== 'production') {
182
- warn(
183
- false,
184
- "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
185
- "expecting an object, function or boolean."
186
- );
187
- }
188
- }
189
- }
190
-
191
27
  /* */
192
28
 
193
29
  var encodeReserveRE = /[!'()*]/g;
@@ -198,10 +34,19 @@ var commaRE = /%2C/g;
198
34
  // - escapes [!'()*]
199
35
  // - preserve commas
200
36
  var encode = function (str) { return encodeURIComponent(str)
201
- .replace(encodeReserveRE, encodeReserveReplacer)
202
- .replace(commaRE, ','); };
37
+ .replace(encodeReserveRE, encodeReserveReplacer)
38
+ .replace(commaRE, ','); };
203
39
 
204
- 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
+ }
205
50
 
206
51
  function resolveQuery (
207
52
  query,
@@ -219,11 +64,16 @@ function resolveQuery (
219
64
  parsedQuery = {};
220
65
  }
221
66
  for (var key in extraQuery) {
222
- parsedQuery[key] = extraQuery[key];
67
+ var value = extraQuery[key];
68
+ parsedQuery[key] = Array.isArray(value)
69
+ ? value.map(castQueryParamValue)
70
+ : castQueryParamValue(value);
223
71
  }
224
72
  return parsedQuery
225
73
  }
226
74
 
75
+ var castQueryParamValue = function (value) { return (value == null || typeof value === 'object' ? value : String(value)); };
76
+
227
77
  function parseQuery (query) {
228
78
  var res = {};
229
79
 
@@ -236,9 +86,7 @@ function parseQuery (query) {
236
86
  query.split('&').forEach(function (param) {
237
87
  var parts = param.replace(/\+/g, ' ').split('=');
238
88
  var key = decode(parts.shift());
239
- var val = parts.length > 0
240
- ? decode(parts.join('='))
241
- : null;
89
+ var val = parts.length > 0 ? decode(parts.join('=')) : null;
242
90
 
243
91
  if (res[key] === undefined) {
244
92
  res[key] = val;
@@ -253,34 +101,39 @@ function parseQuery (query) {
253
101
  }
254
102
 
255
103
  function stringifyQuery (obj) {
256
- var res = obj ? Object.keys(obj).map(function (key) {
257
- var val = obj[key];
104
+ var res = obj
105
+ ? Object.keys(obj)
106
+ .map(function (key) {
107
+ var val = obj[key];
258
108
 
259
- if (val === undefined) {
260
- return ''
261
- }
262
-
263
- if (val === null) {
264
- return encode(key)
265
- }
109
+ if (val === undefined) {
110
+ return ''
111
+ }
266
112
 
267
- if (Array.isArray(val)) {
268
- var result = [];
269
- val.forEach(function (val2) {
270
- if (val2 === undefined) {
271
- return
113
+ if (val === null) {
114
+ return encode(key)
272
115
  }
273
- if (val2 === null) {
274
- result.push(encode(key));
275
- } else {
276
- 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('&')
277
130
  }
278
- });
279
- return result.join('&')
280
- }
281
131
 
282
- return encode(key) + '=' + encode(val)
283
- }).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;
284
137
  return res ? ("?" + res) : ''
285
138
  }
286
139
 
@@ -357,68 +210,244 @@ function getFullPath (
357
210
  return (path || '/') + stringify(query) + hash
358
211
  }
359
212
 
360
- function isSameRoute (a, b) {
213
+ function isSameRoute (a, b, onlyPath) {
361
214
  if (b === START) {
362
215
  return a === b
363
216
  } else if (!b) {
364
217
  return false
365
218
  } else if (a.path && b.path) {
366
- return (
367
- a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
219
+ return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
368
220
  a.hash === b.hash &&
369
- isObjectEqual(a.query, b.query)
370
- )
221
+ isObjectEqual(a.query, b.query))
371
222
  } else if (a.name && b.name) {
372
223
  return (
373
224
  a.name === b.name &&
374
- a.hash === b.hash &&
225
+ (onlyPath || (
226
+ a.hash === b.hash &&
375
227
  isObjectEqual(a.query, b.query) &&
376
- isObjectEqual(a.params, b.params)
228
+ isObjectEqual(a.params, b.params))
229
+ )
377
230
  )
378
231
  } else {
379
232
  return false
380
233
  }
381
234
  }
382
235
 
383
- function isObjectEqual (a, b) {
384
- if ( a === void 0 ) a = {};
385
- if ( b === void 0 ) b = {};
236
+ function isObjectEqual (a, b) {
237
+ if ( a === void 0 ) a = {};
238
+ if ( b === void 0 ) b = {};
239
+
240
+ // handle null value #1566
241
+ if (!a || !b) { return a === b }
242
+ var aKeys = Object.keys(a).sort();
243
+ var bKeys = Object.keys(b).sort();
244
+ if (aKeys.length !== bKeys.length) {
245
+ return false
246
+ }
247
+ return aKeys.every(function (key, i) {
248
+ var aVal = a[key];
249
+ var bKey = bKeys[i];
250
+ if (bKey !== key) { return false }
251
+ var bVal = b[key];
252
+ // query values can be null and undefined
253
+ if (aVal == null || bVal == null) { return aVal === bVal }
254
+ // check nested equality
255
+ if (typeof aVal === 'object' && typeof bVal === 'object') {
256
+ return isObjectEqual(aVal, bVal)
257
+ }
258
+ return String(aVal) === String(bVal)
259
+ })
260
+ }
261
+
262
+ function isIncludedRoute (current, target) {
263
+ return (
264
+ current.path.replace(trailingSlashRE, '/').indexOf(
265
+ target.path.replace(trailingSlashRE, '/')
266
+ ) === 0 &&
267
+ (!target.hash || current.hash === target.hash) &&
268
+ queryIncludes(current.query, target.query)
269
+ )
270
+ }
271
+
272
+ function queryIncludes (current, target) {
273
+ for (var key in target) {
274
+ if (!(key in current)) {
275
+ return false
276
+ }
277
+ }
278
+ return true
279
+ }
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
+ };
386
400
 
387
- // handle null value #1566
388
- if (!a || !b) { return a === b }
389
- var aKeys = Object.keys(a);
390
- var bKeys = Object.keys(b);
391
- if (aKeys.length !== bKeys.length) {
392
- return false
393
- }
394
- return aKeys.every(function (key) {
395
- var aVal = a[key];
396
- var bVal = b[key];
397
- // check nested equality
398
- if (typeof aVal === 'object' && typeof bVal === 'object') {
399
- return isObjectEqual(aVal, bVal)
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);
400
409
  }
401
- return String(aVal) === String(bVal)
402
- })
403
- }
404
410
 
405
- function isIncludedRoute (current, target) {
406
- return (
407
- current.path.replace(trailingSlashRE, '/').indexOf(
408
- target.path.replace(trailingSlashRE, '/')
409
- ) === 0 &&
410
- (!target.hash || current.hash === target.hash) &&
411
- queryIncludes(current.query, target.query)
412
- )
413
- }
411
+ return h(component, data, children)
412
+ }
413
+ };
414
414
 
415
- function queryIncludes (current, target) {
416
- for (var key in target) {
417
- if (!(key in current)) {
418
- return false
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
+ }
419
428
  }
420
429
  }
421
- return true
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
+ }
422
451
  }
423
452
 
424
453
  /* */
@@ -489,7 +518,7 @@ function parsePath (path) {
489
518
  }
490
519
 
491
520
  function cleanPath (path) {
492
- return path.replace(/\/\//g, '/')
521
+ return path.replace(/\/(?:\s*\/)+/g, '/')
493
522
  }
494
523
 
495
524
  var isarray = Array.isArray || function (arr) {
@@ -1029,6 +1058,10 @@ var eventTypes = [String, Array];
1029
1058
 
1030
1059
  var noop = function () {};
1031
1060
 
1061
+ var warnedCustomSlot;
1062
+ var warnedTagProp;
1063
+ var warnedEventProp;
1064
+
1032
1065
  var Link = {
1033
1066
  name: 'RouterLink',
1034
1067
  props: {
@@ -1040,11 +1073,17 @@ var Link = {
1040
1073
  type: String,
1041
1074
  default: 'a'
1042
1075
  },
1076
+ custom: Boolean,
1043
1077
  exact: Boolean,
1078
+ exactPath: Boolean,
1044
1079
  append: Boolean,
1045
1080
  replace: Boolean,
1046
1081
  activeClass: String,
1047
1082
  exactActiveClass: String,
1083
+ ariaCurrentValue: {
1084
+ type: String,
1085
+ default: 'page'
1086
+ },
1048
1087
  event: {
1049
1088
  type: eventTypes,
1050
1089
  default: 'click'
@@ -1085,11 +1124,13 @@ var Link = {
1085
1124
  ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1086
1125
  : route;
1087
1126
 
1088
- classes[exactActiveClass] = isSameRoute(current, compareTarget);
1089
- classes[activeClass] = this.exact
1127
+ classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath);
1128
+ classes[activeClass] = this.exact || this.exactPath
1090
1129
  ? classes[exactActiveClass]
1091
1130
  : isIncludedRoute(current, compareTarget);
1092
1131
 
1132
+ var ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null;
1133
+
1093
1134
  var handler = function (e) {
1094
1135
  if (guardEvent(e)) {
1095
1136
  if (this$1.replace) {
@@ -1123,22 +1164,43 @@ var Link = {
1123
1164
  });
1124
1165
 
1125
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
+ }
1126
1171
  if (scopedSlot.length === 1) {
1127
1172
  return scopedSlot[0]
1128
1173
  } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1129
1174
  if (process.env.NODE_ENV !== 'production') {
1130
1175
  warn(
1131
1176
  false,
1132
- ("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.")
1133
1178
  );
1134
1179
  }
1135
1180
  return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1136
1181
  }
1137
1182
  }
1138
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
+
1139
1201
  if (this.tag === 'a') {
1140
1202
  data.on = on;
1141
- data.attrs = { href: href };
1203
+ data.attrs = { href: href, 'aria-current': ariaCurrentValue };
1142
1204
  } else {
1143
1205
  // find the first <a> child and apply listener and href
1144
1206
  var a = findAnchor(this.$slots.default);
@@ -1166,6 +1228,7 @@ var Link = {
1166
1228
 
1167
1229
  var aAttrs = (a.data.attrs = extend({}, a.data.attrs));
1168
1230
  aAttrs.href = href;
1231
+ aAttrs['aria-current'] = ariaCurrentValue;
1169
1232
  } else {
1170
1233
  // doesn't have <a> child, apply listener to self
1171
1234
  data.on = on;
@@ -1270,7 +1333,8 @@ function createRouteMap (
1270
1333
  routes,
1271
1334
  oldPathList,
1272
1335
  oldPathMap,
1273
- oldNameMap
1336
+ oldNameMap,
1337
+ parentRoute
1274
1338
  ) {
1275
1339
  // the path list is used to control path matching priority
1276
1340
  var pathList = oldPathList || [];
@@ -1280,7 +1344,7 @@ function createRouteMap (
1280
1344
  var nameMap = oldNameMap || Object.create(null);
1281
1345
 
1282
1346
  routes.forEach(function (route) {
1283
- addRouteRecord(pathList, pathMap, nameMap, route);
1347
+ addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
1284
1348
  });
1285
1349
 
1286
1350
  // ensure wildcard routes are always at the end
@@ -1329,6 +1393,14 @@ function addRouteRecord (
1329
1393
  path || name
1330
1394
  )) + " cannot be a " + "string id. Use an actual component instead."
1331
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
+ );
1332
1404
  }
1333
1405
 
1334
1406
  var pathToRegexpOptions =
@@ -1343,7 +1415,13 @@ function addRouteRecord (
1343
1415
  path: normalizedPath,
1344
1416
  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1345
1417
  components: route.components || { default: route.component },
1418
+ alias: route.alias
1419
+ ? typeof route.alias === 'string'
1420
+ ? [route.alias]
1421
+ : route.alias
1422
+ : [],
1346
1423
  instances: {},
1424
+ enteredCbs: {},
1347
1425
  name: name,
1348
1426
  parent: parent,
1349
1427
  matchAs: matchAs,
@@ -1371,7 +1449,7 @@ function addRouteRecord (
1371
1449
  warn(
1372
1450
  false,
1373
1451
  "Named Route '" + (route.name) + "' has a default child route. " +
1374
- "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " +
1452
+ "When navigating to this named route (:to=\"{name: '" + (route.name) + "'}\"), " +
1375
1453
  "the default child route will not be rendered. Remove the name from " +
1376
1454
  "this route and use the name of the default child route for named " +
1377
1455
  "links instead."
@@ -1478,6 +1556,28 @@ function createMatcher (
1478
1556
  createRouteMap(routes, pathList, pathMap, nameMap);
1479
1557
  }
1480
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
+
1481
1581
  function match (
1482
1582
  raw,
1483
1583
  currentRoute,
@@ -1624,6 +1724,8 @@ function createMatcher (
1624
1724
 
1625
1725
  return {
1626
1726
  match: match,
1727
+ addRoute: addRoute,
1728
+ getRoutes: getRoutes,
1627
1729
  addRoutes: addRoutes
1628
1730
  }
1629
1731
  }
@@ -1643,10 +1745,9 @@ function matchRoute (
1643
1745
 
1644
1746
  for (var i = 1, len = m.length; i < len; ++i) {
1645
1747
  var key = regex.keys[i - 1];
1646
- var val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i];
1647
1748
  if (key) {
1648
1749
  // Fix #1994: using * with props: true generates a param named 0
1649
- params[key.name || 'pathMatch'] = val;
1750
+ params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i];
1650
1751
  }
1651
1752
  }
1652
1753
 
@@ -1684,6 +1785,10 @@ function setStateKey (key) {
1684
1785
  var positionStore = Object.create(null);
1685
1786
 
1686
1787
  function setupScroll () {
1788
+ // Prevent browser scroll behavior on History popstate
1789
+ if ('scrollRestoration' in window.history) {
1790
+ window.history.scrollRestoration = 'manual';
1791
+ }
1687
1792
  // Fix for #1585 for Firefox
1688
1793
  // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
1689
1794
  // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
@@ -1695,12 +1800,10 @@ function setupScroll () {
1695
1800
  var stateCopy = extend({}, window.history.state);
1696
1801
  stateCopy.key = getStateKey();
1697
1802
  window.history.replaceState(stateCopy, '', absolutePath);
1698
- window.addEventListener('popstate', function (e) {
1699
- saveScrollPosition();
1700
- if (e.state && e.state.key) {
1701
- setStateKey(e.state.key);
1702
- }
1703
- });
1803
+ window.addEventListener('popstate', handlePopState);
1804
+ return function () {
1805
+ window.removeEventListener('popstate', handlePopState);
1806
+ }
1704
1807
  }
1705
1808
 
1706
1809
  function handleScroll (
@@ -1762,6 +1865,13 @@ function saveScrollPosition () {
1762
1865
  }
1763
1866
  }
1764
1867
 
1868
+ function handlePopState (e) {
1869
+ saveScrollPosition();
1870
+ if (e.state && e.state.key) {
1871
+ setStateKey(e.state.key);
1872
+ }
1873
+ }
1874
+
1765
1875
  function getScrollPosition () {
1766
1876
  var key = getStateKey();
1767
1877
  if (key) {
@@ -1827,7 +1937,17 @@ function scrollToPosition (shouldScroll, position) {
1827
1937
  }
1828
1938
 
1829
1939
  if (position) {
1830
- 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
+ }
1831
1951
  }
1832
1952
  }
1833
1953
 
@@ -1847,7 +1967,7 @@ var supportsPushState =
1847
1967
  return false
1848
1968
  }
1849
1969
 
1850
- return window.history && 'pushState' in window.history
1970
+ return window.history && typeof window.history.pushState === 'function'
1851
1971
  })();
1852
1972
 
1853
1973
  function pushState (url, replace) {
@@ -1892,6 +2012,89 @@ function runQueue (queue, fn, cb) {
1892
2012
  step(0);
1893
2013
  }
1894
2014
 
2015
+ // When changing thing, also edit router.d.ts
2016
+ var NavigationFailureType = {
2017
+ redirected: 2,
2018
+ aborted: 4,
2019
+ cancelled: 8,
2020
+ duplicated: 16
2021
+ };
2022
+
2023
+ function createNavigationRedirectedError (from, to) {
2024
+ return createRouterError(
2025
+ from,
2026
+ to,
2027
+ NavigationFailureType.redirected,
2028
+ ("Redirected when going from \"" + (from.fullPath) + "\" to \"" + (stringifyRoute(
2029
+ to
2030
+ )) + "\" via a navigation guard.")
2031
+ )
2032
+ }
2033
+
2034
+ function createNavigationDuplicatedError (from, to) {
2035
+ var error = createRouterError(
2036
+ from,
2037
+ to,
2038
+ NavigationFailureType.duplicated,
2039
+ ("Avoided redundant navigation to current location: \"" + (from.fullPath) + "\".")
2040
+ );
2041
+ // backwards compatible with the first introduction of Errors
2042
+ error.name = 'NavigationDuplicated';
2043
+ return error
2044
+ }
2045
+
2046
+ function createNavigationCancelledError (from, to) {
2047
+ return createRouterError(
2048
+ from,
2049
+ to,
2050
+ NavigationFailureType.cancelled,
2051
+ ("Navigation cancelled from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" with a new navigation.")
2052
+ )
2053
+ }
2054
+
2055
+ function createNavigationAbortedError (from, to) {
2056
+ return createRouterError(
2057
+ from,
2058
+ to,
2059
+ NavigationFailureType.aborted,
2060
+ ("Navigation aborted from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" via a navigation guard.")
2061
+ )
2062
+ }
2063
+
2064
+ function createRouterError (from, to, type, message) {
2065
+ var error = new Error(message);
2066
+ error._isRouter = true;
2067
+ error.from = from;
2068
+ error.to = to;
2069
+ error.type = type;
2070
+
2071
+ return error
2072
+ }
2073
+
2074
+ var propertiesToLog = ['params', 'query', 'hash'];
2075
+
2076
+ function stringifyRoute (to) {
2077
+ if (typeof to === 'string') { return to }
2078
+ if ('path' in to) { return to.path }
2079
+ var location = {};
2080
+ propertiesToLog.forEach(function (key) {
2081
+ if (key in to) { location[key] = to[key]; }
2082
+ });
2083
+ return JSON.stringify(location, null, 2)
2084
+ }
2085
+
2086
+ function isError (err) {
2087
+ return Object.prototype.toString.call(err).indexOf('Error') > -1
2088
+ }
2089
+
2090
+ function isNavigationFailure (err, errorType) {
2091
+ return (
2092
+ isError(err) &&
2093
+ err._isRouter &&
2094
+ (errorType == null || err.type === errorType)
2095
+ )
2096
+ }
2097
+
1895
2098
  /* */
1896
2099
 
1897
2100
  function resolveAsyncComponents (matched) {
@@ -2001,33 +2204,6 @@ function once (fn) {
2001
2204
  }
2002
2205
  }
2003
2206
 
2004
- var NavigationDuplicated = /*@__PURE__*/(function (Error) {
2005
- function NavigationDuplicated (normalizedLocation) {
2006
- Error.call(this);
2007
- this.name = this._name = 'NavigationDuplicated';
2008
- // passing the message to super() doesn't seem to work in the transpiled version
2009
- this.message = "Navigating to current location (\"" + (normalizedLocation.fullPath) + "\") is not allowed";
2010
- // add a stack property so services like Sentry can correctly display it
2011
- Object.defineProperty(this, 'stack', {
2012
- value: new Error().stack,
2013
- writable: true,
2014
- configurable: true
2015
- });
2016
- // we could also have used
2017
- // Error.captureStackTrace(this, this.constructor)
2018
- // but it only exists on node and chrome
2019
- }
2020
-
2021
- if ( Error ) NavigationDuplicated.__proto__ = Error;
2022
- NavigationDuplicated.prototype = Object.create( Error && Error.prototype );
2023
- NavigationDuplicated.prototype.constructor = NavigationDuplicated;
2024
-
2025
- return NavigationDuplicated;
2026
- }(Error));
2027
-
2028
- // support IE9
2029
- NavigationDuplicated._name = 'NavigationDuplicated';
2030
-
2031
2207
  /* */
2032
2208
 
2033
2209
  var History = function History (router, base) {
@@ -2040,6 +2216,7 @@ var History = function History (router, base) {
2040
2216
  this.readyCbs = [];
2041
2217
  this.readyErrorCbs = [];
2042
2218
  this.errorCbs = [];
2219
+ this.listeners = [];
2043
2220
  };
2044
2221
 
2045
2222
  History.prototype.listen = function listen (cb) {
@@ -2068,13 +2245,27 @@ History.prototype.transitionTo = function transitionTo (
2068
2245
  ) {
2069
2246
  var this$1 = this;
2070
2247
 
2071
- var route = this.router.match(location, this.current);
2248
+ var route;
2249
+ // catch redirect option
2250
+ try {
2251
+ route = this.router.match(location, this.current);
2252
+ } catch (e) {
2253
+ this.errorCbs.forEach(function (cb) {
2254
+ cb(e);
2255
+ });
2256
+ // Exception should still be thrown
2257
+ throw e
2258
+ }
2259
+ var prev = this.current;
2072
2260
  this.confirmTransition(
2073
2261
  route,
2074
2262
  function () {
2075
2263
  this$1.updateRoute(route);
2076
2264
  onComplete && onComplete(route);
2077
2265
  this$1.ensureURL();
2266
+ this$1.router.afterHooks.forEach(function (hook) {
2267
+ hook && hook(route, prev);
2268
+ });
2078
2269
 
2079
2270
  // fire ready cbs once
2080
2271
  if (!this$1.ready) {
@@ -2089,10 +2280,14 @@ History.prototype.transitionTo = function transitionTo (
2089
2280
  onAbort(err);
2090
2281
  }
2091
2282
  if (err && !this$1.ready) {
2092
- this$1.ready = true;
2093
- this$1.readyErrorCbs.forEach(function (cb) {
2094
- cb(err);
2095
- });
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;
2287
+ this$1.readyErrorCbs.forEach(function (cb) {
2288
+ cb(err);
2289
+ });
2290
+ }
2096
2291
  }
2097
2292
  }
2098
2293
  );
@@ -2102,29 +2297,37 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2102
2297
  var this$1 = this;
2103
2298
 
2104
2299
  var current = this.current;
2300
+ this.pending = route;
2105
2301
  var abort = function (err) {
2106
- // When the user navigates through history through back/forward buttons
2107
- // we do not want to throw the error. We only throw it if directly calling
2108
- // push/replace. That's why it's not included in isError
2109
- if (!isExtendedError(NavigationDuplicated, err) && isError(err)) {
2302
+ // changed after adding errors
2303
+ // before that change, redirect and aborted navigation would produce an err == null
2304
+ if (!isNavigationFailure(err) && isError(err)) {
2110
2305
  if (this$1.errorCbs.length) {
2111
2306
  this$1.errorCbs.forEach(function (cb) {
2112
2307
  cb(err);
2113
2308
  });
2114
2309
  } else {
2115
- warn(false, 'uncaught error during route navigation:');
2310
+ if (process.env.NODE_ENV !== 'production') {
2311
+ warn(false, 'uncaught error during route navigation:');
2312
+ }
2116
2313
  console.error(err);
2117
2314
  }
2118
2315
  }
2119
2316
  onAbort && onAbort(err);
2120
2317
  };
2318
+ var lastRouteIndex = route.matched.length - 1;
2319
+ var lastCurrentIndex = current.matched.length - 1;
2121
2320
  if (
2122
2321
  isSameRoute(route, current) &&
2123
2322
  // in the case the route map has been dynamically appended to
2124
- route.matched.length === current.matched.length
2323
+ lastRouteIndex === lastCurrentIndex &&
2324
+ route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
2125
2325
  ) {
2126
2326
  this.ensureURL();
2127
- return abort(new NavigationDuplicated(route))
2327
+ if (route.hash) {
2328
+ handleScroll(this.router, current, route, false);
2329
+ }
2330
+ return abort(createNavigationDuplicatedError(current, route))
2128
2331
  }
2129
2332
 
2130
2333
  var ref = resolveQueue(
@@ -2148,15 +2351,17 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2148
2351
  resolveAsyncComponents(activated)
2149
2352
  );
2150
2353
 
2151
- this.pending = route;
2152
2354
  var iterator = function (hook, next) {
2153
2355
  if (this$1.pending !== route) {
2154
- return abort()
2356
+ return abort(createNavigationCancelledError(current, route))
2155
2357
  }
2156
2358
  try {
2157
2359
  hook(route, current, function (to) {
2158
- if (to === false || isError(to)) {
2360
+ if (to === false) {
2159
2361
  // next(false) -> abort navigation, ensure current URL
2362
+ this$1.ensureURL(true);
2363
+ abort(createNavigationAbortedError(current, route));
2364
+ } else if (isError(to)) {
2160
2365
  this$1.ensureURL(true);
2161
2366
  abort(to);
2162
2367
  } else if (
@@ -2165,7 +2370,7 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2165
2370
  (typeof to.path === 'string' || typeof to.name === 'string'))
2166
2371
  ) {
2167
2372
  // next('/') or next({ path: '/' }) -> redirect
2168
- abort();
2373
+ abort(createNavigationRedirectedError(current, route));
2169
2374
  if (typeof to === 'object' && to.replace) {
2170
2375
  this$1.replace(to);
2171
2376
  } else {
@@ -2182,23 +2387,19 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2182
2387
  };
2183
2388
 
2184
2389
  runQueue(queue, iterator, function () {
2185
- var postEnterCbs = [];
2186
- var isValid = function () { return this$1.current === route; };
2187
2390
  // wait until async components are resolved before
2188
2391
  // extracting in-component enter guards
2189
- var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
2392
+ var enterGuards = extractEnterGuards(activated);
2190
2393
  var queue = enterGuards.concat(this$1.router.resolveHooks);
2191
2394
  runQueue(queue, iterator, function () {
2192
2395
  if (this$1.pending !== route) {
2193
- return abort()
2396
+ return abort(createNavigationCancelledError(current, route))
2194
2397
  }
2195
2398
  this$1.pending = null;
2196
2399
  onComplete(route);
2197
2400
  if (this$1.router.app) {
2198
2401
  this$1.router.app.$nextTick(function () {
2199
- postEnterCbs.forEach(function (cb) {
2200
- cb();
2201
- });
2402
+ handleRouteEntered(route);
2202
2403
  });
2203
2404
  }
2204
2405
  });
@@ -2206,12 +2407,24 @@ History.prototype.confirmTransition = function confirmTransition (route, onCompl
2206
2407
  };
2207
2408
 
2208
2409
  History.prototype.updateRoute = function updateRoute (route) {
2209
- var prev = this.current;
2210
2410
  this.current = route;
2211
2411
  this.cb && this.cb(route);
2212
- this.router.afterHooks.forEach(function (hook) {
2213
- hook && hook(route, prev);
2412
+ };
2413
+
2414
+ History.prototype.setupListeners = function setupListeners () {
2415
+ // Default implementation is empty
2416
+ };
2417
+
2418
+ History.prototype.teardown = function teardown () {
2419
+ // clean up event listeners
2420
+ this.listeners.forEach(function (cleanupListener) {
2421
+ cleanupListener();
2214
2422
  });
2423
+ this.listeners = [];
2424
+
2425
+ // reset current history route
2426
+ this.current = START;
2427
+ this.pending = null;
2215
2428
  };
2216
2429
 
2217
2430
  function normalizeBase (base) {
@@ -2297,15 +2510,13 @@ function bindGuard (guard, instance) {
2297
2510
  }
2298
2511
 
2299
2512
  function extractEnterGuards (
2300
- activated,
2301
- cbs,
2302
- isValid
2513
+ activated
2303
2514
  ) {
2304
2515
  return extractGuards(
2305
2516
  activated,
2306
2517
  'beforeRouteEnter',
2307
2518
  function (guard, _, match, key) {
2308
- return bindEnterGuard(guard, match, key, cbs, isValid)
2519
+ return bindEnterGuard(guard, match, key)
2309
2520
  }
2310
2521
  )
2311
2522
  }
@@ -2313,68 +2524,56 @@ function extractEnterGuards (
2313
2524
  function bindEnterGuard (
2314
2525
  guard,
2315
2526
  match,
2316
- key,
2317
- cbs,
2318
- isValid
2527
+ key
2319
2528
  ) {
2320
2529
  return function routeEnterGuard (to, from, next) {
2321
2530
  return guard(to, from, function (cb) {
2322
2531
  if (typeof cb === 'function') {
2323
- cbs.push(function () {
2324
- // #750
2325
- // if a router-view is wrapped with an out-in transition,
2326
- // the instance may not have been registered at this time.
2327
- // we will need to poll for registration until current route
2328
- // is no longer valid.
2329
- poll(cb, match.instances, key, isValid);
2330
- });
2532
+ if (!match.enteredCbs[key]) {
2533
+ match.enteredCbs[key] = [];
2534
+ }
2535
+ match.enteredCbs[key].push(cb);
2331
2536
  }
2332
2537
  next(cb);
2333
2538
  })
2334
2539
  }
2335
2540
  }
2336
2541
 
2337
- function poll (
2338
- cb, // somehow flow cannot infer this is a function
2339
- instances,
2340
- key,
2341
- isValid
2342
- ) {
2343
- if (
2344
- instances[key] &&
2345
- !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
2346
- ) {
2347
- cb(instances[key]);
2348
- } else if (isValid()) {
2349
- setTimeout(function () {
2350
- poll(cb, instances, key, isValid);
2351
- }, 16);
2352
- }
2353
- }
2354
-
2355
2542
  /* */
2356
2543
 
2357
2544
  var HTML5History = /*@__PURE__*/(function (History) {
2358
2545
  function HTML5History (router, base) {
2546
+ History.call(this, router, base);
2547
+
2548
+ this._startLocation = getLocation(this.base);
2549
+ }
2550
+
2551
+ if ( History ) HTML5History.__proto__ = History;
2552
+ HTML5History.prototype = Object.create( History && History.prototype );
2553
+ HTML5History.prototype.constructor = HTML5History;
2554
+
2555
+ HTML5History.prototype.setupListeners = function setupListeners () {
2359
2556
  var this$1 = this;
2360
2557
 
2361
- History.call(this, router, base);
2558
+ if (this.listeners.length > 0) {
2559
+ return
2560
+ }
2362
2561
 
2562
+ var router = this.router;
2363
2563
  var expectScroll = router.options.scrollBehavior;
2364
2564
  var supportsScroll = supportsPushState && expectScroll;
2365
2565
 
2366
2566
  if (supportsScroll) {
2367
- setupScroll();
2567
+ this.listeners.push(setupScroll());
2368
2568
  }
2369
2569
 
2370
- var initLocation = getLocation(this.base);
2371
- window.addEventListener('popstate', function (e) {
2570
+ var handleRoutingEvent = function () {
2372
2571
  var current = this$1.current;
2373
2572
 
2374
2573
  // Avoiding first `popstate` event dispatched in some browsers but first
2375
2574
  // history route not updated since async guard at the same time.
2376
2575
  var location = getLocation(this$1.base);
2377
- if (this$1.current === START && location === initLocation) {
2576
+ if (this$1.current === START && location === this$1._startLocation) {
2378
2577
  return
2379
2578
  }
2380
2579
 
@@ -2383,12 +2582,12 @@ var HTML5History = /*@__PURE__*/(function (History) {
2383
2582
  handleScroll(router, route, current, true);
2384
2583
  }
2385
2584
  });
2585
+ };
2586
+ window.addEventListener('popstate', handleRoutingEvent);
2587
+ this.listeners.push(function () {
2588
+ window.removeEventListener('popstate', handleRoutingEvent);
2386
2589
  });
2387
- }
2388
-
2389
- if ( History ) HTML5History.__proto__ = History;
2390
- HTML5History.prototype = Object.create( History && History.prototype );
2391
- HTML5History.prototype.constructor = HTML5History;
2590
+ };
2392
2591
 
2393
2592
  HTML5History.prototype.go = function go (n) {
2394
2593
  window.history.go(n);
@@ -2433,8 +2632,13 @@ var HTML5History = /*@__PURE__*/(function (History) {
2433
2632
  }(History));
2434
2633
 
2435
2634
  function getLocation (base) {
2436
- var path = decodeURI(window.location.pathname);
2437
- if (base && path.indexOf(base) === 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))) {
2438
2642
  path = path.slice(base.length);
2439
2643
  }
2440
2644
  return (path || '/') + window.location.search + window.location.hash
@@ -2461,31 +2665,40 @@ var HashHistory = /*@__PURE__*/(function (History) {
2461
2665
  HashHistory.prototype.setupListeners = function setupListeners () {
2462
2666
  var this$1 = this;
2463
2667
 
2668
+ if (this.listeners.length > 0) {
2669
+ return
2670
+ }
2671
+
2464
2672
  var router = this.router;
2465
2673
  var expectScroll = router.options.scrollBehavior;
2466
2674
  var supportsScroll = supportsPushState && expectScroll;
2467
2675
 
2468
2676
  if (supportsScroll) {
2469
- setupScroll();
2677
+ this.listeners.push(setupScroll());
2470
2678
  }
2471
2679
 
2472
- window.addEventListener(
2473
- supportsPushState ? 'popstate' : 'hashchange',
2474
- function () {
2475
- var current = this$1.current;
2476
- if (!ensureSlash()) {
2477
- return
2478
- }
2479
- this$1.transitionTo(getHash(), function (route) {
2480
- if (supportsScroll) {
2481
- handleScroll(this$1.router, route, current, true);
2482
- }
2483
- if (!supportsPushState) {
2484
- replaceHash(route.fullPath);
2485
- }
2486
- });
2680
+ var handleRoutingEvent = function () {
2681
+ var current = this$1.current;
2682
+ if (!ensureSlash()) {
2683
+ return
2487
2684
  }
2685
+ this$1.transitionTo(getHash(), function (route) {
2686
+ if (supportsScroll) {
2687
+ handleScroll(this$1.router, route, current, true);
2688
+ }
2689
+ if (!supportsPushState) {
2690
+ replaceHash(route.fullPath);
2691
+ }
2692
+ });
2693
+ };
2694
+ var eventType = supportsPushState ? 'popstate' : 'hashchange';
2695
+ window.addEventListener(
2696
+ eventType,
2697
+ handleRoutingEvent
2488
2698
  );
2699
+ this.listeners.push(function () {
2700
+ window.removeEventListener(eventType, handleRoutingEvent);
2701
+ });
2489
2702
  };
2490
2703
 
2491
2704
  HashHistory.prototype.push = function push (location, onComplete, onAbort) {
@@ -2564,17 +2777,6 @@ function getHash () {
2564
2777
  if (index < 0) { return '' }
2565
2778
 
2566
2779
  href = href.slice(index + 1);
2567
- // decode the hash but not the search or hash
2568
- // as search(query) is already decoded
2569
- var searchIndex = href.indexOf('?');
2570
- if (searchIndex < 0) {
2571
- var hashIndex = href.indexOf('#');
2572
- if (hashIndex > -1) {
2573
- href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2574
- } else { href = decodeURI(href); }
2575
- } else {
2576
- href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2577
- }
2578
2780
 
2579
2781
  return href
2580
2782
  }
@@ -2653,11 +2855,15 @@ var AbstractHistory = /*@__PURE__*/(function (History) {
2653
2855
  this.confirmTransition(
2654
2856
  route,
2655
2857
  function () {
2858
+ var prev = this$1.current;
2656
2859
  this$1.index = targetIndex;
2657
2860
  this$1.updateRoute(route);
2861
+ this$1.router.afterHooks.forEach(function (hook) {
2862
+ hook && hook(route, prev);
2863
+ });
2658
2864
  },
2659
2865
  function (err) {
2660
- if (isExtendedError(NavigationDuplicated, err)) {
2866
+ if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
2661
2867
  this$1.index = targetIndex;
2662
2868
  }
2663
2869
  }
@@ -2678,11 +2884,12 @@ var AbstractHistory = /*@__PURE__*/(function (History) {
2678
2884
 
2679
2885
  /* */
2680
2886
 
2681
-
2682
-
2683
2887
  var KduRouter = function KduRouter (options) {
2684
2888
  if ( options === void 0 ) options = {};
2685
2889
 
2890
+ if (process.env.NODE_ENV !== 'production') {
2891
+ warn(this instanceof KduRouter, "Router must be called with the new operator.");
2892
+ }
2686
2893
  this.app = null;
2687
2894
  this.apps = [];
2688
2895
  this.options = options;
@@ -2692,7 +2899,8 @@ var KduRouter = function KduRouter (options) {
2692
2899
  this.matcher = createMatcher(options.routes || [], this);
2693
2900
 
2694
2901
  var mode = options.mode || 'hash';
2695
- this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false;
2902
+ this.fallback =
2903
+ mode === 'history' && !supportsPushState && options.fallback !== false;
2696
2904
  if (this.fallback) {
2697
2905
  mode = 'hash';
2698
2906
  }
@@ -2720,11 +2928,7 @@ var KduRouter = function KduRouter (options) {
2720
2928
 
2721
2929
  var prototypeAccessors = { currentRoute: { configurable: true } };
2722
2930
 
2723
- KduRouter.prototype.match = function match (
2724
- raw,
2725
- current,
2726
- redirectedFrom
2727
- ) {
2931
+ KduRouter.prototype.match = function match (raw, current, redirectedFrom) {
2728
2932
  return this.matcher.match(raw, current, redirectedFrom)
2729
2933
  };
2730
2934
 
@@ -2735,11 +2939,12 @@ prototypeAccessors.currentRoute.get = function () {
2735
2939
  KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2736
2940
  var this$1 = this;
2737
2941
 
2738
- process.env.NODE_ENV !== 'production' && assert(
2739
- install.installed,
2740
- "not installed. Make sure to call `Kdu.use(KduRouter)` " +
2741
- "before creating root instance."
2742
- );
2942
+ process.env.NODE_ENV !== 'production' &&
2943
+ assert(
2944
+ install.installed,
2945
+ "not installed. Make sure to call `Kdu.use(KduRouter)` " +
2946
+ "before creating root instance."
2947
+ );
2743
2948
 
2744
2949
  this.apps.push(app);
2745
2950
 
@@ -2751,6 +2956,8 @@ KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2751
2956
  // ensure we still have a main app or null if no apps
2752
2957
  // we do not release the router so it can be reused
2753
2958
  if (this$1.app === app) { this$1.app = this$1.apps[0] || null; }
2959
+
2960
+ if (!this$1.app) { this$1.history.teardown(); }
2754
2961
  });
2755
2962
 
2756
2963
  // main app previously initialized
@@ -2763,16 +2970,24 @@ KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2763
2970
 
2764
2971
  var history = this.history;
2765
2972
 
2766
- if (history instanceof HTML5History) {
2767
- history.transitionTo(history.getCurrentLocation());
2768
- } else if (history instanceof HashHistory) {
2769
- var setupHashListener = function () {
2973
+ if (history instanceof HTML5History || history instanceof HashHistory) {
2974
+ var handleInitialScroll = function (routeOrError) {
2975
+ var from = history.current;
2976
+ var expectScroll = this$1.options.scrollBehavior;
2977
+ var supportsScroll = supportsPushState && expectScroll;
2978
+
2979
+ if (supportsScroll && 'fullPath' in routeOrError) {
2980
+ handleScroll(this$1, routeOrError, from, false);
2981
+ }
2982
+ };
2983
+ var setupListeners = function (routeOrError) {
2770
2984
  history.setupListeners();
2985
+ handleInitialScroll(routeOrError);
2771
2986
  };
2772
2987
  history.transitionTo(
2773
2988
  history.getCurrentLocation(),
2774
- setupHashListener,
2775
- setupHashListener
2989
+ setupListeners,
2990
+ setupListeners
2776
2991
  );
2777
2992
  }
2778
2993
 
@@ -2850,11 +3065,14 @@ KduRouter.prototype.getMatchedComponents = function getMatchedComponents (to) {
2850
3065
  if (!route) {
2851
3066
  return []
2852
3067
  }
2853
- return [].concat.apply([], route.matched.map(function (m) {
2854
- return Object.keys(m.components).map(function (key) {
2855
- return m.components[key]
3068
+ return [].concat.apply(
3069
+ [],
3070
+ route.matched.map(function (m) {
3071
+ return Object.keys(m.components).map(function (key) {
3072
+ return m.components[key]
3073
+ })
2856
3074
  })
2857
- }))
3075
+ )
2858
3076
  };
2859
3077
 
2860
3078
  KduRouter.prototype.resolve = function resolve (
@@ -2863,12 +3081,7 @@ KduRouter.prototype.resolve = function resolve (
2863
3081
  append
2864
3082
  ) {
2865
3083
  current = current || this.history.current;
2866
- var location = normalizeLocation(
2867
- to,
2868
- current,
2869
- append,
2870
- this
2871
- );
3084
+ var location = normalizeLocation(to, current, append, this);
2872
3085
  var route = this.match(location, current);
2873
3086
  var fullPath = route.redirectedFrom || route.fullPath;
2874
3087
  var base = this.history.base;
@@ -2883,7 +3096,21 @@ KduRouter.prototype.resolve = function resolve (
2883
3096
  }
2884
3097
  };
2885
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
+
2886
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
+ }
2887
3114
  this.matcher.addRoutes(routes);
2888
3115
  if (this.history.current !== START) {
2889
3116
  this.history.transitionTo(this.history.getCurrentLocation());
@@ -2906,7 +3133,10 @@ function createHref (base, fullPath, mode) {
2906
3133
  }
2907
3134
 
2908
3135
  KduRouter.install = install;
2909
- KduRouter.version = '3.1.7';
3136
+ KduRouter.version = '3.5.4';
3137
+ KduRouter.isNavigationFailure = isNavigationFailure;
3138
+ KduRouter.NavigationFailureType = NavigationFailureType;
3139
+ KduRouter.START_LOCATION = START;
2910
3140
 
2911
3141
  if (inBrowser && window.Kdu) {
2912
3142
  window.Kdu.use(KduRouter);