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,12 +1,12 @@
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
  */
6
6
  (function (global, factory) {
7
7
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
8
8
  typeof define === 'function' && define.amd ? define(factory) :
9
- (global = global || self, global.KduRouter = factory());
9
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.KduRouter = factory());
10
10
  }(this, (function () { 'use strict';
11
11
 
12
12
  /* */
@@ -18,23 +18,11 @@
18
18
  }
19
19
 
20
20
  function warn (condition, message) {
21
- if ( !condition) {
21
+ if (!condition) {
22
22
  typeof console !== 'undefined' && console.warn(("[kdu-router] " + message));
23
23
  }
24
24
  }
25
25
 
26
- function isError (err) {
27
- return Object.prototype.toString.call(err).indexOf('Error') > -1
28
- }
29
-
30
- function isExtendedError (constructor, err) {
31
- return (
32
- err instanceof constructor ||
33
- // _name is to support IE9 too
34
- (err && (err.name === constructor.name || err._name === constructor._name))
35
- )
36
- }
37
-
38
26
  function extend (a, b) {
39
27
  for (var key in b) {
40
28
  a[key] = b[key];
@@ -42,158 +30,6 @@
42
30
  return a
43
31
  }
44
32
 
45
- var View = {
46
- name: 'RouterView',
47
- functional: true,
48
- props: {
49
- name: {
50
- type: String,
51
- default: 'default'
52
- }
53
- },
54
- render: function render (_, ref) {
55
- var props = ref.props;
56
- var children = ref.children;
57
- var parent = ref.parent;
58
- var data = ref.data;
59
-
60
- // used by devtools to display a router-view badge
61
- data.routerView = true;
62
-
63
- // directly use parent context's createElement() function
64
- // so that components rendered by router-view can resolve named slots
65
- var h = parent.$createElement;
66
- var name = props.name;
67
- var route = parent.$route;
68
- var cache = parent._routerViewCache || (parent._routerViewCache = {});
69
-
70
- // determine current view depth, also check to see if the tree
71
- // has been toggled inactive but kept-alive.
72
- var depth = 0;
73
- var inactive = false;
74
- while (parent && parent._routerRoot !== parent) {
75
- var knodeData = parent.$knode ? parent.$knode.data : {};
76
- if (knodeData.routerView) {
77
- depth++;
78
- }
79
- if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
80
- inactive = true;
81
- }
82
- parent = parent.$parent;
83
- }
84
- data.routerViewDepth = depth;
85
-
86
- // render previous view if the tree is inactive and kept-alive
87
- if (inactive) {
88
- var cachedData = cache[name];
89
- var cachedComponent = cachedData && cachedData.component;
90
- if (cachedComponent) {
91
- // #2301
92
- // pass props
93
- if (cachedData.configProps) {
94
- fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
95
- }
96
- return h(cachedComponent, data, children)
97
- } else {
98
- // render previous empty view
99
- return h()
100
- }
101
- }
102
-
103
- var matched = route.matched[depth];
104
- var component = matched && matched.components[name];
105
-
106
- // render empty node if no matched route or no config component
107
- if (!matched || !component) {
108
- cache[name] = null;
109
- return h()
110
- }
111
-
112
- // cache component
113
- cache[name] = { component: component };
114
-
115
- // attach instance registration hook
116
- // this will be called in the instance's injected lifecycle hooks
117
- data.registerRouteInstance = function (vm, val) {
118
- // val could be undefined for unregistration
119
- var current = matched.instances[name];
120
- if (
121
- (val && current !== vm) ||
122
- (!val && current === vm)
123
- ) {
124
- matched.instances[name] = val;
125
- }
126
- }
127
-
128
- // also register instance in prepatch hook
129
- // in case the same component instance is reused across different routes
130
- ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
131
- matched.instances[name] = knode.componentInstance;
132
- };
133
-
134
- // register instance in init hook
135
- // in case kept-alive component be actived when routes changed
136
- data.hook.init = function (knode) {
137
- if (knode.data.keepAlive &&
138
- knode.componentInstance &&
139
- knode.componentInstance !== matched.instances[name]
140
- ) {
141
- matched.instances[name] = knode.componentInstance;
142
- }
143
- };
144
-
145
- var configProps = matched.props && matched.props[name];
146
- // save route and configProps in cachce
147
- if (configProps) {
148
- extend(cache[name], {
149
- route: route,
150
- configProps: configProps
151
- });
152
- fillPropsinData(component, data, route, configProps);
153
- }
154
-
155
- return h(component, data, children)
156
- }
157
- };
158
-
159
- function fillPropsinData (component, data, route, configProps) {
160
- // resolve props
161
- var propsToPass = data.props = resolveProps(route, configProps);
162
- if (propsToPass) {
163
- // clone to prevent mutation
164
- propsToPass = data.props = extend({}, propsToPass);
165
- // pass non-declared props as attrs
166
- var attrs = data.attrs = data.attrs || {};
167
- for (var key in propsToPass) {
168
- if (!component.props || !(key in component.props)) {
169
- attrs[key] = propsToPass[key];
170
- delete propsToPass[key];
171
- }
172
- }
173
- }
174
- }
175
-
176
- function resolveProps (route, config) {
177
- switch (typeof config) {
178
- case 'undefined':
179
- return
180
- case 'object':
181
- return config
182
- case 'function':
183
- return config(route)
184
- case 'boolean':
185
- return config ? route.params : undefined
186
- default:
187
- {
188
- warn(
189
- false,
190
- "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
191
- "expecting an object, function or boolean."
192
- );
193
- }
194
- }
195
- }
196
-
197
33
  /* */
198
34
 
199
35
  var encodeReserveRE = /[!'()*]/g;
@@ -204,10 +40,19 @@
204
40
  // - escapes [!'()*]
205
41
  // - preserve commas
206
42
  var encode = function (str) { return encodeURIComponent(str)
207
- .replace(encodeReserveRE, encodeReserveReplacer)
208
- .replace(commaRE, ','); };
43
+ .replace(encodeReserveRE, encodeReserveReplacer)
44
+ .replace(commaRE, ','); };
209
45
 
210
- var decode = decodeURIComponent;
46
+ function decode (str) {
47
+ try {
48
+ return decodeURIComponent(str)
49
+ } catch (err) {
50
+ {
51
+ warn(false, ("Error decoding \"" + str + "\". Leaving it intact."));
52
+ }
53
+ }
54
+ return str
55
+ }
211
56
 
212
57
  function resolveQuery (
213
58
  query,
@@ -221,15 +66,20 @@
221
66
  try {
222
67
  parsedQuery = parse(query || '');
223
68
  } catch (e) {
224
- warn(false, e.message);
69
+ warn(false, e.message);
225
70
  parsedQuery = {};
226
71
  }
227
72
  for (var key in extraQuery) {
228
- parsedQuery[key] = extraQuery[key];
73
+ var value = extraQuery[key];
74
+ parsedQuery[key] = Array.isArray(value)
75
+ ? value.map(castQueryParamValue)
76
+ : castQueryParamValue(value);
229
77
  }
230
78
  return parsedQuery
231
79
  }
232
80
 
81
+ var castQueryParamValue = function (value) { return (value == null || typeof value === 'object' ? value : String(value)); };
82
+
233
83
  function parseQuery (query) {
234
84
  var res = {};
235
85
 
@@ -242,9 +92,7 @@
242
92
  query.split('&').forEach(function (param) {
243
93
  var parts = param.replace(/\+/g, ' ').split('=');
244
94
  var key = decode(parts.shift());
245
- var val = parts.length > 0
246
- ? decode(parts.join('='))
247
- : null;
95
+ var val = parts.length > 0 ? decode(parts.join('=')) : null;
248
96
 
249
97
  if (res[key] === undefined) {
250
98
  res[key] = val;
@@ -259,34 +107,39 @@
259
107
  }
260
108
 
261
109
  function stringifyQuery (obj) {
262
- var res = obj ? Object.keys(obj).map(function (key) {
263
- var val = obj[key];
110
+ var res = obj
111
+ ? Object.keys(obj)
112
+ .map(function (key) {
113
+ var val = obj[key];
264
114
 
265
- if (val === undefined) {
266
- return ''
267
- }
268
-
269
- if (val === null) {
270
- return encode(key)
271
- }
115
+ if (val === undefined) {
116
+ return ''
117
+ }
272
118
 
273
- if (Array.isArray(val)) {
274
- var result = [];
275
- val.forEach(function (val2) {
276
- if (val2 === undefined) {
277
- return
119
+ if (val === null) {
120
+ return encode(key)
278
121
  }
279
- if (val2 === null) {
280
- result.push(encode(key));
281
- } else {
282
- result.push(encode(key) + '=' + encode(val2));
122
+
123
+ if (Array.isArray(val)) {
124
+ var result = [];
125
+ val.forEach(function (val2) {
126
+ if (val2 === undefined) {
127
+ return
128
+ }
129
+ if (val2 === null) {
130
+ result.push(encode(key));
131
+ } else {
132
+ result.push(encode(key) + '=' + encode(val2));
133
+ }
134
+ });
135
+ return result.join('&')
283
136
  }
284
- });
285
- return result.join('&')
286
- }
287
137
 
288
- return encode(key) + '=' + encode(val)
289
- }).filter(function (x) { return x.length > 0; }).join('&') : null;
138
+ return encode(key) + '=' + encode(val)
139
+ })
140
+ .filter(function (x) { return x.length > 0; })
141
+ .join('&')
142
+ : null;
290
143
  return res ? ("?" + res) : ''
291
144
  }
292
145
 
@@ -363,68 +216,244 @@
363
216
  return (path || '/') + stringify(query) + hash
364
217
  }
365
218
 
366
- function isSameRoute (a, b) {
219
+ function isSameRoute (a, b, onlyPath) {
367
220
  if (b === START) {
368
221
  return a === b
369
222
  } else if (!b) {
370
223
  return false
371
224
  } else if (a.path && b.path) {
372
- return (
373
- a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
225
+ return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
374
226
  a.hash === b.hash &&
375
- isObjectEqual(a.query, b.query)
376
- )
227
+ isObjectEqual(a.query, b.query))
377
228
  } else if (a.name && b.name) {
378
229
  return (
379
230
  a.name === b.name &&
380
- a.hash === b.hash &&
231
+ (onlyPath || (
232
+ a.hash === b.hash &&
381
233
  isObjectEqual(a.query, b.query) &&
382
- isObjectEqual(a.params, b.params)
234
+ isObjectEqual(a.params, b.params))
235
+ )
383
236
  )
384
237
  } else {
385
238
  return false
386
239
  }
387
240
  }
388
241
 
389
- function isObjectEqual (a, b) {
390
- if ( a === void 0 ) a = {};
391
- if ( b === void 0 ) b = {};
242
+ function isObjectEqual (a, b) {
243
+ if ( a === void 0 ) a = {};
244
+ if ( b === void 0 ) b = {};
245
+
246
+ // handle null value #1566
247
+ if (!a || !b) { return a === b }
248
+ var aKeys = Object.keys(a).sort();
249
+ var bKeys = Object.keys(b).sort();
250
+ if (aKeys.length !== bKeys.length) {
251
+ return false
252
+ }
253
+ return aKeys.every(function (key, i) {
254
+ var aVal = a[key];
255
+ var bKey = bKeys[i];
256
+ if (bKey !== key) { return false }
257
+ var bVal = b[key];
258
+ // query values can be null and undefined
259
+ if (aVal == null || bVal == null) { return aVal === bVal }
260
+ // check nested equality
261
+ if (typeof aVal === 'object' && typeof bVal === 'object') {
262
+ return isObjectEqual(aVal, bVal)
263
+ }
264
+ return String(aVal) === String(bVal)
265
+ })
266
+ }
267
+
268
+ function isIncludedRoute (current, target) {
269
+ return (
270
+ current.path.replace(trailingSlashRE, '/').indexOf(
271
+ target.path.replace(trailingSlashRE, '/')
272
+ ) === 0 &&
273
+ (!target.hash || current.hash === target.hash) &&
274
+ queryIncludes(current.query, target.query)
275
+ )
276
+ }
277
+
278
+ function queryIncludes (current, target) {
279
+ for (var key in target) {
280
+ if (!(key in current)) {
281
+ return false
282
+ }
283
+ }
284
+ return true
285
+ }
286
+
287
+ function handleRouteEntered (route) {
288
+ for (var i = 0; i < route.matched.length; i++) {
289
+ var record = route.matched[i];
290
+ for (var name in record.instances) {
291
+ var instance = record.instances[name];
292
+ var cbs = record.enteredCbs[name];
293
+ if (!instance || !cbs) { continue }
294
+ delete record.enteredCbs[name];
295
+ for (var i$1 = 0; i$1 < cbs.length; i$1++) {
296
+ if (!instance._isBeingDestroyed) { cbs[i$1](instance); }
297
+ }
298
+ }
299
+ }
300
+ }
301
+
302
+ var View = {
303
+ name: 'RouterView',
304
+ functional: true,
305
+ props: {
306
+ name: {
307
+ type: String,
308
+ default: 'default'
309
+ }
310
+ },
311
+ render: function render (_, ref) {
312
+ var props = ref.props;
313
+ var children = ref.children;
314
+ var parent = ref.parent;
315
+ var data = ref.data;
316
+
317
+ // used by devtools to display a router-view badge
318
+ data.routerView = true;
319
+
320
+ // directly use parent context's createElement() function
321
+ // so that components rendered by router-view can resolve named slots
322
+ var h = parent.$createElement;
323
+ var name = props.name;
324
+ var route = parent.$route;
325
+ var cache = parent._routerViewCache || (parent._routerViewCache = {});
326
+
327
+ // determine current view depth, also check to see if the tree
328
+ // has been toggled inactive but kept-alive.
329
+ var depth = 0;
330
+ var inactive = false;
331
+ while (parent && parent._routerRoot !== parent) {
332
+ var knodeData = parent.$knode ? parent.$knode.data : {};
333
+ if (knodeData.routerView) {
334
+ depth++;
335
+ }
336
+ if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
337
+ inactive = true;
338
+ }
339
+ parent = parent.$parent;
340
+ }
341
+ data.routerViewDepth = depth;
342
+
343
+ // render previous view if the tree is inactive and kept-alive
344
+ if (inactive) {
345
+ var cachedData = cache[name];
346
+ var cachedComponent = cachedData && cachedData.component;
347
+ if (cachedComponent) {
348
+ // #2301
349
+ // pass props
350
+ if (cachedData.configProps) {
351
+ fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
352
+ }
353
+ return h(cachedComponent, data, children)
354
+ } else {
355
+ // render previous empty view
356
+ return h()
357
+ }
358
+ }
359
+
360
+ var matched = route.matched[depth];
361
+ var component = matched && matched.components[name];
362
+
363
+ // render empty node if no matched route or no config component
364
+ if (!matched || !component) {
365
+ cache[name] = null;
366
+ return h()
367
+ }
368
+
369
+ // cache component
370
+ cache[name] = { component: component };
371
+
372
+ // attach instance registration hook
373
+ // this will be called in the instance's injected lifecycle hooks
374
+ data.registerRouteInstance = function (vm, val) {
375
+ // val could be undefined for unregistration
376
+ var current = matched.instances[name];
377
+ if (
378
+ (val && current !== vm) ||
379
+ (!val && current === vm)
380
+ ) {
381
+ matched.instances[name] = val;
382
+ }
383
+ }
384
+
385
+ // also register instance in prepatch hook
386
+ // in case the same component instance is reused across different routes
387
+ ;(data.hook || (data.hook = {})).prepatch = function (_, knode) {
388
+ matched.instances[name] = knode.componentInstance;
389
+ };
390
+
391
+ // register instance in init hook
392
+ // in case kept-alive component be actived when routes changed
393
+ data.hook.init = function (knode) {
394
+ if (knode.data.keepAlive &&
395
+ knode.componentInstance &&
396
+ knode.componentInstance !== matched.instances[name]
397
+ ) {
398
+ matched.instances[name] = knode.componentInstance;
399
+ }
400
+
401
+ // if the route transition has already been confirmed then we weren't
402
+ // able to call the cbs during confirmation as the component was not
403
+ // registered yet, so we call it here.
404
+ handleRouteEntered(route);
405
+ };
392
406
 
393
- // handle null value #1566
394
- if (!a || !b) { return a === b }
395
- var aKeys = Object.keys(a);
396
- var bKeys = Object.keys(b);
397
- if (aKeys.length !== bKeys.length) {
398
- return false
399
- }
400
- return aKeys.every(function (key) {
401
- var aVal = a[key];
402
- var bVal = b[key];
403
- // check nested equality
404
- if (typeof aVal === 'object' && typeof bVal === 'object') {
405
- return isObjectEqual(aVal, bVal)
407
+ var configProps = matched.props && matched.props[name];
408
+ // save route and configProps in cache
409
+ if (configProps) {
410
+ extend(cache[name], {
411
+ route: route,
412
+ configProps: configProps
413
+ });
414
+ fillPropsinData(component, data, route, configProps);
406
415
  }
407
- return String(aVal) === String(bVal)
408
- })
409
- }
410
416
 
411
- function isIncludedRoute (current, target) {
412
- return (
413
- current.path.replace(trailingSlashRE, '/').indexOf(
414
- target.path.replace(trailingSlashRE, '/')
415
- ) === 0 &&
416
- (!target.hash || current.hash === target.hash) &&
417
- queryIncludes(current.query, target.query)
418
- )
419
- }
417
+ return h(component, data, children)
418
+ }
419
+ };
420
420
 
421
- function queryIncludes (current, target) {
422
- for (var key in target) {
423
- if (!(key in current)) {
424
- return false
421
+ function fillPropsinData (component, data, route, configProps) {
422
+ // resolve props
423
+ var propsToPass = data.props = resolveProps(route, configProps);
424
+ if (propsToPass) {
425
+ // clone to prevent mutation
426
+ propsToPass = data.props = extend({}, propsToPass);
427
+ // pass non-declared props as attrs
428
+ var attrs = data.attrs = data.attrs || {};
429
+ for (var key in propsToPass) {
430
+ if (!component.props || !(key in component.props)) {
431
+ attrs[key] = propsToPass[key];
432
+ delete propsToPass[key];
433
+ }
425
434
  }
426
435
  }
427
- return true
436
+ }
437
+
438
+ function resolveProps (route, config) {
439
+ switch (typeof config) {
440
+ case 'undefined':
441
+ return
442
+ case 'object':
443
+ return config
444
+ case 'function':
445
+ return config(route)
446
+ case 'boolean':
447
+ return config ? route.params : undefined
448
+ default:
449
+ {
450
+ warn(
451
+ false,
452
+ "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " +
453
+ "expecting an object, function or boolean."
454
+ );
455
+ }
456
+ }
428
457
  }
429
458
 
430
459
  /* */
@@ -495,7 +524,7 @@
495
524
  }
496
525
 
497
526
  function cleanPath (path) {
498
- return path.replace(/\/\//g, '/')
527
+ return path.replace(/\/(?:\s*\/)+/g, '/')
499
528
  }
500
529
 
501
530
  var isarray = Array.isArray || function (arr) {
@@ -1035,6 +1064,10 @@
1035
1064
 
1036
1065
  var noop = function () {};
1037
1066
 
1067
+ var warnedCustomSlot;
1068
+ var warnedTagProp;
1069
+ var warnedEventProp;
1070
+
1038
1071
  var Link = {
1039
1072
  name: 'RouterLink',
1040
1073
  props: {
@@ -1046,11 +1079,17 @@
1046
1079
  type: String,
1047
1080
  default: 'a'
1048
1081
  },
1082
+ custom: Boolean,
1049
1083
  exact: Boolean,
1084
+ exactPath: Boolean,
1050
1085
  append: Boolean,
1051
1086
  replace: Boolean,
1052
1087
  activeClass: String,
1053
1088
  exactActiveClass: String,
1089
+ ariaCurrentValue: {
1090
+ type: String,
1091
+ default: 'page'
1092
+ },
1054
1093
  event: {
1055
1094
  type: eventTypes,
1056
1095
  default: 'click'
@@ -1091,11 +1130,13 @@
1091
1130
  ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1092
1131
  : route;
1093
1132
 
1094
- classes[exactActiveClass] = isSameRoute(current, compareTarget);
1095
- classes[activeClass] = this.exact
1133
+ classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath);
1134
+ classes[activeClass] = this.exact || this.exactPath
1096
1135
  ? classes[exactActiveClass]
1097
1136
  : isIncludedRoute(current, compareTarget);
1098
1137
 
1138
+ var ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null;
1139
+
1099
1140
  var handler = function (e) {
1100
1141
  if (guardEvent(e)) {
1101
1142
  if (this$1.replace) {
@@ -1129,22 +1170,43 @@
1129
1170
  });
1130
1171
 
1131
1172
  if (scopedSlot) {
1173
+ if (!this.custom) {
1174
+ !warnedCustomSlot && warn(false, 'In Kdu Router 4, the k-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link k-slot="{ navigate, href }" custom></router-link>\n');
1175
+ warnedCustomSlot = true;
1176
+ }
1132
1177
  if (scopedSlot.length === 1) {
1133
1178
  return scopedSlot[0]
1134
1179
  } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1135
1180
  {
1136
1181
  warn(
1137
1182
  false,
1138
- ("RouterLink with to=\"" + (this.to) + "\" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.")
1183
+ ("<router-link> with to=\"" + (this.to) + "\" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.")
1139
1184
  );
1140
1185
  }
1141
1186
  return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1142
1187
  }
1143
1188
  }
1144
1189
 
1190
+ {
1191
+ if ('tag' in this.$options.propsData && !warnedTagProp) {
1192
+ warn(
1193
+ false,
1194
+ "<router-link>'s tag prop is deprecated and has been removed in Kdu Router 4. Use the k-slot API to remove this warning: https://kdujs-router.web.app/guide/migration/#removal-of-event-and-tag-props-in-router-link."
1195
+ );
1196
+ warnedTagProp = true;
1197
+ }
1198
+ if ('event' in this.$options.propsData && !warnedEventProp) {
1199
+ warn(
1200
+ false,
1201
+ "<router-link>'s event prop is deprecated and has been removed in Kdu Router 4. Use the k-slot API to remove this warning: https://kdujs-router.web.app/guide/migration/#removal-of-event-and-tag-props-in-router-link."
1202
+ );
1203
+ warnedEventProp = true;
1204
+ }
1205
+ }
1206
+
1145
1207
  if (this.tag === 'a') {
1146
1208
  data.on = on;
1147
- data.attrs = { href: href };
1209
+ data.attrs = { href: href, 'aria-current': ariaCurrentValue };
1148
1210
  } else {
1149
1211
  // find the first <a> child and apply listener and href
1150
1212
  var a = findAnchor(this.$slots.default);
@@ -1172,6 +1234,7 @@
1172
1234
 
1173
1235
  var aAttrs = (a.data.attrs = extend({}, a.data.attrs));
1174
1236
  aAttrs.href = href;
1237
+ aAttrs['aria-current'] = ariaCurrentValue;
1175
1238
  } else {
1176
1239
  // doesn't have <a> child, apply listener to self
1177
1240
  data.on = on;
@@ -1276,7 +1339,8 @@
1276
1339
  routes,
1277
1340
  oldPathList,
1278
1341
  oldPathMap,
1279
- oldNameMap
1342
+ oldNameMap,
1343
+ parentRoute
1280
1344
  ) {
1281
1345
  // the path list is used to control path matching priority
1282
1346
  var pathList = oldPathList || [];
@@ -1286,7 +1350,7 @@
1286
1350
  var nameMap = oldNameMap || Object.create(null);
1287
1351
 
1288
1352
  routes.forEach(function (route) {
1289
- addRouteRecord(pathList, pathMap, nameMap, route);
1353
+ addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
1290
1354
  });
1291
1355
 
1292
1356
  // ensure wildcard routes are always at the end
@@ -1335,6 +1399,14 @@
1335
1399
  path || name
1336
1400
  )) + " cannot be a " + "string id. Use an actual component instead."
1337
1401
  );
1402
+
1403
+ warn(
1404
+ // eslint-disable-next-line no-control-regex
1405
+ !/[^\u0000-\u007F]+/.test(path),
1406
+ "Route with path \"" + path + "\" contains unencoded characters, make sure " +
1407
+ "your path is correctly encoded before passing it to the router. Use " +
1408
+ "encodeURI to encode static segments of your path."
1409
+ );
1338
1410
  }
1339
1411
 
1340
1412
  var pathToRegexpOptions =
@@ -1349,7 +1421,13 @@
1349
1421
  path: normalizedPath,
1350
1422
  regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1351
1423
  components: route.components || { default: route.component },
1424
+ alias: route.alias
1425
+ ? typeof route.alias === 'string'
1426
+ ? [route.alias]
1427
+ : route.alias
1428
+ : [],
1352
1429
  instances: {},
1430
+ enteredCbs: {},
1353
1431
  name: name,
1354
1432
  parent: parent,
1355
1433
  matchAs: matchAs,
@@ -1377,7 +1455,7 @@
1377
1455
  warn(
1378
1456
  false,
1379
1457
  "Named Route '" + (route.name) + "' has a default child route. " +
1380
- "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " +
1458
+ "When navigating to this named route (:to=\"{name: '" + (route.name) + "'}\"), " +
1381
1459
  "the default child route will not be rendered. Remove the name from " +
1382
1460
  "this route and use the name of the default child route for named " +
1383
1461
  "links instead."
@@ -1401,7 +1479,7 @@
1401
1479
  var aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
1402
1480
  for (var i = 0; i < aliases.length; ++i) {
1403
1481
  var alias = aliases[i];
1404
- if ( alias === path) {
1482
+ if (alias === path) {
1405
1483
  warn(
1406
1484
  false,
1407
1485
  ("Found an alias with the same value as the path: \"" + path + "\". You have to remove that alias. It will be ignored in development.")
@@ -1428,7 +1506,7 @@
1428
1506
  if (name) {
1429
1507
  if (!nameMap[name]) {
1430
1508
  nameMap[name] = record;
1431
- } else if ( !matchAs) {
1509
+ } else if (!matchAs) {
1432
1510
  warn(
1433
1511
  false,
1434
1512
  "Duplicate named routes definition: " +
@@ -1484,6 +1562,28 @@
1484
1562
  createRouteMap(routes, pathList, pathMap, nameMap);
1485
1563
  }
1486
1564
 
1565
+ function addRoute (parentOrRoute, route) {
1566
+ var parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined;
1567
+ // $flow-disable-line
1568
+ createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent);
1569
+
1570
+ // add aliases of parent
1571
+ if (parent && parent.alias.length) {
1572
+ createRouteMap(
1573
+ // $flow-disable-line route is defined if parent is
1574
+ parent.alias.map(function (alias) { return ({ path: alias, children: [route] }); }),
1575
+ pathList,
1576
+ pathMap,
1577
+ nameMap,
1578
+ parent
1579
+ );
1580
+ }
1581
+ }
1582
+
1583
+ function getRoutes () {
1584
+ return pathList.map(function (path) { return pathMap[path]; })
1585
+ }
1586
+
1487
1587
  function match (
1488
1588
  raw,
1489
1589
  currentRoute,
@@ -1630,6 +1730,8 @@
1630
1730
 
1631
1731
  return {
1632
1732
  match: match,
1733
+ addRoute: addRoute,
1734
+ getRoutes: getRoutes,
1633
1735
  addRoutes: addRoutes
1634
1736
  }
1635
1737
  }
@@ -1649,10 +1751,9 @@
1649
1751
 
1650
1752
  for (var i = 1, len = m.length; i < len; ++i) {
1651
1753
  var key = regex.keys[i - 1];
1652
- var val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i];
1653
1754
  if (key) {
1654
1755
  // Fix #1994: using * with props: true generates a param named 0
1655
- params[key.name || 'pathMatch'] = val;
1756
+ params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i];
1656
1757
  }
1657
1758
  }
1658
1759
 
@@ -1690,6 +1791,10 @@
1690
1791
  var positionStore = Object.create(null);
1691
1792
 
1692
1793
  function setupScroll () {
1794
+ // Prevent browser scroll behavior on History popstate
1795
+ if ('scrollRestoration' in window.history) {
1796
+ window.history.scrollRestoration = 'manual';
1797
+ }
1693
1798
  // Fix for #1585 for Firefox
1694
1799
  // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
1695
1800
  // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
@@ -1701,12 +1806,10 @@
1701
1806
  var stateCopy = extend({}, window.history.state);
1702
1807
  stateCopy.key = getStateKey();
1703
1808
  window.history.replaceState(stateCopy, '', absolutePath);
1704
- window.addEventListener('popstate', function (e) {
1705
- saveScrollPosition();
1706
- if (e.state && e.state.key) {
1707
- setStateKey(e.state.key);
1708
- }
1709
- });
1809
+ window.addEventListener('popstate', handlePopState);
1810
+ return function () {
1811
+ window.removeEventListener('popstate', handlePopState);
1812
+ }
1710
1813
  }
1711
1814
 
1712
1815
  function handleScroll (
@@ -1768,6 +1871,13 @@
1768
1871
  }
1769
1872
  }
1770
1873
 
1874
+ function handlePopState (e) {
1875
+ saveScrollPosition();
1876
+ if (e.state && e.state.key) {
1877
+ setStateKey(e.state.key);
1878
+ }
1879
+ }
1880
+
1771
1881
  function getScrollPosition () {
1772
1882
  var key = getStateKey();
1773
1883
  if (key) {
@@ -1833,7 +1943,17 @@
1833
1943
  }
1834
1944
 
1835
1945
  if (position) {
1836
- window.scrollTo(position.x, position.y);
1946
+ // $flow-disable-line
1947
+ if ('scrollBehavior' in document.documentElement.style) {
1948
+ window.scrollTo({
1949
+ left: position.x,
1950
+ top: position.y,
1951
+ // $flow-disable-line
1952
+ behavior: shouldScroll.behavior
1953
+ });
1954
+ } else {
1955
+ window.scrollTo(position.x, position.y);
1956
+ }
1837
1957
  }
1838
1958
  }
1839
1959
 
@@ -1853,7 +1973,7 @@
1853
1973
  return false
1854
1974
  }
1855
1975
 
1856
- return window.history && 'pushState' in window.history
1976
+ return window.history && typeof window.history.pushState === 'function'
1857
1977
  })();
1858
1978
 
1859
1979
  function pushState (url, replace) {
@@ -1898,6 +2018,89 @@
1898
2018
  step(0);
1899
2019
  }
1900
2020
 
2021
+ // When changing thing, also edit router.d.ts
2022
+ var NavigationFailureType = {
2023
+ redirected: 2,
2024
+ aborted: 4,
2025
+ cancelled: 8,
2026
+ duplicated: 16
2027
+ };
2028
+
2029
+ function createNavigationRedirectedError (from, to) {
2030
+ return createRouterError(
2031
+ from,
2032
+ to,
2033
+ NavigationFailureType.redirected,
2034
+ ("Redirected when going from \"" + (from.fullPath) + "\" to \"" + (stringifyRoute(
2035
+ to
2036
+ )) + "\" via a navigation guard.")
2037
+ )
2038
+ }
2039
+
2040
+ function createNavigationDuplicatedError (from, to) {
2041
+ var error = createRouterError(
2042
+ from,
2043
+ to,
2044
+ NavigationFailureType.duplicated,
2045
+ ("Avoided redundant navigation to current location: \"" + (from.fullPath) + "\".")
2046
+ );
2047
+ // backwards compatible with the first introduction of Errors
2048
+ error.name = 'NavigationDuplicated';
2049
+ return error
2050
+ }
2051
+
2052
+ function createNavigationCancelledError (from, to) {
2053
+ return createRouterError(
2054
+ from,
2055
+ to,
2056
+ NavigationFailureType.cancelled,
2057
+ ("Navigation cancelled from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" with a new navigation.")
2058
+ )
2059
+ }
2060
+
2061
+ function createNavigationAbortedError (from, to) {
2062
+ return createRouterError(
2063
+ from,
2064
+ to,
2065
+ NavigationFailureType.aborted,
2066
+ ("Navigation aborted from \"" + (from.fullPath) + "\" to \"" + (to.fullPath) + "\" via a navigation guard.")
2067
+ )
2068
+ }
2069
+
2070
+ function createRouterError (from, to, type, message) {
2071
+ var error = new Error(message);
2072
+ error._isRouter = true;
2073
+ error.from = from;
2074
+ error.to = to;
2075
+ error.type = type;
2076
+
2077
+ return error
2078
+ }
2079
+
2080
+ var propertiesToLog = ['params', 'query', 'hash'];
2081
+
2082
+ function stringifyRoute (to) {
2083
+ if (typeof to === 'string') { return to }
2084
+ if ('path' in to) { return to.path }
2085
+ var location = {};
2086
+ propertiesToLog.forEach(function (key) {
2087
+ if (key in to) { location[key] = to[key]; }
2088
+ });
2089
+ return JSON.stringify(location, null, 2)
2090
+ }
2091
+
2092
+ function isError (err) {
2093
+ return Object.prototype.toString.call(err).indexOf('Error') > -1
2094
+ }
2095
+
2096
+ function isNavigationFailure (err, errorType) {
2097
+ return (
2098
+ isError(err) &&
2099
+ err._isRouter &&
2100
+ (errorType == null || err.type === errorType)
2101
+ )
2102
+ }
2103
+
1901
2104
  /* */
1902
2105
 
1903
2106
  function resolveAsyncComponents (matched) {
@@ -1933,7 +2136,7 @@
1933
2136
 
1934
2137
  var reject = once(function (reason) {
1935
2138
  var msg = "Failed to resolve async component " + key + ": " + reason;
1936
- warn(false, msg);
2139
+ warn(false, msg);
1937
2140
  if (!error) {
1938
2141
  error = isError(reason)
1939
2142
  ? reason
@@ -2007,33 +2210,6 @@
2007
2210
  }
2008
2211
  }
2009
2212
 
2010
- var NavigationDuplicated = /*@__PURE__*/(function (Error) {
2011
- function NavigationDuplicated (normalizedLocation) {
2012
- Error.call(this);
2013
- this.name = this._name = 'NavigationDuplicated';
2014
- // passing the message to super() doesn't seem to work in the transpiled version
2015
- this.message = "Navigating to current location (\"" + (normalizedLocation.fullPath) + "\") is not allowed";
2016
- // add a stack property so services like Sentry can correctly display it
2017
- Object.defineProperty(this, 'stack', {
2018
- value: new Error().stack,
2019
- writable: true,
2020
- configurable: true
2021
- });
2022
- // we could also have used
2023
- // Error.captureStackTrace(this, this.constructor)
2024
- // but it only exists on node and chrome
2025
- }
2026
-
2027
- if ( Error ) NavigationDuplicated.__proto__ = Error;
2028
- NavigationDuplicated.prototype = Object.create( Error && Error.prototype );
2029
- NavigationDuplicated.prototype.constructor = NavigationDuplicated;
2030
-
2031
- return NavigationDuplicated;
2032
- }(Error));
2033
-
2034
- // support IE9
2035
- NavigationDuplicated._name = 'NavigationDuplicated';
2036
-
2037
2213
  /* */
2038
2214
 
2039
2215
  var History = function History (router, base) {
@@ -2046,6 +2222,7 @@
2046
2222
  this.readyCbs = [];
2047
2223
  this.readyErrorCbs = [];
2048
2224
  this.errorCbs = [];
2225
+ this.listeners = [];
2049
2226
  };
2050
2227
 
2051
2228
  History.prototype.listen = function listen (cb) {
@@ -2074,13 +2251,27 @@
2074
2251
  ) {
2075
2252
  var this$1 = this;
2076
2253
 
2077
- var route = this.router.match(location, this.current);
2254
+ var route;
2255
+ // catch redirect option
2256
+ try {
2257
+ route = this.router.match(location, this.current);
2258
+ } catch (e) {
2259
+ this.errorCbs.forEach(function (cb) {
2260
+ cb(e);
2261
+ });
2262
+ // Exception should still be thrown
2263
+ throw e
2264
+ }
2265
+ var prev = this.current;
2078
2266
  this.confirmTransition(
2079
2267
  route,
2080
2268
  function () {
2081
2269
  this$1.updateRoute(route);
2082
2270
  onComplete && onComplete(route);
2083
2271
  this$1.ensureURL();
2272
+ this$1.router.afterHooks.forEach(function (hook) {
2273
+ hook && hook(route, prev);
2274
+ });
2084
2275
 
2085
2276
  // fire ready cbs once
2086
2277
  if (!this$1.ready) {
@@ -2095,10 +2286,14 @@
2095
2286
  onAbort(err);
2096
2287
  }
2097
2288
  if (err && !this$1.ready) {
2098
- this$1.ready = true;
2099
- this$1.readyErrorCbs.forEach(function (cb) {
2100
- cb(err);
2101
- });
2289
+ // Initial redirection should not mark the history as ready yet
2290
+ // because it's triggered by the redirection instead
2291
+ if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
2292
+ this$1.ready = true;
2293
+ this$1.readyErrorCbs.forEach(function (cb) {
2294
+ cb(err);
2295
+ });
2296
+ }
2102
2297
  }
2103
2298
  }
2104
2299
  );
@@ -2108,29 +2303,37 @@
2108
2303
  var this$1 = this;
2109
2304
 
2110
2305
  var current = this.current;
2306
+ this.pending = route;
2111
2307
  var abort = function (err) {
2112
- // When the user navigates through history through back/forward buttons
2113
- // we do not want to throw the error. We only throw it if directly calling
2114
- // push/replace. That's why it's not included in isError
2115
- if (!isExtendedError(NavigationDuplicated, err) && isError(err)) {
2308
+ // changed after adding errors
2309
+ // before that change, redirect and aborted navigation would produce an err == null
2310
+ if (!isNavigationFailure(err) && isError(err)) {
2116
2311
  if (this$1.errorCbs.length) {
2117
2312
  this$1.errorCbs.forEach(function (cb) {
2118
2313
  cb(err);
2119
2314
  });
2120
2315
  } else {
2121
- warn(false, 'uncaught error during route navigation:');
2316
+ {
2317
+ warn(false, 'uncaught error during route navigation:');
2318
+ }
2122
2319
  console.error(err);
2123
2320
  }
2124
2321
  }
2125
2322
  onAbort && onAbort(err);
2126
2323
  };
2324
+ var lastRouteIndex = route.matched.length - 1;
2325
+ var lastCurrentIndex = current.matched.length - 1;
2127
2326
  if (
2128
2327
  isSameRoute(route, current) &&
2129
2328
  // in the case the route map has been dynamically appended to
2130
- route.matched.length === current.matched.length
2329
+ lastRouteIndex === lastCurrentIndex &&
2330
+ route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
2131
2331
  ) {
2132
2332
  this.ensureURL();
2133
- return abort(new NavigationDuplicated(route))
2333
+ if (route.hash) {
2334
+ handleScroll(this.router, current, route, false);
2335
+ }
2336
+ return abort(createNavigationDuplicatedError(current, route))
2134
2337
  }
2135
2338
 
2136
2339
  var ref = resolveQueue(
@@ -2154,15 +2357,17 @@
2154
2357
  resolveAsyncComponents(activated)
2155
2358
  );
2156
2359
 
2157
- this.pending = route;
2158
2360
  var iterator = function (hook, next) {
2159
2361
  if (this$1.pending !== route) {
2160
- return abort()
2362
+ return abort(createNavigationCancelledError(current, route))
2161
2363
  }
2162
2364
  try {
2163
2365
  hook(route, current, function (to) {
2164
- if (to === false || isError(to)) {
2366
+ if (to === false) {
2165
2367
  // next(false) -> abort navigation, ensure current URL
2368
+ this$1.ensureURL(true);
2369
+ abort(createNavigationAbortedError(current, route));
2370
+ } else if (isError(to)) {
2166
2371
  this$1.ensureURL(true);
2167
2372
  abort(to);
2168
2373
  } else if (
@@ -2171,7 +2376,7 @@
2171
2376
  (typeof to.path === 'string' || typeof to.name === 'string'))
2172
2377
  ) {
2173
2378
  // next('/') or next({ path: '/' }) -> redirect
2174
- abort();
2379
+ abort(createNavigationRedirectedError(current, route));
2175
2380
  if (typeof to === 'object' && to.replace) {
2176
2381
  this$1.replace(to);
2177
2382
  } else {
@@ -2188,23 +2393,19 @@
2188
2393
  };
2189
2394
 
2190
2395
  runQueue(queue, iterator, function () {
2191
- var postEnterCbs = [];
2192
- var isValid = function () { return this$1.current === route; };
2193
2396
  // wait until async components are resolved before
2194
2397
  // extracting in-component enter guards
2195
- var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
2398
+ var enterGuards = extractEnterGuards(activated);
2196
2399
  var queue = enterGuards.concat(this$1.router.resolveHooks);
2197
2400
  runQueue(queue, iterator, function () {
2198
2401
  if (this$1.pending !== route) {
2199
- return abort()
2402
+ return abort(createNavigationCancelledError(current, route))
2200
2403
  }
2201
2404
  this$1.pending = null;
2202
2405
  onComplete(route);
2203
2406
  if (this$1.router.app) {
2204
2407
  this$1.router.app.$nextTick(function () {
2205
- postEnterCbs.forEach(function (cb) {
2206
- cb();
2207
- });
2408
+ handleRouteEntered(route);
2208
2409
  });
2209
2410
  }
2210
2411
  });
@@ -2212,12 +2413,24 @@
2212
2413
  };
2213
2414
 
2214
2415
  History.prototype.updateRoute = function updateRoute (route) {
2215
- var prev = this.current;
2216
2416
  this.current = route;
2217
2417
  this.cb && this.cb(route);
2218
- this.router.afterHooks.forEach(function (hook) {
2219
- hook && hook(route, prev);
2418
+ };
2419
+
2420
+ History.prototype.setupListeners = function setupListeners () {
2421
+ // Default implementation is empty
2422
+ };
2423
+
2424
+ History.prototype.teardown = function teardown () {
2425
+ // clean up event listeners
2426
+ this.listeners.forEach(function (cleanupListener) {
2427
+ cleanupListener();
2220
2428
  });
2429
+ this.listeners = [];
2430
+
2431
+ // reset current history route
2432
+ this.current = START;
2433
+ this.pending = null;
2221
2434
  };
2222
2435
 
2223
2436
  function normalizeBase (base) {
@@ -2303,15 +2516,13 @@
2303
2516
  }
2304
2517
 
2305
2518
  function extractEnterGuards (
2306
- activated,
2307
- cbs,
2308
- isValid
2519
+ activated
2309
2520
  ) {
2310
2521
  return extractGuards(
2311
2522
  activated,
2312
2523
  'beforeRouteEnter',
2313
2524
  function (guard, _, match, key) {
2314
- return bindEnterGuard(guard, match, key, cbs, isValid)
2525
+ return bindEnterGuard(guard, match, key)
2315
2526
  }
2316
2527
  )
2317
2528
  }
@@ -2319,68 +2530,56 @@
2319
2530
  function bindEnterGuard (
2320
2531
  guard,
2321
2532
  match,
2322
- key,
2323
- cbs,
2324
- isValid
2533
+ key
2325
2534
  ) {
2326
2535
  return function routeEnterGuard (to, from, next) {
2327
2536
  return guard(to, from, function (cb) {
2328
2537
  if (typeof cb === 'function') {
2329
- cbs.push(function () {
2330
- // #750
2331
- // if a router-view is wrapped with an out-in transition,
2332
- // the instance may not have been registered at this time.
2333
- // we will need to poll for registration until current route
2334
- // is no longer valid.
2335
- poll(cb, match.instances, key, isValid);
2336
- });
2538
+ if (!match.enteredCbs[key]) {
2539
+ match.enteredCbs[key] = [];
2540
+ }
2541
+ match.enteredCbs[key].push(cb);
2337
2542
  }
2338
2543
  next(cb);
2339
2544
  })
2340
2545
  }
2341
2546
  }
2342
2547
 
2343
- function poll (
2344
- cb, // somehow flow cannot infer this is a function
2345
- instances,
2346
- key,
2347
- isValid
2348
- ) {
2349
- if (
2350
- instances[key] &&
2351
- !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
2352
- ) {
2353
- cb(instances[key]);
2354
- } else if (isValid()) {
2355
- setTimeout(function () {
2356
- poll(cb, instances, key, isValid);
2357
- }, 16);
2358
- }
2359
- }
2360
-
2361
2548
  /* */
2362
2549
 
2363
2550
  var HTML5History = /*@__PURE__*/(function (History) {
2364
2551
  function HTML5History (router, base) {
2552
+ History.call(this, router, base);
2553
+
2554
+ this._startLocation = getLocation(this.base);
2555
+ }
2556
+
2557
+ if ( History ) HTML5History.__proto__ = History;
2558
+ HTML5History.prototype = Object.create( History && History.prototype );
2559
+ HTML5History.prototype.constructor = HTML5History;
2560
+
2561
+ HTML5History.prototype.setupListeners = function setupListeners () {
2365
2562
  var this$1 = this;
2366
2563
 
2367
- History.call(this, router, base);
2564
+ if (this.listeners.length > 0) {
2565
+ return
2566
+ }
2368
2567
 
2568
+ var router = this.router;
2369
2569
  var expectScroll = router.options.scrollBehavior;
2370
2570
  var supportsScroll = supportsPushState && expectScroll;
2371
2571
 
2372
2572
  if (supportsScroll) {
2373
- setupScroll();
2573
+ this.listeners.push(setupScroll());
2374
2574
  }
2375
2575
 
2376
- var initLocation = getLocation(this.base);
2377
- window.addEventListener('popstate', function (e) {
2576
+ var handleRoutingEvent = function () {
2378
2577
  var current = this$1.current;
2379
2578
 
2380
2579
  // Avoiding first `popstate` event dispatched in some browsers but first
2381
2580
  // history route not updated since async guard at the same time.
2382
2581
  var location = getLocation(this$1.base);
2383
- if (this$1.current === START && location === initLocation) {
2582
+ if (this$1.current === START && location === this$1._startLocation) {
2384
2583
  return
2385
2584
  }
2386
2585
 
@@ -2389,12 +2588,12 @@
2389
2588
  handleScroll(router, route, current, true);
2390
2589
  }
2391
2590
  });
2591
+ };
2592
+ window.addEventListener('popstate', handleRoutingEvent);
2593
+ this.listeners.push(function () {
2594
+ window.removeEventListener('popstate', handleRoutingEvent);
2392
2595
  });
2393
- }
2394
-
2395
- if ( History ) HTML5History.__proto__ = History;
2396
- HTML5History.prototype = Object.create( History && History.prototype );
2397
- HTML5History.prototype.constructor = HTML5History;
2596
+ };
2398
2597
 
2399
2598
  HTML5History.prototype.go = function go (n) {
2400
2599
  window.history.go(n);
@@ -2439,8 +2638,13 @@
2439
2638
  }(History));
2440
2639
 
2441
2640
  function getLocation (base) {
2442
- var path = decodeURI(window.location.pathname);
2443
- if (base && path.indexOf(base) === 0) {
2641
+ var path = window.location.pathname;
2642
+ var pathLowerCase = path.toLowerCase();
2643
+ var baseLowerCase = base.toLowerCase();
2644
+ // base="/a" shouldn't turn path="/app" into "/a/pp"
2645
+ // so we ensure the trailing slash in the base
2646
+ if (base && ((pathLowerCase === baseLowerCase) ||
2647
+ (pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
2444
2648
  path = path.slice(base.length);
2445
2649
  }
2446
2650
  return (path || '/') + window.location.search + window.location.hash
@@ -2467,31 +2671,40 @@
2467
2671
  HashHistory.prototype.setupListeners = function setupListeners () {
2468
2672
  var this$1 = this;
2469
2673
 
2674
+ if (this.listeners.length > 0) {
2675
+ return
2676
+ }
2677
+
2470
2678
  var router = this.router;
2471
2679
  var expectScroll = router.options.scrollBehavior;
2472
2680
  var supportsScroll = supportsPushState && expectScroll;
2473
2681
 
2474
2682
  if (supportsScroll) {
2475
- setupScroll();
2683
+ this.listeners.push(setupScroll());
2476
2684
  }
2477
2685
 
2478
- window.addEventListener(
2479
- supportsPushState ? 'popstate' : 'hashchange',
2480
- function () {
2481
- var current = this$1.current;
2482
- if (!ensureSlash()) {
2483
- return
2484
- }
2485
- this$1.transitionTo(getHash(), function (route) {
2486
- if (supportsScroll) {
2487
- handleScroll(this$1.router, route, current, true);
2488
- }
2489
- if (!supportsPushState) {
2490
- replaceHash(route.fullPath);
2491
- }
2492
- });
2686
+ var handleRoutingEvent = function () {
2687
+ var current = this$1.current;
2688
+ if (!ensureSlash()) {
2689
+ return
2493
2690
  }
2691
+ this$1.transitionTo(getHash(), function (route) {
2692
+ if (supportsScroll) {
2693
+ handleScroll(this$1.router, route, current, true);
2694
+ }
2695
+ if (!supportsPushState) {
2696
+ replaceHash(route.fullPath);
2697
+ }
2698
+ });
2699
+ };
2700
+ var eventType = supportsPushState ? 'popstate' : 'hashchange';
2701
+ window.addEventListener(
2702
+ eventType,
2703
+ handleRoutingEvent
2494
2704
  );
2705
+ this.listeners.push(function () {
2706
+ window.removeEventListener(eventType, handleRoutingEvent);
2707
+ });
2495
2708
  };
2496
2709
 
2497
2710
  HashHistory.prototype.push = function push (location, onComplete, onAbort) {
@@ -2570,17 +2783,6 @@
2570
2783
  if (index < 0) { return '' }
2571
2784
 
2572
2785
  href = href.slice(index + 1);
2573
- // decode the hash but not the search or hash
2574
- // as search(query) is already decoded
2575
- var searchIndex = href.indexOf('?');
2576
- if (searchIndex < 0) {
2577
- var hashIndex = href.indexOf('#');
2578
- if (hashIndex > -1) {
2579
- href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2580
- } else { href = decodeURI(href); }
2581
- } else {
2582
- href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2583
- }
2584
2786
 
2585
2787
  return href
2586
2788
  }
@@ -2659,11 +2861,15 @@
2659
2861
  this.confirmTransition(
2660
2862
  route,
2661
2863
  function () {
2864
+ var prev = this$1.current;
2662
2865
  this$1.index = targetIndex;
2663
2866
  this$1.updateRoute(route);
2867
+ this$1.router.afterHooks.forEach(function (hook) {
2868
+ hook && hook(route, prev);
2869
+ });
2664
2870
  },
2665
2871
  function (err) {
2666
- if (isExtendedError(NavigationDuplicated, err)) {
2872
+ if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
2667
2873
  this$1.index = targetIndex;
2668
2874
  }
2669
2875
  }
@@ -2684,11 +2890,12 @@
2684
2890
 
2685
2891
  /* */
2686
2892
 
2687
-
2688
-
2689
2893
  var KduRouter = function KduRouter (options) {
2690
2894
  if ( options === void 0 ) options = {};
2691
2895
 
2896
+ {
2897
+ warn(this instanceof KduRouter, "Router must be called with the new operator.");
2898
+ }
2692
2899
  this.app = null;
2693
2900
  this.apps = [];
2694
2901
  this.options = options;
@@ -2698,7 +2905,8 @@
2698
2905
  this.matcher = createMatcher(options.routes || [], this);
2699
2906
 
2700
2907
  var mode = options.mode || 'hash';
2701
- this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false;
2908
+ this.fallback =
2909
+ mode === 'history' && !supportsPushState && options.fallback !== false;
2702
2910
  if (this.fallback) {
2703
2911
  mode = 'hash';
2704
2912
  }
@@ -2726,11 +2934,7 @@
2726
2934
 
2727
2935
  var prototypeAccessors = { currentRoute: { configurable: true } };
2728
2936
 
2729
- KduRouter.prototype.match = function match (
2730
- raw,
2731
- current,
2732
- redirectedFrom
2733
- ) {
2937
+ KduRouter.prototype.match = function match (raw, current, redirectedFrom) {
2734
2938
  return this.matcher.match(raw, current, redirectedFrom)
2735
2939
  };
2736
2940
 
@@ -2741,11 +2945,11 @@
2741
2945
  KduRouter.prototype.init = function init (app /* Kdu component instance */) {
2742
2946
  var this$1 = this;
2743
2947
 
2744
- assert(
2745
- install.installed,
2746
- "not installed. Make sure to call `Kdu.use(KduRouter)` " +
2747
- "before creating root instance."
2748
- );
2948
+ assert(
2949
+ install.installed,
2950
+ "not installed. Make sure to call `Kdu.use(KduRouter)` " +
2951
+ "before creating root instance."
2952
+ );
2749
2953
 
2750
2954
  this.apps.push(app);
2751
2955
 
@@ -2757,6 +2961,8 @@
2757
2961
  // ensure we still have a main app or null if no apps
2758
2962
  // we do not release the router so it can be reused
2759
2963
  if (this$1.app === app) { this$1.app = this$1.apps[0] || null; }
2964
+
2965
+ if (!this$1.app) { this$1.history.teardown(); }
2760
2966
  });
2761
2967
 
2762
2968
  // main app previously initialized
@@ -2769,16 +2975,24 @@
2769
2975
 
2770
2976
  var history = this.history;
2771
2977
 
2772
- if (history instanceof HTML5History) {
2773
- history.transitionTo(history.getCurrentLocation());
2774
- } else if (history instanceof HashHistory) {
2775
- var setupHashListener = function () {
2978
+ if (history instanceof HTML5History || history instanceof HashHistory) {
2979
+ var handleInitialScroll = function (routeOrError) {
2980
+ var from = history.current;
2981
+ var expectScroll = this$1.options.scrollBehavior;
2982
+ var supportsScroll = supportsPushState && expectScroll;
2983
+
2984
+ if (supportsScroll && 'fullPath' in routeOrError) {
2985
+ handleScroll(this$1, routeOrError, from, false);
2986
+ }
2987
+ };
2988
+ var setupListeners = function (routeOrError) {
2776
2989
  history.setupListeners();
2990
+ handleInitialScroll(routeOrError);
2777
2991
  };
2778
2992
  history.transitionTo(
2779
2993
  history.getCurrentLocation(),
2780
- setupHashListener,
2781
- setupHashListener
2994
+ setupListeners,
2995
+ setupListeners
2782
2996
  );
2783
2997
  }
2784
2998
 
@@ -2856,11 +3070,14 @@
2856
3070
  if (!route) {
2857
3071
  return []
2858
3072
  }
2859
- return [].concat.apply([], route.matched.map(function (m) {
2860
- return Object.keys(m.components).map(function (key) {
2861
- return m.components[key]
3073
+ return [].concat.apply(
3074
+ [],
3075
+ route.matched.map(function (m) {
3076
+ return Object.keys(m.components).map(function (key) {
3077
+ return m.components[key]
3078
+ })
2862
3079
  })
2863
- }))
3080
+ )
2864
3081
  };
2865
3082
 
2866
3083
  KduRouter.prototype.resolve = function resolve (
@@ -2869,12 +3086,7 @@
2869
3086
  append
2870
3087
  ) {
2871
3088
  current = current || this.history.current;
2872
- var location = normalizeLocation(
2873
- to,
2874
- current,
2875
- append,
2876
- this
2877
- );
3089
+ var location = normalizeLocation(to, current, append, this);
2878
3090
  var route = this.match(location, current);
2879
3091
  var fullPath = route.redirectedFrom || route.fullPath;
2880
3092
  var base = this.history.base;
@@ -2889,7 +3101,21 @@
2889
3101
  }
2890
3102
  };
2891
3103
 
3104
+ KduRouter.prototype.getRoutes = function getRoutes () {
3105
+ return this.matcher.getRoutes()
3106
+ };
3107
+
3108
+ KduRouter.prototype.addRoute = function addRoute (parentOrRoute, route) {
3109
+ this.matcher.addRoute(parentOrRoute, route);
3110
+ if (this.history.current !== START) {
3111
+ this.history.transitionTo(this.history.getCurrentLocation());
3112
+ }
3113
+ };
3114
+
2892
3115
  KduRouter.prototype.addRoutes = function addRoutes (routes) {
3116
+ {
3117
+ warn(false, 'router.addRoutes() is deprecated and has been removed in Kdu Router 4. Use router.addRoute() instead.');
3118
+ }
2893
3119
  this.matcher.addRoutes(routes);
2894
3120
  if (this.history.current !== START) {
2895
3121
  this.history.transitionTo(this.history.getCurrentLocation());
@@ -2912,7 +3138,10 @@
2912
3138
  }
2913
3139
 
2914
3140
  KduRouter.install = install;
2915
- KduRouter.version = '3.1.7';
3141
+ KduRouter.version = '3.5.4';
3142
+ KduRouter.isNavigationFailure = isNavigationFailure;
3143
+ KduRouter.NavigationFailureType = NavigationFailureType;
3144
+ KduRouter.START_LOCATION = START;
2916
3145
 
2917
3146
  if (inBrowser && window.Kdu) {
2918
3147
  window.Kdu.use(KduRouter);