kdu-router 3.0.7 → 3.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/kdu-router.common.js +729 -486
- package/dist/kdu-router.esm.browser.js +712 -477
- package/dist/kdu-router.esm.browser.min.js +3 -3
- package/dist/kdu-router.esm.js +729 -486
- package/dist/kdu-router.js +2591 -2348
- package/dist/kdu-router.min.js +3 -3
- package/package.json +8 -8
- package/src/components/link.js +190 -0
- package/src/components/view.js +149 -0
- package/src/create-route-map.js +69 -35
- package/src/history/abstract.js +69 -0
- package/src/history/base.js +352 -0
- package/src/history/errors.js +22 -0
- package/src/history/hash.js +154 -0
- package/src/history/html5.js +80 -0
- package/src/index.js +16 -2
- package/src/util/async.js +18 -0
- package/src/util/dom.js +3 -0
- package/src/util/location.js +69 -0
- package/src/util/misc.js +6 -0
- package/src/util/params.js +37 -0
- package/src/util/path.js +74 -0
- package/src/util/push-state.js +46 -0
- package/src/util/query.js +95 -0
- package/src/util/resolve-components.js +108 -0
- package/src/util/route.js +132 -0
- package/src/util/scroll.js +156 -0
- package/src/util/state-key.js +22 -0
- package/src/util/warn.js +25 -0
- package/types/index.d.ts +4 -4
- package/types/kdu.d.ts +10 -10
- package/types/router.d.ts +104 -86
package/src/create-route-map.js
CHANGED
|
@@ -10,9 +10,9 @@ export function createRouteMap (
|
|
|
10
10
|
oldPathMap?: Dictionary<RouteRecord>,
|
|
11
11
|
oldNameMap?: Dictionary<RouteRecord>
|
|
12
12
|
): {
|
|
13
|
-
pathList: Array<string
|
|
14
|
-
pathMap: Dictionary<RouteRecord
|
|
15
|
-
nameMap: Dictionary<RouteRecord
|
|
13
|
+
pathList: Array<string>,
|
|
14
|
+
pathMap: Dictionary<RouteRecord>,
|
|
15
|
+
nameMap: Dictionary<RouteRecord>
|
|
16
16
|
} {
|
|
17
17
|
// the path list is used to control path matching priority
|
|
18
18
|
const pathList: Array<string> = oldPathList || []
|
|
@@ -34,6 +34,18 @@ export function createRouteMap (
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
if (process.env.NODE_ENV === 'development') {
|
|
38
|
+
// warn if routes do not include leading slashes
|
|
39
|
+
const found = pathList
|
|
40
|
+
// check for missing leading slash
|
|
41
|
+
.filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/')
|
|
42
|
+
|
|
43
|
+
if (found.length > 0) {
|
|
44
|
+
const pathNames = found.map(path => `- ${path}`).join('\n')
|
|
45
|
+
warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
return {
|
|
38
50
|
pathList,
|
|
39
51
|
pathMap,
|
|
@@ -54,17 +66,15 @@ function addRouteRecord (
|
|
|
54
66
|
assert(path != null, `"path" is required in a route configuration.`)
|
|
55
67
|
assert(
|
|
56
68
|
typeof route.component !== 'string',
|
|
57
|
-
`route config "component" for path: ${String(
|
|
58
|
-
|
|
69
|
+
`route config "component" for path: ${String(
|
|
70
|
+
path || name
|
|
71
|
+
)} cannot be a ` + `string id. Use an actual component instead.`
|
|
59
72
|
)
|
|
60
73
|
}
|
|
61
74
|
|
|
62
|
-
const pathToRegexpOptions: PathToRegexpOptions =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
parent,
|
|
66
|
-
pathToRegexpOptions.strict
|
|
67
|
-
)
|
|
75
|
+
const pathToRegexpOptions: PathToRegexpOptions =
|
|
76
|
+
route.pathToRegexpOptions || {}
|
|
77
|
+
const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
|
|
68
78
|
|
|
69
79
|
if (typeof route.caseSensitive === 'boolean') {
|
|
70
80
|
pathToRegexpOptions.sensitive = route.caseSensitive
|
|
@@ -81,11 +91,12 @@ function addRouteRecord (
|
|
|
81
91
|
redirect: route.redirect,
|
|
82
92
|
beforeEnter: route.beforeEnter,
|
|
83
93
|
meta: route.meta || {},
|
|
84
|
-
props:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
props:
|
|
95
|
+
route.props == null
|
|
96
|
+
? {}
|
|
97
|
+
: route.components
|
|
98
|
+
? route.props
|
|
99
|
+
: { default: route.props }
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
if (route.children) {
|
|
@@ -93,14 +104,20 @@ function addRouteRecord (
|
|
|
93
104
|
// If users navigate to this route by name, the default child will
|
|
94
105
|
// not be rendered (GH Issue #629)
|
|
95
106
|
if (process.env.NODE_ENV !== 'production') {
|
|
96
|
-
if (
|
|
107
|
+
if (
|
|
108
|
+
route.name &&
|
|
109
|
+
!route.redirect &&
|
|
110
|
+
route.children.some(child => /^\/?$/.test(child.path))
|
|
111
|
+
) {
|
|
97
112
|
warn(
|
|
98
113
|
false,
|
|
99
114
|
`Named Route '${route.name}' has a default child route. ` +
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
115
|
+
`When navigating to this named route (:to="{name: '${
|
|
116
|
+
route.name
|
|
117
|
+
}'"), ` +
|
|
118
|
+
`the default child route will not be rendered. Remove the name from ` +
|
|
119
|
+
`this route and use the name of the default child route for named ` +
|
|
120
|
+
`links instead.`
|
|
104
121
|
)
|
|
105
122
|
}
|
|
106
123
|
}
|
|
@@ -112,12 +129,24 @@ function addRouteRecord (
|
|
|
112
129
|
})
|
|
113
130
|
}
|
|
114
131
|
|
|
132
|
+
if (!pathMap[record.path]) {
|
|
133
|
+
pathList.push(record.path)
|
|
134
|
+
pathMap[record.path] = record
|
|
135
|
+
}
|
|
136
|
+
|
|
115
137
|
if (route.alias !== undefined) {
|
|
116
|
-
const aliases = Array.isArray(route.alias)
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]
|
|
139
|
+
for (let i = 0; i < aliases.length; ++i) {
|
|
140
|
+
const alias = aliases[i]
|
|
141
|
+
if (process.env.NODE_ENV !== 'production' && alias === path) {
|
|
142
|
+
warn(
|
|
143
|
+
false,
|
|
144
|
+
`Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
|
|
145
|
+
)
|
|
146
|
+
// skip in dev to make it work
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
119
149
|
|
|
120
|
-
aliases.forEach(alias => {
|
|
121
150
|
const aliasRoute = {
|
|
122
151
|
path: alias,
|
|
123
152
|
children: route.children
|
|
@@ -130,12 +159,7 @@ function addRouteRecord (
|
|
|
130
159
|
parent,
|
|
131
160
|
record.path || '/' // matchAs
|
|
132
161
|
)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (!pathMap[record.path]) {
|
|
137
|
-
pathList.push(record.path)
|
|
138
|
-
pathMap[record.path] = record
|
|
162
|
+
}
|
|
139
163
|
}
|
|
140
164
|
|
|
141
165
|
if (name) {
|
|
@@ -145,25 +169,35 @@ function addRouteRecord (
|
|
|
145
169
|
warn(
|
|
146
170
|
false,
|
|
147
171
|
`Duplicate named routes definition: ` +
|
|
148
|
-
|
|
172
|
+
`{ name: "${name}", path: "${record.path}" }`
|
|
149
173
|
)
|
|
150
174
|
}
|
|
151
175
|
}
|
|
152
176
|
}
|
|
153
177
|
|
|
154
|
-
function compileRouteRegex (
|
|
178
|
+
function compileRouteRegex (
|
|
179
|
+
path: string,
|
|
180
|
+
pathToRegexpOptions: PathToRegexpOptions
|
|
181
|
+
): RouteRegExp {
|
|
155
182
|
const regex = Regexp(path, [], pathToRegexpOptions)
|
|
156
183
|
if (process.env.NODE_ENV !== 'production') {
|
|
157
184
|
const keys: any = Object.create(null)
|
|
158
185
|
regex.keys.forEach(key => {
|
|
159
|
-
warn(
|
|
186
|
+
warn(
|
|
187
|
+
!keys[key.name],
|
|
188
|
+
`Duplicate param keys in route with path: "${path}"`
|
|
189
|
+
)
|
|
160
190
|
keys[key.name] = true
|
|
161
191
|
})
|
|
162
192
|
}
|
|
163
193
|
return regex
|
|
164
194
|
}
|
|
165
195
|
|
|
166
|
-
function normalizePath (
|
|
196
|
+
function normalizePath (
|
|
197
|
+
path: string,
|
|
198
|
+
parent?: RouteRecord,
|
|
199
|
+
strict?: boolean
|
|
200
|
+
): string {
|
|
167
201
|
if (!strict) path = path.replace(/\/$/, '')
|
|
168
202
|
if (path[0] === '/') return path
|
|
169
203
|
if (parent == null) return path
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import type Router from '../index'
|
|
4
|
+
import { History } from './base'
|
|
5
|
+
import { NavigationDuplicated } from './errors'
|
|
6
|
+
import { isExtendedError } from '../util/warn'
|
|
7
|
+
|
|
8
|
+
export class AbstractHistory extends History {
|
|
9
|
+
index: number
|
|
10
|
+
stack: Array<Route>
|
|
11
|
+
|
|
12
|
+
constructor (router: Router, base: ?string) {
|
|
13
|
+
super(router, base)
|
|
14
|
+
this.stack = []
|
|
15
|
+
this.index = -1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
|
|
19
|
+
this.transitionTo(
|
|
20
|
+
location,
|
|
21
|
+
route => {
|
|
22
|
+
this.stack = this.stack.slice(0, this.index + 1).concat(route)
|
|
23
|
+
this.index++
|
|
24
|
+
onComplete && onComplete(route)
|
|
25
|
+
},
|
|
26
|
+
onAbort
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
|
|
31
|
+
this.transitionTo(
|
|
32
|
+
location,
|
|
33
|
+
route => {
|
|
34
|
+
this.stack = this.stack.slice(0, this.index).concat(route)
|
|
35
|
+
onComplete && onComplete(route)
|
|
36
|
+
},
|
|
37
|
+
onAbort
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
go (n: number) {
|
|
42
|
+
const targetIndex = this.index + n
|
|
43
|
+
if (targetIndex < 0 || targetIndex >= this.stack.length) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
const route = this.stack[targetIndex]
|
|
47
|
+
this.confirmTransition(
|
|
48
|
+
route,
|
|
49
|
+
() => {
|
|
50
|
+
this.index = targetIndex
|
|
51
|
+
this.updateRoute(route)
|
|
52
|
+
},
|
|
53
|
+
err => {
|
|
54
|
+
if (isExtendedError(NavigationDuplicated, err)) {
|
|
55
|
+
this.index = targetIndex
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getCurrentLocation () {
|
|
62
|
+
const current = this.stack[this.stack.length - 1]
|
|
63
|
+
return current ? current.fullPath : '/'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
ensureURL () {
|
|
67
|
+
// noop
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
|
|
3
|
+
import { _Kdu } from '../install'
|
|
4
|
+
import type Router from '../index'
|
|
5
|
+
import { inBrowser } from '../util/dom'
|
|
6
|
+
import { runQueue } from '../util/async'
|
|
7
|
+
import { warn, isError, isExtendedError } from '../util/warn'
|
|
8
|
+
import { START, isSameRoute } from '../util/route'
|
|
9
|
+
import {
|
|
10
|
+
flatten,
|
|
11
|
+
flatMapComponents,
|
|
12
|
+
resolveAsyncComponents
|
|
13
|
+
} from '../util/resolve-components'
|
|
14
|
+
import { NavigationDuplicated } from './errors'
|
|
15
|
+
|
|
16
|
+
export class History {
|
|
17
|
+
router: Router
|
|
18
|
+
base: string
|
|
19
|
+
current: Route
|
|
20
|
+
pending: ?Route
|
|
21
|
+
cb: (r: Route) => void
|
|
22
|
+
ready: boolean
|
|
23
|
+
readyCbs: Array<Function>
|
|
24
|
+
readyErrorCbs: Array<Function>
|
|
25
|
+
errorCbs: Array<Function>
|
|
26
|
+
|
|
27
|
+
// implemented by sub-classes
|
|
28
|
+
+go: (n: number) => void
|
|
29
|
+
+push: (loc: RawLocation) => void
|
|
30
|
+
+replace: (loc: RawLocation) => void
|
|
31
|
+
+ensureURL: (push?: boolean) => void
|
|
32
|
+
+getCurrentLocation: () => string
|
|
33
|
+
|
|
34
|
+
constructor (router: Router, base: ?string) {
|
|
35
|
+
this.router = router
|
|
36
|
+
this.base = normalizeBase(base)
|
|
37
|
+
// start with a route object that stands for "nowhere"
|
|
38
|
+
this.current = START
|
|
39
|
+
this.pending = null
|
|
40
|
+
this.ready = false
|
|
41
|
+
this.readyCbs = []
|
|
42
|
+
this.readyErrorCbs = []
|
|
43
|
+
this.errorCbs = []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
listen (cb: Function) {
|
|
47
|
+
this.cb = cb
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onReady (cb: Function, errorCb: ?Function) {
|
|
51
|
+
if (this.ready) {
|
|
52
|
+
cb()
|
|
53
|
+
} else {
|
|
54
|
+
this.readyCbs.push(cb)
|
|
55
|
+
if (errorCb) {
|
|
56
|
+
this.readyErrorCbs.push(errorCb)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onError (errorCb: Function) {
|
|
62
|
+
this.errorCbs.push(errorCb)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
transitionTo (
|
|
66
|
+
location: RawLocation,
|
|
67
|
+
onComplete?: Function,
|
|
68
|
+
onAbort?: Function
|
|
69
|
+
) {
|
|
70
|
+
const route = this.router.match(location, this.current)
|
|
71
|
+
this.confirmTransition(
|
|
72
|
+
route,
|
|
73
|
+
() => {
|
|
74
|
+
this.updateRoute(route)
|
|
75
|
+
onComplete && onComplete(route)
|
|
76
|
+
this.ensureURL()
|
|
77
|
+
|
|
78
|
+
// fire ready cbs once
|
|
79
|
+
if (!this.ready) {
|
|
80
|
+
this.ready = true
|
|
81
|
+
this.readyCbs.forEach(cb => {
|
|
82
|
+
cb(route)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
err => {
|
|
87
|
+
if (onAbort) {
|
|
88
|
+
onAbort(err)
|
|
89
|
+
}
|
|
90
|
+
if (err && !this.ready) {
|
|
91
|
+
this.ready = true
|
|
92
|
+
this.readyErrorCbs.forEach(cb => {
|
|
93
|
+
cb(err)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
|
|
101
|
+
const current = this.current
|
|
102
|
+
const abort = err => {
|
|
103
|
+
// When the user navigates through history through back/forward buttons
|
|
104
|
+
// we do not want to throw the error. We only throw it if directly calling
|
|
105
|
+
// push/replace. That's why it's not included in isError
|
|
106
|
+
if (!isExtendedError(NavigationDuplicated, err) && isError(err)) {
|
|
107
|
+
if (this.errorCbs.length) {
|
|
108
|
+
this.errorCbs.forEach(cb => {
|
|
109
|
+
cb(err)
|
|
110
|
+
})
|
|
111
|
+
} else {
|
|
112
|
+
warn(false, 'uncaught error during route navigation:')
|
|
113
|
+
console.error(err)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
onAbort && onAbort(err)
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
isSameRoute(route, current) &&
|
|
120
|
+
// in the case the route map has been dynamically appended to
|
|
121
|
+
route.matched.length === current.matched.length
|
|
122
|
+
) {
|
|
123
|
+
this.ensureURL()
|
|
124
|
+
return abort(new NavigationDuplicated(route))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { updated, deactivated, activated } = resolveQueue(
|
|
128
|
+
this.current.matched,
|
|
129
|
+
route.matched
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const queue: Array<?NavigationGuard> = [].concat(
|
|
133
|
+
// in-component leave guards
|
|
134
|
+
extractLeaveGuards(deactivated),
|
|
135
|
+
// global before hooks
|
|
136
|
+
this.router.beforeHooks,
|
|
137
|
+
// in-component update hooks
|
|
138
|
+
extractUpdateHooks(updated),
|
|
139
|
+
// in-config enter guards
|
|
140
|
+
activated.map(m => m.beforeEnter),
|
|
141
|
+
// async components
|
|
142
|
+
resolveAsyncComponents(activated)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
this.pending = route
|
|
146
|
+
const iterator = (hook: NavigationGuard, next) => {
|
|
147
|
+
if (this.pending !== route) {
|
|
148
|
+
return abort()
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
hook(route, current, (to: any) => {
|
|
152
|
+
if (to === false || isError(to)) {
|
|
153
|
+
// next(false) -> abort navigation, ensure current URL
|
|
154
|
+
this.ensureURL(true)
|
|
155
|
+
abort(to)
|
|
156
|
+
} else if (
|
|
157
|
+
typeof to === 'string' ||
|
|
158
|
+
(typeof to === 'object' &&
|
|
159
|
+
(typeof to.path === 'string' || typeof to.name === 'string'))
|
|
160
|
+
) {
|
|
161
|
+
// next('/') or next({ path: '/' }) -> redirect
|
|
162
|
+
abort()
|
|
163
|
+
if (typeof to === 'object' && to.replace) {
|
|
164
|
+
this.replace(to)
|
|
165
|
+
} else {
|
|
166
|
+
this.push(to)
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
// confirm transition and pass on the value
|
|
170
|
+
next(to)
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
} catch (e) {
|
|
174
|
+
abort(e)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
runQueue(queue, iterator, () => {
|
|
179
|
+
const postEnterCbs = []
|
|
180
|
+
const isValid = () => this.current === route
|
|
181
|
+
// wait until async components are resolved before
|
|
182
|
+
// extracting in-component enter guards
|
|
183
|
+
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
|
|
184
|
+
const queue = enterGuards.concat(this.router.resolveHooks)
|
|
185
|
+
runQueue(queue, iterator, () => {
|
|
186
|
+
if (this.pending !== route) {
|
|
187
|
+
return abort()
|
|
188
|
+
}
|
|
189
|
+
this.pending = null
|
|
190
|
+
onComplete(route)
|
|
191
|
+
if (this.router.app) {
|
|
192
|
+
this.router.app.$nextTick(() => {
|
|
193
|
+
postEnterCbs.forEach(cb => {
|
|
194
|
+
cb()
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
updateRoute (route: Route) {
|
|
203
|
+
const prev = this.current
|
|
204
|
+
this.current = route
|
|
205
|
+
this.cb && this.cb(route)
|
|
206
|
+
this.router.afterHooks.forEach(hook => {
|
|
207
|
+
hook && hook(route, prev)
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function normalizeBase (base: ?string): string {
|
|
213
|
+
if (!base) {
|
|
214
|
+
if (inBrowser) {
|
|
215
|
+
// respect <base> tag
|
|
216
|
+
const baseEl = document.querySelector('base')
|
|
217
|
+
base = (baseEl && baseEl.getAttribute('href')) || '/'
|
|
218
|
+
// strip full URL origin
|
|
219
|
+
base = base.replace(/^https?:\/\/[^\/]+/, '')
|
|
220
|
+
} else {
|
|
221
|
+
base = '/'
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// make sure there's the starting slash
|
|
225
|
+
if (base.charAt(0) !== '/') {
|
|
226
|
+
base = '/' + base
|
|
227
|
+
}
|
|
228
|
+
// remove trailing slash
|
|
229
|
+
return base.replace(/\/$/, '')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function resolveQueue (
|
|
233
|
+
current: Array<RouteRecord>,
|
|
234
|
+
next: Array<RouteRecord>
|
|
235
|
+
): {
|
|
236
|
+
updated: Array<RouteRecord>,
|
|
237
|
+
activated: Array<RouteRecord>,
|
|
238
|
+
deactivated: Array<RouteRecord>
|
|
239
|
+
} {
|
|
240
|
+
let i
|
|
241
|
+
const max = Math.max(current.length, next.length)
|
|
242
|
+
for (i = 0; i < max; i++) {
|
|
243
|
+
if (current[i] !== next[i]) {
|
|
244
|
+
break
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
updated: next.slice(0, i),
|
|
249
|
+
activated: next.slice(i),
|
|
250
|
+
deactivated: current.slice(i)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function extractGuards (
|
|
255
|
+
records: Array<RouteRecord>,
|
|
256
|
+
name: string,
|
|
257
|
+
bind: Function,
|
|
258
|
+
reverse?: boolean
|
|
259
|
+
): Array<?Function> {
|
|
260
|
+
const guards = flatMapComponents(records, (def, instance, match, key) => {
|
|
261
|
+
const guard = extractGuard(def, name)
|
|
262
|
+
if (guard) {
|
|
263
|
+
return Array.isArray(guard)
|
|
264
|
+
? guard.map(guard => bind(guard, instance, match, key))
|
|
265
|
+
: bind(guard, instance, match, key)
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
return flatten(reverse ? guards.reverse() : guards)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function extractGuard (
|
|
272
|
+
def: Object | Function,
|
|
273
|
+
key: string
|
|
274
|
+
): NavigationGuard | Array<NavigationGuard> {
|
|
275
|
+
if (typeof def !== 'function') {
|
|
276
|
+
// extend now so that global mixins are applied.
|
|
277
|
+
def = _Kdu.extend(def)
|
|
278
|
+
}
|
|
279
|
+
return def.options[key]
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {
|
|
283
|
+
return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
|
|
287
|
+
return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function bindGuard (guard: NavigationGuard, instance: ?_Kdu): ?NavigationGuard {
|
|
291
|
+
if (instance) {
|
|
292
|
+
return function boundRouteGuard () {
|
|
293
|
+
return guard.apply(instance, arguments)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function extractEnterGuards (
|
|
299
|
+
activated: Array<RouteRecord>,
|
|
300
|
+
cbs: Array<Function>,
|
|
301
|
+
isValid: () => boolean
|
|
302
|
+
): Array<?Function> {
|
|
303
|
+
return extractGuards(
|
|
304
|
+
activated,
|
|
305
|
+
'beforeRouteEnter',
|
|
306
|
+
(guard, _, match, key) => {
|
|
307
|
+
return bindEnterGuard(guard, match, key, cbs, isValid)
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function bindEnterGuard (
|
|
313
|
+
guard: NavigationGuard,
|
|
314
|
+
match: RouteRecord,
|
|
315
|
+
key: string,
|
|
316
|
+
cbs: Array<Function>,
|
|
317
|
+
isValid: () => boolean
|
|
318
|
+
): NavigationGuard {
|
|
319
|
+
return function routeEnterGuard (to, from, next) {
|
|
320
|
+
return guard(to, from, cb => {
|
|
321
|
+
if (typeof cb === 'function') {
|
|
322
|
+
cbs.push(() => {
|
|
323
|
+
// #750
|
|
324
|
+
// if a router-view is wrapped with an out-in transition,
|
|
325
|
+
// the instance may not have been registered at this time.
|
|
326
|
+
// we will need to poll for registration until current route
|
|
327
|
+
// is no longer valid.
|
|
328
|
+
poll(cb, match.instances, key, isValid)
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
next(cb)
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function poll (
|
|
337
|
+
cb: any, // somehow flow cannot infer this is a function
|
|
338
|
+
instances: Object,
|
|
339
|
+
key: string,
|
|
340
|
+
isValid: () => boolean
|
|
341
|
+
) {
|
|
342
|
+
if (
|
|
343
|
+
instances[key] &&
|
|
344
|
+
!instances[key]._isBeingDestroyed // do not reuse being destroyed instance
|
|
345
|
+
) {
|
|
346
|
+
cb(instances[key])
|
|
347
|
+
} else if (isValid()) {
|
|
348
|
+
setTimeout(() => {
|
|
349
|
+
poll(cb, instances, key, isValid)
|
|
350
|
+
}, 16)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class NavigationDuplicated extends Error {
|
|
2
|
+
constructor (normalizedLocation) {
|
|
3
|
+
super()
|
|
4
|
+
this.name = this._name = 'NavigationDuplicated'
|
|
5
|
+
// passing the message to super() doesn't seem to work in the transpiled version
|
|
6
|
+
this.message = `Navigating to current location ("${
|
|
7
|
+
normalizedLocation.fullPath
|
|
8
|
+
}") is not allowed`
|
|
9
|
+
// add a stack property so services like Sentry can correctly display it
|
|
10
|
+
Object.defineProperty(this, 'stack', {
|
|
11
|
+
value: new Error().stack,
|
|
12
|
+
writable: true,
|
|
13
|
+
configurable: true
|
|
14
|
+
})
|
|
15
|
+
// we could also have used
|
|
16
|
+
// Error.captureStackTrace(this, this.constructor)
|
|
17
|
+
// but it only exists on node and chrome
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// support IE9
|
|
22
|
+
NavigationDuplicated._name = 'NavigationDuplicated'
|