From ba1ecedca3732427578eeba1ed332d802574f562 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 10 Apr 2026 18:51:46 +0700 Subject: [PATCH] feat: middle-click to close tabs on desktop (#232) Add useMiddleClickClose hook that listens for auxclick (button === 1) events on web, enabling middle-click-to-close behavior on tab chips. --- .../workspace/workspace-desktop-tabs-row.tsx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx index 110f01970..93493da3b 100644 --- a/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx +++ b/packages/app/src/screens/workspace/workspace-desktop-tabs-row.tsx @@ -1,4 +1,12 @@ -import { useCallback, useMemo, useState, type Dispatch, type SetStateAction } from "react"; +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, + type Dispatch, + type SetStateAction, +} from "react"; import { ActivityIndicator, Platform, @@ -101,6 +109,27 @@ function getFallbackTabLabel(tab: WorkspaceTabDescriptor): string { return "Agent"; } +function useMiddleClickClose(onClose: () => void) { + const ref = useRef(null); + + useEffect(() => { + const node = ref.current as unknown as HTMLElement | null; + if (!node) return; + + function handleAuxClick(event: MouseEvent) { + if (event.button === 1) { + event.preventDefault(); + onClose(); + } + } + + node.addEventListener("auxclick", handleAuxClick); + return () => node.removeEventListener("auxclick", handleAuxClick); + }, [onClose]); + + return ref; +} + function TabChip({ tab, isActive, @@ -140,6 +169,9 @@ function TabChip({ }) { const { theme } = useUnistyles(); const { closeButtonTestId, contextMenuTestId, menuEntries } = resolvedTab; + const middleClickRef = useMiddleClickClose( + useCallback(() => void onCloseTab(tab.tabId), [onCloseTab, tab.tabId]), + ); const [hovered, setHovered] = useState(false); const isHighlighted = isActive || hovered || isCloseHovered; const closeButtonDragBlockers = @@ -155,6 +187,7 @@ function TabChip({ : undefined; return ( + @@ -325,6 +358,7 @@ function TabChip({ )} + ); }