fix(grid): improve transposed selection and navigation
This commit is contained in:
4 files changed
+169
-37
No files matched your search
@@ -1,5 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { averageTransposeRecordWidth, calculateTransposeRecordWidth, defaultTransposeRecordWidth, minTransposeFieldWidth, shouldAutoTransposeSingleRow, transposeFieldWidth, transposeRecordWidthsForDensity, visibleTransposeRecordWindow } from "@/lib/dataGrid/dataGridTranspose";
|
||||
import {
|
||||
averageTransposeRecordWidth,
|
||||
calculateTransposeRecordWidth,
|
||||
defaultTransposeRecordWidth,
|
||||
minTransposeFieldWidth,
|
||||
shouldAutoTransposeSingleRow,
|
||||
transposeAnchorRowIndex,
|
||||
transposeFieldWidth,
|
||||
transposeScrollLeftForRecord,
|
||||
transposeRecordWidthsForDensity,
|
||||
visibleTransposeRecordWindow,
|
||||
} from "@/lib/dataGrid/dataGridTranspose";
|
||||
|
||||
describe("single-row automatic transpose", () => {
|
||||
it("only opens for enabled multi-column results that are not preserving a manual transpose", () => {
|
||||
@@ -12,6 +23,21 @@ describe("single-row automatic transpose", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("transpose row anchor", () => {
|
||||
it("keeps the requested row when multiple rows or cells are selected", () => {
|
||||
const rowIds = [1, 2, 3, 4];
|
||||
|
||||
expect(
|
||||
transposeAnchorRowIndex({
|
||||
requestedRowIndex: 3,
|
||||
rowIds,
|
||||
selectedRowIds: new Set([1, 4]),
|
||||
selectedRange: { startRow: 0, endRow: 3, startCol: 0, endCol: 1 },
|
||||
}),
|
||||
).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("dataGridTranspose density widths", () => {
|
||||
it("uses the shared density preset for record and field widths", () => {
|
||||
const values = ["x".repeat(40)];
|
||||
@@ -78,3 +104,46 @@ describe("dataGridTranspose density widths", () => {
|
||||
expect(averageTransposeRecordWidth([], "compact")).toBe(defaultTransposeRecordWidth("compact"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("transpose record scrolling", () => {
|
||||
it("uses the scroll position after the sticky field for virtualization", () => {
|
||||
expect(
|
||||
visibleTransposeRecordWindow({
|
||||
totalRecords: 3,
|
||||
scrollLeft: 500,
|
||||
viewportWidth: 300,
|
||||
pinnedWidth: 100,
|
||||
recordWidth: 230,
|
||||
recordOffsets: [0, 500, 596, 692],
|
||||
overscan: 0,
|
||||
}),
|
||||
).toEqual({ start: 1, end: 3, beforeWidth: 500, afterWidth: 0 });
|
||||
});
|
||||
|
||||
it("uses actual record widths and nearest scrolling", () => {
|
||||
const recordOffsets = [0, 500, 596, 692];
|
||||
|
||||
expect(
|
||||
transposeScrollLeftForRecord({
|
||||
recordIndex: 2,
|
||||
totalRecords: 3,
|
||||
viewportWidth: 300,
|
||||
pinnedWidth: 100,
|
||||
recordWidth: 230,
|
||||
recordOffsets,
|
||||
currentScrollLeft: 0,
|
||||
}),
|
||||
).toBe(492);
|
||||
expect(
|
||||
transposeScrollLeftForRecord({
|
||||
recordIndex: 1,
|
||||
totalRecords: 3,
|
||||
viewportWidth: 300,
|
||||
pinnedWidth: 100,
|
||||
recordWidth: 230,
|
||||
recordOffsets,
|
||||
currentScrollLeft: 450,
|
||||
}),
|
||||
).toBe(450);
|
||||
});
|
||||
});
|
||||
@@ -50,6 +50,7 @@ export interface TransposeRecordWindowOptions {
|
||||
viewportWidth: number;
|
||||
pinnedWidth: number;
|
||||
recordWidth: number;
|
||||
recordOffsets?: readonly number[];
|
||||
overscan?: number;
|
||||
}
|
||||
|
||||
@@ -159,6 +160,8 @@ export interface TransposeScrollLeftOptions {
|
||||
viewportWidth: number;
|
||||
pinnedWidth: number;
|
||||
recordWidth: number;
|
||||
recordOffsets?: readonly number[];
|
||||
currentScrollLeft?: number;
|
||||
}
|
||||
|
||||
export interface TransposeRecordIndexesForModeOptions {
|
||||
@@ -269,8 +272,25 @@ export function visibleTransposeRecordWindow(options: TransposeRecordWindowOptio
|
||||
}
|
||||
|
||||
const overscan = options.overscan ?? 2;
|
||||
const recordScrollLeft = Math.max(0, options.scrollLeft - options.pinnedWidth);
|
||||
const recordScrollLeft = Math.max(0, options.scrollLeft);
|
||||
const recordViewportWidth = Math.max(0, options.viewportWidth - options.pinnedWidth);
|
||||
if (options.recordOffsets?.length === options.totalRecords + 1) {
|
||||
const offsets = options.recordOffsets;
|
||||
const firstVisible = Math.max(
|
||||
0,
|
||||
offsets.findIndex((_offset, index) => index < options.totalRecords && offsets[index + 1] > recordScrollLeft),
|
||||
);
|
||||
const firstAfterViewport = offsets.findIndex((offset, index) => index > firstVisible && offset >= recordScrollLeft + recordViewportWidth);
|
||||
const start = Math.max(0, firstVisible - overscan);
|
||||
const end = Math.min(options.totalRecords, (firstAfterViewport < 0 ? options.totalRecords : firstAfterViewport) + overscan);
|
||||
const totalWidth = offsets[options.totalRecords];
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
beforeWidth: offsets[start],
|
||||
afterWidth: Math.max(0, totalWidth - offsets[end]),
|
||||
};
|
||||
}
|
||||
const start = Math.max(0, Math.floor(recordScrollLeft / options.recordWidth) - overscan);
|
||||
const end = Math.min(options.totalRecords, Math.ceil((recordScrollLeft + recordViewportWidth) / options.recordWidth) + overscan + 1);
|
||||
|
||||
@@ -290,18 +310,7 @@ export function transposeRecordIndexesForMode(options: TransposeRecordIndexesFor
|
||||
}
|
||||
|
||||
export function transposeAnchorRowIndex(options: TransposeAnchorOptions): number {
|
||||
const requestedRowId = options.rowIds[options.requestedRowIndex];
|
||||
if (requestedRowId !== undefined && options.selectedRowIds.size > 1 && options.selectedRowIds.has(requestedRowId)) {
|
||||
const firstSelectedIndex = options.rowIds.findIndex((rowId) => options.selectedRowIds.has(rowId));
|
||||
if (firstSelectedIndex >= 0) return firstSelectedIndex;
|
||||
}
|
||||
|
||||
const range = options.selectedRange;
|
||||
if (range && range.startRow !== range.endRow && options.requestedRowIndex >= range.startRow && options.requestedRowIndex <= range.endRow) {
|
||||
return range.startRow;
|
||||
}
|
||||
|
||||
return options.requestedRowIndex;
|
||||
return Math.max(0, Math.min(options.rowIds.length - 1, options.requestedRowIndex));
|
||||
}
|
||||
|
||||
export function transposeFieldWidth(columns: string[], options: TransposeFieldWidthOptions = {}): number {
|
||||
@@ -317,8 +326,15 @@ export function transposeFieldWidth(columns: string[], options: TransposeFieldWi
|
||||
|
||||
export function transposeScrollLeftForRecord(options: TransposeScrollLeftOptions): number {
|
||||
if (options.recordWidth <= 0 || options.totalRecords <= 0) return 0;
|
||||
const desired = Math.max(0, options.recordIndex) * options.recordWidth;
|
||||
const totalWidth = options.pinnedWidth + options.totalRecords * options.recordWidth;
|
||||
const recordIndex = Math.max(0, Math.min(options.totalRecords - 1, options.recordIndex));
|
||||
const offsets = options.recordOffsets?.length === options.totalRecords + 1 ? options.recordOffsets : undefined;
|
||||
const recordStart = offsets ? offsets[recordIndex] : recordIndex * options.recordWidth;
|
||||
const recordEnd = offsets ? offsets[recordIndex + 1] : recordStart + options.recordWidth;
|
||||
const recordsWidth = offsets ? offsets[options.totalRecords] : options.totalRecords * options.recordWidth;
|
||||
const recordViewportWidth = Math.max(0, options.viewportWidth - options.pinnedWidth);
|
||||
const currentScrollLeft = Math.max(0, options.currentScrollLeft ?? recordStart);
|
||||
const desired = recordStart < currentScrollLeft ? recordStart : recordEnd > currentScrollLeft + recordViewportWidth ? recordEnd - recordViewportWidth : currentScrollLeft;
|
||||
const totalWidth = options.pinnedWidth + recordsWidth;
|
||||
const maxScrollLeft = Math.max(0, totalWidth - options.viewportWidth);
|
||||
return Math.min(desired, maxScrollLeft);
|
||||
return Math.max(0, Math.min(desired, maxScrollLeft));
|
||||
}
|
||||
Reference in new issue
Block a user