diff --git a/frontend/src/components/server/TerminalDialog.tsx b/frontend/src/components/server/TerminalDialog.tsx index b0b377d..669784a 100644 --- a/frontend/src/components/server/TerminalDialog.tsx +++ b/frontend/src/components/server/TerminalDialog.tsx @@ -132,12 +132,6 @@ export function TerminalDialog({ const ws = new WebSocket(wsUrl); wsRef.current = ws; - // xterm 在目标元素阶段直接转发 Escape,避免 Radix 捕获层吞掉 Vim 控制字符。 // Logan.Li - term.attachCustomKeyEventHandler((event) => { - if (event.key !== "Escape") return true; - if (event.type === "keydown") sendTerminalInput("\x1b"); - return false; - }); term.onData(sendTerminalInput); ws.onmessage = (event) => { @@ -189,6 +183,7 @@ export function TerminalDialog({ if (!open || !authReady) return; let cleanup: (() => void) | undefined; + let detachEscape: (() => void) | undefined; let fitFrame = 0; let retry = 0; @@ -201,6 +196,16 @@ export function TerminalDialog({ if (retry++ < 30) requestAnimationFrame(attach); return; } + // 在 xterm 目标元素之前截获 Escape,直接发送给远端并阻止 Radix/xterm 重复处理。 // Logan.Li + const forwardEscape = (event: KeyboardEvent) => { + if (event.key !== "Escape") return; + event.preventDefault(); + event.stopPropagation(); + sendTerminalInput("\x1b"); + }; + container.addEventListener("keydown", forwardEscape, true); + detachEscape = () => + container.removeEventListener("keydown", forwardEscape, true); cleanup = attachTerminalResize(container, fit, term, ws); const applyFit = () => { try { @@ -217,6 +222,7 @@ export function TerminalDialog({ attach(); return () => { if (fitFrame) cancelAnimationFrame(fitFrame); + detachEscape?.(); cleanup?.(); }; }, [open, authReady, fullscreen]);