diff --git a/apps/desktop/src/components/grid/DataGrid.vue b/apps/desktop/src/components/grid/DataGrid.vue index 61f42baa88..6d689ef284 100644 --- a/apps/desktop/src/components/grid/DataGrid.vue +++ b/apps/desktop/src/components/grid/DataGrid.vue @@ -167,7 +167,7 @@ import { isCancelSearchShortcut, isCopyCurrentRowShortcut, isDeleteCurrentRowSho import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGrid/dataGridScrollGutter"; import { canGoNextDataGridPage } from "@/lib/dataGrid/dataGridPagination"; import { dataGridCountQueryOptions } from "@/lib/dataGrid/dataGridQueryOptions"; -import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll"; +import { dataGridBottomScrollTop, dataGridScrollPosition, isDataGridAtScrollBottom, isDataGridNearScrollBottom, restoredDataGridScrollLeft, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGrid/dataGridInfiniteScroll"; import { CANVAS_DATA_GRID_ROW_HEIGHT, drawCanvasDataGrid } from "@/lib/dataGrid/canvasDataGridRenderer"; import { dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGrid/dataGridSaveUi"; import type { QueryEditabilityReason } from "@/lib/sql/sqlAnalysis"; @@ -2912,6 +2912,7 @@ const gridStyle = computed(() => ({ })); const gridHorizontalScrollLeft = ref(0); const gridViewportWidth = ref(0); +let gridScrollLeftBeforeTranspose = 0; const renderedColumnOffsets = computed(() => { const widths = renderedColumnWidths.value; const offsets = Array.from({ length: widths.length + 1 }, () => 0); @@ -7877,7 +7878,20 @@ function transposeNav(delta: number) { } watch(isTransposeMode, (active) => { - if (active) nextTick(updateTransposeViewport); + if (active) { + gridScrollLeftBeforeTranspose = gridScrollerElement()?.scrollLeft ?? gridHorizontalScrollLeft.value; + nextTick(updateTransposeViewport); + return; + } + + nextTick(() => { + const scroller = gridScrollerElement(); + if (!scroller) return; + // Transpose mode replaces the normal grid scroller, so restore its state and + // reconnect overflow observers only after Vue mounts the new element. + scroller.scrollLeft = restoredDataGridScrollLeft(gridScrollLeftBeforeTranspose, scroller.scrollWidth, scroller.clientWidth); + refreshGridScrollerMetrics(); + }); }); watch( diff --git a/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts b/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts index 3cf968c289..409e098f12 100644 --- a/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts +++ b/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { dataGridBottomScrollTop, isDataGridAtScrollBottom } from "@/lib/dataGrid/dataGridInfiniteScroll"; +import { dataGridBottomScrollTop, isDataGridAtScrollBottom, restoredDataGridScrollLeft } from "@/lib/dataGrid/dataGridInfiniteScroll"; describe("data grid bottom anchoring", () => { it("keeps DOM rows anchored when scrollbar padding increases the scroll height", () => { @@ -30,3 +30,17 @@ describe("data grid bottom anchoring", () => { expect(isDataGridAtScrollBottom({ scrollTop: 700, scrollHeight: 1000, clientHeight: 260 })).toBe(false); }); }); + +describe("data grid horizontal restoration", () => { + it("restores the previous horizontal position after the grid remounts", () => { + expect(restoredDataGridScrollLeft(320, 1200, 600)).toBe(320); + }); + + it("clamps the previous position when the scrollable width shrinks", () => { + expect(restoredDataGridScrollLeft(720, 1000, 600)).toBe(400); + }); + + it("resets the position when the rebuilt grid no longer overflows", () => { + expect(restoredDataGridScrollLeft(320, 500, 600)).toBe(0); + }); +}); diff --git a/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts b/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts index 9b9bcce71f..73cb95c26c 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts @@ -16,6 +16,10 @@ export function dataGridScrollPosition(top: number, left: number): DataGridScrol }; } +export function restoredDataGridScrollLeft(scrollLeft: number, scrollWidth: number, clientWidth: number): number { + return Math.max(0, Math.min(Math.max(0, scrollWidth - clientWidth), scrollLeft)); +} + export function shouldCheckInfiniteScrollAfterScroll(previous: DataGridScrollPosition | undefined, current: DataGridScrollPosition): boolean { if (!previous) return false; // Shift+wheel horizontal scrolling changes scrollLeft only and must not paginate.