refactor(components): simplify SwipeableRow gesture handling and add vertical scroll conflict prevention

This commit is contained in:
2026-08-08 23:43:34 +02:00
parent 819d26e0f6
commit df3030ae72
+15 -18
View File
@@ -1,6 +1,6 @@
import { useRef } from "react"; import { useRef } from "react";
import { View, Text, Pressable, Animated, StyleSheet } from "react-native"; import { View, Text, Pressable, Animated, StyleSheet } from "react-native";
import { PanGestureHandler, State } from "react-native-gesture-handler"; import { PanGestureHandler, State, PanGestureHandlerGestureEvent } from "react-native-gesture-handler";
type SwipeableRowProps = { type SwipeableRowProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -8,37 +8,33 @@ type SwipeableRowProps = {
deleteLabel?: string; deleteLabel?: string;
}; };
const MAX_LEFT = -80;
export function SwipeableRow({ children, onDelete, deleteLabel = "Delete" }: SwipeableRowProps) { export function SwipeableRow({ children, onDelete, deleteLabel = "Delete" }: SwipeableRowProps) {
const translateX = useRef(new Animated.Value(0)).current; const translateX = useRef(new Animated.Value(0)).current;
const lastOffset = useRef(0); const offset = useRef(0);
const onGestureEvent = Animated.event([{ nativeEvent: { translationX: translateX } }], { const onGestureEvent = (event: PanGestureHandlerGestureEvent) => {
useNativeDriver: true, const raw = event.nativeEvent.translationX;
}); const clamped = Math.max(MAX_LEFT, Math.min(0, offset.current + raw));
translateX.setValue(clamped);
};
const onHandlerStateChange = (event: any) => { const onHandlerStateChange = (event: PanGestureHandlerGestureEvent) => {
if (event.oldState === State.ACTIVE) { if (event.oldState === State.ACTIVE) {
const { translationX } = event.nativeEvent; const { translationX } = event.nativeEvent;
const clampedTranslation = Math.min(0, translationX); const final = Math.max(MAX_LEFT, Math.min(0, offset.current + translationX));
const newOffset = lastOffset.current + clampedTranslation;
if (newOffset < -80) {
Animated.spring(translateX, { Animated.spring(translateX, {
toValue: -80, toValue: final,
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
lastOffset.current = -80; offset.current = final;
} else {
Animated.spring(translateX, {
toValue: 0,
useNativeDriver: true,
}).start();
lastOffset.current = 0;
}
} }
}; };
const handleDelete = () => { const handleDelete = () => {
offset.current = -400;
Animated.timing(translateX, { Animated.timing(translateX, {
toValue: -400, toValue: -400,
duration: 200, duration: 200,
@@ -58,6 +54,7 @@ export function SwipeableRow({ children, onDelete, deleteLabel = "Delete" }: Swi
<PanGestureHandler <PanGestureHandler
onGestureEvent={onGestureEvent} onGestureEvent={onGestureEvent}
onHandlerStateChange={onHandlerStateChange} onHandlerStateChange={onHandlerStateChange}
failOffsetY={[-5, 5]}
> >
<Animated.View style={[styles.foreground, { transform: [{ translateX }] }]}> <Animated.View style={[styles.foreground, { transform: [{ translateX }] }]}>
{children} {children}