Make xViewTransitionToHostInstances helpers reusable (#32611)

This prepares from being able to reuse some this in ApplyGesture.

These all start with resetting a counter but it's tricky to have to
remember to do this and tricky to do from the outside of this module. So
we make an exported helper that does the resetting. Ideally it gets
inlined.

We also stop passing "current" to measureViewTransitionHostInstances.
Same thing for cancelViewTransitionHostInstances. This doesn't make
sense for "nested" which has not updated and so might not have an
alternate. Instead we pass in the old and new name if they might be
different.
This commit is contained in:
Sebastian Markbåge 2025-03-14 13:16:42 -04:00 committed by GitHub
parent 3e956805e8
commit 6daef4e7c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 110 additions and 67 deletions

View File

@ -71,15 +71,40 @@ export let viewTransitionCancelableChildren: null | Array<
Instance | string | Props,
> = null; // tupled array where each entry is [instance: Instance, oldName: string, props: Props]
export function setViewTransitionCancelableChildren(
children: null | Array<Instance | string | Props>,
export function pushViewTransitionCancelableScope(): null | Array<
Instance | string | Props,
> {
const prevChildren = viewTransitionCancelableChildren;
viewTransitionCancelableChildren = null;
return prevChildren;
}
export function popViewTransitionCancelableScope(
prevChildren: null | Array<Instance | string | Props>,
): void {
viewTransitionCancelableChildren = children;
viewTransitionCancelableChildren = prevChildren;
}
let viewTransitionHostInstanceIdx = 0;
function applyViewTransitionToHostInstances(
export function applyViewTransitionToHostInstances(
child: null | Fiber,
name: string,
className: ?string,
collectMeasurements: null | Array<InstanceMeasurement>,
stopAtNestedViewTransitions: boolean,
): boolean {
viewTransitionHostInstanceIdx = 0;
return applyViewTransitionToHostInstancesRecursive(
child,
name,
className,
collectMeasurements,
stopAtNestedViewTransitions,
);
}
function applyViewTransitionToHostInstancesRecursive(
child: null | Fiber,
name: string,
className: ?string,
@ -128,7 +153,7 @@ function applyViewTransitionToHostInstances(
// inner most one is the one that handles the update.
} else {
if (
applyViewTransitionToHostInstances(
applyViewTransitionToHostInstancesRecursive(
child.child,
name,
className,
@ -207,7 +232,6 @@ function commitAppearingPairViewTransitions(placement: Fiber): void {
if (className !== 'none') {
// We found a new appearing view transition with the same name as this deletion.
// We'll transition between them.
viewTransitionHostInstanceIdx = 0;
const inViewport = applyViewTransitionToHostInstances(
child.child,
name,
@ -242,7 +266,6 @@ export function commitEnterViewTransitions(placement: Fiber): void {
state.paired ? props.share : props.enter,
);
if (className !== 'none') {
viewTransitionHostInstanceIdx = 0;
const inViewport = applyViewTransitionToHostInstances(
placement.child,
name,
@ -310,7 +333,6 @@ function commitDeletedPairViewTransitions(deletion: Fiber): void {
);
if (className !== 'none') {
// We found a new appearing view transition with the same name as this deletion.
viewTransitionHostInstanceIdx = 0;
const inViewport = applyViewTransitionToHostInstances(
child.child,
name,
@ -361,7 +383,6 @@ export function commitExitViewTransitions(deletion: Fiber): void {
pair !== undefined ? props.share : props.exit,
);
if (className !== 'none') {
viewTransitionHostInstanceIdx = 0;
const inViewport = applyViewTransitionToHostInstances(
deletion.child,
name,
@ -449,7 +470,6 @@ export function commitBeforeUpdateViewTransition(
return;
}
}
viewTransitionHostInstanceIdx = 0;
applyViewTransitionToHostInstances(
current.child,
oldName,
@ -472,7 +492,6 @@ export function commitNestedViewTransitions(changedParent: Fiber): void {
props.layout,
);
if (className !== 'none') {
viewTransitionHostInstanceIdx = 0;
applyViewTransitionToHostInstances(
child.child,
name,
@ -553,9 +572,22 @@ export function restoreNestedViewTransitions(changedParent: Fiber): void {
}
}
function cancelViewTransitionHostInstances(
currentViewTransition: Fiber,
export function cancelViewTransitionHostInstances(
child: null | Fiber,
oldName: string,
stopAtNestedViewTransitions: boolean,
): void {
viewTransitionHostInstanceIdx = 0;
cancelViewTransitionHostInstancesRecursive(
child,
oldName,
stopAtNestedViewTransitions,
);
}
function cancelViewTransitionHostInstancesRecursive(
child: null | Fiber,
oldName: string,
stopAtNestedViewTransitions: boolean,
): void {
if (!supportsMutation) {
@ -564,10 +596,6 @@ function cancelViewTransitionHostInstances(
while (child !== null) {
if (child.tag === HostComponent) {
const instance: Instance = child.stateNode;
const oldName = getViewTransitionName(
currentViewTransition.memoizedProps,
currentViewTransition.stateNode,
);
if (viewTransitionCancelableChildren === null) {
viewTransitionCancelableChildren = [];
}
@ -589,9 +617,9 @@ function cancelViewTransitionHostInstances(
// Skip any nested view transitions for updates since in that case the
// inner most one is the one that handles the update.
} else {
cancelViewTransitionHostInstances(
currentViewTransition,
cancelViewTransitionHostInstancesRecursive(
child.child,
oldName,
stopAtNestedViewTransitions,
);
}
@ -599,11 +627,32 @@ function cancelViewTransitionHostInstances(
}
}
function measureViewTransitionHostInstances(
currentViewTransition: Fiber,
export function measureViewTransitionHostInstances(
parentViewTransition: Fiber,
child: null | Fiber,
name: string,
newName: string,
oldName: string,
className: ?string,
previousMeasurements: null | Array<InstanceMeasurement>,
stopAtNestedViewTransitions: boolean,
): boolean {
viewTransitionHostInstanceIdx = 0;
return measureViewTransitionHostInstancesRecursive(
parentViewTransition,
child,
newName,
oldName,
className,
previousMeasurements,
stopAtNestedViewTransitions,
);
}
function measureViewTransitionHostInstancesRecursive(
parentViewTransition: Fiber,
child: null | Fiber,
newName: string,
oldName: string,
className: ?string,
previousMeasurements: null | Array<InstanceMeasurement>,
stopAtNestedViewTransitions: boolean,
@ -654,10 +703,10 @@ function measureViewTransitionHostInstances(
applyViewTransitionName(
instance,
viewTransitionHostInstanceIdx === 0
? name
? newName
: // If we have multiple Host Instances below, we add a suffix to the name to give
// each one a unique name.
name + '_' + viewTransitionHostInstanceIdx,
newName + '_' + viewTransitionHostInstanceIdx,
className,
);
}
@ -667,10 +716,6 @@ function measureViewTransitionHostInstances(
// animating it. However, in the current model this only works if the parent also
// doesn't animate. So we have to queue these and wait until we complete the parent
// to cancel them.
const oldName = getViewTransitionName(
currentViewTransition.memoizedProps,
currentViewTransition.stateNode,
);
if (viewTransitionCancelableChildren === null) {
viewTransitionCancelableChildren = [];
}
@ -696,11 +741,11 @@ function measureViewTransitionHostInstances(
parentViewTransition.flags |= child.flags & AffectedParentLayout;
} else {
if (
measureViewTransitionHostInstances(
currentViewTransition,
measureViewTransitionHostInstancesRecursive(
parentViewTransition,
child.child,
name,
newName,
oldName,
className,
previousMeasurements,
stopAtNestedViewTransitions,
@ -719,6 +764,11 @@ export function measureUpdateViewTransition(
finishedWork: Fiber,
): boolean {
const props: ViewTransitionProps = finishedWork.memoizedProps;
const newName = getViewTransitionName(props, finishedWork.stateNode);
const oldName = getViewTransitionName(
current.memoizedProps,
current.stateNode,
);
const updateClassName: ?string = getViewTransitionClassName(
props.className,
props.update,
@ -745,24 +795,21 @@ export function measureUpdateViewTransition(
if (layoutClassName === 'none') {
// If we did not update, then all changes are considered a layout. We'll
// attempt to cancel.
viewTransitionHostInstanceIdx = 0;
cancelViewTransitionHostInstances(current, finishedWork.child, true);
cancelViewTransitionHostInstances(finishedWork.child, oldName, true);
return false;
}
// We didn't update but we might still apply layout so we measure each
// instance to see if it moved or resized.
className = layoutClassName;
}
const name = getViewTransitionName(props, finishedWork.stateNode);
// If nothing changed due to a mutation, or children changing size
// and the measurements end up unchanged, we should restore it to not animate.
viewTransitionHostInstanceIdx = 0;
const previousMeasurements = current.memoizedState;
const inViewport = measureViewTransitionHostInstances(
current,
finishedWork,
finishedWork.child,
name,
newName,
oldName,
className,
previousMeasurements,
true,
@ -782,20 +829,17 @@ export function measureNestedViewTransitions(changedParent: Fiber): void {
let child = changedParent.child;
while (child !== null) {
if (child.tag === ViewTransitionComponent) {
const current = child.alternate;
if (current !== null) {
const props: ViewTransitionProps = child.memoizedProps;
const name = getViewTransitionName(props, child.stateNode);
const className: ?string = getViewTransitionClassName(
props.className,
props.layout,
);
viewTransitionHostInstanceIdx = 0;
const inViewport = measureViewTransitionHostInstances(
current,
child,
child.child,
name,
name, // Since this is unchanged, new and old name is the same.
className,
child.memoizedState,
false,
@ -805,7 +849,6 @@ export function measureNestedViewTransitions(changedParent: Fiber): void {
} else {
scheduleViewTransitionEvent(child, props.onLayout);
}
}
} else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
measureNestedViewTransitions(child);
}

View File

@ -254,7 +254,8 @@ import {
resetAppearingViewTransitions,
trackAppearingViewTransition,
viewTransitionCancelableChildren,
setViewTransitionCancelableChildren,
pushViewTransitionCancelableScope,
popViewTransitionCancelableScope,
} from './ReactFiberCommitViewTransitions';
import {
viewTransitionMutationContext,
@ -2474,14 +2475,14 @@ function commitAfterMutationEffectsOnFiber(
switch (finishedWork.tag) {
case HostRoot: {
viewTransitionContextChanged = false;
setViewTransitionCancelableChildren(null);
pushViewTransitionCancelableScope();
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
if (!viewTransitionContextChanged) {
// If we didn't leak any resizing out to the root, we don't have to transition
// the root itself. This means that we can now safely cancel any cancellations
// that bubbled all the way up.
const cancelableChildren = viewTransitionCancelableChildren;
setViewTransitionCancelableChildren(null);
popViewTransitionCancelableScope(null);
if (cancelableChildren !== null) {
for (let i = 0; i < cancelableChildren.length; i += 3) {
cancelViewTransitionName(
@ -2532,9 +2533,8 @@ function commitAfterMutationEffectsOnFiber(
const wasMutated = (finishedWork.flags & Update) !== NoFlags;
const prevContextChanged = viewTransitionContextChanged;
const prevCancelableChildren = viewTransitionCancelableChildren;
const prevCancelableChildren = pushViewTransitionCancelableScope();
viewTransitionContextChanged = false;
setViewTransitionCancelableChildren(null);
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
if (viewTransitionContextChanged) {
@ -2557,7 +2557,7 @@ function commitAfterMutationEffectsOnFiber(
prevCancelableChildren,
viewTransitionCancelableChildren,
);
setViewTransitionCancelableChildren(prevCancelableChildren);
popViewTransitionCancelableScope(prevCancelableChildren);
}
// TODO: If this doesn't end up canceled, because a parent animates,
// then we should probably issue an event since this instance is part of it.
@ -2571,7 +2571,7 @@ function commitAfterMutationEffectsOnFiber(
);
// If this boundary did update, we cannot cancel its children so those are dropped.
setViewTransitionCancelableChildren(prevCancelableChildren);
popViewTransitionCancelableScope(prevCancelableChildren);
}
if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {