mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix: focus composer when typing (#102210)
This commit is contained in:
parent
fc05a8103b
commit
9d7a6b5da6
2 changed files with 79 additions and 0 deletions
|
|
@ -617,6 +617,53 @@ describeControlUiE2e("Control UI mocked Gateway E2E", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("routes page typing to the active composer without stealing text input focus", async () => {
|
||||
const context = await newBrowserContext({
|
||||
locale: "en-US",
|
||||
serviceWorkers: "block",
|
||||
viewport: { height: 900, width: 1280 },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await installMockGateway(page, {
|
||||
historyMessages: [
|
||||
{
|
||||
content: [{ text: "Type whenever you are ready.", type: "text" }],
|
||||
role: "assistant",
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
await page.goto(`${server.baseUrl}chat`);
|
||||
await page.getByText("Type whenever you are ready.").click();
|
||||
|
||||
const composer = page.locator(".agent-chat__composer-combobox textarea");
|
||||
await expect
|
||||
.poll(() => composer.evaluate((element) => element === document.activeElement))
|
||||
.toBe(false);
|
||||
|
||||
await page.keyboard.type("first character preserved");
|
||||
expect(await composer.inputValue()).toBe("first character preserved");
|
||||
await expect
|
||||
.poll(() => composer.evaluate((element) => element === document.activeElement))
|
||||
.toBe(true);
|
||||
|
||||
await page.getByRole("button", { name: "Open command palette" }).click();
|
||||
const paletteInput = page.locator(".cmd-palette__input");
|
||||
await paletteInput.waitFor({ state: "visible", timeout: 10_000 });
|
||||
await expect
|
||||
.poll(() => paletteInput.evaluate((element) => element === document.activeElement))
|
||||
.toBe(true);
|
||||
await page.keyboard.type("session search");
|
||||
|
||||
expect(await paletteInput.inputValue()).toBe("session search");
|
||||
expect(await composer.inputValue()).toBe("first character preserved");
|
||||
} finally {
|
||||
await closeBrowserContext(context);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps stale context visible as approximate without warning or compaction", async () => {
|
||||
const context = await newBrowserContext({
|
||||
locale: "en-US",
|
||||
|
|
|
|||
|
|
@ -88,6 +88,12 @@ type PaneSessionChangeOptions = { replace?: boolean };
|
|||
|
||||
const CHAT_OPEN_DETAILS_SELECTOR =
|
||||
".chat-controls__inline-select[open], .context-usage details[open], .agent-chat__talk-select[open], .agent-chat__attach-menu[open]";
|
||||
const CHAT_COMPOSER_TEXTAREA_SELECTOR = ".agent-chat__composer-combobox > textarea";
|
||||
const CHAT_TEXT_ENTRY_SELECTOR =
|
||||
"input, textarea, select, [contenteditable]:not([contenteditable='false']), [role='combobox'], [role='listbox'], [role='textbox']";
|
||||
const CHAT_SPACE_ACTIVATION_SELECTOR =
|
||||
"a[href], button, summary, [role='button'], [role='checkbox'], [role='link'], [role='radio'], [role='switch']";
|
||||
const CHAT_MODAL_SELECTOR = "dialog[open], [aria-modal='true']";
|
||||
|
||||
const NEW_SESSION_ACTIVE_RUN_MESSAGE =
|
||||
"Start a new session after the active run or queued messages finish.";
|
||||
|
|
@ -96,6 +102,12 @@ const NEW_SESSION_LIST_LOADING_MESSAGE =
|
|||
const NEW_SESSION_CREATE_FAILED_MESSAGE =
|
||||
"New Chat could not create a new session. Try again in a moment.";
|
||||
|
||||
function keyboardEventPathMatches(event: KeyboardEvent, selector: string): boolean {
|
||||
return event
|
||||
.composedPath()
|
||||
.some((target) => target instanceof Element && target.matches(selector));
|
||||
}
|
||||
|
||||
class ChatPane extends LitElement {
|
||||
@consume({ context: applicationContext, subscribe: false })
|
||||
private context!: ChatPageContext;
|
||||
|
|
@ -341,6 +353,26 @@ class ChatPane extends LitElement {
|
|||
}
|
||||
|
||||
private readonly handleDocumentKeydown = (event: KeyboardEvent) => {
|
||||
if (
|
||||
this.active &&
|
||||
!event.defaultPrevented &&
|
||||
!event.isComposing &&
|
||||
!event.metaKey &&
|
||||
!event.ctrlKey &&
|
||||
!event.altKey &&
|
||||
event.key.length === 1 &&
|
||||
!keyboardEventPathMatches(event, CHAT_TEXT_ENTRY_SELECTOR) &&
|
||||
!(event.key === " " && keyboardEventPathMatches(event, CHAT_SPACE_ACTIVATION_SELECTOR)) &&
|
||||
!document.querySelector(CHAT_MODAL_SELECTOR)
|
||||
) {
|
||||
const composer = this.querySelector<HTMLTextAreaElement>(CHAT_COMPOSER_TEXTAREA_SELECTOR);
|
||||
if (composer && !composer.disabled && !composer.readOnly) {
|
||||
// Focus during keydown capture so the browser delivers beforeinput/input,
|
||||
// including the first character, through the composer's normal pipeline.
|
||||
composer.focus({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (event.defaultPrevented || event.key !== "Escape") {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue