@@ -25,6 +25,7 @@ import {
2525 removeRootViewTransitionClone ,
2626 cancelRootViewTransitionName ,
2727 restoreRootViewTransitionName ,
28+ applyViewTransitionName ,
2829 appendChild ,
2930 commitUpdate ,
3031 commitTextUpdate ,
@@ -60,7 +61,12 @@ import {
6061import {
6162 restoreEnterOrExitViewTransitions ,
6263 restoreNestedViewTransitions ,
64+ appearingViewTransitions ,
6365} from './ReactFiberCommitViewTransitions' ;
66+ import {
67+ getViewTransitionName ,
68+ getViewTransitionClassName ,
69+ } from './ReactFiberViewTransitionComponent' ;
6470
6571let didWarnForRootClone = false ;
6672
@@ -78,7 +84,37 @@ const INSERT_APPEND = 6; // Inside a newly mounted tree before the next HostComp
7884const INSERT_APPEARING_PAIR = 7 ; // Inside a newly mounted tree only finding pairs.
7985type VisitPhase = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ;
8086
87+ function applyViewTransitionToClones (
88+ name : string ,
89+ className : ?string ,
90+ clones : Array < Instance > ,
91+ ) : void {
92+ // This gets called when we have found a pair, but after the clone in created. The clone is
93+ // created by the insertion side. If the insertion side if found before the deletion side
94+ // then this is called by the deletion. If the deletion is visited first then this is called
95+ // later by the insertion when the clone has been created.
96+ for ( let i = 0 ; i < clones . length ; i ++ ) {
97+ applyViewTransitionName (
98+ clones [ i ] ,
99+ i === 0
100+ ? name
101+ : // If we have multiple Host Instances below, we add a suffix to the name to give
102+ // each one a unique name.
103+ name + '_' + i ,
104+ className ,
105+ ) ;
106+ }
107+ }
108+
81109function trackDeletedPairViewTransitions ( deletion : Fiber ) : void {
110+ if (
111+ appearingViewTransitions === null ||
112+ appearingViewTransitions . size === 0
113+ ) {
114+ // We've found all.
115+ return ;
116+ }
117+ const pairs = appearingViewTransitions ;
82118 if ( ( deletion . subtreeFlags & ViewTransitionNamedStatic ) === NoFlags ) {
83119 // This has no named view transitions in its subtree.
84120 return ;
@@ -95,7 +131,40 @@ function trackDeletedPairViewTransitions(deletion: Fiber): void {
95131 const props : ViewTransitionProps = child . memoizedProps ;
96132 const name = props . name ;
97133 if ( name != null && name !== 'auto' ) {
98- // TODO: Find a pair
134+ const pair = pairs . get ( name ) ;
135+ if ( pair !== undefined ) {
136+ // Delete the entry so that we know when we've found all of them
137+ // and can stop searching (size reaches zero).
138+ pairs . delete ( name ) ;
139+ const className : ?string = getViewTransitionClassName (
140+ props . className ,
141+ props . share ,
142+ ) ;
143+ if ( className !== 'none' ) {
144+ // TODO: Since the deleted instance already has layout we could
145+ // check if it's in the viewport and if not skip the pairing.
146+ // It would currently cause layout thrash though so if we did that
147+ // we need to avoid inserting the root of the cloned trees until
148+ // the end.
149+
150+ // The "old" instance is actually the one we're inserting.
151+ const oldInstance : ViewTransitionState = pair ;
152+ // The "new" instance is the already mounted one we're deleting.
153+ const newInstance : ViewTransitionState = child . stateNode ;
154+ oldInstance . paired = newInstance ;
155+ newInstance . paired = oldInstance ;
156+ const clones = oldInstance . clones ;
157+ if ( clones !== null ) {
158+ // If we have clones that means that we've already visited this
159+ // ViewTransition boundary before and we can now apply the name
160+ // to those clones. Otherwise, we have to wait until we clone it.
161+ applyViewTransitionToClones ( name , className , clones ) ;
162+ }
163+ }
164+ if ( pairs . size === 0 ) {
165+ break ;
166+ }
167+ }
99168 }
100169 }
101170 trackDeletedPairViewTransitions ( child ) ;
@@ -107,9 +176,41 @@ function trackDeletedPairViewTransitions(deletion: Fiber): void {
107176function trackEnterViewTransitions ( deletion : Fiber ) : void {
108177 if ( deletion . tag === ViewTransitionComponent ) {
109178 const props : ViewTransitionProps = deletion . memoizedProps ;
110- const name = props . name ;
111- if ( name != null && name !== 'auto' ) {
112- // TODO: Find a pair
179+ const name = getViewTransitionName ( props , deletion . stateNode ) ;
180+ const pair =
181+ appearingViewTransitions !== null
182+ ? appearingViewTransitions . get ( name )
183+ : undefined ;
184+ const className : ?string = getViewTransitionClassName (
185+ props . className ,
186+ pair !== undefined ? props . share : props . enter ,
187+ ) ;
188+ if ( className !== 'none' ) {
189+ if ( pair !== undefined ) {
190+ // TODO: Since the deleted instance already has layout we could
191+ // check if it's in the viewport and if not skip the pairing.
192+ // It would currently cause layout thrash though so if we did that
193+ // we need to avoid inserting the root of the cloned trees until
194+ // the end.
195+
196+ // Delete the entry so that we know when we've found all of them
197+ // and can stop searching (size reaches zero).
198+ // $FlowFixMe[incompatible-use]: Refined by the pair.
199+ appearingViewTransitions . delete ( name ) ;
200+ // The "old" instance is actually the one we're inserting.
201+ const oldInstance : ViewTransitionState = pair ;
202+ // The "new" instance is the already mounted one we're deleting.
203+ const newInstance : ViewTransitionState = deletion . stateNode ;
204+ oldInstance . paired = newInstance ;
205+ newInstance . paired = oldInstance ;
206+ const clones = oldInstance . clones ;
207+ if ( clones !== null ) {
208+ // If we have clones that means that we've already visited this
209+ // ViewTransition boundary before and we can now apply the name
210+ // to those clones. Otherwise, we have to wait until we clone it.
211+ applyViewTransitionToClones ( name , className , clones ) ;
212+ }
213+ }
113214 }
114215 // Look for more pairs deeper in the tree.
115216 trackDeletedPairViewTransitions ( deletion ) ;
@@ -124,6 +225,122 @@ function trackEnterViewTransitions(deletion: Fiber): void {
124225 }
125226}
126227
228+ function applyAppearingPairViewTransition ( child : Fiber ) : void {
229+ // Normally these helpers do recursive calls but since insertion/offscreen is forked
230+ // we call this helper from those loops instead. This must be called only on
231+ // ViewTransitionComponent that has already had their clones filled.
232+ if ( ( child . flags & ViewTransitionNamedStatic ) !== NoFlags ) {
233+ const state : ViewTransitionState = child . stateNode ;
234+ // If this is not yet paired, it doesn't mean that we won't pair it later when
235+ // we find the deletion side. If that's the case then we'll add the names to
236+ // the clones then.
237+ if ( state . paired ) {
238+ const props : ViewTransitionProps = child . memoizedProps ;
239+ if ( props . name == null || props . name === 'auto' ) {
240+ throw new Error (
241+ 'Found a pair with an auto name. This is a bug in React.' ,
242+ ) ;
243+ }
244+ const name = props . name ;
245+ // Note that this class name that doesn't actually really matter because the
246+ // "new" side will be the one that wins in practice.
247+ const className : ?string = getViewTransitionClassName (
248+ props . className ,
249+ props . share ,
250+ ) ;
251+ if ( className !== 'none' ) {
252+ const clones = state . clones ;
253+ // If there are no clones at this point, that should mean that there are no
254+ // HostComponent children in this ViewTransition.
255+ if ( clones !== null ) {
256+ applyViewTransitionToClones ( name , className , clones ) ;
257+ }
258+ }
259+ }
260+ }
261+ }
262+
263+ function applyExitViewTransition ( placement : Fiber ) : void {
264+ // Normally these helpers do recursive calls but since insertion/offscreen is forked
265+ // we call this helper from those loops instead. This must be called only on
266+ // ViewTransitionComponent that has already had their clones filled.
267+ const state : ViewTransitionState = placement . stateNode ;
268+ const props : ViewTransitionProps = placement . memoizedProps ;
269+ const name = getViewTransitionName ( props , state ) ;
270+ const className : ?string = getViewTransitionClassName (
271+ props . className ,
272+ // Note that just because we don't have a pair yet doesn't mean we won't find one
273+ // later. However, that doesn't matter because if we do the class name that wins
274+ // is the one applied by the "new" side anyway.
275+ state . paired ? props . share : props . exit ,
276+ ) ;
277+ if ( className !== 'none' ) {
278+ // TODO: Ideally we could determine if this exit is in the viewport and
279+ // exclude it otherwise but that would require waiting until we insert
280+ // and layout the clones first. Currently wait until the view transition
281+ // starts before reading the layout.
282+ const clones = state . clones ;
283+ // If there are no clones at this point, that should mean that there are no
284+ // HostComponent children in this ViewTransition.
285+ if ( clones !== null ) {
286+ applyViewTransitionToClones ( name , className , clones ) ;
287+ }
288+ }
289+ }
290+
291+ function applyNestedViewTransition ( child : Fiber ) : void {
292+ const state : ViewTransitionState = child . stateNode ;
293+ const props : ViewTransitionProps = child . memoizedProps ;
294+ const name = getViewTransitionName ( props , state ) ;
295+ const className : ?string = getViewTransitionClassName (
296+ props . className ,
297+ props . layout ,
298+ ) ;
299+ if ( className !== 'none' ) {
300+ const clones = state . clones ;
301+ // If there are no clones at this point, that should mean that there are no
302+ // HostComponent children in this ViewTransition.
303+ if ( clones !== null ) {
304+ applyViewTransitionToClones ( name , className , clones ) ;
305+ }
306+ }
307+ }
308+
309+ function applyUpdateViewTransition ( current : Fiber , finishedWork : Fiber ) : void {
310+ const state : ViewTransitionState = finishedWork . stateNode ;
311+ // Updates can have conflicting names and classNames.
312+ // Since we're doing a reverse animation the "new" state is actually the current
313+ // and the "old" state is the finishedWork.
314+ const newProps : ViewTransitionProps = current . memoizedProps ;
315+ const oldProps : ViewTransitionProps = finishedWork . memoizedProps ;
316+ const oldName = getViewTransitionName ( oldProps , state ) ;
317+ // This className applies only if there are fewer child DOM nodes than
318+ // before or if this update should've been cancelled but we ended up with
319+ // a parent animating so we need to animate the child too. Otherwise
320+ // the "new" state wins. Since "new" normally wins, that's usually what
321+ // we would use. However, since this animation is going in reverse we actually
322+ // want the props from "current" since that's the class that would've won if
323+ // it was the normal direction. To preserve the same effect in either direction.
324+ let className : ?string = getViewTransitionClassName (
325+ newProps . className ,
326+ newProps . update ,
327+ ) ;
328+ if ( className === 'none' ) {
329+ className = getViewTransitionClassName ( newProps . className , newProps . layout ) ;
330+ if ( className === 'none' ) {
331+ // If both update and layout are both "none" then we don't have to
332+ // apply a name. Since we won't animate this boundary.
333+ return ;
334+ }
335+ }
336+ const clones = state . clones ;
337+ // If there are no clones at this point, that should mean that there are no
338+ // HostComponent children in this ViewTransition.
339+ if ( clones !== null ) {
340+ applyViewTransitionToClones ( oldName , className , clones ) ;
341+ }
342+ }
343+
127344function recursivelyInsertNew (
128345 parentFiber : Fiber ,
129346 hostParentClone : Instance ,
@@ -265,7 +482,6 @@ function recursivelyInsertNewFiber(
265482 // This was an Enter of a ViewTransition. We now move onto inserting the inner
266483 // HostComponents and finding inner pairs.
267484 nextPhase = INSERT_APPEND ;
268- // TODO: Mark the name and find a pair.
269485 } else {
270486 nextPhase = visitPhase ;
271487 }
@@ -275,6 +491,16 @@ function recursivelyInsertNewFiber(
275491 viewTransitionState ,
276492 nextPhase ,
277493 ) ;
494+ // After we've inserted the new nodes into the "clones" set we can apply share
495+ // or exit transitions to them.
496+ if ( visitPhase === INSERT_EXIT ) {
497+ applyExitViewTransition ( finishedWork ) ;
498+ } else if (
499+ visitPhase === INSERT_APPEARING_PAIR ||
500+ visitPhase === INSERT_APPEND
501+ ) {
502+ applyAppearingPairViewTransition ( finishedWork ) ;
503+ }
278504 popMutationContext ( prevMutationContext ) ;
279505 break ;
280506 default: {
@@ -412,8 +638,18 @@ function recursivelyInsertClonesFromExistingTree(
412638 viewTransitionState ,
413639 nextPhase ,
414640 ) ;
415- // TODO: Only the first level should track if this was s
416- // child.flags |= Update;
641+ // After we've collected the cloned instances, we can apply exit or share transitions
642+ // to them.
643+ if ( visitPhase === CLONE_EXIT ) {
644+ applyExitViewTransition ( child ) ;
645+ } else if (
646+ visitPhase === CLONE_APPEARING_PAIR ||
647+ visitPhase === CLONE_UNHIDE
648+ ) {
649+ applyAppearingPairViewTransition ( child ) ;
650+ } else if ( visitPhase === CLONE_UPDATE ) {
651+ applyNestedViewTransition ( child ) ;
652+ }
417653 popMutationContext ( prevMutationContext ) ;
418654 break ;
419655 default: {
@@ -675,6 +911,18 @@ function insertDestinationClonesOfFiber(
675911 // whether it resized or not.
676912 finishedWork . flags |= Update ;
677913 }
914+ // After we've collected the cloned instances, we can apply exit or share transitions
915+ // to them.
916+ if ( visitPhase === CLONE_EXIT ) {
917+ applyExitViewTransition ( finishedWork ) ;
918+ } else if (
919+ visitPhase === CLONE_APPEARING_PAIR ||
920+ visitPhase === CLONE_UNHIDE
921+ ) {
922+ applyAppearingPairViewTransition ( finishedWork ) ;
923+ } else if ( visitPhase === CLONE_UPDATE ) {
924+ applyUpdateViewTransition ( current , finishedWork ) ;
925+ }
678926 popMutationContext ( prevMutationContext ) ;
679927 break ;
680928 default: {
0 commit comments