ant-design-vue/components/animate/src/Animate.js

334 lines
9.0 KiB
JavaScript

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
}
}