kdu-router 3.4.0-beta.0 → 3.6.0

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.
Files changed (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +11 -9
  3. package/dist/composables.js +253 -0
  4. package/dist/composables.mjs +244 -0
  5. package/dist/kdu-router.common.js +2682 -2572
  6. package/dist/kdu-router.esm.browser.js +2677 -2563
  7. package/dist/kdu-router.esm.browser.min.js +5 -5
  8. package/dist/kdu-router.esm.js +2685 -2572
  9. package/dist/kdu-router.js +2682 -2573
  10. package/dist/kdu-router.min.js +5 -5
  11. package/ketur/attributes.json +38 -38
  12. package/ketur/tags.json +20 -20
  13. package/package.json +115 -111
  14. package/src/components/link.js +224 -197
  15. package/src/components/view.js +155 -149
  16. package/src/composables/globals.js +34 -0
  17. package/src/composables/guards.js +68 -0
  18. package/src/composables/index.js +3 -0
  19. package/src/composables/useLink.js +113 -0
  20. package/src/composables/utils.js +11 -0
  21. package/src/create-matcher.js +226 -200
  22. package/src/create-route-map.js +220 -205
  23. package/src/entries/cjs.js +3 -0
  24. package/src/entries/esm.js +12 -0
  25. package/src/history/abstract.js +72 -68
  26. package/src/history/base.js +379 -400
  27. package/src/history/hash.js +152 -163
  28. package/src/history/html5.js +99 -94
  29. package/src/index.js +3 -277
  30. package/src/install.js +52 -52
  31. package/src/router.js +293 -0
  32. package/src/util/async.js +18 -18
  33. package/src/util/dom.js +3 -3
  34. package/src/util/errors.js +86 -85
  35. package/src/util/location.js +69 -69
  36. package/src/util/misc.js +6 -6
  37. package/src/util/params.js +37 -37
  38. package/src/util/path.js +74 -74
  39. package/src/util/push-state.js +46 -46
  40. package/src/util/query.js +113 -96
  41. package/src/util/resolve-components.js +109 -109
  42. package/src/util/route.js +151 -132
  43. package/src/util/scroll.js +175 -165
  44. package/src/util/state-key.js +22 -22
  45. package/src/util/warn.js +14 -14
  46. package/types/composables.d.ts +53 -0
  47. package/types/index.d.ts +25 -17
  48. package/types/kdu.d.ts +22 -22
  49. package/types/router.d.ts +564 -170
@@ -1,470 +1,511 @@
1
1
  /*!
2
- * kdu-router v3.4.0-beta.0
3
- * (c) 2022 NKDuy
2
+ * kdu-router v3.6.0
3
+ * (c) 2026 NKDuy
4
4
  * @license MIT
5
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 extend (a, b) {
21
- for (const key in b) {
22
- a[key] = b[key];
23
- }
24
- return a
25
- }
26
-
27
- var View = {
28
- name: 'RouterView',
29
- functional: true,
30
- props: {
31
- name: {
32
- type: String,
33
- default: 'default'
34
- }
35
- },
36
- render (_, { props, children, parent, data }) {
37
- // used by devtools to display a router-view badge
38
- data.routerView = true;
39
-
40
- // directly use parent context's createElement() function
41
- // so that components rendered by router-view can resolve named slots
42
- const h = parent.$createElement;
43
- const name = props.name;
44
- const route = parent.$route;
45
- const cache = parent._routerViewCache || (parent._routerViewCache = {});
46
-
47
- // determine current view depth, also check to see if the tree
48
- // has been toggled inactive but kept-alive.
49
- let depth = 0;
50
- let inactive = false;
51
- while (parent && parent._routerRoot !== parent) {
52
- const knodeData = parent.$knode ? parent.$knode.data : {};
53
- if (knodeData.routerView) {
54
- depth++;
55
- }
56
- if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
57
- inactive = true;
58
- }
59
- parent = parent.$parent;
60
- }
61
- data.routerViewDepth = depth;
62
-
63
- // render previous view if the tree is inactive and kept-alive
64
- if (inactive) {
65
- const cachedData = cache[name];
66
- const cachedComponent = cachedData && cachedData.component;
67
- if (cachedComponent) {
68
- // #2301
69
- // pass props
70
- if (cachedData.configProps) {
71
- fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
72
- }
73
- return h(cachedComponent, data, children)
74
- } else {
75
- // render previous empty view
76
- return h()
77
- }
78
- }
79
-
80
- const matched = route.matched[depth];
81
- const component = matched && matched.components[name];
82
-
83
- // render empty node if no matched route or no config component
84
- if (!matched || !component) {
85
- cache[name] = null;
86
- return h()
87
- }
88
-
89
- // cache component
90
- cache[name] = { component };
91
-
92
- // attach instance registration hook
93
- // this will be called in the instance's injected lifecycle hooks
94
- data.registerRouteInstance = (vm, val) => {
95
- // val could be undefined for unregistration
96
- const current = matched.instances[name];
97
- if (
98
- (val && current !== vm) ||
99
- (!val && current === vm)
100
- ) {
101
- matched.instances[name] = val;
102
- }
103
- }
104
-
105
- // also register instance in prepatch hook
106
- // in case the same component instance is reused across different routes
107
- ;(data.hook || (data.hook = {})).prepatch = (_, knode) => {
108
- matched.instances[name] = knode.componentInstance;
109
- };
110
-
111
- // register instance in init hook
112
- // in case kept-alive component be actived when routes changed
113
- data.hook.init = (knode) => {
114
- if (knode.data.keepAlive &&
115
- knode.componentInstance &&
116
- knode.componentInstance !== matched.instances[name]
117
- ) {
118
- matched.instances[name] = knode.componentInstance;
119
- }
120
- };
121
-
122
- const configProps = matched.props && matched.props[name];
123
- // save route and configProps in cache
124
- if (configProps) {
125
- extend(cache[name], {
126
- route,
127
- configProps
128
- });
129
- fillPropsinData(component, data, route, configProps);
130
- }
131
-
132
- return h(component, data, children)
133
- }
134
- };
135
-
136
- function fillPropsinData (component, data, route, configProps) {
137
- // resolve props
138
- let propsToPass = data.props = resolveProps(route, configProps);
139
- if (propsToPass) {
140
- // clone to prevent mutation
141
- propsToPass = data.props = extend({}, propsToPass);
142
- // pass non-declared props as attrs
143
- const attrs = data.attrs = data.attrs || {};
144
- for (const key in propsToPass) {
145
- if (!component.props || !(key in component.props)) {
146
- attrs[key] = propsToPass[key];
147
- delete propsToPass[key];
148
- }
149
- }
150
- }
151
- }
152
-
153
- function resolveProps (route, config) {
154
- switch (typeof config) {
155
- case 'undefined':
156
- return
157
- case 'object':
158
- return config
159
- case 'function':
160
- return config(route)
161
- case 'boolean':
162
- return config ? route.params : undefined
163
- default:
164
- {
165
- warn(
166
- false,
167
- `props in "${route.path}" is a ${typeof config}, ` +
168
- `expecting an object, function or boolean.`
169
- );
170
- }
171
- }
172
- }
173
-
174
- /* */
175
-
176
- const encodeReserveRE = /[!'()*]/g;
177
- const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16);
178
- const commaRE = /%2C/g;
179
-
180
- // fixed encodeURIComponent which is more conformant to RFC3986:
181
- // - escapes [!'()*]
182
- // - preserve commas
183
- const encode = str => encodeURIComponent(str)
184
- .replace(encodeReserveRE, encodeReserveReplacer)
185
- .replace(commaRE, ',');
186
-
187
- const decode = decodeURIComponent;
188
-
189
- function resolveQuery (
190
- query,
191
- extraQuery = {},
192
- _parseQuery
193
- ) {
194
- const parse = _parseQuery || parseQuery;
195
- let parsedQuery;
196
- try {
197
- parsedQuery = parse(query || '');
198
- } catch (e) {
199
- warn(false, e.message);
200
- parsedQuery = {};
201
- }
202
- for (const key in extraQuery) {
203
- const value = extraQuery[key];
204
- parsedQuery[key] = Array.isArray(value) ? value.map(v => '' + v) : '' + value;
205
- }
206
- return parsedQuery
207
- }
208
-
209
- function parseQuery (query) {
210
- const res = {};
211
-
212
- query = query.trim().replace(/^(\?|#|&)/, '');
213
-
214
- if (!query) {
215
- return res
216
- }
217
-
218
- query.split('&').forEach(param => {
219
- const parts = param.replace(/\+/g, ' ').split('=');
220
- const key = decode(parts.shift());
221
- const val = parts.length > 0
222
- ? decode(parts.join('='))
223
- : null;
224
-
225
- if (res[key] === undefined) {
226
- res[key] = val;
227
- } else if (Array.isArray(res[key])) {
228
- res[key].push(val);
229
- } else {
230
- res[key] = [res[key], val];
231
- }
232
- });
233
-
234
- return res
235
- }
236
-
237
- function stringifyQuery (obj) {
238
- const res = obj ? Object.keys(obj).map(key => {
239
- const val = obj[key];
240
-
241
- if (val === undefined) {
242
- return ''
243
- }
244
-
245
- if (val === null) {
246
- return encode(key)
247
- }
248
-
249
- if (Array.isArray(val)) {
250
- const result = [];
251
- val.forEach(val2 => {
252
- if (val2 === undefined) {
253
- return
254
- }
255
- if (val2 === null) {
256
- result.push(encode(key));
257
- } else {
258
- result.push(encode(key) + '=' + encode(val2));
259
- }
260
- });
261
- return result.join('&')
262
- }
263
-
264
- return encode(key) + '=' + encode(val)
265
- }).filter(x => x.length > 0).join('&') : null;
266
- return res ? `?${res}` : ''
267
- }
268
-
269
- /* */
270
-
271
- const trailingSlashRE = /\/?$/;
272
-
273
- function createRoute (
274
- record,
275
- location,
276
- redirectedFrom,
277
- router
278
- ) {
279
- const stringifyQuery = router && router.options.stringifyQuery;
280
-
281
- let query = location.query || {};
282
- try {
283
- query = clone(query);
284
- } catch (e) {}
285
-
286
- const route = {
287
- name: location.name || (record && record.name),
288
- meta: (record && record.meta) || {},
289
- path: location.path || '/',
290
- hash: location.hash || '',
291
- query,
292
- params: location.params || {},
293
- fullPath: getFullPath(location, stringifyQuery),
294
- matched: record ? formatMatch(record) : []
295
- };
296
- if (redirectedFrom) {
297
- route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery);
298
- }
299
- return Object.freeze(route)
300
- }
301
-
302
- function clone (value) {
303
- if (Array.isArray(value)) {
304
- return value.map(clone)
305
- } else if (value && typeof value === 'object') {
306
- const res = {};
307
- for (const key in value) {
308
- res[key] = clone(value[key]);
309
- }
310
- return res
311
- } else {
312
- return value
313
- }
314
- }
315
-
316
- // the starting route that represents the initial state
317
- const START = createRoute(null, {
318
- path: '/'
319
- });
320
-
321
- function formatMatch (record) {
322
- const res = [];
323
- while (record) {
324
- res.unshift(record);
325
- record = record.parent;
326
- }
327
- return res
328
- }
329
-
330
- function getFullPath (
331
- { path, query = {}, hash = '' },
332
- _stringifyQuery
333
- ) {
334
- const stringify = _stringifyQuery || stringifyQuery;
335
- return (path || '/') + stringify(query) + hash
336
- }
337
-
338
- function isSameRoute (a, b) {
339
- if (b === START) {
340
- return a === b
341
- } else if (!b) {
342
- return false
343
- } else if (a.path && b.path) {
344
- return (
345
- a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
346
- a.hash === b.hash &&
347
- isObjectEqual(a.query, b.query)
348
- )
349
- } else if (a.name && b.name) {
350
- return (
351
- a.name === b.name &&
352
- a.hash === b.hash &&
353
- isObjectEqual(a.query, b.query) &&
354
- isObjectEqual(a.params, b.params)
355
- )
356
- } else {
357
- return false
358
- }
359
- }
360
-
361
- function isObjectEqual (a = {}, b = {}) {
362
- // handle null value #1566
363
- if (!a || !b) return a === b
364
- const aKeys = Object.keys(a);
365
- const bKeys = Object.keys(b);
366
- if (aKeys.length !== bKeys.length) {
367
- return false
368
- }
369
- return aKeys.every(key => {
370
- const aVal = a[key];
371
- const bVal = b[key];
372
- // check nested equality
373
- if (typeof aVal === 'object' && typeof bVal === 'object') {
374
- return isObjectEqual(aVal, bVal)
375
- }
376
- return String(aVal) === String(bVal)
377
- })
378
- }
379
-
380
- function isIncludedRoute (current, target) {
381
- return (
382
- current.path.replace(trailingSlashRE, '/').indexOf(
383
- target.path.replace(trailingSlashRE, '/')
384
- ) === 0 &&
385
- (!target.hash || current.hash === target.hash) &&
386
- queryIncludes(current.query, target.query)
387
- )
388
- }
389
-
390
- function queryIncludes (current, target) {
391
- for (const key in target) {
392
- if (!(key in current)) {
393
- return false
394
- }
395
- }
396
- return true
397
- }
398
-
399
- /* */
400
-
401
- function resolvePath (
402
- relative,
403
- base,
404
- append
405
- ) {
406
- const firstChar = relative.charAt(0);
407
- if (firstChar === '/') {
408
- return relative
409
- }
410
-
411
- if (firstChar === '?' || firstChar === '#') {
412
- return base + relative
413
- }
414
-
415
- const stack = base.split('/');
416
-
417
- // remove trailing segment if:
418
- // - not appending
419
- // - appending to trailing slash (last segment is empty)
420
- if (!append || !stack[stack.length - 1]) {
421
- stack.pop();
422
- }
423
-
424
- // resolve relative path
425
- const segments = relative.replace(/^\//, '').split('/');
426
- for (let i = 0; i < segments.length; i++) {
427
- const segment = segments[i];
428
- if (segment === '..') {
429
- stack.pop();
430
- } else if (segment !== '.') {
431
- stack.push(segment);
432
- }
433
- }
434
-
435
- // ensure leading slash
436
- if (stack[0] !== '') {
437
- stack.unshift('');
438
- }
439
-
440
- return stack.join('/')
441
- }
442
-
443
- function parsePath (path) {
444
- let hash = '';
445
- let query = '';
446
-
447
- const hashIndex = path.indexOf('#');
448
- if (hashIndex >= 0) {
449
- hash = path.slice(hashIndex);
450
- path = path.slice(0, hashIndex);
451
- }
452
-
453
- const queryIndex = path.indexOf('?');
454
- if (queryIndex >= 0) {
455
- query = path.slice(queryIndex + 1);
456
- path = path.slice(0, queryIndex);
457
- }
458
-
459
- return {
460
- path,
461
- query,
462
- hash
463
- }
464
- }
465
-
466
- function cleanPath (path) {
467
- return path.replace(/\/\//g, '/')
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 extend (a, b) {
21
+ for (const key in b) {
22
+ a[key] = b[key];
23
+ }
24
+ return a
25
+ }
26
+
27
+ /* */
28
+
29
+ const encodeReserveRE = /[!'()*]/g;
30
+ const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16);
31
+ const commaRE = /%2C/g;
32
+
33
+ // fixed encodeURIComponent which is more conformant to RFC3986:
34
+ // - escapes [!'()*]
35
+ // - preserve commas
36
+ const encode = str =>
37
+ encodeURIComponent(str)
38
+ .replace(encodeReserveRE, encodeReserveReplacer)
39
+ .replace(commaRE, ',');
40
+
41
+ function decode (str) {
42
+ try {
43
+ return decodeURIComponent(str)
44
+ } catch (err) {
45
+ {
46
+ warn(false, `Error decoding "${str}". Leaving it intact.`);
47
+ }
48
+ }
49
+ return str
50
+ }
51
+
52
+ function resolveQuery (
53
+ query,
54
+ extraQuery = {},
55
+ _parseQuery
56
+ ) {
57
+ const parse = _parseQuery || parseQuery;
58
+ let parsedQuery;
59
+ try {
60
+ parsedQuery = parse(query || '');
61
+ } catch (e) {
62
+ warn(false, e.message);
63
+ parsedQuery = {};
64
+ }
65
+ for (const key in extraQuery) {
66
+ const value = extraQuery[key];
67
+ parsedQuery[key] = Array.isArray(value)
68
+ ? value.map(castQueryParamValue)
69
+ : castQueryParamValue(value);
70
+ }
71
+ return parsedQuery
72
+ }
73
+
74
+ const castQueryParamValue = value => (value == null || typeof value === 'object' ? value : String(value));
75
+
76
+ function parseQuery (query) {
77
+ const res = {};
78
+
79
+ query = query.trim().replace(/^(\?|#|&)/, '');
80
+
81
+ if (!query) {
82
+ return res
83
+ }
84
+
85
+ query.split('&').forEach(param => {
86
+ const parts = param.replace(/\+/g, ' ').split('=');
87
+ const key = decode(parts.shift());
88
+ const val = parts.length > 0 ? decode(parts.join('=')) : null;
89
+
90
+ if (res[key] === undefined) {
91
+ res[key] = val;
92
+ } else if (Array.isArray(res[key])) {
93
+ res[key].push(val);
94
+ } else {
95
+ res[key] = [res[key], val];
96
+ }
97
+ });
98
+
99
+ return res
100
+ }
101
+
102
+ function stringifyQuery (obj) {
103
+ const res = obj
104
+ ? Object.keys(obj)
105
+ .map(key => {
106
+ const val = obj[key];
107
+
108
+ if (val === undefined) {
109
+ return ''
110
+ }
111
+
112
+ if (val === null) {
113
+ return encode(key)
114
+ }
115
+
116
+ if (Array.isArray(val)) {
117
+ const result = [];
118
+ val.forEach(val2 => {
119
+ if (val2 === undefined) {
120
+ return
121
+ }
122
+ if (val2 === null) {
123
+ result.push(encode(key));
124
+ } else {
125
+ result.push(encode(key) + '=' + encode(val2));
126
+ }
127
+ });
128
+ return result.join('&')
129
+ }
130
+
131
+ return encode(key) + '=' + encode(val)
132
+ })
133
+ .filter(x => x.length > 0)
134
+ .join('&')
135
+ : null;
136
+ return res ? `?${res}` : ''
137
+ }
138
+
139
+ /* */
140
+
141
+ const trailingSlashRE = /\/?$/;
142
+
143
+ function createRoute (
144
+ record,
145
+ location,
146
+ redirectedFrom,
147
+ router
148
+ ) {
149
+ const stringifyQuery = router && router.options.stringifyQuery;
150
+
151
+ let query = location.query || {};
152
+ try {
153
+ query = clone(query);
154
+ } catch (e) {}
155
+
156
+ const route = {
157
+ name: location.name || (record && record.name),
158
+ meta: (record && record.meta) || {},
159
+ path: location.path || '/',
160
+ hash: location.hash || '',
161
+ query,
162
+ params: location.params || {},
163
+ fullPath: getFullPath(location, stringifyQuery),
164
+ matched: record ? formatMatch(record) : []
165
+ };
166
+ if (redirectedFrom) {
167
+ route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery);
168
+ }
169
+ return Object.freeze(route)
170
+ }
171
+
172
+ function clone (value) {
173
+ if (Array.isArray(value)) {
174
+ return value.map(clone)
175
+ } else if (value && typeof value === 'object') {
176
+ const res = {};
177
+ for (const key in value) {
178
+ res[key] = clone(value[key]);
179
+ }
180
+ return res
181
+ } else {
182
+ return value
183
+ }
184
+ }
185
+
186
+ // the starting route that represents the initial state
187
+ const START = createRoute(null, {
188
+ path: '/'
189
+ });
190
+
191
+ function formatMatch (record) {
192
+ const res = [];
193
+ while (record) {
194
+ res.unshift(record);
195
+ record = record.parent;
196
+ }
197
+ return res
198
+ }
199
+
200
+ function getFullPath (
201
+ { path, query = {}, hash = '' },
202
+ _stringifyQuery
203
+ ) {
204
+ const stringify = _stringifyQuery || stringifyQuery;
205
+ return (path || '/') + stringify(query) + hash
206
+ }
207
+
208
+ function isSameRoute (a, b, onlyPath) {
209
+ if (b === START) {
210
+ return a === b
211
+ } else if (!b) {
212
+ return false
213
+ } else if (a.path && b.path) {
214
+ return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
215
+ a.hash === b.hash &&
216
+ isObjectEqual(a.query, b.query))
217
+ } else if (a.name && b.name) {
218
+ return (
219
+ a.name === b.name &&
220
+ (onlyPath || (
221
+ a.hash === b.hash &&
222
+ isObjectEqual(a.query, b.query) &&
223
+ isObjectEqual(a.params, b.params))
224
+ )
225
+ )
226
+ } else {
227
+ return false
228
+ }
229
+ }
230
+
231
+ function isObjectEqual (a = {}, b = {}) {
232
+ // handle null value #1566
233
+ if (!a || !b) return a === b
234
+ const aKeys = Object.keys(a).sort();
235
+ const bKeys = Object.keys(b).sort();
236
+ if (aKeys.length !== bKeys.length) {
237
+ return false
238
+ }
239
+ return aKeys.every((key, i) => {
240
+ const aVal = a[key];
241
+ const bKey = bKeys[i];
242
+ if (bKey !== key) return false
243
+ const bVal = b[key];
244
+ // query values can be null and undefined
245
+ if (aVal == null || bVal == null) return aVal === bVal
246
+ // check nested equality
247
+ if (typeof aVal === 'object' && typeof bVal === 'object') {
248
+ return isObjectEqual(aVal, bVal)
249
+ }
250
+ return String(aVal) === String(bVal)
251
+ })
252
+ }
253
+
254
+ function isIncludedRoute (current, target) {
255
+ return (
256
+ current.path.replace(trailingSlashRE, '/').indexOf(
257
+ target.path.replace(trailingSlashRE, '/')
258
+ ) === 0 &&
259
+ (!target.hash || current.hash === target.hash) &&
260
+ queryIncludes(current.query, target.query)
261
+ )
262
+ }
263
+
264
+ function queryIncludes (current, target) {
265
+ for (const key in target) {
266
+ if (!(key in current)) {
267
+ return false
268
+ }
269
+ }
270
+ return true
271
+ }
272
+
273
+ function handleRouteEntered (route) {
274
+ for (let i = 0; i < route.matched.length; i++) {
275
+ const record = route.matched[i];
276
+ for (const name in record.instances) {
277
+ const instance = record.instances[name];
278
+ const cbs = record.enteredCbs[name];
279
+ if (!instance || !cbs) continue
280
+ delete record.enteredCbs[name];
281
+ for (let i = 0; i < cbs.length; i++) {
282
+ if (!instance._isBeingDestroyed) cbs[i](instance);
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ var View = {
289
+ name: 'RouterView',
290
+ functional: true,
291
+ props: {
292
+ name: {
293
+ type: String,
294
+ default: 'default'
295
+ }
296
+ },
297
+ render (_, { props, children, parent, data }) {
298
+ // used by devtools to display a router-view badge
299
+ data.routerView = true;
300
+
301
+ // directly use parent context's createElement() function
302
+ // so that components rendered by router-view can resolve named slots
303
+ const h = parent.$createElement;
304
+ const name = props.name;
305
+ const route = parent.$route;
306
+ const cache = parent._routerViewCache || (parent._routerViewCache = {});
307
+
308
+ // determine current view depth, also check to see if the tree
309
+ // has been toggled inactive but kept-alive.
310
+ let depth = 0;
311
+ let inactive = false;
312
+ while (parent && parent._routerRoot !== parent) {
313
+ const knodeData = parent.$knode ? parent.$knode.data : {};
314
+ if (knodeData.routerView) {
315
+ depth++;
316
+ }
317
+ if (knodeData.keepAlive && parent._directInactive && parent._inactive) {
318
+ inactive = true;
319
+ }
320
+ parent = parent.$parent;
321
+ }
322
+ data.routerViewDepth = depth;
323
+
324
+ // render previous view if the tree is inactive and kept-alive
325
+ if (inactive) {
326
+ const cachedData = cache[name];
327
+ const cachedComponent = cachedData && cachedData.component;
328
+ if (cachedComponent) {
329
+ // #2301
330
+ // pass props
331
+ if (cachedData.configProps) {
332
+ fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps);
333
+ }
334
+ return h(cachedComponent, data, children)
335
+ } else {
336
+ // render previous empty view
337
+ return h()
338
+ }
339
+ }
340
+
341
+ const matched = route.matched[depth];
342
+ const component = matched && matched.components[name];
343
+
344
+ // render empty node if no matched route or no config component
345
+ if (!matched || !component) {
346
+ cache[name] = null;
347
+ return h()
348
+ }
349
+
350
+ // cache component
351
+ cache[name] = { component };
352
+
353
+ // attach instance registration hook
354
+ // this will be called in the instance's injected lifecycle hooks
355
+ data.registerRouteInstance = (vm, val) => {
356
+ // val could be undefined for unregistration
357
+ const current = matched.instances[name];
358
+ if (
359
+ (val && current !== vm) ||
360
+ (!val && current === vm)
361
+ ) {
362
+ matched.instances[name] = val;
363
+ }
364
+ }
365
+
366
+ // also register instance in prepatch hook
367
+ // in case the same component instance is reused across different routes
368
+ ;(data.hook || (data.hook = {})).prepatch = (_, knode) => {
369
+ matched.instances[name] = knode.componentInstance;
370
+ };
371
+
372
+ // register instance in init hook
373
+ // in case kept-alive component be actived when routes changed
374
+ data.hook.init = (knode) => {
375
+ if (knode.data.keepAlive &&
376
+ knode.componentInstance &&
377
+ knode.componentInstance !== matched.instances[name]
378
+ ) {
379
+ matched.instances[name] = knode.componentInstance;
380
+ }
381
+
382
+ // if the route transition has already been confirmed then we weren't
383
+ // able to call the cbs during confirmation as the component was not
384
+ // registered yet, so we call it here.
385
+ handleRouteEntered(route);
386
+ };
387
+
388
+ const configProps = matched.props && matched.props[name];
389
+ // save route and configProps in cache
390
+ if (configProps) {
391
+ extend(cache[name], {
392
+ route,
393
+ configProps
394
+ });
395
+ fillPropsinData(component, data, route, configProps);
396
+ }
397
+
398
+ return h(component, data, children)
399
+ }
400
+ };
401
+
402
+ function fillPropsinData (component, data, route, configProps) {
403
+ // resolve props
404
+ let propsToPass = data.props = resolveProps(route, configProps);
405
+ if (propsToPass) {
406
+ // clone to prevent mutation
407
+ propsToPass = data.props = extend({}, propsToPass);
408
+ // pass non-declared props as attrs
409
+ const attrs = data.attrs = data.attrs || {};
410
+ for (const key in propsToPass) {
411
+ if (!component.props || !(key in component.props)) {
412
+ attrs[key] = propsToPass[key];
413
+ delete propsToPass[key];
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ function resolveProps (route, config) {
420
+ switch (typeof config) {
421
+ case 'undefined':
422
+ return
423
+ case 'object':
424
+ return config
425
+ case 'function':
426
+ return config(route)
427
+ case 'boolean':
428
+ return config ? route.params : undefined
429
+ default:
430
+ {
431
+ warn(
432
+ false,
433
+ `props in "${route.path}" is a ${typeof config}, ` +
434
+ `expecting an object, function or boolean.`
435
+ );
436
+ }
437
+ }
438
+ }
439
+
440
+ /* */
441
+
442
+ function resolvePath (
443
+ relative,
444
+ base,
445
+ append
446
+ ) {
447
+ const firstChar = relative.charAt(0);
448
+ if (firstChar === '/') {
449
+ return relative
450
+ }
451
+
452
+ if (firstChar === '?' || firstChar === '#') {
453
+ return base + relative
454
+ }
455
+
456
+ const stack = base.split('/');
457
+
458
+ // remove trailing segment if:
459
+ // - not appending
460
+ // - appending to trailing slash (last segment is empty)
461
+ if (!append || !stack[stack.length - 1]) {
462
+ stack.pop();
463
+ }
464
+
465
+ // resolve relative path
466
+ const segments = relative.replace(/^\//, '').split('/');
467
+ for (let i = 0; i < segments.length; i++) {
468
+ const segment = segments[i];
469
+ if (segment === '..') {
470
+ stack.pop();
471
+ } else if (segment !== '.') {
472
+ stack.push(segment);
473
+ }
474
+ }
475
+
476
+ // ensure leading slash
477
+ if (stack[0] !== '') {
478
+ stack.unshift('');
479
+ }
480
+
481
+ return stack.join('/')
482
+ }
483
+
484
+ function parsePath (path) {
485
+ let hash = '';
486
+ let query = '';
487
+
488
+ const hashIndex = path.indexOf('#');
489
+ if (hashIndex >= 0) {
490
+ hash = path.slice(hashIndex);
491
+ path = path.slice(0, hashIndex);
492
+ }
493
+
494
+ const queryIndex = path.indexOf('?');
495
+ if (queryIndex >= 0) {
496
+ query = path.slice(queryIndex + 1);
497
+ path = path.slice(0, queryIndex);
498
+ }
499
+
500
+ return {
501
+ path,
502
+ query,
503
+ hash
504
+ }
505
+ }
506
+
507
+ function cleanPath (path) {
508
+ return path.replace(/\/(?:\s*\/)+/g, '/')
468
509
  }
469
510
 
470
511
  var isarray = Array.isArray || function (arr) {
@@ -900,2106 +941,2179 @@ pathToRegexp_1.compile = compile_1;
900
941
  pathToRegexp_1.tokensToFunction = tokensToFunction_1;
901
942
  pathToRegexp_1.tokensToRegExp = tokensToRegExp_1;
902
943
 
903
- /* */
904
-
905
- // $flow-disable-line
906
- const regexpCompileCache = Object.create(null);
907
-
908
- function fillParams (
909
- path,
910
- params,
911
- routeMsg
912
- ) {
913
- params = params || {};
914
- try {
915
- const filler =
916
- regexpCompileCache[path] ||
917
- (regexpCompileCache[path] = pathToRegexp_1.compile(path));
918
-
919
- // Fix #2505 resolving asterisk routes { name: 'not-found', params: { pathMatch: '/not-found' }}
920
- // and fix #3106 so that you can work with location descriptor object having params.pathMatch equal to empty string
921
- if (typeof params.pathMatch === 'string') params[0] = params.pathMatch;
922
-
923
- return filler(params, { pretty: true })
924
- } catch (e) {
925
- {
926
- // Fix #3072 no warn if `pathMatch` is string
927
- warn(typeof params.pathMatch === 'string', `missing param for ${routeMsg}: ${e.message}`);
928
- }
929
- return ''
930
- } finally {
931
- // delete the 0 if it was added
932
- delete params[0];
933
- }
934
- }
935
-
936
- /* */
937
-
938
- function normalizeLocation (
939
- raw,
940
- current,
941
- append,
942
- router
943
- ) {
944
- let next = typeof raw === 'string' ? { path: raw } : raw;
945
- // named target
946
- if (next._normalized) {
947
- return next
948
- } else if (next.name) {
949
- next = extend({}, raw);
950
- const params = next.params;
951
- if (params && typeof params === 'object') {
952
- next.params = extend({}, params);
953
- }
954
- return next
955
- }
956
-
957
- // relative params
958
- if (!next.path && next.params && current) {
959
- next = extend({}, next);
960
- next._normalized = true;
961
- const params = extend(extend({}, current.params), next.params);
962
- if (current.name) {
963
- next.name = current.name;
964
- next.params = params;
965
- } else if (current.matched.length) {
966
- const rawPath = current.matched[current.matched.length - 1].path;
967
- next.path = fillParams(rawPath, params, `path ${current.path}`);
968
- } else {
969
- warn(false, `relative params navigation requires a current route.`);
970
- }
971
- return next
972
- }
973
-
974
- const parsedPath = parsePath(next.path || '');
975
- const basePath = (current && current.path) || '/';
976
- const path = parsedPath.path
977
- ? resolvePath(parsedPath.path, basePath, append || next.append)
978
- : basePath;
979
-
980
- const query = resolveQuery(
981
- parsedPath.query,
982
- next.query,
983
- router && router.options.parseQuery
984
- );
985
-
986
- let hash = next.hash || parsedPath.hash;
987
- if (hash && hash.charAt(0) !== '#') {
988
- hash = `#${hash}`;
989
- }
990
-
991
- return {
992
- _normalized: true,
993
- path,
994
- query,
995
- hash
996
- }
997
- }
998
-
999
- /* */
1000
-
1001
- // work around weird flow bug
1002
- const toTypes = [String, Object];
1003
- const eventTypes = [String, Array];
1004
-
1005
- const noop = () => {};
1006
-
1007
- var Link = {
1008
- name: 'RouterLink',
1009
- props: {
1010
- to: {
1011
- type: toTypes,
1012
- required: true
1013
- },
1014
- tag: {
1015
- type: String,
1016
- default: 'a'
1017
- },
1018
- exact: Boolean,
1019
- append: Boolean,
1020
- replace: Boolean,
1021
- activeClass: String,
1022
- exactActiveClass: String,
1023
- ariaCurrentValue: {
1024
- type: String,
1025
- default: 'page'
1026
- },
1027
- event: {
1028
- type: eventTypes,
1029
- default: 'click'
1030
- }
1031
- },
1032
- render (h) {
1033
- const router = this.$router;
1034
- const current = this.$route;
1035
- const { location, route, href } = router.resolve(
1036
- this.to,
1037
- current,
1038
- this.append
1039
- );
1040
-
1041
- const classes = {};
1042
- const globalActiveClass = router.options.linkActiveClass;
1043
- const globalExactActiveClass = router.options.linkExactActiveClass;
1044
- // Support global empty active class
1045
- const activeClassFallback =
1046
- globalActiveClass == null ? 'router-link-active' : globalActiveClass;
1047
- const exactActiveClassFallback =
1048
- globalExactActiveClass == null
1049
- ? 'router-link-exact-active'
1050
- : globalExactActiveClass;
1051
- const activeClass =
1052
- this.activeClass == null ? activeClassFallback : this.activeClass;
1053
- const exactActiveClass =
1054
- this.exactActiveClass == null
1055
- ? exactActiveClassFallback
1056
- : this.exactActiveClass;
1057
-
1058
- const compareTarget = route.redirectedFrom
1059
- ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1060
- : route;
1061
-
1062
- classes[exactActiveClass] = isSameRoute(current, compareTarget);
1063
- classes[activeClass] = this.exact
1064
- ? classes[exactActiveClass]
1065
- : isIncludedRoute(current, compareTarget);
1066
-
1067
- const ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null;
1068
-
1069
- const handler = e => {
1070
- if (guardEvent(e)) {
1071
- if (this.replace) {
1072
- router.replace(location, noop);
1073
- } else {
1074
- router.push(location, noop);
1075
- }
1076
- }
1077
- };
1078
-
1079
- const on = { click: guardEvent };
1080
- if (Array.isArray(this.event)) {
1081
- this.event.forEach(e => {
1082
- on[e] = handler;
1083
- });
1084
- } else {
1085
- on[this.event] = handler;
1086
- }
1087
-
1088
- const data = { class: classes };
1089
-
1090
- const scopedSlot =
1091
- !this.$scopedSlots.$hasNormal &&
1092
- this.$scopedSlots.default &&
1093
- this.$scopedSlots.default({
1094
- href,
1095
- route,
1096
- navigate: handler,
1097
- isActive: classes[activeClass],
1098
- isExactActive: classes[exactActiveClass]
1099
- });
1100
-
1101
- if (scopedSlot) {
1102
- if (scopedSlot.length === 1) {
1103
- return scopedSlot[0]
1104
- } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1105
- {
1106
- warn(
1107
- false,
1108
- `RouterLink with to="${
1109
- this.to
1110
- }" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
1111
- );
1112
- }
1113
- return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1114
- }
1115
- }
1116
-
1117
- if (this.tag === 'a') {
1118
- data.on = on;
1119
- data.attrs = { href, 'aria-current': ariaCurrentValue };
1120
- } else {
1121
- // find the first <a> child and apply listener and href
1122
- const a = findAnchor(this.$slots.default);
1123
- if (a) {
1124
- // in case the <a> is a static node
1125
- a.isStatic = false;
1126
- const aData = (a.data = extend({}, a.data));
1127
- aData.on = aData.on || {};
1128
- // transform existing events in both objects into arrays so we can push later
1129
- for (const event in aData.on) {
1130
- const handler = aData.on[event];
1131
- if (event in on) {
1132
- aData.on[event] = Array.isArray(handler) ? handler : [handler];
1133
- }
1134
- }
1135
- // append new listeners for router-link
1136
- for (const event in on) {
1137
- if (event in aData.on) {
1138
- // on[event] is always a function
1139
- aData.on[event].push(on[event]);
1140
- } else {
1141
- aData.on[event] = handler;
1142
- }
1143
- }
1144
-
1145
- const aAttrs = (a.data.attrs = extend({}, a.data.attrs));
1146
- aAttrs.href = href;
1147
- aAttrs['aria-current'] = ariaCurrentValue;
1148
- } else {
1149
- // doesn't have <a> child, apply listener to self
1150
- data.on = on;
1151
- }
1152
- }
1153
-
1154
- return h(this.tag, data, this.$slots.default)
1155
- }
1156
- };
1157
-
1158
- function guardEvent (e) {
1159
- // don't redirect with control keys
1160
- if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
1161
- // don't redirect when preventDefault called
1162
- if (e.defaultPrevented) return
1163
- // don't redirect on right click
1164
- if (e.button !== undefined && e.button !== 0) return
1165
- // don't redirect if `target="_blank"`
1166
- if (e.currentTarget && e.currentTarget.getAttribute) {
1167
- const target = e.currentTarget.getAttribute('target');
1168
- if (/\b_blank\b/i.test(target)) return
1169
- }
1170
- // this may be a Weex event which doesn't have this method
1171
- if (e.preventDefault) {
1172
- e.preventDefault();
1173
- }
1174
- return true
1175
- }
1176
-
1177
- function findAnchor (children) {
1178
- if (children) {
1179
- let child;
1180
- for (let i = 0; i < children.length; i++) {
1181
- child = children[i];
1182
- if (child.tag === 'a') {
1183
- return child
1184
- }
1185
- if (child.children && (child = findAnchor(child.children))) {
1186
- return child
1187
- }
1188
- }
1189
- }
1190
- }
1191
-
1192
- let _Kdu;
1193
-
1194
- function install (Kdu) {
1195
- if (install.installed && _Kdu === Kdu) return
1196
- install.installed = true;
1197
-
1198
- _Kdu = Kdu;
1199
-
1200
- const isDef = v => v !== undefined;
1201
-
1202
- const registerInstance = (vm, callVal) => {
1203
- let i = vm.$options._parentKnode;
1204
- if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
1205
- i(vm, callVal);
1206
- }
1207
- };
1208
-
1209
- Kdu.mixin({
1210
- beforeCreate () {
1211
- if (isDef(this.$options.router)) {
1212
- this._routerRoot = this;
1213
- this._router = this.$options.router;
1214
- this._router.init(this);
1215
- Kdu.util.defineReactive(this, '_route', this._router.history.current);
1216
- } else {
1217
- this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
1218
- }
1219
- registerInstance(this, this);
1220
- },
1221
- destroyed () {
1222
- registerInstance(this);
1223
- }
1224
- });
1225
-
1226
- Object.defineProperty(Kdu.prototype, '$router', {
1227
- get () { return this._routerRoot._router }
1228
- });
1229
-
1230
- Object.defineProperty(Kdu.prototype, '$route', {
1231
- get () { return this._routerRoot._route }
1232
- });
1233
-
1234
- Kdu.component('RouterView', View);
1235
- Kdu.component('RouterLink', Link);
1236
-
1237
- const strats = Kdu.config.optionMergeStrategies;
1238
- // use the same hook merging strategy for route hooks
1239
- strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
1240
- }
1241
-
1242
- /* */
1243
-
944
+ /* */
945
+
946
+ // $flow-disable-line
947
+ const regexpCompileCache = Object.create(null);
948
+
949
+ function fillParams (
950
+ path,
951
+ params,
952
+ routeMsg
953
+ ) {
954
+ params = params || {};
955
+ try {
956
+ const filler =
957
+ regexpCompileCache[path] ||
958
+ (regexpCompileCache[path] = pathToRegexp_1.compile(path));
959
+
960
+ // Fix #2505 resolving asterisk routes { name: 'not-found', params: { pathMatch: '/not-found' }}
961
+ // and fix #3106 so that you can work with location descriptor object having params.pathMatch equal to empty string
962
+ if (typeof params.pathMatch === 'string') params[0] = params.pathMatch;
963
+
964
+ return filler(params, { pretty: true })
965
+ } catch (e) {
966
+ {
967
+ // Fix #3072 no warn if `pathMatch` is string
968
+ warn(typeof params.pathMatch === 'string', `missing param for ${routeMsg}: ${e.message}`);
969
+ }
970
+ return ''
971
+ } finally {
972
+ // delete the 0 if it was added
973
+ delete params[0];
974
+ }
975
+ }
976
+
977
+ /* */
978
+
979
+ function normalizeLocation (
980
+ raw,
981
+ current,
982
+ append,
983
+ router
984
+ ) {
985
+ let next = typeof raw === 'string' ? { path: raw } : raw;
986
+ // named target
987
+ if (next._normalized) {
988
+ return next
989
+ } else if (next.name) {
990
+ next = extend({}, raw);
991
+ const params = next.params;
992
+ if (params && typeof params === 'object') {
993
+ next.params = extend({}, params);
994
+ }
995
+ return next
996
+ }
997
+
998
+ // relative params
999
+ if (!next.path && next.params && current) {
1000
+ next = extend({}, next);
1001
+ next._normalized = true;
1002
+ const params = extend(extend({}, current.params), next.params);
1003
+ if (current.name) {
1004
+ next.name = current.name;
1005
+ next.params = params;
1006
+ } else if (current.matched.length) {
1007
+ const rawPath = current.matched[current.matched.length - 1].path;
1008
+ next.path = fillParams(rawPath, params, `path ${current.path}`);
1009
+ } else {
1010
+ warn(false, `relative params navigation requires a current route.`);
1011
+ }
1012
+ return next
1013
+ }
1014
+
1015
+ const parsedPath = parsePath(next.path || '');
1016
+ const basePath = (current && current.path) || '/';
1017
+ const path = parsedPath.path
1018
+ ? resolvePath(parsedPath.path, basePath, append || next.append)
1019
+ : basePath;
1020
+
1021
+ const query = resolveQuery(
1022
+ parsedPath.query,
1023
+ next.query,
1024
+ router && router.options.parseQuery
1025
+ );
1026
+
1027
+ let hash = next.hash || parsedPath.hash;
1028
+ if (hash && hash.charAt(0) !== '#') {
1029
+ hash = `#${hash}`;
1030
+ }
1031
+
1032
+ return {
1033
+ _normalized: true,
1034
+ path,
1035
+ query,
1036
+ hash
1037
+ }
1038
+ }
1039
+
1040
+ /* */
1041
+
1042
+ // work around weird flow bug
1043
+ const toTypes = [String, Object];
1044
+ const eventTypes = [String, Array];
1045
+
1046
+ const noop = () => {};
1047
+
1048
+ let warnedCustomSlot;
1049
+ let warnedTagProp;
1050
+ let warnedEventProp;
1051
+
1052
+ var Link = {
1053
+ name: 'RouterLink',
1054
+ props: {
1055
+ to: {
1056
+ type: toTypes,
1057
+ required: true
1058
+ },
1059
+ tag: {
1060
+ type: String,
1061
+ default: 'a'
1062
+ },
1063
+ custom: Boolean,
1064
+ exact: Boolean,
1065
+ exactPath: Boolean,
1066
+ append: Boolean,
1067
+ replace: Boolean,
1068
+ activeClass: String,
1069
+ exactActiveClass: String,
1070
+ ariaCurrentValue: {
1071
+ type: String,
1072
+ default: 'page'
1073
+ },
1074
+ event: {
1075
+ type: eventTypes,
1076
+ default: 'click'
1077
+ }
1078
+ },
1079
+ render (h) {
1080
+ const router = this.$router;
1081
+ const current = this.$route;
1082
+ const { location, route, href } = router.resolve(
1083
+ this.to,
1084
+ current,
1085
+ this.append
1086
+ );
1087
+
1088
+ const classes = {};
1089
+ const globalActiveClass = router.options.linkActiveClass;
1090
+ const globalExactActiveClass = router.options.linkExactActiveClass;
1091
+ // Support global empty active class
1092
+ const activeClassFallback =
1093
+ globalActiveClass == null ? 'router-link-active' : globalActiveClass;
1094
+ const exactActiveClassFallback =
1095
+ globalExactActiveClass == null
1096
+ ? 'router-link-exact-active'
1097
+ : globalExactActiveClass;
1098
+ const activeClass =
1099
+ this.activeClass == null ? activeClassFallback : this.activeClass;
1100
+ const exactActiveClass =
1101
+ this.exactActiveClass == null
1102
+ ? exactActiveClassFallback
1103
+ : this.exactActiveClass;
1104
+
1105
+ const compareTarget = route.redirectedFrom
1106
+ ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
1107
+ : route;
1108
+
1109
+ classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath);
1110
+ classes[activeClass] = this.exact || this.exactPath
1111
+ ? classes[exactActiveClass]
1112
+ : isIncludedRoute(current, compareTarget);
1113
+
1114
+ const ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null;
1115
+
1116
+ const handler = e => {
1117
+ if (guardEvent(e)) {
1118
+ if (this.replace) {
1119
+ router.replace(location, noop);
1120
+ } else {
1121
+ router.push(location, noop);
1122
+ }
1123
+ }
1124
+ };
1125
+
1126
+ const on = { click: guardEvent };
1127
+ if (Array.isArray(this.event)) {
1128
+ this.event.forEach(e => {
1129
+ on[e] = handler;
1130
+ });
1131
+ } else {
1132
+ on[this.event] = handler;
1133
+ }
1134
+
1135
+ const data = { class: classes };
1136
+
1137
+ const scopedSlot =
1138
+ !this.$scopedSlots.$hasNormal &&
1139
+ this.$scopedSlots.default &&
1140
+ this.$scopedSlots.default({
1141
+ href,
1142
+ route,
1143
+ navigate: handler,
1144
+ isActive: classes[activeClass],
1145
+ isExactActive: classes[exactActiveClass]
1146
+ });
1147
+
1148
+ if (scopedSlot) {
1149
+ if (!this.custom) {
1150
+ !warnedCustomSlot && warn(false, 'In Kdu Router 4, the k-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link k-slot="{ navigate, href }" custom></router-link>\n');
1151
+ warnedCustomSlot = true;
1152
+ }
1153
+ if (scopedSlot.length === 1) {
1154
+ return scopedSlot[0]
1155
+ } else if (scopedSlot.length > 1 || !scopedSlot.length) {
1156
+ {
1157
+ warn(
1158
+ false,
1159
+ `<router-link> with to="${
1160
+ this.to
1161
+ }" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
1162
+ );
1163
+ }
1164
+ return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
1165
+ }
1166
+ }
1167
+
1168
+ {
1169
+ if ('tag' in this.$options.propsData && !warnedTagProp) {
1170
+ warn(
1171
+ false,
1172
+ `<router-link>'s tag prop is deprecated and has been removed in Kdu Router 4. Use the k-slot API to remove this warning: https://kdujs-router.web.app/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
1173
+ );
1174
+ warnedTagProp = true;
1175
+ }
1176
+ if ('event' in this.$options.propsData && !warnedEventProp) {
1177
+ warn(
1178
+ false,
1179
+ `<router-link>'s event prop is deprecated and has been removed in Kdu Router 4. Use the k-slot API to remove this warning: https://kdujs-router.web.app/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
1180
+ );
1181
+ warnedEventProp = true;
1182
+ }
1183
+ }
1184
+
1185
+ if (this.tag === 'a') {
1186
+ data.on = on;
1187
+ data.attrs = { href, 'aria-current': ariaCurrentValue };
1188
+ } else {
1189
+ // find the first <a> child and apply listener and href
1190
+ const a = findAnchor(this.$slots.default);
1191
+ if (a) {
1192
+ // in case the <a> is a static node
1193
+ a.isStatic = false;
1194
+ const aData = (a.data = extend({}, a.data));
1195
+ aData.on = aData.on || {};
1196
+ // transform existing events in both objects into arrays so we can push later
1197
+ for (const event in aData.on) {
1198
+ const handler = aData.on[event];
1199
+ if (event in on) {
1200
+ aData.on[event] = Array.isArray(handler) ? handler : [handler];
1201
+ }
1202
+ }
1203
+ // append new listeners for router-link
1204
+ for (const event in on) {
1205
+ if (event in aData.on) {
1206
+ // on[event] is always a function
1207
+ aData.on[event].push(on[event]);
1208
+ } else {
1209
+ aData.on[event] = handler;
1210
+ }
1211
+ }
1212
+
1213
+ const aAttrs = (a.data.attrs = extend({}, a.data.attrs));
1214
+ aAttrs.href = href;
1215
+ aAttrs['aria-current'] = ariaCurrentValue;
1216
+ } else {
1217
+ // doesn't have <a> child, apply listener to self
1218
+ data.on = on;
1219
+ }
1220
+ }
1221
+
1222
+ return h(this.tag, data, this.$slots.default)
1223
+ }
1224
+ };
1225
+
1226
+ function guardEvent (e) {
1227
+ // don't redirect with control keys
1228
+ if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
1229
+ // don't redirect when preventDefault called
1230
+ if (e.defaultPrevented) return
1231
+ // don't redirect on right click
1232
+ if (e.button !== undefined && e.button !== 0) return
1233
+ // don't redirect if `target="_blank"`
1234
+ if (e.currentTarget && e.currentTarget.getAttribute) {
1235
+ const target = e.currentTarget.getAttribute('target');
1236
+ if (/\b_blank\b/i.test(target)) return
1237
+ }
1238
+ // this may be a Weex event which doesn't have this method
1239
+ if (e.preventDefault) {
1240
+ e.preventDefault();
1241
+ }
1242
+ return true
1243
+ }
1244
+
1245
+ function findAnchor (children) {
1246
+ if (children) {
1247
+ let child;
1248
+ for (let i = 0; i < children.length; i++) {
1249
+ child = children[i];
1250
+ if (child.tag === 'a') {
1251
+ return child
1252
+ }
1253
+ if (child.children && (child = findAnchor(child.children))) {
1254
+ return child
1255
+ }
1256
+ }
1257
+ }
1258
+ }
1259
+
1260
+ let _Kdu;
1261
+
1262
+ function install (Kdu) {
1263
+ if (install.installed && _Kdu === Kdu) return
1264
+ install.installed = true;
1265
+
1266
+ _Kdu = Kdu;
1267
+
1268
+ const isDef = v => v !== undefined;
1269
+
1270
+ const registerInstance = (vm, callVal) => {
1271
+ let i = vm.$options._parentKnode;
1272
+ if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
1273
+ i(vm, callVal);
1274
+ }
1275
+ };
1276
+
1277
+ Kdu.mixin({
1278
+ beforeCreate () {
1279
+ if (isDef(this.$options.router)) {
1280
+ this._routerRoot = this;
1281
+ this._router = this.$options.router;
1282
+ this._router.init(this);
1283
+ Kdu.util.defineReactive(this, '_route', this._router.history.current);
1284
+ } else {
1285
+ this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
1286
+ }
1287
+ registerInstance(this, this);
1288
+ },
1289
+ destroyed () {
1290
+ registerInstance(this);
1291
+ }
1292
+ });
1293
+
1294
+ Object.defineProperty(Kdu.prototype, '$router', {
1295
+ get () { return this._routerRoot._router }
1296
+ });
1297
+
1298
+ Object.defineProperty(Kdu.prototype, '$route', {
1299
+ get () { return this._routerRoot._route }
1300
+ });
1301
+
1302
+ Kdu.component('RouterView', View);
1303
+ Kdu.component('RouterLink', Link);
1304
+
1305
+ const strats = Kdu.config.optionMergeStrategies;
1306
+ // use the same hook merging strategy for route hooks
1307
+ strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;
1308
+ }
1309
+
1310
+ /* */
1311
+
1244
1312
  const inBrowser = typeof window !== 'undefined';
1245
1313
 
1246
- /* */
1247
-
1248
- function createRouteMap (
1249
- routes,
1250
- oldPathList,
1251
- oldPathMap,
1252
- oldNameMap
1253
- ) {
1254
- // the path list is used to control path matching priority
1255
- const pathList = oldPathList || [];
1256
- // $flow-disable-line
1257
- const pathMap = oldPathMap || Object.create(null);
1258
- // $flow-disable-line
1259
- const nameMap = oldNameMap || Object.create(null);
1260
-
1261
- routes.forEach(route => {
1262
- addRouteRecord(pathList, pathMap, nameMap, route);
1263
- });
1264
-
1265
- // ensure wildcard routes are always at the end
1266
- for (let i = 0, l = pathList.length; i < l; i++) {
1267
- if (pathList[i] === '*') {
1268
- pathList.push(pathList.splice(i, 1)[0]);
1269
- l--;
1270
- i--;
1271
- }
1272
- }
1273
-
1274
- {
1275
- // warn if routes do not include leading slashes
1276
- const found = pathList
1277
- // check for missing leading slash
1278
- .filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/');
1279
-
1280
- if (found.length > 0) {
1281
- const pathNames = found.map(path => `- ${path}`).join('\n');
1282
- warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`);
1283
- }
1284
- }
1285
-
1286
- return {
1287
- pathList,
1288
- pathMap,
1289
- nameMap
1290
- }
1291
- }
1292
-
1293
- function addRouteRecord (
1294
- pathList,
1295
- pathMap,
1296
- nameMap,
1297
- route,
1298
- parent,
1299
- matchAs
1300
- ) {
1301
- const { path, name } = route;
1302
- {
1303
- assert(path != null, `"path" is required in a route configuration.`);
1304
- assert(
1305
- typeof route.component !== 'string',
1306
- `route config "component" for path: ${String(
1307
- path || name
1308
- )} cannot be a ` + `string id. Use an actual component instead.`
1309
- );
1310
- }
1311
-
1312
- const pathToRegexpOptions =
1313
- route.pathToRegexpOptions || {};
1314
- const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict);
1315
-
1316
- if (typeof route.caseSensitive === 'boolean') {
1317
- pathToRegexpOptions.sensitive = route.caseSensitive;
1318
- }
1319
-
1320
- const record = {
1321
- path: normalizedPath,
1322
- regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1323
- components: route.components || { default: route.component },
1324
- instances: {},
1325
- name,
1326
- parent,
1327
- matchAs,
1328
- redirect: route.redirect,
1329
- beforeEnter: route.beforeEnter,
1330
- meta: route.meta || {},
1331
- props:
1332
- route.props == null
1333
- ? {}
1334
- : route.components
1335
- ? route.props
1336
- : { default: route.props }
1337
- };
1338
-
1339
- if (route.children) {
1340
- // Warn if route is named, does not redirect and has a default child route.
1341
- // If users navigate to this route by name, the default child will
1342
- // not be rendered (GH Issue #629)
1343
- {
1344
- if (
1345
- route.name &&
1346
- !route.redirect &&
1347
- route.children.some(child => /^\/?$/.test(child.path))
1348
- ) {
1349
- warn(
1350
- false,
1351
- `Named Route '${route.name}' has a default child route. ` +
1352
- `When navigating to this named route (:to="{name: '${
1353
- route.name
1354
- }'"), ` +
1355
- `the default child route will not be rendered. Remove the name from ` +
1356
- `this route and use the name of the default child route for named ` +
1357
- `links instead.`
1358
- );
1359
- }
1360
- }
1361
- route.children.forEach(child => {
1362
- const childMatchAs = matchAs
1363
- ? cleanPath(`${matchAs}/${child.path}`)
1364
- : undefined;
1365
- addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
1366
- });
1367
- }
1368
-
1369
- if (!pathMap[record.path]) {
1370
- pathList.push(record.path);
1371
- pathMap[record.path] = record;
1372
- }
1373
-
1374
- if (route.alias !== undefined) {
1375
- const aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
1376
- for (let i = 0; i < aliases.length; ++i) {
1377
- const alias = aliases[i];
1378
- if ( alias === path) {
1379
- warn(
1380
- false,
1381
- `Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
1382
- );
1383
- // skip in dev to make it work
1384
- continue
1385
- }
1386
-
1387
- const aliasRoute = {
1388
- path: alias,
1389
- children: route.children
1390
- };
1391
- addRouteRecord(
1392
- pathList,
1393
- pathMap,
1394
- nameMap,
1395
- aliasRoute,
1396
- parent,
1397
- record.path || '/' // matchAs
1398
- );
1399
- }
1400
- }
1401
-
1402
- if (name) {
1403
- if (!nameMap[name]) {
1404
- nameMap[name] = record;
1405
- } else if ( !matchAs) {
1406
- warn(
1407
- false,
1408
- `Duplicate named routes definition: ` +
1409
- `{ name: "${name}", path: "${record.path}" }`
1410
- );
1411
- }
1412
- }
1413
- }
1414
-
1415
- function compileRouteRegex (
1416
- path,
1417
- pathToRegexpOptions
1418
- ) {
1419
- const regex = pathToRegexp_1(path, [], pathToRegexpOptions);
1420
- {
1421
- const keys = Object.create(null);
1422
- regex.keys.forEach(key => {
1423
- warn(
1424
- !keys[key.name],
1425
- `Duplicate param keys in route with path: "${path}"`
1426
- );
1427
- keys[key.name] = true;
1428
- });
1429
- }
1430
- return regex
1431
- }
1432
-
1433
- function normalizePath (
1434
- path,
1435
- parent,
1436
- strict
1437
- ) {
1438
- if (!strict) path = path.replace(/\/$/, '');
1439
- if (path[0] === '/') return path
1440
- if (parent == null) return path
1441
- return cleanPath(`${parent.path}/${path}`)
1442
- }
1443
-
1444
- /* */
1445
-
1446
-
1447
-
1448
- function createMatcher (
1449
- routes,
1450
- router
1451
- ) {
1452
- const { pathList, pathMap, nameMap } = createRouteMap(routes);
1453
-
1454
- function addRoutes (routes) {
1455
- createRouteMap(routes, pathList, pathMap, nameMap);
1456
- }
1457
-
1458
- function match (
1459
- raw,
1460
- currentRoute,
1461
- redirectedFrom
1462
- ) {
1463
- const location = normalizeLocation(raw, currentRoute, false, router);
1464
- const { name } = location;
1465
-
1466
- if (name) {
1467
- const record = nameMap[name];
1468
- {
1469
- warn(record, `Route with name '${name}' does not exist`);
1470
- }
1471
- if (!record) return _createRoute(null, location)
1472
- const paramNames = record.regex.keys
1473
- .filter(key => !key.optional)
1474
- .map(key => key.name);
1475
-
1476
- if (typeof location.params !== 'object') {
1477
- location.params = {};
1478
- }
1479
-
1480
- if (currentRoute && typeof currentRoute.params === 'object') {
1481
- for (const key in currentRoute.params) {
1482
- if (!(key in location.params) && paramNames.indexOf(key) > -1) {
1483
- location.params[key] = currentRoute.params[key];
1484
- }
1485
- }
1486
- }
1487
-
1488
- location.path = fillParams(record.path, location.params, `named route "${name}"`);
1489
- return _createRoute(record, location, redirectedFrom)
1490
- } else if (location.path) {
1491
- location.params = {};
1492
- for (let i = 0; i < pathList.length; i++) {
1493
- const path = pathList[i];
1494
- const record = pathMap[path];
1495
- if (matchRoute(record.regex, location.path, location.params)) {
1496
- return _createRoute(record, location, redirectedFrom)
1497
- }
1498
- }
1499
- }
1500
- // no match
1501
- return _createRoute(null, location)
1502
- }
1503
-
1504
- function redirect (
1505
- record,
1506
- location
1507
- ) {
1508
- const originalRedirect = record.redirect;
1509
- let redirect = typeof originalRedirect === 'function'
1510
- ? originalRedirect(createRoute(record, location, null, router))
1511
- : originalRedirect;
1512
-
1513
- if (typeof redirect === 'string') {
1514
- redirect = { path: redirect };
1515
- }
1516
-
1517
- if (!redirect || typeof redirect !== 'object') {
1518
- {
1519
- warn(
1520
- false, `invalid redirect option: ${JSON.stringify(redirect)}`
1521
- );
1522
- }
1523
- return _createRoute(null, location)
1524
- }
1525
-
1526
- const re = redirect;
1527
- const { name, path } = re;
1528
- let { query, hash, params } = location;
1529
- query = re.hasOwnProperty('query') ? re.query : query;
1530
- hash = re.hasOwnProperty('hash') ? re.hash : hash;
1531
- params = re.hasOwnProperty('params') ? re.params : params;
1532
-
1533
- if (name) {
1534
- // resolved named direct
1535
- const targetRecord = nameMap[name];
1536
- {
1537
- assert(targetRecord, `redirect failed: named route "${name}" not found.`);
1538
- }
1539
- return match({
1540
- _normalized: true,
1541
- name,
1542
- query,
1543
- hash,
1544
- params
1545
- }, undefined, location)
1546
- } else if (path) {
1547
- // 1. resolve relative redirect
1548
- const rawPath = resolveRecordPath(path, record);
1549
- // 2. resolve params
1550
- const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`);
1551
- // 3. rematch with existing query and hash
1552
- return match({
1553
- _normalized: true,
1554
- path: resolvedPath,
1555
- query,
1556
- hash
1557
- }, undefined, location)
1558
- } else {
1559
- {
1560
- warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`);
1561
- }
1562
- return _createRoute(null, location)
1563
- }
1564
- }
1565
-
1566
- function alias (
1567
- record,
1568
- location,
1569
- matchAs
1570
- ) {
1571
- const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`);
1572
- const aliasedMatch = match({
1573
- _normalized: true,
1574
- path: aliasedPath
1575
- });
1576
- if (aliasedMatch) {
1577
- const matched = aliasedMatch.matched;
1578
- const aliasedRecord = matched[matched.length - 1];
1579
- location.params = aliasedMatch.params;
1580
- return _createRoute(aliasedRecord, location)
1581
- }
1582
- return _createRoute(null, location)
1583
- }
1584
-
1585
- function _createRoute (
1586
- record,
1587
- location,
1588
- redirectedFrom
1589
- ) {
1590
- if (record && record.redirect) {
1591
- return redirect(record, redirectedFrom || location)
1592
- }
1593
- if (record && record.matchAs) {
1594
- return alias(record, location, record.matchAs)
1595
- }
1596
- return createRoute(record, location, redirectedFrom, router)
1597
- }
1598
-
1599
- return {
1600
- match,
1601
- addRoutes
1602
- }
1603
- }
1604
-
1605
- function matchRoute (
1606
- regex,
1607
- path,
1608
- params
1609
- ) {
1610
- const m = path.match(regex);
1611
-
1612
- if (!m) {
1613
- return false
1614
- } else if (!params) {
1615
- return true
1616
- }
1617
-
1618
- for (let i = 1, len = m.length; i < len; ++i) {
1619
- const key = regex.keys[i - 1];
1620
- const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i];
1621
- if (key) {
1622
- // Fix #1994: using * with props: true generates a param named 0
1623
- params[key.name || 'pathMatch'] = val;
1624
- }
1625
- }
1626
-
1627
- return true
1628
- }
1629
-
1630
- function resolveRecordPath (path, record) {
1631
- return resolvePath(path, record.parent ? record.parent.path : '/', true)
1632
- }
1633
-
1634
- /* */
1635
-
1636
- // use User Timing api (if present) for more accurate key precision
1637
- const Time =
1638
- inBrowser && window.performance && window.performance.now
1639
- ? window.performance
1640
- : Date;
1641
-
1642
- function genStateKey () {
1643
- return Time.now().toFixed(3)
1644
- }
1645
-
1646
- let _key = genStateKey();
1647
-
1648
- function getStateKey () {
1649
- return _key
1650
- }
1651
-
1652
- function setStateKey (key) {
1653
- return (_key = key)
1654
- }
1655
-
1656
- /* */
1657
-
1658
- const positionStore = Object.create(null);
1659
-
1660
- function setupScroll () {
1661
- // Prevent browser scroll behavior on History popstate
1662
- if ('scrollRestoration' in window.history) {
1663
- window.history.scrollRestoration = 'manual';
1664
- }
1665
- // Fix for #1585 for Firefox
1666
- // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
1667
- // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
1668
- // window.location.protocol + '//' + window.location.host
1669
- // location.host contains the port and location.hostname doesn't
1670
- const protocolAndPath = window.location.protocol + '//' + window.location.host;
1671
- const absolutePath = window.location.href.replace(protocolAndPath, '');
1672
- // preserve existing history state as it could be overriden by the user
1673
- const stateCopy = extend({}, window.history.state);
1674
- stateCopy.key = getStateKey();
1675
- window.history.replaceState(stateCopy, '', absolutePath);
1676
- window.addEventListener('popstate', handlePopState);
1677
- return () => {
1678
- window.removeEventListener('popstate', handlePopState);
1679
- }
1680
- }
1681
-
1682
- function handleScroll (
1683
- router,
1684
- to,
1685
- from,
1686
- isPop
1687
- ) {
1688
- if (!router.app) {
1689
- return
1690
- }
1691
-
1692
- const behavior = router.options.scrollBehavior;
1693
- if (!behavior) {
1694
- return
1695
- }
1696
-
1697
- {
1698
- assert(typeof behavior === 'function', `scrollBehavior must be a function`);
1699
- }
1700
-
1701
- // wait until re-render finishes before scrolling
1702
- router.app.$nextTick(() => {
1703
- const position = getScrollPosition();
1704
- const shouldScroll = behavior.call(
1705
- router,
1706
- to,
1707
- from,
1708
- isPop ? position : null
1709
- );
1710
-
1711
- if (!shouldScroll) {
1712
- return
1713
- }
1714
-
1715
- if (typeof shouldScroll.then === 'function') {
1716
- shouldScroll
1717
- .then(shouldScroll => {
1718
- scrollToPosition((shouldScroll), position);
1719
- })
1720
- .catch(err => {
1721
- {
1722
- assert(false, err.toString());
1723
- }
1724
- });
1725
- } else {
1726
- scrollToPosition(shouldScroll, position);
1727
- }
1728
- });
1729
- }
1730
-
1731
- function saveScrollPosition () {
1732
- const key = getStateKey();
1733
- if (key) {
1734
- positionStore[key] = {
1735
- x: window.pageXOffset,
1736
- y: window.pageYOffset
1737
- };
1738
- }
1739
- }
1740
-
1741
- function handlePopState (e) {
1742
- saveScrollPosition();
1743
- if (e.state && e.state.key) {
1744
- setStateKey(e.state.key);
1745
- }
1746
- }
1747
-
1748
- function getScrollPosition () {
1749
- const key = getStateKey();
1750
- if (key) {
1751
- return positionStore[key]
1752
- }
1753
- }
1754
-
1755
- function getElementPosition (el, offset) {
1756
- const docEl = document.documentElement;
1757
- const docRect = docEl.getBoundingClientRect();
1758
- const elRect = el.getBoundingClientRect();
1759
- return {
1760
- x: elRect.left - docRect.left - offset.x,
1761
- y: elRect.top - docRect.top - offset.y
1762
- }
1763
- }
1764
-
1765
- function isValidPosition (obj) {
1766
- return isNumber(obj.x) || isNumber(obj.y)
1767
- }
1768
-
1769
- function normalizePosition (obj) {
1770
- return {
1771
- x: isNumber(obj.x) ? obj.x : window.pageXOffset,
1772
- y: isNumber(obj.y) ? obj.y : window.pageYOffset
1773
- }
1774
- }
1775
-
1776
- function normalizeOffset (obj) {
1777
- return {
1778
- x: isNumber(obj.x) ? obj.x : 0,
1779
- y: isNumber(obj.y) ? obj.y : 0
1780
- }
1781
- }
1782
-
1783
- function isNumber (v) {
1784
- return typeof v === 'number'
1785
- }
1786
-
1787
- const hashStartsWithNumberRE = /^#\d/;
1788
-
1789
- function scrollToPosition (shouldScroll, position) {
1790
- const isObject = typeof shouldScroll === 'object';
1791
- if (isObject && typeof shouldScroll.selector === 'string') {
1792
- // getElementById would still fail if the selector contains a more complicated query like #main[data-attr]
1793
- // but at the same time, it doesn't make much sense to select an element with an id and an extra selector
1794
- const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line
1795
- ? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line
1796
- : document.querySelector(shouldScroll.selector);
1797
-
1798
- if (el) {
1799
- let offset =
1800
- shouldScroll.offset && typeof shouldScroll.offset === 'object'
1801
- ? shouldScroll.offset
1802
- : {};
1803
- offset = normalizeOffset(offset);
1804
- position = getElementPosition(el, offset);
1805
- } else if (isValidPosition(shouldScroll)) {
1806
- position = normalizePosition(shouldScroll);
1807
- }
1808
- } else if (isObject && isValidPosition(shouldScroll)) {
1809
- position = normalizePosition(shouldScroll);
1810
- }
1811
-
1812
- if (position) {
1813
- window.scrollTo(position.x, position.y);
1814
- }
1815
- }
1816
-
1817
- /* */
1818
-
1819
- const supportsPushState =
1820
- inBrowser &&
1821
- (function () {
1822
- const ua = window.navigator.userAgent;
1823
-
1824
- if (
1825
- (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
1826
- ua.indexOf('Mobile Safari') !== -1 &&
1827
- ua.indexOf('Chrome') === -1 &&
1828
- ua.indexOf('Windows Phone') === -1
1829
- ) {
1830
- return false
1831
- }
1832
-
1833
- return window.history && typeof window.history.pushState === 'function'
1834
- })();
1835
-
1836
- function pushState (url, replace) {
1837
- saveScrollPosition();
1838
- // try...catch the pushState call to get around Safari
1839
- // DOM Exception 18 where it limits to 100 pushState calls
1840
- const history = window.history;
1841
- try {
1842
- if (replace) {
1843
- // preserve existing history state as it could be overriden by the user
1844
- const stateCopy = extend({}, history.state);
1845
- stateCopy.key = getStateKey();
1846
- history.replaceState(stateCopy, '', url);
1847
- } else {
1848
- history.pushState({ key: setStateKey(genStateKey()) }, '', url);
1849
- }
1850
- } catch (e) {
1851
- window.location[replace ? 'replace' : 'assign'](url);
1852
- }
1853
- }
1854
-
1855
- function replaceState (url) {
1856
- pushState(url, true);
1857
- }
1858
-
1859
- /* */
1860
-
1861
- function runQueue (queue, fn, cb) {
1862
- const step = index => {
1863
- if (index >= queue.length) {
1864
- cb();
1865
- } else {
1866
- if (queue[index]) {
1867
- fn(queue[index], () => {
1868
- step(index + 1);
1869
- });
1870
- } else {
1871
- step(index + 1);
1872
- }
1873
- }
1874
- };
1875
- step(0);
1876
- }
1877
-
1878
- const NavigationFailureType = {
1879
- redirected: 2,
1880
- aborted: 4,
1881
- cancelled: 8,
1882
- duplicated: 16
1883
- };
1884
-
1885
- function createNavigationRedirectedError (from, to) {
1886
- return createRouterError(
1887
- from,
1888
- to,
1889
- NavigationFailureType.redirected,
1890
- `Redirected when going from "${from.fullPath}" to "${stringifyRoute(
1891
- to
1892
- )}" via a navigation guard.`
1893
- )
1894
- }
1895
-
1896
- function createNavigationDuplicatedError (from, to) {
1897
- const error = createRouterError(
1898
- from,
1899
- to,
1900
- NavigationFailureType.duplicated,
1901
- `Avoided redundant navigation to current location: "${from.fullPath}".`
1902
- );
1903
- // backwards compatible with the first introduction of Errors
1904
- error.name = 'NavigationDuplicated';
1905
- return error
1906
- }
1907
-
1908
- function createNavigationCancelledError (from, to) {
1909
- return createRouterError(
1910
- from,
1911
- to,
1912
- NavigationFailureType.cancelled,
1913
- `Navigation cancelled from "${from.fullPath}" to "${
1914
- to.fullPath
1915
- }" with a new navigation.`
1916
- )
1917
- }
1918
-
1919
- function createNavigationAbortedError (from, to) {
1920
- return createRouterError(
1921
- from,
1922
- to,
1923
- NavigationFailureType.aborted,
1924
- `Navigation aborted from "${from.fullPath}" to "${
1925
- to.fullPath
1926
- }" via a navigation guard.`
1927
- )
1928
- }
1929
-
1930
- function createRouterError (from, to, type, message) {
1931
- const error = new Error(message);
1932
- error._isRouter = true;
1933
- error.from = from;
1934
- error.to = to;
1935
- error.type = type;
1936
-
1937
- return error
1938
- }
1939
-
1940
- const propertiesToLog = ['params', 'query', 'hash'];
1941
-
1942
- function stringifyRoute (to) {
1943
- if (typeof to === 'string') return to
1944
- if ('path' in to) return to.path
1945
- const location = {};
1946
- propertiesToLog.forEach(key => {
1947
- if (key in to) location[key] = to[key];
1948
- });
1949
- return JSON.stringify(location, null, 2)
1950
- }
1951
-
1952
- function isError (err) {
1953
- return Object.prototype.toString.call(err).indexOf('Error') > -1
1954
- }
1955
-
1956
- function isNavigationFailure (err, errorType) {
1957
- return (
1958
- isError(err) &&
1959
- err._isRouter &&
1960
- (errorType == null || err.type === errorType)
1961
- )
1962
- }
1963
-
1964
- /* */
1965
-
1966
- function resolveAsyncComponents (matched) {
1967
- return (to, from, next) => {
1968
- let hasAsync = false;
1969
- let pending = 0;
1970
- let error = null;
1971
-
1972
- flatMapComponents(matched, (def, _, match, key) => {
1973
- // if it's a function and doesn't have cid attached,
1974
- // assume it's an async component resolve function.
1975
- // we are not using Kdu's default async resolving mechanism because
1976
- // we want to halt the navigation until the incoming component has been
1977
- // resolved.
1978
- if (typeof def === 'function' && def.cid === undefined) {
1979
- hasAsync = true;
1980
- pending++;
1981
-
1982
- const resolve = once(resolvedDef => {
1983
- if (isESModule(resolvedDef)) {
1984
- resolvedDef = resolvedDef.default;
1985
- }
1986
- // save resolved on async factory in case it's used elsewhere
1987
- def.resolved = typeof resolvedDef === 'function'
1988
- ? resolvedDef
1989
- : _Kdu.extend(resolvedDef);
1990
- match.components[key] = resolvedDef;
1991
- pending--;
1992
- if (pending <= 0) {
1993
- next();
1994
- }
1995
- });
1996
-
1997
- const reject = once(reason => {
1998
- const msg = `Failed to resolve async component ${key}: ${reason}`;
1999
- warn(false, msg);
2000
- if (!error) {
2001
- error = isError(reason)
2002
- ? reason
2003
- : new Error(msg);
2004
- next(error);
2005
- }
2006
- });
2007
-
2008
- let res;
2009
- try {
2010
- res = def(resolve, reject);
2011
- } catch (e) {
2012
- reject(e);
2013
- }
2014
- if (res) {
2015
- if (typeof res.then === 'function') {
2016
- res.then(resolve, reject);
2017
- } else {
2018
- // new syntax in Kdu 2.3
2019
- const comp = res.component;
2020
- if (comp && typeof comp.then === 'function') {
2021
- comp.then(resolve, reject);
2022
- }
2023
- }
2024
- }
2025
- }
2026
- });
2027
-
2028
- if (!hasAsync) next();
2029
- }
2030
- }
2031
-
2032
- function flatMapComponents (
2033
- matched,
2034
- fn
2035
- ) {
2036
- return flatten(matched.map(m => {
2037
- return Object.keys(m.components).map(key => fn(
2038
- m.components[key],
2039
- m.instances[key],
2040
- m, key
2041
- ))
2042
- }))
2043
- }
2044
-
2045
- function flatten (arr) {
2046
- return Array.prototype.concat.apply([], arr)
2047
- }
2048
-
2049
- const hasSymbol =
2050
- typeof Symbol === 'function' &&
2051
- typeof Symbol.toStringTag === 'symbol';
2052
-
2053
- function isESModule (obj) {
2054
- return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
2055
- }
2056
-
2057
- // in Webpack 2, require.ensure now also returns a Promise
2058
- // so the resolve/reject functions may get called an extra time
2059
- // if the user uses an arrow function shorthand that happens to
2060
- // return that Promise.
2061
- function once (fn) {
2062
- let called = false;
2063
- return function (...args) {
2064
- if (called) return
2065
- called = true;
2066
- return fn.apply(this, args)
2067
- }
2068
- }
2069
-
2070
- /* */
2071
-
2072
- class History {
2073
-
2074
-
2075
-
2076
-
2077
-
2078
-
2079
-
2080
-
2081
-
2082
-
2083
-
2084
-
2085
- // implemented by sub-classes
2086
-
2087
-
2088
-
2089
-
2090
-
2091
-
2092
-
2093
- constructor (router, base) {
2094
- this.router = router;
2095
- this.base = normalizeBase(base);
2096
- // start with a route object that stands for "nowhere"
2097
- this.current = START;
2098
- this.pending = null;
2099
- this.ready = false;
2100
- this.readyCbs = [];
2101
- this.readyErrorCbs = [];
2102
- this.errorCbs = [];
2103
- this.listeners = [];
2104
- }
2105
-
2106
- listen (cb) {
2107
- this.cb = cb;
2108
- }
2109
-
2110
- onReady (cb, errorCb) {
2111
- if (this.ready) {
2112
- cb();
2113
- } else {
2114
- this.readyCbs.push(cb);
2115
- if (errorCb) {
2116
- this.readyErrorCbs.push(errorCb);
2117
- }
2118
- }
2119
- }
2120
-
2121
- onError (errorCb) {
2122
- this.errorCbs.push(errorCb);
2123
- }
2124
-
2125
- transitionTo (
2126
- location,
2127
- onComplete,
2128
- onAbort
2129
- ) {
2130
- let route;
2131
- try {
2132
- route = this.router.match(location, this.current);
2133
- } catch (e) {
2134
- this.errorCbs.forEach(cb => {
2135
- cb(e);
2136
- });
2137
- // Exception should still be thrown
2138
- throw e
2139
- }
2140
- this.confirmTransition(
2141
- route,
2142
- () => {
2143
- const prev = this.current;
2144
- this.updateRoute(route);
2145
- onComplete && onComplete(route);
2146
- this.ensureURL();
2147
- this.router.afterHooks.forEach(hook => {
2148
- hook && hook(route, prev);
2149
- });
2150
-
2151
- // fire ready cbs once
2152
- if (!this.ready) {
2153
- this.ready = true;
2154
- this.readyCbs.forEach(cb => {
2155
- cb(route);
2156
- });
2157
- }
2158
- },
2159
- err => {
2160
- if (onAbort) {
2161
- onAbort(err);
2162
- }
2163
- if (err && !this.ready) {
2164
- this.ready = true;
2165
- // Initial redirection should still trigger the onReady onSuccess
2166
- if (!isNavigationFailure(err, NavigationFailureType.redirected)) {
2167
- this.readyErrorCbs.forEach(cb => {
2168
- cb(err);
2169
- });
2170
- } else {
2171
- this.readyCbs.forEach(cb => {
2172
- cb(route);
2173
- });
2174
- }
2175
- }
2176
- }
2177
- );
2178
- }
2179
-
2180
- confirmTransition (route, onComplete, onAbort) {
2181
- const current = this.current;
2182
- const abort = err => {
2183
- // changed after adding errors before that change,
2184
- // redirect and aborted navigation would produce an err == null
2185
- if (!isNavigationFailure(err) && isError(err)) {
2186
- if (this.errorCbs.length) {
2187
- this.errorCbs.forEach(cb => {
2188
- cb(err);
2189
- });
2190
- } else {
2191
- warn(false, 'uncaught error during route navigation:');
2192
- console.error(err);
2193
- }
2194
- }
2195
- onAbort && onAbort(err);
2196
- };
2197
- const lastRouteIndex = route.matched.length - 1;
2198
- const lastCurrentIndex = current.matched.length - 1;
2199
- if (
2200
- isSameRoute(route, current) &&
2201
- // in the case the route map has been dynamically appended to
2202
- lastRouteIndex === lastCurrentIndex &&
2203
- route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
2204
- ) {
2205
- this.ensureURL();
2206
- return abort(createNavigationDuplicatedError(current, route))
2207
- }
2208
-
2209
- const { updated, deactivated, activated } = resolveQueue(
2210
- this.current.matched,
2211
- route.matched
2212
- );
2213
-
2214
- const queue = [].concat(
2215
- // in-component leave guards
2216
- extractLeaveGuards(deactivated),
2217
- // global before hooks
2218
- this.router.beforeHooks,
2219
- // in-component update hooks
2220
- extractUpdateHooks(updated),
2221
- // in-config enter guards
2222
- activated.map(m => m.beforeEnter),
2223
- // async components
2224
- resolveAsyncComponents(activated)
2225
- );
2226
-
2227
- this.pending = route;
2228
- const iterator = (hook, next) => {
2229
- if (this.pending !== route) {
2230
- return abort(createNavigationCancelledError(current, route))
2231
- }
2232
- try {
2233
- hook(route, current, (to) => {
2234
- if (to === false) {
2235
- // next(false) -> abort navigation, ensure current URL
2236
- this.ensureURL(true);
2237
- abort(createNavigationAbortedError(current, route));
2238
- } else if (isError(to)) {
2239
- this.ensureURL(true);
2240
- abort(to);
2241
- } else if (
2242
- typeof to === 'string' ||
2243
- (typeof to === 'object' &&
2244
- (typeof to.path === 'string' || typeof to.name === 'string'))
2245
- ) {
2246
- // next('/') or next({ path: '/' }) -> redirect
2247
- abort(createNavigationRedirectedError(current, route));
2248
- if (typeof to === 'object' && to.replace) {
2249
- this.replace(to);
2250
- } else {
2251
- this.push(to);
2252
- }
2253
- } else {
2254
- // confirm transition and pass on the value
2255
- next(to);
2256
- }
2257
- });
2258
- } catch (e) {
2259
- abort(e);
2260
- }
2261
- };
2262
-
2263
- runQueue(queue, iterator, () => {
2264
- const postEnterCbs = [];
2265
- const isValid = () => this.current === route;
2266
- // wait until async components are resolved before
2267
- // extracting in-component enter guards
2268
- const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
2269
- const queue = enterGuards.concat(this.router.resolveHooks);
2270
- runQueue(queue, iterator, () => {
2271
- if (this.pending !== route) {
2272
- return abort(createNavigationCancelledError(current, route))
2273
- }
2274
- this.pending = null;
2275
- onComplete(route);
2276
- if (this.router.app) {
2277
- this.router.app.$nextTick(() => {
2278
- postEnterCbs.forEach(cb => {
2279
- cb();
2280
- });
2281
- });
2282
- }
2283
- });
2284
- });
2285
- }
2286
-
2287
- updateRoute (route) {
2288
- this.current = route;
2289
- this.cb && this.cb(route);
2290
- }
2291
-
2292
- setupListeners () {
2293
- // Default implementation is empty
2294
- }
2295
-
2296
- teardownListeners () {
2297
- this.listeners.forEach(cleanupListener => {
2298
- cleanupListener();
2299
- });
2300
- this.listeners = [];
2301
- }
2302
- }
2303
-
2304
- function normalizeBase (base) {
2305
- if (!base) {
2306
- if (inBrowser) {
2307
- // respect <base> tag
2308
- const baseEl = document.querySelector('base');
2309
- base = (baseEl && baseEl.getAttribute('href')) || '/';
2310
- // strip full URL origin
2311
- base = base.replace(/^https?:\/\/[^\/]+/, '');
2312
- } else {
2313
- base = '/';
2314
- }
2315
- }
2316
- // make sure there's the starting slash
2317
- if (base.charAt(0) !== '/') {
2318
- base = '/' + base;
2319
- }
2320
- // remove trailing slash
2321
- return base.replace(/\/$/, '')
2322
- }
2323
-
2324
- function resolveQueue (
2325
- current,
2326
- next
2327
- ) {
2328
- let i;
2329
- const max = Math.max(current.length, next.length);
2330
- for (i = 0; i < max; i++) {
2331
- if (current[i] !== next[i]) {
2332
- break
2333
- }
2334
- }
2335
- return {
2336
- updated: next.slice(0, i),
2337
- activated: next.slice(i),
2338
- deactivated: current.slice(i)
2339
- }
2340
- }
2341
-
2342
- function extractGuards (
2343
- records,
2344
- name,
2345
- bind,
2346
- reverse
2347
- ) {
2348
- const guards = flatMapComponents(records, (def, instance, match, key) => {
2349
- const guard = extractGuard(def, name);
2350
- if (guard) {
2351
- return Array.isArray(guard)
2352
- ? guard.map(guard => bind(guard, instance, match, key))
2353
- : bind(guard, instance, match, key)
2354
- }
2355
- });
2356
- return flatten(reverse ? guards.reverse() : guards)
2357
- }
2358
-
2359
- function extractGuard (
2360
- def,
2361
- key
2362
- ) {
2363
- if (typeof def !== 'function') {
2364
- // extend now so that global mixins are applied.
2365
- def = _Kdu.extend(def);
2366
- }
2367
- return def.options[key]
2368
- }
2369
-
2370
- function extractLeaveGuards (deactivated) {
2371
- return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
2372
- }
2373
-
2374
- function extractUpdateHooks (updated) {
2375
- return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
2376
- }
2377
-
2378
- function bindGuard (guard, instance) {
2379
- if (instance) {
2380
- return function boundRouteGuard () {
2381
- return guard.apply(instance, arguments)
2382
- }
2383
- }
2384
- }
2385
-
2386
- function extractEnterGuards (
2387
- activated,
2388
- cbs,
2389
- isValid
2390
- ) {
2391
- return extractGuards(
2392
- activated,
2393
- 'beforeRouteEnter',
2394
- (guard, _, match, key) => {
2395
- return bindEnterGuard(guard, match, key, cbs, isValid)
2396
- }
2397
- )
2398
- }
2399
-
2400
- function bindEnterGuard (
2401
- guard,
2402
- match,
2403
- key,
2404
- cbs,
2405
- isValid
2406
- ) {
2407
- return function routeEnterGuard (to, from, next) {
2408
- return guard(to, from, cb => {
2409
- if (typeof cb === 'function') {
2410
- cbs.push(() => {
2411
- // #750
2412
- // if a router-view is wrapped with an out-in transition,
2413
- // the instance may not have been registered at this time.
2414
- // we will need to poll for registration until current route
2415
- // is no longer valid.
2416
- poll(cb, match.instances, key, isValid);
2417
- });
2418
- }
2419
- next(cb);
2420
- })
2421
- }
2422
- }
2423
-
2424
- function poll (
2425
- cb, // somehow flow cannot infer this is a function
2426
- instances,
2427
- key,
2428
- isValid
2429
- ) {
2430
- if (
2431
- instances[key] &&
2432
- !instances[key]._isBeingDestroyed // do not reuse being destroyed instance
2433
- ) {
2434
- cb(instances[key]);
2435
- } else if (isValid()) {
2436
- setTimeout(() => {
2437
- poll(cb, instances, key, isValid);
2438
- }, 16);
2439
- }
2440
- }
2441
-
2442
- /* */
2443
-
2444
- class HTML5History extends History {
2445
-
2446
-
2447
- constructor (router, base) {
2448
- super(router, base);
2449
-
2450
- this._startLocation = getLocation(this.base);
2451
- }
2452
-
2453
- setupListeners () {
2454
- if (this.listeners.length > 0) {
2455
- return
2456
- }
2457
-
2458
- const router = this.router;
2459
- const expectScroll = router.options.scrollBehavior;
2460
- const supportsScroll = supportsPushState && expectScroll;
2461
-
2462
- if (supportsScroll) {
2463
- this.listeners.push(setupScroll());
2464
- }
2465
-
2466
- const handleRoutingEvent = () => {
2467
- const current = this.current;
2468
-
2469
- // Avoiding first `popstate` event dispatched in some browsers but first
2470
- // history route not updated since async guard at the same time.
2471
- const location = getLocation(this.base);
2472
- if (this.current === START && location === this._startLocation) {
2473
- return
2474
- }
2475
-
2476
- this.transitionTo(location, route => {
2477
- if (supportsScroll) {
2478
- handleScroll(router, route, current, true);
2479
- }
2480
- });
2481
- };
2482
- window.addEventListener('popstate', handleRoutingEvent);
2483
- this.listeners.push(() => {
2484
- window.removeEventListener('popstate', handleRoutingEvent);
2485
- });
2486
- }
2487
-
2488
- go (n) {
2489
- window.history.go(n);
2490
- }
2491
-
2492
- push (location, onComplete, onAbort) {
2493
- const { current: fromRoute } = this;
2494
- this.transitionTo(location, route => {
2495
- pushState(cleanPath(this.base + route.fullPath));
2496
- handleScroll(this.router, route, fromRoute, false);
2497
- onComplete && onComplete(route);
2498
- }, onAbort);
2499
- }
2500
-
2501
- replace (location, onComplete, onAbort) {
2502
- const { current: fromRoute } = this;
2503
- this.transitionTo(location, route => {
2504
- replaceState(cleanPath(this.base + route.fullPath));
2505
- handleScroll(this.router, route, fromRoute, false);
2506
- onComplete && onComplete(route);
2507
- }, onAbort);
2508
- }
2509
-
2510
- ensureURL (push) {
2511
- if (getLocation(this.base) !== this.current.fullPath) {
2512
- const current = cleanPath(this.base + this.current.fullPath);
2513
- push ? pushState(current) : replaceState(current);
2514
- }
2515
- }
2516
-
2517
- getCurrentLocation () {
2518
- return getLocation(this.base)
2519
- }
2520
- }
2521
-
2522
- function getLocation (base) {
2523
- let path = decodeURI(window.location.pathname);
2524
- if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
2525
- path = path.slice(base.length);
2526
- }
2527
- return (path || '/') + window.location.search + window.location.hash
2528
- }
2529
-
2530
- /* */
2531
-
2532
- class HashHistory extends History {
2533
- constructor (router, base, fallback) {
2534
- super(router, base);
2535
- // check history fallback deeplinking
2536
- if (fallback && checkFallback(this.base)) {
2537
- return
2538
- }
2539
- ensureSlash();
2540
- }
2541
-
2542
- // this is delayed until the app mounts
2543
- // to avoid the hashchange listener being fired too early
2544
- setupListeners () {
2545
- if (this.listeners.length > 0) {
2546
- return
2547
- }
2548
-
2549
- const router = this.router;
2550
- const expectScroll = router.options.scrollBehavior;
2551
- const supportsScroll = supportsPushState && expectScroll;
2552
-
2553
- if (supportsScroll) {
2554
- this.listeners.push(setupScroll());
2555
- }
2556
-
2557
- const handleRoutingEvent = () => {
2558
- const current = this.current;
2559
- if (!ensureSlash()) {
2560
- return
2561
- }
2562
- this.transitionTo(getHash(), route => {
2563
- if (supportsScroll) {
2564
- handleScroll(this.router, route, current, true);
2565
- }
2566
- if (!supportsPushState) {
2567
- replaceHash(route.fullPath);
2568
- }
2569
- });
2570
- };
2571
- const eventType = supportsPushState ? 'popstate' : 'hashchange';
2572
- window.addEventListener(
2573
- eventType,
2574
- handleRoutingEvent
2575
- );
2576
- this.listeners.push(() => {
2577
- window.removeEventListener(eventType, handleRoutingEvent);
2578
- });
2579
- }
2580
-
2581
- push (location, onComplete, onAbort) {
2582
- const { current: fromRoute } = this;
2583
- this.transitionTo(
2584
- location,
2585
- route => {
2586
- pushHash(route.fullPath);
2587
- handleScroll(this.router, route, fromRoute, false);
2588
- onComplete && onComplete(route);
2589
- },
2590
- onAbort
2591
- );
2592
- }
2593
-
2594
- replace (location, onComplete, onAbort) {
2595
- const { current: fromRoute } = this;
2596
- this.transitionTo(
2597
- location,
2598
- route => {
2599
- replaceHash(route.fullPath);
2600
- handleScroll(this.router, route, fromRoute, false);
2601
- onComplete && onComplete(route);
2602
- },
2603
- onAbort
2604
- );
2605
- }
2606
-
2607
- go (n) {
2608
- window.history.go(n);
2609
- }
2610
-
2611
- ensureURL (push) {
2612
- const current = this.current.fullPath;
2613
- if (getHash() !== current) {
2614
- push ? pushHash(current) : replaceHash(current);
2615
- }
2616
- }
2617
-
2618
- getCurrentLocation () {
2619
- return getHash()
2620
- }
2621
- }
2622
-
2623
- function checkFallback (base) {
2624
- const location = getLocation(base);
2625
- if (!/^\/#/.test(location)) {
2626
- window.location.replace(cleanPath(base + '/#' + location));
2627
- return true
2628
- }
2629
- }
2630
-
2631
- function ensureSlash () {
2632
- const path = getHash();
2633
- if (path.charAt(0) === '/') {
2634
- return true
2635
- }
2636
- replaceHash('/' + path);
2637
- return false
2638
- }
2639
-
2640
- function getHash () {
2641
- // We can't use window.location.hash here because it's not
2642
- // consistent across browsers - Firefox will pre-decode it!
2643
- let href = window.location.href;
2644
- const index = href.indexOf('#');
2645
- // empty path
2646
- if (index < 0) return ''
2647
-
2648
- href = href.slice(index + 1);
2649
- // decode the hash but not the search or hash
2650
- // as search(query) is already decoded
2651
- const searchIndex = href.indexOf('?');
2652
- if (searchIndex < 0) {
2653
- const hashIndex = href.indexOf('#');
2654
- if (hashIndex > -1) {
2655
- href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
2656
- } else href = decodeURI(href);
2657
- } else {
2658
- href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
2659
- }
2660
-
2661
- return href
2662
- }
2663
-
2664
- function getUrl (path) {
2665
- const href = window.location.href;
2666
- const i = href.indexOf('#');
2667
- const base = i >= 0 ? href.slice(0, i) : href;
2668
- return `${base}#${path}`
2669
- }
2670
-
2671
- function pushHash (path) {
2672
- if (supportsPushState) {
2673
- pushState(getUrl(path));
2674
- } else {
2675
- window.location.hash = path;
2676
- }
2677
- }
2678
-
2679
- function replaceHash (path) {
2680
- if (supportsPushState) {
2681
- replaceState(getUrl(path));
2682
- } else {
2683
- window.location.replace(getUrl(path));
2684
- }
2685
- }
2686
-
2687
- /* */
2688
-
2689
- class AbstractHistory extends History {
2690
-
2691
-
2692
-
2693
- constructor (router, base) {
2694
- super(router, base);
2695
- this.stack = [];
2696
- this.index = -1;
2697
- }
2698
-
2699
- push (location, onComplete, onAbort) {
2700
- this.transitionTo(
2701
- location,
2702
- route => {
2703
- this.stack = this.stack.slice(0, this.index + 1).concat(route);
2704
- this.index++;
2705
- onComplete && onComplete(route);
2706
- },
2707
- onAbort
2708
- );
2709
- }
2710
-
2711
- replace (location, onComplete, onAbort) {
2712
- this.transitionTo(
2713
- location,
2714
- route => {
2715
- this.stack = this.stack.slice(0, this.index).concat(route);
2716
- onComplete && onComplete(route);
2717
- },
2718
- onAbort
2719
- );
2720
- }
2721
-
2722
- go (n) {
2723
- const targetIndex = this.index + n;
2724
- if (targetIndex < 0 || targetIndex >= this.stack.length) {
2725
- return
2726
- }
2727
- const route = this.stack[targetIndex];
2728
- this.confirmTransition(
2729
- route,
2730
- () => {
2731
- this.index = targetIndex;
2732
- this.updateRoute(route);
2733
- },
2734
- err => {
2735
- if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
2736
- this.index = targetIndex;
2737
- }
2738
- }
2739
- );
2740
- }
2741
-
2742
- getCurrentLocation () {
2743
- const current = this.stack[this.stack.length - 1];
2744
- return current ? current.fullPath : '/'
2745
- }
2746
-
2747
- ensureURL () {
2748
- // noop
2749
- }
2750
- }
2751
-
2752
- /* */
2753
-
2754
- class KduRouter {
2755
-
2756
-
2757
-
2758
-
2759
-
2760
-
2761
-
2762
-
2763
-
2764
-
2765
-
2766
-
2767
-
2768
-
2769
-
2770
-
2771
-
2772
-
2773
- constructor (options = {}) {
2774
- this.app = null;
2775
- this.apps = [];
2776
- this.options = options;
2777
- this.beforeHooks = [];
2778
- this.resolveHooks = [];
2779
- this.afterHooks = [];
2780
- this.matcher = createMatcher(options.routes || [], this);
2781
-
2782
- let mode = options.mode || 'hash';
2783
- this.fallback =
2784
- mode === 'history' && !supportsPushState && options.fallback !== false;
2785
- if (this.fallback) {
2786
- mode = 'hash';
2787
- }
2788
- if (!inBrowser) {
2789
- mode = 'abstract';
2790
- }
2791
- this.mode = mode;
2792
-
2793
- switch (mode) {
2794
- case 'history':
2795
- this.history = new HTML5History(this, options.base);
2796
- break
2797
- case 'hash':
2798
- this.history = new HashHistory(this, options.base, this.fallback);
2799
- break
2800
- case 'abstract':
2801
- this.history = new AbstractHistory(this, options.base);
2802
- break
2803
- default:
2804
- {
2805
- assert(false, `invalid mode: ${mode}`);
2806
- }
2807
- }
2808
- }
2809
-
2810
- match (raw, current, redirectedFrom) {
2811
- return this.matcher.match(raw, current, redirectedFrom)
2812
- }
2813
-
2814
- get currentRoute () {
2815
- return this.history && this.history.current
2816
- }
2817
-
2818
- init (app /* Kdu component instance */) {
2819
-
2820
- assert(
2821
- install.installed,
2822
- `not installed. Make sure to call \`Kdu.use(KduRouter)\` ` +
2823
- `before creating root instance.`
2824
- );
2825
-
2826
- this.apps.push(app);
2827
-
2828
- // set up app destroyed handler
2829
- app.$once('hook:destroyed', () => {
2830
- // clean out app from this.apps array once destroyed
2831
- const index = this.apps.indexOf(app);
2832
- if (index > -1) this.apps.splice(index, 1);
2833
- // ensure we still have a main app or null if no apps
2834
- // we do not release the router so it can be reused
2835
- if (this.app === app) this.app = this.apps[0] || null;
2836
-
2837
- if (!this.app) {
2838
- // clean up event listeners
2839
- this.history.teardownListeners();
2840
- }
2841
- });
2842
-
2843
- // main app previously initialized
2844
- // return as we don't need to set up new history listener
2845
- if (this.app) {
2846
- return
2847
- }
2848
-
2849
- this.app = app;
2850
-
2851
- const history = this.history;
2852
-
2853
- if (history instanceof HTML5History || history instanceof HashHistory) {
2854
- const handleInitialScroll = routeOrError => {
2855
- const from = history.current;
2856
- const expectScroll = this.options.scrollBehavior;
2857
- const supportsScroll = supportsPushState && expectScroll;
2858
-
2859
- if (supportsScroll && 'fullPath' in routeOrError) {
2860
- handleScroll(this, routeOrError, from, false);
2861
- }
2862
- };
2863
- const setupListeners = routeOrError => {
2864
- history.setupListeners();
2865
- handleInitialScroll(routeOrError);
2866
- };
2867
- history.transitionTo(
2868
- history.getCurrentLocation(),
2869
- setupListeners,
2870
- setupListeners
2871
- );
2872
- }
2873
-
2874
- history.listen(route => {
2875
- this.apps.forEach(app => {
2876
- app._route = route;
2877
- });
2878
- });
2879
- }
2880
-
2881
- beforeEach (fn) {
2882
- return registerHook(this.beforeHooks, fn)
2883
- }
2884
-
2885
- beforeResolve (fn) {
2886
- return registerHook(this.resolveHooks, fn)
2887
- }
2888
-
2889
- afterEach (fn) {
2890
- return registerHook(this.afterHooks, fn)
2891
- }
2892
-
2893
- onReady (cb, errorCb) {
2894
- this.history.onReady(cb, errorCb);
2895
- }
2896
-
2897
- onError (errorCb) {
2898
- this.history.onError(errorCb);
2899
- }
2900
-
2901
- push (location, onComplete, onAbort) {
2902
- // $flow-disable-line
2903
- if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
2904
- return new Promise((resolve, reject) => {
2905
- this.history.push(location, resolve, reject);
2906
- })
2907
- } else {
2908
- this.history.push(location, onComplete, onAbort);
2909
- }
2910
- }
2911
-
2912
- replace (location, onComplete, onAbort) {
2913
- // $flow-disable-line
2914
- if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
2915
- return new Promise((resolve, reject) => {
2916
- this.history.replace(location, resolve, reject);
2917
- })
2918
- } else {
2919
- this.history.replace(location, onComplete, onAbort);
2920
- }
2921
- }
2922
-
2923
- go (n) {
2924
- this.history.go(n);
2925
- }
2926
-
2927
- back () {
2928
- this.go(-1);
2929
- }
2930
-
2931
- forward () {
2932
- this.go(1);
2933
- }
2934
-
2935
- getMatchedComponents (to) {
2936
- const route = to
2937
- ? to.matched
2938
- ? to
2939
- : this.resolve(to).route
2940
- : this.currentRoute;
2941
- if (!route) {
2942
- return []
2943
- }
2944
- return [].concat.apply(
2945
- [],
2946
- route.matched.map(m => {
2947
- return Object.keys(m.components).map(key => {
2948
- return m.components[key]
2949
- })
2950
- })
2951
- )
2952
- }
2953
-
2954
- resolve (
2955
- to,
2956
- current,
2957
- append
2958
- ) {
2959
- current = current || this.history.current;
2960
- const location = normalizeLocation(to, current, append, this);
2961
- const route = this.match(location, current);
2962
- const fullPath = route.redirectedFrom || route.fullPath;
2963
- const base = this.history.base;
2964
- const href = createHref(base, fullPath, this.mode);
2965
- return {
2966
- location,
2967
- route,
2968
- href,
2969
- // for backwards compat
2970
- normalizedTo: location,
2971
- resolved: route
2972
- }
2973
- }
2974
-
2975
- addRoutes (routes) {
2976
- this.matcher.addRoutes(routes);
2977
- if (this.history.current !== START) {
2978
- this.history.transitionTo(this.history.getCurrentLocation());
2979
- }
2980
- }
2981
- }
2982
-
2983
- function registerHook (list, fn) {
2984
- list.push(fn);
2985
- return () => {
2986
- const i = list.indexOf(fn);
2987
- if (i > -1) list.splice(i, 1);
2988
- }
2989
- }
2990
-
2991
- function createHref (base, fullPath, mode) {
2992
- var path = mode === 'hash' ? '#' + fullPath : fullPath;
2993
- return base ? cleanPath(base + '/' + path) : path
2994
- }
2995
-
2996
- KduRouter.install = install;
2997
- KduRouter.version = '3.4.0-beta.0';
2998
- KduRouter.isNavigationFailure = isNavigationFailure;
2999
- KduRouter.NavigationFailureType = NavigationFailureType;
3000
-
3001
- if (inBrowser && window.Kdu) {
3002
- window.Kdu.use(KduRouter);
3003
- }
1314
+ /* */
1315
+
1316
+ function createRouteMap (
1317
+ routes,
1318
+ oldPathList,
1319
+ oldPathMap,
1320
+ oldNameMap,
1321
+ parentRoute
1322
+ ) {
1323
+ // the path list is used to control path matching priority
1324
+ const pathList = oldPathList || [];
1325
+ // $flow-disable-line
1326
+ const pathMap = oldPathMap || Object.create(null);
1327
+ // $flow-disable-line
1328
+ const nameMap = oldNameMap || Object.create(null);
1329
+
1330
+ routes.forEach(route => {
1331
+ addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
1332
+ });
1333
+
1334
+ // ensure wildcard routes are always at the end
1335
+ for (let i = 0, l = pathList.length; i < l; i++) {
1336
+ if (pathList[i] === '*') {
1337
+ pathList.push(pathList.splice(i, 1)[0]);
1338
+ l--;
1339
+ i--;
1340
+ }
1341
+ }
1342
+
1343
+ {
1344
+ // warn if routes do not include leading slashes
1345
+ const found = pathList
1346
+ // check for missing leading slash
1347
+ .filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/');
1348
+
1349
+ if (found.length > 0) {
1350
+ const pathNames = found.map(path => `- ${path}`).join('\n');
1351
+ warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`);
1352
+ }
1353
+ }
1354
+
1355
+ return {
1356
+ pathList,
1357
+ pathMap,
1358
+ nameMap
1359
+ }
1360
+ }
1361
+
1362
+ function addRouteRecord (
1363
+ pathList,
1364
+ pathMap,
1365
+ nameMap,
1366
+ route,
1367
+ parent,
1368
+ matchAs
1369
+ ) {
1370
+ const { path, name } = route;
1371
+ {
1372
+ assert(path != null, `"path" is required in a route configuration.`);
1373
+ assert(
1374
+ typeof route.component !== 'string',
1375
+ `route config "component" for path: ${String(
1376
+ path || name
1377
+ )} cannot be a ` + `string id. Use an actual component instead.`
1378
+ );
1379
+
1380
+ warn(
1381
+ // eslint-disable-next-line no-control-regex
1382
+ !/[^\u0000-\u007F]+/.test(path),
1383
+ `Route with path "${path}" contains unencoded characters, make sure ` +
1384
+ `your path is correctly encoded before passing it to the router. Use ` +
1385
+ `encodeURI to encode static segments of your path.`
1386
+ );
1387
+ }
1388
+
1389
+ const pathToRegexpOptions =
1390
+ route.pathToRegexpOptions || {};
1391
+ const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict);
1392
+
1393
+ if (typeof route.caseSensitive === 'boolean') {
1394
+ pathToRegexpOptions.sensitive = route.caseSensitive;
1395
+ }
1396
+
1397
+ const record = {
1398
+ path: normalizedPath,
1399
+ regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
1400
+ components: route.components || { default: route.component },
1401
+ alias: route.alias
1402
+ ? typeof route.alias === 'string'
1403
+ ? [route.alias]
1404
+ : route.alias
1405
+ : [],
1406
+ instances: {},
1407
+ enteredCbs: {},
1408
+ name,
1409
+ parent,
1410
+ matchAs,
1411
+ redirect: route.redirect,
1412
+ beforeEnter: route.beforeEnter,
1413
+ meta: route.meta || {},
1414
+ props:
1415
+ route.props == null
1416
+ ? {}
1417
+ : route.components
1418
+ ? route.props
1419
+ : { default: route.props }
1420
+ };
1421
+
1422
+ if (route.children) {
1423
+ // Warn if route is named, does not redirect and has a default child route.
1424
+ // If users navigate to this route by name, the default child will
1425
+ // not be rendered (GH Issue #629)
1426
+ {
1427
+ if (
1428
+ route.name &&
1429
+ !route.redirect &&
1430
+ route.children.some(child => /^\/?$/.test(child.path))
1431
+ ) {
1432
+ warn(
1433
+ false,
1434
+ `Named Route '${route.name}' has a default child route. ` +
1435
+ `When navigating to this named route (:to="{name: '${
1436
+ route.name
1437
+ }'}"), ` +
1438
+ `the default child route will not be rendered. Remove the name from ` +
1439
+ `this route and use the name of the default child route for named ` +
1440
+ `links instead.`
1441
+ );
1442
+ }
1443
+ }
1444
+ route.children.forEach(child => {
1445
+ const childMatchAs = matchAs
1446
+ ? cleanPath(`${matchAs}/${child.path}`)
1447
+ : undefined;
1448
+ addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
1449
+ });
1450
+ }
1451
+
1452
+ if (!pathMap[record.path]) {
1453
+ pathList.push(record.path);
1454
+ pathMap[record.path] = record;
1455
+ }
1456
+
1457
+ if (route.alias !== undefined) {
1458
+ const aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
1459
+ for (let i = 0; i < aliases.length; ++i) {
1460
+ const alias = aliases[i];
1461
+ if (alias === path) {
1462
+ warn(
1463
+ false,
1464
+ `Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
1465
+ );
1466
+ // skip in dev to make it work
1467
+ continue
1468
+ }
1469
+
1470
+ const aliasRoute = {
1471
+ path: alias,
1472
+ children: route.children
1473
+ };
1474
+ addRouteRecord(
1475
+ pathList,
1476
+ pathMap,
1477
+ nameMap,
1478
+ aliasRoute,
1479
+ parent,
1480
+ record.path || '/' // matchAs
1481
+ );
1482
+ }
1483
+ }
1484
+
1485
+ if (name) {
1486
+ if (!nameMap[name]) {
1487
+ nameMap[name] = record;
1488
+ } else if (!matchAs) {
1489
+ warn(
1490
+ false,
1491
+ `Duplicate named routes definition: ` +
1492
+ `{ name: "${name}", path: "${record.path}" }`
1493
+ );
1494
+ }
1495
+ }
1496
+ }
1497
+
1498
+ function compileRouteRegex (
1499
+ path,
1500
+ pathToRegexpOptions
1501
+ ) {
1502
+ const regex = pathToRegexp_1(path, [], pathToRegexpOptions);
1503
+ {
1504
+ const keys = Object.create(null);
1505
+ regex.keys.forEach(key => {
1506
+ warn(
1507
+ !keys[key.name],
1508
+ `Duplicate param keys in route with path: "${path}"`
1509
+ );
1510
+ keys[key.name] = true;
1511
+ });
1512
+ }
1513
+ return regex
1514
+ }
1515
+
1516
+ function normalizePath (
1517
+ path,
1518
+ parent,
1519
+ strict
1520
+ ) {
1521
+ if (!strict) path = path.replace(/\/$/, '');
1522
+ if (path[0] === '/') return path
1523
+ if (parent == null) return path
1524
+ return cleanPath(`${parent.path}/${path}`)
1525
+ }
1526
+
1527
+ /* */
1528
+
1529
+
1530
+
1531
+ function createMatcher (
1532
+ routes,
1533
+ router
1534
+ ) {
1535
+ const { pathList, pathMap, nameMap } = createRouteMap(routes);
1536
+
1537
+ function addRoutes (routes) {
1538
+ createRouteMap(routes, pathList, pathMap, nameMap);
1539
+ }
1540
+
1541
+ function addRoute (parentOrRoute, route) {
1542
+ const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined;
1543
+ // $flow-disable-line
1544
+ createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent);
1545
+
1546
+ // add aliases of parent
1547
+ if (parent && parent.alias.length) {
1548
+ createRouteMap(
1549
+ // $flow-disable-line route is defined if parent is
1550
+ parent.alias.map(alias => ({ path: alias, children: [route] })),
1551
+ pathList,
1552
+ pathMap,
1553
+ nameMap,
1554
+ parent
1555
+ );
1556
+ }
1557
+ }
1558
+
1559
+ function getRoutes () {
1560
+ return pathList.map(path => pathMap[path])
1561
+ }
1562
+
1563
+ function match (
1564
+ raw,
1565
+ currentRoute,
1566
+ redirectedFrom
1567
+ ) {
1568
+ const location = normalizeLocation(raw, currentRoute, false, router);
1569
+ const { name } = location;
1570
+
1571
+ if (name) {
1572
+ const record = nameMap[name];
1573
+ {
1574
+ warn(record, `Route with name '${name}' does not exist`);
1575
+ }
1576
+ if (!record) return _createRoute(null, location)
1577
+ const paramNames = record.regex.keys
1578
+ .filter(key => !key.optional)
1579
+ .map(key => key.name);
1580
+
1581
+ if (typeof location.params !== 'object') {
1582
+ location.params = {};
1583
+ }
1584
+
1585
+ if (currentRoute && typeof currentRoute.params === 'object') {
1586
+ for (const key in currentRoute.params) {
1587
+ if (!(key in location.params) && paramNames.indexOf(key) > -1) {
1588
+ location.params[key] = currentRoute.params[key];
1589
+ }
1590
+ }
1591
+ }
1592
+
1593
+ location.path = fillParams(record.path, location.params, `named route "${name}"`);
1594
+ return _createRoute(record, location, redirectedFrom)
1595
+ } else if (location.path) {
1596
+ location.params = {};
1597
+ for (let i = 0; i < pathList.length; i++) {
1598
+ const path = pathList[i];
1599
+ const record = pathMap[path];
1600
+ if (matchRoute(record.regex, location.path, location.params)) {
1601
+ return _createRoute(record, location, redirectedFrom)
1602
+ }
1603
+ }
1604
+ }
1605
+ // no match
1606
+ return _createRoute(null, location)
1607
+ }
1608
+
1609
+ function redirect (
1610
+ record,
1611
+ location
1612
+ ) {
1613
+ const originalRedirect = record.redirect;
1614
+ let redirect = typeof originalRedirect === 'function'
1615
+ ? originalRedirect(createRoute(record, location, null, router))
1616
+ : originalRedirect;
1617
+
1618
+ if (typeof redirect === 'string') {
1619
+ redirect = { path: redirect };
1620
+ }
1621
+
1622
+ if (!redirect || typeof redirect !== 'object') {
1623
+ {
1624
+ warn(
1625
+ false, `invalid redirect option: ${JSON.stringify(redirect)}`
1626
+ );
1627
+ }
1628
+ return _createRoute(null, location)
1629
+ }
1630
+
1631
+ const re = redirect;
1632
+ const { name, path } = re;
1633
+ let { query, hash, params } = location;
1634
+ query = re.hasOwnProperty('query') ? re.query : query;
1635
+ hash = re.hasOwnProperty('hash') ? re.hash : hash;
1636
+ params = re.hasOwnProperty('params') ? re.params : params;
1637
+
1638
+ if (name) {
1639
+ // resolved named direct
1640
+ const targetRecord = nameMap[name];
1641
+ {
1642
+ assert(targetRecord, `redirect failed: named route "${name}" not found.`);
1643
+ }
1644
+ return match({
1645
+ _normalized: true,
1646
+ name,
1647
+ query,
1648
+ hash,
1649
+ params
1650
+ }, undefined, location)
1651
+ } else if (path) {
1652
+ // 1. resolve relative redirect
1653
+ const rawPath = resolveRecordPath(path, record);
1654
+ // 2. resolve params
1655
+ const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`);
1656
+ // 3. rematch with existing query and hash
1657
+ return match({
1658
+ _normalized: true,
1659
+ path: resolvedPath,
1660
+ query,
1661
+ hash
1662
+ }, undefined, location)
1663
+ } else {
1664
+ {
1665
+ warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`);
1666
+ }
1667
+ return _createRoute(null, location)
1668
+ }
1669
+ }
1670
+
1671
+ function alias (
1672
+ record,
1673
+ location,
1674
+ matchAs
1675
+ ) {
1676
+ const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`);
1677
+ const aliasedMatch = match({
1678
+ _normalized: true,
1679
+ path: aliasedPath
1680
+ });
1681
+ if (aliasedMatch) {
1682
+ const matched = aliasedMatch.matched;
1683
+ const aliasedRecord = matched[matched.length - 1];
1684
+ location.params = aliasedMatch.params;
1685
+ return _createRoute(aliasedRecord, location)
1686
+ }
1687
+ return _createRoute(null, location)
1688
+ }
1689
+
1690
+ function _createRoute (
1691
+ record,
1692
+ location,
1693
+ redirectedFrom
1694
+ ) {
1695
+ if (record && record.redirect) {
1696
+ return redirect(record, redirectedFrom || location)
1697
+ }
1698
+ if (record && record.matchAs) {
1699
+ return alias(record, location, record.matchAs)
1700
+ }
1701
+ return createRoute(record, location, redirectedFrom, router)
1702
+ }
1703
+
1704
+ return {
1705
+ match,
1706
+ addRoute,
1707
+ getRoutes,
1708
+ addRoutes
1709
+ }
1710
+ }
1711
+
1712
+ function matchRoute (
1713
+ regex,
1714
+ path,
1715
+ params
1716
+ ) {
1717
+ const m = path.match(regex);
1718
+
1719
+ if (!m) {
1720
+ return false
1721
+ } else if (!params) {
1722
+ return true
1723
+ }
1724
+
1725
+ for (let i = 1, len = m.length; i < len; ++i) {
1726
+ const key = regex.keys[i - 1];
1727
+ if (key) {
1728
+ // Fix #1994: using * with props: true generates a param named 0
1729
+ params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i];
1730
+ }
1731
+ }
1732
+
1733
+ return true
1734
+ }
1735
+
1736
+ function resolveRecordPath (path, record) {
1737
+ return resolvePath(path, record.parent ? record.parent.path : '/', true)
1738
+ }
1739
+
1740
+ /* */
1741
+
1742
+ // use User Timing api (if present) for more accurate key precision
1743
+ const Time =
1744
+ inBrowser && window.performance && window.performance.now
1745
+ ? window.performance
1746
+ : Date;
1747
+
1748
+ function genStateKey () {
1749
+ return Time.now().toFixed(3)
1750
+ }
1751
+
1752
+ let _key = genStateKey();
1753
+
1754
+ function getStateKey () {
1755
+ return _key
1756
+ }
1757
+
1758
+ function setStateKey (key) {
1759
+ return (_key = key)
1760
+ }
1761
+
1762
+ /* */
1763
+
1764
+ const positionStore = Object.create(null);
1765
+
1766
+ function setupScroll () {
1767
+ // Prevent browser scroll behavior on History popstate
1768
+ if ('scrollRestoration' in window.history) {
1769
+ window.history.scrollRestoration = 'manual';
1770
+ }
1771
+ // Fix for #1585 for Firefox
1772
+ // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
1773
+ // Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
1774
+ // window.location.protocol + '//' + window.location.host
1775
+ // location.host contains the port and location.hostname doesn't
1776
+ const protocolAndPath = window.location.protocol + '//' + window.location.host;
1777
+ const absolutePath = window.location.href.replace(protocolAndPath, '');
1778
+ // preserve existing history state as it could be overriden by the user
1779
+ const stateCopy = extend({}, window.history.state);
1780
+ stateCopy.key = getStateKey();
1781
+ window.history.replaceState(stateCopy, '', absolutePath);
1782
+ window.addEventListener('popstate', handlePopState);
1783
+ return () => {
1784
+ window.removeEventListener('popstate', handlePopState);
1785
+ }
1786
+ }
1787
+
1788
+ function handleScroll (
1789
+ router,
1790
+ to,
1791
+ from,
1792
+ isPop
1793
+ ) {
1794
+ if (!router.app) {
1795
+ return
1796
+ }
1797
+
1798
+ const behavior = router.options.scrollBehavior;
1799
+ if (!behavior) {
1800
+ return
1801
+ }
1802
+
1803
+ {
1804
+ assert(typeof behavior === 'function', `scrollBehavior must be a function`);
1805
+ }
1806
+
1807
+ // wait until re-render finishes before scrolling
1808
+ router.app.$nextTick(() => {
1809
+ const position = getScrollPosition();
1810
+ const shouldScroll = behavior.call(
1811
+ router,
1812
+ to,
1813
+ from,
1814
+ isPop ? position : null
1815
+ );
1816
+
1817
+ if (!shouldScroll) {
1818
+ return
1819
+ }
1820
+
1821
+ if (typeof shouldScroll.then === 'function') {
1822
+ shouldScroll
1823
+ .then(shouldScroll => {
1824
+ scrollToPosition((shouldScroll), position);
1825
+ })
1826
+ .catch(err => {
1827
+ {
1828
+ assert(false, err.toString());
1829
+ }
1830
+ });
1831
+ } else {
1832
+ scrollToPosition(shouldScroll, position);
1833
+ }
1834
+ });
1835
+ }
1836
+
1837
+ function saveScrollPosition () {
1838
+ const key = getStateKey();
1839
+ if (key) {
1840
+ positionStore[key] = {
1841
+ x: window.pageXOffset,
1842
+ y: window.pageYOffset
1843
+ };
1844
+ }
1845
+ }
1846
+
1847
+ function handlePopState (e) {
1848
+ saveScrollPosition();
1849
+ if (e.state && e.state.key) {
1850
+ setStateKey(e.state.key);
1851
+ }
1852
+ }
1853
+
1854
+ function getScrollPosition () {
1855
+ const key = getStateKey();
1856
+ if (key) {
1857
+ return positionStore[key]
1858
+ }
1859
+ }
1860
+
1861
+ function getElementPosition (el, offset) {
1862
+ const docEl = document.documentElement;
1863
+ const docRect = docEl.getBoundingClientRect();
1864
+ const elRect = el.getBoundingClientRect();
1865
+ return {
1866
+ x: elRect.left - docRect.left - offset.x,
1867
+ y: elRect.top - docRect.top - offset.y
1868
+ }
1869
+ }
1870
+
1871
+ function isValidPosition (obj) {
1872
+ return isNumber(obj.x) || isNumber(obj.y)
1873
+ }
1874
+
1875
+ function normalizePosition (obj) {
1876
+ return {
1877
+ x: isNumber(obj.x) ? obj.x : window.pageXOffset,
1878
+ y: isNumber(obj.y) ? obj.y : window.pageYOffset
1879
+ }
1880
+ }
1881
+
1882
+ function normalizeOffset (obj) {
1883
+ return {
1884
+ x: isNumber(obj.x) ? obj.x : 0,
1885
+ y: isNumber(obj.y) ? obj.y : 0
1886
+ }
1887
+ }
1888
+
1889
+ function isNumber (v) {
1890
+ return typeof v === 'number'
1891
+ }
1892
+
1893
+ const hashStartsWithNumberRE = /^#\d/;
1894
+
1895
+ function scrollToPosition (shouldScroll, position) {
1896
+ const isObject = typeof shouldScroll === 'object';
1897
+ if (isObject && typeof shouldScroll.selector === 'string') {
1898
+ // getElementById would still fail if the selector contains a more complicated query like #main[data-attr]
1899
+ // but at the same time, it doesn't make much sense to select an element with an id and an extra selector
1900
+ const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line
1901
+ ? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line
1902
+ : document.querySelector(shouldScroll.selector);
1903
+
1904
+ if (el) {
1905
+ let offset =
1906
+ shouldScroll.offset && typeof shouldScroll.offset === 'object'
1907
+ ? shouldScroll.offset
1908
+ : {};
1909
+ offset = normalizeOffset(offset);
1910
+ position = getElementPosition(el, offset);
1911
+ } else if (isValidPosition(shouldScroll)) {
1912
+ position = normalizePosition(shouldScroll);
1913
+ }
1914
+ } else if (isObject && isValidPosition(shouldScroll)) {
1915
+ position = normalizePosition(shouldScroll);
1916
+ }
1917
+
1918
+ if (position) {
1919
+ // $flow-disable-line
1920
+ if ('scrollBehavior' in document.documentElement.style) {
1921
+ window.scrollTo({
1922
+ left: position.x,
1923
+ top: position.y,
1924
+ // $flow-disable-line
1925
+ behavior: shouldScroll.behavior
1926
+ });
1927
+ } else {
1928
+ window.scrollTo(position.x, position.y);
1929
+ }
1930
+ }
1931
+ }
1932
+
1933
+ /* */
1934
+
1935
+ const supportsPushState =
1936
+ inBrowser &&
1937
+ (function () {
1938
+ const ua = window.navigator.userAgent;
1939
+
1940
+ if (
1941
+ (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
1942
+ ua.indexOf('Mobile Safari') !== -1 &&
1943
+ ua.indexOf('Chrome') === -1 &&
1944
+ ua.indexOf('Windows Phone') === -1
1945
+ ) {
1946
+ return false
1947
+ }
1948
+
1949
+ return window.history && typeof window.history.pushState === 'function'
1950
+ })();
1951
+
1952
+ function pushState (url, replace) {
1953
+ saveScrollPosition();
1954
+ // try...catch the pushState call to get around Safari
1955
+ // DOM Exception 18 where it limits to 100 pushState calls
1956
+ const history = window.history;
1957
+ try {
1958
+ if (replace) {
1959
+ // preserve existing history state as it could be overriden by the user
1960
+ const stateCopy = extend({}, history.state);
1961
+ stateCopy.key = getStateKey();
1962
+ history.replaceState(stateCopy, '', url);
1963
+ } else {
1964
+ history.pushState({ key: setStateKey(genStateKey()) }, '', url);
1965
+ }
1966
+ } catch (e) {
1967
+ window.location[replace ? 'replace' : 'assign'](url);
1968
+ }
1969
+ }
1970
+
1971
+ function replaceState (url) {
1972
+ pushState(url, true);
1973
+ }
1974
+
1975
+ // When changing thing, also edit router.d.ts
1976
+ const NavigationFailureType = {
1977
+ redirected: 2,
1978
+ aborted: 4,
1979
+ cancelled: 8,
1980
+ duplicated: 16
1981
+ };
1982
+
1983
+ function createNavigationRedirectedError (from, to) {
1984
+ return createRouterError(
1985
+ from,
1986
+ to,
1987
+ NavigationFailureType.redirected,
1988
+ `Redirected when going from "${from.fullPath}" to "${stringifyRoute(
1989
+ to
1990
+ )}" via a navigation guard.`
1991
+ )
1992
+ }
1993
+
1994
+ function createNavigationDuplicatedError (from, to) {
1995
+ const error = createRouterError(
1996
+ from,
1997
+ to,
1998
+ NavigationFailureType.duplicated,
1999
+ `Avoided redundant navigation to current location: "${from.fullPath}".`
2000
+ );
2001
+ // backwards compatible with the first introduction of Errors
2002
+ error.name = 'NavigationDuplicated';
2003
+ return error
2004
+ }
2005
+
2006
+ function createNavigationCancelledError (from, to) {
2007
+ return createRouterError(
2008
+ from,
2009
+ to,
2010
+ NavigationFailureType.cancelled,
2011
+ `Navigation cancelled from "${from.fullPath}" to "${
2012
+ to.fullPath
2013
+ }" with a new navigation.`
2014
+ )
2015
+ }
2016
+
2017
+ function createNavigationAbortedError (from, to) {
2018
+ return createRouterError(
2019
+ from,
2020
+ to,
2021
+ NavigationFailureType.aborted,
2022
+ `Navigation aborted from "${from.fullPath}" to "${
2023
+ to.fullPath
2024
+ }" via a navigation guard.`
2025
+ )
2026
+ }
2027
+
2028
+ function createRouterError (from, to, type, message) {
2029
+ const error = new Error(message);
2030
+ error._isRouter = true;
2031
+ error.from = from;
2032
+ error.to = to;
2033
+ error.type = type;
2034
+
2035
+ return error
2036
+ }
2037
+
2038
+ const propertiesToLog = ['params', 'query', 'hash'];
2039
+
2040
+ function stringifyRoute (to) {
2041
+ if (typeof to === 'string') return to
2042
+ if ('path' in to) return to.path
2043
+ const location = {};
2044
+ propertiesToLog.forEach(key => {
2045
+ if (key in to) location[key] = to[key];
2046
+ });
2047
+ return JSON.stringify(location, null, 2)
2048
+ }
2049
+
2050
+ function isError (err) {
2051
+ return Object.prototype.toString.call(err).indexOf('Error') > -1
2052
+ }
2053
+
2054
+ function isNavigationFailure (err, errorType) {
2055
+ return (
2056
+ isError(err) &&
2057
+ err._isRouter &&
2058
+ (errorType == null || err.type === errorType)
2059
+ )
2060
+ }
2061
+
2062
+ /* */
2063
+
2064
+ function runQueue (queue, fn, cb) {
2065
+ const step = index => {
2066
+ if (index >= queue.length) {
2067
+ cb();
2068
+ } else {
2069
+ if (queue[index]) {
2070
+ fn(queue[index], () => {
2071
+ step(index + 1);
2072
+ });
2073
+ } else {
2074
+ step(index + 1);
2075
+ }
2076
+ }
2077
+ };
2078
+ step(0);
2079
+ }
2080
+
2081
+ /* */
2082
+
2083
+ function resolveAsyncComponents (matched) {
2084
+ return (to, from, next) => {
2085
+ let hasAsync = false;
2086
+ let pending = 0;
2087
+ let error = null;
2088
+
2089
+ flatMapComponents(matched, (def, _, match, key) => {
2090
+ // if it's a function and doesn't have cid attached,
2091
+ // assume it's an async component resolve function.
2092
+ // we are not using Kdu's default async resolving mechanism because
2093
+ // we want to halt the navigation until the incoming component has been
2094
+ // resolved.
2095
+ if (typeof def === 'function' && def.cid === undefined) {
2096
+ hasAsync = true;
2097
+ pending++;
2098
+
2099
+ const resolve = once(resolvedDef => {
2100
+ if (isESModule(resolvedDef)) {
2101
+ resolvedDef = resolvedDef.default;
2102
+ }
2103
+ // save resolved on async factory in case it's used elsewhere
2104
+ def.resolved = typeof resolvedDef === 'function'
2105
+ ? resolvedDef
2106
+ : _Kdu.extend(resolvedDef);
2107
+ match.components[key] = resolvedDef;
2108
+ pending--;
2109
+ if (pending <= 0) {
2110
+ next();
2111
+ }
2112
+ });
2113
+
2114
+ const reject = once(reason => {
2115
+ const msg = `Failed to resolve async component ${key}: ${reason}`;
2116
+ warn(false, msg);
2117
+ if (!error) {
2118
+ error = isError(reason)
2119
+ ? reason
2120
+ : new Error(msg);
2121
+ next(error);
2122
+ }
2123
+ });
2124
+
2125
+ let res;
2126
+ try {
2127
+ res = def(resolve, reject);
2128
+ } catch (e) {
2129
+ reject(e);
2130
+ }
2131
+ if (res) {
2132
+ if (typeof res.then === 'function') {
2133
+ res.then(resolve, reject);
2134
+ } else {
2135
+ // new syntax in Kdu 2.3
2136
+ const comp = res.component;
2137
+ if (comp && typeof comp.then === 'function') {
2138
+ comp.then(resolve, reject);
2139
+ }
2140
+ }
2141
+ }
2142
+ }
2143
+ });
2144
+
2145
+ if (!hasAsync) next();
2146
+ }
2147
+ }
2148
+
2149
+ function flatMapComponents (
2150
+ matched,
2151
+ fn
2152
+ ) {
2153
+ return flatten(matched.map(m => {
2154
+ return Object.keys(m.components).map(key => fn(
2155
+ m.components[key],
2156
+ m.instances[key],
2157
+ m, key
2158
+ ))
2159
+ }))
2160
+ }
2161
+
2162
+ function flatten (arr) {
2163
+ return Array.prototype.concat.apply([], arr)
2164
+ }
2165
+
2166
+ const hasSymbol =
2167
+ typeof Symbol === 'function' &&
2168
+ typeof Symbol.toStringTag === 'symbol';
2169
+
2170
+ function isESModule (obj) {
2171
+ return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
2172
+ }
2173
+
2174
+ // in Webpack 2, require.ensure now also returns a Promise
2175
+ // so the resolve/reject functions may get called an extra time
2176
+ // if the user uses an arrow function shorthand that happens to
2177
+ // return that Promise.
2178
+ function once (fn) {
2179
+ let called = false;
2180
+ return function (...args) {
2181
+ if (called) return
2182
+ called = true;
2183
+ return fn.apply(this, args)
2184
+ }
2185
+ }
2186
+
2187
+ /* */
2188
+
2189
+ class History {
2190
+
2191
+
2192
+
2193
+
2194
+
2195
+
2196
+
2197
+
2198
+
2199
+
2200
+
2201
+
2202
+ // implemented by sub-classes
2203
+
2204
+
2205
+
2206
+
2207
+
2208
+
2209
+
2210
+ constructor (router, base) {
2211
+ this.router = router;
2212
+ this.base = normalizeBase(base);
2213
+ // start with a route object that stands for "nowhere"
2214
+ this.current = START;
2215
+ this.pending = null;
2216
+ this.ready = false;
2217
+ this.readyCbs = [];
2218
+ this.readyErrorCbs = [];
2219
+ this.errorCbs = [];
2220
+ this.listeners = [];
2221
+ }
2222
+
2223
+ listen (cb) {
2224
+ this.cb = cb;
2225
+ }
2226
+
2227
+ onReady (cb, errorCb) {
2228
+ if (this.ready) {
2229
+ cb();
2230
+ } else {
2231
+ this.readyCbs.push(cb);
2232
+ if (errorCb) {
2233
+ this.readyErrorCbs.push(errorCb);
2234
+ }
2235
+ }
2236
+ }
2237
+
2238
+ onError (errorCb) {
2239
+ this.errorCbs.push(errorCb);
2240
+ }
2241
+
2242
+ transitionTo (
2243
+ location,
2244
+ onComplete,
2245
+ onAbort
2246
+ ) {
2247
+ let route;
2248
+ // catch redirect option
2249
+ try {
2250
+ route = this.router.match(location, this.current);
2251
+ } catch (e) {
2252
+ this.errorCbs.forEach(cb => {
2253
+ cb(e);
2254
+ });
2255
+ // Exception should still be thrown
2256
+ throw e
2257
+ }
2258
+ const prev = this.current;
2259
+ this.confirmTransition(
2260
+ route,
2261
+ () => {
2262
+ this.updateRoute(route);
2263
+ onComplete && onComplete(route);
2264
+ this.ensureURL();
2265
+ this.router.afterHooks.forEach(hook => {
2266
+ hook && hook(route, prev);
2267
+ });
2268
+
2269
+ // fire ready cbs once
2270
+ if (!this.ready) {
2271
+ this.ready = true;
2272
+ this.readyCbs.forEach(cb => {
2273
+ cb(route);
2274
+ });
2275
+ }
2276
+ },
2277
+ err => {
2278
+ if (onAbort) {
2279
+ onAbort(err);
2280
+ }
2281
+ if (err && !this.ready) {
2282
+ // Initial redirection should not mark the history as ready yet
2283
+ // because it's triggered by the redirection instead
2284
+ if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
2285
+ this.ready = true;
2286
+ this.readyErrorCbs.forEach(cb => {
2287
+ cb(err);
2288
+ });
2289
+ }
2290
+ }
2291
+ }
2292
+ );
2293
+ }
2294
+
2295
+ confirmTransition (route, onComplete, onAbort) {
2296
+ const current = this.current;
2297
+ this.pending = route;
2298
+ const abort = err => {
2299
+ // changed after adding errors
2300
+ // before that change, redirect and aborted navigation would produce an err == null
2301
+ if (!isNavigationFailure(err) && isError(err)) {
2302
+ if (this.errorCbs.length) {
2303
+ this.errorCbs.forEach(cb => {
2304
+ cb(err);
2305
+ });
2306
+ } else {
2307
+ {
2308
+ warn(false, 'uncaught error during route navigation:');
2309
+ }
2310
+ console.error(err);
2311
+ }
2312
+ }
2313
+ onAbort && onAbort(err);
2314
+ };
2315
+ const lastRouteIndex = route.matched.length - 1;
2316
+ const lastCurrentIndex = current.matched.length - 1;
2317
+ if (
2318
+ isSameRoute(route, current) &&
2319
+ // in the case the route map has been dynamically appended to
2320
+ lastRouteIndex === lastCurrentIndex &&
2321
+ route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
2322
+ ) {
2323
+ this.ensureURL();
2324
+ if (route.hash) {
2325
+ handleScroll(this.router, current, route, false);
2326
+ }
2327
+ return abort(createNavigationDuplicatedError(current, route))
2328
+ }
2329
+
2330
+ const { updated, deactivated, activated } = resolveQueue(
2331
+ this.current.matched,
2332
+ route.matched
2333
+ );
2334
+
2335
+ const queue = [].concat(
2336
+ // in-component leave guards
2337
+ extractLeaveGuards(deactivated),
2338
+ // global before hooks
2339
+ this.router.beforeHooks,
2340
+ // in-component update hooks
2341
+ extractUpdateHooks(updated),
2342
+ // in-config enter guards
2343
+ activated.map(m => m.beforeEnter),
2344
+ // async components
2345
+ resolveAsyncComponents(activated)
2346
+ );
2347
+
2348
+ const iterator = (hook, next) => {
2349
+ if (this.pending !== route) {
2350
+ return abort(createNavigationCancelledError(current, route))
2351
+ }
2352
+ try {
2353
+ hook(route, current, (to) => {
2354
+ if (to === false) {
2355
+ // next(false) -> abort navigation, ensure current URL
2356
+ this.ensureURL(true);
2357
+ abort(createNavigationAbortedError(current, route));
2358
+ } else if (isError(to)) {
2359
+ this.ensureURL(true);
2360
+ abort(to);
2361
+ } else if (
2362
+ typeof to === 'string' ||
2363
+ (typeof to === 'object' &&
2364
+ (typeof to.path === 'string' || typeof to.name === 'string'))
2365
+ ) {
2366
+ // next('/') or next({ path: '/' }) -> redirect
2367
+ abort(createNavigationRedirectedError(current, route));
2368
+ if (typeof to === 'object' && to.replace) {
2369
+ this.replace(to);
2370
+ } else {
2371
+ this.push(to);
2372
+ }
2373
+ } else {
2374
+ // confirm transition and pass on the value
2375
+ next(to);
2376
+ }
2377
+ });
2378
+ } catch (e) {
2379
+ abort(e);
2380
+ }
2381
+ };
2382
+
2383
+ runQueue(queue, iterator, () => {
2384
+ // wait until async components are resolved before
2385
+ // extracting in-component enter guards
2386
+ const enterGuards = extractEnterGuards(activated);
2387
+ const queue = enterGuards.concat(this.router.resolveHooks);
2388
+ runQueue(queue, iterator, () => {
2389
+ if (this.pending !== route) {
2390
+ return abort(createNavigationCancelledError(current, route))
2391
+ }
2392
+ this.pending = null;
2393
+ onComplete(route);
2394
+ if (this.router.app) {
2395
+ this.router.app.$nextTick(() => {
2396
+ handleRouteEntered(route);
2397
+ });
2398
+ }
2399
+ });
2400
+ });
2401
+ }
2402
+
2403
+ updateRoute (route) {
2404
+ this.current = route;
2405
+ this.cb && this.cb(route);
2406
+ }
2407
+
2408
+ setupListeners () {
2409
+ // Default implementation is empty
2410
+ }
2411
+
2412
+ teardown () {
2413
+ // clean up event listeners
2414
+ this.listeners.forEach(cleanupListener => {
2415
+ cleanupListener();
2416
+ });
2417
+ this.listeners = [];
2418
+
2419
+ // reset current history route
2420
+ this.current = START;
2421
+ this.pending = null;
2422
+ }
2423
+ }
2424
+
2425
+ function normalizeBase (base) {
2426
+ if (!base) {
2427
+ if (inBrowser) {
2428
+ // respect <base> tag
2429
+ const baseEl = document.querySelector('base');
2430
+ base = (baseEl && baseEl.getAttribute('href')) || '/';
2431
+ // strip full URL origin
2432
+ base = base.replace(/^https?:\/\/[^\/]+/, '');
2433
+ } else {
2434
+ base = '/';
2435
+ }
2436
+ }
2437
+ // make sure there's the starting slash
2438
+ if (base.charAt(0) !== '/') {
2439
+ base = '/' + base;
2440
+ }
2441
+ // remove trailing slash
2442
+ return base.replace(/\/$/, '')
2443
+ }
2444
+
2445
+ function resolveQueue (
2446
+ current,
2447
+ next
2448
+ ) {
2449
+ let i;
2450
+ const max = Math.max(current.length, next.length);
2451
+ for (i = 0; i < max; i++) {
2452
+ if (current[i] !== next[i]) {
2453
+ break
2454
+ }
2455
+ }
2456
+ return {
2457
+ updated: next.slice(0, i),
2458
+ activated: next.slice(i),
2459
+ deactivated: current.slice(i)
2460
+ }
2461
+ }
2462
+
2463
+ function extractGuards (
2464
+ records,
2465
+ name,
2466
+ bind,
2467
+ reverse
2468
+ ) {
2469
+ const guards = flatMapComponents(records, (def, instance, match, key) => {
2470
+ const guard = extractGuard(def, name);
2471
+ if (guard) {
2472
+ return Array.isArray(guard)
2473
+ ? guard.map(guard => bind(guard, instance, match, key))
2474
+ : bind(guard, instance, match, key)
2475
+ }
2476
+ });
2477
+ return flatten(reverse ? guards.reverse() : guards)
2478
+ }
2479
+
2480
+ function extractGuard (
2481
+ def,
2482
+ key
2483
+ ) {
2484
+ if (typeof def !== 'function') {
2485
+ // extend now so that global mixins are applied.
2486
+ def = _Kdu.extend(def);
2487
+ }
2488
+ return def.options[key]
2489
+ }
2490
+
2491
+ function extractLeaveGuards (deactivated) {
2492
+ return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
2493
+ }
2494
+
2495
+ function extractUpdateHooks (updated) {
2496
+ return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
2497
+ }
2498
+
2499
+ function bindGuard (guard, instance) {
2500
+ if (instance) {
2501
+ return function boundRouteGuard () {
2502
+ return guard.apply(instance, arguments)
2503
+ }
2504
+ }
2505
+ }
2506
+
2507
+ function extractEnterGuards (
2508
+ activated
2509
+ ) {
2510
+ return extractGuards(
2511
+ activated,
2512
+ 'beforeRouteEnter',
2513
+ (guard, _, match, key) => {
2514
+ return bindEnterGuard(guard, match, key)
2515
+ }
2516
+ )
2517
+ }
2518
+
2519
+ function bindEnterGuard (
2520
+ guard,
2521
+ match,
2522
+ key
2523
+ ) {
2524
+ return function routeEnterGuard (to, from, next) {
2525
+ return guard(to, from, cb => {
2526
+ if (typeof cb === 'function') {
2527
+ if (!match.enteredCbs[key]) {
2528
+ match.enteredCbs[key] = [];
2529
+ }
2530
+ match.enteredCbs[key].push(cb);
2531
+ }
2532
+ next(cb);
2533
+ })
2534
+ }
2535
+ }
2536
+
2537
+ /* */
2538
+
2539
+ class HTML5History extends History {
2540
+
2541
+
2542
+ constructor (router, base) {
2543
+ super(router, base);
2544
+
2545
+ this._startLocation = getLocation(this.base);
2546
+ }
2547
+
2548
+ setupListeners () {
2549
+ if (this.listeners.length > 0) {
2550
+ return
2551
+ }
2552
+
2553
+ const router = this.router;
2554
+ const expectScroll = router.options.scrollBehavior;
2555
+ const supportsScroll = supportsPushState && expectScroll;
2556
+
2557
+ if (supportsScroll) {
2558
+ this.listeners.push(setupScroll());
2559
+ }
2560
+
2561
+ const handleRoutingEvent = () => {
2562
+ const current = this.current;
2563
+
2564
+ // Avoiding first `popstate` event dispatched in some browsers but first
2565
+ // history route not updated since async guard at the same time.
2566
+ const location = getLocation(this.base);
2567
+ if (this.current === START && location === this._startLocation) {
2568
+ return
2569
+ }
2570
+
2571
+ this.transitionTo(location, route => {
2572
+ if (supportsScroll) {
2573
+ handleScroll(router, route, current, true);
2574
+ }
2575
+ });
2576
+ };
2577
+ window.addEventListener('popstate', handleRoutingEvent);
2578
+ this.listeners.push(() => {
2579
+ window.removeEventListener('popstate', handleRoutingEvent);
2580
+ });
2581
+ }
2582
+
2583
+ go (n) {
2584
+ window.history.go(n);
2585
+ }
2586
+
2587
+ push (location, onComplete, onAbort) {
2588
+ const { current: fromRoute } = this;
2589
+ this.transitionTo(location, route => {
2590
+ pushState(cleanPath(this.base + route.fullPath));
2591
+ handleScroll(this.router, route, fromRoute, false);
2592
+ onComplete && onComplete(route);
2593
+ }, onAbort);
2594
+ }
2595
+
2596
+ replace (location, onComplete, onAbort) {
2597
+ const { current: fromRoute } = this;
2598
+ this.transitionTo(location, route => {
2599
+ replaceState(cleanPath(this.base + route.fullPath));
2600
+ handleScroll(this.router, route, fromRoute, false);
2601
+ onComplete && onComplete(route);
2602
+ }, onAbort);
2603
+ }
2604
+
2605
+ ensureURL (push) {
2606
+ if (getLocation(this.base) !== this.current.fullPath) {
2607
+ const current = cleanPath(this.base + this.current.fullPath);
2608
+ push ? pushState(current) : replaceState(current);
2609
+ }
2610
+ }
2611
+
2612
+ getCurrentLocation () {
2613
+ return getLocation(this.base)
2614
+ }
2615
+ }
2616
+
2617
+ function getLocation (base) {
2618
+ let path = window.location.pathname;
2619
+ const pathLowerCase = path.toLowerCase();
2620
+ const baseLowerCase = base.toLowerCase();
2621
+ // base="/a" shouldn't turn path="/app" into "/a/pp"
2622
+ // so we ensure the trailing slash in the base
2623
+ if (base && ((pathLowerCase === baseLowerCase) ||
2624
+ (pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
2625
+ path = path.slice(base.length);
2626
+ }
2627
+ return (path || '/') + window.location.search + window.location.hash
2628
+ }
2629
+
2630
+ /* */
2631
+
2632
+ class HashHistory extends History {
2633
+ constructor (router, base, fallback) {
2634
+ super(router, base);
2635
+ // check history fallback deeplinking
2636
+ if (fallback && checkFallback(this.base)) {
2637
+ return
2638
+ }
2639
+ ensureSlash();
2640
+ }
2641
+
2642
+ // this is delayed until the app mounts
2643
+ // to avoid the hashchange listener being fired too early
2644
+ setupListeners () {
2645
+ if (this.listeners.length > 0) {
2646
+ return
2647
+ }
2648
+
2649
+ const router = this.router;
2650
+ const expectScroll = router.options.scrollBehavior;
2651
+ const supportsScroll = supportsPushState && expectScroll;
2652
+
2653
+ if (supportsScroll) {
2654
+ this.listeners.push(setupScroll());
2655
+ }
2656
+
2657
+ const handleRoutingEvent = () => {
2658
+ const current = this.current;
2659
+ if (!ensureSlash()) {
2660
+ return
2661
+ }
2662
+ this.transitionTo(getHash(), route => {
2663
+ if (supportsScroll) {
2664
+ handleScroll(this.router, route, current, true);
2665
+ }
2666
+ if (!supportsPushState) {
2667
+ replaceHash(route.fullPath);
2668
+ }
2669
+ });
2670
+ };
2671
+ const eventType = supportsPushState ? 'popstate' : 'hashchange';
2672
+ window.addEventListener(
2673
+ eventType,
2674
+ handleRoutingEvent
2675
+ );
2676
+ this.listeners.push(() => {
2677
+ window.removeEventListener(eventType, handleRoutingEvent);
2678
+ });
2679
+ }
2680
+
2681
+ push (location, onComplete, onAbort) {
2682
+ const { current: fromRoute } = this;
2683
+ this.transitionTo(
2684
+ location,
2685
+ route => {
2686
+ pushHash(route.fullPath);
2687
+ handleScroll(this.router, route, fromRoute, false);
2688
+ onComplete && onComplete(route);
2689
+ },
2690
+ onAbort
2691
+ );
2692
+ }
2693
+
2694
+ replace (location, onComplete, onAbort) {
2695
+ const { current: fromRoute } = this;
2696
+ this.transitionTo(
2697
+ location,
2698
+ route => {
2699
+ replaceHash(route.fullPath);
2700
+ handleScroll(this.router, route, fromRoute, false);
2701
+ onComplete && onComplete(route);
2702
+ },
2703
+ onAbort
2704
+ );
2705
+ }
2706
+
2707
+ go (n) {
2708
+ window.history.go(n);
2709
+ }
2710
+
2711
+ ensureURL (push) {
2712
+ const current = this.current.fullPath;
2713
+ if (getHash() !== current) {
2714
+ push ? pushHash(current) : replaceHash(current);
2715
+ }
2716
+ }
2717
+
2718
+ getCurrentLocation () {
2719
+ return getHash()
2720
+ }
2721
+ }
2722
+
2723
+ function checkFallback (base) {
2724
+ const location = getLocation(base);
2725
+ if (!/^\/#/.test(location)) {
2726
+ window.location.replace(cleanPath(base + '/#' + location));
2727
+ return true
2728
+ }
2729
+ }
2730
+
2731
+ function ensureSlash () {
2732
+ const path = getHash();
2733
+ if (path.charAt(0) === '/') {
2734
+ return true
2735
+ }
2736
+ replaceHash('/' + path);
2737
+ return false
2738
+ }
2739
+
2740
+ function getHash () {
2741
+ // We can't use window.location.hash here because it's not
2742
+ // consistent across browsers - Firefox will pre-decode it!
2743
+ let href = window.location.href;
2744
+ const index = href.indexOf('#');
2745
+ // empty path
2746
+ if (index < 0) return ''
2747
+
2748
+ href = href.slice(index + 1);
2749
+
2750
+ return href
2751
+ }
2752
+
2753
+ function getUrl (path) {
2754
+ const href = window.location.href;
2755
+ const i = href.indexOf('#');
2756
+ const base = i >= 0 ? href.slice(0, i) : href;
2757
+ return `${base}#${path}`
2758
+ }
2759
+
2760
+ function pushHash (path) {
2761
+ if (supportsPushState) {
2762
+ pushState(getUrl(path));
2763
+ } else {
2764
+ window.location.hash = path;
2765
+ }
2766
+ }
2767
+
2768
+ function replaceHash (path) {
2769
+ if (supportsPushState) {
2770
+ replaceState(getUrl(path));
2771
+ } else {
2772
+ window.location.replace(getUrl(path));
2773
+ }
2774
+ }
2775
+
2776
+ /* */
2777
+
2778
+ class AbstractHistory extends History {
2779
+
2780
+
2781
+
2782
+ constructor (router, base) {
2783
+ super(router, base);
2784
+ this.stack = [];
2785
+ this.index = -1;
2786
+ }
2787
+
2788
+ push (location, onComplete, onAbort) {
2789
+ this.transitionTo(
2790
+ location,
2791
+ route => {
2792
+ this.stack = this.stack.slice(0, this.index + 1).concat(route);
2793
+ this.index++;
2794
+ onComplete && onComplete(route);
2795
+ },
2796
+ onAbort
2797
+ );
2798
+ }
2799
+
2800
+ replace (location, onComplete, onAbort) {
2801
+ this.transitionTo(
2802
+ location,
2803
+ route => {
2804
+ this.stack = this.stack.slice(0, this.index).concat(route);
2805
+ onComplete && onComplete(route);
2806
+ },
2807
+ onAbort
2808
+ );
2809
+ }
2810
+
2811
+ go (n) {
2812
+ const targetIndex = this.index + n;
2813
+ if (targetIndex < 0 || targetIndex >= this.stack.length) {
2814
+ return
2815
+ }
2816
+ const route = this.stack[targetIndex];
2817
+ this.confirmTransition(
2818
+ route,
2819
+ () => {
2820
+ const prev = this.current;
2821
+ this.index = targetIndex;
2822
+ this.updateRoute(route);
2823
+ this.router.afterHooks.forEach(hook => {
2824
+ hook && hook(route, prev);
2825
+ });
2826
+ },
2827
+ err => {
2828
+ if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
2829
+ this.index = targetIndex;
2830
+ }
2831
+ }
2832
+ );
2833
+ }
2834
+
2835
+ getCurrentLocation () {
2836
+ const current = this.stack[this.stack.length - 1];
2837
+ return current ? current.fullPath : '/'
2838
+ }
2839
+
2840
+ ensureURL () {
2841
+ // noop
2842
+ }
2843
+ }
2844
+
2845
+ /* */
2846
+
2847
+
2848
+
2849
+ class KduRouter {
2850
+
2851
+
2852
+
2853
+
2854
+
2855
+
2856
+
2857
+
2858
+
2859
+
2860
+
2861
+
2862
+
2863
+
2864
+
2865
+
2866
+
2867
+
2868
+
2869
+ constructor (options = {}) {
2870
+ {
2871
+ warn(this instanceof KduRouter, `Router must be called with the new operator.`);
2872
+ }
2873
+ this.app = null;
2874
+ this.apps = [];
2875
+ this.options = options;
2876
+ this.beforeHooks = [];
2877
+ this.resolveHooks = [];
2878
+ this.afterHooks = [];
2879
+ this.matcher = createMatcher(options.routes || [], this);
2880
+
2881
+ let mode = options.mode || 'hash';
2882
+ this.fallback =
2883
+ mode === 'history' && !supportsPushState && options.fallback !== false;
2884
+ if (this.fallback) {
2885
+ mode = 'hash';
2886
+ }
2887
+ if (!inBrowser) {
2888
+ mode = 'abstract';
2889
+ }
2890
+ this.mode = mode;
2891
+
2892
+ switch (mode) {
2893
+ case 'history':
2894
+ this.history = new HTML5History(this, options.base);
2895
+ break
2896
+ case 'hash':
2897
+ this.history = new HashHistory(this, options.base, this.fallback);
2898
+ break
2899
+ case 'abstract':
2900
+ this.history = new AbstractHistory(this, options.base);
2901
+ break
2902
+ default:
2903
+ {
2904
+ assert(false, `invalid mode: ${mode}`);
2905
+ }
2906
+ }
2907
+ }
2908
+
2909
+ match (raw, current, redirectedFrom) {
2910
+ return this.matcher.match(raw, current, redirectedFrom)
2911
+ }
2912
+
2913
+ get currentRoute () {
2914
+ return this.history && this.history.current
2915
+ }
2916
+
2917
+ init (app /* Kdu component instance */) {
2918
+ assert(
2919
+ install.installed,
2920
+ `not installed. Make sure to call \`Kdu.use(KduRouter)\` ` +
2921
+ `before creating root instance.`
2922
+ );
2923
+
2924
+ this.apps.push(app);
2925
+
2926
+ // set up app destroyed handler
2927
+ app.$once('hook:destroyed', () => {
2928
+ // clean out app from this.apps array once destroyed
2929
+ const index = this.apps.indexOf(app);
2930
+ if (index > -1) this.apps.splice(index, 1);
2931
+ // ensure we still have a main app or null if no apps
2932
+ // we do not release the router so it can be reused
2933
+ if (this.app === app) this.app = this.apps[0] || null;
2934
+
2935
+ if (!this.app) this.history.teardown();
2936
+ });
2937
+
2938
+ // main app previously initialized
2939
+ // return as we don't need to set up new history listener
2940
+ if (this.app) {
2941
+ return
2942
+ }
2943
+
2944
+ this.app = app;
2945
+
2946
+ const history = this.history;
2947
+
2948
+ if (history instanceof HTML5History || history instanceof HashHistory) {
2949
+ const handleInitialScroll = routeOrError => {
2950
+ const from = history.current;
2951
+ const expectScroll = this.options.scrollBehavior;
2952
+ const supportsScroll = supportsPushState && expectScroll;
2953
+
2954
+ if (supportsScroll && 'fullPath' in routeOrError) {
2955
+ handleScroll(this, routeOrError, from, false);
2956
+ }
2957
+ };
2958
+ const setupListeners = routeOrError => {
2959
+ history.setupListeners();
2960
+ handleInitialScroll(routeOrError);
2961
+ };
2962
+ history.transitionTo(
2963
+ history.getCurrentLocation(),
2964
+ setupListeners,
2965
+ setupListeners
2966
+ );
2967
+ }
2968
+
2969
+ history.listen(route => {
2970
+ this.apps.forEach(app => {
2971
+ app._route = route;
2972
+ });
2973
+ });
2974
+ }
2975
+
2976
+ beforeEach (fn) {
2977
+ return registerHook(this.beforeHooks, fn)
2978
+ }
2979
+
2980
+ beforeResolve (fn) {
2981
+ return registerHook(this.resolveHooks, fn)
2982
+ }
2983
+
2984
+ afterEach (fn) {
2985
+ return registerHook(this.afterHooks, fn)
2986
+ }
2987
+
2988
+ onReady (cb, errorCb) {
2989
+ this.history.onReady(cb, errorCb);
2990
+ }
2991
+
2992
+ onError (errorCb) {
2993
+ this.history.onError(errorCb);
2994
+ }
2995
+
2996
+ push (location, onComplete, onAbort) {
2997
+ // $flow-disable-line
2998
+ if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
2999
+ return new Promise((resolve, reject) => {
3000
+ this.history.push(location, resolve, reject);
3001
+ })
3002
+ } else {
3003
+ this.history.push(location, onComplete, onAbort);
3004
+ }
3005
+ }
3006
+
3007
+ replace (location, onComplete, onAbort) {
3008
+ // $flow-disable-line
3009
+ if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
3010
+ return new Promise((resolve, reject) => {
3011
+ this.history.replace(location, resolve, reject);
3012
+ })
3013
+ } else {
3014
+ this.history.replace(location, onComplete, onAbort);
3015
+ }
3016
+ }
3017
+
3018
+ go (n) {
3019
+ this.history.go(n);
3020
+ }
3021
+
3022
+ back () {
3023
+ this.go(-1);
3024
+ }
3025
+
3026
+ forward () {
3027
+ this.go(1);
3028
+ }
3029
+
3030
+ getMatchedComponents (to) {
3031
+ const route = to
3032
+ ? to.matched
3033
+ ? to
3034
+ : this.resolve(to).route
3035
+ : this.currentRoute;
3036
+ if (!route) {
3037
+ return []
3038
+ }
3039
+ return [].concat.apply(
3040
+ [],
3041
+ route.matched.map(m => {
3042
+ return Object.keys(m.components).map(key => {
3043
+ return m.components[key]
3044
+ })
3045
+ })
3046
+ )
3047
+ }
3048
+
3049
+ resolve (
3050
+ to,
3051
+ current,
3052
+ append
3053
+ ) {
3054
+ current = current || this.history.current;
3055
+ const location = normalizeLocation(to, current, append, this);
3056
+ const route = this.match(location, current);
3057
+ const fullPath = route.redirectedFrom || route.fullPath;
3058
+ const base = this.history.base;
3059
+ const href = createHref(base, fullPath, this.mode);
3060
+ return {
3061
+ location,
3062
+ route,
3063
+ href,
3064
+ // for backwards compat
3065
+ normalizedTo: location,
3066
+ resolved: route
3067
+ }
3068
+ }
3069
+
3070
+ getRoutes () {
3071
+ return this.matcher.getRoutes()
3072
+ }
3073
+
3074
+ addRoute (parentOrRoute, route) {
3075
+ this.matcher.addRoute(parentOrRoute, route);
3076
+ if (this.history.current !== START) {
3077
+ this.history.transitionTo(this.history.getCurrentLocation());
3078
+ }
3079
+ }
3080
+
3081
+ addRoutes (routes) {
3082
+ {
3083
+ warn(false, 'router.addRoutes() is deprecated and has been removed in Kdu Router 4. Use router.addRoute() instead.');
3084
+ }
3085
+ this.matcher.addRoutes(routes);
3086
+ if (this.history.current !== START) {
3087
+ this.history.transitionTo(this.history.getCurrentLocation());
3088
+ }
3089
+ }
3090
+ }
3091
+
3092
+ function registerHook (list, fn) {
3093
+ list.push(fn);
3094
+ return () => {
3095
+ const i = list.indexOf(fn);
3096
+ if (i > -1) list.splice(i, 1);
3097
+ }
3098
+ }
3099
+
3100
+ function createHref (base, fullPath, mode) {
3101
+ var path = mode === 'hash' ? '#' + fullPath : fullPath;
3102
+ return base ? cleanPath(base + '/' + path) : path
3103
+ }
3104
+
3105
+ if (inBrowser && window.Kdu) {
3106
+ window.Kdu.use(KduRouter);
3107
+ }
3108
+
3109
+ // We cannot remove this as it would be a breaking change
3110
+ KduRouter.install = install;
3111
+ KduRouter.version = '3.6.0';
3112
+ KduRouter.isNavigationFailure = isNavigationFailure;
3113
+ KduRouter.NavigationFailureType = NavigationFailureType;
3114
+ KduRouter.START_LOCATION = START;
3115
+
3116
+ const version = '3.6.0';
3004
3117
 
3005
3118
  export default KduRouter;
3119
+ export { NavigationFailureType, Link as RouterLink, View as RouterView, START as START_LOCATION, isNavigationFailure, version };