1
+/**
2
+ * Copyright 2013 Facebook, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ * @providesModule ReactTransitionableChild
17
+ */
18
+
19
+"use strict";
20
+
21
+var React = require('React');
22
+var CSSCore = require('CSSCore');
23
+var ReactTransitionEvents = require('ReactTransitionEvents');
24
+
25
+// We don't remove the element from the DOM until we receive an animationend or
26
+// transitionend event. If the user screws up and forgets to add an animation
27
+// their node will be stuck in the DOM forever, so we detect if an animation
28
+// does not start and if it doesn't, we just call the end listener immediately.
29
+var TICK = 17;
30
+var NO_EVENT_TIMEOUT = 5000;
31
+
32
+var noEventListener = null;
33
+
34
+if (__DEV__) {
35
+ noEventListener = function() {
36
+ console.warn(
37
+ 'transition(): tried to perform an animation without ' +
38
+ 'an animationend or transitionend event after timeout (' +
39
+ NO_EVENT_TIMEOUT + 'ms). You should either disable this ' +
40
+ 'transition in JS or add a CSS animation/transition.'
41
+ );
42
+ };
43
+}
44
+
45
+/**
46
+ * This component is simply responsible for watching when its single child
47
+ * changes to undefined and animating the old child out. It does this by
48
+ * recording its old child in savedChildren when it detects this event is about
49
+ * to occur.
50
+ */
51
+var ReactTransitionableChild = React.createClass({
52
+ /**
53
+ * Perform an actual DOM transition. This takes care of a few things:
54
+ * - Adding the second CSS class to trigger the transition
55
+ * - Listening for the finish event
56
+ * - Cleaning up the css (unless noReset is true)
57
+ */
58
+ transition: function(animationType, noReset, finishCallback) {
59
+ var node = this.getDOMNode();
60
+ var className = this.props.name + '-' + animationType;
61
+ var activeClassName = className + '-active';
62
+ var noEventTimeout = null;
63
+
64
+ var endListener = function() {
65
+ if (__DEV__) {
66
+ clearTimeout(noEventTimeout);
67
+ }
68
+
69
+ // If this gets invoked after the component is unmounted it's OK.
70
+ if (!noReset) {
71
+ // Usually this means you're about to remove the node if you want to
72
+ // leave it in its animated state.
73
+ CSSCore.removeClass(node, className);
74
+ CSSCore.removeClass(node, activeClassName);
75
+ }
76
+
77
+ ReactTransitionEvents.removeEndEventListener(node, endListener);
78
+
79
+ // Usually this optional callback is used for informing an owner of
80
+ // a leave animation and telling it to remove the child.
81
+ finishCallback && finishCallback();
82
+ };
83
+
84
+ ReactTransitionEvents.addEndEventListener(node, endListener);
85
+
86
+ CSSCore.addClass(node, className);
87
+
88
+ // Need to do this to actually trigger a transition.
89
+ this.queueClass(activeClassName);
90
+
91
+ if (__DEV__) {
92
+ noEventTimeout = setTimeout(noEventListener, NO_EVENT_TIMEOUT);
93
+ }
94
+ },
95
+
96
+ queueClass: function(className) {
97
+ this.classNameQueue.push(className);
98
+ this.runNextTick(this.flushClassNameQueue);
99
+ },
100
+
101
+ runNextTick: function() {
102
+ if (this.props.runNextTick) {
103
+ this.props.runNextTick(this.flushClassNameQueue);
104
+ return;
105
+ }
106
+
107
+ if (!this.timeout) {
108
+ this.timeout = setTimeout(this.flushClassNameQueue, TICK);
109
+ }
110
+ },
111
+
112
+ flushClassNameQueue: function() {
113
+ if (this.isMounted()) {
114
+ this.classNameQueue.forEach(
115
+ CSSCore.addClass.bind(CSSCore, this.getDOMNode())
116
+ );
117
+ }
118
+ this.classNameQueue.length = 0;
119
+ this.timeout = null;
120
+ },
121
+
122
+ componentWillMount: function() {
123
+ this.classNameQueue = [];
124
+ },
125
+
126
+ componentWillUnmount: function() {
127
+ if (this.timeout) {
128
+ clearTimeout(this.timeout);
129
+ }
130
+ },
131
+
132
+ componentWillReceiveProps: function(nextProps) {
133
+ if (!nextProps.children && this.props.children) {
134
+ this.savedChildren = this.props.children;
135
+ }
136
+ },
137
+
138
+ componentDidMount: function(node) {
139
+ if (this.props.enter) {
140
+ this.transition('enter');
141
+ }
142
+ },
143
+
144
+ componentDidUpdate: function(prevProps, prevState, node) {
145
+ if (prevProps.children && !this.props.children) {
146
+ this.transition('leave', true, this.props.onDoneLeaving);
147
+ }
148
+ },
149
+
150
+ render: function() {
151
+ return this.props.children || this.savedChildren;
152
+ }
153
+});
154
+
155
+module.exports = ReactTransitionableChild;