import * as React from "react"; import { cn } from "../utils.js"; export type DataGridSelectionMode = "none" | "single" | "multiple"; export type DataGridDirection = "up" | "down" | "left" | "right"; export interface DataGridActiveCell { rowId: string; columnId: string; editing: boolean; } export interface DataGridColumn { id: string; label?: React.ReactNode; width?: number; minWidth?: number; maxWidth?: number; resizable?: boolean; editable?: boolean | ((row: Row) => boolean); getValue?: (row: Row) => Value; renderCell?: (context: DataGridCellContext) => React.ReactNode; renderEditor?: ( context: DataGridEditorContext, ) => React.ReactNode; } export interface DataGridCellContext { row: Row; rowIndex: number; column: DataGridColumn; value: Value | undefined; active: boolean; editing: boolean; editable: boolean; startEditing: () => void; commit: (value: Value) => void; cancel: () => void; move: (direction: DataGridDirection) => void; } export interface DataGridEditorContext< Row, Value = unknown, > extends DataGridCellContext { onCommit: (value: Value) => void; onCancel: () => void; onMove: (direction: DataGridDirection) => void; } export interface DataGridCommitContext { row: Row; rowIndex: number; column: DataGridColumn; previousValue: unknown; value: unknown; } export interface DataGridSlotContext { rows: readonly Row[]; columns: readonly DataGridColumn[]; columnWidths: Readonly>; gridTemplateColumns: string; selection: DataGridSelectionMode; selectedRowIds: ReadonlySet; getRowId: (row: Row, rowIndex: number) => string; setActiveCell: (cell: DataGridActiveCell | null) => void; toggleRowSelection: (rowId: string) => void; resizeColumn: (columnId: string, width: number) => void; } export interface DataGridRowContext extends DataGridSlotContext { row: Row; rowIndex: number; rowId: string; selected: boolean; } type DataGridScrollContainerProps = Omit< React.HTMLAttributes, "children" > & Record<`data-${string}`, string | undefined>; export interface DataGridProps extends Omit< React.HTMLAttributes, "children" > { rows: readonly Row[]; columns: readonly DataGridColumn[]; getRowId: (row: Row, rowIndex: number) => string; columnWidths?: Readonly>; onColumnWidthsChange?: (columnWidths: Record) => void; selection?: DataGridSelectionMode; selectedRowIds?: ReadonlySet; onSelectedRowIdsChange?: (rowIds: ReadonlySet) => void; activeCell?: DataGridActiveCell | null; onActiveCellChange?: (cell: DataGridActiveCell | null) => void; onCellCommit?: (context: DataGridCommitContext) => void; renderHeader?: (context: DataGridSlotContext) => React.ReactNode; renderBody?: (context: DataGridSlotContext) => React.ReactNode; renderRow?: (context: DataGridRowContext) => React.ReactNode; renderFooter?: (context: DataGridSlotContext) => React.ReactNode; emptyState?: React.ReactNode; loading?: boolean; loadingRowCount?: number; contentClassName?: string; rowClassName?: string | ((row: Row, rowIndex: number) => string); scrollContainerProps?: DataGridScrollContainerProps; } const DEFAULT_COLUMN_WIDTH = 160; const DEFAULT_MIN_COLUMN_WIDTH = 96; const DEFAULT_MAX_COLUMN_WIDTH = 640; const SELECTION_COLUMN_WIDTH = 40; function columnWidth( column: DataGridColumn, requestedWidth: number | undefined, ) { const fallback = column.width ?? DEFAULT_COLUMN_WIDTH; const width = requestedWidth !== undefined && Number.isFinite(requestedWidth) ? requestedWidth : fallback; const minWidth = column.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH; const maxWidth = Math.max( minWidth, column.maxWidth ?? DEFAULT_MAX_COLUMN_WIDTH, ); return Math.min(maxWidth, Math.max(minWidth, Math.round(width))); } export function clampDataGridColumnWidth( width: number, column: DataGridColumn, ) { return columnWidth(column, width); } export function dataGridColumnTemplate( columns: readonly DataGridColumn[], columnWidths: Readonly> = {}, selection: DataGridSelectionMode = "none", ) { const selectionTemplate = selection === "none" ? [] : [`${SELECTION_COLUMN_WIDTH}px`]; return [ ...selectionTemplate, ...columns.map( (column) => `${columnWidth(column, columnWidths[column.id])}px`, ), ].join(" "); } function isColumnEditable(column: DataGridColumn, row: Row) { if (!column.renderEditor) return false; return typeof column.editable === "function" ? column.editable(row) : column.editable !== false; } function defaultCellText(value: unknown) { return value == null ? "" : String(value); } function loadingRows({ count, columns, gridTemplateColumns, selection, }: { count: number; columns: readonly DataGridColumn[]; gridTemplateColumns: string; selection: DataGridSelectionMode; }) { return Array.from({ length: count }, (_, rowIndex) => (