From 1c27c9b51c9c1ce31473d6c2a78152ceca5463f2 Mon Sep 17 00:00:00 2001 From: Bao Nguyen Date: Tue, 1 Sep 2026 16:31:31 +0700 Subject: [PATCH] fix: keep the source radius when morphBorderRadius is off Disabling the flag zeroed the overlay's borderRadius instead of leaving it alone. Because the cloned child's own radius is reset, the container is the only thing rounding the corners, so a circular avatar flew as a hard square for the whole transition. Resolve the radius range in a pure helper and hold the source radius when morphing is disabled. --- src/TransitionView.tsx | 10 ++++++++-- src/__tests__/overlayRadius.test.ts | 28 ++++++++++++++++++++++++++++ src/overlayRadius.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/overlayRadius.test.ts create mode 100644 src/overlayRadius.ts diff --git a/src/TransitionView.tsx b/src/TransitionView.tsx index f402b5c..a3e43c0 100644 --- a/src/TransitionView.tsx +++ b/src/TransitionView.tsx @@ -24,6 +24,7 @@ import type { WithTimingConfig, } from 'react-native-reanimated'; +import { resolveOverlayRadius } from './overlayRadius'; import { TransitionCoordinator } from './TransitionCoordinator'; import type { TransitionEntry } from './TransitionCoordinator'; import type { SharedTransitionEasing } from './types'; @@ -61,7 +62,10 @@ export function TransitionView({ entry }: { entry: TransitionEntry }) { const y = useSharedValue(from.y); const width = useSharedValue(from.width); const height = useSharedValue(from.height); - const radius = useSharedValue(config.morphBorderRadius ? fromRadius : 0); + const radius = useSharedValue( + resolveOverlayRadius(config.morphBorderRadius, fromRadius, entry.toRadius) + .start + ); const fade = useSharedValue(0); /** * Drives completion. It always travels 0 → 1, which matters: Reanimated's @@ -104,7 +108,9 @@ export function TransitionView({ entry }: { entry: TransitionEntry }) { y.value = animate(to.y); width.value = animate(to.width); height.value = animate(to.height); - radius.value = animate(cfg.morphBorderRadius ? toRadius : 0); + radius.value = animate( + resolveOverlayRadius(cfg.morphBorderRadius, fromRadius, toRadius).end + ); fade.value = animate(1); // Restart progress from 0 so a retarget re-runs the full settle window. diff --git a/src/__tests__/overlayRadius.test.ts b/src/__tests__/overlayRadius.test.ts new file mode 100644 index 0000000..1cca936 --- /dev/null +++ b/src/__tests__/overlayRadius.test.ts @@ -0,0 +1,28 @@ +import { resolveOverlayRadius } from '../overlayRadius'; + +describe('resolveOverlayRadius', () => { + it('morphs from the source radius to the target radius', () => { + expect(resolveOverlayRadius(true, 20, 110)).toEqual({ + start: 20, + end: 110, + }); + }); + + it('holds the source radius when morphing is disabled', () => { + expect(resolveOverlayRadius(false, 55, 12)).toEqual({ + start: 55, + end: 55, + }); + }); + + it('keeps a circular source circular for the whole flight', () => { + const { start, end } = resolveOverlayRadius(false, 32, 0); + expect(start).toBe(32); + expect(end).toBe(32); + }); + + it('leaves square endpoints square', () => { + expect(resolveOverlayRadius(false, 0, 0)).toEqual({ start: 0, end: 0 }); + expect(resolveOverlayRadius(true, 0, 0)).toEqual({ start: 0, end: 0 }); + }); +}); diff --git a/src/overlayRadius.ts b/src/overlayRadius.ts new file mode 100644 index 0000000..32361ae --- /dev/null +++ b/src/overlayRadius.ts @@ -0,0 +1,28 @@ +/** + * overlayRadius + * + * Corner radius resolution for the flying overlay. Pure and Reanimated-free + * so it can be unit tested without a renderer. + */ + +/** The overlay container's borderRadius at both ends of the flight. */ +export interface OverlayRadiusRange { + start: number; + end: number; +} + +/** + * `morphBorderRadius: false` means "do not animate the radius", not "drop it": + * the cloned child's own radius is reset, so the overlay container is the only + * thing rounding the corners and must hold the source radius for the flight. + */ +export function resolveOverlayRadius( + morphBorderRadius: boolean, + fromRadius: number, + toRadius: number +): OverlayRadiusRange { + return { + start: fromRadius, + end: morphBorderRadius ? toRadius : fromRadius, + }; +}