diff --git a/src/HeaderCell.tsx b/src/HeaderCell.tsx index 03cfbfe617..2178282a8a 100644 --- a/src/HeaderCell.tsx +++ b/src/HeaderCell.tsx @@ -3,7 +3,6 @@ import { css } from '@linaria/core'; import { useRovingTabIndex } from './hooks'; import { - clampColumnWidth, getCellClassname, getCellStyle, getHeaderCellRowSpan, @@ -287,8 +286,7 @@ function ResizeHandle({ column, onColumnResize, direction }: ResizeHandle const offset = resizingOffsetRef.current; if (offset === undefined) return; const { width, right, left } = event.currentTarget.parentElement!.getBoundingClientRect(); - let newWidth = isRtl ? right + offset - event.clientX : event.clientX + offset - left; - newWidth = clampColumnWidth(newWidth, column); + const newWidth = isRtl ? right + offset - event.clientX : event.clientX + offset - left; if (width > 0 && newWidth !== width) { onColumnResize(column, newWidth); } diff --git a/src/hooks/useCalculatedColumns.ts b/src/hooks/useCalculatedColumns.ts index 0bbc33d0c9..8d0d56eed8 100644 --- a/src/hooks/useCalculatedColumns.ts +++ b/src/hooks/useCalculatedColumns.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react'; -import { clampColumnWidth, max, min } from '../utils'; +import { max, min } from '../utils'; import type { CalculatedColumn, CalculatedColumnParent, ColumnOrColumnGroup, Omit } from '../types'; import { renderValue } from '../cellRenderers'; import { SELECT_COLUMN_KEY } from '../Columns'; @@ -176,9 +176,7 @@ export function useCalculatedColumns({ for (const column of columns) { let width = getColumnWidth(column); - if (typeof width === 'number') { - width = clampColumnWidth(width, column); - } else { + if (typeof width === 'string') { // This is a placeholder width so we can continue to use virtualization. // The actual value is set after the column is rendered width = column.minWidth; diff --git a/src/hooks/useColumnWidths.ts b/src/hooks/useColumnWidths.ts index a9b8382934..0e12b56c50 100644 --- a/src/hooks/useColumnWidths.ts +++ b/src/hooks/useColumnWidths.ts @@ -29,28 +29,26 @@ export function useColumnWidths( const newTemplateColumns = [...templateColumns]; const columnsToMeasure: string[] = []; - for (const { key, idx, width } of viewportColumns) { + for (const column of viewportColumns) { + const { key, idx, width } = column; if (key === columnToAutoResize?.key) { - newTemplateColumns[idx] = - columnToAutoResize.width === 'max-content' - ? columnToAutoResize.width - : `${columnToAutoResize.width}px`; + newTemplateColumns[idx] = getColumnWidthForMeasurement(columnToAutoResize.width, column); columnsToMeasure.push(key); } else if ( - typeof width === 'string' && (ignorePreviouslyMeasuredColumns || !measuredColumnWidths.has(key)) && + // If the column is resized by the user, we don't want to measure it again !resizedColumnWidths.has(key) ) { - newTemplateColumns[idx] = width; + newTemplateColumns[idx] = getColumnWidthForMeasurement(width, column); columnsToMeasure.push(key); } } const gridTemplateColumns = newTemplateColumns.join(' '); - useLayoutEffect(updateMeasuredWidths); + useLayoutEffect(updateMeasuredAndResizedWidths); - function updateMeasuredWidths() { + function updateMeasuredAndResizedWidths() { setPreviousGridWidth(gridWidth); if (columnsToMeasure.length === 0) return; @@ -112,8 +110,7 @@ export function useColumnWidths( if (onColumnResize) { const previousWidth = resizedColumnWidths.get(resizingKey); - const newWidth = - typeof nextWidth === 'number' ? nextWidth : measureColumnWidth(gridRef, resizingKey); + const newWidth = measureColumnWidth(gridRef, resizingKey); if (newWidth !== undefined && newWidth !== previousWidth) { onColumnResize(column, newWidth); } @@ -131,3 +128,47 @@ function measureColumnWidth(gridRef: React.RefObject, key const measuringCell = gridRef.current?.querySelector(selector); return measuringCell?.getBoundingClientRect().width; } + +function getColumnWidthForMeasurement( + width: number | string, + { minWidth, maxWidth }: CalculatedColumn +) { + const widthWithUnit = typeof width === 'number' ? `${width}px` : width; + + // don't break in Node.js (SSR) and jsdom + if (typeof CSS === 'undefined') { + return widthWithUnit; + } + + const hasMaxWidth = maxWidth != null; + const clampedWidth = hasMaxWidth + ? `clamp(${minWidth}px, ${widthWithUnit}, ${maxWidth}px)` + : `max(${minWidth}px, ${widthWithUnit})`; + + // clamp() and max() do not handle all the css grid column width values + if (isValidCSSGridColumn(clampedWidth)) { + return clampedWidth; + } + + if ( + hasMaxWidth && + // ignore maxWidth if it less than minWidth + maxWidth >= minWidth && + // we do not want to use minmax with max-content as it + // can result in width being larger than max-content + widthWithUnit !== 'max-content' + ) { + // We are setting maxWidth on the measuring cell but the browser only applies + // it after all the widths are calculated. This results in left over space in some cases. + const minMaxWidth = `minmax(${widthWithUnit}, ${maxWidth}px)`; + if (isValidCSSGridColumn(minMaxWidth)) { + return minMaxWidth; + } + } + + return isValidCSSGridColumn(widthWithUnit) ? widthWithUnit : 'auto'; +} + +function isValidCSSGridColumn(width: string) { + return CSS.supports('grid-template-columns', width); +} diff --git a/src/utils/index.ts b/src/utils/index.ts index f6bd990888..d8178d4b3d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,4 @@ -import type { CalculatedColumn, CalculatedColumnOrColumnGroup, Maybe } from '../types'; +import type { CalculatedColumnOrColumnGroup, Maybe } from '../types'; export * from './colSpanUtils'; export * from './domUtils'; @@ -18,20 +18,6 @@ export function assertIsValidKeyGetter( } } -export function clampColumnWidth( - width: number, - { minWidth, maxWidth }: CalculatedColumn -): number { - width = max(width, minWidth); - - // ignore maxWidth if it less than minWidth - if (typeof maxWidth === 'number' && maxWidth >= minWidth) { - return min(width, maxWidth); - } - - return width; -} - export function getHeaderCellRowSpan( column: CalculatedColumnOrColumnGroup, rowIdx: number diff --git a/test/browser/column/resizable.test.tsx b/test/browser/column/resizable.test.tsx index 1a1f0449ac..10b0104c12 100644 --- a/test/browser/column/resizable.test.tsx +++ b/test/browser/column/resizable.test.tsx @@ -76,12 +76,15 @@ test('should resize column when dragging the handle', async () => { }); test('should use the maxWidth if specified', async () => { - setup({ columns, rows: [] }); + const onColumnResize = vi.fn(); + setup({ columns, rows: [], onColumnResize }); const grid = getGrid(); + expect(onColumnResize).not.toHaveBeenCalled(); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 200px ' }); const [, col2] = getHeaderCells(); await resize({ column: col2, resizeBy: 1000 }); await expect.element(grid).toHaveStyle({ gridTemplateColumns: '100px 400px' }); + expect(onColumnResize).toHaveBeenCalledExactlyOnceWith(expect.objectContaining(columns[1]), 400); }); test('should use the minWidth if specified', async () => { diff --git a/website/routes/CommonFeatures.tsx b/website/routes/CommonFeatures.tsx index b938773043..86d47de39d 100644 --- a/website/routes/CommonFeatures.tsx +++ b/website/routes/CommonFeatures.tsx @@ -117,6 +117,7 @@ function getColumns( { key: 'country', name: 'Country', + maxWidth: 150, renderEditCell: (p) => (