diff --git a/apps/web/src/components/ChatView.logic.test.ts b/apps/web/src/components/ChatView.logic.test.ts index 6889515e75be..58b05b3029cc 100644 --- a/apps/web/src/components/ChatView.logic.test.ts +++ b/apps/web/src/components/ChatView.logic.test.ts @@ -1959,6 +1959,12 @@ describe("shouldRefocusComposerOnWindowFocus", () => { ).toBe(false); }); + it("leaves a focused device screen alone", () => { + expect(shouldRefocusComposerOnWindowFocus(element("DIV", { within: "application" }))).toBe( + false, + ); + }); + it("leaves focus inside a dialog or popup alone", () => { expect(shouldRefocusComposerOnWindowFocus(element("BUTTON", { within: "dialog" }))).toBe(false); expect(shouldRefocusComposerOnWindowFocus(element("BUTTON", { within: "-popup" }))).toBe(false); diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index b2c2d8540fd1..f14ce8cf71ab 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -1259,7 +1259,7 @@ export function shouldRefocusComposerOnWindowFocus( } return ( activeElement.closest( - '[role="dialog"], [role="alertdialog"], [data-slot$="-popup"], [data-terminal-owner]', + '[role="dialog"], [role="alertdialog"], [role="application"], [data-slot$="-popup"], [data-terminal-owner]', ) === null ); } diff --git a/apps/web/src/components/chat/composerFocusRedirect.browser.test.ts b/apps/web/src/components/chat/composerFocusRedirect.browser.test.ts new file mode 100644 index 000000000000..b1599fbd1ac7 --- /dev/null +++ b/apps/web/src/components/chat/composerFocusRedirect.browser.test.ts @@ -0,0 +1,31 @@ +import { afterEach, describe, expect, it } from "vite-plus/test"; + +import { shouldTypeToFocusComposer } from "./composerFocusRedirect"; + +function typeFrom(target: HTMLElement): boolean { + let redirected: boolean | undefined; + const listener = (event: KeyboardEvent) => { + redirected = shouldTypeToFocusComposer(event); + }; + window.addEventListener("keydown", listener); + target.dispatchEvent(new KeyboardEvent("keydown", { key: "a", bubbles: true })); + window.removeEventListener("keydown", listener); + return redirected ?? false; +} + +describe("shouldTypeToFocusComposer", () => { + afterEach(() => { + document.body.replaceChildren(); + }); + + it("redirects typing from a plain element but leaves a device screen alone", () => { + const plain = document.createElement("div"); + const deviceScreen = document.createElement("div"); + deviceScreen.setAttribute("role", "application"); + deviceScreen.tabIndex = 0; + document.body.append(plain, deviceScreen); + + expect(typeFrom(plain)).toBe(true); + expect(typeFrom(deviceScreen)).toBe(false); + }); +}); diff --git a/apps/web/src/components/chat/composerFocusRedirect.ts b/apps/web/src/components/chat/composerFocusRedirect.ts index e7937779a76c..ac714f4fba13 100644 --- a/apps/web/src/components/chat/composerFocusRedirect.ts +++ b/apps/web/src/components/chat/composerFocusRedirect.ts @@ -8,6 +8,8 @@ export const TYPE_TO_FOCUS_EDITABLE_SELECTOR = [ '[contenteditable="true"]', '[contenteditable="plaintext-only"]', '[role="textbox"]', + // The device screen forwards keystrokes to the simulator or emulator. + '[role="application"]', ].join(","); const TYPE_TO_FOCUS_INTERACTIVE_SELECTOR = [ "button",