@@ -30,8 +30,7 @@ function createTransitionTimeoutPropValidator(transitionType) {
30
30
+ 'https://fb.me/react-animation-transition-group-timeout for more ' + 'information.' )
31
31
32
32
// If the duration isn't a number
33
- }
34
- else if ( typeof props [ timeoutPropName ] != 'number' ) {
33
+ } else if ( typeof props [ timeoutPropName ] != 'number' ) {
35
34
return new Error ( timeoutPropName + ' must be a number (in milliseconds)' )
36
35
}
37
36
}
@@ -65,7 +64,6 @@ export default class ReactCSSTransitionReplace extends React.Component {
65
64
transitionEnterTimeout : createTransitionTimeoutPropValidator ( 'Enter' ) ,
66
65
transitionLeaveTimeout : createTransitionTimeoutPropValidator ( 'Leave' ) ,
67
66
overflowHidden : PropTypes . bool ,
68
- changeWidth : PropTypes . bool ,
69
67
}
70
68
71
69
static defaultProps = {
@@ -74,23 +72,25 @@ export default class ReactCSSTransitionReplace extends React.Component {
74
72
transitionLeave : true ,
75
73
overflowHidden : true ,
76
74
component : 'span' ,
77
- changeWidth : false ,
75
+ childComponent : 'span' ,
78
76
}
79
77
80
78
state = {
79
+ currentKey : '1' ,
81
80
currentChild : this . props . children ? React . Children . only ( this . props . children ) : undefined ,
82
- currentChildKey : this . props . children ? '1' : '' ,
83
- nextChild : undefined ,
84
- activeHeightTransition : false ,
85
- nextChildKey : '' ,
81
+ prevChildren : { } ,
86
82
height : null ,
87
- width : null ,
88
- isLeaving : false ,
83
+ }
84
+
85
+ componentWillMount ( ) {
86
+ this . shouldEnterCurrent = false
87
+ this . keysToLeave = [ ]
88
+ this . transitioningKeys = { }
89
89
}
90
90
91
91
componentDidMount ( ) {
92
92
if ( this . props . transitionAppear && this . state . currentChild ) {
93
- this . appearCurrent ( )
93
+ this . performAppear ( this . state . currentKey )
94
94
}
95
95
}
96
96
@@ -99,152 +99,115 @@ export default class ReactCSSTransitionReplace extends React.Component {
99
99
}
100
100
101
101
componentWillReceiveProps ( nextProps ) {
102
- // Setting false indicates that the child has changed, but it is a removal so there is no next child.
103
- const nextChild = nextProps . children ? React . Children . only ( nextProps . children ) : false
104
- const currentChild = this . state . currentChild
105
-
106
- // Avoid silencing the transition when this.state.nextChild exists because it means that there’s
107
- // already a transition ongoing that has to be replaced.
108
- if ( currentChild && nextChild && nextChild . key === currentChild . key && ! this . state . nextChild ) {
109
- // Nothing changed, but we are re-rendering so update the currentChild.
110
- return this . setState ( {
111
- currentChild : nextChild ,
112
- } )
113
- }
102
+ const nextChild = nextProps . children ? React . Children . only ( nextProps . children ) : null
103
+ const { currentChild} = this . state
114
104
115
- if ( ! currentChild && ! nextChild && this . state . nextChild ) {
116
- // The container was empty before and the entering element is being removed again while
117
- // transitioning in. Since a CSS transition can't be reversed cleanly midway the height
118
- // is just forced back to zero immediately and the child removed.
119
- return this . cancelTransition ( )
105
+ if ( ( ! currentChild && ! nextChild ) || ( currentChild && nextChild && currentChild . key === nextChild . key ) ) {
106
+ return
120
107
}
121
108
122
109
const { state} = this
110
+ const { currentKey} = state
123
111
124
- // When transitionLeave is set to false, refs.curr does not exist when refs.next is being
125
- // transitioned into existence. When another child is set for this component at the point
126
- // where only refs.next exists, we want to use the width/height of refs.next instead of
127
- // refs.curr.
128
- const ref = this . refs . curr || this . refs . next
129
-
130
- // Set the next child to start the transition, and set the current height.
131
- this . setState ( {
132
- nextChild,
133
- activeHeightTransition : false ,
134
- nextChildKey : state . currentChildKey ? String ( Number ( state . currentChildKey ) + 1 ) : '1' ,
135
- height : state . currentChild ? ReactDOM . findDOMNode ( ref ) . offsetHeight : 0 ,
136
- width : state . currentChild && this . props . changeWidth ? ReactDOM . findDOMNode ( ref ) . offsetWidth : null ,
137
- } )
138
-
139
- // Enqueue setting the next height to trigger the height transition.
140
- this . enqueueHeightTransition ( nextChild )
141
- }
112
+ const nextState = {
113
+ currentKey : String ( Number ( currentKey ) + 1 ) ,
114
+ currentChild : nextChild ,
115
+ height : 0 ,
116
+ }
142
117
143
- componentDidUpdate ( ) {
144
- if ( ! this . isTransitioning && ! this . state . isLeaving ) {
145
- const { currentChild , nextChild } = this . state
118
+ if ( nextChild ) {
119
+ this . shouldEnterCurrent = true
120
+ }
146
121
147
- if ( currentChild && ( nextChild || nextChild === false || nextChild === null ) && this . props . transitionLeave ) {
148
- this . leaveCurrent ( )
122
+ if ( currentChild ) {
123
+ nextState . height = ReactDOM . findDOMNode ( this . refs [ currentKey ] ) . offsetHeight
124
+ nextState . prevChildren = {
125
+ ...state . prevChildren ,
126
+ [ currentKey ] : currentChild ,
149
127
}
150
- if ( nextChild ) {
151
- this . enterNext ( )
128
+ if ( ! this . transitioningKeys [ currentKey ] ) {
129
+ this . keysToLeave . push ( currentKey )
152
130
}
153
131
}
132
+
133
+ this . setState ( nextState )
154
134
}
155
135
156
- enqueueHeightTransition ( nextChild , tickCount = 0 ) {
157
- this . timeout = setTimeout ( ( ) => {
158
- if ( ! nextChild ) {
159
- return this . setState ( {
160
- activeHeightTransition : true ,
161
- height : 0 ,
162
- width : this . props . changeWidth ? 0 : null ,
163
- } )
164
- }
136
+ componentDidUpdate ( ) {
137
+ if ( this . shouldEnterCurrent ) {
138
+ this . shouldEnterCurrent = false
139
+ this . performEnter ( this . state . currentKey )
140
+ }
165
141
166
- const nextNode = ReactDOM . findDOMNode ( this . refs . next )
167
- if ( nextNode ) {
168
- this . setState ( {
169
- activeHeightTransition : true ,
170
- height : nextNode . offsetHeight ,
171
- width : this . props . changeWidth ? nextNode . offsetWidth : null ,
172
- } )
173
- }
174
- else {
175
- // The DOM hasn't rendered the entering element yet, so wait another tick.
176
- // Getting stuck in a loop shouldn't happen, but it's better to be safe.
177
- if ( tickCount < 10 ) {
178
- this . enqueueHeightTransition ( nextChild , tickCount + 1 )
179
- }
180
- }
181
- } , TICK )
142
+ const keysToLeave = this . keysToLeave
143
+ this . keysToLeave = [ ]
144
+ keysToLeave . forEach ( this . performLeave )
182
145
}
183
146
184
- appearCurrent ( ) {
185
- this . refs . curr . componentWillAppear ( this . _handleDoneAppearing )
186
- this . isTransitioning = true
147
+ performAppear ( key ) {
148
+ this . transitioningKeys [ key ] = true
149
+ this . refs [ key ] . componentWillAppear ( this . handleDoneAppearing . bind ( this , key ) )
187
150
}
188
151
189
- _handleDoneAppearing = ( ) => {
190
- this . isTransitioning = false
152
+ handleDoneAppearing = ( key ) => {
153
+ delete this . transitioningKeys [ key ]
154
+ if ( key !== this . state . currentKey ) {
155
+ // This child was removed before it had fully appeared. Remove it.
156
+ this . performLeave ( key )
157
+ }
191
158
}
192
159
193
- enterNext ( ) {
194
- this . refs . next . componentWillEnter ( this . _handleDoneEntering )
195
- this . isTransitioning = true
160
+ performEnter ( key ) {
161
+ this . transitioningKeys [ key ] = true
162
+ this . refs [ key ] . componentWillEnter ( this . handleDoneEntering . bind ( this , key ) )
163
+ this . enqueueHeightTransition ( )
196
164
}
197
165
198
- _handleDoneEntering = ( ) => {
199
- const { state} = this
200
-
201
- this . isTransitioning = false
202
- this . setState ( {
203
- currentChild : state . nextChild ,
204
- currentChildKey : state . nextChildKey ,
205
- activeHeightTransition : false ,
206
- nextChild : undefined ,
207
- nextChildKey : '' ,
208
- height : null ,
209
- width : null ,
210
- } )
166
+ handleDoneEntering ( key ) {
167
+ delete this . transitioningKeys [ key ]
168
+ if ( key === this . state . currentKey ) {
169
+ // The current child has finished entering so the height transition is also cleared.
170
+ this . setState ( { height : null } )
171
+ } else {
172
+ // This child was removed before it had fully appeared. Remove it.
173
+ this . performLeave ( key )
174
+ }
211
175
}
212
176
213
- leaveCurrent ( ) {
214
- this . refs . curr . componentWillLeave ( this . _handleDoneLeaving )
215
- this . isTransitioning = true
216
- this . setState ( { isLeaving : true } )
177
+ performLeave = ( key ) => {
178
+ this . transitioningKeys [ key ] = true
179
+ this . refs [ key ] . componentWillLeave ( this . handleDoneLeaving . bind ( this , key ) )
180
+ if ( ! this . state . currentChild ) {
181
+ // The enter transition dominates, but if there is no
182
+ // entering component the height is set to zero.
183
+ this . enqueueHeightTransition ( )
184
+ }
217
185
}
218
186
219
- // When the leave transition time-out expires the animation classes are removed, so the
220
- // element must be removed from the DOM if the enter transition is still in progress.
221
- _handleDoneLeaving = ( ) => {
222
- if ( this . isTransitioning ) {
223
- const state = { currentChild : undefined , isLeaving : false }
187
+ handleDoneLeaving ( key ) {
188
+ delete this . transitioningKeys [ key ]
224
189
225
- if ( ! this . state . nextChild ) {
226
- this . isTransitioning = false
227
- state . height = null
228
- state . width = null
229
- }
190
+ const nextState = { prevChildren : { ...this . state . prevChildren } }
191
+ delete nextState . prevChildren [ key ]
230
192
231
- this . setState ( state )
193
+ if ( ! this . state . currentChild ) {
194
+ nextState . height = null
232
195
}
196
+
197
+ this . setState ( nextState )
233
198
}
234
199
235
- cancelTransition ( ) {
236
- this . isTransitioning = false
237
- clearTimeout ( this . timeout )
238
- return this . setState ( {
239
- nextChild : undefined ,
240
- activeHeightTransition : false ,
241
- nextChildKey : '' ,
242
- height : null ,
243
- width : null ,
244
- } )
200
+ enqueueHeightTransition ( ) {
201
+ const { state} = this
202
+ this . timeout = setTimeout ( ( ) => {
203
+ if ( ! state . currentChild ) {
204
+ return this . setState ( { height : 0 } )
205
+ }
206
+ this . setState ( { height : ReactDOM . findDOMNode ( this . refs [ state . currentKey ] ) . offsetHeight } )
207
+ } , TICK )
245
208
}
246
209
247
- _wrapChild ( child , moreProps ) {
210
+ wrapChild ( child , moreProps ) {
248
211
let transitionName = this . props . transitionName
249
212
250
213
if ( typeof transitionName == 'object' && transitionName !== null ) {
@@ -268,42 +231,22 @@ export default class ReactCSSTransitionReplace extends React.Component {
268
231
}
269
232
270
233
render ( ) {
271
- const { currentChild , currentChildKey , nextChild , nextChildKey , height, width , isLeaving , activeHeightTransition } = this . state
234
+ const { currentKey , currentChild , prevChildren , height} = this . state
272
235
const childrenToRender = [ ]
273
236
274
237
const {
275
- overflowHidden, transitionName, changeWidth , component ,
238
+ overflowHidden, transitionName, component , childComponent ,
276
239
transitionAppear, transitionEnter, transitionLeave,
277
240
transitionAppearTimeout, transitionEnterTimeout, transitionLeaveTimeout,
278
241
...containerProps
279
242
} = this . props
280
243
281
- if ( currentChild && ! nextChild && ! transitionLeave || currentChild && transitionLeave ) {
282
- childrenToRender . push (
283
- React . createElement (
284
- 'span' ,
285
- { key : currentChildKey } ,
286
- this . _wrapChild (
287
- typeof currentChild . type == 'string' ? currentChild : React . cloneElement ( currentChild , { isLeaving} ) ,
288
- { ref : 'curr' } )
289
- )
290
- )
291
- }
292
-
293
-
294
244
if ( height !== null ) {
295
245
const heightClassName = ( typeof transitionName == 'object' && transitionName !== null )
296
246
? transitionName . height || ''
297
247
: `${ transitionName } -height`
298
248
299
- // Similarly to ReactCSSTransitionGroup, adding `-height-active` suffix to the
300
- // container when we are transitioning height.
301
- const activeHeightClassName = ( nextChild && activeHeightTransition && heightClassName )
302
- ? `${ heightClassName } -active`
303
- : ''
304
-
305
- containerProps . className = `${ containerProps . className || '' } ${ heightClassName } ${ activeHeightClassName } `
306
-
249
+ containerProps . className = `${ containerProps . className || '' } ${ heightClassName } `
307
250
containerProps . style = {
308
251
...containerProps . style ,
309
252
position : 'relative' ,
@@ -314,26 +257,35 @@ export default class ReactCSSTransitionReplace extends React.Component {
314
257
if ( overflowHidden ) {
315
258
containerProps . style . overflow = 'hidden'
316
259
}
317
-
318
- if ( changeWidth ) {
319
- containerProps . style . width = width
320
- }
321
260
}
322
261
323
- if ( nextChild ) {
262
+ Object . keys ( prevChildren ) . forEach ( key => {
324
263
childrenToRender . push (
325
- React . createElement ( 'span' ,
264
+ React . createElement ( childComponent ,
326
265
{
266
+ key,
327
267
style : {
328
268
position : 'absolute' ,
329
269
top : 0 ,
330
270
left : 0 ,
331
271
right : 0 ,
332
272
bottom : 0 ,
333
273
} ,
334
- key : nextChildKey ,
335
274
} ,
336
- this . _wrapChild ( nextChild , { ref : 'next' } )
275
+ this . wrapChild (
276
+ typeof prevChildren [ key ] . type == 'string'
277
+ ? prevChildren [ key ]
278
+ : React . cloneElement ( prevChildren [ key ] , { isLeaving : true } ) ,
279
+ { ref : key } )
280
+ )
281
+ )
282
+ } )
283
+
284
+ if ( currentChild ) {
285
+ childrenToRender . push (
286
+ React . createElement ( childComponent ,
287
+ { key : currentKey } ,
288
+ this . wrapChild ( currentChild , { ref : currentKey } )
337
289
)
338
290
)
339
291
}
0 commit comments