diff --git a/packages/react-native-renderer/src/ReactFiberConfigFabric.js b/packages/react-native-renderer/src/ReactFiberConfigFabric.js index f5a4361fd41d..c503d8820bcb 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, @@ -57,6 +58,11 @@ const { unstable_ContinuousEventPriority: FabricContinuousPriority, unstable_IdleEventPriority: FabricIdlePriority, unstable_getCurrentEventPriority: fabricGetCurrentEventPriority, + measureInstance: fabricMeasureInstance, + applyViewTransitionName: fabricApplyViewTransitionName, + startViewTransition: fabricStartViewTransition, + restoreViewTransitionName: fabricRestoreViewTransitionName, + cancelViewTransitionName: fabricCancelViewTransitionName, } = nativeFabricUIManager; import {getClosestInstanceFromNode} from './ReactFabricComponentTree'; @@ -157,13 +163,273 @@ 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'; +// ------------------- +// ViewTransition +// ------------------- + +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 {} + +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 = { + skipTransition(): void, + finished: Promise, + ready: Promise, + ... +}; + +export type ViewTransitionInstance = null | { + name: string, + ... +}; + +export type GestureTimeline = any; + +export function restoreViewTransitionName( + instance: Instance, + props: Props, +): void { + fabricRestoreViewTransitionName(instance.node); +} + +// Cancel the old and new snapshots of viewTransitionName +export function cancelViewTransitionName( + instance: Instance, + oldName: string, + props: Props, +): void { + fabricCancelViewTransitionName(instance.node, oldName); +} + +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 { + const 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 { + if (__DEV__) { + console.warn('measureClonedInstance is not implemented'); + } + return { + rect: {x: 0, y: 0, width: 0, height: 0}, + abs: false, + clip: false, + view: true, + }; +} + +export function wasInstanceInViewport( + measurement: InstanceMeasurement, +): boolean { + return measurement.view; +} + +export function hasInstanceChanged( + oldMeasurement: InstanceMeasurement, + newMeasurement: InstanceMeasurement, +): boolean { + if (__DEV__) { + console.warn('hasInstanceChanged is not implemented'); + } + return false; +} + +export function hasInstanceAffectedParent( + oldMeasurement: InstanceMeasurement, + newMeasurement: InstanceMeasurement, +): boolean { + if (__DEV__) { + console.warn('hasInstanceAffectedParent is not implemented'); + } + 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 { + 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( + transition: RunningViewTransition, + callback: () => void, +): void { + callback(); +} + +export function createViewTransitionInstance( + name: string, +): ViewTransitionInstance { + return {name}; +} + +export function getCurrentGestureOffset(timeline: GestureTimeline): number { + if (__DEV__) { + console.warn('getCurrentGestureOffset is not implemented'); + } + return 0; +} + +export function applyViewTransitionName( + instance: Instance, + name: string, + className: ?string, +): void { + // add view-transition-name to things that might animate for browser + fabricApplyViewTransitionName(instance.node, name, className); +} + +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, +): null | RunningViewTransition { + const transition = fabricStartViewTransition( + // mutation + () => { + mutationCallback(); // completeRoot should run here + layoutCallback(); + afterMutationCallback(); + }, + // onReady + () => { + spawnedWorkCallback(); + }, + // onComplete + () => { + passiveCallback(); + }, + ); + + if (transition == null) { + 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 transition; +} + export function appendInitialChild( parentInstance: Instance, child: Instance | TextInstance, 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..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 && rootViewTransitionNameCanceled) { + if ( + (supportsMutation || supportsPersistence) && + rootViewTransitionNameCanceled + ) { restoreRootViewTransitionName(finishedRoot.containerInfo); } } 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, ... };