Fix mobile inline selector focus

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-06-06 19:33:58 +00:00
1 parent 1495001502
commit c8c3ee9c07
2 files changed
+60 -10

No files matched your search

@@ -1,22 +1,33 @@
// @vitest-environment jsdom
import { act } from "react";
import { createRoot } from "react-dom/client";
import { flushSync } from "react-dom";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { InlineEntitySelector } from "./InlineEntitySelector";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
async function act(callback: () => void | Promise<void>) {
let result: void | Promise<void> = undefined;
flushSync(() => {
result = callback();
});
await result;
}
describe("InlineEntitySelector", () => {
let container: HTMLDivElement;
let originalMatchMedia: typeof window.matchMedia;
beforeEach(() => {
originalMatchMedia = window.matchMedia;
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
window.matchMedia = originalMatchMedia;
container.remove();
document.body.innerHTML = "";
});
@@ -77,4 +88,51 @@ describe("InlineEntitySelector", () => {
root.unmount();
});
});
it("focuses the search input when opened on coarse pointers", async () => {
window.matchMedia = vi.fn().mockImplementation((query: string) => ({
matches: query === "(pointer: coarse)",
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn(),
}));
const root = createRoot(container);
act(() => {
root.render(
<InlineEntitySelector
value=""
options={[
{ id: "agent:agent-1", label: "CodexCoder" },
{ id: "agent:agent-2", label: "DesignBot" },
]}
placeholder="Assignee"
noneLabel="No assignee"
searchPlaceholder="Search assignees..."
emptyMessage="No assignees found."
onChange={vi.fn()}
/>,
);
});
const trigger = container.querySelector("button") as HTMLButtonElement | null;
expect(trigger).not.toBeNull();
await act(async () => {
trigger?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
});
const searchInput = document.querySelector('input[placeholder="Search assignees..."]') as HTMLInputElement | null;
expect(searchInput).not.toBeNull();
expect(document.activeElement).toBe(searchInput);
act(() => {
root.unmount();
});
});
});
+1 -9
View File
@@ -135,15 +135,7 @@ export const InlineEntitySelector = forwardRef<HTMLButtonElement, InlineEntitySe
disablePortal={disablePortal}
onOpenAutoFocus={(event) => {
event.preventDefault();
// On touch devices, don't auto-focus the search input to avoid
// opening the virtual keyboard which reshapes the viewport and
// pushes the popover off-screen.
const isTouch = typeof window.matchMedia === "function"
? window.matchMedia("(pointer: coarse)").matches
: false;
if (!isTouch) {
inputRef.current?.focus();
}
inputRef.current?.focus();
}}
onCloseAutoFocus={(event) => {
if (!shouldPreventCloseAutoFocusRef.current) return;