Files
bsky-social-app/patches/react-native-drawer-layout@4.2.3.patch
Claude 810d167426 Fix drawer animation snapping on Reanimated 4
Reanimated 4 (New Architecture, #10980) rewrote spring termination: with
`overshootClamping`, which drawer-layout always passes, an animation now ends
as soon as the position leaves the corridor between its start and its target.
Reanimated 3 only treated crossing the target as overshoot. So on Reanimated 4
any release whose velocity opposes the direction the drawer settles in ends the
spring on its first frame, and the drawer teleports instead of animating. Two
easy ways in: an aborted drag under `swipeMinDistance`/`swipeMinVelocity`,
where `nextOpen` keeps its current value while the finger was moving the other
way, and dragging the already-open drawer past its edge before releasing.

Refresh the patch against upstream `main`, which fixes this by zeroing velocity
that opposes the spring direction, and take the two other changes to the same
function while we are here: an early return when the drawer is already at the
target, and `useLayoutEffect` for the `open` prop so the follow-up
`toggleDrawer` call does not depend on the spring still running to be deduped.

Regenerated with `pnpm patch` / `pnpm patch-commit`. Also adds the patch doc
this one was missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DRYvgEawpfehopEibJXQB
2026-07-25 08:14:27 +00:00

62 lines
2.2 KiB
Diff

diff --git a/lib/module/views/Drawer.native.js b/lib/module/views/Drawer.native.js
index 38470a658878a63919186257041098747ec55473..1836d661ca0f8fa3e886da06e72eb004cd8f585e 100644
--- a/lib/module/views/Drawer.native.js
+++ b/lib/module/views/Drawer.native.js
@@ -124,17 +124,33 @@ export function Drawer({
}
onTransitionEnd?.(!open);
});
+ const animatingTo = useSharedValue(null)
const toggleDrawer = React.useCallback((open, velocity) => {
'worklet';
const translateX = getDrawerTranslationX(open);
+
+ if (translationX.value === translateX) {
+ return;
+ }
+
+ if (animatingTo.value === (open ? 'open' : 'close')) {
+ return;
+ }
+
if (velocity === undefined) {
runOnJS(onAnimationStart)(open);
}
touchStartX.value = 0;
touchX.value = 0;
+
+ // Ignore velocity that opposes the spring direction.
+ // Otherwise overshootClamping makes the spring snap instantly.
+ const isSameDirection = velocity !== undefined && (translateX > translationX.value && velocity > 0 || translateX < translationX.value && velocity < 0);
+ const effectiveVelocity = isSameDirection ? velocity : 0;
+ animatingTo.value = open ? 'open' : 'close';
translationX.value = withSpring(translateX, {
- velocity,
+ velocity: effectiveVelocity,
stiffness: 1000,
damping: 500,
mass: 3,
@@ -142,14 +158,19 @@ export function Drawer({
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
reduceMotion: ReduceMotion.Never
- }, finished => runOnJS(onAnimationEnd)(open, finished));
+ }, finished => {
+ animatingTo.value = null;
+ runOnJS(onAnimationEnd)(open, finished);
+ });
if (open) {
runOnJS(onOpen)();
} else {
runOnJS(onClose)();
}
}, [getDrawerTranslationX, onAnimationEnd, onAnimationStart, onClose, onOpen, touchStartX, touchX, translationX]);
- React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]);
+ React.useLayoutEffect(() => {
+ toggleDrawer(open);
+ }, [open, toggleDrawer]);
const startX = useSharedValue(0);
const pan = React.useMemo(() => {
let panGesture = Gesture?.Pan().onBegin(event => {