13
14
import type {OffscreenState} from './ReactFiberActivityComponent';
15
16
+import type {
17
+ ViewTransitionState,
18
+ ViewTransitionProps,
19
+} from './ReactFiberViewTransitionComponent';
20
+
21
import {
22
cloneMutableInstance,
23
cloneMutableTextInstance,
45
ContentReset,
46
NoFlags,
47
Visibility,
48
+ ViewTransitionNamedStatic,
49
+ ViewTransitionStatic,
50
} from './ReactFiberFlags';
51
import {
52
HostComponent,
57
OffscreenComponent,
58
ViewTransitionComponent,
59
} from './ReactWorkTags';
60
+import {
61
+ restoreEnterOrExitViewTransitions,
62
+ restoreNestedViewTransitions,
63
+} from './ReactFiberCommitViewTransitions';
64
65
let didWarnForRootClone = false;
66
68
return true;
69
}
70
60
-let unhideHostChildren = false;
71
+const CLONE_UPDATE = 0; // Mutations in this subtree or potentially affected by layout.
72
+const CLONE_EXIT = 1; // Inside a reappearing offscreen before the next ViewTransition or HostComponent.
73
+const CLONE_UNHIDE = 2; // Inside a reappearing offscreen before the next HostComponent.
74
+const CLONE_APPEARING_PAIR = 3; // Like UNHIDE but we're already inside the first Host Component only finding pairs.
75
+const CLONE_UNCHANGED = 4; // Nothing in this tree was changed but we're still walking to clone it.
76
+const INSERT_EXIT = 5; // Inside a newly mounted tree before the next ViewTransition or HostComponent.
77
+const INSERT_APPEND = 6; // Inside a newly mounted tree before the next HostComponent.
78
+const INSERT_APPEARING_PAIR = 7; // Inside a newly mounted tree only finding pairs.
79
+type VisitPhase = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
80
+
81
+function trackDeletedPairViewTransitions(deletion: Fiber): void {
82
+ if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {
83
+ // This has no named view transitions in its subtree.
84
+ return;
85
+ }
86
+ let child = deletion.child;
87
+ while (child !== null) {
88
+ if (child.tag === OffscreenComponent && child.memoizedState === null) {
89
+ // This tree was already hidden so we skip it.
90
+ } else {
91
+ if (
92
+ child.tag === ViewTransitionComponent &&
93
+ (child.flags & ViewTransitionNamedStatic) !== NoFlags
94
+ ) {
95
+ const props: ViewTransitionProps = child.memoizedProps;
96
+ const name = props.name;
97
+ if (name != null && name !== 'auto') {
98
+ // TODO: Find a pair
99
+ }
100
+ }
101
+ trackDeletedPairViewTransitions(child);
102
+ }
103
+ child = child.sibling;
104
+ }
105
+}
106
+
107
+function trackEnterViewTransitions(deletion: Fiber): void {
108
+ if (deletion.tag === ViewTransitionComponent) {
109
+ const props: ViewTransitionProps = deletion.memoizedProps;
110
+ const name = props.name;
111
+ if (name != null && name !== 'auto') {
112
+ // TODO: Find a pair
113
+ }
114
+ // Look for more pairs deeper in the tree.
115
+ trackDeletedPairViewTransitions(deletion);
116
+ } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
117
+ let child = deletion.child;
118
+ while (child !== null) {
119
+ trackEnterViewTransitions(child);
120
+ child = child.sibling;
121
+ }
122
+ } else {
123
+ trackDeletedPairViewTransitions(deletion);
124
+ }
125
+}
126
+
127
+function recursivelyInsertNew(
128
+ parentFiber: Fiber,
129
+ hostParentClone: Instance,
130
+ parentViewTransition: null | ViewTransitionState,
131
+ visitPhase: VisitPhase,
132
+): void {
133
+ if (
134
+ visitPhase === INSERT_APPEARING_PAIR &&
135
+ parentViewTransition === null &&
136
+ (parentFiber.subtreeFlags & ViewTransitionNamedStatic) === NoFlags
137
+ ) {
138
+ // We're just searching for pairs but we have reached the end.
139
+ return;
140
+ }
141
+ let child = parentFiber.child;
142
+ while (child !== null) {
143
+ recursivelyInsertNewFiber(
144
+ child,
145
+ hostParentClone,
146
+ parentViewTransition,
147
+ visitPhase,
148
+ );
149
+ child = child.sibling;
150
+ }
151
+}
152
+
153
+function recursivelyInsertNewFiber(
154
+ finishedWork: Fiber,
155
+ hostParentClone: Instance,
156
+ parentViewTransition: null | ViewTransitionState,
157
+ visitPhase: VisitPhase,
158
+): void {
159
+ switch (finishedWork.tag) {
160
+ case HostHoistable: {
161
+ if (supportsResources) {
162
+ // TODO: Hoistables should get optimistically inserted and then removed.
163
+ recursivelyInsertNew(
164
+ finishedWork,
165
+ hostParentClone,
166
+ parentViewTransition,
167
+ visitPhase,
168
+ );
169
+ break;
170
+ }
171
+ // Fall through
172
+ }
173
+ case HostSingleton: {
174
+ if (supportsSingletons) {
175
+ recursivelyInsertNew(
176
+ finishedWork,
177
+ hostParentClone,
178
+ parentViewTransition,
179
+ visitPhase,
180
+ );
181
+
182
+ if (__DEV__) {
183
+ // We cannot apply mutations to Host Singletons since by definition
184
+ // they cannot be cloned. Therefore we warn in DEV if this commit
185
+ // had any effect.
186
+ if (finishedWork.flags & Update) {
187
+ console.error(
188
+ 'useSwipeTransition() caused something to render a new <%s>. ' +
189
+ 'This is not possible in the current implementation. ' +
190
+ "Make sure that the swipe doesn't mount any new <%s> elements.",
191
+ finishedWork.type,
192
+ finishedWork.type,
193
+ );
194
+ }
195
+ }
196
+ break;
197
+ }
198
+ // Fall through
199
+ }
200
+ case HostComponent: {
201
+ const instance: Instance = finishedWork.stateNode;
202
+ // For insertions we don't need to clone. It's already new state node.
203
+ if (visitPhase !== INSERT_APPEARING_PAIR) {
204
+ appendChild(hostParentClone, instance);
205
+ recursivelyInsertNew(
206
+ finishedWork,
207
+ instance,
208
+ null,
209
+ INSERT_APPEARING_PAIR,
210
+ );
211
+ } else {
212
+ recursivelyInsertNew(finishedWork, instance, null, visitPhase);
213
+ }
214
+ if (parentViewTransition !== null) {
215
+ if (parentViewTransition.clones === null) {
216
+ parentViewTransition.clones = [instance];
217
+ } else {
218
+ parentViewTransition.clones.push(instance);
219
+ }
220
+ }
221
+ break;
222
+ }
223
+ case HostText: {
224
+ const textInstance: TextInstance = finishedWork.stateNode;
225
+ if (textInstance === null) {
226
+ throw new Error(
227
+ 'This should have a text node initialized. This error is likely ' +
228
+ 'caused by a bug in React. Please file an issue.',
229
+ );
230
+ }
231
+ // For insertions we don't need to clone. It's already new state node.
232
+ if (visitPhase !== INSERT_APPEARING_PAIR) {
233
+ appendChild(hostParentClone, textInstance);
234
+ }
235
+ break;
236
+ }
237
+ case HostPortal: {
238
+ // TODO: Consider what should happen to Portals. For now we exclude them.
239
+ break;
240
+ }
241
+ case OffscreenComponent: {
242
+ const newState: OffscreenState | null = finishedWork.memoizedState;
243
+ const isHidden = newState !== null;
244
+ if (!isHidden) {
245
+ // Only insert nodes if this tree is going to be visible. No need to
246
+ // insert invisible content.
247
+ // Since there was no mutation to this node, it couldn't have changed
248
+ // visibility so we don't need to update visitPhase here.
249
+ recursivelyInsertNew(
250
+ finishedWork,
251
+ hostParentClone,
252
+ parentViewTransition,
253
+ visitPhase,
254
+ );
255
+ }
256
+ break;
257
+ }
258
+ case ViewTransitionComponent:
259
+ const prevMutationContext = pushMutationContext();
260
+ const viewTransitionState: ViewTransitionState = finishedWork.stateNode;
261
+ // TODO: If this was already cloned by a previous pass we can reuse those clones.
262
+ viewTransitionState.clones = null;
263
+ let nextPhase;
264
+ if (visitPhase === INSERT_EXIT) {
265
+ // This was an Enter of a ViewTransition. We now move onto inserting the inner
266
+ // HostComponents and finding inner pairs.
267
+ nextPhase = INSERT_APPEND;
268
+ // TODO: Mark the name and find a pair.
269
+ } else {
270
+ nextPhase = visitPhase;
271
+ }
272
+ recursivelyInsertNew(
273
+ finishedWork,
274
+ hostParentClone,
275
+ viewTransitionState,
276
+ nextPhase,
277
+ );
278
+ popMutationContext(prevMutationContext);
279
+ break;
280
+ default: {
281
+ recursivelyInsertNew(
282
+ finishedWork,
283
+ hostParentClone,
284
+ parentViewTransition,
285
+ visitPhase,
286
+ );
287
+ break;
288
+ }
289
+ }
290
+}
291
292
function recursivelyInsertClonesFromExistingTree(
293
parentFiber: Fiber,
294
hostParentClone: Instance,
295
+ parentViewTransition: null | ViewTransitionState,
296
+ visitPhase: VisitPhase,
297
): void {
298
let child = parentFiber.child;
299
while (child !== null) {
300
switch (child.tag) {
301
case HostComponent: {
302
const instance: Instance = child.stateNode;
71
- // If we have no mutations in this subtree, we just need to make a deep clone.
72
- const clone: Instance = cloneMutableInstance(instance, true);
303
+ let nextPhase: VisitPhase;
304
+ switch (visitPhase) {
305
+ case CLONE_EXIT:
306
+ case CLONE_UNHIDE:
307
+ case CLONE_APPEARING_PAIR:
308
+ // If this was an unhide, we need to keep going if there are any named
309
+ // pairs in this subtree, since they might need to be marked.
310
+ nextPhase =
311
+ (child.subtreeFlags & ViewTransitionNamedStatic) !== NoFlags
312
+ ? CLONE_APPEARING_PAIR
313
+ : CLONE_UNCHANGED;
314
+ break;
315
+ default:
316
+ // We've found any "layout" View Transitions at this point so we can bail.
317
+ nextPhase = CLONE_UNCHANGED;
318
+ }
319
+ let clone: Instance;
320
+ if (nextPhase !== CLONE_UNCHANGED) {
321
+ // We might need a handle on these clones, so we need to do a shallow clone
322
+ // and keep going.
323
+ clone = cloneMutableInstance(instance, false);
324
+ recursivelyInsertClonesFromExistingTree(
325
+ child,
326
+ clone,
327
+ null,
328
+ nextPhase,
329
+ );
330
+ } else {
331
+ // If we have no mutations in this subtree, and we don't need a handle on the
332
+ // clones, then we can do a deep clone instead and bailout.
333
+ clone = cloneMutableInstance(instance, true);
334
+ // TODO: We may need to transfer some DOM state such as scroll position
335
+ // for the deep clones.
336
+ // TODO: If there's a manual view-transition-name inside the clone we
337
+ // should ideally remove it from the original and then restore it in mutation
338
+ // phase. Otherwise it leads to duplicate names.
339
+ }
340
appendChild(hostParentClone, clone);
74
- // TODO: We may need to transfer some DOM state such as scroll position
75
- // for the deep clones.
76
- // TODO: If there's a manual view-transition-name inside the clone we
77
- // should ideally remove it from the original and then restore it in mutation
78
- // phase. Otherwise it leads to duplicate names.
79
- if (unhideHostChildren) {
341
+ if (parentViewTransition !== null) {
342
+ if (parentViewTransition.clones === null) {
343
+ parentViewTransition.clones = [clone];
344
+ } else {
345
+ parentViewTransition.clones.push(clone);
346
+ }
347
+ }
348
+ if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
349
unhideInstance(clone, child.memoizedProps);
350
}
351
break;
360
}
361
const clone = cloneMutableTextInstance(textInstance);
362
appendChild(hostParentClone, clone);
94
- if (unhideHostChildren) {
363
+ if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
364
unhideTextInstance(clone, child.memoizedProps);
365
}
366
break;
377
// clone invisible content.
378
// TODO: If this is visible but detached it should still be cloned.
379
// Since there was no mutation to this node, it couldn't have changed
111
- // visibility so we don't need to update unhideHostChildren here.
112
- recursivelyInsertClonesFromExistingTree(child, hostParentClone);
380
+ // visibility so we don't need to update visitPhase here.
381
+ recursivelyInsertClonesFromExistingTree(
382
+ child,
383
+ hostParentClone,
384
+ parentViewTransition,
385
+ visitPhase,
386
+ );
387
}
388
break;
389
}
390
case ViewTransitionComponent:
391
const prevMutationContext = pushMutationContext();
392
+ const viewTransitionState: ViewTransitionState = child.stateNode;
393
// TODO: If this was already cloned by a previous pass we can reuse those clones.
119
- recursivelyInsertClonesFromExistingTree(child, hostParentClone);
120
- // TODO: Do we need to track whether this should have a name applied?
394
+ viewTransitionState.clones = null;
395
+ let nextPhase;
396
+ if (visitPhase === CLONE_EXIT) {
397
+ // This was an Enter of a ViewTransition. We now move onto unhiding the inner
398
+ // HostComponents and finding inner pairs.
399
+ nextPhase = CLONE_UNHIDE;
400
+ // TODO: Mark the name and find a pair.
401
+ } else if (visitPhase === CLONE_UPDATE) {
402
+ // If the tree had no mutations and we've found the top most ViewTransition
403
+ // then this is the one we might apply the "layout" state too if it has changed
404
+ // position. After we've found its HostComponents we can bail out.
405
+ nextPhase = CLONE_UNCHANGED;
406
+ } else {
407
+ nextPhase = visitPhase;
408
+ }
409
+ recursivelyInsertClonesFromExistingTree(
410
+ child,
411
+ hostParentClone,
412
+ viewTransitionState,
413
+ nextPhase,
414
+ );
415
+ // TODO: Only the first level should track if this was s
416
// child.flags |= Update;
417
popMutationContext(prevMutationContext);
418
break;
419
default: {
125
- recursivelyInsertClonesFromExistingTree(child, hostParentClone);
420
+ recursivelyInsertClonesFromExistingTree(
421
+ child,
422
+ hostParentClone,
423
+ parentViewTransition,
424
+ visitPhase,
425
+ );
426
break;
427
}
428
}
433
function recursivelyInsertClones(
434
parentFiber: Fiber,
435
hostParentClone: Instance,
436
+ parentViewTransition: null | ViewTransitionState,
437
+ visitPhase: VisitPhase,
438
) {
439
const deletions = parentFiber.deletions;
440
if (deletions !== null) {
441
for (let i = 0; i < deletions.length; i++) {
140
- // const childToDelete = deletions[i];
141
- // TODO
442
+ const childToDelete = deletions[i];
443
+ trackEnterViewTransitions(childToDelete);
444
}
445
}
446
451
// If we have mutations or if this is a newly inserted tree, clone as we go.
452
let child = parentFiber.child;
453
while (child !== null) {
152
- insertDestinationClonesOfFiber(child, hostParentClone);
454
+ insertDestinationClonesOfFiber(
455
+ child,
456
+ hostParentClone,
457
+ parentViewTransition,
458
+ visitPhase,
459
+ );
460
child = child.sibling;
461
}
462
} else {
463
// Once we reach a subtree with no more mutations we can bail out.
464
// However, we must still insert deep clones of the HostComponents.
158
- recursivelyInsertClonesFromExistingTree(parentFiber, hostParentClone);
465
+ recursivelyInsertClonesFromExistingTree(
466
+ parentFiber,
467
+ hostParentClone,
468
+ parentViewTransition,
469
+ visitPhase,
470
+ );
471
}
472
}
473
474
function insertDestinationClonesOfFiber(
475
finishedWork: Fiber,
476
hostParentClone: Instance,
477
+ parentViewTransition: null | ViewTransitionState,
478
+ visitPhase: VisitPhase,
479
) {
480
const current = finishedWork.alternate;
481
+ if (current === null) {
482
+ // This is a newly mounted subtree. Insert any HostComponents and trigger
483
+ // Enter transitions.
484
+ recursivelyInsertNewFiber(
485
+ finishedWork,
486
+ hostParentClone,
487
+ parentViewTransition,
488
+ INSERT_EXIT,
489
+ );
490
+ return;
491
+ }
492
+
493
const flags = finishedWork.flags;
494
// The effect flag should be checked *after* we refine the type of fiber,
495
// because the fiber tag is more specific. An exception is any flag related
498
case HostHoistable: {
499
if (supportsResources) {
500
// TODO: Hoistables should get optimistically inserted and then removed.
175
- recursivelyInsertClones(finishedWork, hostParentClone);
501
+ recursivelyInsertClones(
502
+ finishedWork,
503
+ hostParentClone,
504
+ parentViewTransition,
505
+ visitPhase,
506
+ );
507
break;
508
}
509
// Fall through
510
}
511
case HostSingleton: {
512
if (supportsSingletons) {
182
- recursivelyInsertClones(finishedWork, hostParentClone);
513
+ recursivelyInsertClones(
514
+ finishedWork,
515
+ hostParentClone,
516
+ parentViewTransition,
517
+ visitPhase,
518
+ );
519
if (__DEV__) {
520
// We cannot apply mutations to Host Singletons since by definition
521
// they cannot be cloned. Therefore we warn in DEV if this commit
522
// had any effect.
523
if (flags & Update) {
188
- if (current === null) {
189
- console.error(
190
- 'useSwipeTransition() caused something to render a new <%s>. ' +
191
- 'This is not possible in the current implementation. ' +
192
- "Make sure that the swipe doesn't mount any new <%s> elements.",
193
- finishedWork.type,
194
- finishedWork.type,
195
- );
196
- } else {
197
- const newProps = finishedWork.memoizedProps;
198
- const oldProps = current.memoizedProps;
199
- const instance = finishedWork.stateNode;
200
- const type = finishedWork.type;
201
- const prev = pushMutationContext();
202
-
203
- try {
204
- // Since we currently don't have a separate diffing algorithm for
205
- // individual properties, the Update flag can be a false positive.
206
- // We have to apply the new props first o detect any mutations and
207
- // then revert them.
208
- commitUpdate(instance, type, oldProps, newProps, finishedWork);
209
- if (viewTransitionMutationContext) {
210
- console.error(
211
- 'useSwipeTransition() caused something to mutate <%s>. ' +
212
- 'This is not possible in the current implementation. ' +
213
- "Make sure that the swipe doesn't update any state which " +
214
- 'causes <%s> to change.',
215
- finishedWork.type,
216
- finishedWork.type,
217
- );
218
- }
219
- // Revert
220
- commitUpdate(instance, type, newProps, oldProps, finishedWork);
221
- } finally {
222
- popMutationContext(prev);
524
+ const newProps = finishedWork.memoizedProps;
525
+ const oldProps = current.memoizedProps;
526
+ const instance = finishedWork.stateNode;
527
+ const type = finishedWork.type;
528
+ const prev = pushMutationContext();
529
+
530
+ try {
531
+ // Since we currently don't have a separate diffing algorithm for
532
+ // individual properties, the Update flag can be a false positive.
533
+ // We have to apply the new props first o detect any mutations and
534
+ // then revert them.
535
+ commitUpdate(instance, type, oldProps, newProps, finishedWork);
536
+ if (viewTransitionMutationContext) {
537
+ console.error(
538
+ 'useSwipeTransition() caused something to mutate <%s>. ' +
539
+ 'This is not possible in the current implementation. ' +
540
+ "Make sure that the swipe doesn't update any state which " +
541
+ 'causes <%s> to change.',
542
+ finishedWork.type,
543
+ finishedWork.type,
544
+ );
545
}
546
+ // Revert
547
+ commitUpdate(instance, type, newProps, oldProps, finishedWork);
548
+ } finally {
549
+ popMutationContext(prev);
550
}
551
}
552
}
556
}
557
case HostComponent: {
558
const instance: Instance = finishedWork.stateNode;
233
- if (current === null) {
234
- // For insertions we don't need to clone. It's already new state node.
235
- // TODO: Do we need to visit it for ViewTransitions though?
236
- appendChild(hostParentClone, instance);
237
- } else {
238
- let clone: Instance;
239
- if (finishedWork.child === null) {
240
- // This node is terminal. We still do a deep clone in case this has user
241
- // inserted content, text content or dangerouslySetInnerHTML.
242
- clone = cloneMutableInstance(instance, true);
243
- if (finishedWork.flags & ContentReset) {
244
- resetTextContent(clone);
245
- }
246
- } else {
247
- // If we have children we'll clone them as we walk the tree so we just
248
- // do a shallow clone here.
249
- clone = cloneMutableInstance(instance, false);
559
+ let clone: Instance;
560
+ if (finishedWork.child === null) {
561
+ // This node is terminal. We still do a deep clone in case this has user
562
+ // inserted content, text content or dangerouslySetInnerHTML.
563
+ clone = cloneMutableInstance(instance, true);
564
+ if (finishedWork.flags & ContentReset) {
565
+ resetTextContent(clone);
566
}
567
+ } else {
568
+ // If we have children we'll clone them as we walk the tree so we just
569
+ // do a shallow clone here.
570
+ clone = cloneMutableInstance(instance, false);
571
+ }
572
252
- if (flags & Update) {
253
- const newProps = finishedWork.memoizedProps;
254
- const oldProps = current.memoizedProps;
255
- const type = finishedWork.type;
256
- // Apply the delta to the clone.
257
- commitUpdate(clone, type, oldProps, newProps, finishedWork);
258
- }
573
+ if (flags & Update) {
574
+ const newProps = finishedWork.memoizedProps;
575
+ const oldProps = current.memoizedProps;
576
+ const type = finishedWork.type;
577
+ // Apply the delta to the clone.
578
+ commitUpdate(clone, type, oldProps, newProps, finishedWork);
579
+ }
580
260
- if (unhideHostChildren) {
261
- unhideHostChildren = false;
262
- recursivelyInsertClones(finishedWork, clone);
263
- appendChild(hostParentClone, clone);
264
- unhideHostChildren = true;
265
- unhideInstance(clone, finishedWork.memoizedProps);
581
+ if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
582
+ recursivelyInsertClones(
583
+ finishedWork,
584
+ clone,
585
+ null,
586
+ CLONE_APPEARING_PAIR,
587
+ );
588
+ appendChild(hostParentClone, clone);
589
+ unhideInstance(clone, finishedWork.memoizedProps);
590
+ } else {
591
+ recursivelyInsertClones(finishedWork, clone, null, visitPhase);
592
+ appendChild(hostParentClone, clone);
593
+ }
594
+ if (parentViewTransition !== null) {
595
+ if (parentViewTransition.clones === null) {
596
+ parentViewTransition.clones = [clone];
597
} else {
267
- recursivelyInsertClones(finishedWork, clone);
268
- appendChild(hostParentClone, clone);
598
+ parentViewTransition.clones.push(clone);
599
}
600
}
601
break;
608
'caused by a bug in React. Please file an issue.',
609
);
610
}
281
- if (current === null) {
282
- // For insertions we don't need to clone. It's already new state node.
283
- appendChild(hostParentClone, textInstance);
284
- } else {
285
- const clone = cloneMutableTextInstance(textInstance);
286
- if (flags & Update) {
287
- const newText: string = finishedWork.memoizedProps;
288
- const oldText: string = current.memoizedProps;
289
- commitTextUpdate(clone, newText, oldText);
290
- }
291
- appendChild(hostParentClone, clone);
292
- if (unhideHostChildren) {
293
- unhideTextInstance(clone, finishedWork.memoizedProps);
294
- }
611
+ const clone = cloneMutableTextInstance(textInstance);
612
+ if (flags & Update) {
613
+ const newText: string = finishedWork.memoizedProps;
614
+ const oldText: string = current.memoizedProps;
615
+ commitTextUpdate(clone, newText, oldText);
616
+ }
617
+ appendChild(hostParentClone, clone);
618
+ if (visitPhase === CLONE_EXIT || visitPhase === CLONE_UNHIDE) {
619
+ unhideTextInstance(clone, finishedWork.memoizedProps);
620
}
621
break;
622
}
631
// Only insert clones if this tree is going to be visible. No need to
632
// clone invisible content.
633
// TODO: If this is visible but detached it should still be cloned.
309
- const prevUnhide = unhideHostChildren;
310
- unhideHostChildren = prevUnhide || (flags & Visibility) !== NoFlags;
311
- recursivelyInsertClones(finishedWork, hostParentClone);
312
- unhideHostChildren = prevUnhide;
634
+ let nextPhase;
635
+ if (visitPhase === CLONE_UPDATE && (flags & Visibility) !== NoFlags) {
636
+ // This is the root of an appear. We need to trigger Enter transitions.
637
+ nextPhase = CLONE_EXIT;
638
+ } else {
639
+ nextPhase = visitPhase;
640
+ }
641
+ recursivelyInsertClones(
642
+ finishedWork,
643
+ hostParentClone,
644
+ parentViewTransition,
645
+ nextPhase,
646
+ );
647
+ } else if (current !== null && current.memoizedState === null) {
648
+ // Was previously mounted as visible but is now hidden.
649
+ trackEnterViewTransitions(current);
650
}
651
break;
652
}
653
case ViewTransitionComponent:
654
const prevMutationContext = pushMutationContext();
655
+ const viewTransitionState: ViewTransitionState = finishedWork.stateNode;
656
// TODO: If this was already cloned by a previous pass we can reuse those clones.
319
- recursivelyInsertClones(finishedWork, hostParentClone);
657
+ viewTransitionState.clones = null;
658
+ let nextPhase;
659
+ if (visitPhase === CLONE_EXIT) {
660
+ // This was an Enter of a ViewTransition. We now move onto unhiding the inner
661
+ // HostComponents and finding inner pairs.
662
+ nextPhase = CLONE_UNHIDE;
663
+ // TODO: Mark the name and find a pair.
664
+ } else {
665
+ nextPhase = visitPhase;
666
+ }
667
+ recursivelyInsertClones(
668
+ finishedWork,
669
+ hostParentClone,
670
+ viewTransitionState,
671
+ nextPhase,
672
+ );
673
if (viewTransitionMutationContext) {
674
// Track that this boundary had a mutation and therefore needs to animate
675
// whether it resized or not.
678
popMutationContext(prevMutationContext);
679
break;
680
default: {
328
- recursivelyInsertClones(finishedWork, hostParentClone);
681
+ recursivelyInsertClones(
682
+ finishedWork,
683
+ hostParentClone,
684
+ parentViewTransition,
685
+ visitPhase,
686
+ );
687
break;
688
}
689
}
695
root: FiberRoot,
696
finishedWork: Fiber,
697
): void {
340
- unhideHostChildren = false;
698
// We'll either not transition the root, or we'll transition the clone. Regardless
699
// we cancel the root view transition name.
700
const needsClone = detectMutationOrInsertClones(finishedWork);
713
// Clone the whole root
714
const rootClone = cloneRootViewTransitionContainer(root.containerInfo);
715
root.gestureClone = rootClone;
359
- recursivelyInsertClones(finishedWork, rootClone);
716
+ recursivelyInsertClones(finishedWork, rootClone, null, CLONE_UPDATE);
717
} else {
718
root.gestureClone = null;
719
cancelRootViewTransitionName(root.containerInfo);
720
}
721
}
722
723
+function applyDeletedPairViewTransitions(deletion: Fiber): void {
724
+ if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {
725
+ // This has no named view transitions in its subtree.
726
+ return;
727
+ }
728
+ let child = deletion.child;
729
+ while (child !== null) {
730
+ if (child.tag === OffscreenComponent && child.memoizedState === null) {
731
+ // This tree was already hidden so we skip it.
732
+ } else {
733
+ if (
734
+ child.tag === ViewTransitionComponent &&
735
+ (child.flags & ViewTransitionNamedStatic) !== NoFlags
736
+ ) {
737
+ const props: ViewTransitionProps = child.memoizedProps;
738
+ const name = props.name;
739
+ if (name != null && name !== 'auto') {
740
+ // TODO: Find a pair
741
+ }
742
+ }
743
+ applyDeletedPairViewTransitions(child);
744
+ }
745
+ child = child.sibling;
746
+ }
747
+}
748
+
749
+function applyEnterViewTransitions(deletion: Fiber): void {
750
+ if (deletion.tag === ViewTransitionComponent) {
751
+ const props: ViewTransitionProps = deletion.memoizedProps;
752
+ const name = props.name;
753
+ if (name != null && name !== 'auto') {
754
+ // TODO: Find a pair
755
+ }
756
+ // Look for more pairs deeper in the tree.
757
+ applyDeletedPairViewTransitions(deletion);
758
+ } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
759
+ // TODO: Check if this is a hidden Offscreen or a Portal.
760
+ let child = deletion.child;
761
+ while (child !== null) {
762
+ applyEnterViewTransitions(child);
763
+ child = child.sibling;
764
+ }
765
+ } else {
766
+ applyDeletedPairViewTransitions(deletion);
767
+ }
768
+}
769
+
770
+function measureExitViewTransitions(placement: Fiber): void {
771
+ if (placement.tag === ViewTransitionComponent) {
772
+ // const state: ViewTransitionState = placement.stateNode;
773
+ const props: ViewTransitionProps = placement.memoizedProps;
774
+ const name = props.name;
775
+ if (name != null && name !== 'auto') {
776
+ // TODO: Find a pair
777
+ }
778
+ } else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
779
+ // TODO: Check if this is a hidden Offscreen or a Portal.
780
+ let child = placement.child;
781
+ while (child !== null) {
782
+ measureExitViewTransitions(child);
783
+ child = child.sibling;
784
+ }
785
+ } else {
786
+ // We don't need to find pairs here because we would've already found and
787
+ // measured the pairs inside the deletion phase.
788
+ }
789
+}
790
+
791
+function measureNestedViewTransitions(changedParent: Fiber): void {
792
+ let child = changedParent.child;
793
+ while (child !== null) {
794
+ if (child.tag === ViewTransitionComponent) {
795
+ const current = child.alternate;
796
+ if (current !== null) {
797
+ // const props: ViewTransitionProps = child.memoizedProps;
798
+ // const name = getViewTransitionName(props, child.stateNode);
799
+ // TODO: Measure both the old and new state and see if they're different.
800
+ }
801
+ } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
802
+ // TODO: Check if this is a hidden Offscreen or a Portal.
803
+ measureNestedViewTransitions(child);
804
+ }
805
+ child = child.sibling;
806
+ }
807
+}
808
+
809
+function measureUpdateViewTransition(
810
+ current: Fiber,
811
+ finishedWork: Fiber,
812
+): void {
813
+ // TODO
814
+}
815
+
816
+function recursivelyApplyViewTransitions(parentFiber: Fiber) {
817
+ const deletions = parentFiber.deletions;
818
+ if (deletions !== null) {
819
+ for (let i = 0; i < deletions.length; i++) {
820
+ const childToDelete = deletions[i];
821
+ applyEnterViewTransitions(childToDelete);
822
+ }
823
+ }
824
+
825
+ if (
826
+ parentFiber.alternate === null ||
827
+ (parentFiber.subtreeFlags & MutationMask) !== NoFlags
828
+ ) {
829
+ // If we have mutations or if this is a newly inserted tree, clone as we go.
830
+ let child = parentFiber.child;
831
+ while (child !== null) {
832
+ applyViewTransitionsOnFiber(child);
833
+ child = child.sibling;
834
+ }
835
+ } else {
836
+ // Nothing has changed in this subtree, but the parent may have still affected
837
+ // its size and position. We need to measure the old and new state to see if
838
+ // we should animate its size and position.
839
+ measureNestedViewTransitions(parentFiber);
840
+ }
841
+}
842
+
843
+function applyViewTransitionsOnFiber(finishedWork: Fiber) {
844
+ const current = finishedWork.alternate;
845
+ if (current === null) {
846
+ measureExitViewTransitions(finishedWork);
847
+ return;
848
+ }
849
+
850
+ const flags = finishedWork.flags;
851
+ // The effect flag should be checked *after* we refine the type of fiber,
852
+ // because the fiber tag is more specific. An exception is any flag related
853
+ // to reconciliation, because those can be set on all fiber types.
854
+ switch (finishedWork.tag) {
855
+ case HostComponent: {
856
+ // const instance: Instance = finishedWork.stateNode;
857
+ // TODO: Apply name and measure.
858
+ recursivelyApplyViewTransitions(finishedWork);
859
+ break;
860
+ }
861
+ case HostText: {
862
+ break;
863
+ }
864
+ case HostPortal: {
865
+ // TODO: Consider what should happen to Portals. For now we exclude them.
866
+ break;
867
+ }
868
+ case OffscreenComponent: {
869
+ if (flags & Visibility) {
870
+ const newState: OffscreenState | null = finishedWork.memoizedState;
871
+ const isHidden = newState !== null;
872
+ if (!isHidden) {
873
+ measureExitViewTransitions(finishedWork);
874
+ } else if (current !== null && current.memoizedState === null) {
875
+ // Was previously mounted as visible but is now hidden.
876
+ applyEnterViewTransitions(current);
877
+ }
878
+ }
879
+ break;
880
+ }
881
+ case ViewTransitionComponent:
882
+ measureUpdateViewTransition(current, finishedWork);
883
+ const viewTransitionState: ViewTransitionState = finishedWork.stateNode;
884
+ viewTransitionState.clones = null; // Reset
885
+ recursivelyApplyViewTransitions(finishedWork);
886
+ break;
887
+ default: {
888
+ recursivelyApplyViewTransitions(finishedWork);
889
+ break;
890
+ }
891
+ }
892
+}
893
+
894
// Revert insertions and apply view transition names to the "new" (current) state.
895
export function applyDepartureTransitions(
896
root: FiberRoot,
897
finishedWork: Fiber,
898
): void {
899
+ // First measure and apply view-transition-names to the "new" states.
900
+ recursivelyApplyViewTransitions(finishedWork);
901
+ // Then remove the clones.
902
const rootClone = root.gestureClone;
903
if (rootClone !== null) {
904
root.gestureClone = null;
905
removeRootViewTransitionClone(root.containerInfo, rootClone);
906
}
376
- // TODO
907
+}
908
+
909
+function recursivelyRestoreViewTransitions(parentFiber: Fiber) {
910
+ const deletions = parentFiber.deletions;
911
+ if (deletions !== null) {
912
+ for (let i = 0; i < deletions.length; i++) {
913
+ const childToDelete = deletions[i];
914
+ restoreEnterOrExitViewTransitions(childToDelete);
915
+ }
916
+ }
917
+
918
+ if (
919
+ parentFiber.alternate === null ||
920
+ (parentFiber.subtreeFlags & MutationMask) !== NoFlags
921
+ ) {
922
+ // If we have mutations or if this is a newly inserted tree, clone as we go.
923
+ let child = parentFiber.child;
924
+ while (child !== null) {
925
+ restoreViewTransitionsOnFiber(child);
926
+ child = child.sibling;
927
+ }
928
+ } else {
929
+ // Nothing has changed in this subtree, but the parent may have still affected
930
+ // its size and position. We need to measure the old and new state to see if
931
+ // we should animate its size and position.
932
+ restoreNestedViewTransitions(parentFiber);
933
+ }
934
+}
935
+
936
+function restoreViewTransitionsOnFiber(finishedWork: Fiber) {
937
+ const current = finishedWork.alternate;
938
+ if (current === null) {
939
+ restoreEnterOrExitViewTransitions(finishedWork);
940
+ return;
941
+ }
942
+
943
+ const flags = finishedWork.flags;
944
+ // The effect flag should be checked *after* we refine the type of fiber,
945
+ // because the fiber tag is more specific. An exception is any flag related
946
+ // to reconciliation, because those can be set on all fiber types.
947
+ switch (finishedWork.tag) {
948
+ case HostComponent: {
949
+ // const instance: Instance = finishedWork.stateNode;
950
+ // TODO: Restore the name.
951
+ recursivelyRestoreViewTransitions(finishedWork);
952
+ break;
953
+ }
954
+ case HostText: {
955
+ break;
956
+ }
957
+ case HostPortal: {
958
+ // TODO: Consider what should happen to Portals. For now we exclude them.
959
+ break;
960
+ }
961
+ case OffscreenComponent: {
962
+ if (flags & Visibility) {
963
+ const newState: OffscreenState | null = finishedWork.memoizedState;
964
+ const isHidden = newState !== null;
965
+ if (!isHidden) {
966
+ restoreEnterOrExitViewTransitions(finishedWork);
967
+ } else if (current !== null && current.memoizedState === null) {
968
+ // Was previously mounted as visible but is now hidden.
969
+ restoreEnterOrExitViewTransitions(current);
970
+ }
971
+ }
972
+ break;
973
+ }
974
+ case ViewTransitionComponent:
975
+ const viewTransitionState: ViewTransitionState = finishedWork.stateNode;
976
+ viewTransitionState.clones = null; // Reset
977
+ recursivelyRestoreViewTransitions(finishedWork);
978
+ break;
979
+ default: {
980
+ recursivelyRestoreViewTransitions(finishedWork);
981
+ break;
982
+ }
983
+ }
984
}
985
986
// Revert transition names and start/adjust animations on the started View Transition.
988
root: FiberRoot,
989
finishedWork: Fiber,
990
): void {
384
- // TODO
991
+ restoreViewTransitionsOnFiber(finishedWork);
992
restoreRootViewTransitionName(root.containerInfo);
993
}