From ff07f87cc3b90955eacef276b9737f22f7321c39 Mon Sep 17 00:00:00 2001 From: Lucien <164857476+dakjdakd@users.noreply.github.com> Date: Wed, 21 Oct 2026 20:57:56 +0800 Subject: [PATCH] fix(browser): preserve Live keyboard modifiers (#7213) * fix(browser): preserve Live keyboard modifiers * test(browser): cover Meta modifier on named keys --------- Co-authored-by: Willem Jiang --- .../browser-view/browser-view-panel.tsx | 2 + .../workspace/browser-view/keyboard.ts | 16 +++++++- .../workspace/browser-view/keyboard.test.ts | 38 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/workspace/browser-view/browser-view-panel.tsx b/frontend/src/components/workspace/browser-view/browser-view-panel.tsx index 34631434cf..e1a073bba7 100644 --- a/frontend/src/components/workspace/browser-view/browser-view-panel.tsx +++ b/frontend/src/components/workspace/browser-view/browser-view-panel.tsx @@ -328,6 +328,8 @@ export function BrowserViewPanel({ key: event.key, metaKey: event.metaKey, ctrlKey: event.ctrlKey, + shiftKey: event.shiftKey, + altKey: event.altKey, }); if (!input) { return; diff --git a/frontend/src/components/workspace/browser-view/keyboard.ts b/frontend/src/components/workspace/browser-view/keyboard.ts index b9a157bb93..60d1d3647f 100644 --- a/frontend/src/components/workspace/browser-view/keyboard.ts +++ b/frontend/src/components/workspace/browser-view/keyboard.ts @@ -25,6 +25,18 @@ export interface BrowserKeyContext { key: string; metaKey: boolean; ctrlKey: boolean; + shiftKey: boolean; + altKey: boolean; +} + +function keyChord(ctx: BrowserKeyContext): string { + const modifiers = [ + ctx.ctrlKey ? "Control" : null, + ctx.metaKey ? "Meta" : null, + ctx.altKey ? "Alt" : null, + ctx.shiftKey ? "Shift" : null, + ].filter((modifier): modifier is string => modifier !== null); + return [...modifiers, ctx.key].join("+"); } /** @@ -42,14 +54,14 @@ export function decideBrowserKeyInput( if ((ctx.ctrlKey || ctx.metaKey) && ctx.key.length === 1) { return { type: "key", - key: `${ctx.metaKey ? "Meta" : "Control"}+${ctx.key.toUpperCase()}`, + key: keyChord({ ...ctx, key: ctx.key.toUpperCase() }), }; } if (ctx.key.length === 1 && !ctx.metaKey && !ctx.ctrlKey) { return { type: "text", text: ctx.key }; } if ((FORWARDED_NAMED_KEYS as readonly string[]).includes(ctx.key)) { - return { type: "key", key: ctx.key }; + return { type: "key", key: keyChord(ctx) }; } return null; } diff --git a/frontend/tests/unit/components/workspace/browser-view/keyboard.test.ts b/frontend/tests/unit/components/workspace/browser-view/keyboard.test.ts index 3c3dcb7d54..551d8a1530 100644 --- a/frontend/tests/unit/components/workspace/browser-view/keyboard.test.ts +++ b/frontend/tests/unit/components/workspace/browser-view/keyboard.test.ts @@ -12,6 +12,8 @@ function ctx( key: "a", metaKey: false, ctrlKey: false, + shiftKey: false, + altKey: false, ...overrides, }; } @@ -33,6 +35,16 @@ describe("decideBrowserKeyInput", () => { type: "key", key: "ArrowLeft", }); + expect(decideBrowserKeyInput(ctx({ key: "Tab", shiftKey: true }))).toEqual({ + type: "key", + key: "Shift+Tab", + }); + expect( + decideBrowserKeyInput(ctx({ key: "ArrowLeft", shiftKey: true })), + ).toEqual({ + type: "key", + key: "Shift+ArrowLeft", + }); }); it("forwards modifier combos as a normalized key chord", () => { @@ -44,6 +56,32 @@ describe("decideBrowserKeyInput", () => { type: "key", key: "Control+A", }); + expect( + decideBrowserKeyInput( + ctx({ key: "ArrowLeft", ctrlKey: true, shiftKey: true }), + ), + ).toEqual({ + type: "key", + key: "Control+Shift+ArrowLeft", + }); + expect( + decideBrowserKeyInput(ctx({ key: "Backspace", ctrlKey: true })), + ).toEqual({ + type: "key", + key: "Control+Backspace", + }); + expect( + decideBrowserKeyInput(ctx({ key: "Backspace", metaKey: true })), + ).toEqual({ + type: "key", + key: "Meta+Backspace", + }); + expect( + decideBrowserKeyInput(ctx({ key: "ArrowRight", altKey: true })), + ).toEqual({ + type: "key", + key: "Alt+ArrowRight", + }); }); it("does not forward while an IME composition is active", () => {