diff --git a/frontend/src/components/DataGrid.ddl.test.tsx b/frontend/src/components/DataGrid.ddl.test.tsx
index 8887b51..cc16b52 100644
--- a/frontend/src/components/DataGrid.ddl.test.tsx
+++ b/frontend/src/components/DataGrid.ddl.test.tsx
@@ -237,13 +237,17 @@ vi.mock('antd', () => {
);
- return {
- Table: (props: any) => {
+ const MockTable = React.forwardRef((_props: any, _ref) => {
+ const props = _props;
const { columns } = props;
testRenderState.latestColumns = Array.isArray(columns) ? columns : [];
testRenderState.latestTableProps = props;
return
;
- },
+ });
+ MockTable.displayName = 'MockTable';
+
+ return {
+ Table: MockTable,
message: messageApi,
Input,
Button,
@@ -719,12 +723,25 @@ describe('DataGrid DDL interactions', () => {
const idColumn = testRenderState.latestColumns.find((column) => column.key === 'id');
const cellProps = idColumn.onCell({ __gonavi_row_key__: 'row-1', id: 1 });
+ const contextTarget = {
+ closest: (selector: string) => selector === '[data-row-key][data-col-name]'
+ ? {
+ getAttribute: (name: string) => {
+ if (name === 'data-row-key') return 'row-1';
+ if (name === 'data-col-name') return 'id';
+ return null;
+ },
+ }
+ : null,
+ } as unknown as HTMLElement;
await act(async () => {
cellProps.onContextMenu({
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
clientX: 160,
clientY: 120,
+ currentTarget: contextTarget,
+ target: contextTarget,
});
});
@@ -803,12 +820,25 @@ describe('DataGrid DDL interactions', () => {
const nameColumn = testRenderState.latestColumns.find((column) => column.key === 'name');
const cellProps = nameColumn.onCell({ __gonavi_row_key__: 'row-1', id: 1, name: 'alpha' });
+ const contextTarget = {
+ closest: (selector: string) => selector === '[data-row-key][data-col-name]'
+ ? {
+ getAttribute: (name: string) => {
+ if (name === 'data-row-key') return 'row-1';
+ if (name === 'data-col-name') return 'name';
+ return null;
+ },
+ }
+ : null,
+ } as unknown as HTMLElement;
await act(async () => {
cellProps.onContextMenu({
preventDefault: vi.fn(),
stopPropagation: vi.fn(),
clientX: 160,
clientY: 120,
+ currentTarget: contextTarget,
+ target: contextTarget,
});
});
@@ -827,6 +857,8 @@ describe('DataGrid DDL interactions', () => {
stopPropagation: vi.fn(),
clientX: 160,
clientY: 120,
+ currentTarget: contextTarget,
+ target: contextTarget,
});
});
await act(async () => {
diff --git a/frontend/src/components/DataGrid.layout.test.tsx b/frontend/src/components/DataGrid.layout.test.tsx
index 04f5de0..de07fca 100644
--- a/frontend/src/components/DataGrid.layout.test.tsx
+++ b/frontend/src/components/DataGrid.layout.test.tsx
@@ -369,10 +369,13 @@ describe('DataGrid layout', () => {
const source = readFileSync(new URL('./DataGrid.tsx', import.meta.url), 'utf8');
expect(source).toContain('virtualHorizontalElementsRef');
+ expect(source).toContain('type VirtualTableScrollReference = TableReference & {');
+ expect(source).toContain('const tableRef = useRef(null);');
expect(source).toContain('resolveDataGridHorizontalWheelDelta({');
expect(source).toContain('const scheduleVirtualHorizontalWheel = useCallback');
expect(source).toContain('pendingTableHorizontalDeltaRef.current += delta;');
expect(source).toContain('tableHorizontalWheelRafRef.current = requestAnimationFrame');
+ expect(source).toContain('tableInstance.scrollTo({ left: clampedOffset, top: holderEl.scrollTop });');
expect(source).toContain('if (externalSyncRafRef.current !== null)');
expect(source).toContain('externalSyncRafRef.current = requestAnimationFrame');
expect(source).toContain('const scheduleSyncExternalScrollFromTargets = useCallback');
@@ -388,11 +391,33 @@ describe('DataGrid layout', () => {
expect(source).toContain('const attachDataGridVirtualEditRenderVersion = (');
expect(source).toContain('hasDataGridVirtualEditRenderVersionChanged(record, prevRecord)');
expect(source).not.toContain('if (enableVirtual && enableInlineEditableCell) {\n return (\n {
+ const harnessSource = readFileSync(new URL('../dev/PerfDataGridHarness.tsx', import.meta.url), 'utf8');
+ expect(harnessSource).toContain("options={[");
+ expect(harnessSource).toContain("{ label: '旧版 UI', value: 'legacy' }");
+ expect(harnessSource).toContain("{ label: '新版 UI', value: 'v2' }");
+ expect(harnessSource).toContain("{ value: 'comfortable', label: '标准' }");
+ expect(harnessSource).toContain("{ value: 'standard', label: '紧凑' }");
+ expect(harnessSource).toContain("{ value: 'compact', label: '极紧凑' }");
+ expect(harnessSource).toContain("document.body.setAttribute('data-ui-version', uiVersion);");
+ expect(harnessSource).toContain("if (value === null || value === undefined || value === '') {");
+ expect(harnessSource).toContain("const currentState = useStore.getState();");
});
});
diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx
index 790fc42..90ccc5b 100644
--- a/frontend/src/components/DataGrid.tsx
+++ b/frontend/src/components/DataGrid.tsx
@@ -4,6 +4,7 @@ import { createPortal } from 'react-dom';
import { Table, message, Input, Button, Dropdown, MenuProps, Form, Pagination, Select, Modal, Checkbox, Segmented, Tooltip, Popover, DatePicker, TimePicker } from 'antd';
import dayjs from 'dayjs';
import type { SortOrder, ColumnType } from 'antd/es/table/interface';
+import type { Reference as TableReference } from 'rc-table';
import { CloseOutlined, ConsoleSqlOutlined, CopyOutlined, EditOutlined, ExportOutlined, FileTextOutlined, LeftOutlined, RightOutlined, SearchOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons';
import {
DndContext,
@@ -38,6 +39,7 @@ import {
import { resolvePaginationPageText, resolvePaginationSummaryText, resolvePaginationTotalForControl } from '../utils/dataGridPagination';
import { resolveGridSortInfoFromTableSorter } from '../utils/dataGridSort';
import {
+ calculateExternalHorizontalScrollInnerWidth,
calculateTableBodyBottomPadding,
calculateVirtualTableScrollX,
resolveDataGridHorizontalWheelDelta,
@@ -178,6 +180,7 @@ const CELL_KEY_SEP = '\u0001';
const CELL_SELECTION_DRAG_THRESHOLD_PX = 4;
const DATE_TIME_CACHE_LIMIT = 2000;
const TABLE_CELL_PREVIEW_MAX_CHARS = 240;
+const DATA_GRID_DISPLAY_RENDER_VERSION = Symbol('DATA_GRID_DISPLAY_RENDER_VERSION');
const DATA_GRID_VIRTUAL_EDIT_RENDER_VERSION = Symbol('DATA_GRID_VIRTUAL_EDIT_RENDER_VERSION');
const normalizedDateTimeCache = new Map();
const objectCellPreviewCache = new WeakMap