From 0259f11017f1165628fdac94575e5e6bc0a91389 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Wed, 29 Jul 2026 01:33:51 +0300 Subject: [PATCH] fix(ui): ignore prompt rail in escape abort guard --- .../src/hooks/keyboard-shortcut-dom.test.ts | 30 +++++++++++++++++++ .../ui/src/hooks/keyboard-shortcut-dom.ts | 8 +++++ packages/ui/src/hooks/useKeyboardShortcuts.ts | 8 +---- 3 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 packages/ui/src/hooks/keyboard-shortcut-dom.test.ts create mode 100644 packages/ui/src/hooks/keyboard-shortcut-dom.ts diff --git a/packages/ui/src/hooks/keyboard-shortcut-dom.test.ts b/packages/ui/src/hooks/keyboard-shortcut-dom.test.ts new file mode 100644 index 00000000..608bb203 --- /dev/null +++ b/packages/ui/src/hooks/keyboard-shortcut-dom.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'bun:test'; + +import { hasOpenDropdown } from './keyboard-shortcut-dom'; + +test('does not treat an unrelated visible listbox as an open dropdown', () => { + const promptNavigator = {} as Element; + const root = { + querySelector: (selector: string) => selector.includes('[role="listbox"]') ? promptNavigator : null, + } as unknown as ParentNode; + + expect(hasOpenDropdown(root)).toBe(false); +}); + +test('detects an open dropdown popup', () => { + const dropdown = {} as Element; + const root = { + querySelector: (selector: string) => selector.includes('[data-slot="dropdown-menu-content"][data-open]') ? dropdown : null, + } as unknown as ParentNode; + + expect(hasOpenDropdown(root)).toBe(true); +}); + +test('detects an open select popup', () => { + const select = {} as Element; + const root = { + querySelector: (selector: string) => selector.includes('[data-slot="select-content"][data-open]') ? select : null, + } as unknown as ParentNode; + + expect(hasOpenDropdown(root)).toBe(true); +}); diff --git a/packages/ui/src/hooks/keyboard-shortcut-dom.ts b/packages/ui/src/hooks/keyboard-shortcut-dom.ts new file mode 100644 index 00000000..413b6be2 --- /dev/null +++ b/packages/ui/src/hooks/keyboard-shortcut-dom.ts @@ -0,0 +1,8 @@ +const OPEN_DROPDOWN_SELECTOR = [ + '[data-slot="dropdown-menu-content"][data-open]', + '[data-slot="select-content"][data-open]', +].join(','); + +export function hasOpenDropdown(root: ParentNode = document): boolean { + return Boolean(root.querySelector(OPEN_DROPDOWN_SELECTOR)); +} diff --git a/packages/ui/src/hooks/useKeyboardShortcuts.ts b/packages/ui/src/hooks/useKeyboardShortcuts.ts index 16cf0b2c..c3f5a6f4 100644 --- a/packages/ui/src/hooks/useKeyboardShortcuts.ts +++ b/packages/ui/src/hooks/useKeyboardShortcuts.ts @@ -16,6 +16,7 @@ import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useProjectsStore } from '@/stores/useProjectsStore'; import { getCycledPrimaryAgentName } from '@/components/chat/mobileControlsUtils'; import { focusChatInput } from '@/components/chat/composer/editor/dom'; +import { hasOpenDropdown } from './keyboard-shortcut-dom'; export const useKeyboardShortcuts = () => { const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft); @@ -93,13 +94,6 @@ export const useKeyboardShortcuts = () => { return target instanceof Element && Boolean(target.closest(dropdownTargetSelector)); }; - const hasOpenDropdown = () => { - const openDropdowns = document.querySelectorAll( - '[data-slot="dropdown-menu-content"], [data-slot="select-content"], [role="listbox"], [role="menu"], [data-radix-popper-content-wrapper]' - ); - return Array.from(openDropdowns).some((element) => element.getClientRects().length > 0); - }; - const handleTerminalShortcutCapture = (e: KeyboardEvent) => { if (!isTerminalEventTarget(e.target)) { return;