/** Focus routing between tool windows and the editor area. */ import { setContext, useContextKeys } from "@/platform/context"; import { maybeEditorService } from "./contributions"; export const EDITOR_AREA_SELECTOR = "[data-editor-area]"; /** Moves focus to the active editor (or the editor area when none is open). */ export function focusEditor() { const service = maybeEditorService(); if (service?.focus) { service.focus(); return; } if (typeof document === "undefined") return; const area = document.querySelector(EDITOR_AREA_SELECTOR); const target = area?.querySelector(".cm-content, [data-editor-focus]") ?? area; target?.focus({ preventScroll: true }); } /** * Tracks which workbench part holds focus and publishes `toolWindowFocus`, * `activeToolWindow` and `editorAreaFocus`. Returns an uninstaller. */ export function installFocusTracking(onToolWindowFocus: (id: string | null) => void): () => void { const onFocusIn = (e: FocusEvent) => { const el = e.target as Element | null; const tw = el?.closest?.("[data-toolwindow]"); onToolWindowFocus(tw?.getAttribute("data-toolwindow") ?? null); setContext("editorAreaFocus", !!el?.closest?.(EDITOR_AREA_SELECTOR)); }; const onFocusOut = (e: FocusEvent) => { if (e.relatedTarget) return; // Focus left to nowhere (window blur keeps document.activeElement). queueMicrotask(() => { if (document.activeElement && document.activeElement !== document.body) return; onToolWindowFocus(null); setContext("editorAreaFocus", false); }); }; document.addEventListener("focusin", onFocusIn); document.addEventListener("focusout", onFocusOut); // IDEA: closing the last popup / menu / dialog never leaves focus nowhere. let popupOpen = !!useContextKeys.getState().popupOpen; let refocus: ReturnType | undefined; const unsubPopups = useContextKeys.subscribe((ctx) => { const open = !!ctx.popupOpen; if (open === popupOpen) return; popupOpen = open; clearTimeout(refocus); if (open) return; refocus = setTimeout(() => { if (!popupOpen && (!document.activeElement || document.activeElement === document.body)) focusEditor(); }); }); return () => { clearTimeout(refocus); unsubPopups(); document.removeEventListener("focusin", onFocusIn); document.removeEventListener("focusout", onFocusOut); }; }