mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge 6e1e8fba1e into sapling-pr-archive-mofeiZ
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
import React, {
|
||||
unstable_ViewTransition as ViewTransition,
|
||||
unstable_Activity as Activity,
|
||||
useRef,
|
||||
useLayoutEffect,
|
||||
} from 'react';
|
||||
|
||||
import './Page.css';
|
||||
@@ -37,21 +35,17 @@ function Component() {
|
||||
}
|
||||
|
||||
export default function Page({url, navigate}) {
|
||||
const ref = useRef();
|
||||
const show = url === '/?b';
|
||||
useLayoutEffect(() => {
|
||||
const viewTransition = ref.current;
|
||||
requestAnimationFrame(() => {
|
||||
const keyframes = [
|
||||
{rotate: '0deg', transformOrigin: '30px 8px'},
|
||||
{rotate: '360deg', transformOrigin: '30px 8px'},
|
||||
];
|
||||
viewTransition.old.animate(keyframes, 300);
|
||||
viewTransition.new.animate(keyframes, 300);
|
||||
});
|
||||
}, [show]);
|
||||
function onTransition(viewTransition) {
|
||||
const keyframes = [
|
||||
{rotate: '0deg', transformOrigin: '30px 8px'},
|
||||
{rotate: '360deg', transformOrigin: '30px 8px'},
|
||||
];
|
||||
viewTransition.old.animate(keyframes, 250);
|
||||
viewTransition.new.animate(keyframes, 250);
|
||||
}
|
||||
const exclamation = (
|
||||
<ViewTransition name="exclamation">
|
||||
<ViewTransition name="exclamation" onShare={onTransition}>
|
||||
<span>!</span>
|
||||
</ViewTransition>
|
||||
);
|
||||
@@ -63,7 +57,7 @@ export default function Page({url, navigate}) {
|
||||
}}>
|
||||
{show ? 'A' : 'B'}
|
||||
</button>
|
||||
<ViewTransition>
|
||||
<ViewTransition className="none">
|
||||
<div>
|
||||
{show ? (
|
||||
<div>
|
||||
@@ -76,7 +70,7 @@ export default function Page({url, navigate}) {
|
||||
{a}
|
||||
</div>
|
||||
)}
|
||||
<ViewTransition ref={ref}>
|
||||
<ViewTransition>
|
||||
{show ? <div>hello{exclamation}</div> : <section>Loading</section>}
|
||||
</ViewTransition>
|
||||
<p>scroll me</p>
|
||||
@@ -98,7 +92,7 @@ export default function Page({url, navigate}) {
|
||||
<div>!!</div>
|
||||
</ViewTransition>
|
||||
</Activity>
|
||||
{show ? <Component /> : <p> </p>}
|
||||
{show ? <Component /> : null}
|
||||
</div>
|
||||
</ViewTransition>
|
||||
</div>
|
||||
|
||||
+267
-87
@@ -186,6 +186,7 @@ import {
|
||||
addMarkerIncompleteCallbackToPendingTransition,
|
||||
addMarkerCompleteCallbackToPendingTransition,
|
||||
retryDehydratedSuspenseBoundary,
|
||||
scheduleViewTransitionEvent,
|
||||
} from './ReactFiberWorkLoop';
|
||||
import {
|
||||
HasEffect as HookHasEffect,
|
||||
@@ -202,7 +203,10 @@ import {
|
||||
OffscreenDetached,
|
||||
OffscreenPassiveEffectsConnected,
|
||||
} from './ReactFiberActivityComponent';
|
||||
import {getViewTransitionName} from './ReactFiberViewTransitionComponent';
|
||||
import {
|
||||
getViewTransitionName,
|
||||
getViewTransitionClassName,
|
||||
} from './ReactFiberViewTransitionComponent';
|
||||
import {
|
||||
TransitionRoot,
|
||||
TransitionTracingMarker,
|
||||
@@ -503,7 +507,7 @@ function commitBeforeMutationEffectsOnFiber(
|
||||
// We should just stash the parent ViewTransitionComponent and continue
|
||||
// walking the tree until we find HostComponent but to do that we need
|
||||
// to use a stack which requires refactoring this phase.
|
||||
commitBeforeUpdateViewTransition(current);
|
||||
commitBeforeUpdateViewTransition(current, finishedWork);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -649,6 +653,7 @@ function commitAppearingPairViewTransitions(placement: Fiber): void {
|
||||
if (child.tag === OffscreenComponent && child.memoizedState === null) {
|
||||
// This tree was already hidden so we skip it.
|
||||
} else {
|
||||
commitAppearingPairViewTransitions(child);
|
||||
if (
|
||||
child.tag === ViewTransitionComponent &&
|
||||
(child.flags & ViewTransitionNamedStatic) !== NoFlags
|
||||
@@ -661,28 +666,34 @@ function commitAppearingPairViewTransitions(placement: Fiber): void {
|
||||
'Found a pair with an auto name. This is a bug in React.',
|
||||
);
|
||||
}
|
||||
// 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,
|
||||
props.name,
|
||||
const name = props.name;
|
||||
const className: ?string = getViewTransitionClassName(
|
||||
props.className,
|
||||
null,
|
||||
false,
|
||||
props.share,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// This boundary is exiting within the viewport but is going to leave the viewport.
|
||||
// Instead, we treat this as an exit of the previous entry by reverting the new name.
|
||||
// Ideally we could undo the old transition but it's now too late. It's also on its
|
||||
// on snapshot. We have know was for it to paint onto the original group.
|
||||
// TODO: This will lead to things unexpectedly having exit animations that normally
|
||||
// wouldn't happen. Consider if we should just let this fly off the screen instead.
|
||||
restoreViewTransitionOnHostInstances(child.child, false);
|
||||
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,
|
||||
className,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// This boundary is exiting within the viewport but is going to leave the viewport.
|
||||
// Instead, we treat this as an exit of the previous entry by reverting the new name.
|
||||
// Ideally we could undo the old transition but it's now too late. It's also on its
|
||||
// on snapshot. We have know was for it to paint onto the original group.
|
||||
// TODO: This will lead to things unexpectedly having exit animations that normally
|
||||
// wouldn't happen. Consider if we should just let this fly off the screen instead.
|
||||
restoreViewTransitionOnHostInstances(child.child, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
commitAppearingPairViewTransitions(child);
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
@@ -690,21 +701,35 @@ function commitAppearingPairViewTransitions(placement: Fiber): void {
|
||||
|
||||
function commitEnterViewTransitions(placement: Fiber): void {
|
||||
if (placement.tag === ViewTransitionComponent) {
|
||||
const state: ViewTransitionState = placement.stateNode;
|
||||
const props: ViewTransitionProps = placement.memoizedProps;
|
||||
const name = getViewTransitionName(props, placement.stateNode);
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
const inViewport = applyViewTransitionToHostInstances(
|
||||
placement.child,
|
||||
name,
|
||||
const name = getViewTransitionName(props, state);
|
||||
const className: ?string = getViewTransitionClassName(
|
||||
props.className,
|
||||
null,
|
||||
false,
|
||||
state.paired ? props.share : props.enter,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// Revert the transition names. This boundary is not in the viewport
|
||||
// so we won't bother animating it.
|
||||
restoreViewTransitionOnHostInstances(placement.child, false);
|
||||
// TODO: Should we still visit the children in case a named one was in the viewport?
|
||||
if (className !== 'none') {
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
const inViewport = applyViewTransitionToHostInstances(
|
||||
placement.child,
|
||||
name,
|
||||
className,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// TODO: If this was part of a pair we will still run the onShare callback.
|
||||
// Revert the transition names. This boundary is not in the viewport
|
||||
// so we won't bother animating it.
|
||||
restoreViewTransitionOnHostInstances(placement.child, false);
|
||||
// TODO: Should we still visit the children in case a named one was in the viewport?
|
||||
} else {
|
||||
commitAppearingPairViewTransitions(placement);
|
||||
|
||||
if (!state.paired) {
|
||||
scheduleViewTransitionEvent(placement, props.onEnter);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
commitAppearingPairViewTransitions(placement);
|
||||
}
|
||||
@@ -745,25 +770,34 @@ function commitDeletedPairViewTransitions(
|
||||
if (name != null && name !== 'auto') {
|
||||
const pair = appearingViewTransitions.get(name);
|
||||
if (pair !== undefined) {
|
||||
// We found a new appearing view transition with the same name as this deletion.
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
const inViewport = applyViewTransitionToHostInstances(
|
||||
child.child,
|
||||
name,
|
||||
const className: ?string = getViewTransitionClassName(
|
||||
props.className,
|
||||
null,
|
||||
false,
|
||||
props.share,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// This boundary is not in the viewport so we won't treat it as a matched pair.
|
||||
// Revert the transition names. This avoids it flying onto the screen which can
|
||||
// be disruptive and doesn't really preserve any continuity anyway.
|
||||
restoreViewTransitionOnHostInstances(child.child, false);
|
||||
} else {
|
||||
// We'll transition between them.
|
||||
const oldinstance: ViewTransitionState = child.stateNode;
|
||||
const newInstance: ViewTransitionState = pair;
|
||||
newInstance.paired = oldinstance;
|
||||
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,
|
||||
className,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// This boundary is not in the viewport so we won't treat it as a matched pair.
|
||||
// Revert the transition names. This avoids it flying onto the screen which can
|
||||
// be disruptive and doesn't really preserve any continuity anyway.
|
||||
restoreViewTransitionOnHostInstances(child.child, false);
|
||||
} else {
|
||||
// We'll transition between them.
|
||||
const oldinstance: ViewTransitionState = child.stateNode;
|
||||
const newInstance: ViewTransitionState = pair;
|
||||
newInstance.paired = oldinstance;
|
||||
// Note: If the other side ends up outside the viewport, we'll still run this.
|
||||
// Therefore it's possible for onShare to be called with only an old snapshot.
|
||||
scheduleViewTransitionEvent(child, props.onShare);
|
||||
}
|
||||
}
|
||||
// Delete the entry so that we know when we've found all of them
|
||||
// and can stop searching (size reaches zero).
|
||||
@@ -787,22 +821,29 @@ function commitExitViewTransitions(
|
||||
if (deletion.tag === ViewTransitionComponent) {
|
||||
const props: ViewTransitionProps = deletion.memoizedProps;
|
||||
const name = getViewTransitionName(props, deletion.stateNode);
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
const inViewport = applyViewTransitionToHostInstances(
|
||||
deletion.child,
|
||||
name,
|
||||
const pair =
|
||||
appearingViewTransitions !== null
|
||||
? appearingViewTransitions.get(name)
|
||||
: undefined;
|
||||
const className: ?string = getViewTransitionClassName(
|
||||
props.className,
|
||||
null,
|
||||
false,
|
||||
pair !== undefined ? props.share : props.exit,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// Revert the transition names. This boundary is not in the viewport
|
||||
// so we won't bother animating it.
|
||||
restoreViewTransitionOnHostInstances(deletion.child, false);
|
||||
// TODO: Should we still visit the children in case a named one was in the viewport?
|
||||
} else if (appearingViewTransitions !== null) {
|
||||
const pair = appearingViewTransitions.get(name);
|
||||
if (pair !== undefined) {
|
||||
if (className !== 'none') {
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
const inViewport = applyViewTransitionToHostInstances(
|
||||
deletion.child,
|
||||
name,
|
||||
className,
|
||||
null,
|
||||
false,
|
||||
);
|
||||
if (!inViewport) {
|
||||
// Revert the transition names. This boundary is not in the viewport
|
||||
// so we won't bother animating it.
|
||||
restoreViewTransitionOnHostInstances(deletion.child, false);
|
||||
// TODO: Should we still visit the children in case a named one was in the viewport?
|
||||
} else if (pair !== undefined) {
|
||||
// We found a new appearing view transition with the same name as this deletion.
|
||||
// We'll transition between them instead of running the normal exit.
|
||||
const oldinstance: ViewTransitionState = deletion.stateNode;
|
||||
@@ -810,8 +851,16 @@ function commitExitViewTransitions(
|
||||
newInstance.paired = oldinstance;
|
||||
// Delete the entry so that we know when we've found all of them
|
||||
// and can stop searching (size reaches zero).
|
||||
// $FlowFixMe[incompatible-use]: Refined by the pair.
|
||||
appearingViewTransitions.delete(name);
|
||||
// Note: If the other side ends up outside the viewport, we'll still run this.
|
||||
// Therefore it's possible for onShare to be called with only an old snapshot.
|
||||
scheduleViewTransitionEvent(deletion, props.onShare);
|
||||
} else {
|
||||
scheduleViewTransitionEvent(deletion, props.onExit);
|
||||
}
|
||||
}
|
||||
if (appearingViewTransitions !== null) {
|
||||
// Look for more pairs deeper in the tree.
|
||||
commitDeletedPairViewTransitions(deletion, appearingViewTransitions);
|
||||
}
|
||||
@@ -828,7 +877,10 @@ function commitExitViewTransitions(
|
||||
}
|
||||
}
|
||||
|
||||
function commitBeforeUpdateViewTransition(current: Fiber): void {
|
||||
function commitBeforeUpdateViewTransition(
|
||||
current: Fiber,
|
||||
finishedWork: Fiber,
|
||||
): void {
|
||||
// The way we deal with multiple HostInstances as children of a View Transition in an
|
||||
// update can get tricky. The important bit is that if you swap out n HostInstances
|
||||
// from n HostInstances then they match up in order. Similarly, if you don't swap
|
||||
@@ -845,13 +897,32 @@ function commitBeforeUpdateViewTransition(current: Fiber): void {
|
||||
// be unexpected but it is in line with the semantics that the ViewTransition is its
|
||||
// own layer that cross-fades its content when it updates. If you want to reorder then
|
||||
// each child needs its own ViewTransition.
|
||||
const props: ViewTransitionProps = current.memoizedProps;
|
||||
const name = getViewTransitionName(props, current.stateNode);
|
||||
const oldProps: ViewTransitionProps = current.memoizedProps;
|
||||
const oldName = getViewTransitionName(oldProps, current.stateNode);
|
||||
const newProps: ViewTransitionProps = finishedWork.memoizedProps;
|
||||
// This className applies only if there are fewer child DOM nodes than
|
||||
// before or if this update should've been cancelled but we ended up with
|
||||
// a parent animating so we need to animate the child too.
|
||||
// For example, if update="foo" layout="none" and it turns out this was
|
||||
// a layout only change, then the "foo" class will be applied even though
|
||||
// it was not actually an update. Which is a bug.
|
||||
let className: ?string = getViewTransitionClassName(
|
||||
newProps.className,
|
||||
newProps.update,
|
||||
);
|
||||
if (className === 'none') {
|
||||
className = getViewTransitionClassName(newProps.className, newProps.layout);
|
||||
if (className === 'none') {
|
||||
// If both update and layout are both "none" then we don't have to
|
||||
// apply a name. Since we won't animate this boundary.
|
||||
return;
|
||||
}
|
||||
}
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
applyViewTransitionToHostInstances(
|
||||
current.child,
|
||||
name,
|
||||
props.className,
|
||||
oldName,
|
||||
className,
|
||||
(current.memoizedState = []),
|
||||
true,
|
||||
);
|
||||
@@ -865,14 +936,20 @@ function commitNestedViewTransitions(changedParent: Fiber): void {
|
||||
// was an update through this component then the inner one wins.
|
||||
const props: ViewTransitionProps = child.memoizedProps;
|
||||
const name = getViewTransitionName(props, child.stateNode);
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
applyViewTransitionToHostInstances(
|
||||
child.child,
|
||||
name,
|
||||
const className: ?string = getViewTransitionClassName(
|
||||
props.className,
|
||||
(child.memoizedState = []),
|
||||
false,
|
||||
props.layout,
|
||||
);
|
||||
if (className !== 'none') {
|
||||
viewTransitionHostInstanceIdx = 0;
|
||||
applyViewTransitionToHostInstances(
|
||||
child.child,
|
||||
name,
|
||||
className,
|
||||
(child.memoizedState = []),
|
||||
false,
|
||||
);
|
||||
}
|
||||
} else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
|
||||
commitNestedViewTransitions(child);
|
||||
}
|
||||
@@ -962,10 +1039,58 @@ function restoreNestedViewTransitions(changedParent: Fiber): void {
|
||||
}
|
||||
}
|
||||
|
||||
function cancelViewTransitionHostInstances(
|
||||
currentViewTransition: Fiber,
|
||||
child: null | Fiber,
|
||||
stopAtNestedViewTransitions: boolean,
|
||||
): void {
|
||||
if (!supportsMutation) {
|
||||
return;
|
||||
}
|
||||
while (child !== null) {
|
||||
if (child.tag === HostComponent) {
|
||||
const instance: Instance = child.stateNode;
|
||||
const oldName = getViewTransitionName(
|
||||
currentViewTransition.memoizedProps,
|
||||
currentViewTransition.stateNode,
|
||||
);
|
||||
if (viewTransitionCancelableChildren === null) {
|
||||
viewTransitionCancelableChildren = [];
|
||||
}
|
||||
viewTransitionCancelableChildren.push(
|
||||
instance,
|
||||
oldName,
|
||||
child.memoizedProps,
|
||||
);
|
||||
viewTransitionHostInstanceIdx++;
|
||||
} else if (
|
||||
child.tag === OffscreenComponent &&
|
||||
child.memoizedState !== null
|
||||
) {
|
||||
// Skip any hidden subtrees. They were or are effectively not there.
|
||||
} else if (
|
||||
child.tag === ViewTransitionComponent &&
|
||||
stopAtNestedViewTransitions
|
||||
) {
|
||||
// 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,
|
||||
child.child,
|
||||
stopAtNestedViewTransitions,
|
||||
);
|
||||
}
|
||||
child = child.sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function measureViewTransitionHostInstances(
|
||||
currentViewTransition: Fiber,
|
||||
parentViewTransition: Fiber,
|
||||
child: null | Fiber,
|
||||
name: string,
|
||||
className: ?string,
|
||||
previousMeasurements: null | Array<InstanceMeasurement>,
|
||||
stopAtNestedViewTransitions: boolean,
|
||||
): boolean {
|
||||
@@ -1011,20 +1136,15 @@ function measureViewTransitionHostInstances(
|
||||
parentViewTransition.flags |= AffectedParentLayout;
|
||||
}
|
||||
if ((parentViewTransition.flags & Update) !== NoFlags) {
|
||||
const props: ViewTransitionProps = parentViewTransition.memoizedProps;
|
||||
// We might update this node so we need to apply its new name for the new state.
|
||||
const newName = getViewTransitionName(
|
||||
props,
|
||||
parentViewTransition.stateNode,
|
||||
);
|
||||
applyViewTransitionName(
|
||||
instance,
|
||||
viewTransitionHostInstanceIdx === 0
|
||||
? newName
|
||||
? name
|
||||
: // If we have multiple Host Instances below, we add a suffix to the name to give
|
||||
// each one a unique name.
|
||||
newName + '_' + viewTransitionHostInstanceIdx,
|
||||
props.className,
|
||||
name + '_' + viewTransitionHostInstanceIdx,
|
||||
className,
|
||||
);
|
||||
}
|
||||
if (!inViewport || (parentViewTransition.flags & Update) === NoFlags) {
|
||||
@@ -1066,6 +1186,8 @@ function measureViewTransitionHostInstances(
|
||||
currentViewTransition,
|
||||
parentViewTransition,
|
||||
child.child,
|
||||
name,
|
||||
className,
|
||||
previousMeasurements,
|
||||
stopAtNestedViewTransitions,
|
||||
)
|
||||
@@ -1082,6 +1204,42 @@ function measureUpdateViewTransition(
|
||||
current: Fiber,
|
||||
finishedWork: Fiber,
|
||||
): boolean {
|
||||
const props: ViewTransitionProps = finishedWork.memoizedProps;
|
||||
const updateClassName: ?string = getViewTransitionClassName(
|
||||
props.className,
|
||||
props.update,
|
||||
);
|
||||
const layoutClassName: ?string = getViewTransitionClassName(
|
||||
props.className,
|
||||
props.update,
|
||||
);
|
||||
let className: ?string;
|
||||
if (updateClassName === 'none') {
|
||||
if (layoutClassName === 'none') {
|
||||
// If both update and layout class name were none, then we didn't apply any
|
||||
// names in the before update phase so we shouldn't now neither.
|
||||
return false;
|
||||
}
|
||||
// We don't care if this is mutated or children layout changed, but we still
|
||||
// measure each instance to see if it moved and therefore should apply layout.
|
||||
finishedWork.flags &= ~Update;
|
||||
className = layoutClassName;
|
||||
} else if ((finishedWork.flags & Update) !== NoFlags) {
|
||||
// It was updated and we have an appropriate class name to apply.
|
||||
className = updateClassName;
|
||||
} else {
|
||||
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);
|
||||
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;
|
||||
@@ -1090,6 +1248,8 @@ function measureUpdateViewTransition(
|
||||
current,
|
||||
finishedWork,
|
||||
finishedWork.child,
|
||||
name,
|
||||
className,
|
||||
previousMeasurements,
|
||||
true,
|
||||
);
|
||||
@@ -1110,14 +1270,27 @@ function measureNestedViewTransitions(changedParent: Fiber): void {
|
||||
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;
|
||||
measureViewTransitionHostInstances(
|
||||
const inViewport = measureViewTransitionHostInstances(
|
||||
current,
|
||||
child,
|
||||
child.child,
|
||||
name,
|
||||
className,
|
||||
child.memoizedState,
|
||||
false,
|
||||
);
|
||||
if ((child.flags & Update) === NoFlags || !inViewport) {
|
||||
// Nothing changed.
|
||||
} else {
|
||||
scheduleViewTransitionEvent(child, props.onLayout);
|
||||
}
|
||||
}
|
||||
} else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
|
||||
measureNestedViewTransitions(child);
|
||||
@@ -2991,11 +3164,6 @@ function recursivelyTraverseAfterMutationEffects(
|
||||
// its size and position. We need to measure this and if not, restore it to
|
||||
// not animate.
|
||||
measureNestedViewTransitions(parentFiber);
|
||||
if ((parentFiber.flags & AffectedParentLayout) !== NoFlags) {
|
||||
// This boundary changed size in a way that may have caused its parent to
|
||||
// relayout. We need to bubble this information up to the parent.
|
||||
viewTransitionContextChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3075,6 +3243,8 @@ function commitAfterMutationEffectsOnFiber(
|
||||
(Placement | Update | ChildDeletion | ContentReset | Visibility)) !==
|
||||
NoFlags
|
||||
) {
|
||||
const wasMutated = (finishedWork.flags & Update) !== NoFlags;
|
||||
|
||||
const prevContextChanged = viewTransitionContextChanged;
|
||||
const prevCancelableChildren = viewTransitionCancelableChildren;
|
||||
viewTransitionContextChanged = false;
|
||||
@@ -3103,7 +3273,17 @@ function commitAfterMutationEffectsOnFiber(
|
||||
);
|
||||
viewTransitionCancelableChildren = 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.
|
||||
} else {
|
||||
const props: ViewTransitionProps = finishedWork.memoizedProps;
|
||||
scheduleViewTransitionEvent(
|
||||
finishedWork,
|
||||
wasMutated || viewTransitionContextChanged
|
||||
? props.onUpdate
|
||||
: props.onLayout,
|
||||
);
|
||||
|
||||
// If this boundary did update, we cannot cancel its children so those are dropped.
|
||||
viewTransitionCancelableChildren = prevCancelableChildren;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,18 @@ import {getTreeId} from './ReactFiberTreeContext';
|
||||
|
||||
export type ViewTransitionProps = {
|
||||
name?: string,
|
||||
className?: string,
|
||||
children?: ReactNodeList,
|
||||
className?: 'none' | string,
|
||||
enter?: 'none' | string,
|
||||
exit?: 'none' | string,
|
||||
layout?: 'none' | string,
|
||||
share?: 'none' | string,
|
||||
update?: 'none' | string,
|
||||
onEnter?: (instance: ViewTransitionInstance) => void,
|
||||
onExit?: (instance: ViewTransitionInstance) => void,
|
||||
onLayout?: (instance: ViewTransitionInstance) => void,
|
||||
onShare?: (instance: ViewTransitionInstance) => void,
|
||||
onUpdate?: (instance: ViewTransitionInstance) => void,
|
||||
};
|
||||
|
||||
export type ViewTransitionState = {
|
||||
@@ -71,3 +81,19 @@ export function getViewTransitionName(
|
||||
// We should have assigned a name by now.
|
||||
return (instance.autoName: any);
|
||||
}
|
||||
|
||||
export function getViewTransitionClassName(
|
||||
className: ?string,
|
||||
eventClassName: ?string,
|
||||
): ?string {
|
||||
if (eventClassName == null) {
|
||||
return className;
|
||||
}
|
||||
if (eventClassName === 'none') {
|
||||
return eventClassName;
|
||||
}
|
||||
if (className != null) {
|
||||
return className + ' ' + eventClassName;
|
||||
}
|
||||
return eventClassName;
|
||||
}
|
||||
|
||||
+46
-2
@@ -21,9 +21,12 @@ import type {
|
||||
TransitionAbort,
|
||||
} from './ReactFiberTracingMarkerComponent';
|
||||
import type {OffscreenInstance} from './ReactFiberActivityComponent';
|
||||
import type {Resource} from './ReactFiberConfig';
|
||||
import type {Resource, ViewTransitionInstance} from './ReactFiberConfig';
|
||||
import type {RootState} from './ReactFiberRoot';
|
||||
import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
|
||||
import {
|
||||
getViewTransitionName,
|
||||
type ViewTransitionState,
|
||||
} from './ReactFiberViewTransitionComponent';
|
||||
|
||||
import {
|
||||
enableCreateEventHandleAPI,
|
||||
@@ -95,6 +98,7 @@ import {
|
||||
resolveUpdatePriority,
|
||||
trackSchedulerEvent,
|
||||
startViewTransition,
|
||||
createViewTransitionInstance,
|
||||
} from './ReactFiberConfig';
|
||||
|
||||
import {createWorkInProgress, resetWorkInProgress} from './ReactFiber';
|
||||
@@ -649,6 +653,7 @@ let pendingEffectsRemainingLanes: Lanes = NoLanes;
|
||||
let pendingEffectsRenderEndTime: number = -0; // Profiling-only
|
||||
let pendingPassiveTransitions: Array<Transition> | null = null;
|
||||
let pendingRecoverableErrors: null | Array<CapturedValue<mixed>> = null;
|
||||
let pendingViewTransitionEvents: Array<() => void> | null = null;
|
||||
let pendingDidIncludeRenderPhaseUpdate: boolean = false;
|
||||
let pendingSuspendedCommitReason: SuspendedCommitReason = IMMEDIATE_COMMIT; // Profiling-only
|
||||
|
||||
@@ -797,6 +802,27 @@ export function requestDeferredLane(): Lane {
|
||||
return workInProgressDeferredLane;
|
||||
}
|
||||
|
||||
export function scheduleViewTransitionEvent(
|
||||
fiber: Fiber,
|
||||
callback: ?(instance: ViewTransitionInstance) => void,
|
||||
): void {
|
||||
if (enableViewTransition) {
|
||||
if (callback != null) {
|
||||
const state: ViewTransitionState = fiber.stateNode;
|
||||
let instance = state.ref;
|
||||
if (instance === null) {
|
||||
instance = state.ref = createViewTransitionInstance(
|
||||
getViewTransitionName(fiber.memoizedProps, state),
|
||||
);
|
||||
}
|
||||
if (pendingViewTransitionEvents === null) {
|
||||
pendingViewTransitionEvents = [];
|
||||
}
|
||||
pendingViewTransitionEvents.push(callback.bind(null, instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function peekDeferredLane(): Lane {
|
||||
return workInProgressDeferredLane;
|
||||
}
|
||||
@@ -3322,6 +3348,9 @@ function commitRoot(
|
||||
pendingEffectsRemainingLanes = remainingLanes;
|
||||
pendingPassiveTransitions = transitions;
|
||||
pendingRecoverableErrors = recoverableErrors;
|
||||
if (enableViewTransition) {
|
||||
pendingViewTransitionEvents = null;
|
||||
}
|
||||
pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;
|
||||
if (enableProfilerTimer) {
|
||||
pendingEffectsRenderEndTime = completedRenderEndTime;
|
||||
@@ -3673,6 +3702,21 @@ function flushSpawnedWork(): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (enableViewTransition) {
|
||||
// We should now be after the startViewTransition's .ready call which is late enough
|
||||
// to start animating any pseudo-elements. We do this before flushing any passive
|
||||
// effects or spawned sync work since this is still part of the previous commit.
|
||||
// Even though conceptually it's like its own task between layout effets and passive.
|
||||
const pendingEvents = pendingViewTransitionEvents;
|
||||
if (pendingEvents !== null) {
|
||||
pendingViewTransitionEvents = null;
|
||||
for (let i = 0; i < pendingEvents.length; i++) {
|
||||
const viewTransitionEvent = pendingEvents[i];
|
||||
viewTransitionEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the passive effects are the result of a discrete render, flush them
|
||||
// synchronously at the end of the current task so that the result is
|
||||
// immediately observable. Otherwise, we assume that they are not
|
||||
|
||||
Reference in New Issue
Block a user