kdu-router 3.0.7 → 3.1.7

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.0.7
2
+ * kdu-router v3.1.7
3
3
  * (c) 2022 NKDuy
4
4
  * @license MIT
5
5
  */
@@ -12,7 +12,7 @@ function assert (condition, message) {
12
12
  }
13
13
 
14
14
  function warn (condition, message) {
15
- if ("development" !== 'production' && !condition) {
15
+ if ( !condition) {
16
16
  typeof console !== 'undefined' && console.warn(`[kdu-router] ${message}`);
17
17
  }
18
18
  }
@@ -21,6 +21,14 @@ function isError (err) {
21
21
  return Object.prototype.toString.call(err).indexOf('Error') > -1
22
22
  }
23
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
+
24
32
  function extend (a, b) {
25
33
  for (const key in b) {
26
34
  a[key] = b[key];
@@ -53,14 +61,12 @@ var View = {
53
61
  let depth = 0;
54
62
  let inactive = false;
55
63
  while (parent && parent._routerRoot !== parent) {
56
- const knodeData = parent.$knode && parent.$knode.data;
57
- if (knodeData) {
58
- if (knodeData.routerView) {
59
- depth++;
60
- }
61
- if (knodeData.keepAlive && parent._inactive) {
62
- inactive = true;
63
- }
64
+ const knodeData = parent.$knode ? parent.$knode.data : {};
65
+ if (knodeData.routerView) {
66
+ depth++;
67
+ }
68
+ if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
69
+ inactive = true;
64
70
  }
65
71
  parent = parent.$parent;
66
72
  }
@@ -68,17 +74,32 @@ var View = {
68
74
 
69
75
  // render previous view if the tree is inactive and kept-alive
70
76
  if (inactive) {
71
- return h(cache[name], data, children)
77
+ const cachedData = cache[name];
78
+ const cachedComponent = cachedData && cachedData.component;
79
+ if (cachedComponent) {
80
+ // #2301
81
+ // pass props
82
+ if (cachedData.configProps) {
83
+ fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
84
+ }
85
+ return h(cachedComponent, data, children)
86
+ } else {
87
+ // render previous empty view
88
+ return h()
89
+ }
72
90
  }
73
91
 
74
92
  const matched = route.matched[depth];
75
- // render empty node if no matched route
76
- if (!matched) {
93
+ const component = matched && matched.components[name];
94
+
95
+ // render empty node if no matched route or no config component
96
+ if (!matched || !component) {
77
97
  cache[name] = null;
78
98
  return h()
79
99
  }
80
100
 
81
- const component = cache[name] = matched.components[name];
101
+ // cache component
102
+ cache[name] = { component };
82
103
 
83
104
  // attach instance registration hook
84
105
  // this will be called in the instance's injected lifecycle hooks
@@ -110,23 +131,35 @@ var View = {
110
131
  }
111
132
  };
112
133
 
113
- // resolve props
114
- let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]);
115
- if (propsToPass) {
116
- // clone to prevent mutation
117
- propsToPass = data.props = extend({}, propsToPass);
118
- // pass non-declared props as attrs
119
- const attrs = data.attrs = data.attrs || {};
120
- for (const key in propsToPass) {
121
- if (!component.props || !(key in component.props)) {
122
- attrs[key] = propsToPass[key];
123
- delete propsToPass[key];
124
- }
125
- }
134
+ const configProps = matched.props && matched.props[name];
135
+ // save route and configProps in cachce
136
+ if (configProps) {
137
+ extend(cache[name], {
138
+ route,
139
+ configProps
140
+ });
141
+ fillPropsinData(component, data, route, configProps);
126
142
  }
127
143
 
128
144
  return h(component, data, children)
129
145
  }
146
+ };
147
+
148
+ function fillPropsinData (component, data, route, configProps) {
149
+ // resolve props
150
+ let propsToPass = data.props = resolveProps(route, configProps);
151
+ if (propsToPass) {
152
+ // clone to prevent mutation
153
+ propsToPass = data.props = extend({}, propsToPass);
154
+ // pass non-declared props as attrs
155
+ const attrs = data.attrs = data.attrs || {};
156
+ for (const key in propsToPass) {
157
+ if (!component.props || !(key in component.props)) {
158
+ attrs[key] = propsToPass[key];
159
+ delete propsToPass[key];
160
+ }
161
+ }
162
+ }
130
163
  }
131
164
 
132
165
  function resolveProps (route, config) {
@@ -175,7 +208,7 @@ function resolveQuery (
175
208
  try {
176
209
  parsedQuery = parse(query || '');
177
210
  } catch (e) {
178
- "development" !== 'production' && warn(false, e.message);
211
+ warn(false, e.message);
179
212
  parsedQuery = {};
180
213
  }
181
214
  for (const key in extraQuery) {
@@ -254,7 +287,7 @@ function createRoute (
254
287
  redirectedFrom,
255
288
  router
256
289
  ) {
257
- const stringifyQuery$$1 = router && router.options.stringifyQuery;
290
+ const stringifyQuery = router && router.options.stringifyQuery;
258
291
 
259
292
  let query = location.query || {};
260
293
  try {
@@ -268,11 +301,11 @@ function createRoute (
268
301
  hash: location.hash || '',
269
302
  query,
270
303
  params: location.params || {},
271
- fullPath: getFullPath(location, stringifyQuery$$1),
304
+ fullPath: getFullPath(location, stringifyQuery),
272
305
  matched: record ? formatMatch(record) : []
273
306
  };
274
307
  if (redirectedFrom) {
275
- route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery$$1);
308
+ route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery);
276
309
  }
277
310
  return Object.freeze(route)
278
311
  }
@@ -376,195 +409,6 @@ function queryIncludes (current, target) {
376
409
 
377
410
  /* */
378
411
 
379
- // work around weird flow bug
380
- const toTypes = [String, Object];
381
- const eventTypes = [String, Array];
382
-
383
- var Link = {
384
- name: 'RouterLink',
385
- props: {
386
- to: {
387
- type: toTypes,
388
- required: true
389
- },
390
- tag: {
391
- type: String,
392
- default: 'a'
393
- },
394
- exact: Boolean,
395
- append: Boolean,
396
- replace: Boolean,
397
- activeClass: String,
398
- exactActiveClass: String,
399
- event: {
400
- type: eventTypes,
401
- default: 'click'
402
- }
403
- },
404
- render (h) {
405
- const router = this.$router;
406
- const current = this.$route;
407
- const { location, route, href } = router.resolve(this.to, current, this.append);
408
-
409
- const classes = {};
410
- const globalActiveClass = router.options.linkActiveClass;
411
- const globalExactActiveClass = router.options.linkExactActiveClass;
412
- // Support global empty active class
413
- const activeClassFallback = globalActiveClass == null
414
- ? 'router-link-active'
415
- : globalActiveClass;
416
- const exactActiveClassFallback = globalExactActiveClass == null
417
- ? 'router-link-exact-active'
418
- : globalExactActiveClass;
419
- const activeClass = this.activeClass == null
420
- ? activeClassFallback
421
- : this.activeClass;
422
- const exactActiveClass = this.exactActiveClass == null
423
- ? exactActiveClassFallback
424
- : this.exactActiveClass;
425
- const compareTarget = location.path
426
- ? createRoute(null, location, null, router)
427
- : route;
428
-
429
- classes[exactActiveClass] = isSameRoute(current, compareTarget);
430
- classes[activeClass] = this.exact
431
- ? classes[exactActiveClass]
432
- : isIncludedRoute(current, compareTarget);
433
-
434
- const handler = e => {
435
- if (guardEvent(e)) {
436
- if (this.replace) {
437
- router.replace(location);
438
- } else {
439
- router.push(location);
440
- }
441
- }
442
- };
443
-
444
- const on = { click: guardEvent };
445
- if (Array.isArray(this.event)) {
446
- this.event.forEach(e => { on[e] = handler; });
447
- } else {
448
- on[this.event] = handler;
449
- }
450
-
451
- const data = {
452
- class: classes
453
- };
454
-
455
- if (this.tag === 'a') {
456
- data.on = on;
457
- data.attrs = { href };
458
- } else {
459
- // find the first <a> child and apply listener and href
460
- const a = findAnchor(this.$slots.default);
461
- if (a) {
462
- // in case the <a> is a static node
463
- a.isStatic = false;
464
- const aData = a.data = extend({}, a.data);
465
- aData.on = on;
466
- const aAttrs = a.data.attrs = extend({}, a.data.attrs);
467
- aAttrs.href = href;
468
- } else {
469
- // doesn't have <a> child, apply listener to self
470
- data.on = on;
471
- }
472
- }
473
-
474
- return h(this.tag, data, this.$slots.default)
475
- }
476
- }
477
-
478
- function guardEvent (e) {
479
- // don't redirect with control keys
480
- if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
481
- // don't redirect when preventDefault called
482
- if (e.defaultPrevented) return
483
- // don't redirect on right click
484
- if (e.button !== undefined && e.button !== 0) return
485
- // don't redirect if `target="_blank"`
486
- if (e.currentTarget && e.currentTarget.getAttribute) {
487
- const target = e.currentTarget.getAttribute('target');
488
- if (/\b_blank\b/i.test(target)) return
489
- }
490
- // this may be a Weex event which doesn't have this method
491
- if (e.preventDefault) {
492
- e.preventDefault();
493
- }
494
- return true
495
- }
496
-
497
- function findAnchor (children) {
498
- if (children) {
499
- let child;
500
- for (let i = 0; i < children.length; i++) {
501
- child = children[i];
502
- if (child.tag === 'a') {
503
- return child
504
- }
505
- if (child.children && (child = findAnchor(child.children))) {
506
- return child
507
- }
508
- }
509
- }
510
- }
511
-
512
- let _Kdu;
513
-
514
- function install (Kdu) {
515
- if (install.installed && _Kdu === Kdu) return
516
- install.installed = true;
517
-
518
- _Kdu = Kdu;
519
-
520
- const isDef = v => v !== undefined;
521
-
522
- const registerInstance = (vm, callVal) => {
523
- let i = vm.$options._parentKnode;
524
- if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
525
- i(vm, callVal);
526
- }
527
- };
528
-
529
- Kdu.mixin({
530
- beforeCreate () {
531
- if (isDef(this.$options.router)) {
532
- this._routerRoot = this;
533
- this._router = this.$options.router;
534
- this._router.init(this);
535
- Kdu.util.defineReactive(this, '_route', this._router.history.current);
536
- } else {
537
- this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
538
- }
539
- registerInstance(this, this);
540
- },
541
- destroyed () {
542
- registerInstance(this);
543
- }
544
- });
545
-
546
- Object.defineProperty(Kdu.prototype, '$router', {
547
- get () { return this._routerRoot._router }
548
- });
549
-
550
- Object.defineProperty(Kdu.prototype, '$route', {
551
- get () { return this._routerRoot._route }
552
- });
553
-
554
- Kdu.component('RouterView', View);
555
- Kdu.component('RouterLink', Link);
556
-
557
- const strats = Kdu.config.optionMergeStrategies;
558
- // use the same hook merging strategy for route hooks
559
- strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
560
- }
561
-
562
- /* */
563
-
564
- const inBrowser = typeof window !== 'undefined';
565
-
566
- /* */
567
-
568
412
  function resolvePath (
569
413
  relative,
570
414
  base,
@@ -1052,51 +896,356 @@ function pathToRegexp (path, keys, options) {
1052
896
 
1053
897
  options = options || {};
1054
898
 
1055
- if (path instanceof RegExp) {
1056
- return regexpToRegexp(path, /** @type {!Array} */ (keys))
1057
- }
899
+ if (path instanceof RegExp) {
900
+ return regexpToRegexp(path, /** @type {!Array} */ (keys))
901
+ }
902
+
903
+ if (isarray(path)) {
904
+ return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
905
+ }
906
+
907
+ return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
908
+ }
909
+ pathToRegexp_1.parse = parse_1;
910
+ pathToRegexp_1.compile = compile_1;
911
+ pathToRegexp_1.tokensToFunction = tokensToFunction_1;
912
+ pathToRegexp_1.tokensToRegExp = tokensToRegExp_1;
913
+
914
+ /* */
915
+
916
+ // $flow-disable-line
917
+ const regexpCompileCache = Object.create(null);
918
+
919
+ function fillParams (
920
+ path,
921
+ params,
922
+ routeMsg
923
+ ) {
924
+ params = params || {};
925
+ try {
926
+ const filler =
927
+ regexpCompileCache[path] ||
928
+ (regexpCompileCache[path] = pathToRegexp_1.compile(path));
929
+
930
+ // Fix #2505 resolving asterisk routes { name: 'not-found', params: { pathMatch: '/not-found' }}
931
+ // and fix #3106 so that you can work with location descriptor object having params.pathMatch equal to empty string
932
+ if (typeof params.pathMatch === 'string') params[0] = params.pathMatch;
933
+
934
+ return filler(params, { pretty: true })
935
+ } catch (e) {
936
+ {
937
+ // Fix #3072 no warn if `pathMatch` is string
938
+ warn(typeof params.pathMatch === 'string', `missing param for ${routeMsg}: ${e.message}`);
939
+ }
940
+ return ''
941
+ } finally {
942
+ // delete the 0 if it was added
943
+ delete params[0];
944
+ }
945
+ }
946
+
947
+ /* */
948
+
949
+ function normalizeLocation (
950
+ raw,
951
+ current,
952
+ append,
953
+ router
954
+ ) {
955
+ let next = typeof raw === 'string' ? { path: raw } : raw;
956
+ // named target
957
+ if (next._normalized) {
958
+ return next
959
+ } else if (next.name) {
960
+ next = extend({}, raw);
961
+ const params = next.params;
962
+ if (params && typeof params === 'object') {
963
+ next.params = extend({}, params);
964
+ }
965
+ return next
966
+ }
967
+
968
+ // relative params
969
+ if (!next.path && next.params && current) {
970
+ next = extend({}, next);
971
+ next._normalized = true;
972
+ const params = extend(extend({}, current.params), next.params);
973
+ if (current.name) {
974
+ next.name = current.name;
975
+ next.params = params;
976
+ } else if (current.matched.length) {
977
+ const rawPath = current.matched[current.matched.length - 1].path;
978
+ next.path = fillParams(rawPath, params, `path ${current.path}`);
979
+ } else {
980
+ warn(false, `relative params navigation requires a current route.`);
981
+ }
982
+ return next
983
+ }
984
+
985
+ const parsedPath = parsePath(next.path || '');
986
+ const basePath = (current && current.path) || '/';
987
+ const path = parsedPath.path
988
+ ? resolvePath(parsedPath.path, basePath, append || next.append)
989
+ : basePath;
990
+
991
+ const query = resolveQuery(
992
+ parsedPath.query,
993
+ next.query,
994
+ router && router.options.parseQuery
995
+ );
996
+
997
+ let hash = next.hash || parsedPath.hash;
998
+ if (hash && hash.charAt(0) !== '#') {
999
+ hash = `#${hash}`;
1000
+ }
1001
+
1002
+ return {
1003
+ _normalized: true,
1004
+ path,
1005
+ query,
1006
+ hash
1007
+ }
1008
+ }
1009
+
1010
+ /* */
1011
+
1012
+ // work around weird flow bug
1013
+ const toTypes = [String, Object];
1014
+ const eventTypes = [String, Array];
1015
+
1016
+ const noop = () => {};
1017
+
1018
+ var Link = {
1019
+ name: 'RouterLink',
1020
+ props: {
1021
+ to: {
1022
+ type: toTypes,
1023
+ required: true
1024
+ },
1025
+ tag: {
1026
+ type: String,
1027
+ default: 'a'
1028
+ },
1029
+ exact: Boolean,
1030
+ append: Boolean,
1031
+ replace: Boolean,
1032
+ activeClass: String,
1033
+ exactActiveClass: String,
1034
+ event: {
1035
+ type: eventTypes,
1036
+ default: 'click'
1037
+ }
1038
+ },
1039
+ render (h) {
1040
+ const router = this.$router;
1041
+ const current = this.$route;
1042
+ const { location, route, href } = router.resolve(
1043
+ this.to,
1044
+ current,
1045
+ this.append
1046
+ );
1047
+
1048
+ const classes = {};
1049
+ const globalActiveClass = router.options.linkActiveClass;
1050
+ const globalExactActiveClass = router.options.linkExactActiveClass;
1051
+ // Support global empty active class
1052
+ const activeClassFallback =
1053
+ globalActiveClass == null ? 'router-link-active' : globalActiveClass;
1054
+ const exactActiveClassFallback =
1055
+ globalExactActiveClass == null
1056
+ ? 'router-link-exact-active'
1057
+ : globalExactActiveClass;
1058
+ const activeClass =
1059
+ this.activeClass == null ? activeClassFallback : this.activeClass;
1060
+ const exactActiveClass =
1061
+ this.exactActiveClass == null
1062
+ ? exactActiveClassFallback
1063
+ : this.exactActiveClass;
1064
+
1065
+ const compareTarget = route.redirectedFrom
1066
+ ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1067
+ : route;
1068
+
1069
+ classes[exactActiveClass] = isSameRoute(current, compareTarget);
1070
+ classes[activeClass] = this.exact
1071
+ ? classes[exactActiveClass]
1072
+ : isIncludedRoute(current, compareTarget);
1073
+
1074
+ const handler = e => {
1075
+ if (guardEvent(e)) {
1076
+ if (this.replace) {
1077
+ router.replace(location, noop);
1078
+ } else {
1079
+ router.push(location, noop);
1080
+ }
1081
+ }
1082
+ };
1083
+
1084
+ const on = { click: guardEvent };
1085
+ if (Array.isArray(this.event)) {
1086
+ this.event.forEach(e => {
1087
+ on[e] = handler;
1088
+ });
1089
+ } else {
1090
+ on[this.event] = handler;
1091
+ }
1092
+
1093
+ const data = { class: classes };
1094
+
1095
+ const scopedSlot =
1096
+ !this.$scopedSlots.$hasNormal &&
1097
+ this.$scopedSlots.default &&
1098
+ this.$scopedSlots.default({
1099
+ href,
1100
+ route,
1101
+ navigate: handler,
1102
+ isActive: classes[activeClass],
1103
+ isExactActive: classes[exactActiveClass]
1104
+ });
1105
+
1106
+ if (scopedSlot) {
1107
+ if (scopedSlot.length === 1) {
1108
+ return scopedSlot[0]
1109
+ } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1110
+ {
1111
+ warn(
1112
+ false,
1113
+ `RouterLink with to="${
1114
+ this.to
1115
+ }" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
1116
+ );
1117
+ }
1118
+ return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1119
+ }
1120
+ }
1121
+
1122
+ if (this.tag === 'a') {
1123
+ data.on = on;
1124
+ data.attrs = { href };
1125
+ } else {
1126
+ // find the first <a> child and apply listener and href
1127
+ const a = findAnchor(this.$slots.default);
1128
+ if (a) {
1129
+ // in case the <a> is a static node
1130
+ a.isStatic = false;
1131
+ const aData = (a.data = extend({}, a.data));
1132
+ aData.on = aData.on || {};
1133
+ // transform existing events in both objects into arrays so we can push later
1134
+ for (const event in aData.on) {
1135
+ const handler = aData.on[event];
1136
+ if (event in on) {
1137
+ aData.on[event] = Array.isArray(handler) ? handler : [handler];
1138
+ }
1139
+ }
1140
+ // append new listeners for router-link
1141
+ for (const event in on) {
1142
+ if (event in aData.on) {
1143
+ // on[event] is always a function
1144
+ aData.on[event].push(on[event]);
1145
+ } else {
1146
+ aData.on[event] = handler;
1147
+ }
1148
+ }
1149
+
1150
+ const aAttrs = (a.data.attrs = extend({}, a.data.attrs));
1151
+ aAttrs.href = href;
1152
+ } else {
1153
+ // doesn't have <a> child, apply listener to self
1154
+ data.on = on;
1155
+ }
1156
+ }
1157
+
1158
+ return h(this.tag, data, this.$slots.default)
1159
+ }
1160
+ };
1161
+
1162
+ function guardEvent (e) {
1163
+ // don't redirect with control keys
1164
+ if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
1165
+ // don't redirect when preventDefault called
1166
+ if (e.defaultPrevented) return
1167
+ // don't redirect on right click
1168
+ if (e.button !== undefined && e.button !== 0) return
1169
+ // don't redirect if `target="_blank"`
1170
+ if (e.currentTarget && e.currentTarget.getAttribute) {
1171
+ const target = e.currentTarget.getAttribute('target');
1172
+ if (/\b_blank\b/i.test(target)) return
1173
+ }
1174
+ // this may be a Weex event which doesn't have this method
1175
+ if (e.preventDefault) {
1176
+ e.preventDefault();
1177
+ }
1178
+ return true
1179
+ }
1180
+
1181
+ function findAnchor (children) {
1182
+ if (children) {
1183
+ let child;
1184
+ for (let i = 0; i < children.length; i++) {
1185
+ child = children[i];
1186
+ if (child.tag === 'a') {
1187
+ return child
1188
+ }
1189
+ if (child.children && (child = findAnchor(child.children))) {
1190
+ return child
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+
1196
+ let _Kdu;
1197
+
1198
+ function install (Kdu) {
1199
+ if (install.installed && _Kdu === Kdu) return
1200
+ install.installed = true;
1201
+
1202
+ _Kdu = Kdu;
1203
+
1204
+ const isDef = v => v !== undefined;
1205
+
1206
+ const registerInstance = (vm, callVal) => {
1207
+ let i = vm.$options._parentKnode;
1208
+ if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
1209
+ i(vm, callVal);
1210
+ }
1211
+ };
1212
+
1213
+ Kdu.mixin({
1214
+ beforeCreate () {
1215
+ if (isDef(this.$options.router)) {
1216
+ this._routerRoot = this;
1217
+ this._router = this.$options.router;
1218
+ this._router.init(this);
1219
+ Kdu.util.defineReactive(this, '_route', this._router.history.current);
1220
+ } else {
1221
+ this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
1222
+ }
1223
+ registerInstance(this, this);
1224
+ },
1225
+ destroyed () {
1226
+ registerInstance(this);
1227
+ }
1228
+ });
1229
+
1230
+ Object.defineProperty(Kdu.prototype, '$router', {
1231
+ get () { return this._routerRoot._router }
1232
+ });
1233
+
1234
+ Object.defineProperty(Kdu.prototype, '$route', {
1235
+ get () { return this._routerRoot._route }
1236
+ });
1058
1237
 
1059
- if (isarray(path)) {
1060
- return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
1061
- }
1238
+ Kdu.component('RouterView', View);
1239
+ Kdu.component('RouterLink', Link);
1062
1240
 
1063
- return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
1241
+ const strats = Kdu.config.optionMergeStrategies;
1242
+ // use the same hook merging strategy for route hooks
1243
+ strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
1064
1244
  }
1065
- pathToRegexp_1.parse = parse_1;
1066
- pathToRegexp_1.compile = compile_1;
1067
- pathToRegexp_1.tokensToFunction = tokensToFunction_1;
1068
- pathToRegexp_1.tokensToRegExp = tokensToRegExp_1;
1069
1245
 
1070
1246
  /* */
1071
1247
 
1072
- // $flow-disable-line
1073
- const regexpCompileCache = Object.create(null);
1074
-
1075
- function fillParams (
1076
- path,
1077
- params,
1078
- routeMsg
1079
- ) {
1080
- params = params || {};
1081
- try {
1082
- const filler =
1083
- regexpCompileCache[path] ||
1084
- (regexpCompileCache[path] = pathToRegexp_1.compile(path));
1085
-
1086
- // Fix #2505 resolving asterisk routes { name: 'not-found', params: { pathMatch: '/not-found' }}
1087
- if (params.pathMatch) params[0] = params.pathMatch;
1088
-
1089
- return filler(params, { pretty: true })
1090
- } catch (e) {
1091
- {
1092
- warn(false, `missing param for ${routeMsg}: ${e.message}`);
1093
- }
1094
- return ''
1095
- } finally {
1096
- // delete the 0 if it was added
1097
- delete params[0];
1098
- }
1099
- }
1248
+ const inBrowser = typeof window !== 'undefined';
1100
1249
 
1101
1250
  /* */
1102
1251
 
@@ -1126,6 +1275,18 @@ function createRouteMap (
1126
1275
  }
1127
1276
  }
1128
1277
 
1278
+ {
1279
+ // warn if routes do not include leading slashes
1280
+ const found = pathList
1281
+ // check for missing leading slash
1282
+ .filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/');
1283
+
1284
+ if (found.length > 0) {
1285
+ const pathNames = found.map(path => `- ${path}`).join('\n');
1286
+ warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`);
1287
+ }
1288
+ }
1289
+
1129
1290
  return {
1130
1291
  pathList,
1131
1292
  pathMap,
@@ -1146,17 +1307,15 @@ function addRouteRecord (
1146
1307
  assert(path != null, `"path" is required in a route configuration.`);
1147
1308
  assert(
1148
1309
  typeof route.component !== 'string',
1149
- `route config "component" for path: ${String(path || name)} cannot be a ` +
1150
- `string id. Use an actual component instead.`
1310
+ `route config "component" for path: ${String(
1311
+ path || name
1312
+ )} cannot be a ` + `string id. Use an actual component instead.`
1151
1313
  );
1152
1314
  }
1153
1315
 
1154
- const pathToRegexpOptions = route.pathToRegexpOptions || {};
1155
- const normalizedPath = normalizePath(
1156
- path,
1157
- parent,
1158
- pathToRegexpOptions.strict
1159
- );
1316
+ const pathToRegexpOptions =
1317
+ route.pathToRegexpOptions || {};
1318
+ const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict);
1160
1319
 
1161
1320
  if (typeof route.caseSensitive === 'boolean') {
1162
1321
  pathToRegexpOptions.sensitive = route.caseSensitive;
@@ -1173,11 +1332,12 @@ function addRouteRecord (
1173
1332
  redirect: route.redirect,
1174
1333
  beforeEnter: route.beforeEnter,
1175
1334
  meta: route.meta || {},
1176
- props: route.props == null
1177
- ? {}
1178
- : route.components
1179
- ? route.props
1180
- : { default: route.props }
1335
+ props:
1336
+ route.props == null
1337
+ ? {}
1338
+ : route.components
1339
+ ? route.props
1340
+ : { default: route.props }
1181
1341
  };
1182
1342
 
1183
1343
  if (route.children) {
@@ -1185,14 +1345,20 @@ function addRouteRecord (
1185
1345
  // If users navigate to this route by name, the default child will
1186
1346
  // not be rendered (GH Issue #629)
1187
1347
  {
1188
- if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) {
1348
+ if (
1349
+ route.name &&
1350
+ !route.redirect &&
1351
+ route.children.some(child => /^\/?$/.test(child.path))
1352
+ ) {
1189
1353
  warn(
1190
1354
  false,
1191
1355
  `Named Route '${route.name}' has a default child route. ` +
1192
- `When navigating to this named route (:to="{name: '${route.name}'"), ` +
1193
- `the default child route will not be rendered. Remove the name from ` +
1194
- `this route and use the name of the default child route for named ` +
1195
- `links instead.`
1356
+ `When navigating to this named route (:to="{name: '${
1357
+ route.name
1358
+ }'"), ` +
1359
+ `the default child route will not be rendered. Remove the name from ` +
1360
+ `this route and use the name of the default child route for named ` +
1361
+ `links instead.`
1196
1362
  );
1197
1363
  }
1198
1364
  }
@@ -1204,12 +1370,24 @@ function addRouteRecord (
1204
1370
  });
1205
1371
  }
1206
1372
 
1373
+ if (!pathMap[record.path]) {
1374
+ pathList.push(record.path);
1375
+ pathMap[record.path] = record;
1376
+ }
1377
+
1207
1378
  if (route.alias !== undefined) {
1208
- const aliases = Array.isArray(route.alias)
1209
- ? route.alias
1210
- : [route.alias];
1379
+ const aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
1380
+ for (let i = 0; i < aliases.length; ++i) {
1381
+ const alias = aliases[i];
1382
+ if ( alias === path) {
1383
+ warn(
1384
+ false,
1385
+ `Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
1386
+ );
1387
+ // skip in dev to make it work
1388
+ continue
1389
+ }
1211
1390
 
1212
- aliases.forEach(alias => {
1213
1391
  const aliasRoute = {
1214
1392
  path: alias,
1215
1393
  children: route.children
@@ -1222,40 +1400,45 @@ function addRouteRecord (
1222
1400
  parent,
1223
1401
  record.path || '/' // matchAs
1224
1402
  );
1225
- });
1226
- }
1227
-
1228
- if (!pathMap[record.path]) {
1229
- pathList.push(record.path);
1230
- pathMap[record.path] = record;
1403
+ }
1231
1404
  }
1232
1405
 
1233
1406
  if (name) {
1234
1407
  if (!nameMap[name]) {
1235
1408
  nameMap[name] = record;
1236
- } else if ("development" !== 'production' && !matchAs) {
1409
+ } else if ( !matchAs) {
1237
1410
  warn(
1238
1411
  false,
1239
1412
  `Duplicate named routes definition: ` +
1240
- `{ name: "${name}", path: "${record.path}" }`
1413
+ `{ name: "${name}", path: "${record.path}" }`
1241
1414
  );
1242
1415
  }
1243
1416
  }
1244
1417
  }
1245
1418
 
1246
- function compileRouteRegex (path, pathToRegexpOptions) {
1419
+ function compileRouteRegex (
1420
+ path,
1421
+ pathToRegexpOptions
1422
+ ) {
1247
1423
  const regex = pathToRegexp_1(path, [], pathToRegexpOptions);
1248
1424
  {
1249
1425
  const keys = Object.create(null);
1250
1426
  regex.keys.forEach(key => {
1251
- warn(!keys[key.name], `Duplicate param keys in route with path: "${path}"`);
1427
+ warn(
1428
+ !keys[key.name],
1429
+ `Duplicate param keys in route with path: "${path}"`
1430
+ );
1252
1431
  keys[key.name] = true;
1253
1432
  });
1254
1433
  }
1255
1434
  return regex
1256
1435
  }
1257
1436
 
1258
- function normalizePath (path, parent, strict) {
1437
+ function normalizePath (
1438
+ path,
1439
+ parent,
1440
+ strict
1441
+ ) {
1259
1442
  if (!strict) path = path.replace(/\/$/, '');
1260
1443
  if (path[0] === '/') return path
1261
1444
  if (parent == null) return path
@@ -1264,64 +1447,6 @@ function normalizePath (path, parent, strict) {
1264
1447
 
1265
1448
  /* */
1266
1449
 
1267
- function normalizeLocation (
1268
- raw,
1269
- current,
1270
- append,
1271
- router
1272
- ) {
1273
- let next = typeof raw === 'string' ? { path: raw } : raw;
1274
- // named target
1275
- if (next._normalized) {
1276
- return next
1277
- } else if (next.name) {
1278
- return extend({}, raw)
1279
- }
1280
-
1281
- // relative params
1282
- if (!next.path && next.params && current) {
1283
- next = extend({}, next);
1284
- next._normalized = true;
1285
- const params = extend(extend({}, current.params), next.params);
1286
- if (current.name) {
1287
- next.name = current.name;
1288
- next.params = params;
1289
- } else if (current.matched.length) {
1290
- const rawPath = current.matched[current.matched.length - 1].path;
1291
- next.path = fillParams(rawPath, params, `path ${current.path}`);
1292
- } else {
1293
- warn(false, `relative params navigation requires a current route.`);
1294
- }
1295
- return next
1296
- }
1297
-
1298
- const parsedPath = parsePath(next.path || '');
1299
- const basePath = (current && current.path) || '/';
1300
- const path = parsedPath.path
1301
- ? resolvePath(parsedPath.path, basePath, append || next.append)
1302
- : basePath;
1303
-
1304
- const query = resolveQuery(
1305
- parsedPath.query,
1306
- next.query,
1307
- router && router.options.parseQuery
1308
- );
1309
-
1310
- let hash = next.hash || parsedPath.hash;
1311
- if (hash && hash.charAt(0) !== '#') {
1312
- hash = `#${hash}`;
1313
- }
1314
-
1315
- return {
1316
- _normalized: true,
1317
- path,
1318
- query,
1319
- hash
1320
- }
1321
- }
1322
-
1323
- /* */
1324
-
1325
1450
 
1326
1451
 
1327
1452
  function createMatcher (
@@ -1512,6 +1637,28 @@ function resolveRecordPath (path, record) {
1512
1637
 
1513
1638
  /* */
1514
1639
 
1640
+ // use User Timing api (if present) for more accurate key precision
1641
+ const Time =
1642
+ inBrowser && window.performance && window.performance.now
1643
+ ? window.performance
1644
+ : Date;
1645
+
1646
+ function genStateKey () {
1647
+ return Time.now().toFixed(3)
1648
+ }
1649
+
1650
+ let _key = genStateKey();
1651
+
1652
+ function getStateKey () {
1653
+ return _key
1654
+ }
1655
+
1656
+ function setStateKey (key) {
1657
+ return (_key = key)
1658
+ }
1659
+
1660
+ /* */
1661
+
1515
1662
  const positionStore = Object.create(null);
1516
1663
 
1517
1664
  function setupScroll () {
@@ -1522,7 +1669,10 @@ function setupScroll () {
1522
1669
  // location.host contains the port and location.hostname doesn't
1523
1670
  const protocolAndPath = window.location.protocol + '//' + window.location.host;
1524
1671
  const absolutePath = window.location.href.replace(protocolAndPath, '');
1525
- window.history.replaceState({ key: getStateKey() }, '', absolutePath);
1672
+ // preserve existing history state as it could be overriden by the user
1673
+ const stateCopy = extend({}, window.history.state);
1674
+ stateCopy.key = getStateKey();
1675
+ window.history.replaceState(stateCopy, '', absolutePath);
1526
1676
  window.addEventListener('popstate', e => {
1527
1677
  saveScrollPosition();
1528
1678
  if (e.state && e.state.key) {
@@ -1553,20 +1703,27 @@ function handleScroll (
1553
1703
  // wait until re-render finishes before scrolling
1554
1704
  router.app.$nextTick(() => {
1555
1705
  const position = getScrollPosition();
1556
- const shouldScroll = behavior.call(router, to, from, isPop ? position : null);
1706
+ const shouldScroll = behavior.call(
1707
+ router,
1708
+ to,
1709
+ from,
1710
+ isPop ? position : null
1711
+ );
1557
1712
 
1558
1713
  if (!shouldScroll) {
1559
1714
  return
1560
1715
  }
1561
1716
 
1562
1717
  if (typeof shouldScroll.then === 'function') {
1563
- shouldScroll.then(shouldScroll => {
1564
- scrollToPosition((shouldScroll), position);
1565
- }).catch(err => {
1566
- {
1567
- assert(false, err.toString());
1568
- }
1569
- });
1718
+ shouldScroll
1719
+ .then(shouldScroll => {
1720
+ scrollToPosition((shouldScroll), position);
1721
+ })
1722
+ .catch(err => {
1723
+ {
1724
+ assert(false, err.toString());
1725
+ }
1726
+ });
1570
1727
  } else {
1571
1728
  scrollToPosition(shouldScroll, position);
1572
1729
  }
@@ -1622,12 +1779,22 @@ function isNumber (v) {
1622
1779
  return typeof v === 'number'
1623
1780
  }
1624
1781
 
1782
+ const hashStartsWithNumberRE = /^#\d/;
1783
+
1625
1784
  function scrollToPosition (shouldScroll, position) {
1626
1785
  const isObject = typeof shouldScroll === 'object';
1627
1786
  if (isObject && typeof shouldScroll.selector === 'string') {
1628
- const el = document.querySelector(shouldScroll.selector);
1787
+ // getElementById would still fail if the selector contains a more complicated query like #main[data-attr]
1788
+ // but at the same time, it doesn't make much sense to select an element with an id and an extra selector
1789
+ const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line
1790
+ ? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line
1791
+ : document.querySelector(shouldScroll.selector);
1792
+
1629
1793
  if (el) {
1630
- let offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {};
1794
+ let offset =
1795
+ shouldScroll.offset && typeof shouldScroll.offset === 'object'
1796
+ ? shouldScroll.offset
1797
+ : {};
1631
1798
  offset = normalizeOffset(offset);
1632
1799
  position = getElementPosition(el, offset);
1633
1800
  } else if (isValidPosition(shouldScroll)) {
@@ -1644,39 +1811,22 @@ function scrollToPosition (shouldScroll, position) {
1644
1811
 
1645
1812
  /* */
1646
1813
 
1647
- const supportsPushState = inBrowser && (function () {
1648
- const ua = window.navigator.userAgent;
1649
-
1650
- if (
1651
- (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
1652
- ua.indexOf('Mobile Safari') !== -1 &&
1653
- ua.indexOf('Chrome') === -1 &&
1654
- ua.indexOf('Windows Phone') === -1
1655
- ) {
1656
- return false
1657
- }
1658
-
1659
- return window.history && 'pushState' in window.history
1660
- })();
1661
-
1662
- // use User Timing api (if present) for more accurate key precision
1663
- const Time = inBrowser && window.performance && window.performance.now
1664
- ? window.performance
1665
- : Date;
1666
-
1667
- let _key = genKey();
1814
+ const supportsPushState =
1815
+ inBrowser &&
1816
+ (function () {
1817
+ const ua = window.navigator.userAgent;
1668
1818
 
1669
- function genKey () {
1670
- return Time.now().toFixed(3)
1671
- }
1672
-
1673
- function getStateKey () {
1674
- return _key
1675
- }
1819
+ if (
1820
+ (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
1821
+ ua.indexOf('Mobile Safari') !== -1 &&
1822
+ ua.indexOf('Chrome') === -1 &&
1823
+ ua.indexOf('Windows Phone') === -1
1824
+ ) {
1825
+ return false
1826
+ }
1676
1827
 
1677
- function setStateKey (key) {
1678
- _key = key;
1679
- }
1828
+ return window.history && 'pushState' in window.history
1829
+ })();
1680
1830
 
1681
1831
  function pushState (url, replace) {
1682
1832
  saveScrollPosition();
@@ -1685,10 +1835,12 @@ function pushState (url, replace) {
1685
1835
  const history = window.history;
1686
1836
  try {
1687
1837
  if (replace) {
1688
- history.replaceState({ key: _key }, '', url);
1838
+ // preserve existing history state as it could be overriden by the user
1839
+ const stateCopy = extend({}, history.state);
1840
+ stateCopy.key = getStateKey();
1841
+ history.replaceState(stateCopy, '', url);
1689
1842
  } else {
1690
- _key = genKey();
1691
- history.pushState({ key: _key }, '', url);
1843
+ history.pushState({ key: setStateKey(genStateKey()) }, '', url);
1692
1844
  }
1693
1845
  } catch (e) {
1694
1846
  window.location[replace ? 'replace' : 'assign'](url);
@@ -1753,7 +1905,7 @@ function resolveAsyncComponents (matched) {
1753
1905
 
1754
1906
  const reject = once(reason => {
1755
1907
  const msg = `Failed to resolve async component ${key}: ${reason}`;
1756
- "development" !== 'production' && warn(false, msg);
1908
+ warn(false, msg);
1757
1909
  if (!error) {
1758
1910
  error = isError(reason)
1759
1911
  ? reason
@@ -1824,6 +1976,29 @@ function once (fn) {
1824
1976
  }
1825
1977
  }
1826
1978
 
1979
+ class NavigationDuplicated extends Error {
1980
+ constructor (normalizedLocation) {
1981
+ super();
1982
+ this.name = this._name = 'NavigationDuplicated';
1983
+ // passing the message to super() doesn't seem to work in the transpiled version
1984
+ this.message = `Navigating to current location ("${
1985
+ normalizedLocation.fullPath
1986
+ }") is not allowed`;
1987
+ // add a stack property so services like Sentry can correctly display it
1988
+ Object.defineProperty(this, 'stack', {
1989
+ value: new Error().stack,
1990
+ writable: true,
1991
+ configurable: true
1992
+ });
1993
+ // we could also have used
1994
+ // Error.captureStackTrace(this, this.constructor)
1995
+ // but it only exists on node and chrome
1996
+ }
1997
+ }
1998
+
1999
+ // support IE9
2000
+ NavigationDuplicated._name = 'NavigationDuplicated';
2001
+
1827
2002
  /* */
1828
2003
 
1829
2004
  class History {
@@ -1875,35 +2050,52 @@ class History {
1875
2050
  this.errorCbs.push(errorCb);
1876
2051
  }
1877
2052
 
1878
- transitionTo (location, onComplete, onAbort) {
2053
+ transitionTo (
2054
+ location,
2055
+ onComplete,
2056
+ onAbort
2057
+ ) {
1879
2058
  const route = this.router.match(location, this.current);
1880
- this.confirmTransition(route, () => {
1881
- this.updateRoute(route);
1882
- onComplete && onComplete(route);
1883
- this.ensureURL();
1884
-
1885
- // fire ready cbs once
1886
- if (!this.ready) {
1887
- this.ready = true;
1888
- this.readyCbs.forEach(cb => { cb(route); });
1889
- }
1890
- }, err => {
1891
- if (onAbort) {
1892
- onAbort(err);
1893
- }
1894
- if (err && !this.ready) {
1895
- this.ready = true;
1896
- this.readyErrorCbs.forEach(cb => { cb(err); });
2059
+ this.confirmTransition(
2060
+ route,
2061
+ () => {
2062
+ this.updateRoute(route);
2063
+ onComplete && onComplete(route);
2064
+ this.ensureURL();
2065
+
2066
+ // fire ready cbs once
2067
+ if (!this.ready) {
2068
+ this.ready = true;
2069
+ this.readyCbs.forEach(cb => {
2070
+ cb(route);
2071
+ });
2072
+ }
2073
+ },
2074
+ err => {
2075
+ if (onAbort) {
2076
+ onAbort(err);
2077
+ }
2078
+ if (err && !this.ready) {
2079
+ this.ready = true;
2080
+ this.readyErrorCbs.forEach(cb => {
2081
+ cb(err);
2082
+ });
2083
+ }
1897
2084
  }
1898
- });
2085
+ );
1899
2086
  }
1900
2087
 
1901
2088
  confirmTransition (route, onComplete, onAbort) {
1902
2089
  const current = this.current;
1903
2090
  const abort = err => {
1904
- if (isError(err)) {
2091
+ // When the user navigates through history through back/forward buttons
2092
+ // we do not want to throw the error. We only throw it if directly calling
2093
+ // push/replace. That's why it's not included in isError
2094
+ if (!isExtendedError(NavigationDuplicated, err) && isError(err)) {
1905
2095
  if (this.errorCbs.length) {
1906
- this.errorCbs.forEach(cb => { cb(err); });
2096
+ this.errorCbs.forEach(cb => {
2097
+ cb(err);
2098
+ });
1907
2099
  } else {
1908
2100
  warn(false, 'uncaught error during route navigation:');
1909
2101
  console.error(err);
@@ -1917,14 +2109,13 @@ class History {
1917
2109
  route.matched.length === current.matched.length
1918
2110
  ) {
1919
2111
  this.ensureURL();
1920
- return abort()
2112
+ return abort(new NavigationDuplicated(route))
1921
2113
  }
1922
2114
 
1923
- const {
1924
- updated,
1925
- deactivated,
1926
- activated
1927
- } = resolveQueue(this.current.matched, route.matched);
2115
+ const { updated, deactivated, activated } = resolveQueue(
2116
+ this.current.matched,
2117
+ route.matched
2118
+ );
1928
2119
 
1929
2120
  const queue = [].concat(
1930
2121
  // in-component leave guards
@@ -1952,10 +2143,8 @@ class History {
1952
2143
  abort(to);
1953
2144
  } else if (
1954
2145
  typeof to === 'string' ||
1955
- (typeof to === 'object' && (
1956
- typeof to.path === 'string' ||
1957
- typeof to.name === 'string'
1958
- ))
2146
+ (typeof to === 'object' &&
2147
+ (typeof to.path === 'string' || typeof to.name === 'string'))
1959
2148
  ) {
1960
2149
  // next('/') or next({ path: '/' }) -> redirect
1961
2150
  abort();
@@ -1989,7 +2178,9 @@ class History {
1989
2178
  onComplete(route);
1990
2179
  if (this.router.app) {
1991
2180
  this.router.app.$nextTick(() => {
1992
- postEnterCbs.forEach(cb => { cb(); });
2181
+ postEnterCbs.forEach(cb => {
2182
+ cb();
2183
+ });
1993
2184
  });
1994
2185
  }
1995
2186
  });
@@ -2093,9 +2284,13 @@ function extractEnterGuards (
2093
2284
  cbs,
2094
2285
  isValid
2095
2286
  ) {
2096
- return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => {
2097
- return bindEnterGuard(guard, match, key, cbs, isValid)
2098
- })
2287
+ return extractGuards(
2288
+ activated,
2289
+ 'beforeRouteEnter',
2290
+ (guard, _, match, key) => {
2291
+ return bindEnterGuard(guard, match, key, cbs, isValid)
2292
+ }
2293
+ )
2099
2294
  }
2100
2295
 
2101
2296
  function bindEnterGuard (
@@ -2237,38 +2432,49 @@ class HashHistory extends History {
2237
2432
  setupScroll();
2238
2433
  }
2239
2434
 
2240
- window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', () => {
2241
- const current = this.current;
2242
- if (!ensureSlash()) {
2243
- return
2244
- }
2245
- this.transitionTo(getHash(), route => {
2246
- if (supportsScroll) {
2247
- handleScroll(this.router, route, current, true);
2248
- }
2249
- if (!supportsPushState) {
2250
- replaceHash(route.fullPath);
2435
+ window.addEventListener(
2436
+ supportsPushState ? 'popstate' : 'hashchange',
2437
+ () => {
2438
+ const current = this.current;
2439
+ if (!ensureSlash()) {
2440
+ return
2251
2441
  }
2252
- });
2253
- });
2442
+ this.transitionTo(getHash(), route => {
2443
+ if (supportsScroll) {
2444
+ handleScroll(this.router, route, current, true);
2445
+ }
2446
+ if (!supportsPushState) {
2447
+ replaceHash(route.fullPath);
2448
+ }
2449
+ });
2450
+ }
2451
+ );
2254
2452
  }
2255
2453
 
2256
2454
  push (location, onComplete, onAbort) {
2257
2455
  const { current: fromRoute } = this;
2258
- this.transitionTo(location, route => {
2259
- pushHash(route.fullPath);
2260
- handleScroll(this.router, route, fromRoute, false);
2261
- onComplete && onComplete(route);
2262
- }, onAbort);
2456
+ this.transitionTo(
2457
+ location,
2458
+ route => {
2459
+ pushHash(route.fullPath);
2460
+ handleScroll(this.router, route, fromRoute, false);
2461
+ onComplete && onComplete(route);
2462
+ },
2463
+ onAbort
2464
+ );
2263
2465
  }
2264
2466
 
2265
2467
  replace (location, onComplete, onAbort) {
2266
2468
  const { current: fromRoute } = this;
2267
- this.transitionTo(location, route => {
2268
- replaceHash(route.fullPath);
2269
- handleScroll(this.router, route, fromRoute, false);
2270
- onComplete && onComplete(route);
2271
- }, onAbort);
2469
+ this.transitionTo(
2470
+ location,
2471
+ route => {
2472
+ replaceHash(route.fullPath);
2473
+ handleScroll(this.router, route, fromRoute, false);
2474
+ onComplete && onComplete(route);
2475
+ },
2476
+ onAbort
2477
+ );
2272
2478
  }
2273
2479
 
2274
2480
  go (n) {
@@ -2290,9 +2496,7 @@ class HashHistory extends History {
2290
2496
  function checkFallback (base) {
2291
2497
  const location = getLocation(base);
2292
2498
  if (!/^\/#/.test(location)) {
2293
- window.location.replace(
2294
- cleanPath(base + '/#' + location)
2295
- );
2499
+ window.location.replace(cleanPath(base + '/#' + location));
2296
2500
  return true
2297
2501
  }
2298
2502
  }
@@ -2320,10 +2524,11 @@ function getHash () {
2320
2524
  const searchIndex = href.indexOf('?');
2321
2525
  if (searchIndex < 0) {
2322
2526
  const hashIndex = href.indexOf('#');
2323
- if (hashIndex > -1) href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2324
- else href = decodeURI(href);
2527
+ if (hashIndex > -1) {
2528
+ href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2529
+ } else href = decodeURI(href);
2325
2530
  } else {
2326
- if (searchIndex > -1) href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2531
+ href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2327
2532
  }
2328
2533
 
2329
2534
  return href
@@ -2365,18 +2570,26 @@ class AbstractHistory extends History {
2365
2570
  }
2366
2571
 
2367
2572
  push (location, onComplete, onAbort) {
2368
- this.transitionTo(location, route => {
2369
- this.stack = this.stack.slice(0, this.index + 1).concat(route);
2370
- this.index++;
2371
- onComplete && onComplete(route);
2372
- }, onAbort);
2573
+ this.transitionTo(
2574
+ location,
2575
+ route => {
2576
+ this.stack = this.stack.slice(0, this.index + 1).concat(route);
2577
+ this.index++;
2578
+ onComplete && onComplete(route);
2579
+ },
2580
+ onAbort
2581
+ );
2373
2582
  }
2374
2583
 
2375
2584
  replace (location, onComplete, onAbort) {
2376
- this.transitionTo(location, route => {
2377
- this.stack = this.stack.slice(0, this.index).concat(route);
2378
- onComplete && onComplete(route);
2379
- }, onAbort);
2585
+ this.transitionTo(
2586
+ location,
2587
+ route => {
2588
+ this.stack = this.stack.slice(0, this.index).concat(route);
2589
+ onComplete && onComplete(route);
2590
+ },
2591
+ onAbort
2592
+ );
2380
2593
  }
2381
2594
 
2382
2595
  go (n) {
@@ -2385,10 +2598,18 @@ class AbstractHistory extends History {
2385
2598
  return
2386
2599
  }
2387
2600
  const route = this.stack[targetIndex];
2388
- this.confirmTransition(route, () => {
2389
- this.index = targetIndex;
2390
- this.updateRoute(route);
2391
- });
2601
+ this.confirmTransition(
2602
+ route,
2603
+ () => {
2604
+ this.index = targetIndex;
2605
+ this.updateRoute(route);
2606
+ },
2607
+ err => {
2608
+ if (isExtendedError(NavigationDuplicated, err)) {
2609
+ this.index = targetIndex;
2610
+ }
2611
+ }
2612
+ );
2392
2613
  }
2393
2614
 
2394
2615
  getCurrentLocation () {
@@ -2471,7 +2692,7 @@ class KduRouter {
2471
2692
  }
2472
2693
 
2473
2694
  init (app /* Kdu component instance */) {
2474
- "development" !== 'production' && assert(
2695
+ assert(
2475
2696
  install.installed,
2476
2697
  `not installed. Make sure to call \`Kdu.use(KduRouter)\` ` +
2477
2698
  `before creating root instance.`
@@ -2540,11 +2761,25 @@ class KduRouter {
2540
2761
  }
2541
2762
 
2542
2763
  push (location, onComplete, onAbort) {
2543
- this.history.push(location, onComplete, onAbort);
2764
+ // $flow-disable-line
2765
+ if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
2766
+ return new Promise((resolve, reject) => {
2767
+ this.history.push(location, resolve, reject);
2768
+ })
2769
+ } else {
2770
+ this.history.push(location, onComplete, onAbort);
2771
+ }
2544
2772
  }
2545
2773
 
2546
2774
  replace (location, onComplete, onAbort) {
2547
- this.history.replace(location, onComplete, onAbort);
2775
+ // $flow-disable-line
2776
+ if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
2777
+ return new Promise((resolve, reject) => {
2778
+ this.history.replace(location, resolve, reject);
2779
+ })
2780
+ } else {
2781
+ this.history.replace(location, onComplete, onAbort);
2782
+ }
2548
2783
  }
2549
2784
 
2550
2785
  go (n) {
@@ -2623,7 +2858,7 @@ function createHref (base, fullPath, mode) {
2623
2858
  }
2624
2859
 
2625
2860
  KduRouter.install = install;
2626
- KduRouter.version = '3.0.7';
2861
+ KduRouter.version = '3.1.7';
2627
2862
 
2628
2863
  if (inBrowser && window.Kdu) {
2629
2864
  window.Kdu.use(KduRouter);