UNPKG

@justjarethb/table-core

Version:

Fork of @tanstack/table-core with support for event listeners in different windows

356 lines (306 loc) 10.2 kB
import { functionalUpdate, memo, RequiredKeys } from '../utils' import { Updater, TableOptionsResolved, TableState, Table, InitialTableState, Row, Column, RowModel, ColumnDef, TableOptions, RowData, TableMeta, ColumnDefResolved, GroupColumnDef, } from '../types' // import { createColumn } from './column' import { Headers } from './headers' // import { ColumnSizing } from '../features/ColumnSizing' import { Expanding } from '../features/Expanding' import { Filters } from '../features/Filters' import { Grouping } from '../features/Grouping' import { Ordering } from '../features/Ordering' import { Pagination } from '../features/Pagination' import { Pinning } from '../features/Pinning' import { RowSelection } from '../features/RowSelection' import { Sorting } from '../features/Sorting' import { Visibility } from '../features/Visibility' export interface TableFeature { getDefaultOptions?: (table: any) => any getInitialState?: (initialState?: InitialTableState) => any createTable?: (table: any) => any getDefaultColumnDef?: () => any createColumn?: (column: any, table: any) => any createHeader?: (column: any, table: any) => any createCell?: (cell: any, column: any, row: any, table: any) => any createRow?: (row: any, table: any) => any } const features = [ Headers, Visibility, Ordering, Pinning, Filters, Sorting, Grouping, Expanding, Pagination, RowSelection, ColumnSizing, ] as const // export interface CoreTableState {} export interface CoreOptions<TData extends RowData> { data: TData[] state: Partial<TableState> onStateChange: (updater: Updater<TableState>) => void debugAll?: boolean debugTable?: boolean debugHeaders?: boolean debugColumns?: boolean debugRows?: boolean initialState?: InitialTableState autoResetAll?: boolean mergeOptions?: ( defaultOptions: TableOptions<TData>, options: Partial<TableOptions<TData>> ) => TableOptions<TData> meta?: TableMeta<TData> getCoreRowModel: (table: Table<any>) => () => RowModel<any> getSubRows?: (originalRow: TData, index: number) => undefined | TData[] getRowId?: (originalRow: TData, index: number, parent?: Row<TData>) => string columns: ColumnDef<TData, any>[] defaultColumn?: Partial<ColumnDef<TData, unknown>> renderFallbackValue: any } export interface CoreInstance<TData extends RowData> { initialState: TableState reset: () => void options: RequiredKeys<TableOptionsResolved<TData>, 'state'> setOptions: (newOptions: Updater<TableOptionsResolved<TData>>) => void getState: () => TableState setState: (updater: Updater<TableState>) => void _features: readonly TableFeature[] _queue: (cb: () => void) => void _getRowId: (_: TData, index: number, parent?: Row<TData>) => string getCoreRowModel: () => RowModel<TData> _getCoreRowModel?: () => RowModel<TData> getRowModel: () => RowModel<TData> getRow: (id: string) => Row<TData> _getDefaultColumnDef: () => Partial<ColumnDef<TData, unknown>> _getColumnDefs: () => ColumnDef<TData, unknown>[] _getAllFlatColumnsById: () => Record<string, Column<TData, unknown>> getAllColumns: () => Column<TData, unknown>[] getAllFlatColumns: () => Column<TData, unknown>[] getAllLeafColumns: () => Column<TData, unknown>[] getColumn: (columnId: string) => Column<TData, unknown> | undefined } export function createTable<TData extends RowData>( options: TableOptionsResolved<TData> ): Table<TData> { if (options.debugAll || options.debugTable) { console.info('Creating Table Instance...') } let table = { _features: features } as unknown as Table<TData> const defaultOptions = table._features.reduce((obj, feature) => { return Object.assign(obj, feature.getDefaultOptions?.(table)) }, {}) as TableOptionsResolved<TData> const mergeOptions = (options: TableOptionsResolved<TData>) => { if (table.options.mergeOptions) { return table.options.mergeOptions(defaultOptions, options) } return { ...defaultOptions, ...options, } } const coreInitialState: CoreTableState = {} let initialState = { ...coreInitialState, ...(options.initialState ?? {}), } as TableState table._features.forEach(feature => { initialState = feature.getInitialState?.(initialState) ?? initialState }) const queued: (() => void)[] = [] let queuedTimeout = false const coreInstance: CoreInstance<TData> = { _features: features, options: { ...defaultOptions, ...options, }, initialState, _queue: cb => { queued.push(cb) if (!queuedTimeout) { queuedTimeout = true // Schedule a microtask to run the queued callbacks after // the current call stack (render, etc) has finished. Promise.resolve() .then(() => { while (queued.length) { queued.shift()!() } queuedTimeout = false }) .catch(error => setTimeout(() => { throw error }) ) } }, reset: () => { table.setState(table.initialState) }, setOptions: updater => { const newOptions = functionalUpdate(updater, table.options) table.options = mergeOptions(newOptions) as RequiredKeys< TableOptionsResolved<TData>, 'state' > }, getState: () => { return table.options.state as TableState }, setState: (updater: Updater<TableState>) => { table.options.onStateChange?.(updater) }, _getRowId: (row: TData, index: number, parent?: Row<TData>) => table.options.getRowId?.(row, index, parent) ?? `${parent ? [parent.id, index].join('.') : index}`, getCoreRowModel: () => { if (!table._getCoreRowModel) { table._getCoreRowModel = table.options.getCoreRowModel(table) } return table._getCoreRowModel!() }, // The final calls start at the bottom of the model, // expanded rows, which then work their way up getRowModel: () => { return table.getPaginationRowModel() }, getRow: (id: string) => { const row = table.getRowModel().rowsById[id] if (!row) { if (process.env.NODE_ENV !== 'production') { throw new Error(`getRow expected an ID, but got ${id}`) } throw new Error() } return row }, _getDefaultColumnDef: memo( () => [table.options.defaultColumn], defaultColumn => { defaultColumn = (defaultColumn ?? {}) as Partial< ColumnDef<TData, unknown> > return { header: props => { const resolvedColumnDef = props.header.column .columnDef as ColumnDefResolved<TData> if (resolvedColumnDef.accessorKey) { return resolvedColumnDef.accessorKey } if (resolvedColumnDef.accessorFn) { return resolvedColumnDef.id } return null }, // footer: props => props.header.column.id, cell: props => props.renderValue<any>()?.toString?.() ?? null, ...table._features.reduce((obj, feature) => { return Object.assign(obj, feature.getDefaultColumnDef?.()) }, {}), ...defaultColumn, } as Partial<ColumnDef<TData, unknown>> }, { debug: () => table.options.debugAll ?? table.options.debugColumns, key: process.env.NODE_ENV === 'development' && 'getDefaultColumnDef', } ), _getColumnDefs: () => table.options.columns, getAllColumns: memo( () => [table._getColumnDefs()], columnDefs => { const recurseColumns = ( columnDefs: ColumnDef<TData, unknown>[], parent?: Column<TData, unknown>, depth = 0 ): Column<TData, unknown>[] => { return columnDefs.map(columnDef => { const column = createColumn(table, columnDef, depth, parent) const groupingColumnDef = columnDef as GroupColumnDef< TData, unknown > column.columns = groupingColumnDef.columns ? recurseColumns(groupingColumnDef.columns, column, depth + 1) : [] return column }) } return recurseColumns(columnDefs) }, { key: process.env.NODE_ENV === 'development' && 'getAllColumns', debug: () => table.options.debugAll ?? table.options.debugColumns, } ), getAllFlatColumns: memo( () => [table.getAllColumns()], allColumns => { return allColumns.flatMap(column => { return column.getFlatColumns() }) }, { key: process.env.NODE_ENV === 'development' && 'getAllFlatColumns', debug: () => table.options.debugAll ?? table.options.debugColumns, } ), _getAllFlatColumnsById: memo( () => [table.getAllFlatColumns()], flatColumns => { return flatColumns.reduce((acc, column) => { acc[column.id] = column return acc }, {} as Record<string, Column<TData, unknown>>) }, { key: process.env.NODE_ENV === 'development' && 'getAllFlatColumnsById', debug: () => table.options.debugAll ?? table.options.debugColumns, } ), getAllLeafColumns: memo( () => [table.getAllColumns(), table._getOrderColumnsFn()], (allColumns, orderColumns) => { let leafColumns = allColumns.flatMap(column => column.getLeafColumns()) return orderColumns(leafColumns) }, { key: process.env.NODE_ENV === 'development' && 'getAllLeafColumns', debug: () => table.options.debugAll ?? table.options.debugColumns, } ), getColumn: columnId => { const column = table._getAllFlatColumnsById()[columnId] if (process.env.NODE_ENV !== 'production' && !column) { console.error(`[Table] Column with id '${columnId}' does not exist.`) } return column }, } Object.assign(table, coreInstance) table._features.forEach(feature => { return Object.assign(table, feature.createTable?.(table)) }) return table }