import _defineProperty from 'babel-runtime/helpers/defineProperty'; import _extends from 'babel-runtime/helpers/extends'; import _classCallCheck from 'babel-runtime/helpers/classCallCheck'; import _createClass from 'babel-runtime/helpers/createClass'; import _possibleConstructorReturn from 'babel-runtime/helpers/possibleConstructorReturn'; import _inherits from 'babel-runtime/helpers/inherits'; /* eslint-disable react/default-props-match-prop-types, react/no-multi-comp */ import React from 'react'; import PropTypes from 'prop-types'; import { polyfill } from 'react-lifecycles-compat'; import findDOMNode from 'rc-util/es/Dom/findDOMNode'; import classNames from 'classnames'; import raf from 'raf'; import { getTransitionName, animationEndName, transitionEndName, supportTransition } from './util/motion'; var STATUS_NONE = 'none'; var STATUS_APPEAR = 'appear'; var STATUS_ENTER = 'enter'; var STATUS_LEAVE = 'leave'; export var MotionPropTypes = { eventProps: PropTypes.object, // Internal usage. Only pass by CSSMotionList visible: PropTypes.bool, children: PropTypes.func, motionName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), motionAppear: PropTypes.bool, motionEnter: PropTypes.bool, motionLeave: PropTypes.bool, motionLeaveImmediately: PropTypes.bool, // Trigger leave motion immediately motionDeadline: PropTypes.number, removeOnLeave: PropTypes.bool, leavedClassName: PropTypes.string, onAppearStart: PropTypes.func, onAppearActive: PropTypes.func, onAppearEnd: PropTypes.func, onEnterStart: PropTypes.func, onEnterActive: PropTypes.func, onEnterEnd: PropTypes.func, onLeaveStart: PropTypes.func, onLeaveActive: PropTypes.func, onLeaveEnd: PropTypes.func }; /** * `transitionSupport` is used for none transition test case. * Default we use browser transition event support check. */ export function genCSSMotion(config) { var transitionSupport = config; var forwardRef = !!React.forwardRef; if (typeof config === 'object') { transitionSupport = config.transitionSupport; forwardRef = 'forwardRef' in config ? config.forwardRef : forwardRef; } function isSupportTransition(props) { return !!(props.motionName && transitionSupport); } var CSSMotion = function (_React$Component) { _inherits(CSSMotion, _React$Component); function CSSMotion() { _classCallCheck(this, CSSMotion); var _this = _possibleConstructorReturn(this, (CSSMotion.__proto__ || Object.getPrototypeOf(CSSMotion)).call(this)); _this.onDomUpdate = function () { var _this$state = _this.state, status = _this$state.status, newStatus = _this$state.newStatus; var _this$props = _this.props, onAppearStart = _this$props.onAppearStart, onEnterStart = _this$props.onEnterStart, onLeaveStart = _this$props.onLeaveStart, onAppearActive = _this$props.onAppearActive, onEnterActive = _this$props.onEnterActive, onLeaveActive = _this$props.onLeaveActive, motionAppear = _this$props.motionAppear, motionEnter = _this$props.motionEnter, motionLeave = _this$props.motionLeave; if (!isSupportTransition(_this.props)) { return; } // Event injection var $ele = _this.getElement(); if (_this.$cacheEle !== $ele) { _this.removeEventListener(_this.$cacheEle); _this.addEventListener($ele); _this.$cacheEle = $ele; } // Init status if (newStatus && status === STATUS_APPEAR && motionAppear) { _this.updateStatus(onAppearStart, null, null, function () { _this.updateActiveStatus(onAppearActive, STATUS_APPEAR); }); } else if (newStatus && status === STATUS_ENTER && motionEnter) { _this.updateStatus(onEnterStart, null, null, function () { _this.updateActiveStatus(onEnterActive, STATUS_ENTER); }); } else if (newStatus && status === STATUS_LEAVE && motionLeave) { _this.updateStatus(onLeaveStart, null, null, function () { _this.updateActiveStatus(onLeaveActive, STATUS_LEAVE); }); } }; _this.onMotionEnd = function (event) { var _this$state2 = _this.state, status = _this$state2.status, statusActive = _this$state2.statusActive; var _this$props2 = _this.props, onAppearEnd = _this$props2.onAppearEnd, onEnterEnd = _this$props2.onEnterEnd, onLeaveEnd = _this$props2.onLeaveEnd; if (status === STATUS_APPEAR && statusActive) { _this.updateStatus(onAppearEnd, { status: STATUS_NONE }, event); } else if (status === STATUS_ENTER && statusActive) { _this.updateStatus(onEnterEnd, { status: STATUS_NONE }, event); } else if (status === STATUS_LEAVE && statusActive) { _this.updateStatus(onLeaveEnd, { status: STATUS_NONE }, event); } }; _this.setNodeRef = function (node) { var internalRef = _this.props.internalRef; _this.node = node; if (typeof internalRef === 'function') { internalRef(node); } else if (internalRef && 'current' in internalRef) { internalRef.current = node; } }; _this.getElement = function () { try { return findDOMNode(_this.node || _this); } catch (e) { /** * Fallback to cache element. * This is only happen when `motionDeadline` trigger but element removed. */ return _this.$cacheEle; } }; _this.addEventListener = function ($ele) { if (!$ele) return; $ele.addEventListener(transitionEndName, _this.onMotionEnd); $ele.addEventListener(animationEndName, _this.onMotionEnd); }; _this.removeEventListener = function ($ele) { if (!$ele) return; $ele.removeEventListener(transitionEndName, _this.onMotionEnd); $ele.removeEventListener(animationEndName, _this.onMotionEnd); }; _this.updateStatus = function (styleFunc, additionalState, event, callback) { var statusStyle = styleFunc ? styleFunc(_this.getElement(), event) : null; if (statusStyle === false || _this._destroyed) return; var nextStep = void 0; if (callback) { nextStep = function nextStep() { _this.nextFrame(callback); }; } _this.setState(_extends({ statusStyle: typeof statusStyle === 'object' ? statusStyle : null, newStatus: false }, additionalState), nextStep); // Trigger before next frame & after `componentDidMount` }; _this.updateActiveStatus = function (styleFunc, currentStatus) { // `setState` use `postMessage` to trigger at the end of frame. // Let's use requestAnimationFrame to update new state in next frame. _this.nextFrame(function () { var status = _this.state.status; if (status !== currentStatus) return; var motionDeadline = _this.props.motionDeadline; _this.updateStatus(styleFunc, { statusActive: true }); if (motionDeadline > 0) { setTimeout(function () { _this.onMotionEnd({ deadline: true }); }, motionDeadline); } }); }; _this.nextFrame = function (func) { _this.cancelNextFrame(); _this.raf = raf(func); }; _this.cancelNextFrame = function () { if (_this.raf) { raf.cancel(_this.raf); _this.raf = null; } }; _this.state = { status: STATUS_NONE, statusActive: false, newStatus: false, statusStyle: null }; _this.$cacheEle = null; _this.node = null; _this.raf = null; return _this; } _createClass(CSSMotion, [{ key: 'componentDidMount', value: function componentDidMount() { this.onDomUpdate(); } }, { key: 'componentDidUpdate', value: function componentDidUpdate() { this.onDomUpdate(); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this._destroyed = true; this.removeEventListener(this.$cacheEle); this.cancelNextFrame(); } }, { key: 'render', value: function render() { var _classNames; var _state = this.state, status = _state.status, statusActive = _state.statusActive, statusStyle = _state.statusStyle; var _props = this.props, children = _props.children, motionName = _props.motionName, visible = _props.visible, removeOnLeave = _props.removeOnLeave, leavedClassName = _props.leavedClassName, eventProps = _props.eventProps; if (!children) return null; if (status === STATUS_NONE || !isSupportTransition(this.props)) { if (visible) { return children(_extends({}, eventProps), this.setNodeRef); } else if (!removeOnLeave) { return children(_extends({}, eventProps, { className: leavedClassName }), this.setNodeRef); } return null; } return children(_extends({}, eventProps, { className: classNames((_classNames = {}, _defineProperty(_classNames, getTransitionName(motionName, status), status !== STATUS_NONE), _defineProperty(_classNames, getTransitionName(motionName, status + '-active'), status !== STATUS_NONE && statusActive), _defineProperty(_classNames, motionName, typeof motionName === 'string'), _classNames)), style: statusStyle }), this.setNodeRef); } }], [{ key: 'getDerivedStateFromProps', value: function getDerivedStateFromProps(props, _ref) { var prevProps = _ref.prevProps, prevStatus = _ref.status; if (!isSupportTransition(props)) return {}; var visible = props.visible, motionAppear = props.motionAppear, motionEnter = props.motionEnter, motionLeave = props.motionLeave, motionLeaveImmediately = props.motionLeaveImmediately; var newState = { prevProps: props }; // Clean up status if prop set to false if (prevStatus === STATUS_APPEAR && !motionAppear || prevStatus === STATUS_ENTER && !motionEnter || prevStatus === STATUS_LEAVE && !motionLeave) { newState.status = STATUS_NONE; newState.statusActive = false; newState.newStatus = false; } // Appear if (!prevProps && visible && motionAppear) { newState.status = STATUS_APPEAR; newState.statusActive = false; newState.newStatus = true; } // Enter if (prevProps && !prevProps.visible && visible && motionEnter) { newState.status = STATUS_ENTER; newState.statusActive = false; newState.newStatus = true; } // Leave if (prevProps && prevProps.visible && !visible && motionLeave || !prevProps && motionLeaveImmediately && !visible && motionLeave) { newState.status = STATUS_LEAVE; newState.statusActive = false; newState.newStatus = true; } return newState; } }]); return CSSMotion; }(React.Component); CSSMotion.propTypes = _extends({}, MotionPropTypes, { internalRef: PropTypes.oneOfType([PropTypes.object, PropTypes.func]) }); CSSMotion.defaultProps = { visible: true, motionEnter: true, motionAppear: true, motionLeave: true, removeOnLeave: true }; polyfill(CSSMotion); if (!forwardRef) { return CSSMotion; } return React.forwardRef(function (props, ref) { return React.createElement(CSSMotion, _extends({ internalRef: ref }, props)); }); } export default genCSSMotion(supportTransition);