fix(grid): optimize data grid interactions

This commit is contained in:
zipg authored and GitHub committed 2026-06-25 13:51:14 +08:00
1 parent 84215a7e60
commit 150daadb53
7 files changed
+622 -128

No files matched your search

@@ -83,9 +83,12 @@ interface PendingChangesSnapshot {
rowCount: number;
}
type PendingChangesHistorySnapshot = Pick<PendingChangesSnapshot, "newRows" | "dirtyRows" | "deletedRows" | "transactionActive">;
const pendingChangesCache = new Map<string, PendingChangesSnapshot>();
const closingPendingSnapshotTabs = new Set<string>();
const BEFORE_TAB_SWITCH_EVENT = "dbx:before-tab-switch";
const MAX_PENDING_CHANGES_HISTORY = 100;
function cacheKeyBelongsToTab(cacheKey: string, tabId: string) {
return cacheKey === tabId || cacheKey.startsWith(`${tabId}-`);
@@ -143,6 +146,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const dirtyRows = ref<Map<number, Map<number, CellValue>>>(new Map());
const newRows = ref<CellValue[][]>([]);
const deletedRows = ref<Set<number>>(new Set());
const undoStack = ref<PendingChangesHistorySnapshot[]>([]);
const redoStack = ref<PendingChangesHistorySnapshot[]>([]);
const pendingChangesVersion = ref(0);
let restoredEditingCell = false;
let restoredTransactionActive = false;
@@ -175,6 +180,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const deletedRowCount = computed(() => deletedRows.value.size);
const pendingChangeCount = computed(() => dirtyRowCount.value + newRowCount.value + deletedRowCount.value);
const hasPendingChanges = computed(() => pendingChangeCount.value > 0);
const canUndoPendingChange = computed(() => undoStack.value.length > 0);
const canRedoPendingChange = computed(() => redoStack.value.length > 0);
const resolvedDatabaseType = computed(() => databaseType.value ?? effectiveDatabaseTypeForConnection(connectionStore.getConfig(connectionId.value ?? "")));
// --- Transaction state ---
@@ -225,6 +232,50 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
pendingChangesVersion.value++;
}
function pendingChangesSnapshot(): PendingChangesHistorySnapshot {
return {
newRows: newRows.value.map((row) => [...row]),
dirtyRows: new Map([...dirtyRows.value].map(([rowIndex, changes]) => [rowIndex, new Map(changes)])),
deletedRows: new Set(deletedRows.value),
transactionActive: transactionActive.value,
};
}
function restorePendingChangesSnapshot(snapshot: PendingChangesHistorySnapshot) {
newRows.value = snapshot.newRows.map((row) => [...row]);
dirtyRows.value = new Map([...snapshot.dirtyRows].map(([rowIndex, changes]) => [rowIndex, new Map(changes)]));
deletedRows.value = new Set(snapshot.deletedRows);
transactionActive.value = snapshot.transactionActive === true && useTransaction.value === true;
editingCell.value = null;
touchPendingChanges();
}
function pushUndoSnapshot() {
undoStack.value = [...undoStack.value.slice(-MAX_PENDING_CHANGES_HISTORY + 1), pendingChangesSnapshot()];
redoStack.value = [];
}
function clearPendingChangeHistory() {
undoStack.value = [];
redoStack.value = [];
}
function undoPendingChange() {
const snapshot = undoStack.value[undoStack.value.length - 1];
if (!snapshot) return;
undoStack.value = undoStack.value.slice(0, -1);
redoStack.value = [...redoStack.value, pendingChangesSnapshot()];
restorePendingChangesSnapshot(snapshot);
}
function redoPendingChange() {
const snapshot = redoStack.value[redoStack.value.length - 1];
if (!snapshot) return;
redoStack.value = redoStack.value.slice(0, -1);
undoStack.value = [...undoStack.value, pendingChangesSnapshot()];
restorePendingChangesSnapshot(snapshot);
}
function exitTransaction() {
transactionActive.value = false;
}
@@ -420,6 +471,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const oldVal = newRows.value[item.newIndex]?.[col];
const newVal = coerceCellValue(editValue.value, oldVal, col);
if (newRows.value[item.newIndex]) {
if (newVal !== oldVal) pushUndoSnapshot();
newRows.value[item.newIndex][col] = newVal;
}
newRows.value = [...newRows.value];
@@ -443,6 +495,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const oldVal = result.value.rows[item.sourceIndex]?.[col];
const newVal = coerceCellValue(editValue.value, oldVal, col);
if (newVal !== oldVal) {
pushUndoSnapshot();
if (!dirtyRows.value.has(item.sourceIndex)) dirtyRows.value.set(item.sourceIndex, new Map());
dirtyRows.value.get(item.sourceIndex)!.set(col, newVal);
if (useTransaction.value && !transactionActive.value) {
@@ -450,6 +503,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
} else {
const rowChanges = dirtyRows.value.get(item.sourceIndex);
if (rowChanges?.has(col)) pushUndoSnapshot();
rowChanges?.delete(col);
if (rowChanges?.size === 0) dirtyRows.value.delete(item.sourceIndex);
}
@@ -473,8 +527,13 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
if (!item || item.isDeleted) return;
if (item.isNew && item.newIndex !== undefined) {
const oldVal = newRows.value[item.newIndex]?.[col];
newRows.value[item.newIndex][col] = value === null ? null : coerceCellValue(value, oldVal, col);
const row = newRows.value[item.newIndex];
if (!row) return;
const oldVal = row[col];
const newVal = value === null ? null : coerceCellValue(value, oldVal, col);
if (newVal === oldVal) return;
pushUndoSnapshot();
row[col] = newVal;
newRows.value = [...newRows.value];
touchPendingChanges();
return;
@@ -484,15 +543,20 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
if (!canEditExistingRows.value) return;
const oldVal = result.value.rows[item.sourceIndex]?.[col];
const rowChanges = dirtyRows.value.get(item.sourceIndex);
const hasPendingCellChange = rowChanges?.has(col) ?? false;
const currentVal = hasPendingCellChange ? rowChanges!.get(col) : oldVal;
const newVal = value === null ? null : coerceCellValue(value, oldVal, col);
if (newVal === currentVal) return;
if (newVal !== oldVal) {
pushUndoSnapshot();
if (!dirtyRows.value.has(item.sourceIndex)) dirtyRows.value.set(item.sourceIndex, new Map());
dirtyRows.value.get(item.sourceIndex)!.set(col, newVal);
if (useTransaction.value && !transactionActive.value) {
enterTransaction();
}
} else {
const rowChanges = dirtyRows.value.get(item.sourceIndex);
if (hasPendingCellChange) pushUndoSnapshot();
rowChanges?.delete(col);
if (rowChanges?.size === 0) dirtyRows.value.delete(item.sourceIndex);
}
@@ -500,6 +564,32 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
touchPendingChanges();
}
function restoreCellValue(rowId: number, col: number) {
if (!canEditColumn(col)) return;
const item = getRowItem(rowId);
if (!item || item.isDeleted) return;
if (item.isNew && item.newIndex !== undefined) {
const row = newRows.value[item.newIndex];
if (!row || row[col] === null) return;
pushUndoSnapshot();
row[col] = null;
newRows.value = [...newRows.value];
touchPendingChanges();
return;
}
if (item.sourceIndex === undefined) return;
if (!canEditExistingRows.value) return;
const rowChanges = dirtyRows.value.get(item.sourceIndex);
if (!rowChanges?.has(col)) return;
pushUndoSnapshot();
rowChanges.delete(col);
if (rowChanges.size === 0) dirtyRows.value.delete(item.sourceIndex);
dirtyRows.value = new Map(dirtyRows.value);
touchPendingChanges();
}
function cancelEdit() {
const restoreScroll = preserveScrollPosition();
isCancelling = true;
@@ -521,6 +611,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
function addRow() {
pushUndoSnapshot();
rowStatusFilter.value = rowStatusFilterAfterAddingRow(rowStatusFilter.value);
newRows.value.push(result.value.columns.map(() => null));
newRows.value = [...newRows.value];
@@ -557,6 +648,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const item = getRowItem(rowId);
if (!item) return;
const clonedData = clonedRowData(item);
pushUndoSnapshot();
rowStatusFilter.value = rowStatusFilterAfterAddingRow(rowStatusFilter.value);
newRows.value.push(clonedData);
newRows.value = [...newRows.value];
@@ -573,10 +665,11 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
function cloneRows(rowIds: number[]) {
const rowsToClone = rowIds.map((rowId) => getRowItem(rowId)).filter(Boolean) as RowItem[];
if (rowsToClone.length === 0) return;
pushUndoSnapshot();
rowStatusFilter.value = rowStatusFilterAfterAddingRow(rowStatusFilter.value);
for (const rowId of rowIds) {
const item = getRowItem(rowId);
if (!item) continue;
for (const item of rowsToClone) {
const clonedData = clonedRowData(item);
newRows.value.push(clonedData);
}
@@ -591,10 +684,12 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
const item = getRowItem(rowId);
if (!item) return;
if (item.isNew && item.newIndex !== undefined) {
pushUndoSnapshot();
newRows.value.splice(item.newIndex, 1);
newRows.value = [...newRows.value];
} else if (item.sourceIndex !== undefined) {
if (!canEditExistingRows.value) return;
pushUndoSnapshot();
dirtyRows.value.delete(item.sourceIndex);
deletedRows.value.add(item.sourceIndex);
dirtyRows.value = new Map(dirtyRows.value);
@@ -636,7 +731,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
function restoreRow(rowId: number) {
const item = getRowItem(rowId);
if (item?.sourceIndex !== undefined) {
if (item?.sourceIndex !== undefined && deletedRows.value.has(item.sourceIndex)) {
pushUndoSnapshot();
deletedRows.value.delete(item.sourceIndex);
deletedRows.value = new Set(deletedRows.value);
touchPendingChanges();
@@ -644,9 +740,14 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
function restoreRows(rowIds: number[]) {
for (const rowId of rowIds) {
restoreRow(rowId);
const sourceIndexes = rowIds.map((rowId) => getRowItem(rowId)?.sourceIndex).filter((sourceIndex): sourceIndex is number => sourceIndex !== undefined && deletedRows.value.has(sourceIndex));
if (sourceIndexes.length === 0) return;
pushUndoSnapshot();
for (const sourceIndex of sourceIndexes) {
deletedRows.value.delete(sourceIndex);
}
deletedRows.value = new Set(deletedRows.value);
touchPendingChanges();
}
function deleteSelectedRow(contextCell: Ref<{ rowId: number; rowIndex: number; col: number } | null>) {
@@ -749,6 +850,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
dirtyRows.value.clear();
newRows.value = [];
deletedRows.value.clear();
clearPendingChangeHistory();
touchPendingChanges();
exitTransaction();
isSaving.value = false;
@@ -834,6 +936,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
dirtyRows.value.clear();
newRows.value = [];
deletedRows.value.clear();
clearPendingChangeHistory();
touchPendingChanges();
exitTransaction();
isSaving.value = false;
@@ -846,6 +949,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
dirtyRows.value.clear();
newRows.value = [];
deletedRows.value.clear();
clearPendingChangeHistory();
touchPendingChanges();
editingCell.value = null;
exitTransaction();
@@ -973,6 +1077,8 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
deletedRowCount,
pendingChangeCount,
hasPendingChanges,
canUndoPendingChange,
canRedoPendingChange,
transactionActive,
isSaving,
saveError,
@@ -983,6 +1089,9 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
commitEdit,
commitEditFromBlur,
applyCellValue,
restoreCellValue,
undoPendingChange,
redoPendingChange,
cancelEdit,
onEditKeydown,
addRow,