import { RowModel } from '..' import { BuiltInAggregationFn, aggregationFns } from '../aggregationFns' import { TableFeature } from '../core/table' import { Cell, Column, OnChangeFn, Table, Row, Updater, ColumnDefTemplate, RowData, AggregationFns, } from '../types' import { isFunction, makeStateUpdater } from '../utils' export type GroupingState = string[] export interface GroupingTableState { grouping: GroupingState } export type AggregationFn = ( columnId: string, leafRows: Row[], childRows: Row[] ) => any export type CustomAggregationFns = Record> export type AggregationFnOption = | 'auto' | keyof AggregationFns | BuiltInAggregationFn | AggregationFn export interface GroupingColumnDef { aggregationFn?: AggregationFnOption aggregatedCell?: ColumnDefTemplate< ReturnType['getContext']> > enableGrouping?: boolean } export interface GroupingColumn { getCanGroup: () => boolean getIsGrouped: () => boolean getGroupedIndex: () => number toggleGrouping: () => void getToggleGroupingHandler: () => () => void getAutoAggregationFn: () => AggregationFn | undefined getAggregationFn: () => AggregationFn | undefined } export interface GroupingRow { groupingColumnId?: string groupingValue?: unknown getIsGrouped: () => boolean _groupingValuesCache: Record } export interface GroupingCell { getIsGrouped: () => boolean getIsPlaceholder: () => boolean getIsAggregated: () => boolean } export interface ColumnDefaultOptions { // Column onGroupingChange: OnChangeFn enableGrouping: boolean } interface GroupingOptionsBase { manualGrouping?: boolean onGroupingChange?: OnChangeFn enableGrouping?: boolean getGroupedRowModel?: (table: Table) => () => RowModel groupedColumnMode?: false | 'reorder' | 'remove' } type ResolvedAggregationFns = keyof AggregationFns extends never ? { aggregationFns?: Record> } : { aggregationFns: Record> } export interface GroupingOptions extends GroupingOptionsBase, ResolvedAggregationFns {} export type GroupingColumnMode = false | 'reorder' | 'remove' export interface GroupingInstance { setGrouping: (updater: Updater) => void resetGrouping: (defaultState?: boolean) => void getPreGroupedRowModel: () => RowModel getGroupedRowModel: () => RowModel _getGroupedRowModel?: () => RowModel } // export const Grouping: TableFeature = { getDefaultColumnDef: (): GroupingColumnDef< TData, unknown > => { return { aggregatedCell: props => (props.getValue() as any)?.toString?.() ?? null, aggregationFn: 'auto', } }, getInitialState: (state): GroupingTableState => { return { grouping: [], ...state, } }, getDefaultOptions: ( table: Table ): GroupingOptions => { return { onGroupingChange: makeStateUpdater('grouping', table), groupedColumnMode: 'reorder', } }, createColumn: ( column: Column, table: Table ): GroupingColumn => { return { toggleGrouping: () => { table.setGrouping(old => { // Find any existing grouping for this column if (old?.includes(column.id)) { return old.filter(d => d !== column.id) } return [...(old ?? []), column.id] }) }, getCanGroup: () => { return ( column.columnDef.enableGrouping ?? true ?? table.options.enableGrouping ?? true ?? !!column.accessorFn ) }, getIsGrouped: () => { return table.getState().grouping?.includes(column.id) }, getGroupedIndex: () => table.getState().grouping?.indexOf(column.id), getToggleGroupingHandler: () => { const canGroup = column.getCanGroup() return () => { if (!canGroup) return column.toggleGrouping() } }, getAutoAggregationFn: () => { const firstRow = table.getCoreRowModel().flatRows[0] const value = firstRow?.getValue(column.id) if (typeof value === 'number') { return aggregationFns.sum } if (Object.prototype.toString.call(value) === '[object Date]') { return aggregationFns.extent } }, getAggregationFn: () => { if (!column) { throw new Error() } return isFunction(column.columnDef.aggregationFn) ? column.columnDef.aggregationFn : column.columnDef.aggregationFn === 'auto' ? column.getAutoAggregationFn() : table.options.aggregationFns?.[ column.columnDef.aggregationFn as string ] ?? aggregationFns[ column.columnDef.aggregationFn as BuiltInAggregationFn ] }, } }, createTable: ( table: Table ): GroupingInstance => { return { setGrouping: updater => table.options.onGroupingChange?.(updater), resetGrouping: defaultState => { table.setGrouping( defaultState ? [] : table.initialState?.grouping ?? [] ) }, getPreGroupedRowModel: () => table.getFilteredRowModel(), getGroupedRowModel: () => { if (!table._getGroupedRowModel && table.options.getGroupedRowModel) { table._getGroupedRowModel = table.options.getGroupedRowModel(table) } if (table.options.manualGrouping || !table._getGroupedRowModel) { return table.getPreGroupedRowModel() } return table._getGroupedRowModel() }, } }, createRow: (row: Row): GroupingRow => { return { getIsGrouped: () => !!row.groupingColumnId, _groupingValuesCache: {}, } }, createCell: ( cell: Cell, column: Column, row: Row, table: Table ): GroupingCell => { const getRenderValue = () => cell.getValue() ?? table.options.renderFallbackValue return { getIsGrouped: () => column.getIsGrouped() && column.id === row.groupingColumnId, getIsPlaceholder: () => !cell.getIsGrouped() && column.getIsGrouped(), getIsAggregated: () => !cell.getIsGrouped() && !cell.getIsPlaceholder() && !!row.subRows?.length, } }, } export function orderColumns( leafColumns: Column[], grouping: string[], groupedColumnMode?: GroupingColumnMode ) { if (!grouping?.length || !groupedColumnMode) { return leafColumns } const nonGroupingColumns = leafColumns.filter( col => !grouping.includes(col.id) ) if (groupedColumnMode === 'remove') { return nonGroupingColumns } const groupingColumns = grouping .map(g => leafColumns.find(col => col.id === g)!) .filter(Boolean) return [...groupingColumns, ...nonGroupingColumns] }