All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.core.table.ts Maven / Gradle / Ivy

The newest version!
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 {
  data: TData[]
  state: Partial
  onStateChange: (updater: Updater) => void
  debugAll?: boolean
  debugTable?: boolean
  debugHeaders?: boolean
  debugColumns?: boolean
  debugRows?: boolean
  initialState?: InitialTableState
  autoResetAll?: boolean
  mergeOptions?: (
    defaultOptions: TableOptions,
    options: Partial>
  ) => TableOptions
  meta?: TableMeta
  getCoreRowModel: (table: Table) => () => RowModel
  getSubRows?: (originalRow: TData, index: number) => undefined | TData[]
  getRowId?: (originalRow: TData, index: number, parent?: Row) => string
  columns: ColumnDef[]
  defaultColumn?: Partial>
  renderFallbackValue: any
}

export interface CoreInstance {
  initialState: TableState
  reset: () => void
  options: RequiredKeys, 'state'>
  setOptions: (newOptions: Updater>) => void
  getState: () => TableState
  setState: (updater: Updater) => void
  _features: readonly TableFeature[]
  _queue: (cb: () => void) => void
  _getRowId: (_: TData, index: number, parent?: Row) => string
  getCoreRowModel: () => RowModel
  _getCoreRowModel?: () => RowModel
  getRowModel: () => RowModel
  getRow: (id: string) => Row
  _getDefaultColumnDef: () => Partial>
  _getColumnDefs: () => ColumnDef[]
  _getAllFlatColumnsById: () => Record>
  getAllColumns: () => Column[]
  getAllFlatColumns: () => Column[]
  getAllLeafColumns: () => Column[]
  getColumn: (columnId: string) => Column | undefined
}

export function createTable(
  options: TableOptionsResolved
): Table {
  if (options.debugAll || options.debugTable) {
    console.info('Creating Table Instance...')
  }

  let table = { _features: features } as unknown as Table

  const defaultOptions = table._features.reduce((obj, feature) => {
    return Object.assign(obj, feature.getDefaultOptions?.(table))
  }, {}) as TableOptionsResolved

  const mergeOptions = (options: TableOptionsResolved) => {
    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 = {
    _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,
        'state'
      >
    },

    getState: () => {
      return table.options.state as TableState
    },

    setState: (updater: Updater) => {
      table.options.onStateChange?.(updater)
    },

    _getRowId: (row: TData, index: number, parent?: Row) =>
      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
        >

        return {
          header: props => {
            const resolvedColumnDef = props.header.column
              .columnDef as ColumnDefResolved

            if (resolvedColumnDef.accessorKey) {
              return resolvedColumnDef.accessorKey
            }

            if (resolvedColumnDef.accessorFn) {
              return resolvedColumnDef.id
            }

            return null
          },
          // footer: props => props.header.column.id,
          cell: props => props.renderValue()?.toString?.() ?? null,
          ...table._features.reduce((obj, feature) => {
            return Object.assign(obj, feature.getDefaultColumnDef?.())
          }, {}),
          ...defaultColumn,
        } as Partial>
      },
      {
        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[],
          parent?: Column,
          depth = 0
        ): Column[] => {
          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>)
      },
      {
        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
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy