fix(grid): preserve contiguous row selection

This commit is contained in:
t8y2 2026-07-09 18:56:48 +08:00
parent 15d392bc44
commit 8c8c48f9b1
3 changed files with 91 additions and 4 deletions

View File

@ -7437,7 +7437,6 @@ function onCellContext(rowId: number, rowIndex: number, colIdx: number, visibleC
contextHeaderColumnIndex.value = null;
contextCell.value = { rowId, rowIndex, col: colIdx };
if (hasRowSelection.value && isRowSelected(rowId)) {
clearCellSelection();
void prefetchCopyStatements();
return;
}
@ -7576,9 +7575,6 @@ function onRowContext(rowId: number, rowIndex: number) {
contextHeaderColumn.value = null;
contextHeaderColumnIndex.value = null;
contextCell.value = { rowId, rowIndex, col: -1 };
if (hasRowSelection.value && isRowSelected(rowId)) {
clearCellSelection();
}
if (!isRowSelected(rowId)) {
clearCellSelection();
selectedRowIds.value = new Set([rowId]);

View File

@ -0,0 +1,66 @@
import { computed, ref } from "vue";
import { describe, expect, it } from "vitest";
import { useDataGridSelection } from "@/composables/useDataGridSelection";
function createSelection() {
const columns = computed(() => ["id", "name", "email"]);
const displayItems = computed(() =>
[1, 2, 3, 4].map((id, index) => ({
id,
sourceIndex: index,
data: [id, `name-${id}`, `user-${id}@example.com`],
isNew: false,
isDraft: false,
isDeleted: false,
isDirtyCol: [false, false, false],
status: "clean",
})),
);
return useDataGridSelection({
columns,
displayItems,
editingCell: ref(null),
showTranspose: ref(false),
transposeRowIndex: ref(null),
gridRef: ref(undefined),
});
}
function rowEvent(options: { meta?: boolean; shift?: boolean } = {}): MouseEvent {
return {
metaKey: !!options.meta,
ctrlKey: !!options.meta,
shiftKey: !!options.shift,
} as MouseEvent;
}
describe("useDataGridSelection", () => {
it("creates a whole-row cell range for contiguous meta row selections", () => {
const selection = createSelection();
selection.handleRowClick(1, 2, rowEvent({ meta: true }));
selection.handleRowClick(2, 3, rowEvent({ meta: true }));
selection.handleRowClick(3, 4, rowEvent({ meta: true }));
expect(selection.selectedRowIds.value).toEqual(new Set([2, 3, 4]));
expect(selection.selectedRange.value).toEqual({
startRow: 1,
endRow: 3,
startCol: 0,
endCol: 2,
});
expect(selection.hasCellSelection.value).toBe(true);
});
it("does not create a rectangular cell range for non-contiguous meta row selections", () => {
const selection = createSelection();
selection.handleRowClick(0, 1, rowEvent({ meta: true }));
selection.handleRowClick(2, 3, rowEvent({ meta: true }));
expect(selection.selectedRowIds.value).toEqual(new Set([1, 3]));
expect(selection.selectedRange.value).toBeNull();
expect(selection.hasCellSelection.value).toBe(false);
});
});

View File

@ -213,6 +213,7 @@ export function useDataGridSelection(options: UseDataGridSelectionOptions) {
next.add(rowId);
}
selectedRowIds.value = next;
selectContiguousRowIds(next);
lastClickedRowIndex.value = rowIndex;
} else if (isShift && lastClickedRowIndex.value !== null) {
const start = Math.min(lastClickedRowIndex.value, rowIndex);
@ -231,6 +232,30 @@ export function useDataGridSelection(options: UseDataGridSelectionOptions) {
}
}
function selectContiguousRowIds(rowIds: Set<number>) {
if (rowIds.size === 0) {
clearCellSelection();
return;
}
const indexes = displayItems.value.reduce<number[]>((selectedIndexes, item, index) => {
if (rowIds.has(item.id)) selectedIndexes.push(index);
return selectedIndexes;
}, []);
if (indexes.length !== rowIds.size) {
clearCellSelection();
return;
}
const start = Math.min(...indexes);
const end = Math.max(...indexes);
if (end - start + 1 !== indexes.length) {
clearCellSelection();
return;
}
selectRows(start, end);
}
function finishCellSelection() {
isSelectingCells.value = false;
document.removeEventListener("mouseup", finishCellSelection);