/** * usePretextRowHeights * * Pre-computes row heights using pretext text measurement. * For each row, measures each cell's height at the given column width, * then takes the maximum + padding as the row height. */ import { computed, type Ref } from "vue"; import { measureTextHeight } from "./measureText"; import type { ListTableColumn } from "./types"; const DEFAULT_FONT = "14px Inter, sans-serif"; const DEFAULT_LINE_HEIGHT = 20; const DEFAULT_ROW_PADDING = 5; const CELL_VERTICAL_PADDING = 12; // top + bottom per cell export interface RowHeightEntry { height: number; isCustomRenderer: boolean; cellHeights?: RowHeightEntry[]; } export function usePretextRowHeights( data: Ref, columns: Ref[]>, columnWidths: Ref, formatCellValue: (row: T, col: ListTableColumn) => string, options?: { font?: string; lineHeight?: number; rowPadding?: number; debug?: boolean; } ) { const font = options?.font ?? DEFAULT_FONT; const lineHeight = options?.lineHeight ?? DEFAULT_LINE_HEIGHT; const rowPadding = options?.rowPadding ?? DEFAULT_ROW_PADDING; const rowHeights = computed(() => { if (!data.value.length || !columns.value.length || !columnWidths.value.length) { return []; } return data.value.map((row) => { let maxCellHeight = lineHeight; // minimum 1 line let cellHeights: RowHeightEntry[] | undefined = options?.debug ? [] : undefined; for (let i = 0; i < columns.value.length; i++) { const col = columns.value[i]; // Use flex basis width for height calculation as an approximation // of actual column width (CSS flex layout determines actual width) const colWidth = columnWidths.value[i] ?? 100; // Check if custom renderer exists (we can't measure these with pretext) const hasCustomRenderer = !!(col.cellRenderer || col.slot); if (hasCustomRenderer) { // For custom renderers, we use a placeholder height // Actual height will be measured at runtime via useRuntimeHeightAugment // For now, use a reasonable minimum const placeholderHeight = 44; // default row height maxCellHeight = Math.max(maxCellHeight, placeholderHeight); cellHeights?.push({ height: placeholderHeight, isCustomRenderer: true, }); continue; } // Get raw cell value const cellText = formatCellValue(row, col); if (!cellText) continue; // Calculate available width for text (excluding cell padding) const availableWidth = colWidth - CELL_VERTICAL_PADDING * 2; if (availableWidth <= 0) continue; try { const cellHeight = measureTextHeight(cellText, font, availableWidth, lineHeight); maxCellHeight = Math.max(maxCellHeight, cellHeight); cellHeights?.push({ height: cellHeight, isCustomRenderer: false, }); } catch { // Fallback: assume single line } } const totalHeight = maxCellHeight + rowPadding * 2; return { height: totalHeight, isCustomRenderer: false, cellHeights: cellHeights ?? undefined, }; }); }); // Total height (sum of all row heights) - useful for virtualizer const totalHeight = computed(() => rowHeights.value.reduce((sum, entry) => sum + entry.height, 0)); return { rowHeights, totalHeight, }; }