Fix mobile sidebar swipe and drag failures (#2709)

* fix(app): prevent text selection during Android swipes

Gesture interception did not cancel the native view where the touch began, leaving Android's pending text-selection action alive.

* fix(app): restore iOS sidebar close swipes

Fabric could fail to attach close handlers when their target lived inside retained but display-hidden panel content. Keep a registered native gesture host mounted outside the presentation wrapper so close swipes work over nested lists without activating rows.

* fix(app): recover sidebar drags after cancellation

Cancelled native drags could leave a lifted row and the shared outer list locked. Scope terminal cleanup to the owning gesture session and release the lock on cancellation or unmount.

* test(app): clean up partial drag fixtures
This commit is contained in:
Mohamed Boudra
2026-08-01 04:05:54 +08:00
committed by GitHub
parent eb1c7def54
commit 3778d0ad7b
11 changed files with 966 additions and 22 deletions
@@ -249,6 +249,7 @@ interface SidebarWorkspaceListProps {
listHeaderComponent?: ReactElement | null;
/** Gesture ref for coordinating with parent gestures (e.g., sidebar close) */
parentGestureRef?: MutableRefObject<GestureType | undefined>;
dragGestureHostPresented?: boolean;
}
interface ProjectHeaderRowProps {
@@ -1605,6 +1606,7 @@ function ProjectBlock({
isDragging,
dragHandleProps,
useNestable,
dragGestureHostPresented,
creatingWorkspaceIds,
activeWorkspaceSelection,
hostLabelByServerId,
@@ -1630,6 +1632,7 @@ function ProjectBlock({
isDragging: boolean;
dragHandleProps?: DraggableListDragHandleProps;
useNestable: boolean;
dragGestureHostPresented?: boolean;
creatingWorkspaceIds: ReadonlySet<string>;
activeWorkspaceSelection: ActiveWorkspaceSelection | null;
hostLabelByServerId: ReadonlyMap<string, string>;
@@ -1806,6 +1809,7 @@ function ProjectBlock({
useDragHandle
nestable={useNestable}
simultaneousGestureRef={parentGestureRef}
gestureHostPresented={dragGestureHostPresented}
containerStyle={styles.workspaceListContainer}
/>
{canToggleWorkspaces ? (
@@ -1886,6 +1890,7 @@ function areProjectBlockPropsEqual(previous: ProjectBlockProps, next: ProjectBlo
previous.isDragging === next.isDragging &&
previous.dragHandleProps === next.dragHandleProps &&
previous.useNestable === next.useNestable &&
previous.dragGestureHostPresented === next.dragGestureHostPresented &&
previous.creatingWorkspaceIds === next.creatingWorkspaceIds &&
areProjectBlockSelectionsEqual(previous, next)
);
@@ -1936,6 +1941,7 @@ export function SidebarWorkspaceList({
listFooterComponent,
listHeaderComponent,
parentGestureRef,
dragGestureHostPresented,
}: SidebarWorkspaceListProps) {
const pathname = usePathname();
const hosts = useHosts();
@@ -1991,6 +1997,7 @@ export function SidebarWorkspaceList({
listFooterComponent={listFooterComponent}
listHeaderComponent={listHeaderComponent}
parentGestureRef={parentGestureRef}
dragGestureHostPresented={dragGestureHostPresented}
pathname={pathname}
hostLabelByServerId={hostLabelByServerId}
showHostLabels={showHostLabels}
@@ -2065,6 +2072,7 @@ function ProjectModeList({
listFooterComponent,
listHeaderComponent,
parentGestureRef,
dragGestureHostPresented,
pathname,
hostLabelByServerId,
showHostLabels,
@@ -2279,6 +2287,7 @@ function ProjectModeList({
isDragging={dragState.isDragging}
dragHandleProps={dragState.dragHandleProps}
useNestable={platformIsNative}
dragGestureHostPresented={dragGestureHostPresented}
creatingWorkspaceIds={creatingWorkspaceIds}
activeWorkspaceSelection={activeWorkspaceSelection}
hostLabelByServerId={hostLabelByServerId}
@@ -2302,6 +2311,7 @@ function ProjectModeList({
onWorkspacePress,
onToggleProjectCollapsed,
parentGestureRef,
dragGestureHostPresented,
projectIconByProjectViewKey,
selectionEnabled,
shortcutIndexByWorkspaceKey,
@@ -2399,6 +2409,7 @@ function ProjectModeList({
useDragHandle
nestable={platformIsNative}
simultaneousGestureRef={parentGestureRef}
gestureHostPresented={dragGestureHostPresented}
containerStyle={styles.projectListContainer}
/>
)}