From 530cfa0b7c9b9db2d023bd6a1fa39b4002034630 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 13 May 2026 23:29:30 +0800 Subject: [PATCH] fix(grid): reserve header control width --- src/composables/useDataGridColumnResize.ts | 51 ++++++++-------------- src/lib/dataGridColumnWidth.ts | 34 +++++++++++++++ tests/dataGridColumnWidth.test.ts | 21 +++++++++ 3 files changed, 73 insertions(+), 33 deletions(-) create mode 100644 src/lib/dataGridColumnWidth.ts create mode 100644 tests/dataGridColumnWidth.test.ts diff --git a/src/composables/useDataGridColumnResize.ts b/src/composables/useDataGridColumnResize.ts index 1889a139fe..161372d7b0 100644 --- a/src/composables/useDataGridColumnResize.ts +++ b/src/composables/useDataGridColumnResize.ts @@ -1,20 +1,15 @@ import { ref, computed, type ComputedRef, type Ref } from "vue"; import { useElementSize } from "@vueuse/core"; +import { + calculateDataGridColumnWidth, + DATA_GRID_COL_MIN_WIDTH, + DATA_GRID_SAMPLE_ROWS, +} from "@/lib/dataGridColumnWidth"; type CellValue = string | number | boolean | null; -export const COL_MIN_WIDTH = 60; -export const COL_MAX_WIDTH = 400; -const COL_CHAR_WIDTH = 8; -const COL_HEADER_PADDING = 48; -const COL_CELL_PADDING = 28; -const COL_SAMPLE_ROWS = 50; const ROW_NUM_WIDTH = 48; -function estimateTextWidth(text: string, padding: number): number { - return text.length * COL_CHAR_WIDTH + padding; -} - export interface UseDataGridColumnResizeOptions { columns: ComputedRef; rows: ComputedRef; @@ -28,21 +23,18 @@ export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions) const { width: gridWidth } = useElementSize(gridRef); let isResizing = false; + function sampleColumnValues(rowData: CellValue[][], colIdx: number): CellValue[] { + return rowData.slice(0, DATA_GRID_SAMPLE_ROWS).map((row) => row[colIdx] ?? null); + } + function initColumnWidths() { if (columnWidths.value.length !== columns.value.length) { const rowData = rows.value; - const sampleCount = Math.min(rowData.length, COL_SAMPLE_ROWS); columnWidths.value = columns.value.map((colName, colIdx) => { - let maxWidth = estimateTextWidth(colName, COL_HEADER_PADDING); - for (let i = 0; i < sampleCount; i++) { - const val = rowData[i]?.[colIdx]; - if (val == null) continue; - const text = typeof val === "object" ? JSON.stringify(val) : String(val); - const displayLen = Math.min(text.length, 60); - const w = displayLen * COL_CHAR_WIDTH + COL_CELL_PADDING; - if (w > maxWidth) maxWidth = w; - } - return Math.max(COL_MIN_WIDTH, Math.min(COL_MAX_WIDTH, Math.round(maxWidth))); + return calculateDataGridColumnWidth({ + columnName: colName, + sampleValues: sampleColumnValues(rowData, colIdx), + }); }); } } @@ -53,7 +45,7 @@ export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions) const startX = event.clientX; const startWidth = columnWidths.value[colIdx]; const onMove = (e: MouseEvent) => { - columnWidths.value[colIdx] = Math.max(60, startWidth + e.clientX - startX); + columnWidths.value[colIdx] = Math.max(DATA_GRID_COL_MIN_WIDTH, startWidth + e.clientX - startX); }; const onUp = () => { document.removeEventListener("mousemove", onMove); @@ -70,17 +62,10 @@ export function useDataGridColumnResize(options: UseDataGridColumnResizeOptions) const colName = columns.value[colIdx]; if (!colName) return; const rowData = rows.value; - const sampleCount = Math.min(rowData.length, COL_SAMPLE_ROWS); - let maxWidth = estimateTextWidth(colName, COL_HEADER_PADDING); - for (let i = 0; i < sampleCount; i++) { - const val = rowData[i]?.[colIdx]; - if (val == null) continue; - const text = typeof val === "object" ? JSON.stringify(val) : String(val); - const displayLen = Math.min(text.length, 60); - const w = displayLen * COL_CHAR_WIDTH + COL_CELL_PADDING; - if (w > maxWidth) maxWidth = w; - } - columnWidths.value[colIdx] = Math.max(COL_MIN_WIDTH, Math.min(COL_MAX_WIDTH, Math.round(maxWidth))); + columnWidths.value[colIdx] = calculateDataGridColumnWidth({ + columnName: colName, + sampleValues: sampleColumnValues(rowData, colIdx), + }); } const baseTotalWidth = computed(() => columnWidths.value.reduce((a, b) => a + b, 0)); diff --git a/src/lib/dataGridColumnWidth.ts b/src/lib/dataGridColumnWidth.ts new file mode 100644 index 0000000000..6e5b8ee1a5 --- /dev/null +++ b/src/lib/dataGridColumnWidth.ts @@ -0,0 +1,34 @@ +type CellValue = string | number | boolean | null; + +export const DATA_GRID_COL_MIN_WIDTH = 60; +export const DATA_GRID_COL_MAX_WIDTH = 400; +export const DATA_GRID_CHAR_WIDTH = 8; +export const DATA_GRID_HEADER_CONTROL_WIDTH = 64; +export const DATA_GRID_CELL_PADDING = 28; +export const DATA_GRID_SAMPLE_ROWS = 50; + +function estimateTextWidth(text: string, padding: number): number { + return text.length * DATA_GRID_CHAR_WIDTH + padding; +} + +function displaySampleValue(value: CellValue): string | null { + if (value == null) return null; + return typeof value === "object" ? JSON.stringify(value) : String(value); +} + +export function calculateDataGridColumnWidth(options: { + columnName: string; + sampleValues: readonly CellValue[]; +}): number { + let maxWidth = estimateTextWidth(options.columnName, DATA_GRID_HEADER_CONTROL_WIDTH); + + for (const value of options.sampleValues.slice(0, DATA_GRID_SAMPLE_ROWS)) { + const text = displaySampleValue(value); + if (text == null) continue; + const displayLen = Math.min(text.length, 60); + const width = displayLen * DATA_GRID_CHAR_WIDTH + DATA_GRID_CELL_PADDING; + if (width > maxWidth) maxWidth = width; + } + + return Math.max(DATA_GRID_COL_MIN_WIDTH, Math.min(DATA_GRID_COL_MAX_WIDTH, Math.round(maxWidth))); +} diff --git a/tests/dataGridColumnWidth.test.ts b/tests/dataGridColumnWidth.test.ts new file mode 100644 index 0000000000..a500bf5315 --- /dev/null +++ b/tests/dataGridColumnWidth.test.ts @@ -0,0 +1,21 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { calculateDataGridColumnWidth, DATA_GRID_HEADER_CONTROL_WIDTH } from "../src/lib/dataGridColumnWidth.ts"; + +test("reserves header control space when auto-sizing a column from the header", () => { + const width = calculateDataGridColumnWidth({ + columnName: "created_at", + sampleValues: [], + }); + + assert.equal(width, "created_at".length * 8 + DATA_GRID_HEADER_CONTROL_WIDTH); +}); + +test("keeps cell content as the sizing driver when it is wider than header controls", () => { + const width = calculateDataGridColumnWidth({ + columnName: "id", + sampleValues: ["a long enough value to dominate"], + }); + + assert.equal(width, "a long enough value to dominate".length * 8 + 28); +});