);
- }
-}
+Box.propTypes = {
+ visible: PropTypes.bool,
+};
class Demo extends React.Component {
state = {
diff --git a/examples/transitionLeave.js b/examples/transitionLeave.js
index a49d616..6cd1fa0 100644
--- a/examples/transitionLeave.js
+++ b/examples/transitionLeave.js
@@ -1,9 +1,9 @@
/* eslint no-console:0, react/no-multi-comp:0 */
-import './assets/index.less';
import Animate from 'rc-animate';
import React from 'react';
import ReactDOM from 'react-dom';
+import './assets/index.less';
class Demo extends React.Component {
state = {
diff --git a/package.json b/package.json
index 9e3d1ff..c26d349 100644
--- a/package.json
+++ b/package.json
@@ -46,8 +46,8 @@
"jquery": "^3.3.1",
"pre-commit": "1.x",
"rc-test": "6.x",
- "rc-tools": "6.x",
- "react": "^16.0.0",
+ "rc-tools": "8.x",
+ "react": "^16.3.0",
"react-dom": "^16.0.0",
"velocity-animate": "~1.2.2"
},
@@ -56,7 +56,12 @@
],
"dependencies": {
"babel-runtime": "6.x",
- "css-animation": "^1.3.2",
- "prop-types": "15.x"
+ "classnames": "^2.2.5",
+ "component-classes": "^1.2.6",
+ "fbjs": "^0.8.16",
+ "prop-types": "15.x",
+ "raf": "^3.4.0",
+ "rc-util": "^4.5.0",
+ "react-lifecycles-compat": "^3.0.4"
}
}
diff --git a/src/Animate.js b/src/Animate.js
deleted file mode 100644
index 13ae9a6..0000000
--- a/src/Animate.js
+++ /dev/null
@@ -1,336 +0,0 @@
-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 isAnimate = true; // eslint-disable-line
-
- 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,
- children: PropTypes.node,
- }
-
- 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(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
children');
- }
- return (
- this.childrenRefs[child.key] = node}
- animation={props.animation}
- transitionName={props.transitionName}
- transitionEnter={props.transitionEnter}
- transitionAppear={props.transitionAppear}
- transitionLeave={props.transitionLeave}
- >
- {child}
-
- );
- });
- }
- const Component = props.component;
- if (Component) {
- let passedProps = props;
- if (typeof Component === 'string') {
- passedProps = {
- className: props.className,
- style: props.style,
- ...props.componentProps,
- };
- }
- return {children};
- }
- return children[0] || null;
- }
-}
diff --git a/src/Animate.jsx b/src/Animate.jsx
new file mode 100644
index 0000000..a62c178
--- /dev/null
+++ b/src/Animate.jsx
@@ -0,0 +1,183 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { polyfill } from 'react-lifecycles-compat';
+import toArray from 'rc-util/lib/Children/toArray';
+import warning from 'fbjs/lib/warning';
+
+import AnimateChild from './AnimateChild';
+import { cloneProps, mergeChildren } from './util';
+
+const defaultKey = `rc_animate_${Date.now()}`;
+const clonePropList = ['children'];
+
+
+/**
+ * Default use `AnimateChild` as component.
+ * Here can also pass customize `ChildComponent` for test usage.
+ */
+export function genAnimate(ChildComponent) {
+ class Animate extends React.Component {
+ // [Legacy] Not sure usage
+ // commit: https://github.com/react-component/animate/commit/0a1cbfd647407498b10a8c6602a2dea80b42e324
+ static isAnimate = true; // eslint-disable-line
+
+ 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,
+ children: PropTypes.node,
+ style: PropTypes.object,
+ className: PropTypes.string,
+ }
+
+ static defaultProps = {
+ animation: {},
+ component: 'span',
+ componentProps: {},
+ transitionEnter: true,
+ transitionLeave: true,
+ transitionAppear: false,
+ }
+
+ state = {
+ appeared: true,
+ mergedChildren: [],
+ };
+
+ static getDerivedStateFromProps(nextProps, prevState) {
+ const { prevProps = {} } = prevState;
+ const newState = {
+ prevProps: cloneProps(nextProps, clonePropList),
+ };
+ const { showProp } = nextProps;
+
+ function processState(propName, updater) {
+ if (prevProps[propName] !== nextProps[propName]) {
+ updater(nextProps[propName]);
+ return true;
+ }
+ return false;
+ }
+
+ processState('children', (children) => {
+ const currentChildren = toArray(children).filter(node => node);
+ const prevChildren = prevState.mergedChildren.filter((node) => {
+ // Remove prev child if not show anymore
+ if (
+ currentChildren.every(({ key }) => key !== node.key) &&
+ showProp && !node.props[showProp]
+ ) {
+ return false;
+ }
+ return true;
+ });
+
+ // Merge prev children to keep the animation
+ newState.mergedChildren = mergeChildren(prevChildren, currentChildren);
+ });
+
+ return newState;
+ }
+
+ componentDidMount() {
+ // No need to re-render
+ this.state.appeared = false;
+ }
+
+ onChildLeaved = (key) => {
+ // Remove child which not exist anymore
+ if (!this.hasChild(key)) {
+ const { mergedChildren } = this.state;
+ this.setState({
+ mergedChildren: mergedChildren.filter(node => node.key !== key),
+ });
+ }
+ };
+
+ hasChild = (key) => {
+ const { children } = this.props;
+
+ return toArray(children).some(node => node && node.key === key);
+ };
+
+ render() {
+ const { appeared, mergedChildren } = this.state;
+ const {
+ component: Component, componentProps,
+ className, style, showProp,
+ } = this.props;
+
+ const $children = mergedChildren.map((node) => {
+ if (mergedChildren.length > 1 && !node.key) {
+ warning(false, 'must set key for children');
+ return null;
+ }
+
+ let show = true;
+
+ if (!this.hasChild(node.key)) {
+ show = false;
+ } else if (showProp) {
+ show = node.props[showProp];
+ }
+
+ const key = node.key || defaultKey;
+
+ return (
+
+ {node}
+
+ );
+ });
+
+ // Wrap with component
+ if (Component) {
+ let passedProps = this.props;
+ if (typeof Component === 'string') {
+ passedProps = {
+ className,
+ style,
+ ...componentProps,
+ };
+ }
+
+ return (
+
+ {$children}
+
+ );
+ }
+
+ return $children[0] || null;
+ }
+ }
+
+ polyfill(Animate);
+
+ return Animate;
+}
+
+export default genAnimate(AnimateChild);
\ No newline at end of file
diff --git a/src/AnimateChild.js b/src/AnimateChild.js
deleted file mode 100644
index 7a16321..0000000
--- a/src/AnimateChild.js
+++ /dev/null
@@ -1,86 +0,0 @@
-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;
- }
-}
diff --git a/src/AnimateChild.jsx b/src/AnimateChild.jsx
new file mode 100644
index 0000000..2c7668d
--- /dev/null
+++ b/src/AnimateChild.jsx
@@ -0,0 +1,433 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+import { polyfill } from 'react-lifecycles-compat';
+import classNames from 'classnames';
+import classes from 'component-classes';
+import raf from 'raf';
+
+import {
+ getStyleValue,
+ cloneProps, getTransitionName,
+ supportTransition, animationEndName, transitionEndName,
+} from './util';
+
+const clonePropList = [
+ 'appeared',
+ 'show',
+ 'exclusive',
+ 'children',
+ 'animation',
+];
+
+/**
+ * AnimateChild only accept one child node.
+ * `transitionSupport` is used for none transition test case.
+ * Default we use browser transition event support check.
+ */
+export function genAnimateChild(transitionSupport) {
+ class AnimateChild extends React.Component {
+ static propTypes = {
+ transitionName: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.object,
+ ]),
+ transitionAppear: PropTypes.bool,
+ transitionEnter: PropTypes.bool,
+ transitionLeave: PropTypes.bool,
+ exclusive: PropTypes.bool,
+ appeared: PropTypes.bool,
+ showProp: PropTypes.string,
+
+ animateKey: PropTypes.any,
+ animation: PropTypes.object,
+ onChildLeaved: PropTypes.func,
+
+ onEnd: PropTypes.func,
+ onAppear: PropTypes.func,
+ onEnter: PropTypes.func,
+ onLeave: PropTypes.func,
+ }
+
+ constructor() {
+ super();
+
+ // [Legacy] Since old code addListener on the element.
+ // To avoid break the behaviour that component not handle animation/transition
+ // also can handle the animate, let keep the logic.
+ this.$prevEle = null;
+
+ this.currentEvent = null;
+ this.timeout = null;
+ }
+
+ state = {
+ child: null,
+
+ eventQueue: [],
+ eventActive: false,
+ }
+
+ static getDerivedStateFromProps(nextProps, prevState) {
+ const { prevProps = {} } = prevState;
+ const { appeared } = nextProps;
+
+ const newState = {
+ prevProps: cloneProps(nextProps, clonePropList),
+ };
+
+ function processState(propName, updater) {
+ if (prevProps[propName] !== nextProps[propName]) {
+ if (updater) {
+ updater(nextProps[propName]);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ function pushEvent(eventType) {
+ let eventQueue = newState.eventQueue || prevState.eventQueue.slice();
+ const matchIndex = eventQueue.indexOf(eventType);
+
+ // Clean the rest event if eventType match
+ if (matchIndex !== -1) {
+ eventQueue = eventQueue.slice(0, matchIndex);
+ }
+
+ eventQueue.push(eventType);
+ newState.eventQueue = eventQueue;
+ }
+
+ // Child update. Only set child.
+ processState('children', (child) => {
+ newState.child = child;
+ });
+
+ processState('appeared', (isAppeared) => {
+ if (isAppeared) {
+ pushEvent('appear');
+ }
+ });
+
+ // Show update
+ processState('show', (show) => {
+ if (!appeared) {
+ if (show) {
+ pushEvent('enter');
+ } else {
+ pushEvent('leave');
+ }
+ }
+ });
+
+ return newState;
+ }
+
+ componentDidMount() {
+ this.onDomUpdated();
+ }
+
+ componentDidUpdate() {
+ this.onDomUpdated();
+ }
+
+ componentWillUnmount() {
+ clearTimeout(this.timeout);
+ this._destroy = true;
+ this.cleanDomEvent();
+ }
+
+ onDomUpdated = () => {
+ const { eventActive } = this.state;
+ const {
+ transitionName, animation, onChildLeaved, animateKey,
+ } = this.props;
+
+ const $ele = this.getDomElement();
+
+ // Skip if dom element not ready
+ if (!$ele) return;
+
+ // [Legacy] Add animation/transition event by dom level
+ if (transitionSupport && this.$prevEle !== $ele) {
+ this.cleanDomEvent();
+
+ this.$prevEle = $ele;
+ this.$prevEle.addEventListener(animationEndName, this.onMotionEnd);
+ this.$prevEle.addEventListener(transitionEndName, this.onMotionEnd);
+ }
+
+ const currentEvent = this.getCurrentEvent();
+ if (currentEvent.empty) {
+ // Additional process the leave event
+ if (currentEvent.lastEventType === 'leave') {
+ onChildLeaved(animateKey);
+ }
+ return;
+ }
+
+ const { eventType, restQueue } = currentEvent;
+ const nodeClasses = classes($ele);
+
+ // [Legacy] Since origin code use js to set `className`.
+ // This caused that any component without support `className` can be forced set.
+ // Let's keep the logic.
+ function legacyAppendClass() {
+ if (!transitionSupport) return;
+
+ const basicClassName = getTransitionName(transitionName, `${eventType}`);
+ if (basicClassName) nodeClasses.add(basicClassName);
+
+ if (eventActive) {
+ const activeClassName = getTransitionName(transitionName, `${eventType}-active`);
+ if (activeClassName) nodeClasses.add(activeClassName);
+ }
+
+ }
+
+ if (this.currentEvent && this.currentEvent.type === eventType) {
+ legacyAppendClass();
+ return;
+ }
+
+ // Clear timeout for legacy check
+ clearTimeout(this.timeout);
+
+ // Clean up last event environment
+ if (this.currentEvent && this.currentEvent.animateObj && this.currentEvent.animateObj.stop) {
+ this.currentEvent.animateObj.stop();
+ }
+
+ // Clean up last transition class
+ if (this.currentEvent) {
+ const basicClassName = getTransitionName(transitionName, `${this.currentEvent.type}`);
+ const activeClassName = getTransitionName(transitionName, `${this.currentEvent.type}-active`);
+ if (basicClassName) nodeClasses.remove(basicClassName);
+ if (activeClassName) nodeClasses.remove(activeClassName);
+ }
+
+ // New event come
+ this.currentEvent = {
+ type: eventType,
+ };
+
+ const animationHandler = (animation || {})[eventType];
+ // =============== Check if has customize animation ===============
+ if (animationHandler) {
+ this.currentEvent.animateObj = animationHandler($ele, () => {
+ this.onMotionEnd({ target: $ele });
+ });
+
+ // Do next step if not animate object provided
+ if (!this.currentEvent.animateObj) {
+ this.nextEvent(restQueue);
+ }
+
+ // ==================== Use transition instead ====================
+ } else if (transitionSupport) {
+ legacyAppendClass();
+ if (!eventActive) {
+ // Trigger `eventActive` in next frame
+ raf(() => {
+ if (this.currentEvent && this.currentEvent.type === eventType && !this._destroy) {
+ this.setState({ eventActive: true }, () => {
+ // [Legacy] Handle timeout if browser transition event not handle
+ const transitionDelay = getStyleValue($ele, 'transition-delay') || 0;
+ const transitionDuration = getStyleValue($ele, 'transition-duration') || 0;
+ const animationDelay = getStyleValue($ele, 'animation-delay') || 0;
+ const animationDuration = getStyleValue($ele, 'animation-duration') || 0;
+ const totalTime = Math.max(
+ transitionDuration + transitionDelay,
+ animationDuration + animationDelay
+ );
+
+ if (totalTime >= 0) {
+ this.timeout = setTimeout(() => {
+ this.onMotionEnd({ target: $ele });
+ }, totalTime * 1000);
+ }
+ });
+ }
+ });
+ }
+
+ // ======================= Just next action =======================
+ } else {
+ this.onMotionEnd({ target: $ele });
+ }
+ }
+
+ onMotionEnd = ({ target }) => {
+ const {
+ transitionName, onChildLeaved, animateKey,
+ onAppear, onEnter, onLeave, onEnd,
+ } = this.props;
+ const currentEvent = this.getCurrentEvent();
+ if (currentEvent.empty) return;
+
+ // Clear timeout for legacy check
+ clearTimeout(this.timeout);
+
+ const { restQueue } = currentEvent;
+
+ const $ele = this.getDomElement();
+ if (!this.currentEvent || $ele !== target) return;
+
+ if (this.currentEvent.animateObj && this.currentEvent.animateObj.stop) {
+ this.currentEvent.animateObj.stop();
+ }
+
+ // [Legacy] Same as above, we need call js to remove the class
+ if (transitionSupport && this.currentEvent) {
+ const basicClassName = getTransitionName(transitionName, this.currentEvent.type);
+ const activeClassName = getTransitionName(transitionName, `${this.currentEvent.type}-active`);
+
+ const nodeClasses = classes($ele);
+ if (basicClassName) nodeClasses.remove(basicClassName);
+ if (activeClassName) nodeClasses.remove(activeClassName);
+ }
+
+ // Additional process the leave event
+ if (this.currentEvent && this.currentEvent.type === 'leave') {
+ onChildLeaved(animateKey);
+ }
+
+ // [Legacy] Trigger on event when it's last event
+ if (this.currentEvent && !restQueue.length) {
+ if (this.currentEvent.type === 'appear' && onAppear) {
+ onAppear(animateKey);
+ } else if (this.currentEvent.type === 'enter' && onEnter) {
+ onEnter(animateKey);
+ } else if (this.currentEvent.type === 'leave' && onLeave) {
+ onLeave(animateKey);
+ }
+
+ if (onEnd) {
+ // OnEnd(key, isShow)
+ onEnd(animateKey, this.currentEvent.type !== 'leave');
+ }
+ }
+
+ this.currentEvent = null;
+
+ // Next queue
+ this.nextEvent(restQueue);
+ };
+
+ getDomElement = () => {
+ if (this._destroy) return null;
+ return ReactDOM.findDOMNode(this);
+ };
+
+ getCurrentEvent = () => {
+ const { eventQueue = [] } = this.state;
+ const {
+ animation, exclusive,
+ transitionAppear, transitionEnter, transitionLeave,
+ } = this.props;
+
+ function hasEventHandler(eventType) {
+ return (eventType === 'appear' && (transitionAppear || animation.appear)) ||
+ (eventType === 'enter' && (transitionEnter || animation.enter)) ||
+ (eventType === 'leave' && (transitionLeave || animation.leave));
+ }
+
+ let event = null;
+ // If is exclusive, only check the last event
+ if (exclusive) {
+ const eventType = eventQueue[eventQueue.length - 1];
+ if (hasEventHandler(eventType)) {
+ event = {
+ eventType,
+ restQueue: [],
+ };
+ }
+ } else {
+ // Loop check the queue until find match
+ let cloneQueue = eventQueue.slice();
+ while (cloneQueue.length) {
+ const [eventType, ...restQueue] = cloneQueue;
+ if (hasEventHandler(eventType)) {
+ event = {
+ eventType,
+ restQueue,
+ };
+ break;
+ }
+ cloneQueue = restQueue;
+ }
+ }
+
+ if (!event) {
+ event = {
+ empty: true,
+ lastEventType: eventQueue[eventQueue.length - 1],
+ };
+ }
+
+ return event;
+ };
+
+ nextEvent = (restQueue) => {
+ // Next queue
+ if (!this._destroy) {
+ this.setState({
+ eventQueue: restQueue,
+ eventActive: false,
+ });
+ }
+ };
+
+ cleanDomEvent = () => {
+ if (this.$prevEle && transitionSupport) {
+ this.$prevEle.removeEventListener(animationEndName, this.onMotionEnd);
+ this.$prevEle.removeEventListener(transitionEndName, this.onMotionEnd);
+ }
+ };
+
+ render() {
+ const { child, eventActive } = this.state;
+ const { showProp, transitionName } = this.props;
+ const { className } = child.props || {};
+
+ const currentEvent = this.getCurrentEvent();
+
+ // Class name
+ const connectClassName = (transitionSupport && this.currentEvent) ? classNames(
+ className,
+ getTransitionName(transitionName, this.currentEvent.type),
+ eventActive && getTransitionName(transitionName, `${this.currentEvent.type}-active`),
+ ) : className;
+
+ let show = true;
+
+ // Keep show when is in transition or has customize animate
+ if (transitionSupport && (
+ !currentEvent.empty ||
+ (this.currentEvent && this.currentEvent.animateObj)
+ )) {
+ show = true;
+ } else {
+ show = child.props[showProp];
+ }
+
+ // Clone child
+ const newChildProps = {
+ className: connectClassName,
+ };
+
+ if (showProp) {
+ newChildProps[showProp] = show;
+ }
+
+ return React.cloneElement(child, newChildProps);
+ }
+ }
+
+ polyfill(AnimateChild);
+
+ return AnimateChild;
+}
+
+export default genAnimateChild(supportTransition);
\ No newline at end of file
diff --git a/src/ChildrenUtils.js b/src/ChildrenUtils.js
deleted file mode 100644
index 2fc371f..0000000
--- a/src/ChildrenUtils.js
+++ /dev/null
@@ -1,101 +0,0 @@
-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 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;
-}
diff --git a/src/util.js b/src/util.js
index d84f1e6..972f4a6 100644
--- a/src/util.js
+++ b/src/util.js
@@ -1,21 +1,163 @@
-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;
+import toArray from 'rc-util/lib/Children/toArray';
+import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
+
+// =================== Style ====================
+const stylePrefixes = ['-webkit-', '-moz-', '-o-', 'ms-', ''];
+
+export function getStyleProperty(node, name) {
+ // old ff need null, https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
+ const style = window.getComputedStyle(node, null);
+ let ret = '';
+ for (let i = 0; i < stylePrefixes.length; i++) {
+ ret = style.getPropertyValue(stylePrefixes[i] + name);
+ if (ret) {
+ break;
+ }
+ }
+ return ret;
+}
+
+export function getStyleValue(node, name) {
+ return parseFloat(getStyleProperty(node, name));
+}
+
+// ================= Transition =================
+// Event wrapper. Copy from react source code
+function makePrefixMap(styleProp, eventName) {
+ const prefixes = {};
+
+ prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
+ prefixes[`Webkit${styleProp}`] = `webkit${eventName}`;
+ prefixes[`Moz${styleProp}`] = `moz${eventName}`;
+ prefixes[`ms${styleProp}`] = `MS${eventName}`;
+ prefixes[`O${styleProp}`] = `o${eventName.toLowerCase()}`;
+
+ return prefixes;
+}
+
+export function getVendorPrefixes(domSupport, win) {
+ const prefixes = {
+ animationend: makePrefixMap('Animation', 'AnimationEnd'),
+ transitionend: makePrefixMap('Transition', 'TransitionEnd'),
+ };
+
+ if (domSupport) {
+ if (!('AnimationEvent' in win)) {
+ delete prefixes.animationend.animation;
+ }
+
+ if (!('TransitionEvent' in win)) {
+ delete prefixes.transitionend.transition;
+ }
+ }
+
+ return prefixes;
+}
+
+const vendorPrefixes = getVendorPrefixes(canUseDOM, window);
+
+let style = {};
+
+if (canUseDOM) {
+ style = document.createElement('div').style;
+}
+
+const prefixedEventNames = {};
+
+export function getVendorPrefixedEventName(eventName) {
+ if (prefixedEventNames[eventName]) {
+ return prefixedEventNames[eventName];
+ }
+
+ const prefixMap = vendorPrefixes[eventName];
+
+ if (prefixMap) {
+ const stylePropList = Object.keys(prefixMap);
+ const len = stylePropList.length;
+ for (let i = 0; i < len; i += 1) {
+ const styleProp = stylePropList[i];
+ if (Object.prototype.hasOwnProperty.call(prefixMap, styleProp) && styleProp in style) {
+ prefixedEventNames[eventName] = prefixMap[styleProp];
+ return prefixedEventNames[eventName];
+ }
+ }
+ }
+
+ return '';
+}
+
+export const animationEndName = getVendorPrefixedEventName('animationend');
+export const transitionEndName = getVendorPrefixedEventName('transitionend');
+export const supportTransition = !!(animationEndName && transitionEndName);
+
+// ==================== Node ====================
+/**
+ * [Legacy] Find the same children in both prev & next list.
+ * Insert not find one before the find one, otherwise in the end. For example:
+ * - prev: [1,2,3]
+ * - next: [2,4]
+ * -> [1,2,4,3]
+ */
+export function mergeChildren(prev, next) {
+ const prevList = toArray(prev);
+ const nextList = toArray(next);
+
+ // Skip if is single children
+ if (
+ prevList.length === 1 && nextList.length === 1 &&
+ prevList[0].key === nextList[0].key
+ ) {
+ return nextList;
+ }
+
+ let mergeList = [];
+ const nextChildrenMap = {};
+ let missMatchChildrenList = [];
+
+ // Fill matched prev node into next node map
+ prevList.forEach((prevNode) => {
+ if (prevNode && nextList.some(({ key }) => key === prevNode.key)) {
+ if (missMatchChildrenList.length) {
+ nextChildrenMap[prevNode.key] = missMatchChildrenList;
+ missMatchChildrenList = [];
+ }
+ } else {
+ missMatchChildrenList.push(prevNode);
+ }
+ });
+
+ // Insert prev node before the matched next node
+ nextList.forEach((nextNode) => {
+ if (nextNode && nextChildrenMap[nextNode.key]) {
+ mergeList = mergeList.concat(nextChildrenMap[nextNode.key]);
+ }
+ mergeList.push(nextNode);
+ });
+
+ mergeList = mergeList.concat(missMatchChildrenList);
+
+ return mergeList;
+}
+
+export function cloneProps(props, propList) {
+ const newProps = {};
+ propList.forEach((prop) => {
+ if (prop in props) {
+ newProps[prop] = props[prop];
+ }
+ });
+
+ return newProps;
+}
+
+export function getTransitionName(transitionName, transitionType) {
+ if (!transitionName) return null;
+
+ if (typeof transitionName === 'object') {
+ const type = transitionType.replace(/-\w/g, (match) => match[1].toUpperCase());
+ return transitionName[type];
+ }
+
+ return `${transitionName}-${transitionType}`;
+}
+
diff --git a/tests/basic.spec.js b/tests/basic.spec.js
new file mode 100644
index 0000000..cb97eee
--- /dev/null
+++ b/tests/basic.spec.js
@@ -0,0 +1,333 @@
+/* eslint react/no-render-return-value:0, react/prefer-stateless-function:0, react/no-multi-comp:0 */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+import TestUtils from 'react-dom/test-utils';
+import expect from 'expect.js';
+import Animate from '../src/Animate';
+import AnimateChild from '../src/AnimateChild';
+import { getVendorPrefixes, getVendorPrefixedEventName, transitionEndName, mergeChildren } from '../src/util';
+
+import './index.spec.css';
+
+describe('basic', () => {
+ class SimpleWrapper extends React.Component {
+ state = { show: this.props.show || false };
+
+ render() {
+ const { ...props } = this.props;
+ delete props.show;
+ return (
+
+ {this.state.show && }
+
+ );
+ }
+ }
+ SimpleWrapper.propTypes = {
+ show: PropTypes.bool,
+ };
+
+ let div;
+ beforeEach(() => {
+ div = document.createElement('div');
+ });
+
+ afterEach(() => {
+ try {
+ ReactDOM.unmountComponentAtNode(div);
+ } catch (e) {
+ // Do nothing
+ }
+ });
+
+ it('exception if children has key', () => {
+ const instance = ReactDOM.render(
+
+
+
+ , div);
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'li').length).to.be(2);
+ });
+
+ it('exception if children without key', () => {
+ const instance = ReactDOM.render(
+
+
+
+ , div);
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'li').length).to.be(0);
+ });
+
+ it('transitionName is an object', (done) => {
+ const transitionName = {
+ appear: 'trans-appear',
+ appearActive: 'trans-appear-active',
+ };
+
+ const instance = ReactDOM.render(
+
+
+ , div);
+
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'div')[0].className)
+ .to.contain('trans-appear');
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'div')[0].className)
+ .not.to.contain('trans-appear-active');
+
+ setTimeout(() => {
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'div')[0].className)
+ .to.contain('trans-appear');
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'div')[0].className)
+ .to.contain('trans-appear-active');
+ done();
+ }, 0);
+ });
+
+ it('transition last callback', (done) => {
+ function createOnCalled(type) {
+ function callback() {
+ callback.times += 1;
+ }
+ callback.type = type;
+ callback.times = 0;
+
+ return callback;
+ }
+
+ const onAppear = createOnCalled('appear');
+ const onEnter = createOnCalled('enter');
+ const onLeave = createOnCalled('leave');
+ const onEnd = createOnCalled('end');
+
+ const instance = ReactDOM.render(, div);
+
+ // What event state change, this will only trigger once when final transition finished
+ setTimeout(() => {
+ expect(onAppear.times).to.be(1);
+ expect(onEnter.times).to.be(0);
+ expect(onLeave.times).to.be(0);
+ expect(onEnd.times).to.be(1);
+
+ instance.setState({ show: false }, () => {
+ expect(onAppear.times).to.be(1);
+ expect(onEnter.times).to.be(0);
+ expect(onLeave.times).to.be(0);
+ expect(onEnd.times).to.be(1);
+
+ setTimeout(() => {
+ expect(onAppear.times).to.be(1);
+ expect(onEnter.times).to.be(0);
+ expect(onLeave.times).to.be(1);
+ expect(onEnd.times).to.be(2);
+
+ instance.setState({ show: true }, () => {
+ const child = TestUtils.findRenderedComponentWithType(instance, AnimateChild);
+
+ instance.setState({ show: false }, () => {
+ expect(child.state.eventQueue).to.eql(['enter', 'leave']);
+
+ instance.setState({ show: true }, () => {
+ expect(onAppear.times).to.be(1);
+ expect(onEnter.times).to.be(0);
+ expect(onLeave.times).to.be(1);
+ expect(onEnd.times).to.be(2);
+
+ expect(child.state.eventQueue).to.eql(['enter']);
+
+ setTimeout(() => {
+ expect(child.state.eventQueue).to.eql([]);
+
+ expect(onAppear.times).to.be(1);
+ expect(onEnter.times).to.be(1);
+ expect(onLeave.times).to.be(1);
+ expect(onEnd.times).to.be(3);
+
+ done();
+ }, 100);
+ });
+ });
+ });
+ }, 100);
+ });
+
+
+ }, 100);
+
+
+ });
+
+ it('clean up when hidden children removed', (done) => {
+ // Stateless component not work for `scryRenderedComponentsWithType`
+ class LI extends React.Component {
+ render() {
+ const {show} = this.props;
+ return (
+ show ? : null
+ )
+ }
+ }
+
+ LI.propTypes = {
+ show: PropTypes.bool,
+ };
+
+ class UL extends React.Component {
+ state = {
+ show: true,
+ propShow: true,
+ };
+
+ render() {
+ const { show, propShow } = this.state;
+ return (
+
+ {show && }
+
+ );
+ }
+ }
+
+ const instance = ReactDOM.render(, div);
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'li').length).to.be(1);
+
+ instance.setState({ propShow: false });
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'li').length).to.be(1);
+
+ setTimeout(() => {
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'li').length).to.be(0);
+ expect(TestUtils.scryRenderedComponentsWithType(instance, LI).length).to.be(1);
+
+ instance.setState({ show: false });
+ setTimeout(() => {
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'li').length).to.be(0);
+ expect(TestUtils.scryRenderedComponentsWithType(instance, LI).length).to.be(0);
+ done();
+ }, 100);
+ }, 100);
+ });
+
+ describe('de-dup event', () => {
+ it('without exclusive', (done) => {
+ const instance = ReactDOM.render(, div);
+
+ instance.setState({ show: true }, () => {
+ // Enter
+ const child = TestUtils.findRenderedComponentWithType(instance, AnimateChild);
+ expect(child.state.eventQueue).to.eql(['enter']);
+
+ instance.setState({ show: false }, () => {
+ // Leave
+ expect(child.state.eventQueue).to.eql(['enter', 'leave']);
+
+ instance.setState({ show: true }, () => {
+ // Enter again, clean the leave in queue
+ expect(child.state.eventQueue).to.eql(['enter']);
+
+ done();
+ });
+ });
+ });
+ });
+
+ it('exclusive', (done) => {
+ const instance = ReactDOM.render(, div);
+
+ instance.setState({ show: true }, () => {
+ // Enter
+ const child = TestUtils.findRenderedComponentWithType(instance, AnimateChild);
+
+ instance.setState({ show: false }, () => {
+ // Leave, exclusive only get the latest one
+ const currentEvent = child.getCurrentEvent();
+ expect(currentEvent.eventType).to.eql('leave');
+ done();
+ });
+ });
+ });
+ });
+
+ it('remove child when transitionLeave is false', () => {
+ const instance = ReactDOM.render(, div);
+ instance.setState({ show: false });
+
+ expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'li').length).to.be(0);
+ });
+
+ it('clean up animation when exclusive item remove', (done) => {
+ let stopCalled = false;
+ const animation = {
+ enter() {
+ return {
+ stop() {
+ stopCalled = true;
+ },
+ };
+ },
+ };
+ const instance = ReactDOM.render(
+ , div);
+ instance.setState({ show: true }, () => {
+ instance.setState({ show: false }, () => {
+ expect(stopCalled).to.be.ok();
+ done();
+ });
+ });
+ });
+});
+
+describe('util', () => {
+ it('getVendorPrefixes without window support', () => {
+ const prefix = getVendorPrefixes(true, {});
+ expect(prefix.animationend.animation).to.be(undefined);
+ expect(prefix.transitionend.transition).to.be(undefined);
+ });
+
+ it('getVendorPrefixedEventName cache check', () => {
+ expect(getVendorPrefixedEventName('transitionend')).to.be(transitionEndName);
+ });
+
+ it('getVendorPrefixedEventName not exist', () => {
+ expect(getVendorPrefixedEventName('NotExist')).to.be('');
+ });
+
+ describe('mergeChildren', () => {
+ const gen = (key) => (
+ {key}
+ );
+ const flatten = (list) => list.map(p => p.key);
+
+ it('prepend', () => {
+ const prev = [gen(2), gen(4)];
+ const next = [gen(1), gen(2)];
+ const merge = mergeChildren(prev, next);
+ expect(flatten(merge)).to.eql([1, 2, 4]);
+ });
+
+ it('append', () => {
+ const prev = [gen(1), gen(3)];
+ const next = [gen(5), gen(6)];
+ const merge = mergeChildren(prev, next);
+ expect(flatten(merge)).to.eql([5, 6, 1, 3]);
+ });
+
+ it('mixed', () => {
+ const prev = [gen(1), gen(3), gen(5)];
+ const next = [gen(2), gen(3), gen(6)];
+ const merge = mergeChildren(prev, next);
+ expect(flatten(merge)).to.eql([2, 1, 3, 6, 5]);
+ });
+ });
+});
diff --git a/tests/index.js b/tests/index.js
index e65f2f6..6ec1dac 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -1,5 +1,7 @@
import 'core-js/es6/map';
import 'core-js/es6/set';
+import './basic.spec';
import './single.spec';
import './single-animation.spec';
import './multiple.spec';
+import './no.transition.spec';
diff --git a/tests/multiple.spec.js b/tests/multiple.spec.js
index ebce032..a577265 100644
--- a/tests/multiple.spec.js
+++ b/tests/multiple.spec.js
@@ -1,12 +1,12 @@
/* eslint no-console:0, react/no-multi-comp:0 */
-import Animate from '../';
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import TestUtils from 'react-dom/test-utils';
import expect from 'expect.js';
+import Animate from '../';
+import { supportTransition } from '../src/util';
import './index.spec.css';
-import CssAnimation from 'css-animation';
class Todo extends React.Component {
static propTypes = {
@@ -64,7 +64,7 @@ class TodoList extends React.Component {
}
return (
-
+ { this.handleRemove(i); }}>
{item}
);
@@ -104,7 +104,7 @@ describe('Animate', () => {
expect(TestUtils.scryRenderedDOMComponentsWithClass(list, 'item').length).to.be(4);
});
- if (!CssAnimation.isCssAnimationSupported) {
+ if (!supportTransition) {
return;
}
diff --git a/tests/no.transition.spec.js b/tests/no.transition.spec.js
new file mode 100644
index 0000000..656d45a
--- /dev/null
+++ b/tests/no.transition.spec.js
@@ -0,0 +1,60 @@
+/* eslint react/no-render-return-value:0, react/prefer-stateless-function:0, react/no-multi-comp:0 */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+import TestUtils from 'react-dom/test-utils';
+import expect from 'expect.js';
+import { genAnimate } from '../src/Animate';
+import { genAnimateChild } from '../src/AnimateChild';
+
+
+describe('no transition', () => {
+ const AnimateChild = genAnimateChild(false);
+ const Animate = genAnimate(AnimateChild);
+
+ class SimpleWrapper extends React.Component {
+ state = { show: this.props.show || false };
+
+ render() {
+ const { ...props } = this.props;
+ delete props.show;
+ return (
+
+ {this.state.show && }
+
+ );
+ }
+ }
+ SimpleWrapper.propTypes = {
+ show: PropTypes.bool,
+ };
+
+ let div;
+ beforeEach(() => {
+ div = document.createElement('div');
+ });
+
+ afterEach(() => {
+ try {
+ ReactDOM.unmountComponentAtNode(div);
+ } catch (e) {
+ // Do nothing
+ }
+ });
+
+
+ it('event queue', (done) => {
+ const instance = ReactDOM.render(, div);
+
+ instance.setState({ show: true }, () => {
+ const child = TestUtils.findRenderedComponentWithType(instance, AnimateChild);
+ expect(child.state.eventQueue).to.eql(['enter']);
+
+ setTimeout(() => {
+ expect(child.state.eventQueue).to.eql([]);
+ done();
+ }, 100);
+ });
+ });
+});
diff --git a/tests/single-animation.spec.js b/tests/single-animation.spec.js
index 27823cf..fbee97f 100644
--- a/tests/single-animation.spec.js
+++ b/tests/single-animation.spec.js
@@ -1,9 +1,10 @@
/* eslint no-console:0, react/no-multi-comp:0 */
-import Animate from '../index';
import React from 'react';
-import './index.spec.css';
import $ from 'jquery';
+import Animate from '../index';
+import single from './single-common.spec';
+import './index.spec.css';
function createClass(options) {
return class extends React.Component {
@@ -43,6 +44,5 @@ function createClass(options) {
};
}
-import single from './single-common.spec';
single(createClass, 'animation');
diff --git a/tests/single-common.spec.js b/tests/single-common.spec.js
index 77d2356..0f95ebf 100644
--- a/tests/single-common.spec.js
+++ b/tests/single-common.spec.js
@@ -1,11 +1,11 @@
-/* eslint no-console:0, react/no-multi-comp:0 */
+/* eslint no-console:0, react/no-multi-comp:0, react/no-render-return-value:0 */
import expect from 'expect.js';
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom/test-utils';
-import './index.spec.css';
import $ from 'jquery';
+import './index.spec.css';
export default function test(createClass, title) {
function getOpacity(node) {
@@ -76,7 +76,8 @@ export default function test(createClass, title) {
describe('when toggle transitionEnter', () => {
it('should remove children after transition', (done) => {
if (window.callPhantom) {
- return done();
+ done();
+ return;
}
instance.setState({ transitionEnter: false });
expect(TestUtils.scryRenderedDOMComponentsWithTag(instance, 'div')[0]).to.be.ok();
diff --git a/tests/single.spec.js b/tests/single.spec.js
index ceb666e..43302ab 100644
--- a/tests/single.spec.js
+++ b/tests/single.spec.js
@@ -1,7 +1,9 @@
/* eslint no-console:0, react/no-multi-comp:0 */
-import Animate from '../index';
import React from 'react';
+import Animate from '../index';
+import { supportTransition } from '../src/util';
+import single from './single-common.spec';
import './index.spec.css';
@@ -15,7 +17,7 @@ function createClass(options) {
render() {
return (