fix(grid): prevent horizontal scroll from loading more rows

This commit is contained in:
t8y2
2026-06-26 01:55:03 +08:00
parent 4d000c88fa
commit 2dc71548c3
3 changed files with 67 additions and 6 deletions
+21 -6
View File
@@ -119,6 +119,7 @@ import { isEnumColumn, enumValuesForColumn } from "@/lib/dataGridEnumEditor";
import { isCancelSearchShortcut, isCopyCurrentRowShortcut, isDeleteCurrentRowShortcut, isFocusSearchShortcut, isModRShortcut, isToggleTransposeShortcut } from "@/lib/keyboardShortcuts";
import { dataGridHeaderContentWidth, scrollbarGutterWidth } from "@/lib/dataGridScrollGutter";
import { canGoNextDataGridPage } from "@/lib/dataGridPagination";
import { dataGridScrollPosition, isDataGridNearScrollBottom, shouldCheckInfiniteScrollAfterScroll, type DataGridScrollPosition } from "@/lib/dataGridInfiniteScroll";
import { CANVAS_DATA_GRID_ROW_HEIGHT, drawCanvasDataGrid } from "@/lib/canvasDataGridRenderer";
import { dataGridSaveActionMode, dataGridSaveToolbarState } from "@/lib/dataGridSaveUi";
import { EDITOR_FONT_FAMILY_CSS_VAR } from "@/lib/editorThemes";
@@ -2058,6 +2059,7 @@ function refreshGridScrollerMetrics() {
if (!scrollerEl) return;
updateGridScrollbarGutter(scrollerEl);
updateGridHorizontalViewport(scrollerEl);
rememberInfiniteScrollPosition(scrollerEl);
if (headerRef.value) {
headerRef.value.scrollLeft = scrollerEl.scrollLeft;
}
@@ -2288,6 +2290,7 @@ function columnContentOffsetLeft(visibleColIdx: number): number {
let scrollingTimer = 0;
const isScrolling = ref(false);
let infiniteScrollPositions = new WeakMap<HTMLElement, DataGridScrollPosition>();
function markGridScrolling() {
if (!isScrolling.value) isScrolling.value = true;
@@ -2297,12 +2300,25 @@ function markGridScrolling() {
}, 120);
}
function rememberInfiniteScrollPosition(scroller: HTMLElement) {
infiniteScrollPositions.set(scroller, dataGridScrollPosition(scroller.scrollTop, scroller.scrollLeft));
}
function maybeCheckInfiniteScroll(scroller: HTMLElement) {
const current = dataGridScrollPosition(scroller.scrollTop, scroller.scrollLeft);
const previous = infiniteScrollPositions.get(scroller);
infiniteScrollPositions.set(scroller, current);
if (shouldCheckInfiniteScrollAfterScroll(previous, current)) {
checkInfiniteScroll(scroller);
}
}
function onScrollerScroll(e: Event) {
syncHeaderScroll(e);
const target = e.target;
if (target instanceof HTMLElement) {
recordScrollPosition({ top: target.scrollTop, left: target.scrollLeft });
checkInfiniteScroll(target);
maybeCheckInfiniteScroll(target);
}
markGridScrolling();
}
@@ -2569,11 +2585,8 @@ function checkInfiniteScroll(scroller: HTMLElement) {
infiniteScrollCheckScheduled = true;
requestAnimationFrame(() => {
infiniteScrollCheckScheduled = false;
const { scrollTop, scrollHeight, clientHeight } = scroller;
const threshold = 100;
const distanceToBottom = scrollHeight - scrollTop - clientHeight;
// Only trigger when near bottom AND page has changed since last trigger
if (distanceToBottom < threshold && currentPage.value !== lastInfiniteScrollPage) {
if (isDataGridNearScrollBottom(scroller) && currentPage.value !== lastInfiniteScrollPage) {
lastInfiniteScrollPage = currentPage.value;
infiniteScrollNextPage();
}
@@ -2587,6 +2600,7 @@ function changePageSize(size: number) {
currentPage.value = 1;
lastInfiniteScrollPage = 0;
infiniteScrollAllLoaded = false;
infiniteScrollPositions = new WeakMap();
resetGridVerticalScroll(true);
emit("paginate", 0, normalizedSize, currentWhereInput(), currentOrderBy());
}
@@ -2895,6 +2909,7 @@ function resetInfiniteScrollState() {
infiniteScrollAllLoaded = false;
isInfiniteScrollPaginating.value = false;
infiniteScrollLoading.value = false;
infiniteScrollPositions = new WeakMap();
resetGridVerticalScroll(true);
}
@@ -4193,7 +4208,7 @@ function onCanvasScroll(event: Event) {
recordScrollPosition({ top: scrollTop, left: scrollLeft });
markGridScrolling();
scheduleCanvasDraw();
checkInfiniteScroll(scroller);
maybeCheckInfiniteScroll(scroller);
}
function canvasWheelDeltaToPixels(delta: number, deltaMode: number, pageSize: number): number {
@@ -0,0 +1,26 @@
export interface DataGridScrollPosition {
top: number;
left: number;
}
export interface DataGridScrollMetrics {
scrollTop: number;
scrollHeight: number;
clientHeight: number;
}
export function dataGridScrollPosition(top: number, left: number): DataGridScrollPosition {
return {
top: Math.max(0, top),
left: Math.max(0, left),
};
}
export function shouldCheckInfiniteScrollAfterScroll(previous: DataGridScrollPosition | undefined, current: DataGridScrollPosition): boolean {
if (!previous) return false;
return previous.top !== current.top;
}
export function isDataGridNearScrollBottom(metrics: DataGridScrollMetrics, threshold = 100): boolean {
return metrics.scrollHeight - metrics.scrollTop - metrics.clientHeight < threshold;
}
@@ -0,0 +1,20 @@
import { strict as assert } from "node:assert";
import { test } from "vitest";
import { dataGridScrollPosition, isDataGridNearScrollBottom, shouldCheckInfiniteScrollAfterScroll } from "../../apps/desktop/src/lib/dataGridInfiniteScroll.ts";
test("horizontal-only scroll does not check infinite scroll", () => {
assert.equal(shouldCheckInfiniteScrollAfterScroll(dataGridScrollPosition(240, 0), dataGridScrollPosition(240, 180)), false);
});
test("vertical scroll checks infinite scroll even when horizontal offset also changes", () => {
assert.equal(shouldCheckInfiniteScrollAfterScroll(dataGridScrollPosition(240, 0), dataGridScrollPosition(360, 180)), true);
});
test("first scroll position only establishes the infinite scroll baseline", () => {
assert.equal(shouldCheckInfiniteScrollAfterScroll(undefined, dataGridScrollPosition(360, 180)), false);
});
test("near-bottom check matches the grid threshold", () => {
assert.equal(isDataGridNearScrollBottom({ scrollTop: 801, scrollHeight: 1000, clientHeight: 100 }), true);
assert.equal(isDataGridNearScrollBottom({ scrollTop: 800, scrollHeight: 1000, clientHeight: 100 }), false);
});