Use strict equality (===) to prevent type coercion bugs
if (appearingViewTransitions === null) {
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {ViewTransitionProps} from 'shared/ReactTypes';11import type {Instance, InstanceMeasurement, Props} from './ReactFiberConfig';12import type {Fiber} from './ReactInternalTypes';13import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';1415import {16 HostComponent,17 OffscreenComponent,18 ViewTransitionComponent,19} from './ReactWorkTags';20import {21 NoFlags,22 Update,23 ViewTransitionStatic,24 ViewTransitionStaticParent,25 AffectedParentLayout,26 ViewTransitionNamedStatic,27} from './ReactFiberFlags';28import {29 supportsMutation,30 applyViewTransitionName,31 restoreViewTransitionName,32 measureInstance,33 measureClonedInstance,34 hasInstanceChanged,35 hasInstanceAffectedParent,36 wasInstanceInViewport,37} from './ReactFiberConfig';38import {39 scheduleViewTransitionEvent,40 scheduleGestureTransitionEvent,41} from './ReactFiberWorkLoop';42import {43 getViewTransitionName,44 getViewTransitionClassName,45} from './ReactFiberViewTransitionComponent';46import {trackAnimatingTask} from './ReactProfilerTimer';47import {48 enableComponentPerformanceTrack,49 enableProfilerTimer,50 enableViewTransitionForPersistenceMode,51 enableViewTransitionParentEnterExit,52} from 'shared/ReactFeatureFlags';5354export let shouldStartViewTransition: boolean = false;5556export function resetShouldStartViewTransition(): void {57 shouldStartViewTransition = false;58}5960// This tracks named ViewTransition components found in the accumulateSuspenseyCommit61// phase that might need to find deleted pairs in the beforeMutation phase.62export let appearingViewTransitions: Map<string, ViewTransitionState> | null =63 null;6465export function resetAppearingViewTransitions(): void {66 appearingViewTransitions = null;67}6869export function trackAppearingViewTransition(70 name: string,71 state: ViewTransitionState,72): void {73 if (appearingViewTransitions === null) {74 appearingViewTransitions = new Map();75 }76 appearingViewTransitions.set(name, state);77}7879export function trackEnterViewTransitions(placement: Fiber): void {80 if (81 placement.tag === ViewTransitionComponent ||82 (placement.subtreeFlags & ViewTransitionStatic) !== NoFlags83 ) {84 // If an inserted or appearing Fiber is a ViewTransition component or has one as85 // an immediate child, then that will trigger as an "Enter" in future passes.86 // We don't do anything else for that case in the "before mutation" phase but we87 // still have to mark it as needing to call startViewTransition if nothing else88 // updates.89 shouldStartViewTransition = true;90 }91}9293// We can't cancel view transition children until we know that their parent also94// don't need to transition.95export let viewTransitionCancelableChildren: null | Array<96 Instance | string | Props,97> = null; // tupled array where each entry is [instance: Instance, oldName: string, props: Props]9899export function pushViewTransitionCancelableScope(): null | Array<100 Instance | string | Props,101> {102 const prevChildren = viewTransitionCancelableChildren;103 viewTransitionCancelableChildren = null;104 return prevChildren;105}106107export function popViewTransitionCancelableScope(108 prevChildren: null | Array<Instance | string | Props>,109): void {110 viewTransitionCancelableChildren = prevChildren;111}112113let viewTransitionHostInstanceIdx = 0;114115function applyViewTransitionToHostInstances(116 fiber: Fiber,117 name: string,118 className: ?string,119 collectMeasurements: null | Array<InstanceMeasurement>,120 stopAtNestedViewTransitions: boolean,121): boolean {122 viewTransitionHostInstanceIdx = 0;123 const inViewport = applyViewTransitionToHostInstancesRecursive(124 fiber.child,125 name,126 className,127 collectMeasurements,128 stopAtNestedViewTransitions,129 );130 if (enableProfilerTimer && enableComponentPerformanceTrack && inViewport) {131 if (fiber._debugTask != null) {132 trackAnimatingTask(fiber._debugTask);133 }134 }135 return inViewport;136}137138function applyViewTransitionToHostInstancesRecursive(139 child: null | Fiber,140 name: string,141 className: ?string,142 collectMeasurements: null | Array<InstanceMeasurement>,143 stopAtNestedViewTransitions: boolean,144): boolean {145 // $FlowFixMe[constant-condition]146 if (!supportsMutation) {147 if (enableViewTransitionForPersistenceMode) {148 while (child !== null) {149 if (child.tag === HostComponent) {150 const instance: Instance = child.stateNode;151 // TODO: calculate whether component is in viewport152 shouldStartViewTransition = true;153 applyViewTransitionName(154 instance,155 viewTransitionHostInstanceIdx === 0156 ? name157 : name + '_' + viewTransitionHostInstanceIdx,158 className,159 );160 viewTransitionHostInstanceIdx++;161 } else if (162 child.tag === OffscreenComponent &&163 child.memoizedState !== null164 ) {165 // Skip any hidden subtrees. They were or are effectively not there.166 } else if (167 child.tag === ViewTransitionComponent &&168 stopAtNestedViewTransitions169 ) {170 // Skip any nested view transitions for updates since in that case the171 // inner most one is the one that handles the update.172 } else {173 applyViewTransitionToHostInstancesRecursive(174 child.child,175 name,176 className,177 collectMeasurements,178 stopAtNestedViewTransitions,179 );180 }181 child = child.sibling;182 }183 return true;184 } else {185 return false;186 }187 }188 let inViewport = false;189 while (child !== null) {190 if (child.tag === HostComponent) {191 const instance: Instance = child.stateNode;192 if (collectMeasurements !== null) {193 const measurement = measureInstance(instance);194 collectMeasurements.push(measurement);195 if (wasInstanceInViewport(measurement)) {196 inViewport = true;197 }198 } else if (!inViewport) {199 if (wasInstanceInViewport(measureInstance(instance))) {200 inViewport = true;201 }202 }203 shouldStartViewTransition = true;204 applyViewTransitionName(205 instance,206 viewTransitionHostInstanceIdx === 0207 ? name208 : // If we have multiple Host Instances below, we add a suffix to the name to give209 // each one a unique name.210 name + '_' + viewTransitionHostInstanceIdx,211 className,212 );213 viewTransitionHostInstanceIdx++;214 } else if (215 child.tag === OffscreenComponent &&216 child.memoizedState !== null217 ) {218 // Skip any hidden subtrees. They were or are effectively not there.219 } else if (220 child.tag === ViewTransitionComponent &&221 stopAtNestedViewTransitions222 ) {223 // Skip any nested view transitions for updates since in that case the224 // inner most one is the one that handles the update.225 } else {226 if (227 applyViewTransitionToHostInstancesRecursive(228 child.child,229 name,230 className,231 collectMeasurements,232 stopAtNestedViewTransitions,233 )234 ) {235 inViewport = true;236 }237 }238 child = child.sibling;239 }240 return inViewport;241}242243function restoreViewTransitionOnHostInstances(244 child: null | Fiber,245 stopAtNestedViewTransitions: boolean,246): void {247 // $FlowFixMe[constant-condition]248 if (!supportsMutation) {249 return;250 }251 while (child !== null) {252 if (child.tag === HostComponent) {253 const instance: Instance = child.stateNode;254 restoreViewTransitionName(instance, child.memoizedProps);255 } else if (256 child.tag === OffscreenComponent &&257 child.memoizedState !== null258 ) {259 // Skip any hidden subtrees. They were or are effectively not there.260 } else if (261 child.tag === ViewTransitionComponent &&262 stopAtNestedViewTransitions263 ) {264 // Skip any nested view transitions for updates since in that case the265 // inner most one is the one that handles the update.266 } else {267 restoreViewTransitionOnHostInstances(268 child.child,269 stopAtNestedViewTransitions,270 );271 }272 child = child.sibling;273 }274}275276function commitAppearingPairViewTransitions(placement: Fiber): void {277 if ((placement.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {278 // This has no named view transitions in its subtree.279 return;280 }281 let child = placement.child;282 while (child !== null) {283 if (child.tag === OffscreenComponent && child.memoizedState !== null) {284 // This tree was already hidden so we skip it.285 } else {286 commitAppearingPairViewTransitions(child);287 if (288 child.tag === ViewTransitionComponent &&289 (child.flags & ViewTransitionNamedStatic) !== NoFlags290 ) {291 const instance: ViewTransitionState = child.stateNode;292 if (instance.paired) {293 const props: ViewTransitionProps = child.memoizedProps;294 if (props.name == null || props.name === 'auto') {295 throw new Error(296 'Found a pair with an auto name. This is a bug in React.',297 );298 }299 const name = props.name;300 const className: ?string = getViewTransitionClassName(301 props.default,302 props.share,303 );304 if (className !== 'none') {305 // We found a new appearing view transition with the same name as this deletion.306 // We'll transition between them.307 const inViewport = applyViewTransitionToHostInstances(308 child,309 name,310 className,311 null,312 false,313 );314 if (!inViewport) {315 // This boundary is exiting within the viewport but is going to leave the viewport.316 // Instead, we treat this as an exit of the previous entry by reverting the new name.317 // Ideally we could undo the old transition but it's now too late. It's also on its318 // on snapshot. We have know was for it to paint onto the original group.319 // TODO: This will lead to things unexpectedly having exit animations that normally320 // wouldn't happen. Consider if we should just let this fly off the screen instead.321 restoreViewTransitionOnHostInstances(child.child, false);322 }323 }324 }325 }326 }327 child = child.sibling;328 }329}330331export function commitParentEnterViewTransitions(332 parent: Fiber,333 gesture: boolean,334): void {335 let child = parent.child;336 while (child !== null) {337 if (child.tag === OffscreenComponent && child.memoizedState !== null) {338 // Skip hidden subtrees.339 } else if (child.tag === ViewTransitionComponent) {340 const props: ViewTransitionProps = child.memoizedProps;341 const hasParentClass = props.parentEnter !== undefined;342 const hasParentHandler = gesture343 ? props.onGestureParentEnter != null344 : props.onParentEnter != null;345 if (hasParentClass || hasParentHandler) {346 let relay = true;347 if (hasParentClass) {348 const state: ViewTransitionState = child.stateNode;349 const name = getViewTransitionName(props, state);350 const className: ?string = getViewTransitionClassName(351 props.default,352 props.parentEnter,353 );354 if (className === 'none') {355 relay = false;356 } else {357 applyViewTransitionToHostInstances(358 child,359 name,360 className,361 null,362 false,363 );364 if (hasParentHandler) {365 if (gesture) {366 scheduleGestureTransitionEvent(367 child,368 props.onGestureParentEnter,369 );370 } else {371 scheduleViewTransitionEvent(child, props.onParentEnter);372 }373 }374 }375 } else {376 if (gesture) {377 scheduleGestureTransitionEvent(child, props.onGestureParentEnter);378 } else {379 scheduleViewTransitionEvent(child, props.onParentEnter);380 }381 }382 if (relay) {383 commitParentEnterViewTransitions(child, gesture);384 }385 }386 } else if ((child.subtreeFlags & ViewTransitionStaticParent) !== NoFlags) {387 commitParentEnterViewTransitions(child, gesture);388 }389 child = child.sibling;390 }391}392393export function commitParentExitViewTransitions(394 parent: Fiber,395 gesture: boolean,396): void {397 let child = parent.child;398 while (child !== null) {399 if (child.tag === OffscreenComponent && child.memoizedState !== null) {400 // Skip hidden subtrees.401 } else if (child.tag === ViewTransitionComponent) {402 const props: ViewTransitionProps = child.memoizedProps;403 const hasParentClass = props.parentExit !== undefined;404 const hasParentHandler = gesture405 ? props.onGestureParentExit != null406 : props.onParentExit != null;407 if (hasParentClass || hasParentHandler) {408 let relay = true;409 if (hasParentClass) {410 const state: ViewTransitionState = child.stateNode;411 const name = getViewTransitionName(props, state);412 const className: ?string = getViewTransitionClassName(413 props.default,414 props.parentExit,415 );416 if (className === 'none') {417 relay = false;418 } else {419 applyViewTransitionToHostInstances(420 child,421 name,422 className,423 null,424 false,425 );426 if (hasParentHandler) {427 if (gesture) {428 scheduleGestureTransitionEvent(429 child,430 props.onGestureParentExit,431 );432 } else {433 scheduleViewTransitionEvent(child, props.onParentExit);434 }435 }436 }437 } else {438 if (gesture) {439 scheduleGestureTransitionEvent(child, props.onGestureParentExit);440 } else {441 scheduleViewTransitionEvent(child, props.onParentExit);442 }443 }444 if (relay) {445 commitParentExitViewTransitions(child, gesture);446 }447 }448 } else if ((child.subtreeFlags & ViewTransitionStaticParent) !== NoFlags) {449 commitParentExitViewTransitions(child, gesture);450 }451 child = child.sibling;452 }453}454455function restoreParentEnterOrExitViewTransitions(parent: Fiber): void {456 let child = parent.child;457 while (child !== null) {458 if (child.tag === OffscreenComponent && child.memoizedState !== null) {459 // Skip hidden subtrees.460 } else if (child.tag === ViewTransitionComponent) {461 const props: ViewTransitionProps = child.memoizedProps;462 const hasParentClass =463 props.parentEnter !== undefined || props.parentExit !== undefined;464 const hasParentHandler =465 props.onParentEnter != null ||466 props.onParentExit != null ||467 props.onGestureParentEnter != null ||468 props.onGestureParentExit != null;469 if (hasParentClass) {470 restoreViewTransitionOnHostInstances(child.child, false);471 }472 if (hasParentClass || hasParentHandler) {473 restoreParentEnterOrExitViewTransitions(child);474 }475 } else if ((child.subtreeFlags & ViewTransitionStaticParent) !== NoFlags) {476 restoreParentEnterOrExitViewTransitions(child);477 }478 child = child.sibling;479 }480}481482export function commitEnterViewTransitions(483 placement: Fiber,484 gesture: boolean,485): void {486 if (placement.tag === ViewTransitionComponent) {487 const state: ViewTransitionState = placement.stateNode;488 const props: ViewTransitionProps = placement.memoizedProps;489 const name = getViewTransitionName(props, state);490 const className: ?string = getViewTransitionClassName(491 props.default,492 state.paired ? props.share : props.enter,493 );494 if (className !== 'none') {495 const inViewport = applyViewTransitionToHostInstances(496 placement,497 name,498 className,499 null,500 false,501 );502 if (!inViewport) {503 // TODO: If this was part of a pair we will still run the onShare callback.504 // Revert the transition names. This boundary is not in the viewport505 // so we won't bother animating it.506 restoreViewTransitionOnHostInstances(placement.child, false);507 // TODO: Should we still visit the children in case a named one was in the viewport?508 } else {509 commitAppearingPairViewTransitions(placement);510511 if (!state.paired) {512 if (gesture) {513 scheduleGestureTransitionEvent(placement, props.onGestureEnter);514 } else {515 scheduleViewTransitionEvent(placement, props.onEnter);516 }517 if (enableViewTransitionParentEnterExit) {518 commitParentEnterViewTransitions(placement, gesture);519 }520 }521 }522 } else {523 commitAppearingPairViewTransitions(placement);524 }525 } else if ((placement.subtreeFlags & ViewTransitionStatic) !== NoFlags) {526 let child = placement.child;527 while (child !== null) {528 commitEnterViewTransitions(child, gesture);529 child = child.sibling;530 }531 } else {532 commitAppearingPairViewTransitions(placement);533 }534}535536function commitDeletedPairViewTransitions(deletion: Fiber): void {537 if (538 appearingViewTransitions === null ||539 appearingViewTransitions.size === 0540 ) {541 // We've found all.542 return;543 }544 const pairs = appearingViewTransitions;545 if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {546 // This has no named view transitions in its subtree.547 return;548 }549 let child = deletion.child;550 while (child !== null) {551 if (child.tag === OffscreenComponent && child.memoizedState !== null) {552 // This tree was already hidden so we skip it.553 } else {554 if (555 child.tag === ViewTransitionComponent &&556 (child.flags & ViewTransitionNamedStatic) !== NoFlags557 ) {558 const props: ViewTransitionProps = child.memoizedProps;559 const name = props.name;560 if (name != null && name !== 'auto') {561 const pair = pairs.get(name);562 if (pair !== undefined) {563 const className: ?string = getViewTransitionClassName(564 props.default,565 props.share,566 );567 if (className !== 'none') {568 // We found a new appearing view transition with the same name as this deletion.569 const inViewport = applyViewTransitionToHostInstances(570 child,571 name,572 className,573 null,574 false,575 );576 if (!inViewport) {577 // This boundary is not in the viewport so we won't treat it as a matched pair.578 // Revert the transition names. This avoids it flying onto the screen which can579 // be disruptive and doesn't really preserve any continuity anyway.580 restoreViewTransitionOnHostInstances(child.child, false);581 } else {582 // We'll transition between them.583 const oldInstance: ViewTransitionState = child.stateNode;584 const newInstance: ViewTransitionState = pair;585 newInstance.paired = oldInstance;586 oldInstance.paired = newInstance;587 // Note: If the other side ends up outside the viewport, we'll still run this.588 // Therefore it's possible for onShare to be called with only an old snapshot.589 scheduleViewTransitionEvent(child, props.onShare);590 }591 }592 // Delete the entry so that we know when we've found all of them593 // and can stop searching (size reaches zero).594 pairs.delete(name);595 if (pairs.size === 0) {596 break;597 }598 }599 }600 }601 commitDeletedPairViewTransitions(child);602 }603 child = child.sibling;604 }605}606607export function commitExitViewTransitions(deletion: Fiber): void {608 if (deletion.tag === ViewTransitionComponent) {609 const props: ViewTransitionProps = deletion.memoizedProps;610 const name = getViewTransitionName(props, deletion.stateNode);611 const pair =612 appearingViewTransitions !== null613 ? appearingViewTransitions.get(name)614 : undefined;615 const className: ?string = getViewTransitionClassName(616 props.default,617 pair !== undefined ? props.share : props.exit,618 );619 if (className !== 'none') {620 const inViewport = applyViewTransitionToHostInstances(621 deletion,622 name,623 className,624 null,625 false,626 );627 if (!inViewport) {628 // Revert the transition names. This boundary is not in the viewport629 // so we won't bother animating it.630 restoreViewTransitionOnHostInstances(deletion.child, false);631 // TODO: Should we still visit the children in case a named one was in the viewport?632 } else if (pair !== undefined) {633 // We found a new appearing view transition with the same name as this deletion.634 // We'll transition between them instead of running the normal exit.635 const oldInstance: ViewTransitionState = deletion.stateNode;636 const newInstance: ViewTransitionState = pair;637 newInstance.paired = oldInstance;638 oldInstance.paired = newInstance;639 // Delete the entry so that we know when we've found all of them640 // and can stop searching (size reaches zero).641 // $FlowFixMe[incompatible-use]: Refined by the pair.642 appearingViewTransitions.delete(name);643 // Note: If the other side ends up outside the viewport, we'll still run this.644 // Therefore it's possible for onShare to be called with only an old snapshot.645 scheduleViewTransitionEvent(deletion, props.onShare);646 } else {647 scheduleViewTransitionEvent(deletion, props.onExit);648 if (enableViewTransitionParentEnterExit) {649 commitParentExitViewTransitions(deletion, false);650 }651 }652 }653 if (appearingViewTransitions !== null) {654 // Look for more pairs deeper in the tree.655 commitDeletedPairViewTransitions(deletion);656 }657 } else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) {658 let child = deletion.child;659 while (child !== null) {660 commitExitViewTransitions(child);661 child = child.sibling;662 }663 } else {664 if (appearingViewTransitions !== null) {665 commitDeletedPairViewTransitions(deletion);666 }667 }668}669670export function commitBeforeUpdateViewTransition(671 current: Fiber,672 finishedWork: Fiber,673): void {674 // The way we deal with multiple HostInstances as children of a View Transition in an675 // update can get tricky. The important bit is that if you swap out n HostInstances676 // from n HostInstances then they match up in order. Similarly, if you don't swap677 // any HostInstances each instance just transitions as is.678 //679 // We call this function twice. First we apply the view transition names on the680 // "current" tree in the snapshot phase. Then in the mutation phase we apply view681 // transition names to the "finishedWork" tree.682 //683 // This means that if there were insertions or deletions before an updated Instance684 // that same Instance might get different names in the "old" and the "new" state.685 // For example if you swap two HostInstances inside a ViewTransition they don't686 // animate to swap position but rather cross-fade into the other instance. This might687 // be unexpected but it is in line with the semantics that the ViewTransition is its688 // own layer that cross-fades its content when it updates. If you want to reorder then689 // each child needs its own ViewTransition.690 const oldProps: ViewTransitionProps = current.memoizedProps;691 const oldName = getViewTransitionName(oldProps, current.stateNode);692 const newProps: ViewTransitionProps = finishedWork.memoizedProps;693 // This className applies only if there are fewer child DOM nodes than694 // before or if this update should've been cancelled but we ended up with695 // a parent animating so we need to animate the child too.696 // For example, if update="foo" layout="none" and it turns out this was697 // a layout only change, then the "foo" class will be applied even though698 // it was not actually an update. Which is a bug.699 const className: ?string = getViewTransitionClassName(700 newProps.default,701 newProps.update,702 );703 if (className === 'none') {704 // If update is "none" then we don't have to apply a name. Since we won't animate this boundary.705 return;706 }707 applyViewTransitionToHostInstances(708 current,709 oldName,710 className,711 (current.memoizedState = []),712 true,713 );714}715716export function commitNestedViewTransitions(changedParent: Fiber): void {717 let child = changedParent.child;718 while (child !== null) {719 if (child.tag === ViewTransitionComponent) {720 // In this case the outer ViewTransition component wins but if there721 // was an update through this component then the inner one wins.722 const props: ViewTransitionProps = child.memoizedProps;723 const name = getViewTransitionName(props, child.stateNode);724 const className: ?string = getViewTransitionClassName(725 props.default,726 props.update,727 );728 // "Nested" view transitions are in subtrees that didn't update so729 // this is a "current". We normally clear this upon rerendering730 // but we use this flag to track changes from layout in the commit.731 // So we need it to be cleared before we do that.732 // TODO: Use some other temporary state to track this.733 child.flags &= ~Update;734 if (className !== 'none') {735 applyViewTransitionToHostInstances(736 child,737 name,738 className,739 (child.memoizedState = []),740 false,741 );742 }743 } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {744 commitNestedViewTransitions(child);745 }746 child = child.sibling;747 }748}749750function restorePairedViewTransitions(parent: Fiber): void {751 if ((parent.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {752 // This has no named view transitions in its subtree.753 return;754 }755 let child = parent.child;756 while (child !== null) {757 if (child.tag === OffscreenComponent && child.memoizedState !== null) {758 // This tree was already hidden so we skip it.759 } else {760 if (761 child.tag === ViewTransitionComponent &&762 (child.flags & ViewTransitionNamedStatic) !== NoFlags763 ) {764 const instance: ViewTransitionState = child.stateNode;765 if (instance.paired !== null) {766 instance.paired = null;767 restoreViewTransitionOnHostInstances(child.child, false);768 }769 }770 restorePairedViewTransitions(child);771 }772 child = child.sibling;773 }774}775776export function restoreEnterOrExitViewTransitions(fiber: Fiber): void {777 if (fiber.tag === ViewTransitionComponent) {778 const instance: ViewTransitionState = fiber.stateNode;779 instance.paired = null;780 restoreViewTransitionOnHostInstances(fiber.child, false);781 if (enableViewTransitionParentEnterExit) {782 restoreParentEnterOrExitViewTransitions(fiber);783 }784 restorePairedViewTransitions(fiber);785 } else if ((fiber.subtreeFlags & ViewTransitionStatic) !== NoFlags) {786 let child = fiber.child;787 while (child !== null) {788 restoreEnterOrExitViewTransitions(child);789 child = child.sibling;790 }791 } else {792 restorePairedViewTransitions(fiber);793 }794}795796export function restoreUpdateViewTransition(797 current: Fiber,798 finishedWork: Fiber,799): void {800 restoreViewTransitionOnHostInstances(current.child, true);801 restoreViewTransitionOnHostInstances(finishedWork.child, true);802}803804export function restoreUpdateViewTransitionForGesture(805 current: Fiber,806 finishedWork: Fiber,807): void {808 // For gestures we don't need to reset "finishedWork" because those would809 // have all been clones that got deleted.810 restoreViewTransitionOnHostInstances(current.child, true);811}812813export function restoreNestedViewTransitions(changedParent: Fiber): void {814 let child = changedParent.child;815 while (child !== null) {816 if (child.tag === ViewTransitionComponent) {817 restoreViewTransitionOnHostInstances(child.child, false);818 } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {819 restoreNestedViewTransitions(child);820 }821 child = child.sibling;822 }823}824825export function measureViewTransitionHostInstances(826 parentViewTransition: Fiber,827 child: null | Fiber,828 newName: string,829 oldName: string,830 className: ?string,831 previousMeasurements: null | Array<InstanceMeasurement>,832 stopAtNestedViewTransitions: boolean,833): boolean {834 viewTransitionHostInstanceIdx = 0;835 return measureViewTransitionHostInstancesRecursive(836 parentViewTransition,837 child,838 newName,839 oldName,840 className,841 previousMeasurements,842 stopAtNestedViewTransitions,843 );844}845846function measureViewTransitionHostInstancesRecursive(847 parentViewTransition: Fiber,848 child: null | Fiber,849 newName: string,850 oldName: string,851 className: ?string,852 previousMeasurements: null | Array<InstanceMeasurement>,853 stopAtNestedViewTransitions: boolean,854): boolean {855 // $FlowFixMe[constant-condition]856 if (!supportsMutation) {857 if (enableViewTransitionForPersistenceMode) {858 while (child !== null) {859 if (child.tag === HostComponent) {860 const instance: Instance = child.stateNode;861 if (862 previousMeasurements == null ||863 viewTransitionHostInstanceIdx >= previousMeasurements.length864 ) {865 // If there was an insertion of extra nodes, we have to assume they affected the parent.866 // It should have already been marked as an Update due to the mutation.867 parentViewTransition.flags |= AffectedParentLayout;868 }869 // TODO: check if instance is out of viewport870 if ((parentViewTransition.flags & Update) !== NoFlags) {871 applyViewTransitionName(872 instance,873 viewTransitionHostInstanceIdx === 0874 ? newName875 : newName + '_' + viewTransitionHostInstanceIdx,876 className,877 );878 }879 // TODO: cancel transition by pushing into viewTransitionCancelableChildren880 viewTransitionHostInstanceIdx++;881 } else if (882 child.tag === OffscreenComponent &&883 child.memoizedState !== null884 ) {885 // Skip any hidden subtrees. They were or are effectively not there.886 } else if (887 child.tag === ViewTransitionComponent &&888 stopAtNestedViewTransitions889 ) {890 // Skip any nested view transitions for updates since in that case the891 // inner most one is the one that handles the update.892 // If this inner boundary resized we need to bubble that information up.893 parentViewTransition.flags |= child.flags & AffectedParentLayout;894 } else {895 measureViewTransitionHostInstancesRecursive(896 parentViewTransition,897 child.child,898 newName,899 oldName,900 className,901 previousMeasurements,902 stopAtNestedViewTransitions,903 );904 }905 child = child.sibling;906 }907 return true;908 } else {909 return false;910 }911 }912 let inViewport = false;913 while (child !== null) {914 if (child.tag === HostComponent) {915 const instance: Instance = child.stateNode;916 if (917 previousMeasurements !== null &&918 viewTransitionHostInstanceIdx < previousMeasurements.length919 ) {920 // The previous measurement of the Instance in this location within the ViewTransition.921 // Note that this might not be the same exact Instance if the Instances within the922 // ViewTransition changed.923 const previousMeasurement =924 previousMeasurements[viewTransitionHostInstanceIdx];925 const nextMeasurement = measureInstance(instance);926 if (927 wasInstanceInViewport(previousMeasurement) ||928 wasInstanceInViewport(nextMeasurement)929 ) {930 // If either the old or new state was within the viewport we have to animate this.931 // But if it turns out that none of them were we'll be able to skip it.932 inViewport = true;933 }934 if (935 (parentViewTransition.flags & Update) === NoFlags &&936 hasInstanceChanged(previousMeasurement, nextMeasurement)937 ) {938 parentViewTransition.flags |= Update;939 }940 if (hasInstanceAffectedParent(previousMeasurement, nextMeasurement)) {941 // If this instance size within its parent has changed it might have caused the942 // parent to relayout which needs a cross fade.943 parentViewTransition.flags |= AffectedParentLayout;944 }945 } else {946 // If there was an insertion of extra nodes, we have to assume they affected the parent.947 // It should have already been marked as an Update due to the mutation.948 parentViewTransition.flags |= AffectedParentLayout;949 }950 if ((parentViewTransition.flags & Update) !== NoFlags) {951 // We might update this node so we need to apply its new name for the new state.952 // Additionally in the ApplyGesture case we also need to do this because the clone953 // will have the name but this one won't.954 applyViewTransitionName(955 instance,956 viewTransitionHostInstanceIdx === 0957 ? newName958 : // If we have multiple Host Instances below, we add a suffix to the name to give959 // each one a unique name.960 newName + '_' + viewTransitionHostInstanceIdx,961 className,962 );963 }964 if (!inViewport || (parentViewTransition.flags & Update) === NoFlags) {965 // It turns out that we had no other deeper mutations, the child transitions didn't966 // affect the parent layout and this instance hasn't changed size. So we can skip967 // animating it. However, in the current model this only works if the parent also968 // doesn't animate. So we have to queue these and wait until we complete the parent969 // to cancel them.970 if (viewTransitionCancelableChildren === null) {971 viewTransitionCancelableChildren = [];972 }973 viewTransitionCancelableChildren.push(974 instance,975 viewTransitionHostInstanceIdx === 0976 ? oldName977 : // If we have multiple Host Instances below, we add a suffix to the name to give978 // each one a unique name.979 oldName + '_' + viewTransitionHostInstanceIdx,980 child.memoizedProps,981 );982 }983 viewTransitionHostInstanceIdx++;984 } else if (985 child.tag === OffscreenComponent &&986 child.memoizedState !== null987 ) {988 // Skip any hidden subtrees. They were or are effectively not there.989 } else if (990 child.tag === ViewTransitionComponent &&991 stopAtNestedViewTransitions992 ) {993 // Skip any nested view transitions for updates since in that case the994 // inner most one is the one that handles the update.995 // If this inner boundary resized we need to bubble that information up.996 parentViewTransition.flags |= child.flags & AffectedParentLayout;997 } else {998 if (999 measureViewTransitionHostInstancesRecursive(1000 parentViewTransition,1001 child.child,1002 newName,1003 oldName,1004 className,1005 previousMeasurements,1006 stopAtNestedViewTransitions,1007 )1008 ) {1009 inViewport = true;1010 }1011 }1012 child = child.sibling;1013 }1014 return inViewport;1015}10161017export function measureUpdateViewTransition(1018 current: Fiber,1019 finishedWork: Fiber,1020 gesture: boolean,1021): boolean {1022 // If this was a gesture then which Fiber was used for the "old" vs "new" state is reversed.1023 // We still need to treat "finishedWork" as the Fiber that contains the flags for this commmit.1024 const oldFiber = gesture ? finishedWork : current;1025 const newFiber = gesture ? current : finishedWork;1026 const props: ViewTransitionProps = newFiber.memoizedProps;1027 const state: ViewTransitionState = newFiber.stateNode;1028 const newName = getViewTransitionName(props, state);1029 const oldName = getViewTransitionName(oldFiber.memoizedProps, state);1030 // Whether it ends up having been updated or relayout we apply the update class name.1031 const className: ?string = getViewTransitionClassName(1032 props.default,1033 props.update,1034 );1035 if (className === 'none') {1036 // If update is "none" then we don't have to apply a name. Since we won't animate this boundary.1037 return false;1038 }1039 // If nothing changed due to a mutation, or children changing size1040 // and the measurements end up unchanged, we should restore it to not animate.1041 let previousMeasurements: null | Array<InstanceMeasurement>;1042 if (gesture) {1043 const clones = state.clones;1044 if (clones === null) {1045 previousMeasurements = null;1046 } else {1047 previousMeasurements = clones.map(measureClonedInstance);1048 }1049 } else {1050 previousMeasurements = oldFiber.memoizedState;1051 oldFiber.memoizedState = null; // Clear it. We won't need it anymore.1052 }1053 const inViewport = measureViewTransitionHostInstances(1054 finishedWork, // This is always finishedWork since it's used to assign flags.1055 newFiber.child, // This either current or finishedWork depending on if was a gesture.1056 newName,1057 oldName,1058 className,1059 previousMeasurements,1060 true,1061 );1062 const previousCount =1063 previousMeasurements === null ? 0 : previousMeasurements.length;1064 if (viewTransitionHostInstanceIdx !== previousCount) {1065 // If we found a different number of child DOM nodes we need to assume that1066 // the parent layout may have changed as a result. This is not necessarily1067 // true if those nodes were absolutely positioned.1068 finishedWork.flags |= AffectedParentLayout;1069 }1070 return inViewport;1071}10721073export function measureNestedViewTransitions(1074 changedParent: Fiber,1075 gesture: boolean,1076): void {1077 let child = changedParent.child;1078 while (child !== null) {1079 if (child.tag === ViewTransitionComponent) {1080 const props: ViewTransitionProps = child.memoizedProps;1081 const state: ViewTransitionState = child.stateNode;1082 const name = getViewTransitionName(props, state);1083 const className: ?string = getViewTransitionClassName(1084 props.default,1085 props.update,1086 );1087 let previousMeasurements: null | Array<InstanceMeasurement>;1088 if (gesture) {1089 const clones = state.clones;1090 if (clones === null) {1091 previousMeasurements = null;1092 } else {1093 previousMeasurements = clones.map(measureClonedInstance);1094 }1095 } else {1096 previousMeasurements = child.memoizedState;1097 child.memoizedState = null; // Clear it. We won't need it anymore.1098 }1099 const inViewport = measureViewTransitionHostInstances(1100 child,1101 child.child,1102 name,1103 name, // Since this is unchanged, new and old name is the same.1104 className,1105 previousMeasurements,1106 false,1107 );1108 if ((child.flags & Update) === NoFlags || !inViewport) {1109 // Nothing changed.1110 } else {1111 if (gesture) {1112 scheduleGestureTransitionEvent(child, props.onGestureUpdate);1113 } else {1114 scheduleViewTransitionEvent(child, props.onUpdate);1115 }1116 }1117 } else if ((child.subtreeFlags & ViewTransitionStatic) !== NoFlags) {1118 measureNestedViewTransitions(child, gesture);1119 }1120 child = child.sibling;1121 }1122}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.