From 143ef231122408ab47a8966d4c410961b88aaec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 13 May 2026 16:26:11 +0200 Subject: [PATCH 01/15] Update Swipeable types --- .../docs/components/reanimated_swipeable.mdx | 4 ++-- .../ReanimatedSwipeable/ReanimatedSwipeableProps.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx index 817ec0310d..4db1676c94 100644 --- a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx +++ b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx @@ -55,7 +55,7 @@ Distance from the right edge at which released panel will animate to the open st ### dragOffsetFromLeft ```ts -dragOffsetFromLeft?: number; +dragOffsetFromLeft?: number | SharedValue; ``` The minimum horizontal distance from the starting point required to trigger a right-swipe gesture. Defaults to `10`. @@ -63,7 +63,7 @@ The minimum horizontal distance from the starting point required to trigger a ri ### dragOffsetFromRight ```ts -dragOffsetFromRight?: number; +dragOffsetFromRight?: number | SharedValue; ``` The minimum horizontal distance from the starting point required to trigger a left-swipe gesture. Defaults to `10`. 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 392e3a44f0..7ccf0c99b0 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts @@ -69,12 +69,12 @@ export interface SwipeableProps { /** * The minimum horizontal distance from the starting point required to trigger a right-swipe gesture. Defaults to `10`. */ - dragOffsetFromLeft?: number; + dragOffsetFromLeft?: number | SharedValue; /** * The minimum horizontal distance from the starting point required to trigger a left-swipe gesture. Defaults to `10`. */ - dragOffsetFromRight?: number; + dragOffsetFromRight?: number | SharedValue; /** * Value indicating if the swipeable panel can be pulled further than the left From e84296797118cd583b147df333079f5e901fae42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 13 May 2026 18:07:06 +0200 Subject: [PATCH 02/15] Change logic --- .../ReanimatedSwipeable.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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 fa0b0fb82a..11eb60026d 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -1,5 +1,5 @@ import type { ForwardedRef } from 'react'; -import { useCallback, useImperativeHandle, useMemo } from 'react'; +import { useCallback, useEffect, useImperativeHandle, useMemo } from 'react'; import type { LayoutChangeEvent } from 'react-native'; import { I18nManager, StyleSheet, View } from 'react-native'; import Animated, { @@ -14,9 +14,11 @@ import Animated, { withSpring, } from 'react-native-reanimated'; +import { tagMessage } from '../../utils'; import { GestureDetector } from '../../v3/detectors'; import type { PanGestureActiveEvent } from '../../v3/hooks/gestures'; import { usePanGesture, useTapGesture } from '../../v3/hooks/gestures'; +import { maybeUnpackValue } from '../../v3/hooks/utils'; import type { SwipeableMethods, SwipeableProps, @@ -63,6 +65,18 @@ const Swipeable = (props: SwipeableProps) => { ...remainingProps } = props; + useEffect(() => { + if (__DEV__) { + const value = maybeUnpackValue(dragOffsetFromRight); + + if (value > 0) { + throw new Error( + tagMessage('dragOffsetFromRight should be non-positive.') + ); + } + } + }, [dragOffsetFromRight]); + const shouldEnableTap = useSharedValue(false); const rowState = useSharedValue(0); @@ -470,7 +484,7 @@ const Swipeable = (props: SwipeableProps) => { const panGesture = usePanGesture({ enabled: enabled !== false, enableTrackpadTwoFingerGesture: enableTrackpadTwoFingerGesture, - activeOffsetX: [-dragOffsetFromRight, dragOffsetFromLeft], + activeOffsetX: [dragOffsetFromRight, dragOffsetFromLeft], simultaneousWith, requireToFail, block, From 23de6d7b349f6e52dd6e21eda7b8fadfaf4e5447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 13 May 2026 18:14:41 +0200 Subject: [PATCH 03/15] Change default value to negative --- .../src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 11eb60026d..1a2b95c0ed 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -47,7 +47,7 @@ const Swipeable = (props: SwipeableProps) => { children, enableTrackpadTwoFingerGesture = DEFAULT_ENABLE_TRACKING_TWO_FINGER_GESTURE, dragOffsetFromLeft = DEFAULT_DRAG_OFFSET, - dragOffsetFromRight = DEFAULT_DRAG_OFFSET, + dragOffsetFromRight = -DEFAULT_DRAG_OFFSET, friction = DEFAULT_FRICTION, overshootFriction = DEFAULT_OVERSHOOT_FRICTION, onSwipeableOpenStartDrag, From b6132950f084c48d0ee4300d58a040c37add7824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 May 2026 09:16:10 +0200 Subject: [PATCH 04/15] Update docs --- .../docs/components/reanimated_swipeable.mdx | 4 ++-- .../ReanimatedSwipeable/ReanimatedSwipeableProps.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx index 4db1676c94..4f4831708a 100644 --- a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx +++ b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx @@ -58,7 +58,7 @@ Distance from the right edge at which released panel will animate to the open st dragOffsetFromLeft?: number | SharedValue; ``` -The minimum horizontal distance from the starting point required to trigger a right-swipe gesture. Defaults to `10`. +he horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10. ### dragOffsetFromRight @@ -66,7 +66,7 @@ The minimum horizontal distance from the starting point required to trigger a ri dragOffsetFromRight?: number | SharedValue; ``` -The minimum horizontal distance from the starting point required to trigger a left-swipe gesture. Defaults to `10`. +The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10. ### overshootLeft 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 7ccf0c99b0..41fa05e932 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts @@ -67,12 +67,12 @@ export interface SwipeableProps { rightThreshold?: number; /** - * The minimum horizontal distance from the starting point required to trigger a right-swipe gesture. Defaults to `10`. + * The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10. */ dragOffsetFromLeft?: number | SharedValue; /** - * The minimum horizontal distance from the starting point required to trigger a left-swipe gesture. Defaults to `10`. + * The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10. */ dragOffsetFromRight?: number | SharedValue; From 67cf8885ba65f37cc2bfd0d3b2a48afc11107c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 May 2026 09:30:44 +0200 Subject: [PATCH 05/15] Update migration guide --- .../docs/components/reanimated_swipeable.mdx | 8 ++++---- .../docs-gesture-handler/docs/guides/upgrading-to-3.mdx | 6 ++++++ skills/gesture-handler-3-migration/SKILL.md | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx index 4f4831708a..39a935604f 100644 --- a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx +++ b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx @@ -16,9 +16,9 @@ This component is a drop-in replacement for the `Swipeable` component, rewritten -Reanimated `Swipeable` is designed for implementing swipeable rows or similar interactions. It places its children inside a pannable container that enables horizontal swiping to the left and right. Depending on the direction of the swipe, one of two "action" containers will be displayed, which can be configured using the [`renderLeftActions`](#renderleftactions) or [`renderRightActions`](#renderrightactions) props. +`ReanimatedSwipeable` is designed for implementing swipeable rows or similar interactions. It places its children inside a pannable container that enables horizontal swiping to the left and right. Depending on the direction of the swipe, one of two "action" containers will be displayed, which can be configured using the [`renderLeftActions`](#renderleftactions) or [`renderRightActions`](#renderrightactions) props. -To use Reanimated `Swipeable`, first ensure that Reanimated is installed and that your app is wrapped in `GestureHandlerRootView`. You can then import it as follows: +To use `ReanimatedSwipeable`, first ensure that Reanimated is installed and that your app is wrapped in `GestureHandlerRootView`. You can then import it as follows: ```ts import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable'; @@ -58,7 +58,7 @@ Distance from the right edge at which released panel will animate to the open st dragOffsetFromLeft?: number | SharedValue; ``` -he horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10. +he horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to `10`. ### dragOffsetFromRight @@ -66,7 +66,7 @@ he horizontal offset from the starting point required to trigger a right-swipe g dragOffsetFromRight?: number | SharedValue; ``` -The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10. +The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to `-10`. ### overshootLeft diff --git a/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx b/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx index a7a1aeb6c1..ca24420628 100644 --- a/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx +++ b/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx @@ -299,6 +299,12 @@ Although the legacy JS implementation of the buttons is still available, they al +### ReanimatedSwipeable + +`ReanimatedSwipeable` has been rewritten using the new hook API. Additionaly, `dragOffsetFromLeft` and `dragOffsetFromRight` props now accept [`SharedValues`](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value). + +`dragOffsetFromRight` now accepts negative values. If you were using it with positive values, make sure to change the sign when migrating. + ### Touchables [`Touchable`](/docs/components/touchable) can also be used as a substitute for old, deprecated `Touchables`. diff --git a/skills/gesture-handler-3-migration/SKILL.md b/skills/gesture-handler-3-migration/SKILL.md index ac6e82efd4..3cce8b87bf 100644 --- a/skills/gesture-handler-3-migration/SKILL.md +++ b/skills/gesture-handler-3-migration/SKILL.md @@ -186,6 +186,8 @@ The implementation of buttons has been updated, resolving most button-related is When migrating buttons, you should use new `Touchable` component instead. To replace `BaseButton` use `Touchable` with default props, to replace `RectButton` use `Touchable` with `underlayColor="black"` and to replace `BorderlessButton` use `Touchable` with `activeOpacity={0.3}`. +ReanimatedSwipeable prop `dragOffsetFromRight` now accepts negative values. If it was used with positive values, make sure to change the sign. + Legacy Touchables (`TouchableOpacity`, `TouchableHighlight`, `TouchableWithoutFeedback`, `TouchableNativeFeedback`) from Gesture Handler are also deprecated and should be replaced with `Touchable`. To replace `TouchableOpacity` use `Touchable` with `activeOpacity={0.2}` and to replace `TouchableHighlight` use `Touchable` with `activeUnderlayOpacity={1}`. To replace `TouchableWithoutFeedback` use a plain `Touchable`. `TouchableNativeFeedback` can be replaced with `Touchable` by setting `androidRipple` property. At minimum, it should be set to `{foreground: true}`, to mimic `TouchableNativeFeedback` ripple effect. Other components have also been internally rewritten using the new hook API but are exported under their original names, so no changes are necessary on your part. However, if you need to use the previous implementation for any reason, the legacy components are also available and are prefixed with `Legacy`, e.g., `ScrollView` is now available as `LegacyScrollView`. From 33240ab49abb905dece18cc0dc14522a2e0ef617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 May 2026 09:32:51 +0200 Subject: [PATCH 06/15] missing T --- .../docs/components/reanimated_swipeable.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx index 39a935604f..0c16331a37 100644 --- a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx +++ b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx @@ -58,7 +58,7 @@ Distance from the right edge at which released panel will animate to the open st dragOffsetFromLeft?: number | SharedValue; ``` -he horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to `10`. +The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to `10`. ### dragOffsetFromRight From 0401093468c1ff19ea0c31fd6eca67f0b38febf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 May 2026 10:25:15 +0200 Subject: [PATCH 07/15] Update other types --- .../ReanimatedSwipeable.tsx | 2 +- .../ReanimatedSwipeableProps.ts | 68 +++++++++---------- 2 files changed, 32 insertions(+), 38 deletions(-) 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 1a2b95c0ed..86eb117b5a 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -482,7 +482,7 @@ const Swipeable = (props: SwipeableProps) => { }); const panGesture = usePanGesture({ - enabled: enabled !== false, + enabled: enabled ?? true, enableTrackpadTwoFingerGesture: enableTrackpadTwoFingerGesture, activeOffsetX: [dragOffsetFromRight, dragOffsetFromLeft], simultaneousWith, 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 41fa05e932..1e1246f709 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts @@ -3,17 +3,14 @@ import type { StyleProp, ViewStyle } from 'react-native'; import type { SharedValue } from 'react-native-reanimated'; import type { HitSlop } from '../../handlers/gestureHandlerCommon'; -import type { AnyGesture } from '../../v3/types'; +import type { AnyGesture, WithSharedValue } from '../../v3/types'; export enum SwipeDirection { LEFT = 'left', RIGHT = 'right', } -export interface SwipeableProps { - /** - * - */ +export type SwipeableProps = { ref?: React.Ref; /** @@ -23,27 +20,6 @@ export interface SwipeableProps { children?: React.ReactNode; - /** - * Indicates whether `ReanimatedSwipeable` should be analyzing stream of touch events or not. - * @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#enabledvalue-boolean - */ - enabled?: boolean; - - /** - * This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture. - * When a negative number is provided the bounds of the view will reduce the area by the given number of points in each of the sides evenly. - * @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#hitslopsettings - */ - hitSlop?: HitSlop; - - /** - * Enables two-finger gestures on supported devices, for example iPads with - * trackpads. If not enabled the gesture will require click + drag, with - * `enableTrackpadTwoFingerGesture` swiping with two fingers will also trigger - * the gesture. - */ - enableTrackpadTwoFingerGesture?: boolean; - /** * Specifies how much the visual interaction will be delayed compared to the * gesture distance. e.g. value of 1 will indicate that the swipeable panel @@ -66,16 +42,6 @@ export interface SwipeableProps { */ rightThreshold?: number; - /** - * The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10. - */ - dragOffsetFromLeft?: number | SharedValue; - - /** - * The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10. - */ - dragOffsetFromRight?: number | SharedValue; - /** * Value indicating if the swipeable panel can be pulled further than the left * actions panel's width. It is set to true by default as long as the left @@ -202,7 +168,35 @@ export interface SwipeableProps { * used with the swipeable's gesture handler. */ block?: AnyGesture | AnyGesture[]; -} +} & WithSharedValue<{ + /** + * Indicates whether `ReanimatedSwipeable` should be analyzing stream of touch events or not. + * @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#enabledvalue-boolean + */ + enabled?: boolean | undefined; + /** + * This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture. + * When a negative number is provided the bounds of the view will reduce the area by the given number of points in each of the sides evenly. + * @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#hitslopsettings + */ + hitSlop?: HitSlop | undefined; + /** + * Enables two-finger gestures on supported devices, for example iPads with + * trackpads. If not enabled the gesture will require click + drag, with + * `enableTrackpadTwoFingerGesture` swiping with two fingers will also trigger + * the gesture. + */ + enableTrackpadTwoFingerGesture?: boolean | undefined; + /** + * The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10. + */ + dragOffsetFromLeft?: number; + + /** + * The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10. + */ + dragOffsetFromRight?: number; +}>; export interface SwipeableMethods { close: () => void; From 80e8a75fc736c887e19a67c1fc7e8b1cef102353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Bert?= <63123542+m-bert@users.noreply.github.com> Date: Thu, 14 May 2026 10:34:49 +0200 Subject: [PATCH 08/15] Typo Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx b/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx index ca24420628..73015086de 100644 --- a/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx +++ b/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx @@ -301,7 +301,7 @@ Although the legacy JS implementation of the buttons is still available, they al ### ReanimatedSwipeable -`ReanimatedSwipeable` has been rewritten using the new hook API. Additionaly, `dragOffsetFromLeft` and `dragOffsetFromRight` props now accept [`SharedValues`](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value). +`ReanimatedSwipeable` has been rewritten using the new hook API. Additionally, `dragOffsetFromLeft` and `dragOffsetFromRight` props now accept [`SharedValues`](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value). `dragOffsetFromRight` now accepts negative values. If you were using it with positive values, make sure to change the sign when migrating. From 46c9bc71f92be4f2ab7cc44ae7d506d6ed5b90ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 May 2026 10:56:21 +0200 Subject: [PATCH 09/15] Update docs --- .../docs/components/reanimated_swipeable.mdx | 6 +++--- .../docs-gesture-handler/docs/guides/upgrading-to-3.mdx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx index 0c16331a37..f03a9db651 100644 --- a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx +++ b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx @@ -240,12 +240,12 @@ blocksExternalGesture?: AnyGesture | AnyGesture[]; Gestures that `Swipeable` will prevent from activating (see [gesture composition section](/docs/fundamentals/gesture-composition#simultaneouswith)). - + ### enableTrackpadTwoFingerGesture ```ts -enableTrackpadTwoFingerGesture?: boolean; +enableTrackpadTwoFingerGesture: boolean | SharedValue; ``` Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with `enableTrackpadTwoFingerGesture` swiping with two fingers will also trigger the gesture. @@ -253,7 +253,7 @@ Enables two-finger gestures on supported devices, for example iPads with trackpa ### enabled ```ts -enabled: boolean; +enabled: boolean | SharedValue; ``` Indicates whether `ReanimatedSwipeable` should be analyzing the stream of touch events or not. Defaults to `true`. diff --git a/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx b/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx index 73015086de..cf4c4e6e23 100644 --- a/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx +++ b/packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx @@ -301,7 +301,7 @@ Although the legacy JS implementation of the buttons is still available, they al ### ReanimatedSwipeable -`ReanimatedSwipeable` has been rewritten using the new hook API. Additionally, `dragOffsetFromLeft` and `dragOffsetFromRight` props now accept [`SharedValues`](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value). +`ReanimatedSwipeable` has been rewritten using the new hook API. Additionally, `enabled`, `hitSlop`, `enableTrackpadTwoFingerGesture`, `dragOffsetFromLeft` and `dragOffsetFromRight` props now accept [`SharedValues`](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#shared-value). `dragOffsetFromRight` now accepts negative values. If you were using it with positive values, make sure to change the sign when migrating. From 7818a443812f7446fcd830aa799d2266978ff418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 May 2026 11:47:56 +0200 Subject: [PATCH 10/15] React also to SharedValue changes --- .../ReanimatedSwipeable.tsx | 34 +++++++++++++------ .../handlers/gestures/reanimatedWrapper.ts | 4 +++ 2 files changed, 27 insertions(+), 11 deletions(-) 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 86eb117b5a..63ae5d5108 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -14,11 +14,11 @@ import Animated, { withSpring, } from 'react-native-reanimated'; +import { Reanimated } from '../../handlers/gestures/reanimatedWrapper'; import { tagMessage } from '../../utils'; import { GestureDetector } from '../../v3/detectors'; import type { PanGestureActiveEvent } from '../../v3/hooks/gestures'; import { usePanGesture, useTapGesture } from '../../v3/hooks/gestures'; -import { maybeUnpackValue } from '../../v3/hooks/utils'; import type { SwipeableMethods, SwipeableProps, @@ -65,17 +65,29 @@ const Swipeable = (props: SwipeableProps) => { ...remainingProps } = props; - useEffect(() => { - if (__DEV__) { - const value = maybeUnpackValue(dragOffsetFromRight); - - if (value > 0) { - throw new Error( - tagMessage('dragOffsetFromRight should be non-positive.') - ); - } + if (__DEV__) { + if (Reanimated?.isSharedValue(dragOffsetFromRight)) { + // eslint-disable-next-line react-hooks/rules-of-hooks + Reanimated?.useDerivedValue(() => { + 'worklet'; + if (dragOffsetFromRight.value > 0) { + throw new Error( + tagMessage('dragOffsetFromRight should be non-positive.') + ); + } + return dragOffsetFromRight.value; + }); + } else { + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + if ((dragOffsetFromRight as number) > 0) { + throw new Error( + tagMessage('dragOffsetFromRight should be non-positive.') + ); + } + }, [dragOffsetFromRight]); } - }, [dragOffsetFromRight]); + } const shouldEnableTap = useSharedValue(false); const rowState = useSharedValue(0); diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts index 5708870405..660378c3f1 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -74,6 +74,10 @@ let Reanimated: useComposedEventHandler( handlers: (((event: T) => void) | null)[] ): (event: T) => void; + useDerivedValue( + updater: () => T, + dependencies?: readonly unknown[] + ): SharedValue; // Not exactly, but we don't need that anyway runOnUI( fn: (...args: A) => R ): (...args: Parameters) => void; From 9e75c0e3e3378dc547e95ddaf54227f1cf1bd10a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 14 May 2026 11:58:32 +0200 Subject: [PATCH 11/15] Add missing ? --- .../docs/components/reanimated_swipeable.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx index f03a9db651..9618776f83 100644 --- a/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx +++ b/packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx @@ -245,7 +245,7 @@ Gestures that `Swipeable` will prevent from activating (see [gesture composition ```ts -enableTrackpadTwoFingerGesture: boolean | SharedValue; +enableTrackpadTwoFingerGesture?: boolean | SharedValue; ``` Enables two-finger gestures on supported devices, for example iPads with trackpads. If not enabled the gesture will require click + drag, with `enableTrackpadTwoFingerGesture` swiping with two fingers will also trigger the gesture. @@ -253,7 +253,7 @@ Enables two-finger gestures on supported devices, for example iPads with trackpa ### enabled ```ts -enabled: boolean | SharedValue; +enabled?: boolean | SharedValue; ``` Indicates whether `ReanimatedSwipeable` should be analyzing the stream of touch events or not. Defaults to `true`. @@ -261,7 +261,7 @@ Indicates whether `ReanimatedSwipeable` should be analyzing the stream of touch ### testID ```ts -testID: string; +testID?: string; ``` Sets a `testID` property, allowing for querying `ReanimatedSwipeable` for it in tests. @@ -274,7 +274,7 @@ expandedLabel="Hide composed types definitions" lineBounds={[0, 1]} collapsed={false} src={` -hitSlop: HitSlop | SharedValue; +hitSlop?: HitSlop | SharedValue; type HitSlop = | number From 2d06c351fd84981ebc55100eae472d9190d4021c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 15 May 2026 11:26:56 +0200 Subject: [PATCH 12/15] Change check --- .../ReanimatedSwipeable.tsx | 52 ++++++++++++------- .../src/v3/hooks/utils/reanimatedUtils.ts | 2 +- 2 files changed, 35 insertions(+), 19 deletions(-) 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 63ae5d5108..72c9f0a14a 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -1,5 +1,10 @@ import type { ForwardedRef } from 'react'; -import { useCallback, useEffect, useImperativeHandle, useMemo } from 'react'; +import React, { + useCallback, + useEffect, + useImperativeHandle, + useMemo, +} from 'react'; import type { LayoutChangeEvent } from 'react-native'; import { I18nManager, StyleSheet, View } from 'react-native'; import Animated, { @@ -19,6 +24,7 @@ import { tagMessage } from '../../utils'; import { GestureDetector } from '../../v3/detectors'; import type { PanGestureActiveEvent } from '../../v3/hooks/gestures'; import { usePanGesture, useTapGesture } from '../../v3/hooks/gestures'; +import { SHARED_VALUE_OFFSET } from '../../v3/hooks/utils/reanimatedUtils'; import type { SwipeableMethods, SwipeableProps, @@ -66,26 +72,36 @@ const Swipeable = (props: SwipeableProps) => { } = props; if (__DEV__) { + const checkValue = (value: number) => { + 'worklet'; + if (value > 0) { + throw new Error( + tagMessage('dragOffsetFromLeft should be non-negative.') + ); + } + }; + if (Reanimated?.isSharedValue(dragOffsetFromRight)) { - // eslint-disable-next-line react-hooks/rules-of-hooks - Reanimated?.useDerivedValue(() => { - 'worklet'; - if (dragOffsetFromRight.value > 0) { - throw new Error( - tagMessage('dragOffsetFromRight should be non-positive.') - ); - } - return dragOffsetFromRight.value; - }); - } else { + checkValue(dragOffsetFromRight.value); + + const listenerId = Math.random() + SHARED_VALUE_OFFSET; + // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { - if ((dragOffsetFromRight as number) > 0) { - throw new Error( - tagMessage('dragOffsetFromRight should be non-positive.') - ); - } - }, [dragOffsetFromRight]); + Reanimated?.runOnUI(() => { + 'worklet'; + dragOffsetFromRight.addListener(listenerId, checkValue); + })(); + + return () => { + Reanimated?.runOnUI(() => { + 'worklet'; + dragOffsetFromRight.removeListener(listenerId); + })(); + }; + }, [dragOffsetFromRight, checkValue, listenerId]); + } else { + checkValue(dragOffsetFromRight as number); } } diff --git a/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts b/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts index 30ead9484b..c785103e75 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts @@ -20,7 +20,7 @@ function hash(str: string) { return h >>> 0; } -const SHARED_VALUE_OFFSET = 1.618; +export const SHARED_VALUE_OFFSET = 1.618; // Don't transfer entire NativeProxy to the UI thread const { updateGestureHandlerConfig } = NativeProxy; From b2d4e5a7cb8b2214b3e4f26321929c8e477e8521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 15 May 2026 11:44:49 +0200 Subject: [PATCH 13/15] Fix check again --- .../ReanimatedSwipeable.tsx | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) 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 72c9f0a14a..845291e5d2 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -24,7 +24,11 @@ import { tagMessage } from '../../utils'; import { GestureDetector } from '../../v3/detectors'; import type { PanGestureActiveEvent } from '../../v3/hooks/gestures'; import { usePanGesture, useTapGesture } from '../../v3/hooks/gestures'; -import { SHARED_VALUE_OFFSET } from '../../v3/hooks/utils/reanimatedUtils'; +import { + maybeUnpackValue, + SHARED_VALUE_OFFSET, +} from '../../v3/hooks/utils/reanimatedUtils'; +import type { SharedValueOrT } from '../../v3/types'; import type { SwipeableMethods, SwipeableProps, @@ -72,37 +76,37 @@ const Swipeable = (props: SwipeableProps) => { } = props; if (__DEV__) { - const checkValue = (value: number) => { + const checkValue = (value: SharedValueOrT) => { 'worklet'; - if (value > 0) { + if (maybeUnpackValue(value) > 0) { throw new Error( - tagMessage('dragOffsetFromLeft should be non-negative.') + tagMessage('dragOffsetFromRight should be non-negative.') ); } }; - if (Reanimated?.isSharedValue(dragOffsetFromRight)) { - checkValue(dragOffsetFromRight.value); + checkValue(dragOffsetFromRight); + + // eslint-disable-next-line react-hooks/rules-of-hooks + useEffect(() => { + if (!Reanimated?.isSharedValue(dragOffsetFromRight)) { + return; + } const listenerId = Math.random() + SHARED_VALUE_OFFSET; - // eslint-disable-next-line react-hooks/rules-of-hooks - useEffect(() => { + Reanimated?.runOnUI(() => { + 'worklet'; + dragOffsetFromRight.addListener(listenerId, checkValue); + })(); + + return () => { Reanimated?.runOnUI(() => { 'worklet'; - dragOffsetFromRight.addListener(listenerId, checkValue); + dragOffsetFromRight.removeListener(listenerId); })(); - - return () => { - Reanimated?.runOnUI(() => { - 'worklet'; - dragOffsetFromRight.removeListener(listenerId); - })(); - }; - }, [dragOffsetFromRight, checkValue, listenerId]); - } else { - checkValue(dragOffsetFromRight as number); - } + }; + }, [dragOffsetFromRight, checkValue]); } const shouldEnableTap = useSharedValue(false); From d01be7fc3e863ee5a5f4a445e5ca26e30766f576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 15 May 2026 11:46:00 +0200 Subject: [PATCH 14/15] Remove useDerivedValue --- .../src/handlers/gestures/reanimatedWrapper.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts index 660378c3f1..5708870405 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/reanimatedWrapper.ts @@ -74,10 +74,6 @@ let Reanimated: useComposedEventHandler( handlers: (((event: T) => void) | null)[] ): (event: T) => void; - useDerivedValue( - updater: () => T, - dependencies?: readonly unknown[] - ): SharedValue; // Not exactly, but we don't need that anyway runOnUI( fn: (...args: A) => R ): (...args: Parameters) => void; From b7a3506ae5ef61f7874cd5eb3008a1bc884bcc3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 18 May 2026 09:48:56 +0200 Subject: [PATCH 15/15] Error message --- .../src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 845291e5d2..3c4d402aec 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -80,7 +80,7 @@ const Swipeable = (props: SwipeableProps) => { 'worklet'; if (maybeUnpackValue(value) > 0) { throw new Error( - tagMessage('dragOffsetFromRight should be non-negative.') + tagMessage('dragOffsetFromRight should be non-positive.') ); } };