add menu
parent
dfaec7f076
commit
09500d37eb
|
@ -0,0 +1,10 @@
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
setState (state, callback) {
|
||||||
|
Object.assign(this.$date, state)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
callback()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
// do not modify this file
|
||||||
|
import Animate from './src/Animate'
|
||||||
|
export default Animate
|
|
@ -0,0 +1,333 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import {
|
||||||
|
toArrayChildren,
|
||||||
|
mergeChildren,
|
||||||
|
findShownChildInChildrenByKey,
|
||||||
|
findChildInChildrenByKey,
|
||||||
|
isSameChildren,
|
||||||
|
} from './ChildrenUtils'
|
||||||
|
import AnimateChild from './AnimateChild'
|
||||||
|
const defaultKey = `rc_animate_${Date.now()}`
|
||||||
|
import animUtil from './util'
|
||||||
|
|
||||||
|
function getChildrenFromProps (props) {
|
||||||
|
const children = props.children
|
||||||
|
if (React.isValidElement(children)) {
|
||||||
|
if (!children.key) {
|
||||||
|
return React.cloneElement(children, {
|
||||||
|
key: defaultKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop () {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Animate extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
component: PropTypes.any,
|
||||||
|
componentProps: PropTypes.object,
|
||||||
|
animation: PropTypes.object,
|
||||||
|
transitionName: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.object,
|
||||||
|
]),
|
||||||
|
transitionEnter: PropTypes.bool,
|
||||||
|
transitionAppear: PropTypes.bool,
|
||||||
|
exclusive: PropTypes.bool,
|
||||||
|
transitionLeave: PropTypes.bool,
|
||||||
|
onEnd: PropTypes.func,
|
||||||
|
onEnter: PropTypes.func,
|
||||||
|
onLeave: PropTypes.func,
|
||||||
|
onAppear: PropTypes.func,
|
||||||
|
showProp: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
animation: {},
|
||||||
|
component: 'span',
|
||||||
|
componentProps: {},
|
||||||
|
transitionEnter: true,
|
||||||
|
transitionLeave: true,
|
||||||
|
transitionAppear: false,
|
||||||
|
onEnd: noop,
|
||||||
|
onEnter: noop,
|
||||||
|
onLeave: noop,
|
||||||
|
onAppear: noop,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.currentlyAnimatingKeys = {}
|
||||||
|
this.keysToEnter = []
|
||||||
|
this.keysToLeave = []
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
children: toArrayChildren(getChildrenFromProps(this.props)),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.childrenRefs = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const showProp = this.props.showProp
|
||||||
|
let children = this.state.children
|
||||||
|
if (showProp) {
|
||||||
|
children = children.filter((child) => {
|
||||||
|
return !!child.props[showProp]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (child) {
|
||||||
|
this.performAppear(child.key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
this.nextProps = nextProps
|
||||||
|
const nextChildren = toArrayChildren(getChildrenFromProps(nextProps))
|
||||||
|
const props = this.props
|
||||||
|
// exclusive needs immediate response
|
||||||
|
if (props.exclusive) {
|
||||||
|
Object.keys(this.currentlyAnimatingKeys).forEach((key) => {
|
||||||
|
this.stop(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const showProp = props.showProp
|
||||||
|
const currentlyAnimatingKeys = this.currentlyAnimatingKeys
|
||||||
|
// last props children if exclusive
|
||||||
|
const currentChildren = props.exclusive
|
||||||
|
? toArrayChildren(getChildrenFromProps(props))
|
||||||
|
: this.state.children
|
||||||
|
// in case destroy in showProp mode
|
||||||
|
let newChildren = []
|
||||||
|
if (showProp) {
|
||||||
|
currentChildren.forEach((currentChild) => {
|
||||||
|
const nextChild = currentChild && findChildInChildrenByKey(nextChildren, currentChild.key)
|
||||||
|
let newChild
|
||||||
|
if ((!nextChild || !nextChild.props[showProp]) && currentChild.props[showProp]) {
|
||||||
|
newChild = React.cloneElement(nextChild || currentChild, {
|
||||||
|
[showProp]: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
newChild = nextChild
|
||||||
|
}
|
||||||
|
if (newChild) {
|
||||||
|
newChildren.push(newChild)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
nextChildren.forEach((nextChild) => {
|
||||||
|
if (!nextChild || !findChildInChildrenByKey(currentChildren, nextChild.key)) {
|
||||||
|
newChildren.push(nextChild)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
newChildren = mergeChildren(
|
||||||
|
currentChildren,
|
||||||
|
nextChildren
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// need render to avoid update
|
||||||
|
this.setState({
|
||||||
|
children: newChildren,
|
||||||
|
})
|
||||||
|
|
||||||
|
nextChildren.forEach((child) => {
|
||||||
|
const key = child && child.key
|
||||||
|
if (child && currentlyAnimatingKeys[key]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const hasPrev = child && findChildInChildrenByKey(currentChildren, key)
|
||||||
|
if (showProp) {
|
||||||
|
const showInNext = child.props[showProp]
|
||||||
|
if (hasPrev) {
|
||||||
|
const showInNow = findShownChildInChildrenByKey(currentChildren, key, showProp)
|
||||||
|
if (!showInNow && showInNext) {
|
||||||
|
this.keysToEnter.push(key)
|
||||||
|
}
|
||||||
|
} else if (showInNext) {
|
||||||
|
this.keysToEnter.push(key)
|
||||||
|
}
|
||||||
|
} else if (!hasPrev) {
|
||||||
|
this.keysToEnter.push(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
currentChildren.forEach((child) => {
|
||||||
|
const key = child && child.key
|
||||||
|
if (child && currentlyAnimatingKeys[key]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const hasNext = child && findChildInChildrenByKey(nextChildren, key)
|
||||||
|
if (showProp) {
|
||||||
|
const showInNow = child.props[showProp]
|
||||||
|
if (hasNext) {
|
||||||
|
const showInNext = findShownChildInChildrenByKey(nextChildren, key, showProp)
|
||||||
|
if (!showInNext && showInNow) {
|
||||||
|
this.keysToLeave.push(key)
|
||||||
|
}
|
||||||
|
} else if (showInNow) {
|
||||||
|
this.keysToLeave.push(key)
|
||||||
|
}
|
||||||
|
} else if (!hasNext) {
|
||||||
|
this.keysToLeave.push(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate () {
|
||||||
|
const keysToEnter = this.keysToEnter
|
||||||
|
this.keysToEnter = []
|
||||||
|
keysToEnter.forEach(this.performEnter)
|
||||||
|
const keysToLeave = this.keysToLeave
|
||||||
|
this.keysToLeave = []
|
||||||
|
keysToLeave.forEach(this.performLeave)
|
||||||
|
}
|
||||||
|
|
||||||
|
performEnter = (key) => {
|
||||||
|
// may already remove by exclusive
|
||||||
|
if (this.childrenRefs[key]) {
|
||||||
|
this.currentlyAnimatingKeys[key] = true
|
||||||
|
this.childrenRefs[key].componentWillEnter(
|
||||||
|
this.handleDoneAdding.bind(this, key, 'enter')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
performAppear = (key) => {
|
||||||
|
if (this.childrenRefs[key]) {
|
||||||
|
this.currentlyAnimatingKeys[key] = true
|
||||||
|
this.childrenRefs[key].componentWillAppear(
|
||||||
|
this.handleDoneAdding.bind(this, key, 'appear')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDoneAdding = (key, type) => {
|
||||||
|
const props = this.props
|
||||||
|
delete this.currentlyAnimatingKeys[key]
|
||||||
|
// if update on exclusive mode, skip check
|
||||||
|
if (props.exclusive && props !== this.nextProps) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const currentChildren = toArrayChildren(getChildrenFromProps(props))
|
||||||
|
if (!this.isValidChildByKey(currentChildren, key)) {
|
||||||
|
// exclusive will not need this
|
||||||
|
this.performLeave(key)
|
||||||
|
} else {
|
||||||
|
if (type === 'appear') {
|
||||||
|
if (animUtil.allowAppearCallback(props)) {
|
||||||
|
props.onAppear(key)
|
||||||
|
props.onEnd(key, true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (animUtil.allowEnterCallback(props)) {
|
||||||
|
props.onEnter(key)
|
||||||
|
props.onEnd(key, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
performLeave = (key) => {
|
||||||
|
// may already remove by exclusive
|
||||||
|
if (this.childrenRefs[key]) {
|
||||||
|
this.currentlyAnimatingKeys[key] = true
|
||||||
|
this.childrenRefs[key].componentWillLeave(this.handleDoneLeaving.bind(this, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDoneLeaving = (key) => {
|
||||||
|
const props = this.props
|
||||||
|
delete this.currentlyAnimatingKeys[key]
|
||||||
|
// if update on exclusive mode, skip check
|
||||||
|
if (props.exclusive && props !== this.nextProps) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const currentChildren = toArrayChildren(getChildrenFromProps(props))
|
||||||
|
// in case state change is too fast
|
||||||
|
if (this.isValidChildByKey(currentChildren, key)) {
|
||||||
|
this.performEnter(key)
|
||||||
|
} else {
|
||||||
|
const end = () => {
|
||||||
|
if (animUtil.allowLeaveCallback(props)) {
|
||||||
|
props.onLeave(key)
|
||||||
|
props.onEnd(key, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isSameChildren(this.state.children,
|
||||||
|
currentChildren, props.showProp)) {
|
||||||
|
this.setState({
|
||||||
|
children: currentChildren,
|
||||||
|
}, end)
|
||||||
|
} else {
|
||||||
|
end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidChildByKey (currentChildren, key) {
|
||||||
|
const showProp = this.props.showProp
|
||||||
|
if (showProp) {
|
||||||
|
return findShownChildInChildrenByKey(currentChildren, key, showProp)
|
||||||
|
}
|
||||||
|
return findChildInChildrenByKey(currentChildren, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
stop (key) {
|
||||||
|
delete this.currentlyAnimatingKeys[key]
|
||||||
|
const component = this.childrenRefs[key]
|
||||||
|
if (component) {
|
||||||
|
component.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const props = this.props
|
||||||
|
this.nextProps = props
|
||||||
|
const stateChildren = this.state.children
|
||||||
|
let children = null
|
||||||
|
if (stateChildren) {
|
||||||
|
children = stateChildren.map((child) => {
|
||||||
|
if (child === null || child === undefined) {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
if (!child.key) {
|
||||||
|
throw new Error('must set key for <rc-animate> children')
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<AnimateChild
|
||||||
|
key={child.key}
|
||||||
|
ref={node => this.childrenRefs[child.key] = node}
|
||||||
|
animation={props.animation}
|
||||||
|
transitionName={props.transitionName}
|
||||||
|
transitionEnter={props.transitionEnter}
|
||||||
|
transitionAppear={props.transitionAppear}
|
||||||
|
transitionLeave={props.transitionLeave}
|
||||||
|
>
|
||||||
|
{child}
|
||||||
|
</AnimateChild>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const Component = props.component
|
||||||
|
if (Component) {
|
||||||
|
let passedProps = props
|
||||||
|
if (typeof Component === 'string') {
|
||||||
|
passedProps = {
|
||||||
|
className: props.className,
|
||||||
|
style: props.style,
|
||||||
|
...props.componentProps,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <Component {...passedProps}>{children}</Component>
|
||||||
|
}
|
||||||
|
return children[0] || null
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import cssAnimate, { isCssAnimationSupported } from 'css-animation'
|
||||||
|
import animUtil from './util'
|
||||||
|
|
||||||
|
const transitionMap = {
|
||||||
|
enter: 'transitionEnter',
|
||||||
|
appear: 'transitionAppear',
|
||||||
|
leave: 'transitionLeave',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AnimateChild extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.any,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillEnter (done) {
|
||||||
|
if (animUtil.isEnterSupported(this.props)) {
|
||||||
|
this.transition('enter', done)
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillAppear (done) {
|
||||||
|
if (animUtil.isAppearSupported(this.props)) {
|
||||||
|
this.transition('appear', done)
|
||||||
|
} else {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillLeave (done) {
|
||||||
|
if (animUtil.isLeaveSupported(this.props)) {
|
||||||
|
this.transition('leave', done)
|
||||||
|
} else {
|
||||||
|
// always sync, do not interupt with react component life cycle
|
||||||
|
// update hidden -> animate hidden ->
|
||||||
|
// didUpdate -> animate leave -> unmount (if animate is none)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transition (animationType, finishCallback) {
|
||||||
|
const node = ReactDOM.findDOMNode(this)
|
||||||
|
const props = this.props
|
||||||
|
const transitionName = props.transitionName
|
||||||
|
const nameIsObj = typeof transitionName === 'object'
|
||||||
|
this.stop()
|
||||||
|
const end = () => {
|
||||||
|
this.stopper = null
|
||||||
|
finishCallback()
|
||||||
|
}
|
||||||
|
if ((isCssAnimationSupported || !props.animation[animationType]) &&
|
||||||
|
transitionName && props[transitionMap[animationType]]) {
|
||||||
|
const name = nameIsObj ? transitionName[animationType] : `${transitionName}-${animationType}`
|
||||||
|
let activeName = `${name}-active`
|
||||||
|
if (nameIsObj && transitionName[`${animationType}Active`]) {
|
||||||
|
activeName = transitionName[`${animationType}Active`]
|
||||||
|
}
|
||||||
|
this.stopper = cssAnimate(node, {
|
||||||
|
name,
|
||||||
|
active: activeName,
|
||||||
|
}, end)
|
||||||
|
} else {
|
||||||
|
this.stopper = props.animation[animationType](node, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stop () {
|
||||||
|
const stopper = this.stopper
|
||||||
|
if (stopper) {
|
||||||
|
this.stopper = null
|
||||||
|
stopper.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return this.props.children
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function toArrayChildren (children) {
|
||||||
|
const ret = []
|
||||||
|
React.Children.forEach(children, (child) => {
|
||||||
|
ret.push(child)
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findChildInChildrenByKey (children, key) {
|
||||||
|
let ret = null
|
||||||
|
if (children) {
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (ret) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (child && child.key === key) {
|
||||||
|
ret = child
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findShownChildInChildrenByKey (children, key, showProp) {
|
||||||
|
let ret = null
|
||||||
|
if (children) {
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (child && child.key === key && child.props[showProp]) {
|
||||||
|
if (ret) {
|
||||||
|
throw new Error('two child with same key for <rc-animate> children')
|
||||||
|
}
|
||||||
|
ret = child
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findHiddenChildInChildrenByKey (children, key, showProp) {
|
||||||
|
let found = 0
|
||||||
|
if (children) {
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (found) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
found = child && child.key === key && !child.props[showProp]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSameChildren (c1, c2, showProp) {
|
||||||
|
let same = c1.length === c2.length
|
||||||
|
if (same) {
|
||||||
|
c1.forEach((child, index) => {
|
||||||
|
const child2 = c2[index]
|
||||||
|
if (child && child2) {
|
||||||
|
if ((child && !child2) || (!child && child2)) {
|
||||||
|
same = false
|
||||||
|
} else if (child.key !== child2.key) {
|
||||||
|
same = false
|
||||||
|
} else if (showProp && child.props[showProp] !== child2.props[showProp]) {
|
||||||
|
same = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return same
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeChildren (prev, next) {
|
||||||
|
let ret = []
|
||||||
|
|
||||||
|
// For each key of `next`, the list of keys to insert before that key in
|
||||||
|
// the combined list
|
||||||
|
const nextChildrenPending = {}
|
||||||
|
let pendingChildren = []
|
||||||
|
prev.forEach((child) => {
|
||||||
|
if (child && findChildInChildrenByKey(next, child.key)) {
|
||||||
|
if (pendingChildren.length) {
|
||||||
|
nextChildrenPending[child.key] = pendingChildren
|
||||||
|
pendingChildren = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingChildren.push(child)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
next.forEach((child) => {
|
||||||
|
if (child && nextChildrenPending.hasOwnProperty(child.key)) {
|
||||||
|
ret = ret.concat(nextChildrenPending[child.key])
|
||||||
|
}
|
||||||
|
ret.push(child)
|
||||||
|
})
|
||||||
|
|
||||||
|
ret = ret.concat(pendingChildren)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
const util = {
|
||||||
|
isAppearSupported(props) {
|
||||||
|
return props.transitionName && props.transitionAppear || props.animation.appear;
|
||||||
|
},
|
||||||
|
isEnterSupported(props) {
|
||||||
|
return props.transitionName && props.transitionEnter || props.animation.enter;
|
||||||
|
},
|
||||||
|
isLeaveSupported(props) {
|
||||||
|
return props.transitionName && props.transitionLeave || props.animation.leave;
|
||||||
|
},
|
||||||
|
allowAppearCallback(props) {
|
||||||
|
return props.transitionAppear || props.animation.appear;
|
||||||
|
},
|
||||||
|
allowEnterCallback(props) {
|
||||||
|
return props.transitionEnter || props.animation.enter;
|
||||||
|
},
|
||||||
|
allowLeaveCallback(props) {
|
||||||
|
return props.transitionLeave || props.animation.leave;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default util;
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script>
|
||||||
|
import omit from 'omit.js'
|
||||||
|
export default {
|
||||||
|
name: 'DOMWrap',
|
||||||
|
props: {
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
tag: {
|
||||||
|
type: String,
|
||||||
|
default: 'div',
|
||||||
|
},
|
||||||
|
hiddenClassName: String,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
class () {
|
||||||
|
const { visible, hiddenClassName } = this.$props
|
||||||
|
return {
|
||||||
|
[hiddenClassName]: !visible,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const otherProps = omit(this.$props, [
|
||||||
|
'tag',
|
||||||
|
'hiddenClassName',
|
||||||
|
'visible',
|
||||||
|
])
|
||||||
|
const Tag = this.$props.tag
|
||||||
|
const tagProps = {
|
||||||
|
attr: { ...otherProps, ...this.$attrs },
|
||||||
|
}
|
||||||
|
return <Tag {...tagProps} class={this.class} />
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Divider',
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
rootPrefixCls: String,
|
||||||
|
},
|
||||||
|
render () {
|
||||||
|
const { rootPrefixCls } = this.$props
|
||||||
|
return <li class={`${rootPrefixCls}-item-divider`}/>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,221 @@
|
||||||
|
// import React from 'react';
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import createReactClass from 'create-react-class'
|
||||||
|
import MenuMixin from './MenuMixin'
|
||||||
|
import { noop } from './util'
|
||||||
|
|
||||||
|
const Menu = createReactClass({
|
||||||
|
displayName: 'Menu',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
defaultOpenKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
openKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
|
||||||
|
getPopupContainer: PropTypes.func,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
onDeselect: PropTypes.func,
|
||||||
|
onDestroy: PropTypes.func,
|
||||||
|
openTransitionName: PropTypes.string,
|
||||||
|
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
subMenuOpenDelay: PropTypes.number,
|
||||||
|
subMenuCloseDelay: PropTypes.number,
|
||||||
|
forceSubMenuRender: PropTypes.bool,
|
||||||
|
triggerSubMenuAction: PropTypes.string,
|
||||||
|
level: PropTypes.number,
|
||||||
|
selectable: PropTypes.bool,
|
||||||
|
multiple: PropTypes.bool,
|
||||||
|
children: PropTypes.any,
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [MenuMixin],
|
||||||
|
|
||||||
|
isRootMenu: true,
|
||||||
|
|
||||||
|
getDefaultProps () {
|
||||||
|
return {
|
||||||
|
selectable: true,
|
||||||
|
onClick: noop,
|
||||||
|
onSelect: noop,
|
||||||
|
onOpenChange: noop,
|
||||||
|
onDeselect: noop,
|
||||||
|
defaultSelectedKeys: [],
|
||||||
|
defaultOpenKeys: [],
|
||||||
|
subMenuOpenDelay: 0,
|
||||||
|
subMenuCloseDelay: 0.1,
|
||||||
|
triggerSubMenuAction: 'hover',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState () {
|
||||||
|
const props = this.props
|
||||||
|
let selectedKeys = props.defaultSelectedKeys
|
||||||
|
let openKeys = props.defaultOpenKeys
|
||||||
|
if ('selectedKeys' in props) {
|
||||||
|
selectedKeys = props.selectedKeys || []
|
||||||
|
}
|
||||||
|
if ('openKeys' in props) {
|
||||||
|
openKeys = props.openKeys || []
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
selectedKeys,
|
||||||
|
openKeys,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
const props = {}
|
||||||
|
if ('selectedKeys' in nextProps) {
|
||||||
|
props.selectedKeys = nextProps.selectedKeys || []
|
||||||
|
}
|
||||||
|
if ('openKeys' in nextProps) {
|
||||||
|
props.openKeys = nextProps.openKeys || []
|
||||||
|
}
|
||||||
|
this.setState(props)
|
||||||
|
},
|
||||||
|
|
||||||
|
onDestroy (key) {
|
||||||
|
const state = this.state
|
||||||
|
const props = this.props
|
||||||
|
const selectedKeys = state.selectedKeys
|
||||||
|
const openKeys = state.openKeys
|
||||||
|
let index = selectedKeys.indexOf(key)
|
||||||
|
if (!('selectedKeys' in props) && index !== -1) {
|
||||||
|
selectedKeys.splice(index, 1)
|
||||||
|
}
|
||||||
|
index = openKeys.indexOf(key)
|
||||||
|
if (!('openKeys' in props) && index !== -1) {
|
||||||
|
openKeys.splice(index, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelect (selectInfo) {
|
||||||
|
const props = this.props
|
||||||
|
if (props.selectable) {
|
||||||
|
// root menu
|
||||||
|
let selectedKeys = this.state.selectedKeys
|
||||||
|
const selectedKey = selectInfo.key
|
||||||
|
if (props.multiple) {
|
||||||
|
selectedKeys = selectedKeys.concat([selectedKey])
|
||||||
|
} else {
|
||||||
|
selectedKeys = [selectedKey]
|
||||||
|
}
|
||||||
|
if (!('selectedKeys' in props)) {
|
||||||
|
this.setState({
|
||||||
|
selectedKeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
props.onSelect({
|
||||||
|
...selectInfo,
|
||||||
|
selectedKeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick (e) {
|
||||||
|
this.props.onClick(e)
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenChange (e_) {
|
||||||
|
const props = this.props
|
||||||
|
const openKeys = this.state.openKeys.concat()
|
||||||
|
let changed = false
|
||||||
|
const processSingle = (e) => {
|
||||||
|
let oneChanged = false
|
||||||
|
if (e.open) {
|
||||||
|
oneChanged = openKeys.indexOf(e.key) === -1
|
||||||
|
if (oneChanged) {
|
||||||
|
openKeys.push(e.key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const index = openKeys.indexOf(e.key)
|
||||||
|
oneChanged = index !== -1
|
||||||
|
if (oneChanged) {
|
||||||
|
openKeys.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = changed || oneChanged
|
||||||
|
}
|
||||||
|
if (Array.isArray(e_)) {
|
||||||
|
// batch change call
|
||||||
|
e_.forEach(processSingle)
|
||||||
|
} else {
|
||||||
|
processSingle(e_)
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
if (!('openKeys' in this.props)) {
|
||||||
|
this.setState({ openKeys })
|
||||||
|
}
|
||||||
|
props.onOpenChange(openKeys)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDeselect (selectInfo) {
|
||||||
|
const props = this.props
|
||||||
|
if (props.selectable) {
|
||||||
|
const selectedKeys = this.state.selectedKeys.concat()
|
||||||
|
const selectedKey = selectInfo.key
|
||||||
|
const index = selectedKeys.indexOf(selectedKey)
|
||||||
|
if (index !== -1) {
|
||||||
|
selectedKeys.splice(index, 1)
|
||||||
|
}
|
||||||
|
if (!('selectedKeys' in props)) {
|
||||||
|
this.setState({
|
||||||
|
selectedKeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
props.onDeselect({
|
||||||
|
...selectInfo,
|
||||||
|
selectedKeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getOpenTransitionName () {
|
||||||
|
const props = this.props
|
||||||
|
let transitionName = props.openTransitionName
|
||||||
|
const animationName = props.openAnimation
|
||||||
|
if (!transitionName && typeof animationName === 'string') {
|
||||||
|
transitionName = `${props.prefixCls}-open-${animationName}`
|
||||||
|
}
|
||||||
|
return transitionName
|
||||||
|
},
|
||||||
|
|
||||||
|
isInlineMode () {
|
||||||
|
return this.props.mode === 'inline'
|
||||||
|
},
|
||||||
|
|
||||||
|
lastOpenSubMenu () {
|
||||||
|
let lastOpen = []
|
||||||
|
const { openKeys } = this.state
|
||||||
|
if (openKeys.length) {
|
||||||
|
lastOpen = this.getFlatInstanceArray().filter((c) => {
|
||||||
|
return c && openKeys.indexOf(c.props.eventKey) !== -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return lastOpen[0]
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMenuItem (c, i, subIndex) {
|
||||||
|
if (!c) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const state = this.state
|
||||||
|
const extraProps = {
|
||||||
|
openKeys: state.openKeys,
|
||||||
|
selectedKeys: state.selectedKeys,
|
||||||
|
triggerSubMenuAction: this.props.triggerSubMenuAction,
|
||||||
|
}
|
||||||
|
return this.renderCommonMenuItem(c, i, subIndex, extraProps)
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const props = { ...this.props }
|
||||||
|
props.className += ` ${props.prefixCls}-root`
|
||||||
|
return this.renderRoot(props)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Menu
|
|
@ -0,0 +1,165 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import createReactClass from 'create-react-class'
|
||||||
|
import KeyCode from 'rc-util/lib/KeyCode'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { noop } from './util'
|
||||||
|
|
||||||
|
/* eslint react/no-is-mounted:0 */
|
||||||
|
|
||||||
|
const MenuItem = createReactClass({
|
||||||
|
displayName: 'MenuItem',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
rootPrefixCls: PropTypes.string,
|
||||||
|
eventKey: PropTypes.string,
|
||||||
|
active: PropTypes.bool,
|
||||||
|
children: PropTypes.any,
|
||||||
|
selectedKeys: PropTypes.array,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
title: PropTypes.string,
|
||||||
|
onItemHover: PropTypes.func,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onDeselect: PropTypes.func,
|
||||||
|
parentMenu: PropTypes.object,
|
||||||
|
onDestroy: PropTypes.func,
|
||||||
|
onMouseEnter: PropTypes.func,
|
||||||
|
onMouseLeave: PropTypes.func,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps () {
|
||||||
|
return {
|
||||||
|
onSelect: noop,
|
||||||
|
onMouseEnter: noop,
|
||||||
|
onMouseLeave: noop,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
const props = this.props
|
||||||
|
if (props.onDestroy) {
|
||||||
|
props.onDestroy(props.eventKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyDown (e) {
|
||||||
|
const keyCode = e.keyCode
|
||||||
|
if (keyCode === KeyCode.ENTER) {
|
||||||
|
this.onClick(e)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseLeave (e) {
|
||||||
|
const { eventKey, onItemHover, onMouseLeave } = this.props
|
||||||
|
onItemHover({
|
||||||
|
key: eventKey,
|
||||||
|
hover: false,
|
||||||
|
})
|
||||||
|
onMouseLeave({
|
||||||
|
key: eventKey,
|
||||||
|
domEvent: e,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseEnter (e) {
|
||||||
|
const { eventKey, parentMenu, onItemHover, onMouseEnter } = this.props
|
||||||
|
if (parentMenu.subMenuInstance) {
|
||||||
|
parentMenu.subMenuInstance.clearSubMenuTimers()
|
||||||
|
}
|
||||||
|
onItemHover({
|
||||||
|
key: eventKey,
|
||||||
|
hover: true,
|
||||||
|
})
|
||||||
|
onMouseEnter({
|
||||||
|
key: eventKey,
|
||||||
|
domEvent: e,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick (e) {
|
||||||
|
const { eventKey, multiple, onClick, onSelect, onDeselect } = this.props
|
||||||
|
const selected = this.isSelected()
|
||||||
|
const info = {
|
||||||
|
key: eventKey,
|
||||||
|
keyPath: [eventKey],
|
||||||
|
item: this,
|
||||||
|
domEvent: e,
|
||||||
|
}
|
||||||
|
onClick(info)
|
||||||
|
if (multiple) {
|
||||||
|
if (selected) {
|
||||||
|
onDeselect(info)
|
||||||
|
} else {
|
||||||
|
onSelect(info)
|
||||||
|
}
|
||||||
|
} else if (!selected) {
|
||||||
|
onSelect(info)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getPrefixCls () {
|
||||||
|
return `${this.props.rootPrefixCls}-item`
|
||||||
|
},
|
||||||
|
|
||||||
|
getActiveClassName () {
|
||||||
|
return `${this.getPrefixCls()}-active`
|
||||||
|
},
|
||||||
|
|
||||||
|
getSelectedClassName () {
|
||||||
|
return `${this.getPrefixCls()}-selected`
|
||||||
|
},
|
||||||
|
|
||||||
|
getDisabledClassName () {
|
||||||
|
return `${this.getPrefixCls()}-disabled`
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelected () {
|
||||||
|
return this.props.selectedKeys.indexOf(this.props.eventKey) !== -1
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const props = this.props
|
||||||
|
const selected = this.isSelected()
|
||||||
|
const className = classNames(this.getPrefixCls(), props.className, {
|
||||||
|
[this.getActiveClassName()]: !props.disabled && props.active,
|
||||||
|
[this.getSelectedClassName()]: selected,
|
||||||
|
[this.getDisabledClassName()]: props.disabled,
|
||||||
|
})
|
||||||
|
const attrs = {
|
||||||
|
...props.attribute,
|
||||||
|
title: props.title,
|
||||||
|
className,
|
||||||
|
role: 'menuitem',
|
||||||
|
'aria-selected': selected,
|
||||||
|
'aria-disabled': props.disabled,
|
||||||
|
}
|
||||||
|
let mouseEvent = {}
|
||||||
|
if (!props.disabled) {
|
||||||
|
mouseEvent = {
|
||||||
|
onClick: this.onClick,
|
||||||
|
onMouseLeave: this.onMouseLeave,
|
||||||
|
onMouseEnter: this.onMouseEnter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const style = {
|
||||||
|
...props.style,
|
||||||
|
}
|
||||||
|
if (props.mode === 'inline') {
|
||||||
|
style.paddingLeft = props.inlineIndent * props.level
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
{...attrs}
|
||||||
|
{...mouseEvent}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
MenuItem.isMenuItem = 1
|
||||||
|
|
||||||
|
export default MenuItem
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import createReactClass from 'create-react-class'
|
||||||
|
|
||||||
|
const MenuItemGroup = createReactClass({
|
||||||
|
displayName: 'MenuItemGroup',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
renderMenuItem: PropTypes.func,
|
||||||
|
index: PropTypes.number,
|
||||||
|
className: PropTypes.string,
|
||||||
|
rootPrefixCls: PropTypes.string,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps () {
|
||||||
|
// To fix keyboard UX.
|
||||||
|
return { disabled: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
renderInnerMenuItem (item, subIndex) {
|
||||||
|
const { renderMenuItem, index } = this.props
|
||||||
|
return renderMenuItem(item, index, subIndex)
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const props = this.props
|
||||||
|
const { className = '', rootPrefixCls } = props
|
||||||
|
const titleClassName = `${rootPrefixCls}-item-group-title`
|
||||||
|
const listClassName = `${rootPrefixCls}-item-group-list`
|
||||||
|
return (
|
||||||
|
<li className={`${className} ${rootPrefixCls}-item-group`}>
|
||||||
|
<div
|
||||||
|
className={titleClassName}
|
||||||
|
title={typeof props.title === 'string' ? props.title : undefined}
|
||||||
|
>
|
||||||
|
{props.title}
|
||||||
|
</div>
|
||||||
|
<ul className={listClassName}>
|
||||||
|
{React.Children.map(props.children, this.renderInnerMenuItem)}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
MenuItemGroup.isMenuItemGroup = true
|
||||||
|
|
||||||
|
export default MenuItemGroup
|
|
@ -0,0 +1,298 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import KeyCode from 'rc-util/lib/KeyCode'
|
||||||
|
import createChainedFunction from 'rc-util/lib/createChainedFunction'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import scrollIntoView from 'dom-scroll-into-view'
|
||||||
|
import { getKeyFromChildrenIndex, loopMenuItem } from './util'
|
||||||
|
import DOMWrap from './DOMWrap'
|
||||||
|
|
||||||
|
function allDisabled (arr) {
|
||||||
|
if (!arr.length) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return arr.every(c => !!c.props.disabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActiveKey (props, originalActiveKey) {
|
||||||
|
let activeKey = originalActiveKey
|
||||||
|
const { children, eventKey } = props
|
||||||
|
if (activeKey) {
|
||||||
|
let found
|
||||||
|
loopMenuItem(children, (c, i) => {
|
||||||
|
if (c && !c.props.disabled && activeKey === getKeyFromChildrenIndex(c, eventKey, i)) {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (found) {
|
||||||
|
return activeKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activeKey = null
|
||||||
|
if (props.defaultActiveFirst) {
|
||||||
|
loopMenuItem(children, (c, i) => {
|
||||||
|
if (!activeKey && c && !c.props.disabled) {
|
||||||
|
activeKey = getKeyFromChildrenIndex(c, eventKey, i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return activeKey
|
||||||
|
}
|
||||||
|
return activeKey
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveRef (index, subIndex, c) {
|
||||||
|
if (c) {
|
||||||
|
if (subIndex !== undefined) {
|
||||||
|
this.instanceArray[index] = this.instanceArray[index] || []
|
||||||
|
this.instanceArray[index][subIndex] = c
|
||||||
|
} else {
|
||||||
|
this.instanceArray[index] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuMixin = {
|
||||||
|
propTypes: {
|
||||||
|
focusable: PropTypes.bool,
|
||||||
|
multiple: PropTypes.bool,
|
||||||
|
style: PropTypes.object,
|
||||||
|
defaultActiveFirst: PropTypes.bool,
|
||||||
|
visible: PropTypes.bool,
|
||||||
|
activeKey: PropTypes.string,
|
||||||
|
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
defaultOpenKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
openKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
children: PropTypes.any,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps () {
|
||||||
|
return {
|
||||||
|
prefixCls: 'rc-menu',
|
||||||
|
className: '',
|
||||||
|
mode: 'vertical',
|
||||||
|
level: 1,
|
||||||
|
inlineIndent: 24,
|
||||||
|
visible: true,
|
||||||
|
focusable: true,
|
||||||
|
style: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState () {
|
||||||
|
const props = this.props
|
||||||
|
return {
|
||||||
|
activeKey: getActiveKey(props, props.activeKey),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
let props
|
||||||
|
if ('activeKey' in nextProps) {
|
||||||
|
props = {
|
||||||
|
activeKey: getActiveKey(nextProps, nextProps.activeKey),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const originalActiveKey = this.state.activeKey
|
||||||
|
const activeKey = getActiveKey(nextProps, originalActiveKey)
|
||||||
|
// fix: this.setState(), parent.render(),
|
||||||
|
if (activeKey !== originalActiveKey) {
|
||||||
|
props = {
|
||||||
|
activeKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (props) {
|
||||||
|
this.setState(props)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldComponentUpdate (nextProps) {
|
||||||
|
return this.props.visible || nextProps.visible
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
this.instanceArray = []
|
||||||
|
},
|
||||||
|
|
||||||
|
// all keyboard events callbacks run from here at first
|
||||||
|
onKeyDown (e, callback) {
|
||||||
|
const keyCode = e.keyCode
|
||||||
|
let handled
|
||||||
|
this.getFlatInstanceArray().forEach((obj) => {
|
||||||
|
if (obj && obj.props.active && obj.onKeyDown) {
|
||||||
|
handled = obj.onKeyDown(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (handled) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
let activeItem = null
|
||||||
|
if (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN) {
|
||||||
|
activeItem = this.step(keyCode === KeyCode.UP ? -1 : 1)
|
||||||
|
}
|
||||||
|
if (activeItem) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
activeKey: activeItem.props.eventKey,
|
||||||
|
}, () => {
|
||||||
|
scrollIntoView(ReactDOM.findDOMNode(activeItem), ReactDOM.findDOMNode(this), {
|
||||||
|
onlyScrollIfNeeded: true,
|
||||||
|
})
|
||||||
|
// https://github.com/react-component/menu/commit/9899a9672f6f028ec3cdf773f1ecea5badd2d33e
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(activeItem)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return 1
|
||||||
|
} else if (activeItem === undefined) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
activeKey: null,
|
||||||
|
})
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemHover (e) {
|
||||||
|
const { key, hover } = e
|
||||||
|
this.setState({
|
||||||
|
activeKey: hover ? key : null,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getFlatInstanceArray () {
|
||||||
|
let instanceArray = this.instanceArray
|
||||||
|
const hasInnerArray = instanceArray.some((a) => {
|
||||||
|
return Array.isArray(a)
|
||||||
|
})
|
||||||
|
if (hasInnerArray) {
|
||||||
|
instanceArray = []
|
||||||
|
this.instanceArray.forEach((a) => {
|
||||||
|
if (Array.isArray(a)) {
|
||||||
|
instanceArray.push.apply(instanceArray, a)
|
||||||
|
} else {
|
||||||
|
instanceArray.push(a)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.instanceArray = instanceArray
|
||||||
|
}
|
||||||
|
return instanceArray
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCommonMenuItem (child, i, subIndex, extraProps) {
|
||||||
|
const state = this.state
|
||||||
|
const props = this.props
|
||||||
|
const key = getKeyFromChildrenIndex(child, props.eventKey, i)
|
||||||
|
const childProps = child.props
|
||||||
|
const isActive = key === state.activeKey
|
||||||
|
const newChildProps = {
|
||||||
|
mode: props.mode,
|
||||||
|
level: props.level,
|
||||||
|
inlineIndent: props.inlineIndent,
|
||||||
|
renderMenuItem: this.renderMenuItem,
|
||||||
|
rootPrefixCls: props.prefixCls,
|
||||||
|
index: i,
|
||||||
|
parentMenu: this,
|
||||||
|
ref: childProps.disabled ? undefined
|
||||||
|
: createChainedFunction(child.ref, saveRef.bind(this, i, subIndex)),
|
||||||
|
eventKey: key,
|
||||||
|
active: !childProps.disabled && isActive,
|
||||||
|
multiple: props.multiple,
|
||||||
|
onClick: this.onClick,
|
||||||
|
onItemHover: this.onItemHover,
|
||||||
|
openTransitionName: this.getOpenTransitionName(),
|
||||||
|
openAnimation: props.openAnimation,
|
||||||
|
subMenuOpenDelay: props.subMenuOpenDelay,
|
||||||
|
subMenuCloseDelay: props.subMenuCloseDelay,
|
||||||
|
forceSubMenuRender: props.forceSubMenuRender,
|
||||||
|
onOpenChange: this.onOpenChange,
|
||||||
|
onDeselect: this.onDeselect,
|
||||||
|
onDestroy: this.onDestroy,
|
||||||
|
onSelect: this.onSelect,
|
||||||
|
...extraProps,
|
||||||
|
}
|
||||||
|
if (props.mode === 'inline') {
|
||||||
|
newChildProps.triggerSubMenuAction = 'click'
|
||||||
|
}
|
||||||
|
return React.cloneElement(child, newChildProps)
|
||||||
|
},
|
||||||
|
|
||||||
|
renderRoot (props) {
|
||||||
|
this.instanceArray = []
|
||||||
|
const className = classNames(
|
||||||
|
props.prefixCls,
|
||||||
|
props.className,
|
||||||
|
`${props.prefixCls}-${props.mode}`,
|
||||||
|
)
|
||||||
|
const domProps = {
|
||||||
|
className,
|
||||||
|
role: 'menu',
|
||||||
|
'aria-activedescendant': '',
|
||||||
|
}
|
||||||
|
if (props.id) {
|
||||||
|
domProps.id = props.id
|
||||||
|
}
|
||||||
|
if (props.focusable) {
|
||||||
|
domProps.tabIndex = '0'
|
||||||
|
domProps.onKeyDown = this.onKeyDown
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
// ESLint is not smart enough to know that the type of `children` was checked.
|
||||||
|
/* eslint-disable */
|
||||||
|
<DOMWrap
|
||||||
|
style={props.style}
|
||||||
|
tag="ul"
|
||||||
|
hiddenClassName={`${props.prefixCls}-hidden`}
|
||||||
|
visible={props.visible}
|
||||||
|
{...domProps}
|
||||||
|
>
|
||||||
|
{React.Children.map(props.children, this.renderMenuItem)}
|
||||||
|
</DOMWrap>
|
||||||
|
/*eslint -enable */
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
step (direction) {
|
||||||
|
let children = this.getFlatInstanceArray()
|
||||||
|
const activeKey = this.state.activeKey
|
||||||
|
const len = children.length
|
||||||
|
if (!len) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (direction < 0) {
|
||||||
|
children = children.concat().reverse()
|
||||||
|
}
|
||||||
|
// find current activeIndex
|
||||||
|
let activeIndex = -1
|
||||||
|
children.every((c, ci) => {
|
||||||
|
if (c && c.props.eventKey === activeKey) {
|
||||||
|
activeIndex = ci
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if (!this.props.defaultActiveFirst && activeIndex !== -1) {
|
||||||
|
if (allDisabled(children.slice(activeIndex, len - 1))) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const start = (activeIndex + 1) % len
|
||||||
|
let i = start
|
||||||
|
for (; ;) {
|
||||||
|
const child = children[i]
|
||||||
|
if (!child || child.props.disabled) {
|
||||||
|
i = (i + 1 + len) % len
|
||||||
|
// complete a loop
|
||||||
|
if (i === start) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MenuMixin
|
|
@ -0,0 +1,443 @@
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import createReactClass from 'create-react-class'
|
||||||
|
import Trigger from 'rc-trigger'
|
||||||
|
import KeyCode from 'rc-util/lib/KeyCode'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import SubPopupMenu from './SubPopupMenu'
|
||||||
|
import placements from './placements'
|
||||||
|
import { noop, loopMenuItemRecusively } from './util'
|
||||||
|
|
||||||
|
let guid = 0
|
||||||
|
|
||||||
|
const popupPlacementMap = {
|
||||||
|
horizontal: 'bottomLeft',
|
||||||
|
vertical: 'rightTop',
|
||||||
|
'vertical-left': 'rightTop',
|
||||||
|
'vertical-right': 'leftTop',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubMenu = createReactClass({
|
||||||
|
displayName: 'SubMenu',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
parentMenu: PropTypes.object,
|
||||||
|
title: PropTypes.node,
|
||||||
|
children: PropTypes.any,
|
||||||
|
selectedKeys: PropTypes.array,
|
||||||
|
openKeys: PropTypes.array,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onOpenChange: PropTypes.func,
|
||||||
|
rootPrefixCls: PropTypes.string,
|
||||||
|
eventKey: PropTypes.string,
|
||||||
|
multiple: PropTypes.bool,
|
||||||
|
active: PropTypes.bool, // TODO: remove
|
||||||
|
onItemHover: PropTypes.func,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
triggerSubMenuAction: PropTypes.string,
|
||||||
|
onDeselect: PropTypes.func,
|
||||||
|
onDestroy: PropTypes.func,
|
||||||
|
onMouseEnter: PropTypes.func,
|
||||||
|
onMouseLeave: PropTypes.func,
|
||||||
|
onTitleMouseEnter: PropTypes.func,
|
||||||
|
onTitleMouseLeave: PropTypes.func,
|
||||||
|
onTitleClick: PropTypes.func,
|
||||||
|
},
|
||||||
|
|
||||||
|
isRootMenu: false,
|
||||||
|
|
||||||
|
getDefaultProps () {
|
||||||
|
return {
|
||||||
|
onMouseEnter: noop,
|
||||||
|
onMouseLeave: noop,
|
||||||
|
onTitleMouseEnter: noop,
|
||||||
|
onTitleMouseLeave: noop,
|
||||||
|
onTitleClick: noop,
|
||||||
|
title: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState () {
|
||||||
|
this.isSubMenu = 1
|
||||||
|
return {
|
||||||
|
defaultActiveFirst: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.componentDidUpdate()
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidUpdate () {
|
||||||
|
const { mode, parentMenu } = this.props
|
||||||
|
if (mode !== 'horizontal' || !parentMenu.isRootMenu || !this.isOpen()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.subMenuTitle || !this.menuInstance) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const popupMenu = ReactDOM.findDOMNode(this.menuInstance)
|
||||||
|
if (popupMenu.offsetWidth >= this.subMenuTitle.offsetWidth) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`
|
||||||
|
}, 0)
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
const { onDestroy, eventKey, parentMenu } = this.props
|
||||||
|
if (onDestroy) {
|
||||||
|
onDestroy(eventKey)
|
||||||
|
}
|
||||||
|
if (parentMenu.subMenuInstance === this) {
|
||||||
|
this.clearSubMenuTimers()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDestroy (key) {
|
||||||
|
this.props.onDestroy(key)
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyDown (e) {
|
||||||
|
const keyCode = e.keyCode
|
||||||
|
const menu = this.menuInstance
|
||||||
|
const isOpen = this.isOpen()
|
||||||
|
|
||||||
|
if (keyCode === KeyCode.ENTER) {
|
||||||
|
this.onTitleClick(e)
|
||||||
|
this.setState({
|
||||||
|
defaultActiveFirst: true,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCode === KeyCode.RIGHT) {
|
||||||
|
if (isOpen) {
|
||||||
|
menu.onKeyDown(e)
|
||||||
|
} else {
|
||||||
|
this.triggerOpenChange(true)
|
||||||
|
this.setState({
|
||||||
|
defaultActiveFirst: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (keyCode === KeyCode.LEFT) {
|
||||||
|
let handled
|
||||||
|
if (isOpen) {
|
||||||
|
handled = menu.onKeyDown(e)
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (!handled) {
|
||||||
|
this.triggerOpenChange(false)
|
||||||
|
handled = true
|
||||||
|
}
|
||||||
|
return handled
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOpen && (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN)) {
|
||||||
|
return menu.onKeyDown(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenChange (e) {
|
||||||
|
this.props.onOpenChange(e)
|
||||||
|
},
|
||||||
|
|
||||||
|
onPopupVisibleChange (visible) {
|
||||||
|
this.triggerOpenChange(visible, visible ? 'mouseenter' : 'mouseleave')
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseEnter (e) {
|
||||||
|
const { eventKey: key, onMouseEnter } = this.props
|
||||||
|
this.clearSubMenuLeaveTimer()
|
||||||
|
this.setState({
|
||||||
|
defaultActiveFirst: false,
|
||||||
|
})
|
||||||
|
onMouseEnter({
|
||||||
|
key,
|
||||||
|
domEvent: e,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onMouseLeave (e) {
|
||||||
|
const {
|
||||||
|
parentMenu,
|
||||||
|
eventKey,
|
||||||
|
onMouseLeave,
|
||||||
|
} = this.props
|
||||||
|
parentMenu.subMenuInstance = this
|
||||||
|
parentMenu.subMenuLeaveFn = () => {
|
||||||
|
// trigger mouseleave
|
||||||
|
onMouseLeave({
|
||||||
|
key: eventKey,
|
||||||
|
domEvent: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// prevent popup menu and submenu gap
|
||||||
|
parentMenu.subMenuLeaveTimer = setTimeout(parentMenu.subMenuLeaveFn, 100)
|
||||||
|
},
|
||||||
|
|
||||||
|
onTitleMouseEnter (domEvent) {
|
||||||
|
const { eventKey: key, onItemHover, onTitleMouseEnter } = this.props
|
||||||
|
this.clearSubMenuTitleLeaveTimer()
|
||||||
|
onItemHover({
|
||||||
|
key,
|
||||||
|
hover: true,
|
||||||
|
})
|
||||||
|
onTitleMouseEnter({
|
||||||
|
key,
|
||||||
|
domEvent,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onTitleMouseLeave (e) {
|
||||||
|
const { parentMenu, eventKey, onItemHover, onTitleMouseLeave } = this.props
|
||||||
|
parentMenu.subMenuInstance = this
|
||||||
|
parentMenu.subMenuTitleLeaveFn = () => {
|
||||||
|
onItemHover({
|
||||||
|
key: eventKey,
|
||||||
|
hover: false,
|
||||||
|
})
|
||||||
|
onTitleMouseLeave({
|
||||||
|
key: eventKey,
|
||||||
|
domEvent: e,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
parentMenu.subMenuTitleLeaveTimer = setTimeout(parentMenu.subMenuTitleLeaveFn, 100)
|
||||||
|
},
|
||||||
|
|
||||||
|
onTitleClick (e) {
|
||||||
|
const { props } = this
|
||||||
|
props.onTitleClick({
|
||||||
|
key: props.eventKey,
|
||||||
|
domEvent: e,
|
||||||
|
})
|
||||||
|
if (props.triggerSubMenuAction === 'hover') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.triggerOpenChange(!this.isOpen(), 'click')
|
||||||
|
this.setState({
|
||||||
|
defaultActiveFirst: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubMenuClick (info) {
|
||||||
|
this.props.onClick(this.addKeyPath(info))
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelect (info) {
|
||||||
|
this.props.onSelect(info)
|
||||||
|
},
|
||||||
|
|
||||||
|
onDeselect (info) {
|
||||||
|
this.props.onDeselect(info)
|
||||||
|
},
|
||||||
|
|
||||||
|
getPrefixCls () {
|
||||||
|
return `${this.props.rootPrefixCls}-submenu`
|
||||||
|
},
|
||||||
|
|
||||||
|
getActiveClassName () {
|
||||||
|
return `${this.getPrefixCls()}-active`
|
||||||
|
},
|
||||||
|
|
||||||
|
getDisabledClassName () {
|
||||||
|
return `${this.getPrefixCls()}-disabled`
|
||||||
|
},
|
||||||
|
|
||||||
|
getSelectedClassName () {
|
||||||
|
return `${this.getPrefixCls()}-selected`
|
||||||
|
},
|
||||||
|
|
||||||
|
getOpenClassName () {
|
||||||
|
return `${this.props.rootPrefixCls}-submenu-open`
|
||||||
|
},
|
||||||
|
|
||||||
|
saveMenuInstance (c) {
|
||||||
|
this.menuInstance = c
|
||||||
|
},
|
||||||
|
|
||||||
|
addKeyPath (info) {
|
||||||
|
return {
|
||||||
|
...info,
|
||||||
|
keyPath: (info.keyPath || []).concat(this.props.eventKey),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
triggerOpenChange (open, type) {
|
||||||
|
const key = this.props.eventKey
|
||||||
|
this.onOpenChange({
|
||||||
|
key,
|
||||||
|
item: this,
|
||||||
|
trigger: type,
|
||||||
|
open,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSubMenuTimers () {
|
||||||
|
this.clearSubMenuLeaveTimer()
|
||||||
|
this.clearSubMenuTitleLeaveTimer()
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSubMenuTitleLeaveTimer () {
|
||||||
|
const parentMenu = this.props.parentMenu
|
||||||
|
if (parentMenu.subMenuTitleLeaveTimer) {
|
||||||
|
clearTimeout(parentMenu.subMenuTitleLeaveTimer)
|
||||||
|
parentMenu.subMenuTitleLeaveTimer = null
|
||||||
|
parentMenu.subMenuTitleLeaveFn = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSubMenuLeaveTimer () {
|
||||||
|
const parentMenu = this.props.parentMenu
|
||||||
|
if (parentMenu.subMenuLeaveTimer) {
|
||||||
|
clearTimeout(parentMenu.subMenuLeaveTimer)
|
||||||
|
parentMenu.subMenuLeaveTimer = null
|
||||||
|
parentMenu.subMenuLeaveFn = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isChildrenSelected () {
|
||||||
|
const ret = { find: false }
|
||||||
|
loopMenuItemRecusively(this.props.children, this.props.selectedKeys, ret)
|
||||||
|
return ret.find
|
||||||
|
},
|
||||||
|
isOpen () {
|
||||||
|
return this.props.openKeys.indexOf(this.props.eventKey) !== -1
|
||||||
|
},
|
||||||
|
|
||||||
|
renderChildren (children) {
|
||||||
|
const props = this.props
|
||||||
|
const baseProps = {
|
||||||
|
mode: props.mode === 'horizontal' ? 'vertical' : props.mode,
|
||||||
|
visible: this.isOpen(),
|
||||||
|
level: props.level + 1,
|
||||||
|
inlineIndent: props.inlineIndent,
|
||||||
|
focusable: false,
|
||||||
|
onClick: this.onSubMenuClick,
|
||||||
|
onSelect: this.onSelect,
|
||||||
|
onDeselect: this.onDeselect,
|
||||||
|
onDestroy: this.onDestroy,
|
||||||
|
selectedKeys: props.selectedKeys,
|
||||||
|
eventKey: `${props.eventKey}-menu-`,
|
||||||
|
openKeys: props.openKeys,
|
||||||
|
openTransitionName: props.openTransitionName,
|
||||||
|
openAnimation: props.openAnimation,
|
||||||
|
onOpenChange: this.onOpenChange,
|
||||||
|
subMenuOpenDelay: props.subMenuOpenDelay,
|
||||||
|
subMenuCloseDelay: props.subMenuCloseDelay,
|
||||||
|
forceSubMenuRender: props.forceSubMenuRender,
|
||||||
|
triggerSubMenuAction: props.triggerSubMenuAction,
|
||||||
|
defaultActiveFirst: this.state.defaultActiveFirst,
|
||||||
|
multiple: props.multiple,
|
||||||
|
prefixCls: props.rootPrefixCls,
|
||||||
|
id: this._menuId,
|
||||||
|
ref: this.saveMenuInstance,
|
||||||
|
}
|
||||||
|
return <SubPopupMenu {...baseProps}>{children}</SubPopupMenu>
|
||||||
|
},
|
||||||
|
|
||||||
|
saveSubMenuTitle (subMenuTitle) {
|
||||||
|
this.subMenuTitle = subMenuTitle
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const props = this.props
|
||||||
|
const isOpen = this.isOpen()
|
||||||
|
const prefixCls = this.getPrefixCls()
|
||||||
|
const isInlineMode = props.mode === 'inline'
|
||||||
|
const className = classNames(prefixCls, `${prefixCls}-${props.mode}`, {
|
||||||
|
[props.className]: !!props.className,
|
||||||
|
[this.getOpenClassName()]: isOpen,
|
||||||
|
[this.getActiveClassName()]: props.active || (isOpen && !isInlineMode),
|
||||||
|
[this.getDisabledClassName()]: props.disabled,
|
||||||
|
[this.getSelectedClassName()]: this.isChildrenSelected(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!this._menuId) {
|
||||||
|
if (props.eventKey) {
|
||||||
|
this._menuId = `${props.eventKey}$Menu`
|
||||||
|
} else {
|
||||||
|
this._menuId = `$__$${++guid}$Menu`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mouseEvents = {}
|
||||||
|
let titleClickEvents = {}
|
||||||
|
let titleMouseEvents = {}
|
||||||
|
if (!props.disabled) {
|
||||||
|
mouseEvents = {
|
||||||
|
onMouseLeave: this.onMouseLeave,
|
||||||
|
onMouseEnter: this.onMouseEnter,
|
||||||
|
}
|
||||||
|
|
||||||
|
// only works in title, not outer li
|
||||||
|
titleClickEvents = {
|
||||||
|
onClick: this.onTitleClick,
|
||||||
|
}
|
||||||
|
titleMouseEvents = {
|
||||||
|
onMouseEnter: this.onTitleMouseEnter,
|
||||||
|
onMouseLeave: this.onTitleMouseLeave,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = {}
|
||||||
|
if (isInlineMode) {
|
||||||
|
style.paddingLeft = props.inlineIndent * props.level
|
||||||
|
}
|
||||||
|
const title = (
|
||||||
|
<div
|
||||||
|
ref={this.saveSubMenuTitle}
|
||||||
|
style={style}
|
||||||
|
className={`${prefixCls}-title`}
|
||||||
|
{...titleMouseEvents}
|
||||||
|
{...titleClickEvents}
|
||||||
|
aria-expanded={isOpen}
|
||||||
|
aria-owns={this._menuId}
|
||||||
|
aria-haspopup='true'
|
||||||
|
title={typeof props.title === 'string' ? props.title : undefined}
|
||||||
|
>
|
||||||
|
{props.title}
|
||||||
|
<i className={`${prefixCls}-arrow`} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
const children = this.renderChildren(props.children)
|
||||||
|
|
||||||
|
const getPopupContainer = props.parentMenu.isRootMenu
|
||||||
|
? props.parentMenu.props.getPopupContainer : triggerNode => triggerNode.parentNode
|
||||||
|
const popupPlacement = popupPlacementMap[props.mode]
|
||||||
|
const popupClassName = props.mode === 'inline' ? '' : props.popupClassName
|
||||||
|
return (
|
||||||
|
<li {...mouseEvents} className={className} style={props.style}>
|
||||||
|
{isInlineMode && title}
|
||||||
|
{isInlineMode && children}
|
||||||
|
{!isInlineMode && (
|
||||||
|
<Trigger
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
popupClassName={`${prefixCls}-popup ${popupClassName}`}
|
||||||
|
getPopupContainer={getPopupContainer}
|
||||||
|
builtinPlacements={placements}
|
||||||
|
popupPlacement={popupPlacement}
|
||||||
|
popupVisible={isOpen}
|
||||||
|
popup={children}
|
||||||
|
action={props.disabled ? [] : [props.triggerSubMenuAction]}
|
||||||
|
mouseEnterDelay={props.subMenuOpenDelay}
|
||||||
|
mouseLeaveDelay={props.subMenuCloseDelay}
|
||||||
|
onPopupVisibleChange={this.onPopupVisibleChange}
|
||||||
|
forceRender={props.forceSubMenuRender}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Trigger>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
SubMenu.isSubMenu = 1
|
||||||
|
|
||||||
|
export default SubMenu
|
|
@ -0,0 +1,99 @@
|
||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import createReactClass from 'create-react-class'
|
||||||
|
import Animate from 'rc-animate'
|
||||||
|
import MenuMixin from './MenuMixin'
|
||||||
|
|
||||||
|
const SubPopupMenu = createReactClass({
|
||||||
|
displayName: 'SubPopupMenu',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onDeselect: PropTypes.func,
|
||||||
|
onOpenChange: PropTypes.func,
|
||||||
|
onDestroy: PropTypes.func,
|
||||||
|
openTransitionName: PropTypes.string,
|
||||||
|
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
openKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
visible: PropTypes.bool,
|
||||||
|
children: PropTypes.any,
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [MenuMixin],
|
||||||
|
|
||||||
|
onDeselect (selectInfo) {
|
||||||
|
this.props.onDeselect(selectInfo)
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelect (selectInfo) {
|
||||||
|
this.props.onSelect(selectInfo)
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick (e) {
|
||||||
|
this.props.onClick(e)
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenChange (e) {
|
||||||
|
this.props.onOpenChange(e)
|
||||||
|
},
|
||||||
|
|
||||||
|
onDestroy (key) {
|
||||||
|
this.props.onDestroy(key)
|
||||||
|
},
|
||||||
|
|
||||||
|
getOpenTransitionName () {
|
||||||
|
return this.props.openTransitionName
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMenuItem (c, i, subIndex) {
|
||||||
|
if (!c) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const props = this.props
|
||||||
|
const extraProps = {
|
||||||
|
openKeys: props.openKeys,
|
||||||
|
selectedKeys: props.selectedKeys,
|
||||||
|
triggerSubMenuAction: props.triggerSubMenuAction,
|
||||||
|
}
|
||||||
|
return this.renderCommonMenuItem(c, i, subIndex, extraProps)
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const props = { ...this.props }
|
||||||
|
|
||||||
|
const haveRendered = this.haveRendered
|
||||||
|
this.haveRendered = true
|
||||||
|
|
||||||
|
this.haveOpened = this.haveOpened || props.visible || props.forceSubMenuRender
|
||||||
|
if (!this.haveOpened) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const transitionAppear = !(!haveRendered && props.visible && props.mode === 'inline')
|
||||||
|
|
||||||
|
props.className += ` ${props.prefixCls}-sub`
|
||||||
|
const animProps = {}
|
||||||
|
if (props.openTransitionName) {
|
||||||
|
animProps.transitionName = props.openTransitionName
|
||||||
|
} else if (typeof props.openAnimation === 'object') {
|
||||||
|
animProps.animation = { ...props.openAnimation }
|
||||||
|
if (!transitionAppear) {
|
||||||
|
delete animProps.animation.appear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animate
|
||||||
|
{...animProps}
|
||||||
|
showProp='visible'
|
||||||
|
component=''
|
||||||
|
transitionAppear={transitionAppear}
|
||||||
|
>
|
||||||
|
{this.renderRoot(props)}
|
||||||
|
</Animate>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SubPopupMenu
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Menu from './Menu'
|
||||||
|
import SubMenu from './SubMenu'
|
||||||
|
import MenuItem from './MenuItem'
|
||||||
|
import MenuItemGroup from './MenuItemGroup'
|
||||||
|
import Divider from './Divider'
|
||||||
|
|
||||||
|
export { SubMenu, MenuItem as Item, MenuItem, MenuItemGroup, MenuItemGroup as ItemGroup, Divider }
|
||||||
|
|
||||||
|
export default Menu
|
|
@ -0,0 +1,29 @@
|
||||||
|
const autoAdjustOverflow = {
|
||||||
|
adjustX: 1,
|
||||||
|
adjustY: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const placements = {
|
||||||
|
topLeft: {
|
||||||
|
points: ['bl', 'tl'],
|
||||||
|
overflow: autoAdjustOverflow,
|
||||||
|
offset: [0, -7],
|
||||||
|
},
|
||||||
|
bottomLeft: {
|
||||||
|
points: ['tl', 'bl'],
|
||||||
|
overflow: autoAdjustOverflow,
|
||||||
|
offset: [0, 7],
|
||||||
|
},
|
||||||
|
leftTop: {
|
||||||
|
points: ['tr', 'tl'],
|
||||||
|
overflow: autoAdjustOverflow,
|
||||||
|
offset: [-4, 0],
|
||||||
|
},
|
||||||
|
rightTop: {
|
||||||
|
points: ['tl', 'tr'],
|
||||||
|
overflow: autoAdjustOverflow,
|
||||||
|
offset: [4, 0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default placements
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export function noop () {
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKeyFromChildrenIndex (child, menuEventKey, index) {
|
||||||
|
const prefix = menuEventKey || ''
|
||||||
|
return child.key || `${prefix}item_${index}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loopMenuItem (children, cb) {
|
||||||
|
let index = -1
|
||||||
|
React.Children.forEach(children, (c) => {
|
||||||
|
index++
|
||||||
|
if (c && c.type && c.type.isMenuItemGroup) {
|
||||||
|
React.Children.forEach(c.props.children, (c2) => {
|
||||||
|
index++
|
||||||
|
cb(c2, index)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cb(c, index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loopMenuItemRecusively (children, keys, ret) {
|
||||||
|
if (!children || ret.find) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
React.Children.forEach(children, (c) => {
|
||||||
|
if (ret.find) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (c) {
|
||||||
|
const construt = c.type
|
||||||
|
if (!construt || !(construt.isSubMenu || construt.isMenuItem || construt.isMenuItemGroup)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (keys.indexOf(c.key) !== -1) {
|
||||||
|
ret.find = true
|
||||||
|
} else if (c.props.children) {
|
||||||
|
loopMenuItemRecusively(c.props.children, keys, ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -79,6 +79,7 @@
|
||||||
"eslint-plugin-vue": "^3.13.0",
|
"eslint-plugin-vue": "^3.13.0",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"omit.js": "^1.0.0",
|
"omit.js": "^1.0.0",
|
||||||
|
"vue-types": "^1.0.2",
|
||||||
"warning": "^3.0.0"
|
"warning": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue