Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ This component is a drop-in replacement for the `Swipeable` component, rewritten
</GifGallery>


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';
Expand Down Expand Up @@ -55,18 +55,18 @@ Distance from the right edge at which released panel will animate to the open st
### dragOffsetFromLeft

```ts
dragOffsetFromLeft?: number;
dragOffsetFromLeft?: number | SharedValue<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`.

### dragOffsetFromRight

```ts
dragOffsetFromRight?: number;
dragOffsetFromRight?: number | SharedValue<number>;
```

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

Expand Down Expand Up @@ -240,28 +240,28 @@ blocksExternalGesture?: AnyGesture | AnyGesture[];

Gestures that `Swipeable` will prevent from activating (see [gesture composition section](/docs/fundamentals/gesture-composition#simultaneouswith)).

<Badges platforms={['ios']}>
<Badges platforms={['ios', 'web']}>
Comment thread
m-bert marked this conversation as resolved.
### enableTrackpadTwoFingerGesture
</Badges>

```ts
enableTrackpadTwoFingerGesture?: boolean;
enableTrackpadTwoFingerGesture?: boolean | SharedValue<boolean>;
```

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.

### enabled

```ts
enabled: boolean;
enabled?: boolean | SharedValue<boolean>;
```

Indicates whether `ReanimatedSwipeable` should be analyzing the stream of touch events or not. Defaults to `true`.

### testID

```ts
testID: string;
testID?: string;
```

Sets a `testID` property, allowing for querying `ReanimatedSwipeable` for it in tests.
Expand All @@ -274,7 +274,7 @@ expandedLabel="Hide composed types definitions"
lineBounds={[0, 1]}
collapsed={false}
src={`
hitSlop: HitSlop | SharedValue<HitSlop>;
hitSlop?: HitSlop | SharedValue<HitSlop>;

type HitSlop =
| number
Expand Down
6 changes: 6 additions & 0 deletions packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ Although the legacy JS implementation of the buttons is still available, they al

</details>

### ReanimatedSwipeable

`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.

### Touchables

[`Touchable`](/docs/components/touchable) can also be used as a substitute for old, deprecated `Touchables`.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { ForwardedRef } from 'react';
import { useCallback, 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, {
Expand All @@ -14,9 +19,16 @@ 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,
SHARED_VALUE_OFFSET,
} from '../../v3/hooks/utils/reanimatedUtils';
import type { SharedValueOrT } from '../../v3/types';
import type {
SwipeableMethods,
SwipeableProps,
Expand Down Expand Up @@ -45,7 +57,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,
Expand All @@ -63,6 +75,40 @@ const Swipeable = (props: SwipeableProps) => {
...remainingProps
} = props;

if (__DEV__) {
const checkValue = (value: SharedValueOrT<number>) => {
'worklet';
if (maybeUnpackValue<number>(value) > 0) {
throw new Error(
tagMessage('dragOffsetFromRight should be non-positive.')
);
}
};

checkValue(dragOffsetFromRight);

// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (!Reanimated?.isSharedValue<number>(dragOffsetFromRight)) {
return;
}

const listenerId = Math.random() + SHARED_VALUE_OFFSET;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to use a stable id instead of randomizing it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked that and listeners are local for given SharedValue, so that should be doable. Is there any particular reason why you prefer stable id in that case?


Reanimated?.runOnUI(() => {
'worklet';
dragOffsetFromRight.addListener(listenerId, checkValue);
})();

return () => {
Reanimated?.runOnUI(() => {
'worklet';
dragOffsetFromRight.removeListener(listenerId);
})();
};
}, [dragOffsetFromRight, checkValue]);
}
Comment thread
m-bert marked this conversation as resolved.

const shouldEnableTap = useSharedValue<boolean>(false);
const rowState = useSharedValue<number>(0);

Expand Down Expand Up @@ -468,9 +514,9 @@ const Swipeable = (props: SwipeableProps) => {
});

const panGesture = usePanGesture({
enabled: enabled !== false,
enabled: enabled ?? true,
enableTrackpadTwoFingerGesture: enableTrackpadTwoFingerGesture,
activeOffsetX: [-dragOffsetFromRight, dragOffsetFromLeft],
activeOffsetX: [dragOffsetFromRight, dragOffsetFromLeft],
simultaneousWith,
requireToFail,
block,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SwipeableMethods>;

/**
Expand All @@ -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
Expand All @@ -66,16 +42,6 @@ export interface SwipeableProps {
*/
rightThreshold?: number;

/**
* The minimum horizontal distance from the starting point required to trigger a right-swipe gesture. Defaults to `10`.
*/
dragOffsetFromLeft?: number;

/**
* The minimum horizontal distance from the starting point required to trigger a left-swipe gesture. Defaults to `10`.
*/
dragOffsetFromRight?: number;

/**
* 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
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions skills/gesture-handler-3-migration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
j-piasecki marked this conversation as resolved.

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`.
Expand Down
Loading