From 7da166c55de7f1e738450c377b03de3e4e4d45bc Mon Sep 17 00:00:00 2001 From: PathGao Date: Wed, 23 Sep 2026 05:47:03 +0800 Subject: [PATCH] fix(tabs): on a maximized window, the screen's top edge selects the tab Tabs are 28px in a 36px title bar, and the 4px strip above each tab is window drag region. On a maximized window that strip is the screen's top edge, so flicking the mouse to the top and clicking moved the window instead of picking the tab (#823). When the window is maximized on Windows or Linux, each tab's wrapper now spans the full bar height and a click on it outside the tab selects that tab. Chrome on Windows does the same. The tab looks the same. Restored windows keep the strip as drag region, and macOS is left alone because its menu bar sits above a maximized window. --- src/lib/components/TabList.svelte | 34 ++++++++++++++++++++++++------ src/lib/components/TitleBar.svelte | 15 ++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/lib/components/TabList.svelte b/src/lib/components/TabList.svelte index 094e8724..fb651232 100644 --- a/src/lib/components/TabList.svelte +++ b/src/lib/components/TabList.svelte @@ -61,12 +61,20 @@ items: [], }); + function selectTab(tab: TabData) { + if (justDragged) return; + tabManager.setActive(tab.id); + ontabclick?.(); + } + function handleMouseDown(e: MouseEvent, tab: TabData, element: HTMLElement) { if (e.button !== 0) return; e.stopPropagation(); e.preventDefault(); - const rect = element.getBoundingClientRect(); + // The wrapper is as tall as the title bar; the drag proxy has to line up + // with the tab drawn inside it. + const rect = (element.firstElementChild ?? element).getBoundingClientRect(); dragState = { startX: e.clientX, currentX: e.clientX, @@ -204,22 +212,24 @@ }}> {#each tabManager.tabs as tab, i (tab.id)} +
handleMouseDown(e, tab, e.currentTarget as HTMLElement)}> + onmousedown={(e) => handleMouseDown(e, tab, e.currentTarget as HTMLElement)} + onclick={(e) => { + // Only reachable when maximized, where the wrapper is taller + // than the tab (#823). Clicks inside the tab are the tab's own. + if (e.target === e.currentTarget) selectTab(tab); + }}> { - if (justDragged) return; - tabManager.setActive(tab.id); - ontabclick?.(); - }} + onclick={() => selectTab(tab)} onclose={() => oncloseTab?.(tab.id)} />
{/each} @@ -340,6 +350,16 @@ transition: opacity 0.1s; } + /* Maximized on Windows/Linux, the strip above a tab is the screen's top + edge. Give it to the tab, as Chrome does, so a flick to the edge picks the + tab instead of dragging the window (#823). Restored windows keep the strip + as drag region. */ + :global(.custom-title-bar.maximized) .tab-item-wrapper { + display: flex; + align-items: center; + height: 100%; + } + .tab-item-wrapper.drag-opacity { opacity: 0; pointer-events: none; diff --git a/src/lib/components/TitleBar.svelte b/src/lib/components/TitleBar.svelte index 8fc0cccb..b5ed1386 100644 --- a/src/lib/components/TitleBar.svelte +++ b/src/lib/components/TitleBar.svelte @@ -255,6 +255,19 @@ }); }); + // Windows and Linux only: on macOS the top of the screen is the menu bar, so + // a maximized window's tabs never sit on the screen edge (#823). + let isMaximized = $state(false); + $effect(() => { + if (isMac) return; + const sync = () => appWindow.isMaximized().then((v) => (isMaximized = v)).catch(() => {}); + sync(); + const unlisten = appWindow.onResized(sync); + return () => { + unlisten.then((f) => f()); + }; + }); + let tooltip = $state({ visible: false, text: '', @@ -402,7 +415,7 @@ -
+
{#if !isMac && !isWin11}
{/if}