import type * as React from 'react'; import type { CheckboxProps } from '../checkbox/types.js'; import type { PrimitivePropsWithRef } from '../utils/primitive.js'; /** Column sort direction: ascending or descending. */ export type SortDirection = 'asc' | 'desc'; /** Currently sorted column and its sort direction. */ export interface TableSortState { columnKey: string; direction: SortDirection; } /** Array of sorted columns, most significant first, always present even with `multiSort` off. */ export type TableSort = TableSortState[]; /** Active column filters: each key is a `columnKey`, each value the text its rows must contain. */ export type TableFilterState = Record; /** Whether `TableSelectAllCheckbox` covers the page or entire dataset. */ export type TableSelectAllScope = 'page' | 'all'; /** How many rows can be selected: one, many, or none. */ export type TableSelectionMode = 'single' | 'multiple' | 'none'; /** Per-column pixel widths keyed by `columnKey`. */ export type TableColumnWidths = Record; /** Pinned columns keyed by `columnKey`; an unpinned column is absent from the map. */ export type TableColumnPins = Record; /** Shared sort/selection state distributed from `Table` to its parts. */ export interface TableContextValue { /** Unique per-table id for stable detail row `id`/`aria-controls` pairs. */ tableId: string; /** Every sorted column, most significant first. */ sortList: TableSortState[]; /** Whether a second sorted column appends or replaces the first. */ multiSort: boolean; selected: Set; selectionMode: TableSelectionMode; /** Row ids whose detail row is expanded. */ expandedRows: Set; toggleRowExpanded: (rowId: string) => void; isRowExpanded: (rowId: string) => boolean; /** Current per-column pixel widths. */ columnWidths: TableColumnWidths; setColumnWidth: (columnKey: string, width: number) => void; /** Commit column widths, fires `onColumnWidthsCommit` once. */ commitColumnWidths: () => void; /** Cycle column sort asc → desc → unsorted; switch columns always start at asc. */ toggleSort: (columnKey: string, columnLabel?: string) => void; /** Active column filters, keyed by `columnKey`; a column with no entry is unfiltered. */ filterMap: TableFilterState; /** Set one column's filter text; an empty string clears that column. */ setColumnFilter: (columnKey: string, value: string) => void; /** Toggle a row's selection; `extend` selects range in `'multiple'` mode. */ toggleRow: (id: string, extend?: boolean) => void; isRowSelected: (id: string) => boolean; /** Header cells currently mounted, falling back to the root's `columnCount` before any header renders. */ headerCellCount: number; /** @internal Counts a mounted `TableHeaderCell` into `headerCellCount`; the returned callback removes it. */ registerHeaderCell: () => () => void; /** @internal Display order of column keys; keys left out follow in source order. */ columnOrder: string[]; /** @internal Moves one column to `toIndex` in header DOM order and announces the new position. */ moveColumn: (columnKey: string, toIndex: number, columnLabel?: string) => void; /** @internal Which columns are pinned, and to which edge of the table. */ columnPins: TableColumnPins; /** @internal Pins a column to one edge, or unpins it when given `null`. */ setColumnPin: (columnKey: string, side: 'start' | 'end' | null) => void; /** @internal Measured sticky offset in pixels for each pinned column. */ pinOffsets: Record; /** @internal Group keys whose rows are currently hidden by their group row. */ collapsedGroups: Set; /** @internal Collapses an expanded group, or expands a collapsed one. */ toggleGroup: (groupKey: string) => void; /** @internal Whether this group's rows are hidden right now. */ isGroupCollapsed: (groupKey: string) => boolean; /** @internal Row ids of the tree parents whose children are rendered. */ expandedNodes: Set; /** @internal Expands a collapsed tree parent, or collapses an expanded one. */ toggleNode: (rowId: string) => void; /** @internal Whether this tree parent currently renders its child rows. */ isNodeExpanded: (rowId: string) => boolean; /** Deselect all or select all given row ids. */ toggleAll: (allIds: string[]) => void; isAllSelected: (allIds: string[]) => boolean; isSomeSelected: (allIds: string[]) => boolean; } /** @internal Column-scoped defaults `TableHeaderCell` publishes to its nested parts. */ export interface TableHeaderCellContextValue { /** The column's single identifier for sort and resize. */ columnKey?: string | undefined; /** Whether this column can be sorted. */ sortable?: boolean | undefined; /** Whether this column can be filtered. */ filterable?: boolean | undefined; filterLabel?: string | undefined; minWidth?: number | undefined; maxWidth?: number | undefined; resizeLabel?: string | undefined; resizeStep?: number | undefined; /** Whether this column can be moved by its reorder trigger. */ reorderable?: boolean | undefined; /** Whether this column can be pinned by its pin trigger. */ pinnable?: boolean | undefined; reorderLabel?: string | undefined; pinLabel?: string | undefined; } /** Props for `TableRoot`, a native table with optional sorting and selection state. */ export interface TableRootProps extends Omit, 'onChange'> { /** * Hand-composed table sections such as header, body and footer. * @default undefined */ children?: React.ReactNode | undefined; /** * Sorted columns, most significant first, for controlled usage. * @default undefined */ sort?: TableSort | undefined; /** * Initially sorted columns for uncontrolled usage. * @default [] */ defaultSort?: TableSort | undefined; /** * Total rows in the full dataset; set when DOM rows don't represent all. * @default undefined */ rowCount?: number | undefined; /** * Total number of columns in the full dataset. * @default undefined */ columnCount?: number | undefined; /** * Fires with the new sort state on sortable header activation. * @default undefined */ onSortChange?: ((sort: TableSort) => void) | undefined; /** * Active column filters, for controlled usage. * @default undefined */ filter?: TableFilterState | undefined; /** * Initial column filters for uncontrolled usage. * @default {} */ defaultFilter?: TableFilterState | undefined; /** * Fires with the new filter map whenever a column's filter text changes. * @default undefined */ onFilterChange?: ((filter: TableFilterState) => void) | undefined; /** * Whether a second sorted column appends or replaces the first. * @default false */ multiSort?: boolean | undefined; /** * Selected row ids for controlled usage. * @default undefined */ selected?: Set | string[] | undefined; /** * Initially selected row ids for uncontrolled usage. * @default undefined */ defaultSelected?: Set | string[] | undefined; /** * Fires with the new selected ids on selection change. * @default undefined */ onSelectedChange?: ((selected: string[]) => void) | undefined; /** * Whether rows can be selected: one, many, or none. * @default 'none' */ selectionMode?: TableSelectionMode | undefined; /** * Format the message announced on sortable header activation. * @default defaultGetSortLabel */ getSortLabel?: ((column: string, direction: SortDirection | null) => string) | undefined; /** * Row ids whose detail row is expanded for controlled usage. * @default undefined */ expandedRowIds?: string[] | undefined; /** * Initially expanded detail rows for uncontrolled usage. * @default [] */ defaultExpandedRowIds?: string[] | undefined; /** * Fires with new expanded row ids on expand trigger activation. * @default undefined */ onExpandedRowIdsChange?: ((rowIds: string[]) => void) | undefined; /** * Per-column pixel widths keyed by `columnKey` for controlled usage. * @default undefined */ columnWidths?: TableColumnWidths | undefined; /** * Initial per-column pixel widths for uncontrolled usage. * @default {} */ defaultColumnWidths?: TableColumnWidths | undefined; /** * Fires during column resize via drag or keyboard, continuously. * @default undefined */ onColumnWidthsChange?: ((widths: TableColumnWidths) => void) | undefined; /** * Fires once per gesture when column resizing settles. * @default undefined */ onColumnWidthsCommit?: ((widths: TableColumnWidths) => void) | undefined; /** * Display order of column keys for controlled usage; keys left out follow in source order. * @default undefined */ columnOrder?: string[] | undefined; /** * Initial display order of column keys for uncontrolled usage. * @default [] */ defaultColumnOrder?: string[] | undefined; /** * Fires with the full column order whenever a reorder trigger moves a column. * @default undefined */ onColumnOrderChange?: ((columnOrder: string[]) => void) | undefined; /** * Pinned columns keyed by `columnKey` for controlled usage, each pinned to one edge. * @default undefined */ columnPins?: TableColumnPins | undefined; /** * Initially pinned columns for uncontrolled usage, keyed by `columnKey`. * @default {} */ defaultColumnPins?: TableColumnPins | undefined; /** * Fires with the full pin map whenever a pin trigger is activated. * @default undefined */ onColumnPinsChange?: ((columnPins: TableColumnPins) => void) | undefined; /** * Format the message announced after a column is moved, given its 1-based position. * @default defaultGetMoveLabel */ getMoveLabel?: ((column: string, position: number, count: number) => string) | undefined; /** * Group keys whose rows are hidden for controlled usage; groups start expanded. * @default undefined */ collapsedGroups?: string[] | undefined; /** * Initially collapsed group keys for uncontrolled usage. * @default [] */ defaultCollapsedGroups?: string[] | undefined; /** * Fires with the collapsed group keys whenever a group row is toggled. * @default undefined */ onCollapsedGroupsChange?: ((groupKeys: string[]) => void) | undefined; /** * Tree parents whose child rows are rendered, for controlled usage. * @default undefined */ expandedNodeIds?: string[] | undefined; /** * Initially expanded tree parents for uncontrolled usage. * @default [] */ defaultExpandedNodeIds?: string[] | undefined; /** * Fires with the expanded tree parents whenever a tree trigger is activated. * @default undefined */ onExpandedNodeIdsChange?: ((rowIds: string[]) => void) | undefined; /** * Spacing rung the skin packs the cells by, mirrored to `data-density`. * @default undefined — the skin's own default spacing. */ density?: 'compact' | (string & {}) | undefined; /** * Pins the header row while the body scrolls, mirrored as a bare `data-sticky-header`. * @default undefined — the header scrolls with the body. */ stickyHeader?: boolean | undefined; } /** One column in a derived table. */ export interface TableColumn { /** This column's identifier for sorting and resizing; a `T` key, or any string for a computed column. */ key: Extract | (string & {}); /** * The derived header cell's content. * @default column's `key` */ header?: React.ReactNode | undefined; /** * Render a cell for this column. * @default falls back to `renderCell`, then `String(row[key])` */ cell?: ((row: T) => React.ReactNode) | undefined; /** * Override the root's `sortable` for this column alone. * @default inherits root's `sortable` */ sortable?: boolean | undefined; /** * Override the root's `resizable` for this column alone. * @default inherits root's `resizable` */ resizable?: boolean | undefined; /** * Override the root's `filterable` for this column alone. * @default inherits root's `filterable` */ filterable?: boolean | undefined; /** * Override the root's `reorderable` for this column alone. * @default inherits root's `reorderable` */ reorderable?: boolean | undefined; /** * Override the root's `pinnable` for this column alone. * @default inherits root's `pinnable` */ pinnable?: boolean | undefined; /** @default 40 */ minWidth?: number | undefined; /** @default 1000 */ maxWidth?: number | undefined; } /** Props for the Table recipe, generic over row datum `T`. No `children`: use TableRoot for manual composition. */ export interface TableProps extends Omit { /** Columns to derive header cells and row cells from. */ columns: TableColumn[]; /** Rows to render — one per entry. */ data: T[]; /** * Default `sortable` for every column that doesn't set its own. * @default false */ sortable?: boolean | undefined; /** * Derive a leading selection checkbox column, defaulting `selectionMode` to `'multiple'`. * @default false */ selectable?: boolean | undefined; /** * Derive a leading expand trigger column and detail row per row. * @default false */ expandable?: boolean | undefined; /** * Render a derived detail row's content for one row. * @default detail row renders empty */ renderExpanded?: ((row: T) => React.ReactNode) | undefined; /** * Default `resizable` for every column that doesn't set its own. * @default false */ resizable?: boolean | undefined; /** * Default `filterable` for every column that doesn't set its own; the derived view then renders only rows matching every active filter. * @default false */ filterable?: boolean | undefined; /** * Default `reorderable` for every column that doesn't set its own, deriving a reorder trigger. * @default false */ reorderable?: boolean | undefined; /** * Default `pinnable` for every column that doesn't set its own, deriving a pin trigger. * @default false */ pinnable?: boolean | undefined; /** * Column key to group rows by, emitting one collapsible group row per distinct value. * @default no grouping */ groupBy?: (Extract | (string & {})) | undefined; /** * Render one derived group row's label from its key and rows. * @default `${groupKey} (${rows.length})` */ renderGroupHeader?: ((groupKey: string, rows: T[]) => React.ReactNode) | undefined; /** * Return a row's child rows to render the data as an expandable tree. * @default no tree rows */ getSubRows?: ((row: T) => T[] | undefined) | undefined; /** * Content for a derived empty row shown when data is empty. * @default no empty row */ empty?: React.ReactNode | undefined; /** * Content for the derived ``, which names the table for assistive technology. * @default no caption */ caption?: React.ReactNode | undefined; /** * Cell renderer for columns without their own `cell`. * @default `String(row[column.key])` */ renderCell?: ((row: T, column: TableColumn) => React.ReactNode) | undefined; /** * Derive each row's identity for selection, expansion and keys. * @default (row, index) => String(row.id ?? index) */ rowId?: ((row: T, index: number) => string) | undefined; /** * Names each row in its derived select checkbox and expand trigger. * @default (row, index) => `row ${rowId(row, index)}` */ getRowLabel?: ((row: T, index: number) => string) | undefined; /** * Size rung the skin scales the selection boxes by, fed to the derived row and select-all checkboxes. * @default undefined — the skin's own default size. */ size?: 'sm' | 'md' | 'lg' | (string & {}) | undefined; } /** Props for the table header section. */ export interface TableHeaderProps extends PrimitivePropsWithRef<'thead'> { } /** Props for the table body section. */ export interface TableBodyProps extends PrimitivePropsWithRef<'tbody'> { } /** Props for the table footer section. */ export interface TableFooterProps extends PrimitivePropsWithRef<'tfoot'> { } /** Props for a selectable, sortable-aware table row. */ export interface TableRowProps extends PrimitivePropsWithRef<'tr'> { /** * This row's identifier within the table's selection state, required for the row to be selectable. * @default undefined */ rowId?: string | undefined; /** * Tree depth of this row, `0` at the top level, published for the skin to indent by. * @default undefined — the row carries no depth. */ level?: number | undefined; } /** Props for a column header cell, optionally sortable and resizable. */ export interface TableHeaderCellProps extends PrimitivePropsWithRef<'th'> { /** * This column's identifier for sorting and resizing. * @default undefined */ columnKey?: string | undefined; /** * Whether this column can be sorted. * @default undefined */ sortable?: boolean | undefined; /** * Renders a filter text input under the label, bound to this column's entry in the filter map. * @default false */ filterable?: boolean | undefined; /** * Accessible name for the derived filter input. * @default `Filter ${columnKey}` */ filterLabel?: string | undefined; /** * Renders a focusable resize handle, dragged or stepped with Arrow Left/Right. * @default false */ resizable?: boolean | undefined; /** * Smallest width in pixels the column can be resized to. * @default 40 */ minWidth?: number | undefined; /** * Largest width in pixels the column can be resized to. * @default 1000 */ maxWidth?: number | undefined; /** * Accessible name for the resize handle. * @default 'Resize column' */ resizeLabel?: string | undefined; /** * Pixels one Arrow keypress moves the column edge by. * @default 8 */ resizeStep?: number | undefined; /** * Renders a reorder trigger after the label, moved with the arrow keys or dragged. * @default false */ reorderable?: boolean | undefined; /** * Renders a pin trigger after the label, sticking this column to one edge. * @default false */ pinnable?: boolean | undefined; /** * Accessible name for the derived reorder trigger. * @default 'Move column' */ reorderLabel?: string | undefined; /** * Accessible name for the derived pin trigger. * @default 'Pin column' */ pinLabel?: string | undefined; } /** Props for the button inside a header cell that cycles column sort. */ export interface TableColumnHeaderSortTriggerProps extends PrimitivePropsWithRef<'button'> { /** * Column this trigger sorts, inherited from the enclosing `TableHeaderCell`'s `columnKey`. * @default enclosing `TableHeaderCell`'s `columnKey` */ columnKey?: string | undefined; } /** Props for the text input inside a header cell that filters its column. */ export interface TableColumnHeaderFilterInputProps extends PrimitivePropsWithRef<'input'> { /** * Column this input filters, inherited from the enclosing `TableHeaderCell`'s `columnKey`. * @default enclosing `TableHeaderCell`'s `columnKey` */ columnKey?: string | undefined; } /** Props for the decorative sort direction arrow. */ export interface TableSortIndicatorProps extends PrimitivePropsWithRef<'span'> { /** * Column whose direction this indicator reflects, inherited from the enclosing `TableHeaderCell`'s `columnKey`. * @default enclosing `TableHeaderCell`'s `columnKey` */ columnKey?: string | undefined; } /** Props for the tri-state header select-all checkbox. */ export interface TableSelectAllCheckboxProps extends Omit { /** * The row ids this checkbox selects — the whole dataset when `scope='all'`, the page when `scope='page'`. */ rowIds: string[]; /** * Whether `rowIds` is the page or entire dataset. */ scope: TableSelectAllScope; /** * Formats the checkbox's accessible name, given the current scope and how many rows it affects. * @default defaultSelectAllLabel */ getSelectAllLabel?: ((scope: TableSelectAllScope, count: number) => string) | undefined; } /** Props for a single row's selection checkbox. */ export interface TableRowSelectCheckboxProps extends Omit { /** Id of the row this checkbox selects. */ rowId: string; } /** Props for the draggable/keyboard-operable column resize handle. */ export interface TableColumnResizeTriggerProps extends PrimitivePropsWithRef<'span'> { /** * Column whose width this handle resizes, inherited from the enclosing `TableHeaderCell`'s `columnKey`. * @default enclosing `TableHeaderCell`'s `columnKey` */ columnKey?: string | undefined; /** * Smallest width in pixels; **Home** snaps to this. * @default enclosing `TableHeaderCell`'s `minWidth` or `40` */ minWidth?: number | undefined; /** * Largest width in pixels; **End** snaps to this. * @default enclosing `TableHeaderCell`'s `maxWidth` or `1000` */ maxWidth?: number | undefined; /** * Pixels one Arrow keypress moves the edge by. * @default enclosing `TableHeaderCell`'s `resizeStep` or `8` */ step?: number | undefined; /** * Accessible name for the resize handle; a focusable separator widget needs its own name. * @default enclosing `TableHeaderCell`'s `resizeLabel` or `'Resize column'` */ label?: string | undefined; } /** Props for the button that moves its column left or right. */ export interface TableColumnReorderTriggerProps extends PrimitivePropsWithRef<'button'> { /** * Column this trigger moves, inherited from the enclosing `TableHeaderCell`'s `columnKey`. * @default enclosing `TableHeaderCell`'s `columnKey` */ columnKey?: string | undefined; /** * Accessible name for the trigger, which draws only a drag glyph by default. * @default enclosing `TableHeaderCell`'s `reorderLabel` or `'Move column'` */ label?: string | undefined; } /** Props for the toggle button that pins its column to one edge. */ export interface TableColumnPinTriggerProps extends PrimitivePropsWithRef<'button'> { /** * Column this trigger pins, inherited from the enclosing `TableHeaderCell`'s `columnKey`. * @default enclosing `TableHeaderCell`'s `columnKey` */ columnKey?: string | undefined; /** * Which edge of the table this trigger pins its column to. * @default 'start' */ side?: 'start' | 'end' | undefined; /** * Accessible name for the trigger, which draws only a pin glyph by default. * @default enclosing `TableHeaderCell`'s `pinLabel` or `'Pin column'` */ label?: string | undefined; } /** Props for the row that heads and collapses one group of rows. */ export interface TableGroupRowProps extends PrimitivePropsWithRef<'tr'> { /** Key of the group this row heads, as it appears in `collapsedGroups`. */ groupKey: string; /** * How many columns the group cell spans. * @default undefined — the table's own header cell count, so a column added later still spans. */ colSpan?: number | undefined; } /** Props for the disclosure button that reveals a tree row's children. */ export interface TableTreeTriggerProps extends PrimitivePropsWithRef<'button'> { /** Id of the parent row whose child rows this trigger reveals. */ rowId: string; } /** Props for a single body cell. */ export interface TableCellProps extends PrimitivePropsWithRef<'td'> { /** * Column this cell belongs to, so it follows that column's pinning. * @default undefined — the cell never pins. */ columnKey?: string | undefined; } /** Props for the table caption. */ export interface TableCaptionProps extends PrimitivePropsWithRef<'caption'> { } /** Props for the disclosure button that opens a row's detail row. */ export interface TableExpandTriggerProps extends PrimitivePropsWithRef<'button'> { /** Id of the row this trigger expands. */ rowId: string; } /** Props for the full-width detail row revealed by an expand trigger. */ export interface TableExpandedRowProps extends PrimitivePropsWithRef<'tr'> { /** Id of the row this detail row belongs to. */ rowId: string; /** * How many columns the detail cell spans. * @default undefined — the table's own header cell count, so a column added later still spans. */ colSpan?: number | undefined; } /** Props for the full-width row shown when there is no data. */ export interface TableEmptyProps extends PrimitivePropsWithRef<'tr'> { /** * How many columns the empty cell spans. * @default undefined — the table's own header cell count, so a column added later still spans. */ colSpan?: number | undefined; }