-
-
Notifications
You must be signed in to change notification settings - Fork 19
Description
In my Expo app I’m using @mgcrea/react-native-dnd to let users drag recipe cards out of a horizontal list and drop them onto day‐slots elsewhere on screen. Because the stock gets its touch events swallowed by the ScrollView, I built a custom DraggableWrapper with useDraggable. Inside the scroll, cards drag fine—but the moment I move the finger beyond the horizontal list’s bounds, the card get hidden by the compoenents outside the scrollview and cannot be dragged utside of the ScrollView bounds.
Reproduction Steps
Wrap the entire app in a single inside a GestureHandlerRootView.
Use this DraggableWrapper.tsx for each card:
// DraggableWrapper.tsx
import React, { PropsWithChildren } from "react";
import {
Gesture,
GestureDetector,
State as RNGHState,
} from "react-native-gesture-handler";
import Animated, {
useAnimatedStyle,
withSpring,
runOnJS,
} from "react-native-reanimated";
import { useDraggable } from "@mgcrea/react-native-dnd";
import type { ViewStyle } from "react-native";
interface BaseProps {
id: string;
data?: any;
disabled?: boolean;
style?: ViewStyle;
onDragStart?: () => void;
onDragEnd?: () => void;
}
export type DraggableWrapperProps = PropsWithChildren<BaseProps>;
export const DraggableWrapper: React.FC<DraggableWrapperProps> = ({
id,
data,
disabled = false,
style,
onDragStart,
onDragEnd,
children,
}) => {
const {
offset,
props: draggableProps,
activeId,
panGestureState,
} = useDraggable({ id, data, disabled });
const panGesture = Gesture.Pan()
.onBegin(() => {
panGestureState.value = RNGHState.ACTIVE;
if (onDragStart) runOnJS(onDragStart)();
})
.onUpdate((e) => {
offset.x.value = e.translationX;
offset.y.value = e.translationY;
})
.onEnd(() => {
panGestureState.value = RNGHState.END;
offset.x.value = withSpring(0);
offset.y.value = withSpring(0);
if (onDragEnd) runOnJS(onDragEnd)();
});
const gesture = Gesture.Simultaneous(panGesture, Gesture.Native());
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: offset.x.value },
{ translateY: offset.y.value },
],
zIndex: activeId.value === id ? 999 : 1,
}));
return (
<GestureDetector gesture={gesture}>
<Animated.View
{...draggableProps}
style={[style, animatedStyle, { overflow: "visible" }]}
>
{children}
</Animated.View>
</GestureDetector>
);
};
Render your list like this:
// In your component
import { ScrollView as GHScrollView } from "react-native-gesture-handler";
import { DraggableWrapper } from "./DraggableWrapper";
<GHScrollView
horizontal
removeClippedSubviews={false}
style={{ overflow: "visible" }}
contentContainerStyle={{ overflow: "visible" }}
>
{recipes.map((recipe) => (
<DraggableWrapper key={recipe.id} id={recipe.id} style={{ margin: 8 }}>
<MealCard meal={recipe} onPress={handleMealCardPress} />
</DraggableWrapper>
))}
</GHScrollView>;
Long-press and drag a card. It moves within the list, but as soon as your finger crosses the list’s edge, the card is clipped/hidden and cannot be moved further or dropped outside.
Actual Behavior
- Cards drag only within the scroll view’s bounds.
- Once dragged beyond those bounds they get hidden behind compoenents outside the scrollview.
- useDraggable’s activeId remains set, but no drop events fire.
Expected Behavior
After activation, the card should lift out of the scroll container into a full-screen overlay, follow the finger freely in all directions, and onDragEnd should fire on release—regardless of leaving the original scroll bounds.