fix(grid): preserve pending value editor draft on refresh

This commit is contained in:
Melody Ruch authored and GitHub committed 2026-09-04 16:47:10 +08:00
1 parent 1c972b80c0
commit b00821d2c5
1 file changed
+39 -6
+39 -6
View File
@@ -599,6 +599,7 @@ let preservedSelectionOnNextResult: {
} | null = null;
let preservedDetailsOnNextResult: {
sideCell?: PersistedDataGridSelection;
sideCellHasPendingDraft?: boolean;
cellDialog?: PersistedDataGridSelection;
rowDialog?: PersistedDataGridSelection;
columnDialog?: PersistedDataGridSelection;
@@ -5942,6 +5943,10 @@ function captureColumnTargetForRefresh(columnIndex: number | null): PersistedDat
function captureDetailsForRefresh() {
const details = {
sideCell: showCellDetail.value ? captureCellTargetForRefresh(detailCell.value) : undefined,
// A refresh must not silently discard an unsaved value-editor draft: closing
// and reopening the panel (even at the "same" cell) would re-derive its text
// from the freshly fetched row, wiping whatever the user was typing.
sideCellHasPendingDraft: showCellDetail.value && hasPendingDetailEditorDraft.value,
cellDialog: cellDetailDialogOpen.value ? captureCellTargetForRefresh(cellDetailDialogTarget.value) : undefined,
rowDialog: rowDetailDialogOpen.value ? captureRowTargetForRefresh(rowDetailDialogRowId.value) : undefined,
columnDialog: columnDetailDialogOpen.value ? captureColumnTargetForRefresh(columnDetailDialogColumnIndex.value) : undefined,
@@ -5965,11 +5970,17 @@ function restoreDetailCellAfterRefresh(snapshot: PersistedDataGridSelection | un
}
function restoreDetailsAfterRefresh(details: NonNullable<typeof preservedDetailsOnNextResult>) {
const sideCell = restoreDetailCellAfterRefresh(details.sideCell);
if (sideCell) {
detailCell.value = sideCell;
showCellDetail.value = true;
hydrateCellDetailTarget(sideCell);
// When the side panel was left open with an unsaved value-editor draft, it was
// deliberately kept open (not closed) across this refresh, so there is nothing
// to restore here - reassigning detailCell would re-derive the editor text from
// the freshly fetched row and clobber the draft the user is still editing.
if (!details.sideCellHasPendingDraft) {
const sideCell = restoreDetailCellAfterRefresh(details.sideCell);
if (sideCell) {
detailCell.value = sideCell;
showCellDetail.value = true;
hydrateCellDetailTarget(sideCell);
}
}
const cellDialog = restoreDetailCellAfterRefresh(details.cellDialog);
@@ -6461,6 +6472,12 @@ watch(activeCellDetailTab, (tab) => {
const detailEditValue = ref("");
const detailEditOriginalValue = ref("");
const isEditingDetail = ref(false);
// commitDetailEdit() intentionally re-triggers the activeCellDetail watch below to
// re-sync the editor with the canonical committed value. Any other change to the
// same cell's underlying data while still editing (e.g. a result refresh, or a
// large-value hydration resolving) must not clobber the user's in-progress draft.
let allowActiveCellDetailResync = false;
let lastSyncedDetailCellKey: string | null = null;
const detailValueDiffOpen = ref(false);
const detailValueDiffSnapshot = ref<Readonly<JsonValueDiffSnapshot> | null>(null);
const hasPendingDetailEditorDraft = computed(() => isEditingDetail.value && detailEditValue.value !== detailEditOriginalValue.value);
@@ -6591,6 +6608,15 @@ watch(activeCellDetail, (detail) => {
resetDetailEdit();
return;
}
const cellKey = `${detail.rowId}:${detail.colIndex}`;
const isSameCellStillEditing = isEditingDetail.value && cellKey === lastSyncedDetailCellKey;
lastSyncedDetailCellKey = cellKey;
if (isSameCellStillEditing && !allowActiveCellDetailResync) {
// The row's underlying data changed (e.g. a result refresh or a large-value
// hydration) while the user is still editing this same cell. Keep their draft.
return;
}
allowActiveCellDetailResync = false;
const value = dataGridCellEditorText({
value: detail.value,
databaseType: resolvedDatabaseType.value,
@@ -6668,6 +6694,7 @@ function resetDetailEdit() {
isEditingDetail.value = false;
detailEditValue.value = "";
detailEditOriginalValue.value = "";
lastSyncedDetailCellKey = null;
}
function closeCellDetails() {
@@ -6741,6 +6768,9 @@ function commitDetailEdit() {
if (!item || item.isDeleted) return;
applyCellValue(detail.rowId, detail.colIndex, detailEditValue.value);
detailEditOriginalValue.value = detailEditValue.value;
// Force the activeCellDetail watch to re-run so the editor picks up the
// canonical (possibly coerced) committed value.
allowActiveCellDetailResync = true;
detailCell.value = detailCell.value ? { ...detailCell.value } : null;
}
@@ -10529,7 +10559,10 @@ watch(
clearCellSelection();
clearRowSelection();
invalidateContextMenuTarget();
closeCellDetails();
// Don't discard an unsaved value-editor draft just because the result set
// refreshed underneath it; leave the panel/editor state as-is and let
// restoreDetailsAfterRefresh() below skip re-opening it from fresh data.
if (!detailsSnapshot?.sideCellHasPendingDraft) closeCellDetails();
closeDetailDialogs();
if (shouldPreserveTranspose) {
applyTransposeState(nextTransposeStateForRecordCount(showTranspose.value, transposeRowIndex.value, displayRowCount.value));