refactor(desktop): generalize focus check to isFocusWithin primitive

Replace the one-off isTerminalFocused with isFocusWithin(selector) in the
keybinds lib (beside isEditableTarget) — the reusable primitive for any
focus-scoped shortcut. The terminal marks itself data-terminal and the ⌘W
handler routes via isFocusWithin('[data-terminal]'); future surfaces just add
their own marker.
This commit is contained in:
Brooklyn Nicholson 2026-06-28 19:11:48 -05:00
parent 2d55ff8fca
commit b02f453496
4 changed files with 12 additions and 8 deletions

View file

@ -10,6 +10,7 @@ import { GatewayConnectingOverlay } from '@/components/gateway-connecting-overla
import { Pane, PaneMain } from '@/components/pane-shell'
import { RemoteDisplayBanner } from '@/components/remote-display-banner'
import { useMediaQuery } from '@/hooks/use-media-query'
import { isFocusWithin } from '@/lib/keybinds/combo'
import { cn } from '@/lib/utils'
import { useSkinCommand } from '@/themes/use-skin-command'
@ -129,7 +130,7 @@ import { ReviewPane } from './right-sidebar/review'
import { $terminalTakeover } from './right-sidebar/store'
import { TerminalPaneChrome } from './right-sidebar/terminal/chrome'
import { PersistentTerminal } from './right-sidebar/terminal/persistent'
import { closeActiveTerminal, isTerminalFocused } from './right-sidebar/terminal/terminals'
import { closeActiveTerminal } from './right-sidebar/terminal/terminals'
import { CRON_ROUTE, NEW_CHAT_ROUTE, routeSessionId, sessionRoute, SETTINGS_ROUTE } from './routes'
import { SessionPickerOverlay } from './session-picker-overlay'
import { SessionSwitcher } from './session-switcher'
@ -397,7 +398,7 @@ export function DesktopController() {
// Terminal focused: ⌘W closes the active terminal. Ctrl+W is left untouched
// for the shell's werase, and nothing else may steal ⌘/Ctrl+W from a
// focused terminal (so it never closes a preview tab out from under it).
if (isTerminalFocused()) {
if (isFocusWithin('[data-terminal]')) {
if (event.metaKey && !event.ctrlKey) {
event.preventDefault()
event.stopPropagation()

View file

@ -40,6 +40,8 @@ export function TerminalInstance({ id, active, cwd, onAddSelectionToChat }: Term
'absolute inset-0 flex flex-col bg-(--ui-editor-surface-background) px-2 pb-2 pt-0',
active ? 'visible' : 'invisible pointer-events-none'
)}
// Focus-scope marker so isFocusWithin('[data-terminal]') can route ⌘W here.
data-terminal=""
>
{status === 'starting' && (
<div className="pointer-events-none absolute inset-0 z-10 grid place-items-center">

View file

@ -95,12 +95,6 @@ export function closeActiveTerminal(): void {
}
}
/** True while an in-app terminal's xterm holds focus (its helper textarea is the
* active element) lets W close the focused terminal without a ref dance. */
export function isTerminalFocused(): boolean {
return document.activeElement?.classList.contains('xterm-helper-textarea') ?? false
}
export function closeOtherTerminals(id: string): void {
const keep = $terminals.get().find(term => term.id === id)

View file

@ -175,6 +175,13 @@ export function formatCombo(combo: string): string {
return IS_MAC ? tokens.join('') : tokens.join('+')
}
// True when focus currently sits inside an element matching `selector`. The
// primitive for focus-scoped shortcuts — e.g. routing ⌘W to whichever surface
// (terminal, preview, …) owns focus.
export function isFocusWithin(selector: string): boolean {
return document.activeElement?.closest(selector) != null
}
// True when focus is in a text-entry surface, so bare-key shortcuts don't fire
// while the user is typing.
export function isEditableTarget(target: EventTarget | null): boolean {