Skip to content

Commit f2159b8

Browse files
m-bertCopilot
andauthored
Update Swipeable types (#4175)
## Description Some of the props passed to `Swipeable` can be `SharedValue`. However, we do not allow that in types, so TypeScript throws errors. Thank you @coado with help ❤️ ## Test plan Static checks --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent e316be2 commit f2159b8

6 files changed

Lines changed: 101 additions & 53 deletions

File tree

packages/docs-gesture-handler/docs/components/reanimated_swipeable.mdx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ This component is a drop-in replacement for the `Swipeable` component, rewritten
1616
</GifGallery>
1717

1818

19-
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.
19+
`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.
2020

21-
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:
21+
To use `ReanimatedSwipeable`, first ensure that Reanimated is installed and that your app is wrapped in `GestureHandlerRootView`. You can then import it as follows:
2222

2323
```ts
2424
import Swipeable from 'react-native-gesture-handler/ReanimatedSwipeable';
@@ -55,18 +55,18 @@ Distance from the right edge at which released panel will animate to the open st
5555
### dragOffsetFromLeft
5656

5757
```ts
58-
dragOffsetFromLeft?: number;
58+
dragOffsetFromLeft?: number | SharedValue<number>;
5959
```
6060

61-
The minimum horizontal distance from the starting point required to trigger a right-swipe gesture. Defaults to `10`.
61+
The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to `10`.
6262

6363
### dragOffsetFromRight
6464

6565
```ts
66-
dragOffsetFromRight?: number;
66+
dragOffsetFromRight?: number | SharedValue<number>;
6767
```
6868

69-
The minimum horizontal distance from the starting point required to trigger a left-swipe gesture. Defaults to `10`.
69+
The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to `-10`.
7070

7171
### overshootLeft
7272

@@ -240,28 +240,28 @@ blocksExternalGesture?: AnyGesture | AnyGesture[];
240240

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

243-
<Badges platforms={['ios']}>
243+
<Badges platforms={['ios', 'web']}>
244244
### enableTrackpadTwoFingerGesture
245245
</Badges>
246246

247247
```ts
248-
enableTrackpadTwoFingerGesture?: boolean;
248+
enableTrackpadTwoFingerGesture?: boolean | SharedValue<boolean>;
249249
```
250250

251251
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.
252252

253253
### enabled
254254

255255
```ts
256-
enabled: boolean;
256+
enabled?: boolean | SharedValue<boolean>;
257257
```
258258

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

261261
### testID
262262

263263
```ts
264-
testID: string;
264+
testID?: string;
265265
```
266266

267267
Sets a `testID` property, allowing for querying `ReanimatedSwipeable` for it in tests.
@@ -274,7 +274,7 @@ expandedLabel="Hide composed types definitions"
274274
lineBounds={[0, 1]}
275275
collapsed={false}
276276
src={`
277-
hitSlop: HitSlop | SharedValue<HitSlop>;
277+
hitSlop?: HitSlop | SharedValue<HitSlop>;
278278
279279
type HitSlop =
280280
| number

packages/docs-gesture-handler/docs/guides/upgrading-to-3.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,12 @@ Although the legacy JS implementation of the buttons is still available, they al
299299

300300
</details>
301301

302+
### ReanimatedSwipeable
303+
304+
`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).
305+
306+
`dragOffsetFromRight` now accepts negative values. If you were using it with positive values, make sure to change the sign when migrating.
307+
302308
### Touchables
303309

304310
[`Touchable`](/docs/components/touchable) can also be used as a substitute for old, deprecated `Touchables`.

packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { ForwardedRef } from 'react';
2-
import { useCallback, useImperativeHandle, useMemo } from 'react';
2+
import React, {
3+
useCallback,
4+
useEffect,
5+
useImperativeHandle,
6+
useMemo,
7+
} from 'react';
38
import type { LayoutChangeEvent } from 'react-native';
49
import { I18nManager, StyleSheet, View } from 'react-native';
510
import Animated, {
@@ -14,9 +19,16 @@ import Animated, {
1419
withSpring,
1520
} from 'react-native-reanimated';
1621

22+
import { Reanimated } from '../../handlers/gestures/reanimatedWrapper';
23+
import { tagMessage } from '../../utils';
1724
import { GestureDetector } from '../../v3/detectors';
1825
import type { PanGestureActiveEvent } from '../../v3/hooks/gestures';
1926
import { usePanGesture, useTapGesture } from '../../v3/hooks/gestures';
27+
import {
28+
maybeUnpackValue,
29+
SHARED_VALUE_OFFSET,
30+
} from '../../v3/hooks/utils/reanimatedUtils';
31+
import type { SharedValueOrT } from '../../v3/types';
2032
import type {
2133
SwipeableMethods,
2234
SwipeableProps,
@@ -45,7 +57,7 @@ const Swipeable = (props: SwipeableProps) => {
4557
children,
4658
enableTrackpadTwoFingerGesture = DEFAULT_ENABLE_TRACKING_TWO_FINGER_GESTURE,
4759
dragOffsetFromLeft = DEFAULT_DRAG_OFFSET,
48-
dragOffsetFromRight = DEFAULT_DRAG_OFFSET,
60+
dragOffsetFromRight = -DEFAULT_DRAG_OFFSET,
4961
friction = DEFAULT_FRICTION,
5062
overshootFriction = DEFAULT_OVERSHOOT_FRICTION,
5163
onSwipeableOpenStartDrag,
@@ -63,6 +75,40 @@ const Swipeable = (props: SwipeableProps) => {
6375
...remainingProps
6476
} = props;
6577

78+
if (__DEV__) {
79+
const checkValue = (value: SharedValueOrT<number>) => {
80+
'worklet';
81+
if (maybeUnpackValue<number>(value) > 0) {
82+
throw new Error(
83+
tagMessage('dragOffsetFromRight should be non-positive.')
84+
);
85+
}
86+
};
87+
88+
checkValue(dragOffsetFromRight);
89+
90+
// eslint-disable-next-line react-hooks/rules-of-hooks
91+
useEffect(() => {
92+
if (!Reanimated?.isSharedValue<number>(dragOffsetFromRight)) {
93+
return;
94+
}
95+
96+
const listenerId = Math.random() + SHARED_VALUE_OFFSET;
97+
98+
Reanimated?.runOnUI(() => {
99+
'worklet';
100+
dragOffsetFromRight.addListener(listenerId, checkValue);
101+
})();
102+
103+
return () => {
104+
Reanimated?.runOnUI(() => {
105+
'worklet';
106+
dragOffsetFromRight.removeListener(listenerId);
107+
})();
108+
};
109+
}, [dragOffsetFromRight, checkValue]);
110+
}
111+
66112
const shouldEnableTap = useSharedValue<boolean>(false);
67113
const rowState = useSharedValue<number>(0);
68114

@@ -468,9 +514,9 @@ const Swipeable = (props: SwipeableProps) => {
468514
});
469515

470516
const panGesture = usePanGesture({
471-
enabled: enabled !== false,
517+
enabled: enabled ?? true,
472518
enableTrackpadTwoFingerGesture: enableTrackpadTwoFingerGesture,
473-
activeOffsetX: [-dragOffsetFromRight, dragOffsetFromLeft],
519+
activeOffsetX: [dragOffsetFromRight, dragOffsetFromLeft],
474520
simultaneousWith,
475521
requireToFail,
476522
block,

packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ import type { StyleProp, ViewStyle } from 'react-native';
33
import type { SharedValue } from 'react-native-reanimated';
44

55
import type { HitSlop } from '../../handlers/gestureHandlerCommon';
6-
import type { AnyGesture } from '../../v3/types';
6+
import type { AnyGesture, WithSharedValue } from '../../v3/types';
77

88
export enum SwipeDirection {
99
LEFT = 'left',
1010
RIGHT = 'right',
1111
}
1212

13-
export interface SwipeableProps {
14-
/**
15-
*
16-
*/
13+
export type SwipeableProps = {
1714
ref?: React.Ref<SwipeableMethods>;
1815

1916
/**
@@ -23,27 +20,6 @@ export interface SwipeableProps {
2320

2421
children?: React.ReactNode;
2522

26-
/**
27-
* Indicates whether `ReanimatedSwipeable` should be analyzing stream of touch events or not.
28-
* @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#enabledvalue-boolean
29-
*/
30-
enabled?: boolean;
31-
32-
/**
33-
* This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
34-
* 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.
35-
* @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#hitslopsettings
36-
*/
37-
hitSlop?: HitSlop;
38-
39-
/**
40-
* Enables two-finger gestures on supported devices, for example iPads with
41-
* trackpads. If not enabled the gesture will require click + drag, with
42-
* `enableTrackpadTwoFingerGesture` swiping with two fingers will also trigger
43-
* the gesture.
44-
*/
45-
enableTrackpadTwoFingerGesture?: boolean;
46-
4723
/**
4824
* Specifies how much the visual interaction will be delayed compared to the
4925
* gesture distance. e.g. value of 1 will indicate that the swipeable panel
@@ -66,16 +42,6 @@ export interface SwipeableProps {
6642
*/
6743
rightThreshold?: number;
6844

69-
/**
70-
* The minimum horizontal distance from the starting point required to trigger a right-swipe gesture. Defaults to `10`.
71-
*/
72-
dragOffsetFromLeft?: number;
73-
74-
/**
75-
* The minimum horizontal distance from the starting point required to trigger a left-swipe gesture. Defaults to `10`.
76-
*/
77-
dragOffsetFromRight?: number;
78-
7945
/**
8046
* Value indicating if the swipeable panel can be pulled further than the left
8147
* actions panel's width. It is set to true by default as long as the left
@@ -202,7 +168,35 @@ export interface SwipeableProps {
202168
* used with the swipeable's gesture handler.
203169
*/
204170
block?: AnyGesture | AnyGesture[];
205-
}
171+
} & WithSharedValue<{
172+
/**
173+
* Indicates whether `ReanimatedSwipeable` should be analyzing stream of touch events or not.
174+
* @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#enabledvalue-boolean
175+
*/
176+
enabled?: boolean | undefined;
177+
/**
178+
* This parameter enables control over what part of the connected view area can be used to begin recognizing the gesture.
179+
* 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.
180+
* @see https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/pan-gesture#hitslopsettings
181+
*/
182+
hitSlop?: HitSlop | undefined;
183+
/**
184+
* Enables two-finger gestures on supported devices, for example iPads with
185+
* trackpads. If not enabled the gesture will require click + drag, with
186+
* `enableTrackpadTwoFingerGesture` swiping with two fingers will also trigger
187+
* the gesture.
188+
*/
189+
enableTrackpadTwoFingerGesture?: boolean | undefined;
190+
/**
191+
* The horizontal offset from the starting point required to trigger a right-swipe gesture. Defaults to 10.
192+
*/
193+
dragOffsetFromLeft?: number;
194+
195+
/**
196+
* The horizontal offset from the starting point required to trigger a left-swipe gesture. Defaults to -10.
197+
*/
198+
dragOffsetFromRight?: number;
199+
}>;
206200

207201
export interface SwipeableMethods {
208202
close: () => void;

packages/react-native-gesture-handler/src/v3/hooks/utils/reanimatedUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function hash(str: string) {
2020
return h >>> 0;
2121
}
2222

23-
const SHARED_VALUE_OFFSET = 1.618;
23+
export const SHARED_VALUE_OFFSET = 1.618;
2424

2525
// Don't transfer entire NativeProxy to the UI thread
2626
const { updateGestureHandlerConfig } = NativeProxy;

skills/gesture-handler-3-migration/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ The implementation of buttons has been updated, resolving most button-related is
186186

187187
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}`.
188188

189+
ReanimatedSwipeable prop `dragOffsetFromRight` now accepts negative values. If it was used with positive values, make sure to change the sign.
190+
189191
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.
190192

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

0 commit comments

Comments
 (0)