mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 02:32:47 +00:00
fix(app): release iOS sidebar rows after an interrupted drag (#7159)
An iOS pan waiting for manual activation can deliver finger-up without finalizing, leaving the row active and the outer scroller locked. Fail a pending pan when its touch stream ends so the existing finalization path cancels the drag. Track activation immediately so an active drag keeps its normal completion path. Based on the touch-release diagnosis in #5586. The native regression fails before the fix and passes on the Namespace iOS simulator. Co-authored-by: Kassam Khoja <kassam@shopwrk.com>
This commit is contained in:
co-authored by
Kassam Khoja
parent
78c7a9ce3a
commit
98559bf729
@@ -0,0 +1,13 @@
|
||||
context platform=ios timeout=60000 retries=0
|
||||
env APP_ID=sh.paseo.debug
|
||||
|
||||
# Start with the sidebar closed. WORKSPACE_ROW is the testID of a visible,
|
||||
# unselected workspace row. Hold past drag arming (180ms), below menu opening (450ms).
|
||||
open "${APP_ID}"
|
||||
press "label=\"Open menu\""
|
||||
wait "id=\"${WORKSPACE_ROW}\"" 10000
|
||||
longpress "id=\"${WORKSPACE_ROW}\"" 250
|
||||
press "id=\"${WORKSPACE_ROW}\""
|
||||
# A released drag must not swallow the next tap: navigation closes the sidebar.
|
||||
wait "label=\"Open menu\"" 5000
|
||||
close
|
||||
@@ -58,10 +58,14 @@ index 2abb4a2..8ca967f 100644
|
||||
onAnimValInit?: (animVals: ReturnType<typeof useAnimatedValues>) => void;
|
||||
itemEnteringAnimation?: AnimateProps<Animated.View>["entering"];
|
||||
diff --git a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
|
||||
index 7c88afc..6d5719c 100644
|
||||
index 7c88afc..a74140f 100644
|
||||
--- a/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
|
||||
+++ b/node_modules/react-native-draggable-flatlist/src/components/DraggableFlatList.tsx
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
@@ -16,9 +16,11 @@ import {
|
||||
FlatList,
|
||||
Gesture,
|
||||
GestureDetector,
|
||||
+ State,
|
||||
} from "react-native-gesture-handler";
|
||||
import Animated, {
|
||||
runOnJS,
|
||||
@@ -69,7 +73,7 @@ index 7c88afc..6d5719c 100644
|
||||
useAnimatedReaction,
|
||||
useAnimatedScrollHandler,
|
||||
useSharedValue,
|
||||
@@ -92,6 +93,17 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -92,6 +94,17 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
activeCellOffset.value = -1;
|
||||
setActiveKey(null);
|
||||
});
|
||||
@@ -87,7 +91,7 @@ index 7c88afc..6d5719c 100644
|
||||
|
||||
const {
|
||||
dragHitSlop = DEFAULT_PROPS.dragHitSlop,
|
||||
@@ -144,19 +156,88 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -144,19 +157,88 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
});
|
||||
}, [props.data, keyExtractor, keyToIndexRef]);
|
||||
|
||||
@@ -183,7 +187,7 @@ index 7c88afc..6d5719c 100644
|
||||
setActiveKey(activeKey);
|
||||
onDragBegin?.(index);
|
||||
}
|
||||
@@ -218,8 +299,25 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -218,8 +300,25 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
props.onRelease?.(index);
|
||||
});
|
||||
|
||||
@@ -210,7 +214,7 @@ index 7c88afc..6d5719c 100644
|
||||
const { onDragEnd, data } = props;
|
||||
|
||||
const newData = [...data];
|
||||
@@ -238,26 +336,6 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -238,26 +337,6 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
props.onPlaceholderIndexChange?.(index);
|
||||
});
|
||||
|
||||
@@ -237,7 +241,7 @@ index 7c88afc..6d5719c 100644
|
||||
useAnimatedReaction(
|
||||
() => {
|
||||
return spacerIndexAnim.value;
|
||||
@@ -271,11 +349,21 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -271,11 +350,24 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
);
|
||||
|
||||
const gestureDisabled = useSharedValue(false);
|
||||
@@ -257,10 +261,13 @@ index 7c88afc..6d5719c 100644
|
||||
+ gestureSessionActive.value = true;
|
||||
+ terminalHandled.value = false;
|
||||
+ runOnJS(onGestureBegin)(sessionId);
|
||||
+ panGestureState.value = evt.state;
|
||||
+ })
|
||||
+ .onStart((evt) => {
|
||||
panGestureState.value = evt.state;
|
||||
})
|
||||
.onUpdate((evt) => {
|
||||
@@ -286,8 +374,15 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -286,8 +378,15 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
: evt.translationY;
|
||||
touchTranslate.value = translation;
|
||||
})
|
||||
@@ -277,7 +284,7 @@ index 7c88afc..6d5719c 100644
|
||||
// Set touch val to current translate val
|
||||
isTouchActiveNative.value = false;
|
||||
const translation = horizontalAnim.value
|
||||
@@ -299,6 +394,7 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -299,6 +398,7 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
|
||||
// Only call onDragEnd if actually dragging a cell
|
||||
if (activeIndexAnim.value === -1 || disabled.value) return;
|
||||
@@ -285,7 +292,7 @@ index 7c88afc..6d5719c 100644
|
||||
disabled.value = true;
|
||||
runOnJS(onRelease)(activeIndexAnim.value);
|
||||
const springTo = placeholderOffset.value - activeCellOffset.value;
|
||||
@@ -309,14 +405,64 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -309,22 +409,84 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
runOnJS(onDragEnd)({
|
||||
from: activeIndexAnim.value,
|
||||
to: spacerIndexAnim.value,
|
||||
@@ -316,6 +323,7 @@ index 7c88afc..6d5719c 100644
|
||||
+ }
|
||||
runOnJS(onContainerTouchStart)();
|
||||
})
|
||||
- .onTouchesUp(() => {
|
||||
+ .onTouchesMove((event, stateManager) => {
|
||||
+ if (!coordinatesDragWithExternalGesture) return;
|
||||
+
|
||||
@@ -348,10 +356,20 @@ index 7c88afc..6d5719c 100644
|
||||
+ stateManager.activate();
|
||||
+ }
|
||||
+ })
|
||||
.onTouchesUp(() => {
|
||||
+ .onTouchesUp((event, stateManager) => {
|
||||
// Turning this into a worklet causes timing issues. We want it to run
|
||||
// just after the finger lifts.
|
||||
@@ -324,7 +470,9 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
runOnJS(onContainerTouchEnd)();
|
||||
+ // A manually activated iOS pan can remain BEGAN after the last finger
|
||||
+ // lifts. Fail it explicitly so onFinalize cancels the armed row.
|
||||
+ if (event.numberOfTouches === 0 && panGestureState.value === State.BEGAN) {
|
||||
+ stateManager.fail();
|
||||
+ }
|
||||
+ })
|
||||
+ .onTouchesCancelled((_event, stateManager) => {
|
||||
+ if (panGestureState.value === State.BEGAN) {
|
||||
+ stateManager.fail();
|
||||
+ }
|
||||
});
|
||||
|
||||
if (dragHitSlop) panGesture.hitSlop(dragHitSlop);
|
||||
@@ -362,7 +380,7 @@ index 7c88afc..6d5719c 100644
|
||||
const activeOffset = [-activationDistanceProp, activationDistanceProp];
|
||||
if (props.horizontal) {
|
||||
panGesture.activeOffsetX(activeOffset);
|
||||
@@ -332,6 +480,21 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -332,6 +494,21 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
panGesture.activeOffsetY(activeOffset);
|
||||
}
|
||||
}
|
||||
@@ -384,7 +402,7 @@ index 7c88afc..6d5719c 100644
|
||||
|
||||
const onScroll = useStableCallback((scrollOffset: number) => {
|
||||
props.onScrollOffsetChange?.(scrollOffset);
|
||||
@@ -373,8 +536,9 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
@@ -373,8 +550,9 @@ function DraggableFlatListInner<T>(props: DraggableFlatListProps<T>) {
|
||||
horizontal={!!props.horizontal}
|
||||
layoutAnimationDisabled={layoutAnimationDisabled}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user