-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
60 changed files
with
4,812 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
declare var document: Document; | ||
|
||
declare class RouteRegExp extends RegExp { | ||
keys: Array<{ name: string, optional: boolean }>; | ||
} | ||
|
||
declare type PathToRegexpOptions = { | ||
sensitive?: boolean, | ||
strict?: boolean, | ||
end?: boolean | ||
} | ||
|
||
declare module 'path-to-regexp' { | ||
declare module.exports: { | ||
(path: string, keys?: Array<?{ name: string }>, options?: PathToRegexpOptions): RouteRegExp; | ||
compile: (path: string) => (params: Object) => string; | ||
} | ||
} | ||
|
||
declare type Dictionary<T> = { [key: string]: T } | ||
|
||
declare type NavigationGuard = ( | ||
to: Route, | ||
from: Route, | ||
next: (to?: RawLocation | false | Function | void) => void | ||
) => any | ||
|
||
declare type AfterNavigationHook = (to: Route, from: Route) => any | ||
|
||
type Position = { x: number, y: number }; | ||
type PositionResult = Position | { selector: string, offset?: Position } | void; | ||
|
||
declare type RouterOptions = { | ||
routes?: Array<RouteConfig>; | ||
mode?: string; | ||
fallback?: boolean; | ||
base?: string; | ||
linkActiveClass?: string; | ||
linkExactActiveClass?: string; | ||
parseQuery?: (query: string) => Object; | ||
stringifyQuery?: (query: Object) => string; | ||
scrollBehavior?: ( | ||
to: Route, | ||
from: Route, | ||
savedPosition: ?Position | ||
) => PositionResult | Promise<PositionResult>; | ||
} | ||
|
||
declare type RedirectOption = RawLocation | ((to: Route) => RawLocation) | ||
|
||
declare type RouteConfig = { | ||
path: string; | ||
name?: string; | ||
component?: any; | ||
components?: Dictionary<any>; | ||
redirect?: RedirectOption; | ||
alias?: string | Array<string>; | ||
children?: Array<RouteConfig>; | ||
beforeEnter?: NavigationGuard; | ||
meta?: any; | ||
props?: boolean | Object | Function; | ||
caseSensitive?: boolean; | ||
pathToRegexpOptions?: PathToRegexpOptions; | ||
} | ||
|
||
declare type RouteRecord = { | ||
path: string; | ||
regex: RouteRegExp; | ||
components: Dictionary<any>; | ||
instances: Dictionary<any>; | ||
name: ?string; | ||
parent: ?RouteRecord; | ||
redirect: ?RedirectOption; | ||
matchAs: ?string; | ||
beforeEnter: ?NavigationGuard; | ||
meta: any; | ||
props: boolean | Object | Function | Dictionary<boolean | Object | Function>; | ||
} | ||
|
||
declare type Location = { | ||
_normalized?: boolean; | ||
name?: string; | ||
path?: string; | ||
hash?: string; | ||
query?: Dictionary<string>; | ||
params?: Dictionary<string>; | ||
append?: boolean; | ||
replace?: boolean; | ||
} | ||
|
||
declare type RawLocation = string | Location | ||
|
||
declare type Route = { | ||
path: string; | ||
name: ?string; | ||
hash: string; | ||
query: Dictionary<string>; | ||
params: Dictionary<string>; | ||
fullPath: string; | ||
matched: Array<RouteRecord>; | ||
redirectedFrom?: string; | ||
meta?: any; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* @flow */ | ||
|
||
import { createRoute, isSameRoute, isIncludedRoute } from '../util/route' | ||
import { extend } from '../util/misc' | ||
|
||
// work around weird flow bug | ||
const toTypes: Array<Function> = [String, Object] | ||
const eventTypes: Array<Function> = [String, Array] | ||
|
||
export default { | ||
name: 'RouterLink', | ||
props: { | ||
to: { | ||
type: toTypes, | ||
required: true | ||
}, | ||
tag: { | ||
type: String, | ||
default: 'a' | ||
}, | ||
exact: Boolean, | ||
append: Boolean, | ||
replace: Boolean, | ||
activeClass: String, | ||
exactActiveClass: String, | ||
event: { | ||
type: eventTypes, | ||
default: 'click' | ||
} | ||
}, | ||
render (h: Function) { | ||
const router = this.$router | ||
const current = this.$route | ||
const { location, route, href } = router.resolve(this.to, current, this.append) | ||
|
||
const classes = {} | ||
const globalActiveClass = router.options.linkActiveClass | ||
const globalExactActiveClass = router.options.linkExactActiveClass | ||
// Support global empty active class | ||
const activeClassFallback = globalActiveClass == null | ||
? 'router-link-active' | ||
: globalActiveClass | ||
const exactActiveClassFallback = globalExactActiveClass == null | ||
? 'router-link-exact-active' | ||
: globalExactActiveClass | ||
const activeClass = this.activeClass == null | ||
? activeClassFallback | ||
: this.activeClass | ||
const exactActiveClass = this.exactActiveClass == null | ||
? exactActiveClassFallback | ||
: this.exactActiveClass | ||
const compareTarget = location.path | ||
? createRoute(null, location, null, router) | ||
: route | ||
|
||
classes[exactActiveClass] = isSameRoute(current, compareTarget) | ||
classes[activeClass] = this.exact | ||
? classes[exactActiveClass] | ||
: isIncludedRoute(current, compareTarget) | ||
|
||
const handler = e => { | ||
if (guardEvent(e)) { | ||
if (this.replace) { | ||
router.replace(location) | ||
} else { | ||
router.push(location) | ||
} | ||
} | ||
} | ||
|
||
const on = { click: guardEvent } | ||
if (Array.isArray(this.event)) { | ||
this.event.forEach(e => { on[e] = handler }) | ||
} else { | ||
on[this.event] = handler | ||
} | ||
|
||
const data: any = { | ||
class: classes | ||
} | ||
|
||
if (this.tag === 'a') { | ||
data.on = on | ||
data.attrs = { href } | ||
} else { | ||
// find the first <a> child and apply listener and href | ||
const a = findAnchor(this.$slots.default) | ||
if (a) { | ||
// in case the <a> is a static node | ||
a.isStatic = false | ||
const aData = a.data = extend({}, a.data) | ||
aData.on = on | ||
const aAttrs = a.data.attrs = extend({}, a.data.attrs) | ||
aAttrs.href = href | ||
} else { | ||
// doesn't have <a> child, apply listener to self | ||
data.on = on | ||
} | ||
} | ||
|
||
return h(this.tag, data, this.$slots.default) | ||
} | ||
} | ||
|
||
function guardEvent (e) { | ||
// don't redirect with control keys | ||
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return | ||
// don't redirect when preventDefault called | ||
if (e.defaultPrevented) return | ||
// don't redirect on right click | ||
if (e.button !== undefined && e.button !== 0) return | ||
// don't redirect if `target="_blank"` | ||
if (e.currentTarget && e.currentTarget.getAttribute) { | ||
const target = e.currentTarget.getAttribute('target') | ||
if (/\b_blank\b/i.test(target)) return | ||
} | ||
// this may be a Weex event which doesn't have this method | ||
if (e.preventDefault) { | ||
e.preventDefault() | ||
} | ||
return true | ||
} | ||
|
||
function findAnchor (children) { | ||
if (children) { | ||
let child | ||
for (let i = 0; i < children.length; i++) { | ||
child = children[i] | ||
if (child.tag === 'a') { | ||
return child | ||
} | ||
if (child.children && (child = findAnchor(child.children))) { | ||
return child | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { warn } from '../util/warn' | ||
import { extend } from '../util/misc' | ||
|
||
export default { | ||
name: 'RouterView', | ||
functional: true, | ||
props: { | ||
name: { | ||
type: String, | ||
default: 'default' | ||
} | ||
}, | ||
render (_, { props, children, parent, data }) { | ||
// used by devtools to display a router-view badge | ||
data.routerView = true | ||
|
||
// directly use parent context's createElement() function | ||
// so that components rendered by router-view can resolve named slots | ||
const h = parent.$createElement | ||
const name = props.name | ||
const route = parent.$route | ||
const cache = parent._routerViewCache || (parent._routerViewCache = {}) | ||
|
||
// determine current view depth, also check to see if the tree | ||
// has been toggled inactive but kept-alive. | ||
let depth = 0 | ||
let inactive = false | ||
while (parent && parent._routerRoot !== parent) { | ||
const vnodeData = parent.$vnode && parent.$vnode.data | ||
if (vnodeData) { | ||
if (vnodeData.routerView) { | ||
depth++ | ||
} | ||
if (vnodeData.keepAlive && parent._inactive) { | ||
inactive = true | ||
} | ||
} | ||
parent = parent.$parent | ||
} | ||
data.routerViewDepth = depth | ||
|
||
// render previous view if the tree is inactive and kept-alive | ||
if (inactive) { | ||
return h(cache[name], data, children) | ||
} | ||
|
||
const matched = route.matched[depth] | ||
// render empty node if no matched route | ||
if (!matched) { | ||
cache[name] = null | ||
return h() | ||
} | ||
|
||
const component = cache[name] = matched.components[name] | ||
|
||
// attach instance registration hook | ||
// this will be called in the instance's injected lifecycle hooks | ||
data.registerRouteInstance = (vm, val) => { | ||
// val could be undefined for unregistration | ||
const current = matched.instances[name] | ||
if ( | ||
(val && current !== vm) || | ||
(!val && current === vm) | ||
) { | ||
matched.instances[name] = val | ||
} | ||
} | ||
|
||
// also register instance in prepatch hook | ||
// in case the same component instance is reused across different routes | ||
;(data.hook || (data.hook = {})).prepatch = (_, vnode) => { | ||
matched.instances[name] = vnode.componentInstance | ||
} | ||
|
||
// register instance in init hook | ||
// in case kept-alive component be actived when routes changed | ||
data.hook.init = (vnode) => { | ||
if (vnode.data.keepAlive && | ||
vnode.componentInstance && | ||
vnode.componentInstance !== matched.instances[name] | ||
) { | ||
matched.instances[name] = vnode.componentInstance | ||
} | ||
} | ||
|
||
// resolve props | ||
let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]) | ||
if (propsToPass) { | ||
// clone to prevent mutation | ||
propsToPass = data.props = extend({}, propsToPass) | ||
// pass non-declared props as attrs | ||
const attrs = data.attrs = data.attrs || {} | ||
for (const key in propsToPass) { | ||
if (!component.props || !(key in component.props)) { | ||
attrs[key] = propsToPass[key] | ||
delete propsToPass[key] | ||
} | ||
} | ||
} | ||
|
||
return h(component, data, children) | ||
} | ||
} | ||
|
||
function resolveProps (route, config) { | ||
switch (typeof config) { | ||
case 'undefined': | ||
return | ||
case 'object': | ||
return config | ||
case 'function': | ||
return config(route) | ||
case 'boolean': | ||
return config ? route.params : undefined | ||
default: | ||
if (process.env.NODE_ENV !== 'production') { | ||
warn( | ||
false, | ||
`props in "${route.path}" is a ${typeof config}, ` + | ||
`expecting an object, function or boolean.` | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.