diff --git a/frontend/components/layout/TabBar.tsx b/frontend/components/layout/TabBar.tsx index 8ef3d0b1..e5a687c6 100644 --- a/frontend/components/layout/TabBar.tsx +++ b/frontend/components/layout/TabBar.tsx @@ -285,6 +285,11 @@ export function TabBar() { if ((e.target as HTMLElement).closest("button")) return; if (dragState?.settling) return; + if (settleTimer.current) { + clearTimeout(settleTimer.current); + settleTimer.current = null; + } + e.preventDefault(); (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId); @@ -304,6 +309,7 @@ export function TabBar() { const handlePointerMove = useCallback( (e: React.PointerEvent) => { if (!dragState || dragState.settling) return; + if (e.pointerId !== dragState.pointerId) return; const bar = containerRef.current?.getBoundingClientRect(); if (!bar) return; @@ -334,6 +340,7 @@ export function TabBar() { const handlePointerUp = useCallback( (e: React.PointerEvent) => { if (!dragState || dragState.settling) return; + if (e.pointerId !== dragState.pointerId) return; (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId); if (!dragState.activated) { @@ -365,6 +372,16 @@ export function TabBar() { [dragState, tabs, displayVisible, switchTab, updateTabOrder], ); + const handleLostPointerCapture = useCallback(() => { + if (dragState && !dragState.settling) { + setDragState(null); + if (settleTimer.current) { + clearTimeout(settleTimer.current); + settleTimer.current = null; + } + } + }, [dragState]); + // ============================================================ // 스타일 계산 // ============================================================ @@ -471,6 +488,7 @@ export function TabBar() { onPointerDown={(e) => handlePointerDown(e, tab.id, displayIndex)} onPointerMove={handlePointerMove} onPointerUp={handlePointerUp} + onLostPointerCapture={handleLostPointerCapture} onContextMenu={(e) => handleContextMenu(e, tab.id)} className={cn( "group relative flex h-7 shrink-0 cursor-pointer items-center gap-0.5 rounded-t-md border border-b-0 px-3 select-none",