fix(grid): keep cloned rows next to their source

This commit is contained in:
李子凡
2026-08-31 22:29:15 +08:00
committed by GitHub
parent 4baa46ca88
commit b60f04ea61
3 changed files with 70 additions and 5 deletions
@@ -1,4 +1,4 @@
import { computed, ref, type Ref } from "vue";
import { computed, nextTick, ref, type Ref } from "vue";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { clearDataGridPendingSnapshot, DATA_GRID_QUICK_ENTRY_DRAFT_ROW_ID, useDataGridEditor } from "@/composables/useDataGridEditor";
import type { CellValue } from "@/lib/dataGrid/cellValue";
@@ -514,6 +514,69 @@ describe("useDataGridEditor appendPastedRowsToNewRow", () => {
expect(editor.newRows.value[1]).toEqual(["Ada", "full payload", "Lovelace"]);
});
it("places a cloned source row below its source row", () => {
const editor = createEditor(undefined, true, undefined, undefined, [
["Ada", null, "Lovelace"],
["Grace", null, "Hopper"],
]);
editor.newRows.value = [];
editor.newRowMeta.value = [];
editor.cloneRow(1);
expect(editor.newRowMeta.value[0]?.placement).toEqual({ anchorId: 1, position: "below" });
});
it("keeps the scroll position when the cloned row anchors below its source row", async () => {
class ScrollerStub {
scrollTop = 0;
scrollHeight = 4_000;
}
vi.stubGlobal("HTMLElement", ScrollerStub);
const editor = createEditor(undefined, true, undefined, undefined, [
["Ada", null, "Lovelace"],
["Grace", null, "Hopper"],
]);
editor.newRows.value = [];
editor.newRowMeta.value = [];
const scroller = new ScrollerStub();
editor.scrollerRef.value = scroller as unknown as NonNullable<typeof editor.scrollerRef.value>;
editor.cloneRow(1);
await nextTick();
vi.unstubAllGlobals();
expect(editor.newRowMeta.value[0]?.placement).toEqual({ anchorId: 1, position: "below" });
expect(scroller.scrollTop).toBe(0);
});
it("places a cloned pending row below the pending source row", () => {
const editor = createEditor();
editor.newRows.value = [];
editor.newRowMeta.value = [];
editor.addRows(1);
editor.cloneRow(-1);
expect(editor.newRowMeta.value[1]?.placement).toEqual({ anchorId: -1, position: "below" });
});
it("places each multi-row clone below its corresponding source row", () => {
const editor = createEditor(undefined, true, undefined, undefined, [
["Ada", null, "Lovelace"],
["Grace", null, "Hopper"],
]);
editor.newRows.value = [];
editor.newRowMeta.value = [];
editor.cloneRows([1, 0]);
expect(editor.newRowMeta.value.map((meta) => meta.placement)).toEqual([
{ anchorId: 1, position: "below" },
{ anchorId: 0, position: "below" },
]);
});
});
describe("useDataGridEditor saveChanges reload", () => {
@@ -272,7 +272,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
if (value !== baseline[column]) edited.add(column);
});
}
return allocateNewRowMeta(null, sourceIndex, [...edited]);
const placement = item.isNew && item.newIndex !== undefined ? (inherited ? { anchorId: -inherited.token, position: "below" as const } : null) : sourceIndex === undefined ? null : { anchorId: sourceIndex, position: "below" as const };
return allocateNewRowMeta(placement, sourceIndex, [...edited]);
}
// Restore a metadata snapshot and resume token allocation past its maximum so
// newly created rows never collide with tokens held by restored rows (a fresh
@@ -1218,7 +1219,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
pushUndoSnapshot();
rowStatusFilter.value = rowStatusFilterAfterAddingRow(rowStatusFilter.value);
newRows.value.push(clonedData);
newRowMeta.value.push(clonedRowMeta(item, clonedData));
const clonedMeta = clonedRowMeta(item, clonedData);
newRowMeta.value.push(clonedMeta);
newRows.value = [...newRows.value];
newRowMeta.value = [...newRowMeta.value];
touchPendingChanges();
@@ -1228,7 +1230,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const newRowId = -newRows.value.length;
nextTick(() => {
const el = getScrollerElement();
if (el) el.scrollTop = el.scrollHeight;
if (clonedMeta.placement === null && el) el.scrollTop = el.scrollHeight;
startEdit(newRowId, initialEditColumn?.value ?? 0);
});
}
+1 -1
View File
@@ -431,7 +431,7 @@ test("cloning a row preserves its source and edited columns for custom saves", a
assert.deepEqual(savedMeta, [
{
token: 1,
placement: null,
placement: { anchorId: 0, position: "below" },
sourceIndex: 0,
editedColumns: [1],
},