Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 1x 1x 1x 1x 4x 4x 4x 4x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 4x 4x 4x 2x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x | import { Table } from '@tanstack/react-table';
import { useCallback, useEffect, useRef } from 'react';
/**
* Hook to manage automatic column sizing for TanStack Table.
* Co-located with ResultsTable.tsx since it's only used by that component.
*
* This hook measures the content width of table headers and cells to automatically
* calculate optimal column widths. It uses a hidden measurement table to determine
* the required space for each column without affecting the visible table layout.
*
* @param table - The TanStack Table instance to apply sizing to
* @param data - The table data array used to measure cell content
* @returns Object containing measureRef and helper functions for the measurement table
*
* @example
* ```tsx
* const { getMeasurementTableProps } = useAutoColumnSizing(table, data);
*
* // Render hidden measurement table
* <table {...getMeasurementTableProps()}>
* // table content for measurement
* </table>
* ```
* @source
*/
export function useAutoColumnSizing(table: Table<Product>, data: Product[]) {
const measureRef = useRef<HTMLTableElement>(null);
/**
* Measures the hidden measurement table and applies the result as the column
* sizing. Each column is fit to its natural content width — the larger of its
* header text and its widest sampled cell — then clamped to the optional
* `meta.autoSizeMax` (never below the header, so headers can't wrap). Short
* columns shrink to their content; long ones (title, description) settle at
* their cap. Called on data change and by the resizer's double-click handler
* to reset any manual resizes back to this fit.
* @source
*/
const autoSizeColumns = useCallback(() => {
if (!measureRef.current) return;
const headerCells = measureRef.current.querySelectorAll('th');
const bodyRows = measureRef.current.querySelectorAll('tbody tr');
const leafColumns = table.getAllLeafColumns();
const colWidths: Record<string, number> = {};
// Header text width per column, kept separate so it stays a hard minimum:
// a column never shrinks below its header, even under a small auto-size cap.
const headerWidths: Record<string, number> = {};
// Measure headers. `th > span` is a nowrap span sized to the header text
// itself (independent of the column width), so the header sets the column's
// minimum width and can never wrap or clip.
headerCells.forEach((th, idx) => {
const colId = leafColumns[idx]?.id;
if (!colId) return;
const headerContent = th.querySelector('span');
const width = (headerContent?.scrollWidth ?? th.scrollWidth) + 20;
headerWidths[colId] = Math.max(headerWidths[colId] || 0, width);
colWidths[colId] = Math.max(colWidths[colId] || 0, width);
});
// Measure body cells (natural single-line width)
bodyRows.forEach((tr) => {
tr.querySelectorAll('td').forEach((td, idx) => {
const colId = leafColumns[idx]?.id;
if (!colId) return;
colWidths[colId] = Math.max(colWidths[colId] || 0, td.scrollWidth + 20);
});
});
// Clamp each column to its optional auto-size cap, but never below the
// header width. Long-content columns (title, description) settle at their
// cap; everything else stays content-fit.
for (const column of leafColumns) {
const width = colWidths[column.id];
if (width === undefined) continue;
const cap = column.columnDef.meta?.autoSizeMax;
const capped = cap !== undefined ? Math.min(width, cap) : width;
colWidths[column.id] = Math.max(capped, headerWidths[column.id] ?? 0);
}
// Apply the measured widths
if (Object.keys(colWidths).length > 0) {
table.setColumnSizing(colWidths);
}
}, [table]);
useEffect(() => {
if (!data.length) return;
autoSizeColumns();
}, [data, autoSizeColumns]);
return {
/** Ref object for the hidden measurement table element */
measureRef,
/** Re-measures and best-fits every column to its natural content width. */
autoSizeColumns,
/**
* Helper function that returns props for the measurement table.
* The returned object includes a ref and styles to make the table invisible
* and positioned for measurement without affecting layout.
*
* @returns Props object with ref and style properties for the measurement table
* @source
*/
getMeasurementTableProps: () => ({
ref: measureRef,
style: {
visibility: 'hidden' as const,
position: 'absolute' as const,
pointerEvents: 'none' as const,
height: 0,
overflow: 'hidden' as const,
zIndex: -1,
},
}),
};
}
|