From 4e96660eaba1a79abdbcf9136028fbe0d111a671 Mon Sep 17 00:00:00 2001 From: home Date: Thu, 30 Jul 2026 23:44:13 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(datagrid):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=9C=80=E5=B0=8F=E5=88=97=E5=AE=BD=E4=B8=8B=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E4=B8=8E=E6=8E=92=E5=BA=8F=E6=8E=A7=E4=BB=B6=E9=9A=90?= =?UTF-8?q?=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/DataGridColumnTitle.test.tsx | 3 +++ frontend/src/components/DataGridColumnTitle.tsx | 9 ++++++--- .../useDataGridColumnResize.interaction.test.tsx | 4 ++-- frontend/src/components/useDataGridColumnResize.ts | 12 +++++++----- frontend/src/utils/dataGridDisplay.test.ts | 3 ++- frontend/src/utils/dataGridDisplay.ts | 7 +++++-- frontend/src/v2-theme.css | 12 ++++++++++++ 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/DataGridColumnTitle.test.tsx b/frontend/src/components/DataGridColumnTitle.test.tsx index ee9739b1..f9bdc169 100644 --- a/frontend/src/components/DataGridColumnTitle.test.tsx +++ b/frontend/src/components/DataGridColumnTitle.test.tsx @@ -90,6 +90,7 @@ describe('DataGridColumnTitle', () => { expect(markup).toContain('主键 ID'); expect(markup).toContain('flex-direction:column'); expect(markup).toContain('align-items:flex-start'); + expect(markup).toContain('display:inline-flex;width:100%;max-width:100%;min-width:0;overflow:hidden'); }); it('keeps column metadata tooltip readable in light theme', () => { @@ -187,6 +188,8 @@ describe('DataGridColumnTitle', () => { expect(markup).toContain('data-grid-column-filter-active="true"'); expect(markup).toContain('data-grid-column-filter-popover="true"'); expect(markup).toContain('flex:1 1 auto'); + expect(markup).toContain('display:inline-flex;flex:1 1 auto;max-width:100%;min-width:0;overflow:hidden'); + expect(markup).toContain('width:100%'); expect(markup).toContain('Filter status'); expect(markup).toContain('value="active"'); }); diff --git a/frontend/src/components/DataGridColumnTitle.tsx b/frontend/src/components/DataGridColumnTitle.tsx index c4e94da3..1a5debd5 100644 --- a/frontend/src/components/DataGridColumnTitle.tsx +++ b/frontend/src/components/DataGridColumnTitle.tsx @@ -258,7 +258,9 @@ const DataGridColumnTitle: React.FC = ({ styles={{ root: { maxWidth: 640 } }} {...(!darkMode ? { color: 'rgba(0, 0, 0, 0.82)' } : {})} > - {titleNode} + + {titleNode} + ); })(); @@ -483,15 +485,16 @@ const DataGridColumnTitle: React.FC = ({ - + {titleWithOptionalTooltip} { expect(fakeWindow.listenerCount('blur')).toBe(0); }); - it('clamps manual data column resizing to the shared 80px minimum', () => { + it('clamps manual data column resizing to the shared 120px minimum', () => { beginResize(); act(() => fakeDocument.dispatch('mouseup', { clientX: 0 })); - expectLastWidthUpdate(80); + expectLastWidthUpdate(120); }); it('cancels pending RAF and gate work without committing when unmounted mid-resize', () => { diff --git a/frontend/src/components/useDataGridColumnResize.ts b/frontend/src/components/useDataGridColumnResize.ts index 424a63bf..6d6faf36 100644 --- a/frontend/src/components/useDataGridColumnResize.ts +++ b/frontend/src/components/useDataGridColumnResize.ts @@ -1,12 +1,14 @@ import React, { useCallback, useEffect, useRef } from 'react'; -import { resolveDataTableColumnWidth } from '../utils/dataGridDisplay'; +import { + MIN_DATA_TABLE_COLUMN_WIDTH, + resolveDataTableColumnWidth, +} from '../utils/dataGridDisplay'; import { calculateAutoFitColumnWidth } from './dataGridAutoWidth'; import { DEFAULT_GRID_MONO_FONT_FAMILY, GONAVI_ROW_NUMBER_COLUMN_KEY } from './DataGridCore'; const ROW_NUMBER_DEFAULT_WIDTH = 36; const ROW_NUMBER_MIN_WIDTH = 28; const ROW_NUMBER_MAX_WIDTH = 120; -const DATA_COLUMN_MIN_WIDTH = 80; type UseDataGridColumnResizeContext = Record; type ColumnResizeListeners = { @@ -110,7 +112,7 @@ export const useDataGridColumnResize = (ctx: UseDataGridColumnResizeContext) => const finalClientX = Number.isFinite(clientX) ? clientX as number : latestClientX ?? dragState.startX; const deltaX = finalClientX - dragState.startX; const isRowNumberColumn = dragState.key === GONAVI_ROW_NUMBER_COLUMN_KEY; - const minWidth = isRowNumberColumn ? ROW_NUMBER_MIN_WIDTH : DATA_COLUMN_MIN_WIDTH; + const minWidth = isRowNumberColumn ? ROW_NUMBER_MIN_WIDTH : MIN_DATA_TABLE_COLUMN_WIDTH; const maxWidth = isRowNumberColumn ? ROW_NUMBER_MAX_WIDTH : Number.POSITIVE_INFINITY; const newWidth = Math.min(maxWidth, Math.max(minWidth, dragState.startWidth + deltaX)); setColumnWidthsRef.current((prev: Record) => ({ ...prev, [dragState.key]: newWidth })); @@ -216,7 +218,7 @@ export const useDataGridColumnResize = (ctx: UseDataGridColumnResizeContext) => valueTexts: displayData.slice(0, 200).map((row: any) => row?.[key]), measureHeaderText: (text) => measureTextWidth(text, `600 ${font}`), measureCellText: (text) => measureTextWidth(text, `400 ${font}`), - minWidth: 40, + minWidth: MIN_DATA_TABLE_COLUMN_WIDTH, maxWidth: 600, defaultWidth: densityParams.defaultColumnWidth, }); @@ -249,7 +251,7 @@ export const useDataGridColumnResize = (ctx: UseDataGridColumnResizeContext) => measureHeaderText: buildAutoFitMeasurer(headerEl ?? null, `600 ${densityParams.dataFontSize}px ${DEFAULT_GRID_MONO_FONT_FAMILY}`), measureCellText: buildAutoFitMeasurer(sampleCell ?? null, `400 ${densityParams.dataFontSize}px ${DEFAULT_GRID_MONO_FONT_FAMILY}`), defaultWidth, - minWidth: DATA_COLUMN_MIN_WIDTH, + minWidth: MIN_DATA_TABLE_COLUMN_WIDTH, maxWidth: Math.max(720, Math.floor(containerWidth * 0.85)), }); diff --git a/frontend/src/utils/dataGridDisplay.test.ts b/frontend/src/utils/dataGridDisplay.test.ts index ce9c7b84..590f4e7a 100644 --- a/frontend/src/utils/dataGridDisplay.test.ts +++ b/frontend/src/utils/dataGridDisplay.test.ts @@ -47,7 +47,8 @@ describe('dataGridDisplay helpers', () => { it('keeps manual column widths ahead of density defaults', () => { expect(resolveDataTableColumnWidth({ manualWidth: 320, density: 'compact' })).toBe(320); - expect(resolveDataTableColumnWidth({ manualWidth: undefined, density: 'compact' })).toBe(100); + expect(resolveDataTableColumnWidth({ manualWidth: 80, density: 'comfortable' })).toBe(120); + expect(resolveDataTableColumnWidth({ manualWidth: undefined, density: 'compact' })).toBe(120); }); it('uses subtle themed vertical border colors and transparent when disabled', () => { diff --git a/frontend/src/utils/dataGridDisplay.ts b/frontend/src/utils/dataGridDisplay.ts index 540c5cd1..cb3f47b6 100644 --- a/frontend/src/utils/dataGridDisplay.ts +++ b/frontend/src/utils/dataGridDisplay.ts @@ -26,6 +26,9 @@ export const MIN_DATA_TABLE_FONT_SIZE = 10; export const MAX_DATA_TABLE_FONT_SIZE = 18; export const MIN_SIDEBAR_TREE_FONT_SIZE = 10; export const MAX_SIDEBAR_TREE_FONT_SIZE = 18; +// Keep enough room for the column title, filter trigger, sorter and resize handle. +// Default, manual and auto-fit widths must all preserve this invariant. +export const MIN_DATA_TABLE_COLUMN_WIDTH = 120; type DensityOptionTranslator = (key: string) => string; @@ -148,10 +151,10 @@ export const resolveDataTableColumnWidth = ({ density: DataTableDensity | null | undefined; }): number => { if (typeof manualWidth === 'number' && Number.isFinite(manualWidth) && manualWidth > 0) { - return manualWidth; + return Math.max(MIN_DATA_TABLE_COLUMN_WIDTH, manualWidth); } - return resolveDataTableDefaultColumnWidth(density); + return Math.max(MIN_DATA_TABLE_COLUMN_WIDTH, resolveDataTableDefaultColumnWidth(density)); }; export const resolveDataTableVerticalBorderColor = ({ diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css index a06e4573..1fca192a 100644 --- a/frontend/src/v2-theme.css +++ b/frontend/src/v2-theme.css @@ -4331,15 +4331,25 @@ body[data-ui-version="v2"] .gn-v2-data-grid .gn-v2-table-designer.is-embedded .a } body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead .ant-table-column-title { + min-width: 0; + overflow: hidden; font-size: var(--gn-data-table-font-size, var(--gn-font-size-mono, 12px)); font-weight: 400 !important; } body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead .ant-table-column-sorters { + width: 100%; + max-width: 100%; + min-width: 0; + overflow: hidden; min-height: 0 !important; align-items: flex-start !important; } +body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead .ant-table-column-sorter { + flex: 0 0 auto; +} + body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead > tr > th.is-single-line-title { vertical-align: middle !important; } @@ -4350,9 +4360,11 @@ body[data-ui-version="v2"] .gn-v2-data-grid .ant-table-thead > tr > th.is-single } body[data-ui-version="v2"] .gn-v2-data-grid .sortable-header-cell-drag-handle { + box-sizing: border-box; min-height: 48px !important; align-items: flex-start !important; padding: 3px 8px !important; + padding-right: 12px !important; } body[data-ui-version="v2"] .gn-v2-data-grid .sortable-header-cell-drag-handle > div {