fix(mongo): stabilize estimated-total pagination

This commit is contained in:
Freedom authored and GitHub committed 2026-07-31 14:51:25 +08:00
1 parent 96038d2b8a
commit 8f3fd0ebfb
23 files changed
+462 -124

No files matched your search

@@ -4,7 +4,7 @@ import { computed, nextTick, ref } from "vue";
import { beforeEach, describe, expect, it } from "vitest";
import { DATA_GRID_COL_AUTO_FIT_MAX_WIDTH, DATA_GRID_COL_MIN_WIDTH } from "@/lib/dataGrid/dataGridColumnWidth";
import { clearDataGridColumnWidthStates, createDataGridColumnMeasurementSignature, createDataGridColumnStructureSignature, DATA_GRID_COLUMN_WIDTH_STATE_LIMIT, dataGridColumnWidthStateCount, loadDataGridColumnWidthState, saveDataGridColumnWidthState } from "@/lib/dataGrid/dataGridColumnWidthState";
import { DATA_GRID_ROW_NUM_WIDTH, resizeDataGridColumnWidth, useDataGridColumnResize } from "@/composables/useDataGridColumnResize";
import { DATA_GRID_ROW_NUM_WIDTH, dataGridRowNumberColumnWidth, resizeDataGridColumnWidth, resolveDataGridMaxRowNumber, useDataGridColumnResize } from "@/composables/useDataGridColumnResize";
function createResizeState(options: { columns: string[]; rows: Array<Array<string | number | boolean | null>>; columnIndexes?: number[]; columnTypes?: string[]; cacheKey?: string; density?: "compact" | "standard" | "comfortable"; compactColumnHeaderActions?: boolean; headerTextWidth?: number }) {
const compact = ref(options.compactColumnHeaderActions ?? true);
@@ -301,3 +301,19 @@ describe("useDataGridColumnResize", () => {
expect(comf.columnWidths.value[0]).toBeGreaterThanOrEqual(std.columnWidths.value[0]);
});
});
describe("dataGridRowNumberColumnWidth", () => {
it("keeps the default width for small page indexes", () => {
expect(dataGridRowNumberColumnWidth(999)).toBe(DATA_GRID_ROW_NUM_WIDTH);
expect(dataGridRowNumberColumnWidth(9999)).toBe(DATA_GRID_ROW_NUM_WIDTH);
});
it("widens the gutter for multi-million row numbers", () => {
expect(dataGridRowNumberColumnWidth(4_215_101)).toBeGreaterThan(DATA_GRID_ROW_NUM_WIDTH);
expect(dataGridRowNumberColumnWidth(4_215_101)).toBe(dataGridRowNumberColumnWidth(9_999_999));
});
it("prefers measured text width when provided", () => {
expect(dataGridRowNumberColumnWidth(99, 12, () => 40)).toBe(56);
});
});
@@ -405,7 +405,7 @@ export function useDataGridColumnLayout(options: {
renderedColumnWidths: MaybeRefOrGetter<readonly number[]>;
scrollLeft: MaybeRefOrGetter<number>;
viewportWidth: MaybeRefOrGetter<number>;
rowNumberWidth: number;
rowNumberWidth: MaybeRefOrGetter<number>;
bufferPx?: number;
headerRef?: MaybeRefOrGetter<HTMLElement | null | undefined>;
orderedColumnIndexes?: MaybeRefOrGetter<readonly number[]>;
@@ -427,7 +427,7 @@ export function useDataGridColumnLayout(options: {
columnCount: toValue(options.visibleColumnIndexes).length,
scrollLeft: toValue(options.scrollLeft),
viewportWidth: toValue(options.viewportWidth),
rowNumberWidth: options.rowNumberWidth,
rowNumberWidth: toValue(options.rowNumberWidth),
bufferPx: options.bufferPx ?? 900,
}),
);
@@ -478,7 +478,7 @@ export function useDataGridColumnLayout(options: {
}
function columnContentOffsetLeft(visibleColIdx: number): number {
return options.rowNumberWidth + (renderedColumnOffsets.value[visibleColIdx] ?? 0);
return toValue(options.rowNumberWidth) + (renderedColumnOffsets.value[visibleColIdx] ?? 0);
}
const columnHeaderDragState = ref<ColumnHeaderDragState | null>(null);
@@ -5,8 +5,27 @@ import type { ColumnWidthDensity } from "@/stores/settingsStore";
type CellValue = string | number | boolean | null;
/** Minimum row-number gutter; fits ~4 digits with px-2 padding. */
export const DATA_GRID_ROW_NUM_WIDTH = 48;
/** Largest absolute row number that may appear in the gutter for the current page/window. */
export function resolveDataGridMaxRowNumber(options: { infiniteScroll: boolean; allRowsLoaded: boolean; currentPage: number; pageSize: number; rowCount: number }): number {
if (options.infiniteScroll || options.allRowsLoaded) {
return Math.max(1, options.rowCount);
}
const pageSize = Math.max(1, options.pageSize);
return Math.max(1, Math.max(0, options.currentPage - 1) * pageSize + Math.max(options.rowCount, 1));
}
/** Grow the sticky # column so multi-million row indexes are not clipped or spilled into data cells. */
export function dataGridRowNumberColumnWidth(maxRowNumber: number, fontSize = 12, measureTextWidth?: (text: string) => number | undefined): number {
const text = String(Math.max(1, Math.floor(Math.max(0, maxRowNumber))));
const measured = measureTextWidth?.(text);
const contentWidth = typeof measured === "number" && Number.isFinite(measured) && measured > 0 ? measured : text.length * Math.max(8, Math.ceil(fontSize * 0.65));
// Keep ~4 digits in the default 48px gutter; measured text gets the same 16px padding as px-2.
return Math.max(DATA_GRID_ROW_NUM_WIDTH, Math.ceil(contentWidth) + 16);
}
export function resizeDataGridColumnWidth(startWidth: number, deltaX: number): number {
return Math.max(DATA_GRID_COL_MIN_WIDTH, startWidth + deltaX);
}
@@ -21,6 +40,7 @@ export interface UseDataGridColumnResizeOptions {
columnStructureSignature: ComputedRef<string>;
measureHeaderText?: (text: string) => number | undefined;
headerMeasurementKey?: Ref<unknown>;
rowNumberWidth?: Ref<number> | ComputedRef<number>;
}
export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions) {
@@ -143,14 +163,16 @@ export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions)
const renderedColumnWidths = computed(() => columnWidths.value.slice());
const totalWidth = computed(() => renderedColumnWidths.value.reduce((a, b) => a + b, 0) + DATA_GRID_ROW_NUM_WIDTH);
const resolvedRowNumberWidth = computed(() => options.rowNumberWidth?.value ?? DATA_GRID_ROW_NUM_WIDTH);
const totalWidth = computed(() => renderedColumnWidths.value.reduce((a, b) => a + b, 0) + resolvedRowNumberWidth.value);
const columnVars = computed(() => {
const vars: Record<string, string> = {};
renderedColumnWidths.value.forEach((w, i) => {
vars[`--col-w-${i}`] = `${w}px`;
});
vars["--row-num-w"] = `${DATA_GRID_ROW_NUM_WIDTH}px`;
vars["--row-num-w"] = `${resolvedRowNumberWidth.value}px`;
vars["--total-w"] = `${totalWidth.value}px`;
return vars;
});