Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add simultaneousWithExternalGesture to ReanimatedSwipable #3324

16 changes: 16 additions & 0 deletions docs/docs/components/reanimated_swipeable.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ style object for the container (`Animated.View`), for example to override `overf

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

### `simultaneousWithExternalGesture`

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.

For example, to enable a pan gesture alongside the swipeable gesture:

```jsx
const panGesture = Gesture.Pan();

<GestureDetector gesture={panGesture}>
<ReanimatedSwipeable simultaneousWithExternalGesture={panGesture} />
</GestureDetector>
```

More details can be found in the [gesture composition documentation](../fundamentals/gesture-composition.md#simultaneouswithexternalgesture).

### `enableTrackpadTwoFingerGesture` (iOS only)

Enables two-finger gestures on supported devices, for example iPads with trackpads.
Expand Down
3 changes: 3 additions & 0 deletions example/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"baseUrl": ".",
"paths": {
"react-native-gesture-handler": ["../src/index.ts"],
"react-native-gesture-handler/ReanimatedSwipeable": [
"../src/components/ReanimatedSwipeable.tsx"
],
"react-native-gesture-handler/jest-utils": ["../src/jestUtils/index.ts"]
}
},
Expand Down
161 changes: 96 additions & 65 deletions src/components/ReanimatedSwipeable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, {
useImperativeHandle,
useMemo,
} from 'react';
import { GestureRef } from '../handlers/gestures/gesture';
import { GestureObjects as Gesture } from '../handlers/gestures/gestureObjects';
import { GestureDetector } from '../handlers/gestures/GestureDetector';
import {
Expand Down Expand Up @@ -202,6 +203,14 @@ export interface SwipeableProps
* apply `flex: 1`
*/
childrenContainerStyle?: StyleProp<ViewStyle>;

/**
* A gesture object or an array of gesture objects containing the configuration and callbacks to be
* used with the swipeable's gesture handler.
*/
simultaneousWithExternalGesture?:
| Exclude<GestureRef, number>
| Exclude<GestureRef, number>[];
}

export interface SwipeableMethods {
Expand Down Expand Up @@ -247,6 +256,7 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(
onSwipeableClose,
renderLeftActions,
renderRightActions,
simultaneousWithExternalGesture,
...remainingProps
} = props;

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

const dragStarted = useSharedValue<boolean>(false);

const tapGesture = useMemo(
() =>
Gesture.Tap()
.shouldCancelWhenOutside(true)
.onStart(() => {
if (rowState.value !== 0) {
close();
}
}),
[close, rowState]
);

const panGesture = useMemo(
() =>
Gesture.Pan()
.enabled(enabled !== false)
.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture)
.activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge])
.onStart(updateElementWidths)
.onUpdate(
(event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
userDrag.value = event.translationX;

const direction =
rowState.value === -1
? SwipeDirection.RIGHT
: rowState.value === 1
? SwipeDirection.LEFT
: event.translationX > 0
? SwipeDirection.RIGHT
: SwipeDirection.LEFT;

if (!dragStarted.value) {
dragStarted.value = true;
if (rowState.value === 0 && onSwipeableOpenStartDrag) {
runOnJS(onSwipeableOpenStartDrag)(direction);
} else if (onSwipeableCloseStartDrag) {
runOnJS(onSwipeableCloseStartDrag)(direction);
}
const tapGesture = useMemo(() => {
const tap = Gesture.Tap()
.shouldCancelWhenOutside(true)
.onStart(() => {
if (rowState.value !== 0) {
close();
}
});

if (!simultaneousWithExternalGesture) {
return tap;
}

if (Array.isArray(simultaneousWithExternalGesture)) {
tap.simultaneousWithExternalGesture(...simultaneousWithExternalGesture);
} else {
tap.simultaneousWithExternalGesture(simultaneousWithExternalGesture);
}

return tap;
}, [close, rowState, simultaneousWithExternalGesture]);

const panGesture = useMemo(() => {
const pan = Gesture.Pan()
.enabled(enabled !== false)
.enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture)
.activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge])
.onStart(updateElementWidths)
.onUpdate(
(event: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
userDrag.value = event.translationX;

const direction =
rowState.value === -1
? SwipeDirection.RIGHT
: rowState.value === 1
? SwipeDirection.LEFT
: event.translationX > 0
? SwipeDirection.RIGHT
: SwipeDirection.LEFT;

if (!dragStarted.value) {
dragStarted.value = true;
if (rowState.value === 0 && onSwipeableOpenStartDrag) {
runOnJS(onSwipeableOpenStartDrag)(direction);
} else if (onSwipeableCloseStartDrag) {
runOnJS(onSwipeableCloseStartDrag)(direction);
}

updateAnimatedEvent();
}
)
.onEnd(
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
handleRelease(event);
}
)
.onFinalize(() => {
dragStarted.value = false;
}),
[
dragOffsetFromLeftEdge,
dragOffsetFromRightEdge,
dragStarted,
enableTrackpadTwoFingerGesture,
enabled,
handleRelease,
onSwipeableCloseStartDrag,
onSwipeableOpenStartDrag,
rowState,
updateAnimatedEvent,
updateElementWidths,
userDrag,
]
);

updateAnimatedEvent();
}
)
.onEnd(
(event: GestureStateChangeEvent<PanGestureHandlerEventPayload>) => {
handleRelease(event);
}
)
.onFinalize(() => {
dragStarted.value = false;
});

if (!simultaneousWithExternalGesture) {
return pan;
}

if (Array.isArray(simultaneousWithExternalGesture)) {
pan.simultaneousWithExternalGesture(...simultaneousWithExternalGesture);
} else {
pan.simultaneousWithExternalGesture(simultaneousWithExternalGesture);
}

return pan;
}, [
dragOffsetFromLeftEdge,
dragOffsetFromRightEdge,
dragStarted,
enableTrackpadTwoFingerGesture,
enabled,
handleRelease,
onSwipeableCloseStartDrag,
onSwipeableOpenStartDrag,
rowState,
updateAnimatedEvent,
updateElementWidths,
userDrag,
simultaneousWithExternalGesture,
]);

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

Expand Down
Loading