diff --git a/media/webview.css b/media/webview.css index e01bd73..cd89d7a 100644 --- a/media/webview.css +++ b/media/webview.css @@ -1234,3 +1234,11 @@ body.vscode-high-contrast #grid-container.cm-on { color: var(--vscode-charts-orange, #d18616) !important; font-weight: 700 !important; } +/* Override AG Grid's default Alpine popup styles to match the extension's popovers. */ +:is(.ag-theme-alpine-dark, .ag-theme-alpine) .ag-popup .ag-menu, +:is(.ag-theme-alpine-dark, .ag-theme-alpine) .ag-popup .ag-filter { + background: var(--vscode-menu-background, #252526); + border: 1px solid var(--vscode-menu-border, #454545); + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, .45); +} diff --git a/src/webview/features/popups.ts b/src/webview/features/popups.ts index c7f691c..30b68de 100644 --- a/src/webview/features/popups.ts +++ b/src/webview/features/popups.ts @@ -1,3 +1,5 @@ +import { state } from '../state'; + // ── Central popup coordinator (issue #15) ───────────────────────────────────── // The grid has several transient popups: the column and row context menus, the // Export and Delimiter dropdowns, the column-chooser and go-to-row popovers and @@ -28,3 +30,38 @@ export function closeAllPopups(except?: string): void { document.getElementById(id)?.classList.add('hidden'); } } + +// Checks if any coordinated popup is currently visible. +// This ensures the global Esc handler only consumes the keystroke when +// there is actually a popup to dismiss, leaving Esc free for standard +// tasks (like canceling a cell edit). +export function isAnyPopupOpen(): boolean { + return POPUP_IDS.some(id => { + const el = document.getElementById(id); + return el != null && !el.classList.contains('hidden'); + }); +} + +// Sets up a global Esc key listener to dismiss all popups (wired once at startup). +// - Uses the capture phase (true) to intercept the event before AG Grid consumes it. +// - Does NOT call stopPropagation to allow input-focused popups (rename, go-to-row) +// to run their own focus-bound Escape handlers and state cleanup. +// Other popups (context menus, dropdowns) are simply hidden by closeAllPopups(). +export function setupPopups(): void { + document.addEventListener('keydown', (e) => { + if (e.key !== 'Escape') return; + + // AG Grid's filter panel lives outside POPUP_IDS (it has no fixed id). + // Detect it in the DOM and let AG Grid close it. + const agOpen = document.querySelector('.ag-popup .ag-menu, .ag-popup .ag-filter'); + if (agOpen) { + (state.gridApi as any)?.hidePopupMenu?.(); + e.preventDefault(); + return; + } + + if (!isAnyPopupOpen()) return; + closeAllPopups(); + e.preventDefault(); + }, true); +} diff --git a/src/webview/index.ts b/src/webview/index.ts index 3776c40..4ab1419 100644 --- a/src/webview/index.ts +++ b/src/webview/index.ts @@ -18,6 +18,7 @@ import { setupColumnChooser } from './features/column-chooser'; import { setupColorMode } from './features/color-mode'; import { setupKeyboard } from './keyboard'; import { setupMessaging } from './messaging'; +import { setupPopups } from './features/popups'; setupTheme(); setupUndoRedo(); @@ -39,5 +40,6 @@ setupColumnChooser(); setupColorMode(); setupKeyboard(); setupMessaging(); +setupPopups(); vscodeApi.postMessage({ type: 'ready' });