// Pure column/cell value access + display-formatting helpers. They operate // only on their Column/Row/data arguments (plus the numeric formatter), so // they hold no grid state and live outside the controller. import type { CellFormatConfig, Column, ColumnDef, Row, RowData, TableFeatures, } from "./index"; import { formatNumericWithConfig } from "./cell-formatting"; export function getColumnBaseValue(row: Row, column: Column) { const def = column.columnDef; if (def.fieldFn) return def.fieldFn(row.original); if (def.field) return row.original[def.field]; return row.getCellValueByColumnId(column.id); } export function isGroupRow(row: Row) { // Tree rows are expandable too, but they are real data rows - rendering one as // a full-width banner would swallow its cells. Only grouping's synthetic rows // are banners. if ((row as { __treeRow?: boolean }).__treeRow) return false; return typeof row.getCanExpand === "function" && row.getCanExpand(); } export function toolPanelHeaderLabel(column: Column): string { const h = column.columnDef.header; return typeof h === "string" ? h : column.id; } /** Apply the column's `format` config to a raw numeric summary value * (sum / avg / etc.). Mirrors the currency/number/percent branches in * `formatCellValue` so a totaled salary column shows "$1,234,567" in * the footer instead of "1234567". Per-row `formatter` functions are * intentionally NOT invoked here - they may close over row state. */ export function formatSummaryNumeric(column: Column, value: number): string { const formatConfig = column.columnDef.format as | CellFormatConfig | undefined; if ( formatConfig && (formatConfig.type === "number" || formatConfig.type === "currency" || formatConfig.type === "percent") ) { return formatNumericWithConfig(value, { type: formatConfig.type, locales: formatConfig.locales, currency: formatConfig.type === "currency" ? (formatConfig.currency ?? "USD") : undefined, valueIsPercentPoints: formatConfig.type === "percent" ? formatConfig.valueIsPercentPoints : undefined, options: formatConfig.options, }); } return String(value); } /** * Effective horizontal alignment for a column. Honors the explicit * `align` prop on the ColumnDef; otherwise picks a sensible default * based on `editorType`: * number / date / datetime → 'right' * checkbox → 'center' * everything else → 'left' */ export function getColumnAlign(column: Column): "left" | "center" | "right" { const explicit = column.columnDef.align; if (explicit) return explicit; const editorType = column.columnDef.editorType; if ( editorType === "number" || editorType === "date" || editorType === "datetime" ) { return "right"; } if (editorType === "checkbox") return "center"; return "left"; } /** * Read a cell value from a PINNED row. Pinned rows aren't bound to a * TanStack Row, so we resolve the value directly off the raw * TData via `fieldFn` or `field`. Mirrors `getColumnBaseValue`'s * lookup path minus the Row indirection. */ export function getPinnedCellValue( rowData: TData, column: Column, ): unknown { const def = column.columnDef; if (def.fieldFn) return def.fieldFn(rowData); const field = def.field as string | undefined; if (!field) return undefined; return (rowData as unknown as Record)[field]; } export function getColumnAccessorValue(rowData: TData, column: Column) { const def = column.columnDef; if (def.fieldFn) return def.fieldFn(rowData); if (def.field) return rowData[def.field]; return undefined; } export function columnDefMatchesId( def: ColumnDef, columnId: string, ): boolean { return (def.id ?? def.field ?? null) === columnId; }