/** @jsx React.DOM */ 'use strict'; var React = require('react'); var AnimatableComponent = React.createClass({ propTypes: { tag: React.PropTypes.component.isRequired, animationClass: React.PropTypes.string.isRequired, key: React.PropTypes.string, shouldStartAnimation: React.PropTypes.bool, removeOnComplete: React.PropTypes.bool, className: React.PropTypes.string, onAnimationStart: React.PropTypes.func, onAnimationIteration: React.PropTypes.func, onAnimationEnd: React.PropTypes.func }, getDefaultProps: function() { return { tag: React.DOM.div, animationClass: '', className: '', removeOnComplete: true }; }, getInitialState: function() { return { applyAnimationClass: false }; }, componentWillMount: function() { this.isAnimating = false; this.animationStartTime = 0; this.animationElapsedTime = 0; this.animationIterationCount = 0; }, componentDidMount: function() { var node = this.refs.el.getDOMNode(); this.prefixEventHandler(node, 'AnimationStart', this.handleAnimationStart); this.prefixEventHandler(node, 'AnimationEnd', this.handleAnimationEnd); this.prefixEventHandler(node, 'AnimationIteration', this.handleAnimationIteration); }, componentWillReceiveProps: function(nextProps) { this.setState({ applyAnimationClass: nextProps.shouldStartAnimation }); }, handleAnimationStart: function(e) { if(e.animationClass === this.props.animationClass) { this.isAnimating = true; this.animationStartTime = Date.now(); this.animationElapsedTime = 0; this.animationIterationCount = 0; e.stopPropagation(); if(this.props.onAnimationStart) { this.props.onAnimationStart(this, e); } } }, handleAnimationIteration: function(e) { if(e.animationClass === this.props.animationClass) { e.stopPropagation(); this.animationIterationCount += 1; this.animationElapsedTime = e.elapsedTime; if(this.props.onAnimationIteration) { this.props.onAnimationIteration(this, e); } } }, handleAnimationEnd: function(e) { if(e.animationClass === this.props.animationClass) { e.stopPropagation(); this.isAnimating = false; this.animationElapsedTime = e.elapsedTime; if(this.props.removeOnComplete) { this.setState({ applyAnimationClass: false }); } if(this.props.onAnimationEnd) { this.props.onAnimationEnd(this, e); } } }, prefixEventHandler: function(node, name, handler, remove) { var prefixes = ['webkit', 'moz', 'MS', 'o', '']; for(var i = 0; i < prefixes.length; i++) { var eventName = (prefixes[i] === '') ? name.toLowerCase() : prefixes[i] + name; if(!remove) { node.addEventListener(eventName, handler); } else { node.removeEventListener(eventName, handler); } } }, componentWillUnmount: function() { var node = this.refs.el.getDOMNode(); this.prefixEventHandler(node, 'AnimationStart', this.handleAnimationStart, true); this.prefixEventHandler(node, 'AnimationEnd', this.handleAnimationEnd, true); this.prefixEventHandler(node, 'AnimationIteration', this.handleAnimationIteration, true); }, render: function() { var fullClass = this.props.className; if(this.state.applyAnimationClass) { fullClass += ' ' + this.props.animationClass; } return ( this.props.tag({ ref: 'el', className: fullClass.trim(), key: this.props.key }, this.props.children )); } }); module.exports = AnimatableComponent;