react-widgets
Version:
An à la carte set of polished, extensible, and accessible inputs built for React
189 lines (143 loc) • 5.39 kB
JavaScript
/**
* A streamlined version of TransitionGroup built for managing at most two active children
* also provides additional hooks for animation start/end
* https://github.com/facebook/react/blob/master/src/addons/transitions/ReactTransitionGroup.js
* relevent code is licensed accordingly
*/
'use strict';
var React = require('react'),
css = require('dom-helpers/style'),
height = require('dom-helpers/query/height'),
width = require('dom-helpers/query/width'),
compat = require('./util/compat'),
_ = require('./util/_');
module.exports = React.createClass({
displayName: 'ReplaceTransitionGroup',
propTypes: {
component: React.PropTypes.oneOfType([React.PropTypes.element, React.PropTypes.string]),
childFactory: React.PropTypes.func,
onAnimating: React.PropTypes.func,
onAnimate: React.PropTypes.func
},
getDefaultProps: function getDefaultProps() {
return {
component: 'span',
childFactory: function childFactory(a) {
return a;
},
onAnimating: _.noop,
onAnimate: _.noop
};
},
getInitialState: function getInitialState() {
return {
children: _.splat(this.props.children)
};
},
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
var nextChild = getChild(nextProps.children),
stack = this.state.children.slice(),
next = stack[1],
last = stack[0];
var isLastChild = last && key(last) === key(nextChild),
isNextChild = next && key(next) === key(nextChild);
//no children
if (!last) {
stack.push(nextChild);
this.entering = nextChild;
} else if (last && !next && !isLastChild) {
//new child
stack.push(nextChild);
this.leaving = last;
this.entering = nextChild;
} else if (last && next && !isLastChild && !isNextChild) {
// the child is not the current one, exit the current one, add the new one
// - shift the stack down
stack.shift();
stack.push(nextChild);
this.leaving = next;
this.entering = nextChild;
}
//new child that just needs to be re-rendered
else if (isLastChild) stack.splice(0, 1, nextChild);else if (isNextChild) stack.splice(1, 1, nextChild);
if (this.state.children[0] !== stack[0] || this.state.children[1] !== stack[1]) this.setState({ children: stack });
},
componentWillMount: function componentWillMount() {
this.animatingKeys = {};
this.leaving = null;
this.entering = null;
},
componentDidUpdate: function componentDidUpdate() {
var entering = this.entering,
leaving = this.leaving,
first = this.refs[key(entering) || key(leaving)],
node = compat.findDOMNode(this),
el = first && compat.findDOMNode(first);
if (el) css(node, {
overflow: 'hidden',
height: height(el) + 'px',
width: width(el) + 'px'
});
this.props.onAnimating();
this.entering = null;
this.leaving = null;
if (entering) this.performEnter(key(entering));
if (leaving) this.performLeave(key(leaving));
},
performEnter: function performEnter(key) {
var component = this.refs[key];
if (!component) return;
this.animatingKeys[key] = true;
if (component.componentWillEnter) component.componentWillEnter(this._handleDoneEntering.bind(this, key));else this._handleDoneEntering(key);
},
_tryFinish: function _tryFinish() {
if (this.isTransitioning()) return;
if (this.isMounted()) css(compat.findDOMNode(this), { overflow: 'visible', height: '', width: '' });
this.props.onAnimate();
},
_handleDoneEntering: function _handleDoneEntering(enterkey) {
var component = this.refs[enterkey];
if (component && component.componentDidEnter) component.componentDidEnter();
delete this.animatingKeys[enterkey];
if (key(this.props.children) !== enterkey) this.performLeave(enterkey); // This was removed before it had fully entered. Remove it.
this._tryFinish();
},
isTransitioning: function isTransitioning() {
return Object.keys(this.animatingKeys).length !== 0;
},
performLeave: function performLeave(key) {
var component = this.refs[key];
if (!component) return;
this.animatingKeys[key] = true;
if (component.componentWillLeave) component.componentWillLeave(this._handleDoneLeaving.bind(this, key));else this._handleDoneLeaving(key);
},
_handleDoneLeaving: function _handleDoneLeaving(leavekey) {
var component = this.refs[leavekey];
if (component && component.componentDidLeave) component.componentDidLeave();
delete this.animatingKeys[leavekey];
if (key(this.props.children) === leavekey) this.performEnter(leavekey); // This entered again before it fully left. Add it again.
else if (this.isMounted()) this.setState({
children: this.state.children.filter(function (c) {
return key(c) !== leavekey;
})
});
this._tryFinish();
},
render: function render() {
var _this = this;
var Component = this.props.component;
return React.createElement(
Component,
this.props,
this.state.children.map(function (c) {
return _this.props.childFactory(c, key(c));
})
);
}
});
function getChild(children) {
return React.Children.only(children);
}
function key(child) {
return child && child.key;
}