diff --git a/packages/app/e2e/mobile/agent-device/native-sidebar-drag-release.ios.ad b/packages/app/e2e/mobile/agent-device/native-sidebar-drag-release.ios.ad new file mode 100644 index 0000000000..c94e24914d --- /dev/null +++ b/packages/app/e2e/mobile/agent-device/native-sidebar-drag-release.ios.ad @@ -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 diff --git a/patches/react-native-draggable-flatlist+4.0.3.patch b/patches/react-native-draggable-flatlist+4.0.3.patch index 79838c55e2..ddbdb92224 100644 --- a/patches/react-native-draggable-flatlist+4.0.3.patch +++ b/patches/react-native-draggable-flatlist+4.0.3.patch @@ -58,10 +58,14 @@ index 2abb4a2..8ca967f 100644 onAnimValInit?: (animVals: ReturnType) => void; itemEnteringAnimation?: AnimateProps["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(props: DraggableFlatListProps) { +@@ -92,6 +94,17 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { activeCellOffset.value = -1; setActiveKey(null); }); @@ -87,7 +91,7 @@ index 7c88afc..6d5719c 100644 const { dragHitSlop = DEFAULT_PROPS.dragHitSlop, -@@ -144,19 +156,88 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { +@@ -144,19 +157,88 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { }); }, [props.data, keyExtractor, keyToIndexRef]); @@ -183,7 +187,7 @@ index 7c88afc..6d5719c 100644 setActiveKey(activeKey); onDragBegin?.(index); } -@@ -218,8 +299,25 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { +@@ -218,8 +300,25 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { props.onRelease?.(index); }); @@ -210,7 +214,7 @@ index 7c88afc..6d5719c 100644 const { onDragEnd, data } = props; const newData = [...data]; -@@ -238,26 +336,6 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { +@@ -238,26 +337,6 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { props.onPlaceholderIndexChange?.(index); }); @@ -237,7 +241,7 @@ index 7c88afc..6d5719c 100644 useAnimatedReaction( () => { return spacerIndexAnim.value; -@@ -271,11 +349,21 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { +@@ -271,11 +350,24 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { ); 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(props: DraggableFlatListProps) { +@@ -286,8 +378,15 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { : 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(props: DraggableFlatListProps) { +@@ -299,6 +398,7 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { // 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(props: DraggableFlatListProps) { +@@ -309,22 +409,84 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { 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(props: DraggableFlatListProps) { + 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(props: DraggableFlatListProps) { +@@ -332,6 +494,21 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { 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(props: DraggableFlatListProps) { +@@ -373,8 +550,9 @@ function DraggableFlatListInner(props: DraggableFlatListProps) { horizontal={!!props.horizontal} layoutAnimationDisabled={layoutAnimationDisabled} >