From e62561feb90f009dc68ca27fde2ce2124edcede4 Mon Sep 17 00:00:00 2001 From: Kang Date: Thu, 25 Jun 2026 14:27:05 +0800 Subject: [PATCH] feat(layout): add pointer cancel handling to ActivityBar and improve drag end detection in TabBar - Implemented a new `handlePointerCancel` function in the ActivityBar to manage pointer cancellation events, enhancing drag-and-drop interactions. - Updated the TabBar component to utilize a callback for detecting drag end proximity to the tab strip, improving the user experience during tab management. --- src/components/layout/ActivityBar.tsx | 21 ++++++++++++++++- src/components/terminal/TabBar.tsx | 33 +++++++++++++++------------ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/components/layout/ActivityBar.tsx b/src/components/layout/ActivityBar.tsx index ca68adc5..068fc7cd 100644 --- a/src/components/layout/ActivityBar.tsx +++ b/src/components/layout/ActivityBar.tsx @@ -326,6 +326,22 @@ function DropZone({ [requestPointerDrop, resetDragState], ); + const handlePointerCancel = useCallback( + (event: PointerEvent) => { + const state = pointerDragRef.current; + if (!state || state.pointerId !== event.pointerId) return; + + if (event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.releasePointerCapture(event.pointerId); + } + resetDragState(); + window.setTimeout(() => { + suppressClickRef.current = false; + }, 0); + }, + [resetDragState], + ); + const handleDragStart = useCallback( (e: DragEvent, itemId: string) => { dragItemIdRef.current = itemId; @@ -402,6 +418,7 @@ function DropZone({ onPointerDown={(e) => handlePointerDown(e, item.id)} onPointerMove={handlePointerMove} onPointerEnd={handlePointerEnd} + onPointerCancel={handlePointerCancel} draggable={!usePointerDrag} suppressClickRef={suppressClickRef} showDropIndicator={dropIndex === idx} @@ -439,6 +456,7 @@ function ActivityBarButton({ onPointerDown, onPointerMove, onPointerEnd, + onPointerCancel, draggable, suppressClickRef, showDropIndicator, @@ -461,6 +479,7 @@ function ActivityBarButton({ onPointerDown: (e: PointerEvent) => void; onPointerMove: (e: PointerEvent) => void; onPointerEnd: (e: PointerEvent) => void; + onPointerCancel: (e: PointerEvent) => void; draggable: boolean; suppressClickRef: MutableRefObject; showDropIndicator: boolean; @@ -483,7 +502,7 @@ function ActivityBarButton({ onPointerDown={onPointerDown} onPointerMove={onPointerMove} onPointerUp={onPointerEnd} - onPointerCancel={onPointerEnd} + onPointerCancel={onPointerCancel} className={`relative flex flex-col items-center justify-center w-full transition-colors ${showLabel ? "min-h-12 gap-0.5 py-1" : "h-9"}`} style={{ color: active ? "var(--df-primary)" : "var(--df-text-muted)", diff --git a/src/components/terminal/TabBar.tsx b/src/components/terminal/TabBar.tsx index bb8074ec..358dd61a 100644 --- a/src/components/terminal/TabBar.tsx +++ b/src/components/terminal/TabBar.tsx @@ -710,6 +710,22 @@ function TabBar({ [tabs], ); + const isDragEndFallbackNearTabStrip = useCallback((clientX: number, clientY: number) => { + const strip = tabStripRef.current; + if (!strip || clientX === 0) return false; + + const rect = strip.getBoundingClientRect(); + const horizontalTolerance = 48; + const verticalTolerance = Math.max(96, rect.height * 3); + const isWithinHorizontalRange = + clientX >= rect.left - horizontalTolerance && clientX <= rect.right + horizontalTolerance; + if (!isWithinHorizontalRange) return false; + + if (clientY === 0) return true; + + return clientY >= rect.top - verticalTolerance && clientY <= rect.bottom + verticalTolerance; + }, []); + const handleDropAtIndex = useCallback( (insertionIndex: number, event?: DragEvent) => { const externalTabId = event?.dataTransfer.getData("application/nyaterm-tab"); @@ -759,20 +775,9 @@ function TabBar({ } const fallbackTabId = draggedTabIdRef.current; - const strip = tabStripRef.current; - if (fallbackTabId && strip && event.clientX !== 0) { - const rect = strip.getBoundingClientRect(); - const horizontalTolerance = 48; - const verticalTolerance = 16; - const isNearTabStrip = - event.clientX >= rect.left - horizontalTolerance && - event.clientX <= rect.right + horizontalTolerance && - event.clientY >= rect.top - verticalTolerance && - event.clientY <= rect.bottom + verticalTolerance; - if (isNearTabStrip) { - handleDropAtIndex(getInsertionIndexFromClientX(event.clientX)); - return; - } + if (fallbackTabId && isDragEndFallbackNearTabStrip(event.clientX, event.clientY)) { + handleDropAtIndex(getInsertionIndexFromClientX(event.clientX)); + return; } resetDragState();