kdu-router 3.0.1 → 3.0.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.
@@ -0,0 +1,2632 @@
1
+ /*!
2
+ * kdu-router v3.0.7
3
+ * (c) 2022 NKDuy
4
+ * @license MIT
5
+ */
6
+ /* */
7
+
8
+ function assert (condition, message) {
9
+ if (!condition) {
10
+ throw new Error(`[kdu-router] ${message}`)
11
+ }
12
+ }
13
+
14
+ function warn (condition, message) {
15
+ if ("development" !== 'production' && !condition) {
16
+ typeof console !== 'undefined' && console.warn(`[kdu-router] ${message}`);
17
+ }
18
+ }
19
+
20
+ function isError (err) {
21
+ return Object.prototype.toString.call(err).indexOf('Error') > -1
22
+ }
23
+
24
+ function extend (a, b) {
25
+ for (const key in b) {
26
+ a[key] = b[key];
27
+ }
28
+ return a
29
+ }
30
+
31
+ var View = {
32
+ name: 'RouterView',
33
+ functional: true,
34
+ props: {
35
+ name: {
36
+ type: String,
37
+ default: 'default'
38
+ }
39
+ },
40
+ render (_, { props, children, parent, data }) {
41
+ // used by devtools to display a router-view badge
42
+ data.routerView = true;
43
+
44
+ // directly use parent context's createElement() function
45
+ // so that components rendered by router-view can resolve named slots
46
+ const h = parent.$createElement;
47
+ const name = props.name;
48
+ const route = parent.$route;
49
+ const cache = parent._routerViewCache || (parent._routerViewCache = {});
50
+
51
+ // determine current view depth, also check to see if the tree
52
+ // has been toggled inactive but kept-alive.
53
+ let depth = 0;
54
+ let inactive = false;
55
+ 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
+ }
65
+ parent = parent.$parent;
66
+ }
67
+ data.routerViewDepth = depth;
68
+
69
+ // render previous view if the tree is inactive and kept-alive
70
+ if (inactive) {
71
+ return h(cache[name], data, children)
72
+ }
73
+
74
+ const matched = route.matched[depth];
75
+ // render empty node if no matched route
76
+ if (!matched) {
77
+ cache[name] = null;
78
+ return h()
79
+ }
80
+
81
+ const component = cache[name] = matched.components[name];
82
+
83
+ // attach instance registration hook
84
+ // this will be called in the instance's injected lifecycle hooks
85
+ data.registerRouteInstance = (vm, val) => {
86
+ // val could be undefined for unregistration
87
+ const current = matched.instances[name];
88
+ if (
89
+ (val && current !== vm) ||
90
+ (!val && current === vm)
91
+ ) {
92
+ matched.instances[name] = val;
93
+ }
94
+ }
95
+
96
+ // also register instance in prepatch hook
97
+ // in case the same component instance is reused across different routes
98
+ ;(data.hook || (data.hook = {})).prepatch = (_, knode) => {
99
+ matched.instances[name] = knode.componentInstance;
100
+ };
101
+
102
+ // register instance in init hook
103
+ // in case kept-alive component be actived when routes changed
104
+ data.hook.init = (knode) => {
105
+ if (knode.data.keepAlive &&
106
+ knode.componentInstance &&
107
+ knode.componentInstance !== matched.instances[name]
108
+ ) {
109
+ matched.instances[name] = knode.componentInstance;
110
+ }
111
+ };
112
+
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
+ }
126
+ }
127
+
128
+ return h(component, data, children)
129
+ }
130
+ }
131
+
132
+ function resolveProps (route, config) {
133
+ switch (typeof config) {
134
+ case 'undefined':
135
+ return
136
+ case 'object':
137
+ return config
138
+ case 'function':
139
+ return config(route)
140
+ case 'boolean':
141
+ return config ? route.params : undefined
142
+ default:
143
+ {
144
+ warn(
145
+ false,
146
+ `props in "${route.path}" is a ${typeof config}, ` +
147
+ `expecting an object, function or boolean.`
148
+ );
149
+ }
150
+ }
151
+ }
152
+
153
+ /* */
154
+
155
+ const encodeReserveRE = /[!'()*]/g;
156
+ const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16);
157
+ const commaRE = /%2C/g;
158
+
159
+ // fixed encodeURIComponent which is more conformant to RFC3986:
160
+ // - escapes [!'()*]
161
+ // - preserve commas
162
+ const encode = str => encodeURIComponent(str)
163
+ .replace(encodeReserveRE, encodeReserveReplacer)
164
+ .replace(commaRE, ',');
165
+
166
+ const decode = decodeURIComponent;
167
+
168
+ function resolveQuery (
169
+ query,
170
+ extraQuery = {},
171
+ _parseQuery
172
+ ) {
173
+ const parse = _parseQuery || parseQuery;
174
+ let parsedQuery;
175
+ try {
176
+ parsedQuery = parse(query || '');
177
+ } catch (e) {
178
+ "development" !== 'production' && warn(false, e.message);
179
+ parsedQuery = {};
180
+ }
181
+ for (const key in extraQuery) {
182
+ parsedQuery[key] = extraQuery[key];
183
+ }
184
+ return parsedQuery
185
+ }
186
+
187
+ function parseQuery (query) {
188
+ const res = {};
189
+
190
+ query = query.trim().replace(/^(\?|#|&)/, '');
191
+
192
+ if (!query) {
193
+ return res
194
+ }
195
+
196
+ query.split('&').forEach(param => {
197
+ const parts = param.replace(/\+/g, ' ').split('=');
198
+ const key = decode(parts.shift());
199
+ const val = parts.length > 0
200
+ ? decode(parts.join('='))
201
+ : null;
202
+
203
+ if (res[key] === undefined) {
204
+ res[key] = val;
205
+ } else if (Array.isArray(res[key])) {
206
+ res[key].push(val);
207
+ } else {
208
+ res[key] = [res[key], val];
209
+ }
210
+ });
211
+
212
+ return res
213
+ }
214
+
215
+ function stringifyQuery (obj) {
216
+ const res = obj ? Object.keys(obj).map(key => {
217
+ const val = obj[key];
218
+
219
+ if (val === undefined) {
220
+ return ''
221
+ }
222
+
223
+ if (val === null) {
224
+ return encode(key)
225
+ }
226
+
227
+ if (Array.isArray(val)) {
228
+ const result = [];
229
+ val.forEach(val2 => {
230
+ if (val2 === undefined) {
231
+ return
232
+ }
233
+ if (val2 === null) {
234
+ result.push(encode(key));
235
+ } else {
236
+ result.push(encode(key) + '=' + encode(val2));
237
+ }
238
+ });
239
+ return result.join('&')
240
+ }
241
+
242
+ return encode(key) + '=' + encode(val)
243
+ }).filter(x => x.length > 0).join('&') : null;
244
+ return res ? `?${res}` : ''
245
+ }
246
+
247
+ /* */
248
+
249
+ const trailingSlashRE = /\/?$/;
250
+
251
+ function createRoute (
252
+ record,
253
+ location,
254
+ redirectedFrom,
255
+ router
256
+ ) {
257
+ const stringifyQuery$$1 = router && router.options.stringifyQuery;
258
+
259
+ let query = location.query || {};
260
+ try {
261
+ query = clone(query);
262
+ } catch (e) {}
263
+
264
+ const route = {
265
+ name: location.name || (record && record.name),
266
+ meta: (record && record.meta) || {},
267
+ path: location.path || '/',
268
+ hash: location.hash || '',
269
+ query,
270
+ params: location.params || {},
271
+ fullPath: getFullPath(location, stringifyQuery$$1),
272
+ matched: record ? formatMatch(record) : []
273
+ };
274
+ if (redirectedFrom) {
275
+ route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery$$1);
276
+ }
277
+ return Object.freeze(route)
278
+ }
279
+
280
+ function clone (value) {
281
+ if (Array.isArray(value)) {
282
+ return value.map(clone)
283
+ } else if (value && typeof value === 'object') {
284
+ const res = {};
285
+ for (const key in value) {
286
+ res[key] = clone(value[key]);
287
+ }
288
+ return res
289
+ } else {
290
+ return value
291
+ }
292
+ }
293
+
294
+ // the starting route that represents the initial state
295
+ const START = createRoute(null, {
296
+ path: '/'
297
+ });
298
+
299
+ function formatMatch (record) {
300
+ const res = [];
301
+ while (record) {
302
+ res.unshift(record);
303
+ record = record.parent;
304
+ }
305
+ return res
306
+ }
307
+
308
+ function getFullPath (
309
+ { path, query = {}, hash = '' },
310
+ _stringifyQuery
311
+ ) {
312
+ const stringify = _stringifyQuery || stringifyQuery;
313
+ return (path || '/') + stringify(query) + hash
314
+ }
315
+
316
+ function isSameRoute (a, b) {
317
+ if (b === START) {
318
+ return a === b
319
+ } else if (!b) {
320
+ return false
321
+ } else if (a.path && b.path) {
322
+ return (
323
+ a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
324
+ a.hash === b.hash &&
325
+ isObjectEqual(a.query, b.query)
326
+ )
327
+ } else if (a.name && b.name) {
328
+ return (
329
+ a.name === b.name &&
330
+ a.hash === b.hash &&
331
+ isObjectEqual(a.query, b.query) &&
332
+ isObjectEqual(a.params, b.params)
333
+ )
334
+ } else {
335
+ return false
336
+ }
337
+ }
338
+
339
+ function isObjectEqual (a = {}, b = {}) {
340
+ // handle null value #1566
341
+ if (!a || !b) return a === b
342
+ const aKeys = Object.keys(a);
343
+ const bKeys = Object.keys(b);
344
+ if (aKeys.length !== bKeys.length) {
345
+ return false
346
+ }
347
+ return aKeys.every(key => {
348
+ const aVal = a[key];
349
+ const bVal = b[key];
350
+ // check nested equality
351
+ if (typeof aVal === 'object' && typeof bVal === 'object') {
352
+ return isObjectEqual(aVal, bVal)
353
+ }
354
+ return String(aVal) === String(bVal)
355
+ })
356
+ }
357
+
358
+ function isIncludedRoute (current, target) {
359
+ return (
360
+ current.path.replace(trailingSlashRE, '/').indexOf(
361
+ target.path.replace(trailingSlashRE, '/')
362
+ ) === 0 &&
363
+ (!target.hash || current.hash === target.hash) &&
364
+ queryIncludes(current.query, target.query)
365
+ )
366
+ }
367
+
368
+ function queryIncludes (current, target) {
369
+ for (const key in target) {
370
+ if (!(key in current)) {
371
+ return false
372
+ }
373
+ }
374
+ return true
375
+ }
376
+
377
+ /* */
378
+
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
+ function resolvePath (
569
+ relative,
570
+ base,
571
+ append
572
+ ) {
573
+ const firstChar = relative.charAt(0);
574
+ if (firstChar === '/') {
575
+ return relative
576
+ }
577
+
578
+ if (firstChar === '?' || firstChar === '#') {
579
+ return base + relative
580
+ }
581
+
582
+ const stack = base.split('/');
583
+
584
+ // remove trailing segment if:
585
+ // - not appending
586
+ // - appending to trailing slash (last segment is empty)
587
+ if (!append || !stack[stack.length - 1]) {
588
+ stack.pop();
589
+ }
590
+
591
+ // resolve relative path
592
+ const segments = relative.replace(/^\//, '').split('/');
593
+ for (let i = 0; i < segments.length; i++) {
594
+ const segment = segments[i];
595
+ if (segment === '..') {
596
+ stack.pop();
597
+ } else if (segment !== '.') {
598
+ stack.push(segment);
599
+ }
600
+ }
601
+
602
+ // ensure leading slash
603
+ if (stack[0] !== '') {
604
+ stack.unshift('');
605
+ }
606
+
607
+ return stack.join('/')
608
+ }
609
+
610
+ function parsePath (path) {
611
+ let hash = '';
612
+ let query = '';
613
+
614
+ const hashIndex = path.indexOf('#');
615
+ if (hashIndex >= 0) {
616
+ hash = path.slice(hashIndex);
617
+ path = path.slice(0, hashIndex);
618
+ }
619
+
620
+ const queryIndex = path.indexOf('?');
621
+ if (queryIndex >= 0) {
622
+ query = path.slice(queryIndex + 1);
623
+ path = path.slice(0, queryIndex);
624
+ }
625
+
626
+ return {
627
+ path,
628
+ query,
629
+ hash
630
+ }
631
+ }
632
+
633
+ function cleanPath (path) {
634
+ return path.replace(/\/\//g, '/')
635
+ }
636
+
637
+ var isarray = Array.isArray || function (arr) {
638
+ return Object.prototype.toString.call(arr) == '[object Array]';
639
+ };
640
+
641
+ /**
642
+ * Expose `pathToRegexp`.
643
+ */
644
+ var pathToRegexp_1 = pathToRegexp;
645
+ var parse_1 = parse;
646
+ var compile_1 = compile;
647
+ var tokensToFunction_1 = tokensToFunction;
648
+ var tokensToRegExp_1 = tokensToRegExp;
649
+
650
+ /**
651
+ * The main path matching regexp utility.
652
+ *
653
+ * @type {RegExp}
654
+ */
655
+ var PATH_REGEXP = new RegExp([
656
+ // Match escaped characters that would otherwise appear in future matches.
657
+ // This allows the user to escape special characters that won't transform.
658
+ '(\\\\.)',
659
+ // Match Express-style parameters and un-named parameters with a prefix
660
+ // and optional suffixes. Matches appear as:
661
+ //
662
+ // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
663
+ // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
664
+ // "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
665
+ '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'
666
+ ].join('|'), 'g');
667
+
668
+ /**
669
+ * Parse a string for the raw tokens.
670
+ *
671
+ * @param {string} str
672
+ * @param {Object=} options
673
+ * @return {!Array}
674
+ */
675
+ function parse (str, options) {
676
+ var tokens = [];
677
+ var key = 0;
678
+ var index = 0;
679
+ var path = '';
680
+ var defaultDelimiter = options && options.delimiter || '/';
681
+ var res;
682
+
683
+ while ((res = PATH_REGEXP.exec(str)) != null) {
684
+ var m = res[0];
685
+ var escaped = res[1];
686
+ var offset = res.index;
687
+ path += str.slice(index, offset);
688
+ index = offset + m.length;
689
+
690
+ // Ignore already escaped sequences.
691
+ if (escaped) {
692
+ path += escaped[1];
693
+ continue
694
+ }
695
+
696
+ var next = str[index];
697
+ var prefix = res[2];
698
+ var name = res[3];
699
+ var capture = res[4];
700
+ var group = res[5];
701
+ var modifier = res[6];
702
+ var asterisk = res[7];
703
+
704
+ // Push the current path onto the tokens.
705
+ if (path) {
706
+ tokens.push(path);
707
+ path = '';
708
+ }
709
+
710
+ var partial = prefix != null && next != null && next !== prefix;
711
+ var repeat = modifier === '+' || modifier === '*';
712
+ var optional = modifier === '?' || modifier === '*';
713
+ var delimiter = res[2] || defaultDelimiter;
714
+ var pattern = capture || group;
715
+
716
+ tokens.push({
717
+ name: name || key++,
718
+ prefix: prefix || '',
719
+ delimiter: delimiter,
720
+ optional: optional,
721
+ repeat: repeat,
722
+ partial: partial,
723
+ asterisk: !!asterisk,
724
+ pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?')
725
+ });
726
+ }
727
+
728
+ // Match any characters still remaining.
729
+ if (index < str.length) {
730
+ path += str.substr(index);
731
+ }
732
+
733
+ // If the path exists, push it onto the end.
734
+ if (path) {
735
+ tokens.push(path);
736
+ }
737
+
738
+ return tokens
739
+ }
740
+
741
+ /**
742
+ * Compile a string to a template function for the path.
743
+ *
744
+ * @param {string} str
745
+ * @param {Object=} options
746
+ * @return {!function(Object=, Object=)}
747
+ */
748
+ function compile (str, options) {
749
+ return tokensToFunction(parse(str, options), options)
750
+ }
751
+
752
+ /**
753
+ * Prettier encoding of URI path segments.
754
+ *
755
+ * @param {string}
756
+ * @return {string}
757
+ */
758
+ function encodeURIComponentPretty (str) {
759
+ return encodeURI(str).replace(/[\/?#]/g, function (c) {
760
+ return '%' + c.charCodeAt(0).toString(16).toUpperCase()
761
+ })
762
+ }
763
+
764
+ /**
765
+ * Encode the asterisk parameter. Similar to `pretty`, but allows slashes.
766
+ *
767
+ * @param {string}
768
+ * @return {string}
769
+ */
770
+ function encodeAsterisk (str) {
771
+ return encodeURI(str).replace(/[?#]/g, function (c) {
772
+ return '%' + c.charCodeAt(0).toString(16).toUpperCase()
773
+ })
774
+ }
775
+
776
+ /**
777
+ * Expose a method for transforming tokens into the path function.
778
+ */
779
+ function tokensToFunction (tokens, options) {
780
+ // Compile all the tokens into regexps.
781
+ var matches = new Array(tokens.length);
782
+
783
+ // Compile all the patterns before compilation.
784
+ for (var i = 0; i < tokens.length; i++) {
785
+ if (typeof tokens[i] === 'object') {
786
+ matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options));
787
+ }
788
+ }
789
+
790
+ return function (obj, opts) {
791
+ var path = '';
792
+ var data = obj || {};
793
+ var options = opts || {};
794
+ var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent;
795
+
796
+ for (var i = 0; i < tokens.length; i++) {
797
+ var token = tokens[i];
798
+
799
+ if (typeof token === 'string') {
800
+ path += token;
801
+
802
+ continue
803
+ }
804
+
805
+ var value = data[token.name];
806
+ var segment;
807
+
808
+ if (value == null) {
809
+ if (token.optional) {
810
+ // Prepend partial segment prefixes.
811
+ if (token.partial) {
812
+ path += token.prefix;
813
+ }
814
+
815
+ continue
816
+ } else {
817
+ throw new TypeError('Expected "' + token.name + '" to be defined')
818
+ }
819
+ }
820
+
821
+ if (isarray(value)) {
822
+ if (!token.repeat) {
823
+ throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`')
824
+ }
825
+
826
+ if (value.length === 0) {
827
+ if (token.optional) {
828
+ continue
829
+ } else {
830
+ throw new TypeError('Expected "' + token.name + '" to not be empty')
831
+ }
832
+ }
833
+
834
+ for (var j = 0; j < value.length; j++) {
835
+ segment = encode(value[j]);
836
+
837
+ if (!matches[i].test(segment)) {
838
+ throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`')
839
+ }
840
+
841
+ path += (j === 0 ? token.prefix : token.delimiter) + segment;
842
+ }
843
+
844
+ continue
845
+ }
846
+
847
+ segment = token.asterisk ? encodeAsterisk(value) : encode(value);
848
+
849
+ if (!matches[i].test(segment)) {
850
+ throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
851
+ }
852
+
853
+ path += token.prefix + segment;
854
+ }
855
+
856
+ return path
857
+ }
858
+ }
859
+
860
+ /**
861
+ * Escape a regular expression string.
862
+ *
863
+ * @param {string} str
864
+ * @return {string}
865
+ */
866
+ function escapeString (str) {
867
+ return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1')
868
+ }
869
+
870
+ /**
871
+ * Escape the capturing group by escaping special characters and meaning.
872
+ *
873
+ * @param {string} group
874
+ * @return {string}
875
+ */
876
+ function escapeGroup (group) {
877
+ return group.replace(/([=!:$\/()])/g, '\\$1')
878
+ }
879
+
880
+ /**
881
+ * Attach the keys as a property of the regexp.
882
+ *
883
+ * @param {!RegExp} re
884
+ * @param {Array} keys
885
+ * @return {!RegExp}
886
+ */
887
+ function attachKeys (re, keys) {
888
+ re.keys = keys;
889
+ return re
890
+ }
891
+
892
+ /**
893
+ * Get the flags for a regexp from the options.
894
+ *
895
+ * @param {Object} options
896
+ * @return {string}
897
+ */
898
+ function flags (options) {
899
+ return options && options.sensitive ? '' : 'i'
900
+ }
901
+
902
+ /**
903
+ * Pull out keys from a regexp.
904
+ *
905
+ * @param {!RegExp} path
906
+ * @param {!Array} keys
907
+ * @return {!RegExp}
908
+ */
909
+ function regexpToRegexp (path, keys) {
910
+ // Use a negative lookahead to match only capturing groups.
911
+ var groups = path.source.match(/\((?!\?)/g);
912
+
913
+ if (groups) {
914
+ for (var i = 0; i < groups.length; i++) {
915
+ keys.push({
916
+ name: i,
917
+ prefix: null,
918
+ delimiter: null,
919
+ optional: false,
920
+ repeat: false,
921
+ partial: false,
922
+ asterisk: false,
923
+ pattern: null
924
+ });
925
+ }
926
+ }
927
+
928
+ return attachKeys(path, keys)
929
+ }
930
+
931
+ /**
932
+ * Transform an array into a regexp.
933
+ *
934
+ * @param {!Array} path
935
+ * @param {Array} keys
936
+ * @param {!Object} options
937
+ * @return {!RegExp}
938
+ */
939
+ function arrayToRegexp (path, keys, options) {
940
+ var parts = [];
941
+
942
+ for (var i = 0; i < path.length; i++) {
943
+ parts.push(pathToRegexp(path[i], keys, options).source);
944
+ }
945
+
946
+ var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options));
947
+
948
+ return attachKeys(regexp, keys)
949
+ }
950
+
951
+ /**
952
+ * Create a path regexp from string input.
953
+ *
954
+ * @param {string} path
955
+ * @param {!Array} keys
956
+ * @param {!Object} options
957
+ * @return {!RegExp}
958
+ */
959
+ function stringToRegexp (path, keys, options) {
960
+ return tokensToRegExp(parse(path, options), keys, options)
961
+ }
962
+
963
+ /**
964
+ * Expose a function for taking tokens and returning a RegExp.
965
+ *
966
+ * @param {!Array} tokens
967
+ * @param {(Array|Object)=} keys
968
+ * @param {Object=} options
969
+ * @return {!RegExp}
970
+ */
971
+ function tokensToRegExp (tokens, keys, options) {
972
+ if (!isarray(keys)) {
973
+ options = /** @type {!Object} */ (keys || options);
974
+ keys = [];
975
+ }
976
+
977
+ options = options || {};
978
+
979
+ var strict = options.strict;
980
+ var end = options.end !== false;
981
+ var route = '';
982
+
983
+ // Iterate over the tokens and create our regexp string.
984
+ for (var i = 0; i < tokens.length; i++) {
985
+ var token = tokens[i];
986
+
987
+ if (typeof token === 'string') {
988
+ route += escapeString(token);
989
+ } else {
990
+ var prefix = escapeString(token.prefix);
991
+ var capture = '(?:' + token.pattern + ')';
992
+
993
+ keys.push(token);
994
+
995
+ if (token.repeat) {
996
+ capture += '(?:' + prefix + capture + ')*';
997
+ }
998
+
999
+ if (token.optional) {
1000
+ if (!token.partial) {
1001
+ capture = '(?:' + prefix + '(' + capture + '))?';
1002
+ } else {
1003
+ capture = prefix + '(' + capture + ')?';
1004
+ }
1005
+ } else {
1006
+ capture = prefix + '(' + capture + ')';
1007
+ }
1008
+
1009
+ route += capture;
1010
+ }
1011
+ }
1012
+
1013
+ var delimiter = escapeString(options.delimiter || '/');
1014
+ var endsWithDelimiter = route.slice(-delimiter.length) === delimiter;
1015
+
1016
+ // In non-strict mode we allow a slash at the end of match. If the path to
1017
+ // match already ends with a slash, we remove it for consistency. The slash
1018
+ // is valid at the end of a path match, not in the middle. This is important
1019
+ // in non-ending mode, where "/test/" shouldn't match "/test//route".
1020
+ if (!strict) {
1021
+ route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?';
1022
+ }
1023
+
1024
+ if (end) {
1025
+ route += '$';
1026
+ } else {
1027
+ // In non-ending mode, we need the capturing groups to match as much as
1028
+ // possible by using a positive lookahead to the end or next path segment.
1029
+ route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)';
1030
+ }
1031
+
1032
+ return attachKeys(new RegExp('^' + route, flags(options)), keys)
1033
+ }
1034
+
1035
+ /**
1036
+ * Normalize the given path string, returning a regular expression.
1037
+ *
1038
+ * An empty array can be passed in for the keys, which will hold the
1039
+ * placeholder key descriptions. For example, using `/user/:id`, `keys` will
1040
+ * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
1041
+ *
1042
+ * @param {(string|RegExp|Array)} path
1043
+ * @param {(Array|Object)=} keys
1044
+ * @param {Object=} options
1045
+ * @return {!RegExp}
1046
+ */
1047
+ function pathToRegexp (path, keys, options) {
1048
+ if (!isarray(keys)) {
1049
+ options = /** @type {!Object} */ (keys || options);
1050
+ keys = [];
1051
+ }
1052
+
1053
+ options = options || {};
1054
+
1055
+ if (path instanceof RegExp) {
1056
+ return regexpToRegexp(path, /** @type {!Array} */ (keys))
1057
+ }
1058
+
1059
+ if (isarray(path)) {
1060
+ return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options)
1061
+ }
1062
+
1063
+ return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options)
1064
+ }
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
+
1070
+ /* */
1071
+
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
+ }
1100
+
1101
+ /* */
1102
+
1103
+ function createRouteMap (
1104
+ routes,
1105
+ oldPathList,
1106
+ oldPathMap,
1107
+ oldNameMap
1108
+ ) {
1109
+ // the path list is used to control path matching priority
1110
+ const pathList = oldPathList || [];
1111
+ // $flow-disable-line
1112
+ const pathMap = oldPathMap || Object.create(null);
1113
+ // $flow-disable-line
1114
+ const nameMap = oldNameMap || Object.create(null);
1115
+
1116
+ routes.forEach(route => {
1117
+ addRouteRecord(pathList, pathMap, nameMap, route);
1118
+ });
1119
+
1120
+ // ensure wildcard routes are always at the end
1121
+ for (let i = 0, l = pathList.length; i < l; i++) {
1122
+ if (pathList[i] === '*') {
1123
+ pathList.push(pathList.splice(i, 1)[0]);
1124
+ l--;
1125
+ i--;
1126
+ }
1127
+ }
1128
+
1129
+ return {
1130
+ pathList,
1131
+ pathMap,
1132
+ nameMap
1133
+ }
1134
+ }
1135
+
1136
+ function addRouteRecord (
1137
+ pathList,
1138
+ pathMap,
1139
+ nameMap,
1140
+ route,
1141
+ parent,
1142
+ matchAs
1143
+ ) {
1144
+ const { path, name } = route;
1145
+ {
1146
+ assert(path != null, `"path" is required in a route configuration.`);
1147
+ assert(
1148
+ typeof route.component !== 'string',
1149
+ `route config "component" for path: ${String(path || name)} cannot be a ` +
1150
+ `string id. Use an actual component instead.`
1151
+ );
1152
+ }
1153
+
1154
+ const pathToRegexpOptions = route.pathToRegexpOptions || {};
1155
+ const normalizedPath = normalizePath(
1156
+ path,
1157
+ parent,
1158
+ pathToRegexpOptions.strict
1159
+ );
1160
+
1161
+ if (typeof route.caseSensitive === 'boolean') {
1162
+ pathToRegexpOptions.sensitive = route.caseSensitive;
1163
+ }
1164
+
1165
+ const record = {
1166
+ path: normalizedPath,
1167
+ regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1168
+ components: route.components || { default: route.component },
1169
+ instances: {},
1170
+ name,
1171
+ parent,
1172
+ matchAs,
1173
+ redirect: route.redirect,
1174
+ beforeEnter: route.beforeEnter,
1175
+ meta: route.meta || {},
1176
+ props: route.props == null
1177
+ ? {}
1178
+ : route.components
1179
+ ? route.props
1180
+ : { default: route.props }
1181
+ };
1182
+
1183
+ if (route.children) {
1184
+ // Warn if route is named, does not redirect and has a default child route.
1185
+ // If users navigate to this route by name, the default child will
1186
+ // not be rendered (GH Issue #629)
1187
+ {
1188
+ if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) {
1189
+ warn(
1190
+ false,
1191
+ `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.`
1196
+ );
1197
+ }
1198
+ }
1199
+ route.children.forEach(child => {
1200
+ const childMatchAs = matchAs
1201
+ ? cleanPath(`${matchAs}/${child.path}`)
1202
+ : undefined;
1203
+ addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
1204
+ });
1205
+ }
1206
+
1207
+ if (route.alias !== undefined) {
1208
+ const aliases = Array.isArray(route.alias)
1209
+ ? route.alias
1210
+ : [route.alias];
1211
+
1212
+ aliases.forEach(alias => {
1213
+ const aliasRoute = {
1214
+ path: alias,
1215
+ children: route.children
1216
+ };
1217
+ addRouteRecord(
1218
+ pathList,
1219
+ pathMap,
1220
+ nameMap,
1221
+ aliasRoute,
1222
+ parent,
1223
+ record.path || '/' // matchAs
1224
+ );
1225
+ });
1226
+ }
1227
+
1228
+ if (!pathMap[record.path]) {
1229
+ pathList.push(record.path);
1230
+ pathMap[record.path] = record;
1231
+ }
1232
+
1233
+ if (name) {
1234
+ if (!nameMap[name]) {
1235
+ nameMap[name] = record;
1236
+ } else if ("development" !== 'production' && !matchAs) {
1237
+ warn(
1238
+ false,
1239
+ `Duplicate named routes definition: ` +
1240
+ `{ name: "${name}", path: "${record.path}" }`
1241
+ );
1242
+ }
1243
+ }
1244
+ }
1245
+
1246
+ function compileRouteRegex (path, pathToRegexpOptions) {
1247
+ const regex = pathToRegexp_1(path, [], pathToRegexpOptions);
1248
+ {
1249
+ const keys = Object.create(null);
1250
+ regex.keys.forEach(key => {
1251
+ warn(!keys[key.name], `Duplicate param keys in route with path: "${path}"`);
1252
+ keys[key.name] = true;
1253
+ });
1254
+ }
1255
+ return regex
1256
+ }
1257
+
1258
+ function normalizePath (path, parent, strict) {
1259
+ if (!strict) path = path.replace(/\/$/, '');
1260
+ if (path[0] === '/') return path
1261
+ if (parent == null) return path
1262
+ return cleanPath(`${parent.path}/${path}`)
1263
+ }
1264
+
1265
+ /* */
1266
+
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
+
1326
+
1327
+ function createMatcher (
1328
+ routes,
1329
+ router
1330
+ ) {
1331
+ const { pathList, pathMap, nameMap } = createRouteMap(routes);
1332
+
1333
+ function addRoutes (routes) {
1334
+ createRouteMap(routes, pathList, pathMap, nameMap);
1335
+ }
1336
+
1337
+ function match (
1338
+ raw,
1339
+ currentRoute,
1340
+ redirectedFrom
1341
+ ) {
1342
+ const location = normalizeLocation(raw, currentRoute, false, router);
1343
+ const { name } = location;
1344
+
1345
+ if (name) {
1346
+ const record = nameMap[name];
1347
+ {
1348
+ warn(record, `Route with name '${name}' does not exist`);
1349
+ }
1350
+ if (!record) return _createRoute(null, location)
1351
+ const paramNames = record.regex.keys
1352
+ .filter(key => !key.optional)
1353
+ .map(key => key.name);
1354
+
1355
+ if (typeof location.params !== 'object') {
1356
+ location.params = {};
1357
+ }
1358
+
1359
+ if (currentRoute && typeof currentRoute.params === 'object') {
1360
+ for (const key in currentRoute.params) {
1361
+ if (!(key in location.params) && paramNames.indexOf(key) > -1) {
1362
+ location.params[key] = currentRoute.params[key];
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ location.path = fillParams(record.path, location.params, `named route "${name}"`);
1368
+ return _createRoute(record, location, redirectedFrom)
1369
+ } else if (location.path) {
1370
+ location.params = {};
1371
+ for (let i = 0; i < pathList.length; i++) {
1372
+ const path = pathList[i];
1373
+ const record = pathMap[path];
1374
+ if (matchRoute(record.regex, location.path, location.params)) {
1375
+ return _createRoute(record, location, redirectedFrom)
1376
+ }
1377
+ }
1378
+ }
1379
+ // no match
1380
+ return _createRoute(null, location)
1381
+ }
1382
+
1383
+ function redirect (
1384
+ record,
1385
+ location
1386
+ ) {
1387
+ const originalRedirect = record.redirect;
1388
+ let redirect = typeof originalRedirect === 'function'
1389
+ ? originalRedirect(createRoute(record, location, null, router))
1390
+ : originalRedirect;
1391
+
1392
+ if (typeof redirect === 'string') {
1393
+ redirect = { path: redirect };
1394
+ }
1395
+
1396
+ if (!redirect || typeof redirect !== 'object') {
1397
+ {
1398
+ warn(
1399
+ false, `invalid redirect option: ${JSON.stringify(redirect)}`
1400
+ );
1401
+ }
1402
+ return _createRoute(null, location)
1403
+ }
1404
+
1405
+ const re = redirect;
1406
+ const { name, path } = re;
1407
+ let { query, hash, params } = location;
1408
+ query = re.hasOwnProperty('query') ? re.query : query;
1409
+ hash = re.hasOwnProperty('hash') ? re.hash : hash;
1410
+ params = re.hasOwnProperty('params') ? re.params : params;
1411
+
1412
+ if (name) {
1413
+ // resolved named direct
1414
+ const targetRecord = nameMap[name];
1415
+ {
1416
+ assert(targetRecord, `redirect failed: named route "${name}" not found.`);
1417
+ }
1418
+ return match({
1419
+ _normalized: true,
1420
+ name,
1421
+ query,
1422
+ hash,
1423
+ params
1424
+ }, undefined, location)
1425
+ } else if (path) {
1426
+ // 1. resolve relative redirect
1427
+ const rawPath = resolveRecordPath(path, record);
1428
+ // 2. resolve params
1429
+ const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`);
1430
+ // 3. rematch with existing query and hash
1431
+ return match({
1432
+ _normalized: true,
1433
+ path: resolvedPath,
1434
+ query,
1435
+ hash
1436
+ }, undefined, location)
1437
+ } else {
1438
+ {
1439
+ warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`);
1440
+ }
1441
+ return _createRoute(null, location)
1442
+ }
1443
+ }
1444
+
1445
+ function alias (
1446
+ record,
1447
+ location,
1448
+ matchAs
1449
+ ) {
1450
+ const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`);
1451
+ const aliasedMatch = match({
1452
+ _normalized: true,
1453
+ path: aliasedPath
1454
+ });
1455
+ if (aliasedMatch) {
1456
+ const matched = aliasedMatch.matched;
1457
+ const aliasedRecord = matched[matched.length - 1];
1458
+ location.params = aliasedMatch.params;
1459
+ return _createRoute(aliasedRecord, location)
1460
+ }
1461
+ return _createRoute(null, location)
1462
+ }
1463
+
1464
+ function _createRoute (
1465
+ record,
1466
+ location,
1467
+ redirectedFrom
1468
+ ) {
1469
+ if (record && record.redirect) {
1470
+ return redirect(record, redirectedFrom || location)
1471
+ }
1472
+ if (record && record.matchAs) {
1473
+ return alias(record, location, record.matchAs)
1474
+ }
1475
+ return createRoute(record, location, redirectedFrom, router)
1476
+ }
1477
+
1478
+ return {
1479
+ match,
1480
+ addRoutes
1481
+ }
1482
+ }
1483
+
1484
+ function matchRoute (
1485
+ regex,
1486
+ path,
1487
+ params
1488
+ ) {
1489
+ const m = path.match(regex);
1490
+
1491
+ if (!m) {
1492
+ return false
1493
+ } else if (!params) {
1494
+ return true
1495
+ }
1496
+
1497
+ for (let i = 1, len = m.length; i < len; ++i) {
1498
+ const key = regex.keys[i - 1];
1499
+ const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i];
1500
+ if (key) {
1501
+ // Fix #1994: using * with props: true generates a param named 0
1502
+ params[key.name || 'pathMatch'] = val;
1503
+ }
1504
+ }
1505
+
1506
+ return true
1507
+ }
1508
+
1509
+ function resolveRecordPath (path, record) {
1510
+ return resolvePath(path, record.parent ? record.parent.path : '/', true)
1511
+ }
1512
+
1513
+ /* */
1514
+
1515
+ const positionStore = Object.create(null);
1516
+
1517
+ function setupScroll () {
1518
+ // Fix for #1585 for Firefox
1519
+ // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
1520
+ // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
1521
+ // window.location.protocol + '//' + window.location.host
1522
+ // location.host contains the port and location.hostname doesn't
1523
+ const protocolAndPath = window.location.protocol + '//' + window.location.host;
1524
+ const absolutePath = window.location.href.replace(protocolAndPath, '');
1525
+ window.history.replaceState({ key: getStateKey() }, '', absolutePath);
1526
+ window.addEventListener('popstate', e => {
1527
+ saveScrollPosition();
1528
+ if (e.state && e.state.key) {
1529
+ setStateKey(e.state.key);
1530
+ }
1531
+ });
1532
+ }
1533
+
1534
+ function handleScroll (
1535
+ router,
1536
+ to,
1537
+ from,
1538
+ isPop
1539
+ ) {
1540
+ if (!router.app) {
1541
+ return
1542
+ }
1543
+
1544
+ const behavior = router.options.scrollBehavior;
1545
+ if (!behavior) {
1546
+ return
1547
+ }
1548
+
1549
+ {
1550
+ assert(typeof behavior === 'function', `scrollBehavior must be a function`);
1551
+ }
1552
+
1553
+ // wait until re-render finishes before scrolling
1554
+ router.app.$nextTick(() => {
1555
+ const position = getScrollPosition();
1556
+ const shouldScroll = behavior.call(router, to, from, isPop ? position : null);
1557
+
1558
+ if (!shouldScroll) {
1559
+ return
1560
+ }
1561
+
1562
+ if (typeof shouldScroll.then === 'function') {
1563
+ shouldScroll.then(shouldScroll => {
1564
+ scrollToPosition((shouldScroll), position);
1565
+ }).catch(err => {
1566
+ {
1567
+ assert(false, err.toString());
1568
+ }
1569
+ });
1570
+ } else {
1571
+ scrollToPosition(shouldScroll, position);
1572
+ }
1573
+ });
1574
+ }
1575
+
1576
+ function saveScrollPosition () {
1577
+ const key = getStateKey();
1578
+ if (key) {
1579
+ positionStore[key] = {
1580
+ x: window.pageXOffset,
1581
+ y: window.pageYOffset
1582
+ };
1583
+ }
1584
+ }
1585
+
1586
+ function getScrollPosition () {
1587
+ const key = getStateKey();
1588
+ if (key) {
1589
+ return positionStore[key]
1590
+ }
1591
+ }
1592
+
1593
+ function getElementPosition (el, offset) {
1594
+ const docEl = document.documentElement;
1595
+ const docRect = docEl.getBoundingClientRect();
1596
+ const elRect = el.getBoundingClientRect();
1597
+ return {
1598
+ x: elRect.left - docRect.left - offset.x,
1599
+ y: elRect.top - docRect.top - offset.y
1600
+ }
1601
+ }
1602
+
1603
+ function isValidPosition (obj) {
1604
+ return isNumber(obj.x) || isNumber(obj.y)
1605
+ }
1606
+
1607
+ function normalizePosition (obj) {
1608
+ return {
1609
+ x: isNumber(obj.x) ? obj.x : window.pageXOffset,
1610
+ y: isNumber(obj.y) ? obj.y : window.pageYOffset
1611
+ }
1612
+ }
1613
+
1614
+ function normalizeOffset (obj) {
1615
+ return {
1616
+ x: isNumber(obj.x) ? obj.x : 0,
1617
+ y: isNumber(obj.y) ? obj.y : 0
1618
+ }
1619
+ }
1620
+
1621
+ function isNumber (v) {
1622
+ return typeof v === 'number'
1623
+ }
1624
+
1625
+ function scrollToPosition (shouldScroll, position) {
1626
+ const isObject = typeof shouldScroll === 'object';
1627
+ if (isObject && typeof shouldScroll.selector === 'string') {
1628
+ const el = document.querySelector(shouldScroll.selector);
1629
+ if (el) {
1630
+ let offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {};
1631
+ offset = normalizeOffset(offset);
1632
+ position = getElementPosition(el, offset);
1633
+ } else if (isValidPosition(shouldScroll)) {
1634
+ position = normalizePosition(shouldScroll);
1635
+ }
1636
+ } else if (isObject && isValidPosition(shouldScroll)) {
1637
+ position = normalizePosition(shouldScroll);
1638
+ }
1639
+
1640
+ if (position) {
1641
+ window.scrollTo(position.x, position.y);
1642
+ }
1643
+ }
1644
+
1645
+ /* */
1646
+
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();
1668
+
1669
+ function genKey () {
1670
+ return Time.now().toFixed(3)
1671
+ }
1672
+
1673
+ function getStateKey () {
1674
+ return _key
1675
+ }
1676
+
1677
+ function setStateKey (key) {
1678
+ _key = key;
1679
+ }
1680
+
1681
+ function pushState (url, replace) {
1682
+ saveScrollPosition();
1683
+ // try...catch the pushState call to get around Safari
1684
+ // DOM Exception 18 where it limits to 100 pushState calls
1685
+ const history = window.history;
1686
+ try {
1687
+ if (replace) {
1688
+ history.replaceState({ key: _key }, '', url);
1689
+ } else {
1690
+ _key = genKey();
1691
+ history.pushState({ key: _key }, '', url);
1692
+ }
1693
+ } catch (e) {
1694
+ window.location[replace ? 'replace' : 'assign'](url);
1695
+ }
1696
+ }
1697
+
1698
+ function replaceState (url) {
1699
+ pushState(url, true);
1700
+ }
1701
+
1702
+ /* */
1703
+
1704
+ function runQueue (queue, fn, cb) {
1705
+ const step = index => {
1706
+ if (index >= queue.length) {
1707
+ cb();
1708
+ } else {
1709
+ if (queue[index]) {
1710
+ fn(queue[index], () => {
1711
+ step(index + 1);
1712
+ });
1713
+ } else {
1714
+ step(index + 1);
1715
+ }
1716
+ }
1717
+ };
1718
+ step(0);
1719
+ }
1720
+
1721
+ /* */
1722
+
1723
+ function resolveAsyncComponents (matched) {
1724
+ return (to, from, next) => {
1725
+ let hasAsync = false;
1726
+ let pending = 0;
1727
+ let error = null;
1728
+
1729
+ flatMapComponents(matched, (def, _, match, key) => {
1730
+ // if it's a function and doesn't have cid attached,
1731
+ // assume it's an async component resolve function.
1732
+ // we are not using Kdu's default async resolving mechanism because
1733
+ // we want to halt the navigation until the incoming component has been
1734
+ // resolved.
1735
+ if (typeof def === 'function' && def.cid === undefined) {
1736
+ hasAsync = true;
1737
+ pending++;
1738
+
1739
+ const resolve = once(resolvedDef => {
1740
+ if (isESModule(resolvedDef)) {
1741
+ resolvedDef = resolvedDef.default;
1742
+ }
1743
+ // save resolved on async factory in case it's used elsewhere
1744
+ def.resolved = typeof resolvedDef === 'function'
1745
+ ? resolvedDef
1746
+ : _Kdu.extend(resolvedDef);
1747
+ match.components[key] = resolvedDef;
1748
+ pending--;
1749
+ if (pending <= 0) {
1750
+ next();
1751
+ }
1752
+ });
1753
+
1754
+ const reject = once(reason => {
1755
+ const msg = `Failed to resolve async component ${key}: ${reason}`;
1756
+ "development" !== 'production' && warn(false, msg);
1757
+ if (!error) {
1758
+ error = isError(reason)
1759
+ ? reason
1760
+ : new Error(msg);
1761
+ next(error);
1762
+ }
1763
+ });
1764
+
1765
+ let res;
1766
+ try {
1767
+ res = def(resolve, reject);
1768
+ } catch (e) {
1769
+ reject(e);
1770
+ }
1771
+ if (res) {
1772
+ if (typeof res.then === 'function') {
1773
+ res.then(resolve, reject);
1774
+ } else {
1775
+ // new syntax in Kdu 2.3
1776
+ const comp = res.component;
1777
+ if (comp && typeof comp.then === 'function') {
1778
+ comp.then(resolve, reject);
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+ });
1784
+
1785
+ if (!hasAsync) next();
1786
+ }
1787
+ }
1788
+
1789
+ function flatMapComponents (
1790
+ matched,
1791
+ fn
1792
+ ) {
1793
+ return flatten(matched.map(m => {
1794
+ return Object.keys(m.components).map(key => fn(
1795
+ m.components[key],
1796
+ m.instances[key],
1797
+ m, key
1798
+ ))
1799
+ }))
1800
+ }
1801
+
1802
+ function flatten (arr) {
1803
+ return Array.prototype.concat.apply([], arr)
1804
+ }
1805
+
1806
+ const hasSymbol =
1807
+ typeof Symbol === 'function' &&
1808
+ typeof Symbol.toStringTag === 'symbol';
1809
+
1810
+ function isESModule (obj) {
1811
+ return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
1812
+ }
1813
+
1814
+ // in Webpack 2, require.ensure now also returns a Promise
1815
+ // so the resolve/reject functions may get called an extra time
1816
+ // if the user uses an arrow function shorthand that happens to
1817
+ // return that Promise.
1818
+ function once (fn) {
1819
+ let called = false;
1820
+ return function (...args) {
1821
+ if (called) return
1822
+ called = true;
1823
+ return fn.apply(this, args)
1824
+ }
1825
+ }
1826
+
1827
+ /* */
1828
+
1829
+ class History {
1830
+
1831
+
1832
+
1833
+
1834
+
1835
+
1836
+
1837
+
1838
+
1839
+
1840
+ // implemented by sub-classes
1841
+
1842
+
1843
+
1844
+
1845
+
1846
+
1847
+ constructor (router, base) {
1848
+ this.router = router;
1849
+ this.base = normalizeBase(base);
1850
+ // start with a route object that stands for "nowhere"
1851
+ this.current = START;
1852
+ this.pending = null;
1853
+ this.ready = false;
1854
+ this.readyCbs = [];
1855
+ this.readyErrorCbs = [];
1856
+ this.errorCbs = [];
1857
+ }
1858
+
1859
+ listen (cb) {
1860
+ this.cb = cb;
1861
+ }
1862
+
1863
+ onReady (cb, errorCb) {
1864
+ if (this.ready) {
1865
+ cb();
1866
+ } else {
1867
+ this.readyCbs.push(cb);
1868
+ if (errorCb) {
1869
+ this.readyErrorCbs.push(errorCb);
1870
+ }
1871
+ }
1872
+ }
1873
+
1874
+ onError (errorCb) {
1875
+ this.errorCbs.push(errorCb);
1876
+ }
1877
+
1878
+ transitionTo (location, onComplete, onAbort) {
1879
+ 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); });
1897
+ }
1898
+ });
1899
+ }
1900
+
1901
+ confirmTransition (route, onComplete, onAbort) {
1902
+ const current = this.current;
1903
+ const abort = err => {
1904
+ if (isError(err)) {
1905
+ if (this.errorCbs.length) {
1906
+ this.errorCbs.forEach(cb => { cb(err); });
1907
+ } else {
1908
+ warn(false, 'uncaught error during route navigation:');
1909
+ console.error(err);
1910
+ }
1911
+ }
1912
+ onAbort && onAbort(err);
1913
+ };
1914
+ if (
1915
+ isSameRoute(route, current) &&
1916
+ // in the case the route map has been dynamically appended to
1917
+ route.matched.length === current.matched.length
1918
+ ) {
1919
+ this.ensureURL();
1920
+ return abort()
1921
+ }
1922
+
1923
+ const {
1924
+ updated,
1925
+ deactivated,
1926
+ activated
1927
+ } = resolveQueue(this.current.matched, route.matched);
1928
+
1929
+ const queue = [].concat(
1930
+ // in-component leave guards
1931
+ extractLeaveGuards(deactivated),
1932
+ // global before hooks
1933
+ this.router.beforeHooks,
1934
+ // in-component update hooks
1935
+ extractUpdateHooks(updated),
1936
+ // in-config enter guards
1937
+ activated.map(m => m.beforeEnter),
1938
+ // async components
1939
+ resolveAsyncComponents(activated)
1940
+ );
1941
+
1942
+ this.pending = route;
1943
+ const iterator = (hook, next) => {
1944
+ if (this.pending !== route) {
1945
+ return abort()
1946
+ }
1947
+ try {
1948
+ hook(route, current, (to) => {
1949
+ if (to === false || isError(to)) {
1950
+ // next(false) -> abort navigation, ensure current URL
1951
+ this.ensureURL(true);
1952
+ abort(to);
1953
+ } else if (
1954
+ typeof to === 'string' ||
1955
+ (typeof to === 'object' && (
1956
+ typeof to.path === 'string' ||
1957
+ typeof to.name === 'string'
1958
+ ))
1959
+ ) {
1960
+ // next('/') or next({ path: '/' }) -> redirect
1961
+ abort();
1962
+ if (typeof to === 'object' && to.replace) {
1963
+ this.replace(to);
1964
+ } else {
1965
+ this.push(to);
1966
+ }
1967
+ } else {
1968
+ // confirm transition and pass on the value
1969
+ next(to);
1970
+ }
1971
+ });
1972
+ } catch (e) {
1973
+ abort(e);
1974
+ }
1975
+ };
1976
+
1977
+ runQueue(queue, iterator, () => {
1978
+ const postEnterCbs = [];
1979
+ const isValid = () => this.current === route;
1980
+ // wait until async components are resolved before
1981
+ // extracting in-component enter guards
1982
+ const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
1983
+ const queue = enterGuards.concat(this.router.resolveHooks);
1984
+ runQueue(queue, iterator, () => {
1985
+ if (this.pending !== route) {
1986
+ return abort()
1987
+ }
1988
+ this.pending = null;
1989
+ onComplete(route);
1990
+ if (this.router.app) {
1991
+ this.router.app.$nextTick(() => {
1992
+ postEnterCbs.forEach(cb => { cb(); });
1993
+ });
1994
+ }
1995
+ });
1996
+ });
1997
+ }
1998
+
1999
+ updateRoute (route) {
2000
+ const prev = this.current;
2001
+ this.current = route;
2002
+ this.cb && this.cb(route);
2003
+ this.router.afterHooks.forEach(hook => {
2004
+ hook && hook(route, prev);
2005
+ });
2006
+ }
2007
+ }
2008
+
2009
+ function normalizeBase (base) {
2010
+ if (!base) {
2011
+ if (inBrowser) {
2012
+ // respect <base> tag
2013
+ const baseEl = document.querySelector('base');
2014
+ base = (baseEl && baseEl.getAttribute('href')) || '/';
2015
+ // strip full URL origin
2016
+ base = base.replace(/^https?:\/\/[^\/]+/, '');
2017
+ } else {
2018
+ base = '/';
2019
+ }
2020
+ }
2021
+ // make sure there's the starting slash
2022
+ if (base.charAt(0) !== '/') {
2023
+ base = '/' + base;
2024
+ }
2025
+ // remove trailing slash
2026
+ return base.replace(/\/$/, '')
2027
+ }
2028
+
2029
+ function resolveQueue (
2030
+ current,
2031
+ next
2032
+ ) {
2033
+ let i;
2034
+ const max = Math.max(current.length, next.length);
2035
+ for (i = 0; i < max; i++) {
2036
+ if (current[i] !== next[i]) {
2037
+ break
2038
+ }
2039
+ }
2040
+ return {
2041
+ updated: next.slice(0, i),
2042
+ activated: next.slice(i),
2043
+ deactivated: current.slice(i)
2044
+ }
2045
+ }
2046
+
2047
+ function extractGuards (
2048
+ records,
2049
+ name,
2050
+ bind,
2051
+ reverse
2052
+ ) {
2053
+ const guards = flatMapComponents(records, (def, instance, match, key) => {
2054
+ const guard = extractGuard(def, name);
2055
+ if (guard) {
2056
+ return Array.isArray(guard)
2057
+ ? guard.map(guard => bind(guard, instance, match, key))
2058
+ : bind(guard, instance, match, key)
2059
+ }
2060
+ });
2061
+ return flatten(reverse ? guards.reverse() : guards)
2062
+ }
2063
+
2064
+ function extractGuard (
2065
+ def,
2066
+ key
2067
+ ) {
2068
+ if (typeof def !== 'function') {
2069
+ // extend now so that global mixins are applied.
2070
+ def = _Kdu.extend(def);
2071
+ }
2072
+ return def.options[key]
2073
+ }
2074
+
2075
+ function extractLeaveGuards (deactivated) {
2076
+ return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
2077
+ }
2078
+
2079
+ function extractUpdateHooks (updated) {
2080
+ return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
2081
+ }
2082
+
2083
+ function bindGuard (guard, instance) {
2084
+ if (instance) {
2085
+ return function boundRouteGuard () {
2086
+ return guard.apply(instance, arguments)
2087
+ }
2088
+ }
2089
+ }
2090
+
2091
+ function extractEnterGuards (
2092
+ activated,
2093
+ cbs,
2094
+ isValid
2095
+ ) {
2096
+ return extractGuards(activated, 'beforeRouteEnter', (guard, _, match, key) => {
2097
+ return bindEnterGuard(guard, match, key, cbs, isValid)
2098
+ })
2099
+ }
2100
+
2101
+ function bindEnterGuard (
2102
+ guard,
2103
+ match,
2104
+ key,
2105
+ cbs,
2106
+ isValid
2107
+ ) {
2108
+ return function routeEnterGuard (to, from, next) {
2109
+ return guard(to, from, cb => {
2110
+ if (typeof cb === 'function') {
2111
+ cbs.push(() => {
2112
+ // #750
2113
+ // if a router-view is wrapped with an out-in transition,
2114
+ // the instance may not have been registered at this time.
2115
+ // we will need to poll for registration until current route
2116
+ // is no longer valid.
2117
+ poll(cb, match.instances, key, isValid);
2118
+ });
2119
+ }
2120
+ next(cb);
2121
+ })
2122
+ }
2123
+ }
2124
+
2125
+ function poll (
2126
+ cb, // somehow flow cannot infer this is a function
2127
+ instances,
2128
+ key,
2129
+ isValid
2130
+ ) {
2131
+ if (
2132
+ instances[key] &&
2133
+ !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
2134
+ ) {
2135
+ cb(instances[key]);
2136
+ } else if (isValid()) {
2137
+ setTimeout(() => {
2138
+ poll(cb, instances, key, isValid);
2139
+ }, 16);
2140
+ }
2141
+ }
2142
+
2143
+ /* */
2144
+
2145
+ class HTML5History extends History {
2146
+ constructor (router, base) {
2147
+ super(router, base);
2148
+
2149
+ const expectScroll = router.options.scrollBehavior;
2150
+ const supportsScroll = supportsPushState && expectScroll;
2151
+
2152
+ if (supportsScroll) {
2153
+ setupScroll();
2154
+ }
2155
+
2156
+ const initLocation = getLocation(this.base);
2157
+ window.addEventListener('popstate', e => {
2158
+ const current = this.current;
2159
+
2160
+ // Avoiding first `popstate` event dispatched in some browsers but first
2161
+ // history route not updated since async guard at the same time.
2162
+ const location = getLocation(this.base);
2163
+ if (this.current === START && location === initLocation) {
2164
+ return
2165
+ }
2166
+
2167
+ this.transitionTo(location, route => {
2168
+ if (supportsScroll) {
2169
+ handleScroll(router, route, current, true);
2170
+ }
2171
+ });
2172
+ });
2173
+ }
2174
+
2175
+ go (n) {
2176
+ window.history.go(n);
2177
+ }
2178
+
2179
+ push (location, onComplete, onAbort) {
2180
+ const { current: fromRoute } = this;
2181
+ this.transitionTo(location, route => {
2182
+ pushState(cleanPath(this.base + route.fullPath));
2183
+ handleScroll(this.router, route, fromRoute, false);
2184
+ onComplete && onComplete(route);
2185
+ }, onAbort);
2186
+ }
2187
+
2188
+ replace (location, onComplete, onAbort) {
2189
+ const { current: fromRoute } = this;
2190
+ this.transitionTo(location, route => {
2191
+ replaceState(cleanPath(this.base + route.fullPath));
2192
+ handleScroll(this.router, route, fromRoute, false);
2193
+ onComplete && onComplete(route);
2194
+ }, onAbort);
2195
+ }
2196
+
2197
+ ensureURL (push) {
2198
+ if (getLocation(this.base) !== this.current.fullPath) {
2199
+ const current = cleanPath(this.base + this.current.fullPath);
2200
+ push ? pushState(current) : replaceState(current);
2201
+ }
2202
+ }
2203
+
2204
+ getCurrentLocation () {
2205
+ return getLocation(this.base)
2206
+ }
2207
+ }
2208
+
2209
+ function getLocation (base) {
2210
+ let path = decodeURI(window.location.pathname);
2211
+ if (base && path.indexOf(base) === 0) {
2212
+ path = path.slice(base.length);
2213
+ }
2214
+ return (path || '/') + window.location.search + window.location.hash
2215
+ }
2216
+
2217
+ /* */
2218
+
2219
+ class HashHistory extends History {
2220
+ constructor (router, base, fallback) {
2221
+ super(router, base);
2222
+ // check history fallback deeplinking
2223
+ if (fallback && checkFallback(this.base)) {
2224
+ return
2225
+ }
2226
+ ensureSlash();
2227
+ }
2228
+
2229
+ // this is delayed until the app mounts
2230
+ // to avoid the hashchange listener being fired too early
2231
+ setupListeners () {
2232
+ const router = this.router;
2233
+ const expectScroll = router.options.scrollBehavior;
2234
+ const supportsScroll = supportsPushState && expectScroll;
2235
+
2236
+ if (supportsScroll) {
2237
+ setupScroll();
2238
+ }
2239
+
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);
2251
+ }
2252
+ });
2253
+ });
2254
+ }
2255
+
2256
+ push (location, onComplete, onAbort) {
2257
+ 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);
2263
+ }
2264
+
2265
+ replace (location, onComplete, onAbort) {
2266
+ 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);
2272
+ }
2273
+
2274
+ go (n) {
2275
+ window.history.go(n);
2276
+ }
2277
+
2278
+ ensureURL (push) {
2279
+ const current = this.current.fullPath;
2280
+ if (getHash() !== current) {
2281
+ push ? pushHash(current) : replaceHash(current);
2282
+ }
2283
+ }
2284
+
2285
+ getCurrentLocation () {
2286
+ return getHash()
2287
+ }
2288
+ }
2289
+
2290
+ function checkFallback (base) {
2291
+ const location = getLocation(base);
2292
+ if (!/^\/#/.test(location)) {
2293
+ window.location.replace(
2294
+ cleanPath(base + '/#' + location)
2295
+ );
2296
+ return true
2297
+ }
2298
+ }
2299
+
2300
+ function ensureSlash () {
2301
+ const path = getHash();
2302
+ if (path.charAt(0) === '/') {
2303
+ return true
2304
+ }
2305
+ replaceHash('/' + path);
2306
+ return false
2307
+ }
2308
+
2309
+ function getHash () {
2310
+ // We can't use window.location.hash here because it's not
2311
+ // consistent across browsers - Firefox will pre-decode it!
2312
+ let href = window.location.href;
2313
+ const index = href.indexOf('#');
2314
+ // empty path
2315
+ if (index < 0) return ''
2316
+
2317
+ href = href.slice(index + 1);
2318
+ // decode the hash but not the search or hash
2319
+ // as search(query) is already decoded
2320
+ const searchIndex = href.indexOf('?');
2321
+ if (searchIndex < 0) {
2322
+ const hashIndex = href.indexOf('#');
2323
+ if (hashIndex > -1) href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2324
+ else href = decodeURI(href);
2325
+ } else {
2326
+ if (searchIndex > -1) href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2327
+ }
2328
+
2329
+ return href
2330
+ }
2331
+
2332
+ function getUrl (path) {
2333
+ const href = window.location.href;
2334
+ const i = href.indexOf('#');
2335
+ const base = i >= 0 ? href.slice(0, i) : href;
2336
+ return `${base}#${path}`
2337
+ }
2338
+
2339
+ function pushHash (path) {
2340
+ if (supportsPushState) {
2341
+ pushState(getUrl(path));
2342
+ } else {
2343
+ window.location.hash = path;
2344
+ }
2345
+ }
2346
+
2347
+ function replaceHash (path) {
2348
+ if (supportsPushState) {
2349
+ replaceState(getUrl(path));
2350
+ } else {
2351
+ window.location.replace(getUrl(path));
2352
+ }
2353
+ }
2354
+
2355
+ /* */
2356
+
2357
+ class AbstractHistory extends History {
2358
+
2359
+
2360
+
2361
+ constructor (router, base) {
2362
+ super(router, base);
2363
+ this.stack = [];
2364
+ this.index = -1;
2365
+ }
2366
+
2367
+ 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);
2373
+ }
2374
+
2375
+ 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);
2380
+ }
2381
+
2382
+ go (n) {
2383
+ const targetIndex = this.index + n;
2384
+ if (targetIndex < 0 || targetIndex >= this.stack.length) {
2385
+ return
2386
+ }
2387
+ const route = this.stack[targetIndex];
2388
+ this.confirmTransition(route, () => {
2389
+ this.index = targetIndex;
2390
+ this.updateRoute(route);
2391
+ });
2392
+ }
2393
+
2394
+ getCurrentLocation () {
2395
+ const current = this.stack[this.stack.length - 1];
2396
+ return current ? current.fullPath : '/'
2397
+ }
2398
+
2399
+ ensureURL () {
2400
+ // noop
2401
+ }
2402
+ }
2403
+
2404
+ /* */
2405
+
2406
+
2407
+
2408
+ class KduRouter {
2409
+
2410
+
2411
+
2412
+
2413
+
2414
+
2415
+
2416
+
2417
+
2418
+
2419
+
2420
+
2421
+
2422
+
2423
+
2424
+
2425
+ constructor (options = {}) {
2426
+ this.app = null;
2427
+ this.apps = [];
2428
+ this.options = options;
2429
+ this.beforeHooks = [];
2430
+ this.resolveHooks = [];
2431
+ this.afterHooks = [];
2432
+ this.matcher = createMatcher(options.routes || [], this);
2433
+
2434
+ let mode = options.mode || 'hash';
2435
+ this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false;
2436
+ if (this.fallback) {
2437
+ mode = 'hash';
2438
+ }
2439
+ if (!inBrowser) {
2440
+ mode = 'abstract';
2441
+ }
2442
+ this.mode = mode;
2443
+
2444
+ switch (mode) {
2445
+ case 'history':
2446
+ this.history = new HTML5History(this, options.base);
2447
+ break
2448
+ case 'hash':
2449
+ this.history = new HashHistory(this, options.base, this.fallback);
2450
+ break
2451
+ case 'abstract':
2452
+ this.history = new AbstractHistory(this, options.base);
2453
+ break
2454
+ default:
2455
+ {
2456
+ assert(false, `invalid mode: ${mode}`);
2457
+ }
2458
+ }
2459
+ }
2460
+
2461
+ match (
2462
+ raw,
2463
+ current,
2464
+ redirectedFrom
2465
+ ) {
2466
+ return this.matcher.match(raw, current, redirectedFrom)
2467
+ }
2468
+
2469
+ get currentRoute () {
2470
+ return this.history && this.history.current
2471
+ }
2472
+
2473
+ init (app /* Kdu component instance */) {
2474
+ "development" !== 'production' && assert(
2475
+ install.installed,
2476
+ `not installed. Make sure to call \`Kdu.use(KduRouter)\` ` +
2477
+ `before creating root instance.`
2478
+ );
2479
+
2480
+ this.apps.push(app);
2481
+
2482
+ // set up app destroyed handler
2483
+ app.$once('hook:destroyed', () => {
2484
+ // clean out app from this.apps array once destroyed
2485
+ const index = this.apps.indexOf(app);
2486
+ if (index > -1) this.apps.splice(index, 1);
2487
+ // ensure we still have a main app or null if no apps
2488
+ // we do not release the router so it can be reused
2489
+ if (this.app === app) this.app = this.apps[0] || null;
2490
+ });
2491
+
2492
+ // main app previously initialized
2493
+ // return as we don't need to set up new history listener
2494
+ if (this.app) {
2495
+ return
2496
+ }
2497
+
2498
+ this.app = app;
2499
+
2500
+ const history = this.history;
2501
+
2502
+ if (history instanceof HTML5History) {
2503
+ history.transitionTo(history.getCurrentLocation());
2504
+ } else if (history instanceof HashHistory) {
2505
+ const setupHashListener = () => {
2506
+ history.setupListeners();
2507
+ };
2508
+ history.transitionTo(
2509
+ history.getCurrentLocation(),
2510
+ setupHashListener,
2511
+ setupHashListener
2512
+ );
2513
+ }
2514
+
2515
+ history.listen(route => {
2516
+ this.apps.forEach((app) => {
2517
+ app._route = route;
2518
+ });
2519
+ });
2520
+ }
2521
+
2522
+ beforeEach (fn) {
2523
+ return registerHook(this.beforeHooks, fn)
2524
+ }
2525
+
2526
+ beforeResolve (fn) {
2527
+ return registerHook(this.resolveHooks, fn)
2528
+ }
2529
+
2530
+ afterEach (fn) {
2531
+ return registerHook(this.afterHooks, fn)
2532
+ }
2533
+
2534
+ onReady (cb, errorCb) {
2535
+ this.history.onReady(cb, errorCb);
2536
+ }
2537
+
2538
+ onError (errorCb) {
2539
+ this.history.onError(errorCb);
2540
+ }
2541
+
2542
+ push (location, onComplete, onAbort) {
2543
+ this.history.push(location, onComplete, onAbort);
2544
+ }
2545
+
2546
+ replace (location, onComplete, onAbort) {
2547
+ this.history.replace(location, onComplete, onAbort);
2548
+ }
2549
+
2550
+ go (n) {
2551
+ this.history.go(n);
2552
+ }
2553
+
2554
+ back () {
2555
+ this.go(-1);
2556
+ }
2557
+
2558
+ forward () {
2559
+ this.go(1);
2560
+ }
2561
+
2562
+ getMatchedComponents (to) {
2563
+ const route = to
2564
+ ? to.matched
2565
+ ? to
2566
+ : this.resolve(to).route
2567
+ : this.currentRoute;
2568
+ if (!route) {
2569
+ return []
2570
+ }
2571
+ return [].concat.apply([], route.matched.map(m => {
2572
+ return Object.keys(m.components).map(key => {
2573
+ return m.components[key]
2574
+ })
2575
+ }))
2576
+ }
2577
+
2578
+ resolve (
2579
+ to,
2580
+ current,
2581
+ append
2582
+ ) {
2583
+ current = current || this.history.current;
2584
+ const location = normalizeLocation(
2585
+ to,
2586
+ current,
2587
+ append,
2588
+ this
2589
+ );
2590
+ const route = this.match(location, current);
2591
+ const fullPath = route.redirectedFrom || route.fullPath;
2592
+ const base = this.history.base;
2593
+ const href = createHref(base, fullPath, this.mode);
2594
+ return {
2595
+ location,
2596
+ route,
2597
+ href,
2598
+ // for backwards compat
2599
+ normalizedTo: location,
2600
+ resolved: route
2601
+ }
2602
+ }
2603
+
2604
+ addRoutes (routes) {
2605
+ this.matcher.addRoutes(routes);
2606
+ if (this.history.current !== START) {
2607
+ this.history.transitionTo(this.history.getCurrentLocation());
2608
+ }
2609
+ }
2610
+ }
2611
+
2612
+ function registerHook (list, fn) {
2613
+ list.push(fn);
2614
+ return () => {
2615
+ const i = list.indexOf(fn);
2616
+ if (i > -1) list.splice(i, 1);
2617
+ }
2618
+ }
2619
+
2620
+ function createHref (base, fullPath, mode) {
2621
+ var path = mode === 'hash' ? '#' + fullPath : fullPath;
2622
+ return base ? cleanPath(base + '/' + path) : path
2623
+ }
2624
+
2625
+ KduRouter.install = install;
2626
+ KduRouter.version = '3.0.7';
2627
+
2628
+ if (inBrowser && window.Kdu) {
2629
+ window.Kdu.use(KduRouter);
2630
+ }
2631
+
2632
+ export default KduRouter;