Skip to content

Commit 5b535c9

Browse files
authored
feat: Add simultaneousWithExternalGesture to ReanimatedSwipable (#3324)
## Description This PR adds a new prop to `RenimatedSwipable` that allows adding a gesture config that can be used simultaneously with `ReanimatedSwipeable`'s gesture config. The new prop is called `simultaneousWithExternalGesture`, it uses the the cross-component interaction methodology that's mentioned in the [documentation here](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/gesture-composition/#simultaneouswithexternalgesture). I also update the docs for it ### Motivation I've been recently using ReanimatedSwipeable component, and I tried to add an additional gesture config to enable haptic feedback and "release to run function" kind of interaction After looking through the issues in the repo, I found this issue #2862, I tried to apply it, but it turned out that it was only applicable to the Swipable component I tried to find a way to add a gesture config that would work simultaneously with the swipeable one but I couldn't find a way to add it. So I looked through the documentation of RNGH for simultaneous gesture handlers and came up with this solution <!-- Description and motivation for this PR. Include 'Fixes #<number>' if this is fixing some issue. --> ## Test plan I already tested it locally on both iOS and Android, and the callbacks work as expected Not sure if there's something else to do here since this is my first contribution <!-- Describe how did you test this change here. -->
1 parent ce10992 commit 5b535c9

File tree

3 files changed

+115
-65
lines changed

3 files changed

+115
-65
lines changed

docs/docs/components/reanimated_swipeable.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,22 @@ style object for the container (`Animated.View`), for example to override `overf
128128

129129
style object for the children container (`Animated.View`), for example to apply `flex: 1`.
130130

131+
### `simultaneousWithExternalGesture`
132+
133+
A gesture configuration to be recognized simultaneously with the swipeable gesture. This is useful for allowing other gestures to work simultaneously with swipeable gesture handler.
134+
135+
For example, to enable a pan gesture alongside the swipeable gesture:
136+
137+
```jsx
138+
const panGesture = Gesture.Pan();
139+
140+
<GestureDetector gesture={panGesture}>
141+
<ReanimatedSwipeable simultaneousWithExternalGesture={panGesture} />
142+
</GestureDetector>
143+
```
144+
145+
More details can be found in the [gesture composition documentation](../fundamentals/gesture-composition.md#simultaneouswithexternalgesture).
146+
131147
### `enableTrackpadTwoFingerGesture` (iOS only)
132148

133149
Enables two-finger gestures on supported devices, for example iPads with trackpads.

example/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"baseUrl": ".",
66
"paths": {
77
"react-native-gesture-handler": ["../src/index.ts"],
8+
"react-native-gesture-handler/ReanimatedSwipeable": [
9+
"../src/components/ReanimatedSwipeable.tsx"
10+
],
811
"react-native-gesture-handler/jest-utils": ["../src/jestUtils/index.ts"]
912
}
1013
},

src/components/ReanimatedSwipeable.tsx

Lines changed: 96 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import React, {
99
useImperativeHandle,
1010
useMemo,
1111
} from 'react';
12+
import { GestureRef } from '../handlers/gestures/gesture';
1213
import { GestureObjects as Gesture } from '../handlers/gestures/gestureObjects';
1314
import { GestureDetector } from '../handlers/gestures/GestureDetector';
1415
import {
@@ -202,6 +203,14 @@ export interface SwipeableProps
202203
* apply `flex: 1`
203204
*/
204205
childrenContainerStyle?: StyleProp<ViewStyle>;
206+
207+
/**
208+
* A gesture object or an array of gesture objects containing the configuration and callbacks to be
209+
* used with the swipeable's gesture handler.
210+
*/
211+
simultaneousWithExternalGesture?:
212+
| Exclude<GestureRef, number>
213+
| Exclude<GestureRef, number>[];
205214
}
206215

207216
export interface SwipeableMethods {
@@ -247,6 +256,7 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
247256
onSwipeableClose,
248257
renderLeftActions,
249258
renderRightActions,
259+
simultaneousWithExternalGesture,
250260
...remainingProps
251261
} = props;
252262

@@ -635,73 +645,94 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
635645

636646
const dragStarted = useSharedValue<boolean>(false);
637647

638-
const tapGesture = useMemo(
639-
() =>
640-
Gesture.Tap()
641-
.shouldCancelWhenOutside(true)
642-
.onStart(() => {
643-
if (rowState.value !== 0) {
644-
close();
645-
}
646-
}),
647-
[close, rowState]
648-
);
649-
650-
const panGesture = useMemo(
651-
() =>
652-
Gesture.Pan()
653-
.enabled(enabled !== false)
654-
.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture)
655-
.activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge])
656-
.onStart(updateElementWidths)
657-
.onUpdate(
658-
(event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
659-
userDrag.value = event.translationX;
660-
661-
const direction =
662-
rowState.value === -1
663-
? SwipeDirection.RIGHT
664-
: rowState.value === 1
665-
? SwipeDirection.LEFT
666-
: event.translationX > 0
667-
? SwipeDirection.RIGHT
668-
: SwipeDirection.LEFT;
669-
670-
if (!dragStarted.value) {
671-
dragStarted.value = true;
672-
if (rowState.value === 0 && onSwipeableOpenStartDrag) {
673-
runOnJS(onSwipeableOpenStartDrag)(direction);
674-
} else if (onSwipeableCloseStartDrag) {
675-
runOnJS(onSwipeableCloseStartDrag)(direction);
676-
}
648+
const tapGesture = useMemo(() => {
649+
const tap = Gesture.Tap()
650+
.shouldCancelWhenOutside(true)
651+
.onStart(() => {
652+
if (rowState.value !== 0) {
653+
close();
654+
}
655+
});
656+
657+
if (!simultaneousWithExternalGesture) {
658+
return tap;
659+
}
660+
661+
if (Array.isArray(simultaneousWithExternalGesture)) {
662+
tap.simultaneousWithExternalGesture(...simultaneousWithExternalGesture);
663+
} else {
664+
tap.simultaneousWithExternalGesture(simultaneousWithExternalGesture);
665+
}
666+
667+
return tap;
668+
}, [close, rowState, simultaneousWithExternalGesture]);
669+
670+
const panGesture = useMemo(() => {
671+
const pan = Gesture.Pan()
672+
.enabled(enabled !== false)
673+
.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture)
674+
.activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge])
675+
.onStart(updateElementWidths)
676+
.onUpdate(
677+
(event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
678+
userDrag.value = event.translationX;
679+
680+
const direction =
681+
rowState.value === -1
682+
? SwipeDirection.RIGHT
683+
: rowState.value === 1
684+
? SwipeDirection.LEFT
685+
: event.translationX > 0
686+
? SwipeDirection.RIGHT
687+
: SwipeDirection.LEFT;
688+
689+
if (!dragStarted.value) {
690+
dragStarted.value = true;
691+
if (rowState.value === 0 && onSwipeableOpenStartDrag) {
692+
runOnJS(onSwipeableOpenStartDrag)(direction);
693+
} else if (onSwipeableCloseStartDrag) {
694+
runOnJS(onSwipeableCloseStartDrag)(direction);
677695
}
678-
679-
updateAnimatedEvent();
680-
}
681-
)
682-
.onEnd(
683-
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
684-
handleRelease(event);
685696
}
686-
)
687-
.onFinalize(() => {
688-
dragStarted.value = false;
689-
}),
690-
[
691-
dragOffsetFromLeftEdge,
692-
dragOffsetFromRightEdge,
693-
dragStarted,
694-
enableTrackpadTwoFingerGesture,
695-
enabled,
696-
handleRelease,
697-
onSwipeableCloseStartDrag,
698-
onSwipeableOpenStartDrag,
699-
rowState,
700-
updateAnimatedEvent,
701-
updateElementWidths,
702-
userDrag,
703-
]
704-
);
697+
698+
updateAnimatedEvent();
699+
}
700+
)
701+
.onEnd(
702+
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
703+
handleRelease(event);
704+
}
705+
)
706+
.onFinalize(() => {
707+
dragStarted.value = false;
708+
});
709+
710+
if (!simultaneousWithExternalGesture) {
711+
return pan;
712+
}
713+
714+
if (Array.isArray(simultaneousWithExternalGesture)) {
715+
pan.simultaneousWithExternalGesture(...simultaneousWithExternalGesture);
716+
} else {
717+
pan.simultaneousWithExternalGesture(simultaneousWithExternalGesture);
718+
}
719+
720+
return pan;
721+
}, [
722+
dragOffsetFromLeftEdge,
723+
dragOffsetFromRightEdge,
724+
dragStarted,
725+
enableTrackpadTwoFingerGesture,
726+
enabled,
727+
handleRelease,
728+
onSwipeableCloseStartDrag,
729+
onSwipeableOpenStartDrag,
730+
rowState,
731+
updateAnimatedEvent,
732+
updateElementWidths,
733+
userDrag,
734+
simultaneousWithExternalGesture,
735+
]);
705736

706737
useImperativeHandle(ref, () => swipeableMethods, [swipeableMethods]);
707738

0 commit comments

Comments
 (0)