@@ -36,7 +36,7 @@ export interface ChildProps {
3636 className ?: string ;
3737}
3838
39- export interface State {
39+ export interface AnimatorState {
4040 childProps : ChildProps ;
4141 animationsMarkup : React . ReactPortal [ ] ;
4242}
@@ -48,11 +48,18 @@ export interface AnimatorProps extends CollectorChildrenProps, InjectedProps {
4848 name : string ;
4949
5050 /**
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".
5663 */
5764 in ?: boolean ;
5865
@@ -75,7 +82,7 @@ export interface AnimatorProps extends CollectorChildrenProps, InjectedProps {
7582 container : HTMLElement | ( ( ) => HTMLElement ) ;
7683}
7784
78- export default class Animator extends React . PureComponent < AnimatorProps , State > {
85+ export default class Animator extends React . PureComponent < AnimatorProps , AnimatorState > {
7986 static displayName = 'Animator' ;
8087
8188 static defaultProps = {
@@ -84,7 +91,7 @@ export default class Animator extends React.PureComponent<AnimatorProps, State>
8491 container : document . body ,
8592 } ;
8693
87- state : State = {
94+ state : AnimatorState = {
8895 animationsMarkup : [ ] ,
8996 childProps : { } ,
9097 } ;
@@ -115,55 +122,95 @@ export default class Animator extends React.PureComponent<AnimatorProps, State>
115122 if ( componentIn === undefined || componentIn ) {
116123 // Ok nothing is there yet, show ourself and store DOM data for later.
117124 // We'll be waiting for another Animator to mount.
118- this . showSelfAndNotifyManager ( ) ;
125+ this . notifyVisibilityManagerAnimationsAreFinished ( ) ;
119126 }
120127 }
121128
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 ) {
126131 this . storeDOMData ( ) ;
127132 this . delayedClearStore ( ) ;
128133 this . abortAnimations ( ) ;
129134 }
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 ;
130144 }
131145
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 ;
134149
135- if ( isIn === prevProps . in ) {
150+ if ( inPropSame && triggerSelfKeyPropSame ) {
136151 // Nothing has changed, return early.
137152 return ;
138153 }
139154
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.`
146176 ) ;
147177 }
148178
149- if ( isIn ) {
150- if ( store . has ( name ) ) {
179+ if ( this . props . in ) {
180+ if ( store . has ( this . props . name ) ) {
151181 this . executeAnimations ( ) ;
182+ // return early dont tell manager yet dawg
152183 return ;
153184 }
185+ // No animation to trigger, tell manager we're all good regardless.
186+ this . notifyVisibilityManagerAnimationsAreFinished ( ) ;
187+ return ;
188+ }
154189
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+ } ) ;
156197 }
157198 }
158199
159200 componentWillUnmount ( ) {
201+ if ( this . props . triggerSelfKey ) {
202+ this . abortAnimations ( ) ;
203+ this . unmounting = true ;
204+ return ;
205+ }
206+
160207 this . storeDOMData ( ) ;
161208 this . delayedClearStore ( ) ;
162209 this . abortAnimations ( ) ;
163210 this . unmounting = true ;
164211 }
165212
166- showSelfAndNotifyManager ( ) {
213+ notifyVisibilityManagerAnimationsAreFinished ( ) {
167214 const { context, name } = this . props ;
168215
169216 // 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
233280 const { name, container : getContainer , context } = this . props ;
234281 const container = typeof getContainer === 'function' ? getContainer ( ) : getContainer ;
235282 const fromTarget = store . get ( name ) ;
283+ let aborted = false ;
236284
237285 if ( fromTarget ) {
238286 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
332380 container . removeChild ( elementToMountChildren ) ;
333381 }
334382
383+ if ( targetData . payload . abort ) {
384+ targetData . payload . abort ( ) ;
385+ }
386+
335387 if ( this . unmounting ) {
336388 return ;
337389 }
@@ -370,6 +422,8 @@ If it's an image, try and have the image loaded before mounting, or set a static
370422 ) ;
371423
372424 this . abortAnimations = ( ) => {
425+ aborted = true ;
426+
373427 if ( this . animating ) {
374428 this . animating = false ;
375429 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
423477 ) ;
424478 } )
425479 . then ( ( ) => {
480+ if ( aborted ) {
481+ return ;
482+ }
483+
426484 blocks . forEach ( block => block . forEach ( anim => anim . cleanup ( ) ) ) ;
427485 } )
428486 . then ( ( ) => {
0 commit comments