9
9
10
10
import type { Fiber , FiberRoot } from './ReactInternalTypes' ;
11
11
12
- import type { Instance , TextInstance } from './ReactFiberConfig' ;
12
+ import type { Instance , TextInstance , Props } from './ReactFiberConfig' ;
13
13
14
14
import type { OffscreenState } from './ReactFiberActivityComponent' ;
15
15
@@ -25,6 +25,7 @@ import {
25
25
removeRootViewTransitionClone ,
26
26
cancelRootViewTransitionName ,
27
27
restoreRootViewTransitionName ,
28
+ cancelViewTransitionName ,
28
29
applyViewTransitionName ,
29
30
appendChild ,
30
31
commitUpdate ,
@@ -39,6 +40,7 @@ import {
39
40
popMutationContext ,
40
41
pushMutationContext ,
41
42
viewTransitionMutationContext ,
43
+ trackHostMutation ,
42
44
} from './ReactFiberMutationTracking' ;
43
45
import {
44
46
MutationMask ,
@@ -48,6 +50,7 @@ import {
48
50
Visibility ,
49
51
ViewTransitionNamedStatic ,
50
52
ViewTransitionStatic ,
53
+ AffectedParentLayout ,
51
54
} from './ReactFiberFlags' ;
52
55
import {
53
56
HostComponent ,
@@ -61,9 +64,14 @@ import {
61
64
import {
62
65
restoreEnterOrExitViewTransitions ,
63
66
restoreNestedViewTransitions ,
67
+ restoreUpdateViewTransitionForGesture ,
64
68
appearingViewTransitions ,
65
69
commitEnterViewTransitions ,
66
70
measureNestedViewTransitions ,
71
+ measureUpdateViewTransition ,
72
+ viewTransitionCancelableChildren ,
73
+ pushViewTransitionCancelableScope ,
74
+ popViewTransitionCancelableScope ,
67
75
} from './ReactFiberCommitViewTransitions' ;
68
76
import {
69
77
getViewTransitionName ,
@@ -72,6 +80,10 @@ import {
72
80
73
81
let didWarnForRootClone = false ;
74
82
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
+
75
87
function detectMutationOrInsertClones ( finishedWork : Fiber ) : boolean {
76
88
return true ;
77
89
}
@@ -421,6 +433,7 @@ function recursivelyInsertNewFiber(
421
433
// For insertions we don't need to clone. It's already new state node.
422
434
if ( visitPhase !== INSERT_APPEARING_PAIR ) {
423
435
appendChild ( hostParentClone , instance ) ;
436
+ trackHostMutation ( ) ;
424
437
recursivelyInsertNew (
425
438
finishedWork ,
426
439
instance ,
@@ -450,6 +463,7 @@ function recursivelyInsertNewFiber(
450
463
// For insertions we don't need to clone. It's already new state node.
451
464
if ( visitPhase !== INSERT_APPEARING_PAIR ) {
452
465
appendChild ( hostParentClone , textInstance ) ;
466
+ trackHostMutation ( ) ;
453
467
}
454
468
break ;
455
469
}
@@ -575,6 +589,7 @@ function recursivelyInsertClonesFromExistingTree(
575
589
}
576
590
if ( visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE ) {
577
591
unhideInstance ( clone , child . memoizedProps ) ;
592
+ trackHostMutation ( ) ;
578
593
}
579
594
break ;
580
595
}
@@ -590,6 +605,7 @@ function recursivelyInsertClonesFromExistingTree(
590
605
appendChild ( hostParentClone , clone ) ;
591
606
if ( visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE ) {
592
607
unhideTextInstance ( clone , child . memoizedProps ) ;
608
+ trackHostMutation ( ) ;
593
609
}
594
610
break ;
595
611
}
@@ -679,6 +695,10 @@ function recursivelyInsertClones(
679
695
for ( let i = 0 ; i < deletions . length ; i ++ ) {
680
696
const childToDelete = deletions [ i ] ;
681
697
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 ( ) ;
682
702
}
683
703
}
684
704
@@ -801,6 +821,7 @@ function insertDestinationClonesOfFiber(
801
821
clone = cloneMutableInstance ( instance , true ) ;
802
822
if ( finishedWork . flags & ContentReset ) {
803
823
resetTextContent ( clone ) ;
824
+ trackHostMutation ( ) ;
804
825
}
805
826
} else {
806
827
// If we have children we'll clone them as we walk the tree so we just
@@ -825,6 +846,7 @@ function insertDestinationClonesOfFiber(
825
846
) ;
826
847
appendChild ( hostParentClone , clone ) ;
827
848
unhideInstance ( clone , finishedWork . memoizedProps ) ;
849
+ trackHostMutation ( ) ;
828
850
} else {
829
851
recursivelyInsertClones ( finishedWork , clone , null , visitPhase ) ;
830
852
appendChild ( hostParentClone , clone ) ;
@@ -851,10 +873,12 @@ function insertDestinationClonesOfFiber(
851
873
const newText : string = finishedWork . memoizedProps ;
852
874
const oldText : string = current . memoizedProps ;
853
875
commitTextUpdate ( clone , newText , oldText ) ;
876
+ trackHostMutation ( ) ;
854
877
}
855
878
appendChild ( hostParentClone , clone ) ;
856
879
if ( visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE ) {
857
880
unhideTextInstance ( clone , finishedWork . memoizedProps ) ;
881
+ trackHostMutation ( ) ;
858
882
}
859
883
break ;
860
884
}
@@ -885,6 +909,10 @@ function insertDestinationClonesOfFiber(
885
909
} else if ( current !== null && current . memoizedState === null ) {
886
910
// Was previously mounted as visible but is now hidden.
887
911
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 ( ) ;
888
916
}
889
917
break ;
890
918
}
@@ -991,13 +1019,6 @@ function measureExitViewTransitions(placement: Fiber): void {
991
1019
}
992
1020
}
993
1021
994
- function measureUpdateViewTransition (
995
- current : Fiber ,
996
- finishedWork : Fiber ,
997
- ) : void {
998
- // TODO
999
- }
1000
-
1001
1022
function recursivelyApplyViewTransitions ( parentFiber : Fiber ) {
1002
1023
const deletions = parentFiber . deletions ;
1003
1024
if ( deletions !== null ) {
@@ -1037,15 +1058,6 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
1037
1058
// because the fiber tag is more specific. An exception is any flag related
1038
1059
// to reconciliation, because those can be set on all fiber types.
1039
1060
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
- }
1049
1061
case HostPortal : {
1050
1062
// TODO: Consider what should happen to Portals. For now we exclude them.
1051
1063
break ;
@@ -1063,12 +1075,59 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
1063
1075
}
1064
1076
break ;
1065
1077
}
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
+
1068
1127
const viewTransitionState : ViewTransitionState = finishedWork . stateNode ;
1069
1128
viewTransitionState . clones = null ; // Reset
1070
- recursivelyApplyViewTransitions ( finishedWork ) ;
1071
1129
break ;
1130
+ }
1072
1131
default : {
1073
1132
recursivelyApplyViewTransitions ( finishedWork ) ;
1074
1133
break ;
@@ -1082,13 +1141,38 @@ export function applyDepartureTransitions(
1082
1141
finishedWork : Fiber ,
1083
1142
) : void {
1084
1143
// First measure and apply view-transition-names to the "new" states.
1144
+ viewTransitionContextChanged = false ;
1145
+ pushViewTransitionCancelableScope ( ) ;
1146
+
1085
1147
recursivelyApplyViewTransitions ( finishedWork ) ;
1148
+
1086
1149
// Then remove the clones.
1087
1150
const rootClone = root . gestureClone ;
1088
1151
if ( rootClone !== null ) {
1089
1152
root . gestureClone = null ;
1090
1153
removeRootViewTransitionClone ( root . containerInfo , rootClone ) ;
1091
1154
}
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 ) ;
1092
1176
}
1093
1177
1094
1178
function recursivelyRestoreViewTransitions ( parentFiber : Fiber ) {
@@ -1130,15 +1214,6 @@ function restoreViewTransitionsOnFiber(finishedWork: Fiber) {
1130
1214
// because the fiber tag is more specific. An exception is any flag related
1131
1215
// to reconciliation, because those can be set on all fiber types.
1132
1216
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
- }
1142
1217
case HostPortal : {
1143
1218
// TODO: Consider what should happen to Portals. For now we exclude them.
1144
1219
break ;
@@ -1157,8 +1232,7 @@ function restoreViewTransitionsOnFiber(finishedWork: Fiber) {
1157
1232
break ;
1158
1233
}
1159
1234
case ViewTransitionComponent :
1160
- const viewTransitionState : ViewTransitionState = finishedWork . stateNode ;
1161
- viewTransitionState . clones = null ; // Reset
1235
+ restoreUpdateViewTransitionForGesture ( current , finishedWork ) ;
1162
1236
recursivelyRestoreViewTransitions ( finishedWork ) ;
1163
1237
break ;
1164
1238
default : {
0 commit comments