From 75cf9c02af57fdf149c340b36329755b8f148ad7 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Thu, 5 Feb 2026 18:13:14 -0500 Subject: [PATCH 1/8] enableViewTransition for RN Summary: - turn on enableViewTransition feature - stub shim - run some mutation config fn at persistence mode too --- .../src/ReactFiberConfigFabric.js | 226 +++++++++++++++++- .../src/ReactFiberCommitViewTransitions.js | 7 +- .../src/ReactFiberCommitWork.js | 2 +- .../src/ReactFiberConfigWithNoMutation.js | 187 +++++++++++---- .../forks/ReactFeatureFlags.native-fb.js | 2 +- ...actFeatureFlags.test-renderer.native-fb.js | 2 +- 6 files changed, 371 insertions(+), 55 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index f5a4361fd41d..3c293aca3738 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -12,6 +12,7 @@ import type { TouchedViewDataAtPoint, ViewConfig, } from './ReactNativeTypes'; +import type {TransitionTypes} from 'react/src/ReactTransitionType'; import {dispatchEvent} from './ReactFabricEventEmitter'; import { NoEventPriority, @@ -72,6 +73,12 @@ import {passChildrenWhenCloningPersistedNodes} from 'shared/ReactFeatureFlags'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import type {ReactContext} from 'shared/ReactTypes'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoResources'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons'; + export {default as rendererVersion} from 'shared/ReactVersion'; // TODO: Consider exporting the react-native version. export const rendererPackageName = 'react-native-renderer'; export const extraDevToolsConfig = { @@ -157,12 +164,219 @@ if (registerEventHandler) { registerEventHandler(dispatchEvent); } -export * from 'react-reconciler/src/ReactFiberConfigWithNoMutation'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoResources'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons'; +// ------------------- +// Mutation +// ------------------- + +function shim(...args: any): empty { + throw new Error( + 'The current renderer does not support mutation. ' + + 'This error is likely caused by a bug in React. ' + + 'Please file an issue.', + ); +} + +export const supportsMutation = false; + +export const cloneMutableInstance = shim; +export const cloneMutableTextInstance = shim; +export const appendChild = shim; +export const appendChildToContainer = shim; +export const commitTextUpdate = shim; + +export function commitMount( + instance: Instance, + type: string, + newProps: Props, + internalInstanceHandle: Object, +): void { + console.log('[shim] commitMount'); +} + +export const commitUpdate = shim; +export const insertBefore = shim; +export const insertInContainerBefore = shim; +export const removeChild = shim; +export const removeChildFromContainer = shim; +export const resetTextContent = shim; +export const hideInstance = shim; +export const hideTextInstance = shim; +export const unhideInstance = shim; +export const unhideTextInstance = shim; +export const clearContainer = shim; + +export type InstanceMeasurement = { + rect: {x: number, y: number, width: number, height: number}, + abs: boolean, + clip: boolean, + view: boolean, +}; + +export type RunningViewTransition = null; + +export type ViewTransitionInstance = null | { + name: string, + ... +}; + +export type GestureTimeline = any; + +export function restoreViewTransitionName( + instance: Instance, + props: Props, +): void { + console.log('[shim] restoreViewTransitionName ', instance.canonical.nativeTag); +} + +export function cancelViewTransitionName( + instance: Instance, + oldName: string, + props: Props, +): void { + console.log('[shim] cancelViewTransitionName ', oldName, instance.canonical.nativeTag); +} + +export function cancelRootViewTransitionName(rootContainer: Container): void { + console.log('[shim] cancelRootViewTransitionName'); +} + +export function restoreRootViewTransitionName(rootContainer: Container): void { + console.log('[shim] restoreRootViewTransitionName'); +} + +export function cloneRootViewTransitionContainer( + rootContainer: Container, +): Instance { + console.log('[shim] cloneRootViewTransitionContainer'); +} + +export function removeRootViewTransitionClone( + rootContainer: Container, + clone: Instance, +): void { + console.log('[shim] removeRootViewTransitionClone'); +} + +export function measureInstance(instance: Instance): InstanceMeasurement { + console.log('[shim] measureInstance ', instance.canonical.nativeTag); + return {rect: {x: 0, y: 0, width: 0, height: 0}, abs: false, clip: false, view: true}; +} + +export function measureClonedInstance(instance: Instance): InstanceMeasurement { + console.log('[shim] measureClonedInstance ', instance.canonical.nativeTag); + return {rect: {x: 0, y: 0, width: 0, height: 0}, abs: false, clip: false, view: true}; +} + +export function wasInstanceInViewport( + measurement: InstanceMeasurement, +): boolean { + console.log('[shim] wasInstanceInViewport'); + return measurement.view; +} + +export function hasInstanceChanged( + oldMeasurement: InstanceMeasurement, + newMeasurement: InstanceMeasurement, +): boolean { + console.log('[shim] hasInstanceChanged'); + return false; +} + +export function hasInstanceAffectedParent( + oldMeasurement: InstanceMeasurement, + newMeasurement: InstanceMeasurement, +): boolean { + console.log('[shim] hasInstanceAffectedParent'); + return false; +} + +export function startGestureTransition( + suspendedState: null | SuspendedState, + rootContainer: Container, + timeline: GestureTimeline, + rangeStart: number, + rangeEnd: number, + transitionTypes: null | TransitionTypes, + mutationCallback: () => void, + animateCallback: () => void, + errorCallback: (error: mixed) => void, + finishedAnimation: () => void, +): RunningViewTransition { + console.log('[shim] startGestureTransition'); + return null; +} + +export function stopViewTransition(transition: RunningViewTransition): void { + console.log('[shim] stopViewTransition'); +} + +export function addViewTransitionFinishedListener( + transition: RunningViewTransition, + callback: () => void, +): void { + console.log('[shim] addViewTransitionFinishedListener'); + callback(); +} + +export function createViewTransitionInstance( + name: string, +): ViewTransitionInstance { + console.log('[shim] createViewTransitionInstance', name); + return {name}; +} + +export function getCurrentGestureOffset(timeline: GestureTimeline): number { + console.log('[shim] getCurrentGestureOffset'); + return 0; +} + +export function applyViewTransitionName( + instance: Instance, + name: string, + className: ?string, +): void { + // add view-transition-name to things that might animate for browser + console.log('[shim] applyViewTransitionName', name, className, instance.canonical.nativeTag); +} + +export function startViewTransition( + suspendedState: null | SuspendedState, + rootContainer: Container, + transitionTypes: null | TransitionTypes, + mutationCallback: () => void, + layoutCallback: () => void, + afterMutationCallback: () => void, + spawnedWorkCallback: () => void, + passiveCallback: () => mixed, + errorCallback: (error: mixed) => void, + blockedCallback: (name: string) => void, + finishedAnimation: () => void, +): RunningViewTransition { + console.log('[shim] startViewTransition transitionTypes', JSON.stringify(transitionTypes ?? [])); + + // mutation phase + console.log('[shim] startViewTransition mutations start'); + mutationCallback(); + layoutCallback(); // run layout effects + afterMutationCallback(); + console.log('[shim] startViewTransition mutations finish'); + + // browser creates pseudo elements + console.log('[shim] startViewTransition pseudo element captured'); + + // transition ready + console.log('[shim] startViewTransition transition ready'); + spawnedWorkCallback(); + + console.log('[shim] startViewTransition finishedAnimation'); + // finishedAnimation(); + + // transition ends + console.log('[shim] startViewTransition transition ends'); + passiveCallback(); + + return null; +} export function appendInitialChild( parentInstance: Instance, diff --git a/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js index a9edc0c84d23..5431585185dc 100644 --- a/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js +++ b/packages/react-reconciler/src/ReactFiberCommitViewTransitions.js @@ -26,6 +26,7 @@ import { } from './ReactFiberFlags'; import { supportsMutation, + supportsPersistence, applyViewTransitionName, restoreViewTransitionName, measureInstance, @@ -139,7 +140,7 @@ function applyViewTransitionToHostInstancesRecursive( collectMeasurements: null | Array, stopAtNestedViewTransitions: boolean, ): boolean { - if (!supportsMutation) { + if (!supportsMutation && !supportsPersistence) { return false; } let inViewport = false; @@ -201,7 +202,7 @@ function restoreViewTransitionOnHostInstances( child: null | Fiber, stopAtNestedViewTransitions: boolean, ): void { - if (!supportsMutation) { + if (!supportsMutation && !supportsPersistence) { return; } while (child !== null) { @@ -648,7 +649,7 @@ function measureViewTransitionHostInstancesRecursive( previousMeasurements: null | Array, stopAtNestedViewTransitions: boolean, ): boolean { - if (!supportsMutation) { + if (!supportsMutation && !supportsPersistence) { return true; } let inViewport = false; diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 08882a04766c..4b4531c863e1 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -3709,7 +3709,7 @@ function commitPassiveMountOnFiber( } if (isViewTransitionEligible) { - if (supportsMutation && rootViewTransitionNameCanceled) { + if ((supportsMutation || supportsPersistence) && rootViewTransitionNameCanceled) { restoreRootViewTransitionName(finishedRoot.containerInfo); } } diff --git a/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js b/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js index 79cf3990a728..8768ea612e41 100644 --- a/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js +++ b/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js @@ -10,52 +10,153 @@ // Renderers that don't support mutation // can re-export everything from this module. -function shim(...args: any): empty { - throw new Error( - 'The current renderer does not support mutation. ' + - 'This error is likely caused by a bug in React. ' + - 'Please file an issue.', - ); -} - // Mutation (when unsupported) export const supportsMutation = false; -export const cloneMutableInstance = shim; -export const cloneMutableTextInstance = shim; -export const appendChild = shim; -export const appendChildToContainer = shim; -export const commitTextUpdate = shim; -export const commitMount = shim; -export const commitUpdate = shim; -export const insertBefore = shim; -export const insertInContainerBefore = shim; -export const removeChild = shim; -export const removeChildFromContainer = shim; -export const resetTextContent = shim; -export const hideInstance = shim; -export const hideTextInstance = shim; -export const unhideInstance = shim; -export const unhideTextInstance = shim; -export const clearContainer = shim; -export const applyViewTransitionName = shim; -export const restoreViewTransitionName = shim; -export const cancelViewTransitionName = shim; -export const cancelRootViewTransitionName = shim; -export const restoreRootViewTransitionName = shim; -export const cloneRootViewTransitionContainer = shim; -export const removeRootViewTransitionClone = shim; + +export function cloneMutableInstance(...args: any): any { + console.log('[shim] cloneMutableInstance'); +} + +export function cloneMutableTextInstance(...args: any): any { + console.log('[shim] cloneMutableTextInstance'); +} + +export function appendChild(...args: any): any { + console.log('[shim] appendChild'); +} + +export function appendChildToContainer(...args: any): any { + console.log('[shim] appendChildToContainer'); +} + +export function commitTextUpdate(...args: any): any { + console.log('[shim] commitTextUpdate'); +} + +export function commitMount(...args: any): any { + console.log('[shim] commitMount'); +} + +export function commitUpdate(...args: any): any { + console.log('[shim] commitUpdate'); +} + +export function insertBefore(...args: any): any { + console.log('[shim] insertBefore'); +} + +export function insertInContainerBefore(...args: any): any { + console.log('[shim] insertInContainerBefore'); +} + +export function removeChild(...args: any): any { + console.log('[shim] removeChild'); +} + +export function removeChildFromContainer(...args: any): any { + console.log('[shim] removeChildFromContainer'); +} + +export function resetTextContent(...args: any): any { + console.log('[shim] resetTextContent'); +} + +export function hideInstance(...args: any): any { + console.log('[shim] hideInstance'); +} + +export function hideTextInstance(...args: any): any { + console.log('[shim] hideTextInstance'); +} + +export function unhideInstance(...args: any): any { + console.log('[shim] unhideInstance'); +} + +export function unhideTextInstance(...args: any): any { + console.log('[shim] unhideTextInstance'); +} + +export function clearContainer(...args: any): any { + console.log('[shim] clearContainer'); +} + +export function applyViewTransitionName(...args: any): any { + console.log('[shim] applyViewTransitionName'); +} + +export function restoreViewTransitionName(...args: any): any { + console.log('[shim] restoreViewTransitionName'); +} + +export function cancelViewTransitionName(...args: any): any { + console.log('[shim] cancelViewTransitionName'); +} + +export function cancelRootViewTransitionName(...args: any): any { + console.log('[shim] cancelRootViewTransitionName'); +} + +export function restoreRootViewTransitionName(...args: any): any { + console.log('[shim] restoreRootViewTransitionName'); +} + +export function cloneRootViewTransitionContainer(...args: any): any { + console.log('[shim] cloneRootViewTransitionContainer'); +} + +export function removeRootViewTransitionClone(...args: any): any { + console.log('[shim] removeRootViewTransitionClone'); +} + export type InstanceMeasurement = null; -export const measureInstance = shim; -export const measureClonedInstance = shim; -export const wasInstanceInViewport = shim; -export const hasInstanceChanged = shim; -export const hasInstanceAffectedParent = shim; -export const startViewTransition = shim; + +export function measureInstance(...args: any): any { + console.log('[shim] measureInstance'); +} + +export function measureClonedInstance(...args: any): any { + console.log('[shim] measureClonedInstance'); +} + +export function wasInstanceInViewport(...args: any): any { + console.log('[shim] wasInstanceInViewport'); +} + +export function hasInstanceChanged(...args: any): any { + console.log('[shim] hasInstanceChanged'); +} + +export function hasInstanceAffectedParent(...args: any): any { + console.log('[shim] hasInstanceAffectedParent'); +} + +export function startViewTransition(...args: any): any { + console.log('[shim] startViewTransition'); +} + export type RunningViewTransition = null; -export const startGestureTransition = shim; -export const stopViewTransition = shim; -export const addViewTransitionFinishedListener = shim; + +export function startGestureTransition(...args: any): any { + console.log('[shim] startGestureTransition'); +} + +export function stopViewTransition(...args: any): any { + console.log('[shim] stopViewTransition'); +} + +export function addViewTransitionFinishedListener(...args: any): any { + console.log('[shim] addViewTransitionFinishedListener'); +} + export type ViewTransitionInstance = null | {name: string, ...}; -export const createViewTransitionInstance = shim; + +export function createViewTransitionInstance(...args: any): any { + console.log('[shim] createViewTransitionInstance'); +} + export type GestureTimeline = any; -export const getCurrentGestureOffset = shim; + +export function getCurrentGestureOffset(...args: any): any { + console.log('[shim] getCurrentGestureOffset'); +} diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index eeabe40d33bb..6e57ad63b801 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -70,7 +70,7 @@ export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; export const enableYieldingBeforePassive: boolean = false; export const enableThrottledScheduling: boolean = false; -export const enableViewTransition: boolean = false; +export const enableViewTransition: boolean = true; export const enableGestureTransition: boolean = false; export const enableScrollEndPolyfill: boolean = true; export const enableSuspenseyImages: boolean = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index a2e623ff5201..268b53cf90bd 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -56,7 +56,7 @@ export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; -export const enableViewTransition = false; +export const enableViewTransition = true; export const enableGestureTransition = false; export const enableScrollEndPolyfill = true; export const enableSuspenseyImages = false; From 02754699bea1ba7dcf0bfc2a09088fc040163f90 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Fri, 6 Feb 2026 16:07:31 -0500 Subject: [PATCH 2/8] invoke FabricUIManager methods --- .../src/ReactFiberConfigFabric.js | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 3c293aca3738..c44984248452 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -58,6 +58,10 @@ const { unstable_ContinuousEventPriority: FabricContinuousPriority, unstable_IdleEventPriority: FabricIdlePriority, unstable_getCurrentEventPriority: fabricGetCurrentEventPriority, + measureInstance: fabricMeasureInstance, + applyViewTransitionName: fabricApplyViewTransitionName, + executeViewTransition: fabricExecuteViewTransition, + startViewTransition: fabricStartViewTransition, } = nativeFabricUIManager; import {getClosestInstanceFromNode} from './ReactFabricComponentTree'; @@ -259,7 +263,18 @@ export function removeRootViewTransitionClone( export function measureInstance(instance: Instance): InstanceMeasurement { console.log('[shim] measureInstance ', instance.canonical.nativeTag); - return {rect: {x: 0, y: 0, width: 0, height: 0}, abs: false, clip: false, view: true}; + var measurement = fabricMeasureInstance(instance.node); + return { + rect: { + x: measurement.x, + y: measurement.y, + width: measurement.width, + height: measurement.height + }, + abs: false, + clip: false, + view: true + }; } export function measureClonedInstance(instance: Instance): InstanceMeasurement { @@ -337,6 +352,7 @@ export function applyViewTransitionName( ): void { // add view-transition-name to things that might animate for browser console.log('[shim] applyViewTransitionName', name, className, instance.canonical.nativeTag); + fabricApplyViewTransitionName(instance.node, name, className); } export function startViewTransition( @@ -353,6 +369,7 @@ export function startViewTransition( finishedAnimation: () => void, ): RunningViewTransition { console.log('[shim] startViewTransition transitionTypes', JSON.stringify(transitionTypes ?? [])); + fabricStartViewTransition(); // mutation phase console.log('[shim] startViewTransition mutations start'); @@ -367,6 +384,7 @@ export function startViewTransition( // transition ready console.log('[shim] startViewTransition transition ready'); spawnedWorkCallback(); + fabricExecuteViewTransition(); console.log('[shim] startViewTransition finishedAnimation'); // finishedAnimation(); From 7d45a6f335bbcbf895fc1668ae7acc14a6301a51 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Fri, 6 Feb 2026 17:42:16 -0500 Subject: [PATCH 3/8] Rename fabricStartViewTransition to fabricViewTransitionStarted Better reflects that this signals the transition has started rather than initiating it. --- .../src/ReactFiberConfigFabric.js | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index c44984248452..52b4f1216554 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -60,7 +60,6 @@ const { unstable_getCurrentEventPriority: fabricGetCurrentEventPriority, measureInstance: fabricMeasureInstance, applyViewTransitionName: fabricApplyViewTransitionName, - executeViewTransition: fabricExecuteViewTransition, startViewTransition: fabricStartViewTransition, } = nativeFabricUIManager; @@ -368,30 +367,33 @@ export function startViewTransition( blockedCallback: (name: string) => void, finishedAnimation: () => void, ): RunningViewTransition { - console.log('[shim] startViewTransition transitionTypes', JSON.stringify(transitionTypes ?? [])); - fabricStartViewTransition(); - - // mutation phase - console.log('[shim] startViewTransition mutations start'); - mutationCallback(); - layoutCallback(); // run layout effects - afterMutationCallback(); - console.log('[shim] startViewTransition mutations finish'); - - // browser creates pseudo elements - console.log('[shim] startViewTransition pseudo element captured'); - - // transition ready - console.log('[shim] startViewTransition transition ready'); - spawnedWorkCallback(); - fabricExecuteViewTransition(); - - console.log('[shim] startViewTransition finishedAnimation'); - // finishedAnimation(); + console.log( + "[shim] startViewTransition transitionTypes", + JSON.stringify(null != transitionTypes ? transitionTypes : []) + ); - // transition ends - console.log('[shim] startViewTransition transition ends'); - passiveCallback(); + fabricStartViewTransition( + // mutation + ()=>{ + console.log("[shim] startViewTransition mutations start"); + // completeRoot should run here + mutationCallback(); + layoutCallback(); + afterMutationCallback(); + console.log("[shim] startViewTransition mutations finish"); + }, + // onReady + ()=>{ + console.log("[shim] startViewTransition pseudo element captured"); + console.log("[shim] startViewTransition transition ready"); + spawnedWorkCallback(); + }, + // onComplete + ()=>{ + console.log("[shim] startViewTransition finishedAnimation"); + console.log("[shim] startViewTransition transition ends"); + passiveCallback(); + }); return null; } @@ -789,6 +791,7 @@ export function replaceContainerChildren( container: Container, newChildren: ChildSet, ): void { + console.log('completeRoot'); completeRoot(container.containerTag, newChildren); } From a8e8bccc891dcaf726115a450c84fef5277925bd Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Wed, 11 Feb 2026 11:59:03 -0500 Subject: [PATCH 4/8] restore/cancelViewTransitionName; fallback behavior if fabric doesnt enable startViewTransition --- .../src/ReactFiberConfigFabric.js | 77 ++++---- .../src/ReactFiberConfigWithNoMutation.js | 187 ++++-------------- 2 files changed, 78 insertions(+), 186 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 52b4f1216554..b1e35698cb5c 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -61,6 +61,8 @@ const { measureInstance: fabricMeasureInstance, applyViewTransitionName: fabricApplyViewTransitionName, startViewTransition: fabricStartViewTransition, + restoreViewTransitionName: fabricRestoreViewTransitionName, + cancelViewTransitionName: fabricCancelViewTransitionName, } = nativeFabricUIManager; import {getClosestInstanceFromNode} from './ReactFabricComponentTree'; @@ -76,12 +78,6 @@ import {passChildrenWhenCloningPersistedNodes} from 'shared/ReactFeatureFlags'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; import type {ReactContext} from 'shared/ReactTypes'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoResources'; -export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons'; - export {default as rendererVersion} from 'shared/ReactVersion'; // TODO: Consider exporting the react-native version. export const rendererPackageName = 'react-native-renderer'; export const extraDevToolsConfig = { @@ -167,8 +163,14 @@ if (registerEventHandler) { registerEventHandler(dispatchEvent); } +export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoResources'; +export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons'; + // ------------------- -// Mutation +// ViewTransition // ------------------- function shim(...args: any): empty { @@ -192,9 +194,7 @@ export function commitMount( type: string, newProps: Props, internalInstanceHandle: Object, -): void { - console.log('[shim] commitMount'); -} +): void {} export const commitUpdate = shim; export const insertBefore = shim; @@ -228,41 +228,41 @@ export function restoreViewTransitionName( instance: Instance, props: Props, ): void { - console.log('[shim] restoreViewTransitionName ', instance.canonical.nativeTag); + fabricRestoreViewTransitionName(instance.node); } +// Cancel the old and new snapshots of viewTransitionName export function cancelViewTransitionName( instance: Instance, oldName: string, props: Props, ): void { - console.log('[shim] cancelViewTransitionName ', oldName, instance.canonical.nativeTag); + fabricCancelViewTransitionName(instance.node, oldName); } export function cancelRootViewTransitionName(rootContainer: Container): void { - console.log('[shim] cancelRootViewTransitionName'); + } export function restoreRootViewTransitionName(rootContainer: Container): void { - console.log('[shim] restoreRootViewTransitionName'); + } export function cloneRootViewTransitionContainer( rootContainer: Container, ): Instance { - console.log('[shim] cloneRootViewTransitionContainer'); } export function removeRootViewTransitionClone( rootContainer: Container, clone: Instance, ): void { - console.log('[shim] removeRootViewTransitionClone'); + } export function measureInstance(instance: Instance): InstanceMeasurement { - console.log('[shim] measureInstance ', instance.canonical.nativeTag); - var measurement = fabricMeasureInstance(instance.node); + + const measurement = fabricMeasureInstance(instance.node); return { rect: { x: measurement.x, @@ -277,14 +277,12 @@ export function measureInstance(instance: Instance): InstanceMeasurement { } export function measureClonedInstance(instance: Instance): InstanceMeasurement { - console.log('[shim] measureClonedInstance ', instance.canonical.nativeTag); return {rect: {x: 0, y: 0, width: 0, height: 0}, abs: false, clip: false, view: true}; } export function wasInstanceInViewport( measurement: InstanceMeasurement, ): boolean { - console.log('[shim] wasInstanceInViewport'); return measurement.view; } @@ -292,7 +290,6 @@ export function hasInstanceChanged( oldMeasurement: InstanceMeasurement, newMeasurement: InstanceMeasurement, ): boolean { - console.log('[shim] hasInstanceChanged'); return false; } @@ -300,7 +297,6 @@ export function hasInstanceAffectedParent( oldMeasurement: InstanceMeasurement, newMeasurement: InstanceMeasurement, ): boolean { - console.log('[shim] hasInstanceAffectedParent'); return false; } @@ -316,31 +312,26 @@ export function startGestureTransition( errorCallback: (error: mixed) => void, finishedAnimation: () => void, ): RunningViewTransition { - console.log('[shim] startGestureTransition'); return null; } export function stopViewTransition(transition: RunningViewTransition): void { - console.log('[shim] stopViewTransition'); } export function addViewTransitionFinishedListener( transition: RunningViewTransition, callback: () => void, ): void { - console.log('[shim] addViewTransitionFinishedListener'); callback(); } export function createViewTransitionInstance( name: string, ): ViewTransitionInstance { - console.log('[shim] createViewTransitionInstance', name); return {name}; } export function getCurrentGestureOffset(timeline: GestureTimeline): number { - console.log('[shim] getCurrentGestureOffset'); return 0; } @@ -350,7 +341,6 @@ export function applyViewTransitionName( className: ?string, ): void { // add view-transition-name to things that might animate for browser - console.log('[shim] applyViewTransitionName', name, className, instance.canonical.nativeTag); fabricApplyViewTransitionName(instance.node, name, className); } @@ -367,34 +357,38 @@ export function startViewTransition( blockedCallback: (name: string) => void, finishedAnimation: () => void, ): RunningViewTransition { - console.log( - "[shim] startViewTransition transitionTypes", - JSON.stringify(null != transitionTypes ? transitionTypes : []) - ); - fabricStartViewTransition( + const startedTransition = fabricStartViewTransition( // mutation ()=>{ - console.log("[shim] startViewTransition mutations start"); - // completeRoot should run here - mutationCallback(); + mutationCallback(); // completeRoot should run here layoutCallback(); afterMutationCallback(); - console.log("[shim] startViewTransition mutations finish"); }, // onReady ()=>{ - console.log("[shim] startViewTransition pseudo element captured"); - console.log("[shim] startViewTransition transition ready"); spawnedWorkCallback(); }, // onComplete ()=>{ - console.log("[shim] startViewTransition finishedAnimation"); - console.log("[shim] startViewTransition transition ends"); passiveCallback(); }); + if (!startedTransition) { + if (__DEV__) { + console.warn( + "startViewTransition didn't kick off transition in Fabric, the ViewTransition ReactNativeFeatureFlag might not be enabled.", + ); + } + // Flush remaining work synchronously. + mutationCallback(); + layoutCallback(); + // Skip afterMutationCallback(). We don't need it since we're not animating. + spawnedWorkCallback(); + // Skip passiveCallback(). Spawned work will schedule a task. + return null; + } + return null; } @@ -791,7 +785,6 @@ export function replaceContainerChildren( container: Container, newChildren: ChildSet, ): void { - console.log('completeRoot'); completeRoot(container.containerTag, newChildren); } diff --git a/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js b/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js index 8768ea612e41..79cf3990a728 100644 --- a/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js +++ b/packages/react-reconciler/src/ReactFiberConfigWithNoMutation.js @@ -10,153 +10,52 @@ // Renderers that don't support mutation // can re-export everything from this module. -// Mutation (when unsupported) -export const supportsMutation = false; - -export function cloneMutableInstance(...args: any): any { - console.log('[shim] cloneMutableInstance'); -} - -export function cloneMutableTextInstance(...args: any): any { - console.log('[shim] cloneMutableTextInstance'); -} - -export function appendChild(...args: any): any { - console.log('[shim] appendChild'); -} - -export function appendChildToContainer(...args: any): any { - console.log('[shim] appendChildToContainer'); -} - -export function commitTextUpdate(...args: any): any { - console.log('[shim] commitTextUpdate'); -} - -export function commitMount(...args: any): any { - console.log('[shim] commitMount'); -} - -export function commitUpdate(...args: any): any { - console.log('[shim] commitUpdate'); -} - -export function insertBefore(...args: any): any { - console.log('[shim] insertBefore'); -} - -export function insertInContainerBefore(...args: any): any { - console.log('[shim] insertInContainerBefore'); -} - -export function removeChild(...args: any): any { - console.log('[shim] removeChild'); -} - -export function removeChildFromContainer(...args: any): any { - console.log('[shim] removeChildFromContainer'); -} - -export function resetTextContent(...args: any): any { - console.log('[shim] resetTextContent'); -} - -export function hideInstance(...args: any): any { - console.log('[shim] hideInstance'); -} - -export function hideTextInstance(...args: any): any { - console.log('[shim] hideTextInstance'); -} - -export function unhideInstance(...args: any): any { - console.log('[shim] unhideInstance'); -} - -export function unhideTextInstance(...args: any): any { - console.log('[shim] unhideTextInstance'); -} - -export function clearContainer(...args: any): any { - console.log('[shim] clearContainer'); -} - -export function applyViewTransitionName(...args: any): any { - console.log('[shim] applyViewTransitionName'); -} - -export function restoreViewTransitionName(...args: any): any { - console.log('[shim] restoreViewTransitionName'); -} - -export function cancelViewTransitionName(...args: any): any { - console.log('[shim] cancelViewTransitionName'); -} - -export function cancelRootViewTransitionName(...args: any): any { - console.log('[shim] cancelRootViewTransitionName'); -} - -export function restoreRootViewTransitionName(...args: any): any { - console.log('[shim] restoreRootViewTransitionName'); -} - -export function cloneRootViewTransitionContainer(...args: any): any { - console.log('[shim] cloneRootViewTransitionContainer'); -} - -export function removeRootViewTransitionClone(...args: any): any { - console.log('[shim] removeRootViewTransitionClone'); +function shim(...args: any): empty { + throw new Error( + 'The current renderer does not support mutation. ' + + 'This error is likely caused by a bug in React. ' + + 'Please file an issue.', + ); } +// Mutation (when unsupported) +export const supportsMutation = false; +export const cloneMutableInstance = shim; +export const cloneMutableTextInstance = shim; +export const appendChild = shim; +export const appendChildToContainer = shim; +export const commitTextUpdate = shim; +export const commitMount = shim; +export const commitUpdate = shim; +export const insertBefore = shim; +export const insertInContainerBefore = shim; +export const removeChild = shim; +export const removeChildFromContainer = shim; +export const resetTextContent = shim; +export const hideInstance = shim; +export const hideTextInstance = shim; +export const unhideInstance = shim; +export const unhideTextInstance = shim; +export const clearContainer = shim; +export const applyViewTransitionName = shim; +export const restoreViewTransitionName = shim; +export const cancelViewTransitionName = shim; +export const cancelRootViewTransitionName = shim; +export const restoreRootViewTransitionName = shim; +export const cloneRootViewTransitionContainer = shim; +export const removeRootViewTransitionClone = shim; export type InstanceMeasurement = null; - -export function measureInstance(...args: any): any { - console.log('[shim] measureInstance'); -} - -export function measureClonedInstance(...args: any): any { - console.log('[shim] measureClonedInstance'); -} - -export function wasInstanceInViewport(...args: any): any { - console.log('[shim] wasInstanceInViewport'); -} - -export function hasInstanceChanged(...args: any): any { - console.log('[shim] hasInstanceChanged'); -} - -export function hasInstanceAffectedParent(...args: any): any { - console.log('[shim] hasInstanceAffectedParent'); -} - -export function startViewTransition(...args: any): any { - console.log('[shim] startViewTransition'); -} - +export const measureInstance = shim; +export const measureClonedInstance = shim; +export const wasInstanceInViewport = shim; +export const hasInstanceChanged = shim; +export const hasInstanceAffectedParent = shim; +export const startViewTransition = shim; export type RunningViewTransition = null; - -export function startGestureTransition(...args: any): any { - console.log('[shim] startGestureTransition'); -} - -export function stopViewTransition(...args: any): any { - console.log('[shim] stopViewTransition'); -} - -export function addViewTransitionFinishedListener(...args: any): any { - console.log('[shim] addViewTransitionFinishedListener'); -} - +export const startGestureTransition = shim; +export const stopViewTransition = shim; +export const addViewTransitionFinishedListener = shim; export type ViewTransitionInstance = null | {name: string, ...}; - -export function createViewTransitionInstance(...args: any): any { - console.log('[shim] createViewTransitionInstance'); -} - +export const createViewTransitionInstance = shim; export type GestureTimeline = any; - -export function getCurrentGestureOffset(...args: any): any { - console.log('[shim] getCurrentGestureOffset'); -} +export const getCurrentGestureOffset = shim; From 15734d064c6cee125c96805c25789db6a64bdbe1 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Wed, 11 Feb 2026 12:06:37 -0500 Subject: [PATCH 5/8] fix flow --- .../src/ReactFiberConfigFabric.js | 35 +++++++++++++++++-- scripts/flow/react-native-host-hooks.js | 18 ++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index b1e35698cb5c..8162ed80fa7c 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -241,23 +241,34 @@ export function cancelViewTransitionName( } export function cancelRootViewTransitionName(rootContainer: Container): void { - + if (__DEV__) { + console.warn('cancelRootViewTransitionName is not implemented'); + } } export function restoreRootViewTransitionName(rootContainer: Container): void { - + if (__DEV__) { + console.warn('restoreRootViewTransitionName is not implemented'); + } } export function cloneRootViewTransitionContainer( rootContainer: Container, ): Instance { + if (__DEV__) { + console.warn('cloneRootViewTransitionContainer is not implemented'); + } + // $FlowFixMe[incompatible-return] Return empty stub + return null; } export function removeRootViewTransitionClone( rootContainer: Container, clone: Instance, ): void { - + if (__DEV__) { + console.warn('removeRootViewTransitionClone is not implemented'); + } } export function measureInstance(instance: Instance): InstanceMeasurement { @@ -277,6 +288,9 @@ export function measureInstance(instance: Instance): InstanceMeasurement { } export function measureClonedInstance(instance: Instance): InstanceMeasurement { + if (__DEV__) { + console.warn('measureClonedInstance is not implemented'); + } return {rect: {x: 0, y: 0, width: 0, height: 0}, abs: false, clip: false, view: true}; } @@ -290,6 +304,9 @@ export function hasInstanceChanged( oldMeasurement: InstanceMeasurement, newMeasurement: InstanceMeasurement, ): boolean { + if (__DEV__) { + console.warn('hasInstanceChanged is not implemented'); + } return false; } @@ -297,6 +314,9 @@ export function hasInstanceAffectedParent( oldMeasurement: InstanceMeasurement, newMeasurement: InstanceMeasurement, ): boolean { + if (__DEV__) { + console.warn('hasInstanceAffectedParent is not implemented'); + } return false; } @@ -312,10 +332,16 @@ export function startGestureTransition( errorCallback: (error: mixed) => void, finishedAnimation: () => void, ): RunningViewTransition { + if (__DEV__) { + console.warn('startGestureTransition is not implemented'); + } return null; } export function stopViewTransition(transition: RunningViewTransition): void { + if (__DEV__) { + console.warn('stopViewTransition is not implemented'); + } } export function addViewTransitionFinishedListener( @@ -332,6 +358,9 @@ export function createViewTransitionInstance( } export function getCurrentGestureOffset(timeline: GestureTimeline): number { + if (__DEV__) { + console.warn('getCurrentGestureOffset is not implemented'); + } return 0; } diff --git a/scripts/flow/react-native-host-hooks.js b/scripts/flow/react-native-host-hooks.js index 227c78bca24a..db8bbd9efaee 100644 --- a/scripts/flow/react-native-host-hooks.js +++ b/scripts/flow/react-native-host-hooks.js @@ -301,5 +301,23 @@ declare const nativeFabricUIManager: { unstable_ContinuousEventPriority: number, unstable_IdleEventPriority: number, unstable_getCurrentEventPriority: () => number, + measureInstance: (node: Object) => { + x: number, + y: number, + width: number, + height: number, + }, + applyViewTransitionName: ( + node: Object, + name: string, + className: ?string, + ) => void, + startViewTransition: ( + mutationCallback: () => void, + onReady: () => void, + onComplete: () => void, + ) => boolean, + restoreViewTransitionName: (node: Object) => void, + cancelViewTransitionName: (node: Object, oldName: string) => void, ... }; From d6e22c790c9ed0bfc77aba1397e5b57bf0557d0f Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Wed, 11 Feb 2026 12:14:35 -0500 Subject: [PATCH 6/8] turn off enableViewTransition for fb rn renderer by default --- packages/shared/forks/ReactFeatureFlags.native-fb.js | 2 +- .../shared/forks/ReactFeatureFlags.test-renderer.native-fb.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 6e57ad63b801..eeabe40d33bb 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -70,7 +70,7 @@ export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; export const enableYieldingBeforePassive: boolean = false; export const enableThrottledScheduling: boolean = false; -export const enableViewTransition: boolean = true; +export const enableViewTransition: boolean = false; export const enableGestureTransition: boolean = false; export const enableScrollEndPolyfill: boolean = true; export const enableSuspenseyImages: boolean = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js index 268b53cf90bd..a2e623ff5201 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js @@ -56,7 +56,7 @@ export const syncLaneExpirationMs = 250; export const transitionLaneExpirationMs = 5000; export const enableYieldingBeforePassive = false; export const enableThrottledScheduling = false; -export const enableViewTransition = true; +export const enableViewTransition = false; export const enableGestureTransition = false; export const enableScrollEndPolyfill = true; export const enableSuspenseyImages = false; From c1d82d87bedd9d9a96acb44b32fcf499de2eb1b8 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Wed, 11 Feb 2026 12:21:29 -0500 Subject: [PATCH 7/8] prettier --- .../src/ReactFiberConfigFabric.js | 42 ++++++++++--------- .../src/ReactFiberCommitWork.js | 5 ++- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index 8162ed80fa7c..e9ecb5bf3321 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -272,18 +272,17 @@ export function removeRootViewTransitionClone( } export function measureInstance(instance: Instance): InstanceMeasurement { - const measurement = fabricMeasureInstance(instance.node); return { rect: { x: measurement.x, y: measurement.y, width: measurement.width, - height: measurement.height + height: measurement.height, }, abs: false, clip: false, - view: true + view: true, }; } @@ -291,7 +290,12 @@ export function measureClonedInstance(instance: Instance): InstanceMeasurement { if (__DEV__) { console.warn('measureClonedInstance is not implemented'); } - return {rect: {x: 0, y: 0, width: 0, height: 0}, abs: false, clip: false, view: true}; + return { + rect: {x: 0, y: 0, width: 0, height: 0}, + abs: false, + clip: false, + view: true, + }; } export function wasInstanceInViewport( @@ -386,22 +390,22 @@ export function startViewTransition( blockedCallback: (name: string) => void, finishedAnimation: () => void, ): RunningViewTransition { - const startedTransition = fabricStartViewTransition( // mutation - ()=>{ - mutationCallback(); // completeRoot should run here - layoutCallback(); - afterMutationCallback(); - }, - // onReady - ()=>{ - spawnedWorkCallback(); - }, - // onComplete - ()=>{ - passiveCallback(); - }); + () => { + mutationCallback(); // completeRoot should run here + layoutCallback(); + afterMutationCallback(); + }, + // onReady + () => { + spawnedWorkCallback(); + }, + // onComplete + () => { + passiveCallback(); + }, + ); if (!startedTransition) { if (__DEV__) { @@ -409,7 +413,7 @@ export function startViewTransition( "startViewTransition didn't kick off transition in Fabric, the ViewTransition ReactNativeFeatureFlag might not be enabled.", ); } - // Flush remaining work synchronously. + // Flush remaining work synchronously. mutationCallback(); layoutCallback(); // Skip afterMutationCallback(). We don't need it since we're not animating. diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 4b4531c863e1..250b23f97057 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -3709,7 +3709,10 @@ function commitPassiveMountOnFiber( } if (isViewTransitionEligible) { - if ((supportsMutation || supportsPersistence) && rootViewTransitionNameCanceled) { + if ( + (supportsMutation || supportsPersistence) && + rootViewTransitionNameCanceled + ) { restoreRootViewTransitionName(finishedRoot.containerInfo); } } From 86487822aea776b455ac9e6bf199096ea2772b63 Mon Sep 17 00:00:00 2001 From: Zeya Peng Date: Fri, 13 Feb 2026 12:32:19 -0500 Subject: [PATCH 8/8] make startViewTransition async and return ready & finished promises --- .../src/ReactFiberConfigFabric.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index e9ecb5bf3321..c503d8820bcb 100644 --- a/packages/react-native-renderer/src/ReactFiberConfigFabric.js +++ b/packages/react-native-renderer/src/ReactFiberConfigFabric.js @@ -215,7 +215,12 @@ export type InstanceMeasurement = { view: boolean, }; -export type RunningViewTransition = null; +export type RunningViewTransition = { + skipTransition(): void, + finished: Promise, + ready: Promise, + ... +}; export type ViewTransitionInstance = null | { name: string, @@ -389,8 +394,8 @@ export function startViewTransition( errorCallback: (error: mixed) => void, blockedCallback: (name: string) => void, finishedAnimation: () => void, -): RunningViewTransition { - const startedTransition = fabricStartViewTransition( +): null | RunningViewTransition { + const transition = fabricStartViewTransition( // mutation () => { mutationCallback(); // completeRoot should run here @@ -407,7 +412,7 @@ export function startViewTransition( }, ); - if (!startedTransition) { + if (transition == null) { if (__DEV__) { console.warn( "startViewTransition didn't kick off transition in Fabric, the ViewTransition ReactNativeFeatureFlag might not be enabled.", @@ -422,7 +427,7 @@ export function startViewTransition( return null; } - return null; + return transition; } export function appendInitialChild(