var React = require('react');
var ReactDOM = require('react-dom');
var hyphenateStyleName = require('fbjs/lib/hyphenateStyleName');

var TransitionChild = React.createClass({
  displayName: 'TransitionChild',

  propTypes: {
    children: React.PropTypes.any,
    childrenAppearStyle: React.PropTypes.object,
    childrenBaseStyle: React.PropTypes.object,
    childrenEnterStyle: React.PropTypes.object,
    childrenLeaveStyle: React.PropTypes.object,
    id: React.PropTypes.oneOfType(
      [React.PropTypes.string, React.PropTypes.number]
    ),
    onChildAppeared: React.PropTypes.func,
    onChildEntered: React.PropTypes.func,
    onChildLeft: React.PropTypes.func,
    onChildStartAppear: React.PropTypes.func,
    onChildStartEnter: React.PropTypes.func,
    onChildStartLeave: React.PropTypes.func,
    transitionEndProperty: React.PropTypes.string,
  },

  componentWillMount: function () {
    this._callbackTimeout = null;
  },

  componentDidMount: function () {
    var node = ReactDOM.findDOMNode(this);
    if (!node) return;

    var component = this;
    requestAnimationFrame(function () {
      node.setAttribute('style', component._computeNewStyle());
    });
  },

  componentWillUnmount: function () {
    clearTimeout(this._callbackTimeout);
  },

  componentWillAppear: function (callback) {
    this._transition(callback, 'appear');

    if (this.props.onChildStartAppear) {
      this.props.onChildStartAppear(this.props.id);
    }
  },

  componentDidAppear: function () {
    if (this.props.onChildAppeared) {
      this.props.onChildAppeared(this.props.id);
    }
  },

  componentWillEnter: function (callback) {
    this._transition(callback, 'enter');

    if (this.props.onChildStartEnter) {
      this.props.onChildStartEnter(this.props.id);
    }
  },

  componentDidEnter: function () {
    if (this.props.onChildEntered) {
      this.props.onChildEntered(this.props.id);
    }
  },

  componentWillLeave: function (callback) {
    this._transition(callback, 'leave');

    if (this.props.onChildStartLeave) {
      this.props.onChildStartLeave(this.props.id);
    }
  },

  componentDidLeave: function () {
    if (this.props.onChildLeft) this.props.onChildLeft(this.props.id);
  },

  _getTransitionProperties: function (computedStyle) {
    var properties = {};

    properties.transitionDuration = computedStyle.transitionDuration ||
      computedStyle.WebkitTransitionDuration ||
      computedStyle.MozTransitionDuration ||
      computedStyle.msTransitionDuration;

    properties.transitionDelay = computedStyle.transitionDelay ||
      computedStyle.WebkitTransitionDelay ||
      computedStyle.MozTransitionDelay ||
      computedStyle.msTransitionDelay;

    properties.transitionProperty = computedStyle.transitionProperty ||
      computedStyle.WebkitTransitionProperty ||
      computedStyle.msTransitionProperty ||
      computedStyle.MozTransitionProperty;

    return properties;
  },

  // Specs: https://www.w3.org/TR/css3-transitions/
  // A lot of assumptions could be made here, like that probably the duration
  // and delay lists are already truncated by the size of the property list
  // or that values will be returned by window.getComputedStyle in seconds,
  // but I prefer to make this function less vulnerable to changes.
  _getTransitionMaximumTime: function (property, duration, delay) {
    var durationArray = duration.split(',');
    var delayArray = delay.split(',');
    var propertyArray = property.split(',');
    var longestTime = 0;
    var re = /([0-9]*\.?[0-9]+)(m?s)/;
    var durationFactor;
    var delayFactor;
    var durationGroups;
    var delayGroups;

    for (var i = 0; i < propertyArray.length; i++) {
      durationGroups = (durationArray[i] || '0s').match(re);
      if (durationGroups[2] === 's') durationFactor = 1000;
      else durationFactor = 1;

      delayGroups = (delayArray[i] || '0s').match(re);
      if (delayGroups[2] === 's') delayFactor = 1000;
      else delayFactor = 1;

      longestTime = Math.max(
        parseFloat(
          (durationGroups[1] * durationFactor) + (delayGroups[1] * delayFactor)
        ),
        longestTime
      );
    }

    return longestTime;
  },

  _handleTransitionEnd: function (node, maxTime, callback, event) {
    if (maxTime && maxTime >= event.elapsedTime * 1000) {
      node.removeEventListener('transitionend', this.handleRef);
      callback();
    }
    else if (this.props.transitionEndProperty === event.propertyName) {
      node.removeEventListener('transitionend', this.handleRef);
      callback();
    }
  },

  _computeNewStyle: function (phase) {
    var currentStyle;
    if (phase === 'appear') currentStyle = this.props.childrenAppearStyle;
    else if (phase === 'enter') currentStyle = this.props.childrenEnterStyle;
    else if (phase === 'leave') currentStyle = this.props.childrenLeaveStyle;
    else currentStyle = this.props.childrenBaseStyle;

    var mergedStyle = phase
      ? Object.assign({}, this.props.childrenBaseStyle, currentStyle)
      : currentStyle;

    var styleStr = '';
    Object.keys(mergedStyle).forEach(function (key) {
      styleStr += hyphenateStyleName(key) + ':' + mergedStyle[key] + ';';
    });

    return styleStr;
  },

  _transition: function (callback, phase) {
    if ((phase === 'appear' && !this.props.childrenAppearStyle) ||
        (phase === 'enter' && !this.props.childrenEnterStyle) ||
        (phase === 'leave' && !this.props.childrenLeaveStyle)) {
      callback();
    }
    else {
      var component = this;
      requestAnimationFrame(function () {
        component._executeTransition(callback, phase);
      });
    }
  },

  _executeTransition: function (callback, phase) {
    var node = ReactDOM.findDOMNode(this);
    if (!node) return;

    node.setAttribute('style', this._computeNewStyle(phase));

    var properties;
    var maxTransitionTime;
    if (this.props.transitionEndProperty === undefined) {
      properties = this._getTransitionProperties(getComputedStyle(node));
      maxTransitionTime = this._getTransitionMaximumTime(
        properties.transitionProperty,
        properties.transitionDuration,
        properties.transitionDelay
      );
    }

    node.removeEventListener('transitionend', this.handleRef);
    this.handleRef = this._handleTransitionEnd.bind(
      this, node, maxTransitionTime, callback
    );
    node.addEventListener('transitionend', this.handleRef);
  },

  render: function () {
    return React.Children.only(this.props.children);
  },

});

module.exports = TransitionChild;
@@ -1041,76 +1041,8 @@
 n;%0A%0A
-    var component = this;%0A    requestAnimationFrame(function () %7B%0A  
     
@@ -1068,25 +1068,20 @@
 style', 
-component
+this
 ._comput
@@ -1094,22 +1094,70 @@
 yle());%0A
-    %7D)
+%0A    // Flush styles%0A    this.flush = node.offsetWidth
 ;%0A  %7D,%0A%0A
@@ -5486,89 +5486,12 @@
     
-var component = this;%0A      requestAnimationFrame(function () %7B%0A        component
+this
 ._ex
@@ -5528,18 +5528,8 @@
 e);%0A
-      %7D);%0A
     
@@ -6311,16 +6311,72 @@
 dleRef);
+%0A%0A    // Flush styles%0A    this.flush = node.offsetWidth;
 %0A  %7D,%0A%0A 

Remove requestAnimationFrame