25
removeRootViewTransitionClone,
26
cancelRootViewTransitionName,
27
restoreRootViewTransitionName,
28
+ applyViewTransitionName,
29
appendChild,
30
commitUpdate,
31
commitTextUpdate,
61
import {
62
restoreEnterOrExitViewTransitions,
63
restoreNestedViewTransitions,
64
+ appearingViewTransitions,
65
} from './ReactFiberCommitViewTransitions';
66
+import {
67
+ getViewTransitionName,
68
+ getViewTransitionClassName,
69
+} from './ReactFiberViewTransitionComponent';
70
71
let didWarnForRootClone = false;
72
84
const INSERT_APPEARING_PAIR = 7; // Inside a newly mounted tree only finding pairs.
85
type VisitPhase = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
86
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
+
109
function 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;
118
if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {
119
// This has no named view transitions in its subtree.
120
return;
131
const props: ViewTransitionProps = child.memoizedProps;
132
const name = props.name;
133
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
+ }
168
}
169
}
170
trackDeletedPairViewTransitions(child);
176
function trackEnterViewTransitions(deletion: Fiber): void {
177
if (deletion.tag === ViewTransitionComponent) {
178
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
+ }
214
}
215
// Look for more pairs deeper in the tree.
216
trackDeletedPairViewTransitions(deletion);
225
}
226
}
227
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
+
344
function recursivelyInsertNew(
345
parentFiber: Fiber,
346
hostParentClone: Instance,
482
// This was an Enter of a ViewTransition. We now move onto inserting the inner
483
// HostComponents and finding inner pairs.
484
nextPhase = INSERT_APPEND;
268
- // TODO: Mark the name and find a pair.
485
} else {
486
nextPhase = visitPhase;
487
}
491
viewTransitionState,
492
nextPhase,
493
);
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
+ }
504
popMutationContext(prevMutationContext);
505
break;
506
default: {
638
viewTransitionState,
639
nextPhase,
640
);
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
+ }
653
popMutationContext(prevMutationContext);
654
break;
655
default: {
911
// whether it resized or not.
912
finishedWork.flags |= Update;
913
}
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
+ }
926
popMutationContext(prevMutationContext);
927
break;
928
default: {