@@ -36,7 +36,7 @@ export interface ChildProps {
36
36
className ?: string ;
37
37
}
38
38
39
- export interface State {
39
+ export interface AnimatorState {
40
40
childProps : ChildProps ;
41
41
animationsMarkup : React . ReactPortal [ ] ;
42
42
}
@@ -48,11 +48,18 @@ export interface AnimatorProps extends CollectorChildrenProps, InjectedProps {
48
48
name : string ;
49
49
50
50
/**
51
- * Used alternatively to the implicit animation triggering via unmounting or mounting of Animator components.
52
- * Only use `in` if your component is expected to persist through the entire lifecyle of the app.
53
- * When you transition to the "next page" make sure to set your "in" to false. When you transition
54
- * back to the original page set the "in" prop back to true. This lets the Animator components know when to
55
- * execute the animations.
51
+ * Will trigger animations over itself when this prop changes.
52
+ *
53
+ * You can't use the with the "in" prop.
54
+ */
55
+ triggerSelfKey ?: string ;
56
+
57
+ /**
58
+ * Use if your element is expected to persist through an animation.
59
+ * When you transition to the next state set your "in" to false and vice versa.
60
+ * This lets the Animator components know when to execute the animations.
61
+ *
62
+ * You can't use this with the "triggerSelfKey".
56
63
*/
57
64
in ?: boolean ;
58
65
@@ -75,7 +82,7 @@ export interface AnimatorProps extends CollectorChildrenProps, InjectedProps {
75
82
container : HTMLElement | ( ( ) => HTMLElement ) ;
76
83
}
77
84
78
- export default class Animator extends React . PureComponent < AnimatorProps , State > {
85
+ export default class Animator extends React . PureComponent < AnimatorProps , AnimatorState > {
79
86
static displayName = 'Animator' ;
80
87
81
88
static defaultProps = {
@@ -84,7 +91,7 @@ export default class Animator extends React.PureComponent<AnimatorProps, State>
84
91
container : document . body ,
85
92
} ;
86
93
87
- state : State = {
94
+ state : AnimatorState = {
88
95
animationsMarkup : [ ] ,
89
96
childProps : { } ,
90
97
} ;
@@ -115,55 +122,95 @@ export default class Animator extends React.PureComponent<AnimatorProps, State>
115
122
if ( componentIn === undefined || componentIn ) {
116
123
// Ok nothing is there yet, show ourself and store DOM data for later.
117
124
// We'll be waiting for another Animator to mount.
118
- this . showSelfAndNotifyManager ( ) ;
125
+ this . notifyVisibilityManagerAnimationsAreFinished ( ) ;
119
126
}
120
127
}
121
128
122
- componentWillUpdate ( prevProps : AnimatorProps ) {
123
- const { in : isIn } = this . props ;
124
- if ( prevProps . in === false && isIn === true ) {
125
- // We're being removed from "in". Let's recalculate our DOM position.
129
+ getSnapshotBeforeUpdate ( prevProps : AnimatorProps ) {
130
+ if ( prevProps . in === true && this . props . in === false ) {
126
131
this . storeDOMData ( ) ;
127
132
this . delayedClearStore ( ) ;
128
133
this . abortAnimations ( ) ;
129
134
}
135
+
136
+ if ( prevProps . triggerSelfKey !== this . props . triggerSelfKey ) {
137
+ this . storeDOMData ( ) ;
138
+ this . delayedClearStore ( ) ;
139
+ }
140
+
141
+ // we can return snapshot here to circumvent the entire storing of dom data.
142
+ // would remove the need for setting a name!
143
+ return null ;
130
144
}
131
145
132
- componentDidUpdate ( prevProps : AnimatorProps ) {
133
- const { in : isIn , name } = this . props ;
146
+ componentDidUpdate ( prevProps : AnimatorProps , _ : AnimatorState ) {
147
+ const inPropSame = this . props . in === prevProps . in ;
148
+ const triggerSelfKeyPropSame = this . props . triggerSelfKey === prevProps . triggerSelfKey ;
134
149
135
- if ( isIn === prevProps . in ) {
150
+ if ( inPropSame && triggerSelfKeyPropSame ) {
136
151
// Nothing has changed, return early.
137
152
return ;
138
153
}
139
154
140
- if (
141
- process . env . NODE_ENV === 'development' &&
142
- ( isIn === undefined || prevProps . in === undefined )
143
- ) {
144
- warn (
145
- `You're switching between controlled and uncontrolled, don't do this. Either always set the "in" prop as true or false, or keep as undefined.`
155
+ if ( process . env . NODE_ENV === 'development' ) {
156
+ precondition (
157
+ ! ( this . props . in !== undefined && this . props . triggerSelfKey !== undefined ) ,
158
+ `Don't use "in" and "triggerSelfKey" together. If your element is persisted use "in". If your element is targeting itself for animations use "triggerSelfKey".`
159
+ ) ;
160
+ }
161
+
162
+ if ( process . env . NODE_ENV === 'development' ) {
163
+ precondition (
164
+ ! ( ( this . props . in === undefined || prevProps . in === undefined ) && ! inPropSame ) ,
165
+ `You're switching between persisted and unpersisted, don't do this. Either always set the "in" prop as true or false, or keep as undefined.`
166
+ ) ;
167
+ }
168
+
169
+ if ( process . env . NODE_ENV === 'development' ) {
170
+ precondition (
171
+ ! (
172
+ ( this . props . triggerSelfKey === undefined || prevProps . triggerSelfKey === undefined ) &&
173
+ ! triggerSelfKeyPropSame
174
+ ) ,
175
+ `You're switching between self triggering modes, don't do this. Either always set the "triggerSelfKey" prop, or keep as undefined.`
146
176
) ;
147
177
}
148
178
149
- if ( isIn ) {
150
- if ( store . has ( name ) ) {
179
+ if ( this . props . in ) {
180
+ if ( store . has ( this . props . name ) ) {
151
181
this . executeAnimations ( ) ;
182
+ // return early dont tell manager yet dawg
152
183
return ;
153
184
}
185
+ // No animation to trigger, tell manager we're all good regardless.
186
+ this . notifyVisibilityManagerAnimationsAreFinished ( ) ;
187
+ return ;
188
+ }
154
189
155
- this . showSelfAndNotifyManager ( ) ;
190
+ if ( ! triggerSelfKeyPropSame ) {
191
+ // Defer execution to the next frame to capture correctly.
192
+ // Make sure to keep react state the same for any inflight animations to be captured correctly.
193
+ requestAnimationFrame ( ( ) => {
194
+ this . abortAnimations ( ) ;
195
+ this . executeAnimations ( ) ;
196
+ } ) ;
156
197
}
157
198
}
158
199
159
200
componentWillUnmount ( ) {
201
+ if ( this . props . triggerSelfKey ) {
202
+ this . abortAnimations ( ) ;
203
+ this . unmounting = true ;
204
+ return ;
205
+ }
206
+
160
207
this . storeDOMData ( ) ;
161
208
this . delayedClearStore ( ) ;
162
209
this . abortAnimations ( ) ;
163
210
this . unmounting = true ;
164
211
}
165
212
166
- showSelfAndNotifyManager ( ) {
213
+ notifyVisibilityManagerAnimationsAreFinished ( ) {
167
214
const { context, name } = this . props ;
168
215
169
216
// If a VisibilityManager is a parent up the tree context will be available.
@@ -233,6 +280,7 @@ If it's an image, try and have the image loaded before mounting, or set a static
233
280
const { name, container : getContainer , context } = this . props ;
234
281
const container = typeof getContainer === 'function' ? getContainer ( ) : getContainer ;
235
282
const fromTarget = store . get ( name ) ;
283
+ let aborted = false ;
236
284
237
285
if ( fromTarget ) {
238
286
const { collectorData, elementData } = fromTarget ;
@@ -332,6 +380,10 @@ If it's an image, try and have the image loaded before mounting, or set a static
332
380
container . removeChild ( elementToMountChildren ) ;
333
381
}
334
382
383
+ if ( targetData . payload . abort ) {
384
+ targetData . payload . abort ( ) ;
385
+ }
386
+
335
387
if ( this . unmounting ) {
336
388
return ;
337
389
}
@@ -370,6 +422,8 @@ If it's an image, try and have the image loaded before mounting, or set a static
370
422
) ;
371
423
372
424
this . abortAnimations = ( ) => {
425
+ aborted = true ;
426
+
373
427
if ( this . animating ) {
374
428
this . animating = false ;
375
429
blocks . forEach ( block => block . forEach ( anim => anim . cleanup ( ) ) ) ;
@@ -423,6 +477,10 @@ If it's an image, try and have the image loaded before mounting, or set a static
423
477
) ;
424
478
} )
425
479
. then ( ( ) => {
480
+ if ( aborted ) {
481
+ return ;
482
+ }
483
+
426
484
blocks . forEach ( block => block . forEach ( anim => anim . cleanup ( ) ) ) ;
427
485
} )
428
486
. then ( ( ) => {
0 commit comments