kdu-router 2.7.0 → 3.1.3

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