From 1c1f63a0a0a107ce982a7ff38b0e5e57237018b8 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 9 Oct 2026 15:20:12 +0300 Subject: [PATCH] fix: reuse deferred Grid column auto-width calculation in GridPro Port of vaadin/web-components#13047 to 25.2. Co-Authored-By: Claude Opus 5.5 --- dev/pages/GridPro.tsx | 7 +++- packages/react-components-pro/src/GridPro.tsx | 9 +++- packages/react-components/package.json | 4 ++ packages/react-components/src/Grid.tsx | 24 ++--------- .../useDeferredColumnWidthRecalculation.ts | 25 +++++++++++ test/Grid.spec.tsx | 41 ++++++++++++++++++- 6 files changed, 85 insertions(+), 25 deletions(-) create mode 100644 packages/react-components/src/utils/useDeferredColumnWidthRecalculation.ts diff --git a/dev/pages/GridPro.tsx b/dev/pages/GridPro.tsx index 7235886d..cfa3cbf6 100644 --- a/dev/pages/GridPro.tsx +++ b/dev/pages/GridPro.tsx @@ -21,7 +21,12 @@ const users: Item[] = [...Array(1000)].map((_, i) => { export default function () { return ( - + First name (auto width)} + autoWidth + flexGrow={0} + /> } diff --git a/packages/react-components-pro/src/GridPro.tsx b/packages/react-components-pro/src/GridPro.tsx index 5a7202f4..85b9e017 100644 --- a/packages/react-components-pro/src/GridPro.tsx +++ b/packages/react-components-pro/src/GridPro.tsx @@ -12,6 +12,7 @@ import { type ForwardedRef, forwardRef, type ReactElement, type RefAttributes } import type { GridDefaultItem, GridProps } from '@vaadin/react-components/Grid.js'; import { GridPro as _GridPro, type GridProElement, type GridProProps as _GridProProps } from './generated/GridPro.js'; import { useModelRenderer } from '@vaadin/react-components/renderers/useModelRenderer.js'; +import useDeferredColumnWidthRecalculation from '@vaadin/react-components/utils/useDeferredColumnWidthRecalculation.js'; export * from './generated/GridPro.js'; @@ -24,10 +25,14 @@ function GridPro( props: GridProProps, ref: ForwardedRef>, ): ReactElement | null { - const [portals, rowDetailsRenderer] = useModelRenderer(props.rowDetailsRenderer); + const [portals, rowDetailsRenderer] = useModelRenderer(props.rowDetailsRenderer, { + renderMode: 'microtask', + }); + + const finalRef = useDeferredColumnWidthRecalculation(ref); return ( - <_GridPro {...props} ref={ref} rowDetailsRenderer={rowDetailsRenderer}> + <_GridPro {...props} ref={finalRef} rowDetailsRenderer={rowDetailsRenderer}> {props.children} {portals} diff --git a/packages/react-components/package.json b/packages/react-components/package.json index ac35f869..1981abd9 100644 --- a/packages/react-components/package.json +++ b/packages/react-components/package.json @@ -536,6 +536,10 @@ "./utils/mapItemsWithComponents.d.ts.map": "./utils/mapItemsWithComponents.d.ts.map", "./utils/mapItemsWithComponents.js": "./utils/mapItemsWithComponents.js", "./utils/mapItemsWithComponents.js.map": "./utils/mapItemsWithComponents.js.map", + "./utils/useDeferredColumnWidthRecalculation.d.ts": "./utils/useDeferredColumnWidthRecalculation.d.ts", + "./utils/useDeferredColumnWidthRecalculation.d.ts.map": "./utils/useDeferredColumnWidthRecalculation.d.ts.map", + "./utils/useDeferredColumnWidthRecalculation.js": "./utils/useDeferredColumnWidthRecalculation.js", + "./utils/useDeferredColumnWidthRecalculation.js.map": "./utils/useDeferredColumnWidthRecalculation.js.map", "./utils/useMergedRefs.d.ts": "./utils/useMergedRefs.d.ts", "./utils/useMergedRefs.d.ts.map": "./utils/useMergedRefs.d.ts.map", "./utils/useMergedRefs.js": "./utils/useMergedRefs.js", diff --git a/packages/react-components/src/Grid.tsx b/packages/react-components/src/Grid.tsx index 7a761d03..cba5636f 100644 --- a/packages/react-components/src/Grid.tsx +++ b/packages/react-components/src/Grid.tsx @@ -1,12 +1,4 @@ -import { - type ComponentType, - type ForwardedRef, - forwardRef, - type ReactElement, - type RefAttributes, - useLayoutEffect, - useRef, -} from 'react'; +import { type ComponentType, type ForwardedRef, forwardRef, type ReactElement, type RefAttributes } from 'react'; import { Grid as _Grid, type GridDefaultItem, @@ -15,7 +7,7 @@ import { } from './generated/Grid.js'; import type { GridRowDetailsReactRendererProps } from './renderers/grid.js'; import { useModelRenderer } from './renderers/useModelRenderer.js'; -import useMergedRefs from './utils/useMergedRefs.js'; +import useDeferredColumnWidthRecalculation from './utils/useDeferredColumnWidthRecalculation.js'; export * from './generated/Grid.js'; @@ -32,17 +24,7 @@ function Grid( renderMode: 'microtask', }); - const innerRef = useRef(null); - const finalRef = useMergedRefs(innerRef, ref); - - useLayoutEffect(() => { - innerRef.current!.recalculateColumnWidths = function (...args) { - // Wait for column content to finish rendering before recalculating widths. - queueMicrotask(() => { - Object.getPrototypeOf(this).recalculateColumnWidths.call(this, ...args); - }); - }; - }, []); + const finalRef = useDeferredColumnWidthRecalculation(ref); return ( <_Grid {...props} ref={finalRef} rowDetailsRenderer={rowDetailsRenderer}> diff --git a/packages/react-components/src/utils/useDeferredColumnWidthRecalculation.ts b/packages/react-components/src/utils/useDeferredColumnWidthRecalculation.ts new file mode 100644 index 00000000..bfcc3cf1 --- /dev/null +++ b/packages/react-components/src/utils/useDeferredColumnWidthRecalculation.ts @@ -0,0 +1,25 @@ +import { type ForwardedRef, type RefCallback, useCallback } from 'react'; +import type { GridElement } from '../generated/Grid.js'; +import useMergedRefs from './useMergedRefs.js'; + +/** + * Defers `recalculateColumnWidths()` into a microtask so that the React renderer + * portals have content before the column widths are measured. + * Returns the ref to pass to the grid element. + */ +export default function useDeferredColumnWidthRecalculation>( + ref: ForwardedRef, +): RefCallback { + const patchRef = useCallback((element: T | null) => { + if (element) { + element.recalculateColumnWidths = function (...args) { + // Wait for column content to finish rendering before recalculating widths. + queueMicrotask(() => { + Object.getPrototypeOf(this).recalculateColumnWidths.call(this, ...args); + }); + }; + } + }, []); + + return useMergedRefs(patchRef, ref); +} diff --git a/test/Grid.spec.tsx b/test/Grid.spec.tsx index 97ba2044..d8430645 100644 --- a/test/Grid.spec.tsx +++ b/test/Grid.spec.tsx @@ -3,7 +3,10 @@ import { render } from 'vitest-browser-react'; import { Grid, type GridDataProvider } from '../packages/react-components/src/Grid.js'; import { GridColumn, GridColumnElement } from '../packages/react-components/src/GridColumn.js'; import { GridFilterColumn } from '../packages/react-components/src/GridFilterColumn.js'; -import { GridProEditColumn } from '../packages/react-components-pro/src/GridProEditColumn.js'; +import { + GridProEditColumn, + type GridProEditColumnElement, +} from '../packages/react-components-pro/src/GridProEditColumn.js'; import { GridSelectionColumn } from '../packages/react-components/src/GridSelectionColumn.js'; import { GridSortColumn } from '../packages/react-components/src/GridSortColumn.js'; import { GridTreeColumn } from '../packages/react-components/src/GridTreeColumn.js'; @@ -346,6 +349,42 @@ describe('Grid', () => { }); describe('GridProEditColumn', () => { + it('should consider custom renderer content with column auto-width', async () => { + function GridProWithAutoWidthColumns() { + const [gridItems, setGridItems] = useState(); + + useEffect(() => { + setTimeout(() => setGridItems(items)); + }, []); + + return ( + items={gridItems}> + header} autoWidth flexGrow={0} /> + + {({ item }) => } + + footer} autoWidth flexGrow={0} /> + + ); + } + + const error = sinon.stub(console, 'error'); + try { + await render(); + + const grid = await findByQuerySelector('vaadin-grid-pro'); + const columns = Array.from(grid.querySelectorAll('vaadin-grid-pro-edit-column')); + expect(columns).to.have.length(3); + + for (const column of columns) { + await until(() => parseFloat(String(column.width)) > 300); + } + } finally { + error.restore(); + } + expect(error.called).to.be.false; + }); + it('should render correctly', async () => { await render( items={items}>