diff --git a/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts b/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts index 75c278c23c..e7e7f0c9c9 100644 --- a/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts +++ b/packages/table-core/src/features/column-resizing/columnResizingFeature.utils.ts @@ -1,5 +1,6 @@ import { column_getSize, + getDefaultColumnSizingColumnDef, header_getSize, table_setColumnSizing, } from '../column-sizing/columnSizingFeature.utils' @@ -119,6 +120,18 @@ export function header_getResizeHandler< column_getSize(leafHeader.column), ]) + const defaultSizes = getDefaultColumnSizingColumnDef() + const columnSizingConstraints: Record< + string, + { minSize: number; maxSize: number } + > = {} + header.getLeafHeaders().forEach((leafHeader) => { + columnSizingConstraints[leafHeader.column.id] = { + minSize: leafHeader.column.columnDef.minSize ?? defaultSizes.minSize, + maxSize: leafHeader.column.columnDef.maxSize ?? defaultSizes.maxSize, + } + }) + const clientX = isTouchStartEvent(event) ? Math.round(event.touches[0]!.clientX) : (event as MouseEvent).clientX @@ -142,15 +155,16 @@ export function header_getResizeHandler< ) old.columnSizingStart.forEach(([columnId, headerSize]) => { + const rawSize = + headerSize > 0 + ? headerSize + headerSize * deltaPercentage + : deltaOffset / old.columnSizingStart.length + const constraints = columnSizingConstraints[columnId] + const minSize = constraints?.minSize ?? 0 + const maxSize = constraints?.maxSize ?? Number.MAX_SAFE_INTEGER newColumnSizing[columnId] = - Math.round( - Math.max( - headerSize > 0 - ? headerSize + headerSize * deltaPercentage - : deltaOffset / old.columnSizingStart.length, - 0, - ) * 100, - ) / 100 + Math.round(Math.min(maxSize, Math.max(minSize, rawSize)) * 100) / + 100 }) return { diff --git a/packages/table-core/tests/unit/features/column-resizing/columnResizingFeature.utils.test.ts b/packages/table-core/tests/unit/features/column-resizing/columnResizingFeature.utils.test.ts index 2e9355e0d8..88fceb01bf 100644 --- a/packages/table-core/tests/unit/features/column-resizing/columnResizingFeature.utils.test.ts +++ b/packages/table-core/tests/unit/features/column-resizing/columnResizingFeature.utils.test.ts @@ -422,6 +422,118 @@ describe('header_getResizeHandler', () => { removeEventListenerSpy.mockRestore() }) + + it('should not resize a column below its minSize', () => { + const table = generateTestTableWithData(1, { + columnResizeMode: 'onChange', + }) + + let resizingState = getDefaultColumnResizingState() + table.options.onColumnResizingChange = (updater: any) => { + resizingState = + typeof updater === 'function' ? updater(resizingState) : updater + ;(table.store.state as any).columnResizing = resizingState + } + + const sizingUpdates: Record[] = [] + table.options.onColumnSizingChange = (updater: any) => { + if (typeof updater === 'function') { + const result = updater(table.atoms.columnSizing?.get() ?? {}) + sizingUpdates.push(result) + } else { + sizingUpdates.push(updater) + } + } + + // Column starts at 100px, minSize is 50px + const constrainedColumn = { + ...table.getAllColumns()[0], + id: 'firstName', + columnDef: { enableResizing: true, minSize: 50, size: 100 }, + table, + } + const header = createTestResizeHeader(table, { + getSize: () => 100, + getLeafHeaders: () => [ + { + column: constrainedColumn, + getSize: () => 100, + subHeaders: [], + }, + ], + }) + + const handler = header_getResizeHandler(header as any, document) + handler({ type: 'mousedown', clientX: 200 }) + + // Drag far left — would put column at ~10px without the clamp + const moveEvent = new MouseEvent('mousemove', { clientX: 10 }) + document.dispatchEvent(moveEvent) + + const lastUpdate = sizingUpdates[sizingUpdates.length - 1] + expect(lastUpdate).toBeDefined() + const newSize = lastUpdate!['firstName'] + expect(newSize).toBeGreaterThanOrEqual(50) + + const upEvent = new MouseEvent('mouseup', { clientX: 10 }) + document.dispatchEvent(upEvent) + }) + + it('should not resize a column above its maxSize', () => { + const table = generateTestTableWithData(1, { + columnResizeMode: 'onChange', + }) + + let resizingState = getDefaultColumnResizingState() + table.options.onColumnResizingChange = (updater: any) => { + resizingState = + typeof updater === 'function' ? updater(resizingState) : updater + ;(table.store.state as any).columnResizing = resizingState + } + + const sizingUpdates: Record[] = [] + table.options.onColumnSizingChange = (updater: any) => { + if (typeof updater === 'function') { + const result = updater(table.atoms.columnSizing?.get() ?? {}) + sizingUpdates.push(result) + } else { + sizingUpdates.push(updater) + } + } + + // Column starts at 100px, maxSize is 150px + const constrainedColumn = { + ...table.getAllColumns()[0], + id: 'firstName', + columnDef: { enableResizing: true, maxSize: 150, size: 100 }, + table, + } + const header = createTestResizeHeader(table, { + getSize: () => 100, + getLeafHeaders: () => [ + { + column: constrainedColumn, + getSize: () => 100, + subHeaders: [], + }, + ], + }) + + const handler = header_getResizeHandler(header as any, document) + handler({ type: 'mousedown', clientX: 100 }) + + // Drag far right — would put column at ~500px without the clamp + const moveEvent = new MouseEvent('mousemove', { clientX: 600 }) + document.dispatchEvent(moveEvent) + + const lastUpdate = sizingUpdates[sizingUpdates.length - 1] + expect(lastUpdate).toBeDefined() + const newSize = lastUpdate!['firstName'] + expect(newSize).toBeLessThanOrEqual(150) + + const upEvent = new MouseEvent('mouseup', { clientX: 600 }) + document.dispatchEvent(upEvent) + }) }) describe('passiveEventSupported', () => {