From e1052333c1c36aeca5f2a1eb77c0a7f32679f5d7 Mon Sep 17 00:00:00 2001 From: zipg Date: Fri, 31 Jul 2026 18:37:12 +0800 Subject: [PATCH] fix(grid): improve condition suggestion keyboard behavior --- .../__tests__/DataGridConditionEditor.spec.ts | 20 ++++++++++++++ .../useDataGridConditionEditor.spec.ts | 26 ++++++++++++++++--- .../composables/useDataGridConditionEditor.ts | 6 ++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts index 01d0b7a42a..ca74bc966d 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridConditionEditor.spec.ts @@ -123,6 +123,26 @@ describe("DataGridConditionEditor quote completion", () => { expect(input.selectionEnd).toBe(20); }); + it("starts without an active suggestion and selects the first item on ArrowDown", async () => { + const { value, input } = mountEditor("orderBy", "", { columns: ["name", "namespace"] }); + input.focus(); + input.value = "na"; + input.setSelectionRange(2, 2); + input.dispatchEvent(new Event("input", { bubbles: true })); + + await vi.waitFor(() => expect(document.querySelectorAll('[role="option"]')).toHaveLength(2)); + expect(document.querySelector('[role="option"][aria-selected="true"]')).toBeNull(); + expect(value.value).toBe("na"); + + input.dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true, cancelable: true })); + await nextTick(); + expect(document.querySelector('[role="option"][aria-selected="true"]')?.textContent).toContain("name"); + + input.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true, cancelable: true })); + await nextTick(); + expect(value.value).toBe("name"); + }); + it("keeps expanded input first-line indent and wraps long tokens", () => { const source = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGridConditionEditor.vue"), "utf8"); const expandedInputCss = source.match(/\.data-grid-topbar-condition-input--expanded\s*\{(?[\s\S]*?)\n\}/)?.groups?.body; diff --git a/apps/desktop/src/composables/__tests__/useDataGridConditionEditor.spec.ts b/apps/desktop/src/composables/__tests__/useDataGridConditionEditor.spec.ts index 1cfa5bc351..7cb429638f 100644 --- a/apps/desktop/src/composables/__tests__/useDataGridConditionEditor.spec.ts +++ b/apps/desktop/src/composables/__tests__/useDataGridConditionEditor.spec.ts @@ -34,6 +34,8 @@ describe("useDataGridConditionEditor", () => { await nextTick(); await vi.waitFor(() => expect(editor.suggestions.value.map((item) => item.value)).toEqual(["customer_id", "customer_name"])); expect(editor.navigate(1)).toBe(true); + expect(editor.highlightedIndex.value).toBe(0); + expect(editor.navigate(1)).toBe(true); expect(editor.highlightedIndex.value).toBe(1); expect(editor.navigate(1)).toBe(true); expect(editor.highlightedIndex.value).toBe(1); @@ -381,18 +383,34 @@ describe("useDataGridConditionEditor", () => { it("maps Enter, Tab, arrows, and Escape without applying stale selections", async () => { const value = ref(""); - const editor = useDataGridConditionEditor({ kind: "orderBy", value, columns: ["name"], historyScope: {} }); + const editor = useDataGridConditionEditor({ kind: "orderBy", value, columns: ["name", "namespace"], historyScope: {} }); value.value = "na"; await nextTick(); - await vi.waitFor(() => expect(editor.suggestions.value).toHaveLength(1)); + await vi.waitFor(() => expect(editor.suggestions.value).toHaveLength(2)); + expect(editor.highlightedIndex.value).toBe(-1); + + const initialEnter = keyboardEvent("Enter"); + expect(editor.handleKeydown(initialEnter)).toBe("apply"); + expect(initialEnter.preventDefault).toHaveBeenCalledOnce(); + expect(value.value).toBe("na"); const down = keyboardEvent("ArrowDown"); expect(editor.handleKeydown(down)).toBe("navigate"); expect(down.preventDefault).toHaveBeenCalledOnce(); - const tab = keyboardEvent("Tab"); - expect(editor.handleKeydown(tab)).toBe("accept"); + expect(editor.highlightedIndex.value).toBe(0); + const navigatedEnter = keyboardEvent("Enter"); + expect(editor.handleKeydown(navigatedEnter)).toBe("accept"); expect(value.value).toBe("name"); + const tabValue = ref(""); + const tabEditor = useDataGridConditionEditor({ kind: "orderBy", value: tabValue, columns: ["name"], historyScope: {} }); + tabValue.value = "na"; + await nextTick(); + await vi.waitFor(() => expect(tabEditor.suggestions.value).toHaveLength(1)); + const tab = keyboardEvent("Tab"); + expect(tabEditor.handleKeydown(tab)).toBe("accept"); + expect(tabValue.value).toBe("name"); + const enter = keyboardEvent("Enter"); expect(editor.handleKeydown(enter)).toBe("apply"); editor.openHistory(); diff --git a/apps/desktop/src/composables/useDataGridConditionEditor.ts b/apps/desktop/src/composables/useDataGridConditionEditor.ts index 8d12c77ce0..c903e749a3 100644 --- a/apps/desktop/src/composables/useDataGridConditionEditor.ts +++ b/apps/desktop/src/composables/useDataGridConditionEditor.ts @@ -264,7 +264,7 @@ export function useDataGridConditionEditor(options: UseDataGridConditionEditorOp const limit = options.suggestionLimit ?? 8; suggestions.value = values ? [...new Set(values)].slice(0, limit).map((suggestion) => ({ value: suggestion, kind: "column" })) : defaultSuggestions(target).slice(0, limit); replacementRange.value = { from: target.from, to: target.to }; - highlightedIndex.value = suggestions.value.length > 0 ? 0 : -1; + highlightedIndex.value = -1; } catch (error) { if (!controller.signal.aborted && requestId === suggestionRequestId) { suggestions.value = []; @@ -310,7 +310,7 @@ export function useDataGridConditionEditor(options: UseDataGridConditionEditorOp const history = forgetDataGridConditionHistory(options.kind, toValue(options.historyScope), value); const query = options.value.value.trim().toLowerCase(); suggestions.value = history.filter((item) => !query || item.toLowerCase().includes(query)).map((item) => ({ value: item, kind: "history" })); - highlightedIndex.value = suggestions.value.length > 0 ? Math.min(Math.max(highlightedIndex.value, 0), suggestions.value.length - 1) : -1; + highlightedIndex.value = suggestions.value.length > 0 && highlightedIndex.value >= 0 ? Math.min(highlightedIndex.value, suggestions.value.length - 1) : -1; historyOpen.value = true; } @@ -328,7 +328,7 @@ export function useDataGridConditionEditor(options: UseDataGridConditionEditorOp return true; } - function accept(index = highlightedIndex.value) { + function accept(index = highlightedIndex.value >= 0 ? highlightedIndex.value : 0) { const suggestion = suggestions.value[index]; if (!suggestion) return false; let caret: number;