fix(grid): 修复单元格详情输入与焦点问题

This commit is contained in:
zipg
2026-07-22 12:07:27 +08:00
committed by GitHub
parent 6152422ef9
commit 7e66e4cc16
3 changed files with 114 additions and 4 deletions
@@ -185,7 +185,7 @@ function actionLabelClass() {
</div>
</template>
<style scoped>
<style>
.data-grid-topbar-action-button {
align-items: center;
justify-content: center;
@@ -0,0 +1,105 @@
// @vitest-environment happy-dom
import { effectScope, nextTick, ref } from "vue";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { DataGridCellDetail } from "@/lib/dataGrid/dataGridDetail";
const mocks = vi.hoisted(() => ({
create: vi.fn(),
destroy: vi.fn(),
setValue: vi.fn(),
getValue: vi.fn(() => ""),
openSearch: vi.fn(),
focus: vi.fn(),
onChange: undefined as undefined | ((value: string) => void),
}));
vi.mock("@/composables/useCellDetailEditor", () => ({
useCellDetailEditor: (options: { onChange?: (value: string) => void }) => {
mocks.onChange = options.onChange;
return {
create: mocks.create,
destroy: mocks.destroy,
setValue: mocks.setValue,
getValue: mocks.getValue,
openSearch: mocks.openSearch,
view: { value: { focus: mocks.focus } },
};
},
}));
vi.mock("@/composables/useTheme", () => ({ useTheme: () => ({ isDark: ref(false), themePalette: ref({}) }) }));
vi.mock("@/stores/settingsStore", () => ({
useSettingsStore: () => ({ editorSettings: { theme: "default", fontSize: 13, fontFamily: "monospace" } }),
}));
vi.mock("@/lib/dataGrid/geometryPreview", () => ({ renderWktOnCanvas: vi.fn() }));
import { useDataGridCellDetail } from "@/composables/useDataGridCellDetail";
function detail(): DataGridCellDetail {
return {
rowNumber: 1,
rowId: 1,
colIndex: 0,
column: "name",
type: "VARCHAR",
comment: "",
value: "",
rawValue: "",
rawValuePreview: "",
displayValue: "",
displayValuePreview: "",
isValuePreviewTruncated: false,
imagePreviewUrl: null,
length: 0,
formattedJson: null,
isEditable: true,
};
}
beforeEach(() => {
vi.clearAllMocks();
mocks.onChange = undefined;
mocks.getValue.mockReturnValue("");
});
describe("useDataGridCellDetail", () => {
it("focuses CodeMirror after entering cell detail edit mode", async () => {
const scope = effectScope();
const composable = scope.run(() => useDataGridCellDetail({ detail: ref(detail()), editValue: ref(""), onCancel: vi.fn() }))!;
composable.detailsEditorContainer.value = document.createElement("div");
await nextTick();
await Promise.resolve();
expect(mocks.focus).toHaveBeenCalledOnce();
composable.detailsEditorContainer.value = undefined;
await nextTick();
scope.stop();
});
it("does not write editor-originated IME composition text back into CodeMirror", async () => {
const scope = effectScope();
const editValue = ref("");
const composable = scope.run(() => useDataGridCellDetail({ detail: ref(detail()), editValue, onCancel: vi.fn() }))!;
composable.detailsEditorContainer.value = document.createElement("div");
await nextTick();
expect(mocks.create).toHaveBeenCalledOnce();
mocks.getValue.mockReturnValue("ha");
mocks.onChange?.("ha");
await nextTick();
expect(editValue.value).toBe("ha");
expect(mocks.setValue).not.toHaveBeenCalled();
editValue.value = "external value";
await nextTick();
expect(mocks.setValue).toHaveBeenCalledWith("external value", "VARCHAR");
composable.detailsEditorContainer.value = undefined;
await nextTick();
scope.stop();
});
});
@@ -33,8 +33,10 @@ export function useDataGridCellDetail(options: { detail: Ref<DataGridCellDetail>
watch(detailsEditorContainer, async (element) => {
if (element && !detailsEditor) {
detailsEditor = useCellDetailEditor({ onChange: (value) => (options.editValue.value = value), onEscape: options.onCancel, ...editorOptions() });
await detailsEditor.create(element, options.editValue.value, options.detail.value.type);
const editor = useCellDetailEditor({ onChange: (value) => (options.editValue.value = value), onEscape: options.onCancel, ...editorOptions() });
detailsEditor = editor;
await editor.create(element, options.editValue.value, options.detail.value.type);
if (detailsEditor === editor) editor.view.value?.focus();
} else if (!element && detailsEditor) {
detailsEditor.destroy();
detailsEditor = null;
@@ -55,7 +57,10 @@ export function useDataGridCellDetail(options: { detail: Ref<DataGridCellDetail>
() => options.detail.value.formattedJson ?? "",
(value) => sideJsonEditor?.setValue(value, "json"),
);
watch(options.editValue, (value) => detailsEditor?.setValue(value, options.detail.value.type));
watch(options.editValue, (value) => {
if (!detailsEditor || detailsEditor.getValue() === value) return;
detailsEditor.setValue(value, options.detail.value.type);
});
return {
geometryPreviewOpen,