fix(grid): stabilize buffered columns while scrolling

Closes #8498
This commit is contained in:
t8y2 committed 2026-09-22 14:26:52 +08:00
1 parent e42e0711a9
commit 7653ebfda6
2 files changed
+58 -11

No files matched your search

@@ -1,6 +1,6 @@
// @vitest-environment happy-dom
import { effectScope, nextTick, ref } from "vue";
import { effectScope, nextTick, ref, watch } from "vue";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { dataGridColumnOffsets, dataGridHorizontalColumnWindow, useDataGridColumnLayout, useDataGridColumnLayoutState, type ColumnHeaderReferenceDragController } from "@/composables/useDataGridColumnLayout";
import { columnHeaderDragAutoScrollDelta, columnHeaderDropTargetIndex } from "@/lib/dataGrid/dataGridColumnHeaderInteraction";
@@ -40,6 +40,45 @@ describe("useDataGridColumnLayout", () => {
});
});
it("keeps rendered columns stable while scrolling inside the same buffered window", async () => {
const scrollLeft = ref(100);
const columnNames = ref(Array.from({ length: 40 }, (_, index) => `column_${index}`));
const visibleColumnIndexes = ref(columnNames.value.map((_, index) => index));
const renderedColumnWidths = ref(columnNames.value.map(() => 100));
const scope = effectScope();
const layout = scope.run(() =>
useDataGridColumnLayout({
columnNames,
visibleColumnIndexes,
renderedColumnWidths,
scrollLeft,
viewportWidth: ref(500),
rowNumberWidth: ref(40),
}),
)!;
const firstWindow = layout.horizontalColumnWindow.value;
const firstColumns = layout.renderedGridColumns.value;
const renderedColumnUpdates = vi.fn();
const stopWatching = watch(layout.renderedGridColumns, renderedColumnUpdates, { flush: "sync" });
for (let position = 101; position <= 140; position += 1) scrollLeft.value = position;
await nextTick();
expect(layout.horizontalColumnWindow.value).toBe(firstWindow);
expect(layout.renderedGridColumns.value).toBe(firstColumns);
expect(renderedColumnUpdates).not.toHaveBeenCalled();
scrollLeft.value = 1_500;
await nextTick();
expect(layout.horizontalColumnWindow.value).not.toBe(firstWindow);
expect(layout.renderedGridColumns.value).not.toBe(firstColumns);
expect(renderedColumnUpdates).toHaveBeenCalledTimes(1);
stopWatching();
scope.stop();
});
it("accelerates column drag scrolling toward the viewport edges", () => {
expect(columnHeaderDragAutoScrollDelta({ clientX: 100, viewportLeft: 0, viewportRight: 400 })).toBe(0);
expect(columnHeaderDragAutoScrollDelta({ clientX: 32, viewportLeft: 0, viewportRight: 400 })).toBeLessThan(0);
@@ -99,6 +99,11 @@ export function dataGridHorizontalColumnWindow(options: { widths: readonly numbe
return { start, end, beforeWidth: offsets[start] ?? 0, afterWidth: Math.max(0, columnsWidth - visibleWidth) };
}
export function stableDataGridHorizontalColumnWindow(previous: DataGridHorizontalColumnWindow | undefined, next: DataGridHorizontalColumnWindow): DataGridHorizontalColumnWindow {
if (previous && previous.start === next.start && previous.end === next.end && previous.beforeWidth === next.beforeWidth && previous.afterWidth === next.afterWidth) return previous;
return next;
}
export function useDataGridColumnLayoutState(options: {
columns: MaybeRefOrGetter<readonly string[]>;
sourceColumns?: MaybeRefOrGetter<readonly (string | undefined)[] | undefined>;
@@ -503,16 +508,19 @@ export function useDataGridColumnLayout(options: {
}) {
const renderedColumnOffsets = computed(() => dataGridColumnOffsets(toValue(options.renderedColumnWidths)));
const frozenColumnCount = computed(() => toValue(options.frozenColumnCount ?? 0));
const horizontalColumnWindow = computed(() =>
dataGridHorizontalColumnWindow({
widths: toValue(options.renderedColumnWidths),
offsets: renderedColumnOffsets.value,
columnCount: toValue(options.visibleColumnIndexes).length,
scrollLeft: toValue(options.scrollLeft),
viewportWidth: toValue(options.viewportWidth),
rowNumberWidth: toValue(options.rowNumberWidth),
bufferPx: options.bufferPx ?? 900,
}),
const horizontalColumnWindow = computed<DataGridHorizontalColumnWindow>((previous) =>
stableDataGridHorizontalColumnWindow(
previous,
dataGridHorizontalColumnWindow({
widths: toValue(options.renderedColumnWidths),
offsets: renderedColumnOffsets.value,
columnCount: toValue(options.visibleColumnIndexes).length,
scrollLeft: toValue(options.scrollLeft),
viewportWidth: toValue(options.viewportWidth),
rowNumberWidth: toValue(options.rowNumberWidth),
bufferPx: options.bufferPx ?? 900,
}),
),
);
const renderedGridColumns = computed<RenderedDataGridColumn[]>(() => {
const columnNames = toValue(options.columnNames);