diff --git a/ui/src/components/InlineEntitySelector.test.tsx b/ui/src/components/InlineEntitySelector.test.tsx
new file mode 100644
index 00000000000..f127f41c330
--- /dev/null
+++ b/ui/src/components/InlineEntitySelector.test.tsx
@@ -0,0 +1,80 @@
+// @vitest-environment jsdom
+
+import { act } from "react";
+import { createRoot } from "react-dom/client";
+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;
+
+describe("InlineEntitySelector", () => {
+ let container: HTMLDivElement;
+
+ beforeEach(() => {
+ container = document.createElement("div");
+ document.body.appendChild(container);
+ });
+
+ afterEach(() => {
+ container.remove();
+ document.body.innerHTML = "";
+ });
+
+ it("keeps handled search navigation keys inside the popover", async () => {
+ const root = createRoot(container);
+ const onChange = vi.fn();
+ const documentKeyDown = vi.fn();
+ document.addEventListener("keydown", documentKeyDown);
+
+ act(() => {
+ root.render(
+ ,
+ );
+ });
+
+ 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();
+ searchInput?.focus();
+
+ await act(async () => {
+ await Promise.resolve();
+ });
+
+ await act(async () => {
+ searchInput?.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "ArrowDown" }));
+ });
+
+ expect(documentKeyDown).not.toHaveBeenCalled();
+
+ await act(async () => {
+ searchInput?.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "Enter" }));
+ });
+
+ expect(documentKeyDown).not.toHaveBeenCalled();
+ expect(onChange).toHaveBeenCalledWith("agent:agent-1");
+
+ document.removeEventListener("keydown", documentKeyDown);
+ act(() => {
+ root.unmount();
+ });
+ });
+});
diff --git a/ui/src/components/InlineEntitySelector.tsx b/ui/src/components/InlineEntitySelector.tsx
index 6bbcf64940f..cae1897712e 100644
--- a/ui/src/components/InlineEntitySelector.tsx
+++ b/ui/src/components/InlineEntitySelector.tsx
@@ -29,6 +29,8 @@ interface InlineEntitySelectorProps {
openOnFocus?: boolean;
}
+const EMPTY_RECENT_OPTION_IDS: string[] = [];
+
export const InlineEntitySelector = forwardRef(
function InlineEntitySelector(
{
@@ -43,7 +45,7 @@ export const InlineEntitySelector = forwardRef(null);
const shouldPreventCloseAutoFocusRef = useRef(false);
const isPointerDownRef = useRef(false);
@@ -72,10 +75,16 @@ export const InlineEntitySelector = forwardRef option.id === value) ?? null;
+ function setHighlightedIndexValue(next: number | ((current: number) => number)) {
+ const resolved = typeof next === "function" ? next(highlightedIndexRef.current) : next;
+ highlightedIndexRef.current = resolved;
+ setHighlightedIndex(resolved);
+ }
+
useEffect(() => {
if (!open) return;
const selectedIndex = filteredOptions.findIndex((option) => option.id === value);
- setHighlightedIndex(selectedIndex >= 0 ? selectedIndex : 0);
+ setHighlightedIndexValue(selectedIndex >= 0 ? selectedIndex : 0);
}, [filteredOptions, open, value]);
const commitSelection = (index: number, moveNext: boolean) => {
@@ -153,14 +162,16 @@ export const InlineEntitySelector = forwardRef {
if (event.key === "ArrowDown") {
event.preventDefault();
- setHighlightedIndex((current) =>
+ event.stopPropagation();
+ setHighlightedIndexValue((current) =>
filteredOptions.length === 0 ? 0 : (current + 1) % filteredOptions.length,
);
return;
}
if (event.key === "ArrowUp") {
event.preventDefault();
- setHighlightedIndex((current) => {
+ event.stopPropagation();
+ setHighlightedIndexValue((current) => {
if (filteredOptions.length === 0) return 0;
return current <= 0 ? filteredOptions.length - 1 : current - 1;
});
@@ -168,16 +179,19 @@ export const InlineEntitySelector = forwardRef setHighlightedIndex(index)}
+ onMouseEnter={() => setHighlightedIndexValue(index)}
onClick={() => commitSelection(index, true)}
>
{renderOption ? renderOption(option, isSelected) : {option.label}}