From 23eb04a1280e639e4772ef01de0dd998317151df Mon Sep 17 00:00:00 2001 From: "danzicchi@gmail.com" Date: Fri, 2 Oct 2026 19:39:48 +0200 Subject: [PATCH 1/2] [General] Add `failOffsetY` prop to `ReanimatedSwipeable` --- .../docs/components/reanimated_swipeable.mdx | 12 ++++++++++++ .../ReanimatedSwipeable/ReanimatedSwipeable.tsx | 3 +++ .../ReanimatedSwipeable/ReanimatedSwipeableProps.ts | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx index 8dd6651121..88b8f04c81 100644 --- a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx +++ b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx @@ -69,6 +69,18 @@ dragOffsetFromRight?: number | SharedValue; The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to `-10`. +### failOffsetY + +```ts +failOffsetY?: number | + SharedValue | + [number | SharedValue, number | SharedValue]; +``` + +The vertical offset range after which the swipe gesture fails if it hasn't activated yet. Not set by default. Useful when `Swipeable` is placed inside a vertically scrollable container, e.g. a list with pull-to-refresh, so that a mostly vertical drag is not taken over by the swipe. + +See [failOffsetY section](/docs/gestures/use-pan-gesture#failoffsety) in `Pan` gesture for more details. + ### overshootLeft ```ts diff --git a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx index b62da83d89..111e2cffff 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -77,6 +77,7 @@ const Swipeable = (props: SwipeableProps) => { enableTrackpadTwoFingerGesture = DEFAULT_ENABLE_TRACKING_TWO_FINGER_GESTURE, dragOffsetFromLeft = DEFAULT_DRAG_OFFSET, dragOffsetFromRight = -DEFAULT_DRAG_OFFSET, + failOffsetY, friction = DEFAULT_FRICTION, overshootFriction = DEFAULT_OVERSHOOT_FRICTION, onSwipeableOpenStartDrag: onSwipeableOpenStartDragProp, @@ -606,6 +607,7 @@ const Swipeable = (props: SwipeableProps) => { typeof dragOffsetFromRight, typeof dragOffsetFromLeft, ], + ...(failOffsetY !== undefined && { failOffsetY }), simultaneousWith, requireToFail, block, @@ -621,6 +623,7 @@ const Swipeable = (props: SwipeableProps) => { dragOffsetFromRight, enableTrackpadTwoFingerGesture, enabled, + failOffsetY, handleFinalize, handleRelease, handleUpdate, diff --git a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts index 1e1246f709..108c9c865a 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts @@ -196,6 +196,12 @@ export type SwipeableProps = { * The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10. */ dragOffsetFromRight?: number; + + /** + * Vertical offset from the starting point after which the swipe gesture fails. Passed to the underlying pan gesture as `failOffsetY`. + * @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/use-pan-gesture#failoffsety + */ + failOffsetY?: number | [number, number]; }>; export interface SwipeableMethods { From 6adb0052377992fd81b9f52742a6c3e7b529913c Mon Sep 17 00:00:00 2001 From: "danzicchi@gmail.com" Date: Sat, 3 Oct 2026 01:38:52 +0200 Subject: [PATCH 2/2] Add test for failOffsetY forwarding --- .../reanimatedSwipeableCallbacks.test.tsx | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/packages/react-native-gesture-handler/src/__tests__/reanimatedSwipeableCallbacks.test.tsx b/packages/react-native-gesture-handler/src/__tests__/reanimatedSwipeableCallbacks.test.tsx index 77678c8d64..4ea8528689 100644 --- a/packages/react-native-gesture-handler/src/__tests__/reanimatedSwipeableCallbacks.test.tsx +++ b/packages/react-native-gesture-handler/src/__tests__/reanimatedSwipeableCallbacks.test.tsx @@ -193,3 +193,65 @@ describe('ReanimatedSwipeable callback identity', () => { expect(scheduleOnRN).not.toHaveBeenCalled(); }); }); + +describe('ReanimatedSwipeable failOffsetY', () => { + let setConfigSpy: jest.SpyInstance; + + beforeEach(() => { + jest.clearAllMocks(); + setConfigSpy = jest.spyOn( + RNGestureHandlerModule, + 'setGestureHandlerConfig' + ); + }); + + afterEach(() => { + setConfigSpy.mockRestore(); + }); + + function getPanConfigs() { + return setConfigSpy.mock.calls + .map(([, config]) => config as Record) + .filter((config) => 'activeOffsetXStart' in config); + } + + test('forwards failOffsetY to the pan gesture', async () => { + render( + + Delete}> + Row + + + ); + await flushNativeOps(); + + const panConfigs = getPanConfigs(); + expect(panConfigs.length).toBeGreaterThan(0); + for (const config of panConfigs) { + expect(config).toMatchObject({ + failOffsetYStart: -8, + failOffsetYEnd: 8, + }); + } + }); + + test('does not set failOffsetY on the pan gesture when the prop is omitted', async () => { + render( + + Delete}> + Row + + + ); + await flushNativeOps(); + + const panConfigs = getPanConfigs(); + expect(panConfigs.length).toBeGreaterThan(0); + for (const config of panConfigs) { + expect(config).not.toHaveProperty('failOffsetYStart'); + expect(config).not.toHaveProperty('failOffsetYEnd'); + } + }); +});