99
1010import type { Fiber , FiberRoot } from './ReactInternalTypes' ;
1111
12- import type { Instance , TextInstance } from './ReactFiberConfig' ;
12+ import type { Instance , TextInstance , Props } from './ReactFiberConfig' ;
1313
1414import type { OffscreenState } from './ReactFiberActivityComponent' ;
1515
@@ -25,6 +25,7 @@ import {
2525 removeRootViewTransitionClone ,
2626 cancelRootViewTransitionName ,
2727 restoreRootViewTransitionName ,
28+ cancelViewTransitionName ,
2829 applyViewTransitionName ,
2930 appendChild ,
3031 commitUpdate ,
@@ -39,6 +40,7 @@ import {
3940 popMutationContext ,
4041 pushMutationContext ,
4142 viewTransitionMutationContext ,
43+ trackHostMutation ,
4244} from './ReactFiberMutationTracking' ;
4345import {
4446 MutationMask ,
@@ -48,6 +50,7 @@ import {
4850 Visibility ,
4951 ViewTransitionNamedStatic ,
5052 ViewTransitionStatic ,
53+ AffectedParentLayout ,
5154} from './ReactFiberFlags' ;
5255import {
5356 HostComponent ,
@@ -61,9 +64,14 @@ import {
6164import {
6265 restoreEnterOrExitViewTransitions ,
6366 restoreNestedViewTransitions ,
67+ restoreUpdateViewTransitionForGesture ,
6468 appearingViewTransitions ,
6569 commitEnterViewTransitions ,
6670 measureNestedViewTransitions ,
71+ measureUpdateViewTransition ,
72+ viewTransitionCancelableChildren ,
73+ pushViewTransitionCancelableScope ,
74+ popViewTransitionCancelableScope ,
6775} from './ReactFiberCommitViewTransitions' ;
6876import {
6977 getViewTransitionName ,
@@ -72,6 +80,10 @@ import {
7280
7381let didWarnForRootClone = false ;
7482
83+ // Used during the apply phase to track whether a parent ViewTransition component
84+ // might have been affected by any mutations / relayouts below.
85+ let viewTransitionContextChanged : boolean = false ;
86+
7587function detectMutationOrInsertClones ( finishedWork : Fiber ) : boolean {
7688 return true ;
7789}
@@ -421,6 +433,7 @@ function recursivelyInsertNewFiber(
421433 // For insertions we don't need to clone. It's already new state node.
422434 if ( visitPhase !== INSERT_APPEARING_PAIR ) {
423435 appendChild ( hostParentClone , instance ) ;
436+ trackHostMutation ( ) ;
424437 recursivelyInsertNew (
425438 finishedWork ,
426439 instance ,
@@ -450,6 +463,7 @@ function recursivelyInsertNewFiber(
450463 // For insertions we don't need to clone. It's already new state node.
451464 if ( visitPhase !== INSERT_APPEARING_PAIR ) {
452465 appendChild ( hostParentClone , textInstance ) ;
466+ trackHostMutation ( ) ;
453467 }
454468 break ;
455469 }
@@ -575,6 +589,7 @@ function recursivelyInsertClonesFromExistingTree(
575589 }
576590 if ( visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE ) {
577591 unhideInstance ( clone , child . memoizedProps ) ;
592+ trackHostMutation ( ) ;
578593 }
579594 break ;
580595 }
@@ -590,6 +605,7 @@ function recursivelyInsertClonesFromExistingTree(
590605 appendChild ( hostParentClone , clone ) ;
591606 if ( visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE ) {
592607 unhideTextInstance ( clone , child . memoizedProps ) ;
608+ trackHostMutation ( ) ;
593609 }
594610 break ;
595611 }
@@ -679,6 +695,10 @@ function recursivelyInsertClones(
679695 for ( let i = 0 ; i < deletions . length ; i ++ ) {
680696 const childToDelete = deletions [ i ] ;
681697 trackEnterViewTransitions ( childToDelete ) ;
698+ // Normally we would only mark something as triggering a mutation if there was
699+ // actually a HostInstance below here. If this tree didn't contain a HostInstances
700+ // we shouldn't trigger a mutation even though a virtual component was deleted.
701+ trackHostMutation ( ) ;
682702 }
683703 }
684704
@@ -801,6 +821,7 @@ function insertDestinationClonesOfFiber(
801821 clone = cloneMutableInstance ( instance , true ) ;
802822 if ( finishedWork . flags & ContentReset ) {
803823 resetTextContent ( clone ) ;
824+ trackHostMutation ( ) ;
804825 }
805826 } else {
806827 // If we have children we'll clone them as we walk the tree so we just
@@ -825,6 +846,7 @@ function insertDestinationClonesOfFiber(
825846 ) ;
826847 appendChild ( hostParentClone , clone ) ;
827848 unhideInstance ( clone , finishedWork . memoizedProps ) ;
849+ trackHostMutation ( ) ;
828850 } else {
829851 recursivelyInsertClones ( finishedWork , clone , null , visitPhase ) ;
830852 appendChild ( hostParentClone , clone ) ;
@@ -851,10 +873,12 @@ function insertDestinationClonesOfFiber(
851873 const newText : string = finishedWork . memoizedProps ;
852874 const oldText : string = current . memoizedProps ;
853875 commitTextUpdate ( clone , newText , oldText ) ;
876+ trackHostMutation ( ) ;
854877 }
855878 appendChild ( hostParentClone , clone ) ;
856879 if ( visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE ) {
857880 unhideTextInstance ( clone , finishedWork . memoizedProps ) ;
881+ trackHostMutation ( ) ;
858882 }
859883 break ;
860884 }
@@ -885,6 +909,10 @@ function insertDestinationClonesOfFiber(
885909 } else if ( current !== null && current . memoizedState === null ) {
886910 // Was previously mounted as visible but is now hidden.
887911 trackEnterViewTransitions ( current ) ;
912+ // Normally we would only mark something as triggering a mutation if there was
913+ // actually a HostInstance below here. If this tree didn't contain a HostInstances
914+ // we shouldn't trigger a mutation even though a virtual component was hidden.
915+ trackHostMutation ( ) ;
888916 }
889917 break ;
890918 }
@@ -991,13 +1019,6 @@ function measureExitViewTransitions(placement: Fiber): void {
9911019 }
9921020}
9931021
994- function measureUpdateViewTransition (
995- current : Fiber ,
996- finishedWork : Fiber ,
997- ) : void {
998- // TODO
999- }
1000-
10011022function recursivelyApplyViewTransitions ( parentFiber : Fiber ) {
10021023 const deletions = parentFiber . deletions ;
10031024 if ( deletions !== null ) {
@@ -1037,15 +1058,6 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
10371058 // because the fiber tag is more specific. An exception is any flag related
10381059 // to reconciliation, because those can be set on all fiber types.
10391060 switch ( finishedWork . tag ) {
1040- case HostComponent : {
1041- // const instance: Instance = finishedWork.stateNode;
1042- // TODO: Apply name and measure.
1043- recursivelyApplyViewTransitions ( finishedWork ) ;
1044- break ;
1045- }
1046- case HostText : {
1047- break ;
1048- }
10491061 case HostPortal : {
10501062 // TODO: Consider what should happen to Portals. For now we exclude them.
10511063 break ;
@@ -1063,12 +1075,59 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
10631075 }
10641076 break ;
10651077 }
1066- case ViewTransitionComponent :
1067- measureUpdateViewTransition ( current , finishedWork ) ;
1078+ case ViewTransitionComponent : {
1079+ const prevContextChanged = viewTransitionContextChanged ;
1080+ const prevCancelableChildren = pushViewTransitionCancelableScope ( ) ;
1081+ viewTransitionContextChanged = false ;
1082+ recursivelyApplyViewTransitions ( finishedWork ) ;
1083+
1084+ if ( viewTransitionContextChanged ) {
1085+ finishedWork . flags |= Update ;
1086+ }
1087+
1088+ const inViewport = measureUpdateViewTransition (
1089+ current ,
1090+ finishedWork ,
1091+ true ,
1092+ ) ;
1093+
1094+ if ( ( finishedWork . flags & Update ) === NoFlags || ! inViewport ) {
1095+ // If this boundary didn't update, then we may be able to cancel its children.
1096+ // We bubble them up to the parent set to be determined later if we can cancel.
1097+ // Similarly, if old and new state was outside the viewport, we can skip it
1098+ // even if it did update.
1099+ if ( prevCancelableChildren === null ) {
1100+ // Bubbling up this whole set to the parent.
1101+ } else {
1102+ // Merge with parent set.
1103+ // $FlowFixMe[method-unbinding]
1104+ prevCancelableChildren . push . apply (
1105+ prevCancelableChildren ,
1106+ viewTransitionCancelableChildren ,
1107+ ) ;
1108+ popViewTransitionCancelableScope ( prevCancelableChildren ) ;
1109+ }
1110+ // TODO: If this doesn't end up canceled, because a parent animates,
1111+ // then we should probably issue an event since this instance is part of it.
1112+ } else {
1113+ // TODO: Schedule gesture events.
1114+ // If this boundary did update, we cannot cancel its children so those are dropped.
1115+ popViewTransitionCancelableScope ( prevCancelableChildren ) ;
1116+ }
1117+
1118+ if ( ( finishedWork . flags & AffectedParentLayout ) !== NoFlags ) {
1119+ // This boundary changed size in a way that may have caused its parent to
1120+ // relayout. We need to bubble this information up to the parent.
1121+ viewTransitionContextChanged = true ;
1122+ } else {
1123+ // Otherwise, we restore it to whatever the parent had found so far.
1124+ viewTransitionContextChanged = prevContextChanged ;
1125+ }
1126+
10681127 const viewTransitionState : ViewTransitionState = finishedWork . stateNode ;
10691128 viewTransitionState . clones = null ; // Reset
1070- recursivelyApplyViewTransitions ( finishedWork ) ;
10711129 break ;
1130+ }
10721131 default : {
10731132 recursivelyApplyViewTransitions ( finishedWork ) ;
10741133 break ;
@@ -1082,13 +1141,38 @@ export function applyDepartureTransitions(
10821141 finishedWork : Fiber ,
10831142) : void {
10841143 // First measure and apply view-transition-names to the "new" states.
1144+ viewTransitionContextChanged = false ;
1145+ pushViewTransitionCancelableScope ( ) ;
1146+
10851147 recursivelyApplyViewTransitions ( finishedWork ) ;
1148+
10861149 // Then remove the clones.
10871150 const rootClone = root . gestureClone ;
10881151 if ( rootClone !== null ) {
10891152 root . gestureClone = null ;
10901153 removeRootViewTransitionClone ( root . containerInfo , rootClone ) ;
10911154 }
1155+
1156+ if ( ! viewTransitionContextChanged ) {
1157+ // If we didn't leak any resizing out to the root, we don't have to transition
1158+ // the root itself. This means that we can now safely cancel any cancellations
1159+ // that bubbled all the way up.
1160+ const cancelableChildren = viewTransitionCancelableChildren ;
1161+ if ( cancelableChildren !== null ) {
1162+ for ( let i = 0 ; i < cancelableChildren . length ; i += 3 ) {
1163+ cancelViewTransitionName (
1164+ ( ( cancelableChildren [ i ] : any ) : Instance ) ,
1165+ ( ( cancelableChildren [ i + 1 ] : any ) : string ) ,
1166+ ( ( cancelableChildren [ i + 2 ] : any ) : Props ) ,
1167+ ) ;
1168+ }
1169+ }
1170+ // We also cancel the root itself. First we restore the name to the documentElement
1171+ // and then we cancel it.
1172+ restoreRootViewTransitionName ( root . containerInfo ) ;
1173+ cancelRootViewTransitionName ( root . containerInfo ) ;
1174+ }
1175+ popViewTransitionCancelableScope ( null ) ;
10921176}
10931177
10941178function recursivelyRestoreViewTransitions ( parentFiber : Fiber ) {
@@ -1130,15 +1214,6 @@ function restoreViewTransitionsOnFiber(finishedWork: Fiber) {
11301214 // because the fiber tag is more specific. An exception is any flag related
11311215 // to reconciliation, because those can be set on all fiber types.
11321216 switch ( finishedWork . tag ) {
1133- case HostComponent : {
1134- // const instance: Instance = finishedWork.stateNode;
1135- // TODO: Restore the name.
1136- recursivelyRestoreViewTransitions ( finishedWork ) ;
1137- break ;
1138- }
1139- case HostText : {
1140- break ;
1141- }
11421217 case HostPortal : {
11431218 // TODO: Consider what should happen to Portals. For now we exclude them.
11441219 break ;
@@ -1157,8 +1232,7 @@ function restoreViewTransitionsOnFiber(finishedWork: Fiber) {
11571232 break ;
11581233 }
11591234 case ViewTransitionComponent :
1160- const viewTransitionState : ViewTransitionState = finishedWork . stateNode ;
1161- viewTransitionState . clones = null ; // Reset
1235+ restoreUpdateViewTransitionForGesture ( current , finishedWork ) ;
11621236 recursivelyRestoreViewTransitions ( finishedWork ) ;
11631237 break ;
11641238 default : {
0 commit comments