Skip to content

Commit

Permalink
add plugin system!
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinVandy committed Nov 21, 2024
1 parent 1afcf94 commit d3e480d
Show file tree
Hide file tree
Showing 21 changed files with 303 additions and 179 deletions.
88 changes: 54 additions & 34 deletions examples/react/custom-features/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import {
columnFilteringFeature,
createFilteredRowModel,
createPaginatedRowModel,
createSortedRowModel,
filterFns,
flexRender,
functionalUpdate,
makeStateUpdater,
rowPaginationFeature,
rowSortingFeature,
sortFns,
tableFeatures,
useTable,
} from '@tanstack/react-table'
import { makeData } from './makeData'
Expand All @@ -19,6 +25,7 @@ import type {
Table,
TableFeature,
TableFeatures,
TableState,
Updater,
} from '@tanstack/react-table'
import type { Person } from './makeData'
Expand All @@ -27,40 +34,42 @@ import type { Person } from './makeData'

// define types for our new feature's custom state
export type DensityState = 'sm' | 'md' | 'lg'
export interface DensityTableState {
export interface TableState_Density {
density: DensityState
}

// define types for our new feature's table options
export interface DensityOptions {
export interface TableOptions_Density {
enableDensity?: boolean
onDensityChange?: OnChangeFn<DensityState>
}

// Define types for our new feature's table APIs
export interface DensityInstance {
export interface Table_Density {
setDensity: (updater: Updater<DensityState>) => void
toggleDensity: (value?: DensityState) => void
}

// Use declaration merging to add our new feature APIs and state types to TanStack Table's existing types.
declare module '@tanstack/react-table' {
// declare our new feature as a plugin
interface Plugins {
densityPlugin: TableFeature
}
// merge our new feature's state with the existing table state
interface TableState extends DensityTableState {}
interface TableState_Plugins extends TableState_Density {}
// merge our new feature's options with the existing table options
interface TableOptions<TFeatures extends TableFeatures, TData extends RowData>
extends DensityOptions {}
interface TableOptions_Plugins extends TableOptions_Density {}
// merge our new feature's instance APIs with the existing table instance APIs
interface Table<TFeatures extends TableFeatures, TData extends RowData>
extends DensityInstance {}
interface Table_Plugins extends Table_Density {}
// if you need to add cell instance APIs...
// interface Cell<TFeatures extends TableFeatures, TData extends RowData, TValue> extends DensityCell
// interface Cell_Plugins extends Cell_Density {}
// if you need to add row instance APIs...
// interface Row<TFeatures extends TableFeatures, TData extends RowData> extends DensityRow
// interface Row_Plugins extends Row_Density {}
// if you need to add column instance APIs...
// interface Column<TFeatures extends TableFeatures, TData extends RowData, TValue> extends DensityColumn
// interface Column_Plugins extends Column_Density {}
// if you need to add header instance APIs...
// interface Header<TFeatures extends TableFeatures, TData extends RowData, TValue> extends DensityHeader
// interface Header_Plugins extends Header_Density {}

// Note: declaration merging on `ColumnDef` is not possible because it is a type, not an interface.
// But you can still use declaration merging on `ColumnDef.meta`
Expand All @@ -69,12 +78,14 @@ declare module '@tanstack/react-table' {
// end of TS setup!

// Here is all of the actual javascript code for our new feature
export const DensityFeature: TableFeature = {
export const densityPlugin: TableFeature = {
// define the new feature's initial state
getInitialState: (state): DensityTableState => {
getInitialState: <TFeatures extends TableFeatures>(
initialState: Partial<TableState<TFeatures>>,
): Partial<TableState<TFeatures>> => {
return {
density: 'md',
...state,
...initialState, // must come last
}
},

Expand All @@ -83,20 +94,20 @@ export const DensityFeature: TableFeature = {
TFeatures extends TableFeatures,
TData extends RowData,
>(
table: Partial<Table<TFeatures, TData>>,
): DensityOptions => {
table: Table<TFeatures, TData>,
): TableOptions_Density => {
return {
enableDensity: true,
onDensityChange: makeStateUpdater('density', table),
} as DensityOptions
} as TableOptions_Density
},
// if you need to add a default column definition...
// getDefaultColumnDef: <TFeatures extends TableFeatures, TData extends RowData>(): Partial<ColumnDef<TFeatures, TData>> => {
// return { meta: {} } //use meta instead of directly adding to the columnDef to avoid typescript stuff that's hard to workaround
// },

// define the new feature's table instance methods
constructTable: <TFeatures extends TableFeatures, TData extends RowData>(
constructTableAPIs: <TFeatures extends TableFeatures, TData extends RowData>(
table: Table<TFeatures, TData>,
): void => {
table.setDensity = (updater) => {
Expand All @@ -114,20 +125,27 @@ export const DensityFeature: TableFeature = {
}
},

// if you need to add row instance APIs...
// constructRow: <TFeatures extends TableFeatures, TData extends RowData>(row, table): void => {},
// if you need to add cell instance APIs...
// constructCell: <TFeatures extends TableFeatures, TData extends RowData>(cell, column, row, table): void => {},
// if you need to add column instance APIs...
// constructColumn: <TFeatures extends TableFeatures, TData extends RowData>(column, table): void => {},
// if you need to add header instance APIs...
// constructHeader: <TFeatures extends TableFeatures, TData extends RowData>(header, table): void => {},
// // if you need to add row instance APIs...
// constructRowAPIs: <TFeatures extends TableFeatures, TData extends RowData>(row: Row<TFeatures, TData>, table: Table<TFeatures, TData>): void => {},
// // if you need to add cell instance APIs...
// constructCellAPIs: <TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData = CellData>(cell: Cell<TFeatures, TData, TValue>): void => {},
// // if you need to add column instance APIs...
// constructColumnAPIs: <TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData = CellData>(column: Column<TFeatures, TData, TValue>): void => {},
// // if you need to add header instance APIs...
// constructHeaderAPIs: <TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData = CellData>(header: Header<TFeatures, TData, TValue>): void => {},
}
// end of custom feature code

// app code
const _features = tableFeatures({
columnFilteringFeature,
rowSortingFeature,
rowPaginationFeature,
densityPlugin, // pass in our plugin just like any other stock feature
})

function App() {
const columns = React.useMemo<Array<ColumnDef<any, Person>>>(
const columns = React.useMemo<Array<ColumnDef<typeof _features, Person>>>(
() => [
{
accessorKey: 'firstName',
Expand Down Expand Up @@ -169,12 +187,16 @@ function App() {
const [density, setDensity] = React.useState<DensityState>('md')

const table = useTable({
_features: { DensityFeature }, // pass our custom feature to the table to be instantiated upon creation
_features,
_rowModels: {
filteredRowModel: createFilteredRowModel(),
paginatedRowModel: createPaginatedRowModel(),
sortedRowModel: createSortedRowModel(),
},
_processingFns: {
filterFns,
sortFns,
},
columns,
data,
debugTable: true,
Expand Down Expand Up @@ -245,7 +267,7 @@ function App() {
{table.getRowModel().rows.map((row) => {
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
{row.getAllCells().map((cell) => {
return (
<td
key={cell.id}
Expand Down Expand Up @@ -347,17 +369,15 @@ function Filter({
column,
table,
}: {
column: Column<any, any>
table: Table<any, any>
column: Column<typeof _features, Person>
table: Table<typeof _features, Person>
}) {
const firstValue = table
.getPreFilteredRowModel()
.flatRows[0]?.getValue(column.id)

const columnFilterValue = column.getFilterValue()

console.log('columnFilterValue', { columnFilterValue, table, column })

return typeof firstValue === 'number' ? (
<div className="flex space-x-2">
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { column_getFacetedRowModel } from './columnFacetingFeature.utils'
import type { RowModel } from '../../core/row-models/rowModelsFeature.types'
import type { Table_Internal } from '../../types/Table'
import type { TableFeatures } from '../../types/TableFeatures'
import type { RowData } from '../../types/type-utils'

export function createFacetedMinMaxValues<TFeatures extends TableFeatures>(): (
table: Table_Internal<TFeatures, any>,
export function createFacetedMinMaxValues<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(): (
table: Table_Internal<TFeatures, TData>,
columnId: string,
) => () => undefined | [number, number] {
return (table, columnId) =>
Expand All @@ -20,9 +24,12 @@ export function createFacetedMinMaxValues<TFeatures extends TableFeatures>(): (
})
}

function _createFacetedMinMaxValues<TFeatures extends TableFeatures>(
function _createFacetedMinMaxValues<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(
columnId: string,
facetedRowModel?: RowModel<TFeatures, any>,
facetedRowModel?: RowModel<TFeatures, TData>,
): undefined | [number, number] {
if (!facetedRowModel) return undefined

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import type {
import type { TableFeatures } from '../../types/TableFeatures'
import type { RowModel } from '../../core/row-models/rowModelsFeature.types'
import type { Row } from '../../types/Row'
import type { RowData } from '../../types/type-utils'

export function createFacetedRowModel<TFeatures extends TableFeatures>(): (
table: Table_Internal<TFeatures, any>,
export function createFacetedRowModel<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(): (
table: Table_Internal<TFeatures, TData>,
columnId: string,
) => () => RowModel<TFeatures, any> {
) => () => RowModel<TFeatures, TData> {
return (table, columnId) =>
tableMemo({
debug: isDev && (table.options.debugAll ?? table.options.debugTable),
Expand All @@ -34,10 +38,13 @@ export function createFacetedRowModel<TFeatures extends TableFeatures>(): (
})
}

function _createFacetedRowModel<TFeatures extends TableFeatures>(
table: Table_Internal<TFeatures, any>,
function _createFacetedRowModel<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(
table: Table_Internal<TFeatures, TData>,
columnId: string,
preRowModel: RowModel<TFeatures, any>,
preRowModel: RowModel<TFeatures, TData>,
columnFilters?: ColumnFiltersState,
globalFilter?: string,
) {
Expand All @@ -51,7 +58,7 @@ function _createFacetedRowModel<TFeatures extends TableFeatures>(
].filter(Boolean) as Array<string>

const filterRowsImpl = (
row: Row<TFeatures, any> & Partial<Row_ColumnFiltering<TFeatures, any>>,
row: Row<TFeatures, TData> & Partial<Row_ColumnFiltering<TFeatures, TData>>,
) => {
// Horizontally filter rows through each column
for (const colId of filterableIds) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { column_getFacetedRowModel } from './columnFacetingFeature.utils'
import type { Table_Internal } from '../../types/Table'
import type { RowModel } from '../../core/row-models/rowModelsFeature.types'
import type { TableFeatures } from '../../types/TableFeatures'
import type { RowData } from '../../types/type-utils'

export function createFacetedUniqueValues<TFeatures extends TableFeatures>(): (
table: Table_Internal<TFeatures, any>,
export function createFacetedUniqueValues<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(): (
table: Table_Internal<TFeatures, TData>,
columnId: string,
) => () => Map<any, number> {
return (table, columnId) =>
Expand All @@ -20,9 +24,12 @@ export function createFacetedUniqueValues<TFeatures extends TableFeatures>(): (
})
}

function _createFacetedUniqueValues<TFeatures extends TableFeatures>(
function _createFacetedUniqueValues<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(
columnId: string,
facetedRowModel: RowModel<TFeatures, any> | undefined,
facetedRowModel: RowModel<TFeatures, TData> | undefined,
): Map<any, number> {
if (!facetedRowModel) return new Map()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { table_autoResetPageIndex } from '../row-pagination/rowPaginationFeature.utils'
import { filterRows } from './filterRowsUtils'
import { column_getFilterFn } from './columnFilteringFeature.utils'
import type { RowData } from '../../types/type-utils'
import type { TableFeatures } from '../../types/TableFeatures'
import type { RowModel } from '../../core/row-models/rowModelsFeature.types'
import type { Table_Internal } from '../../types/Table'
Expand All @@ -16,9 +17,12 @@ import type {
Row_ColumnFiltering,
} from './columnFilteringFeature.types'

export function createFilteredRowModel<TFeatures extends TableFeatures>(): (
table: Table_Internal<TFeatures, any>,
) => () => RowModel<TFeatures, any> {
export function createFilteredRowModel<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(): (
table: Table_Internal<TFeatures, TData>,
) => () => RowModel<TFeatures, TData> {
return (table) =>
tableMemo({
debug: isDev && (table.options.debugAll ?? table.options.debugTable),
Expand All @@ -33,24 +37,27 @@ export function createFilteredRowModel<TFeatures extends TableFeatures>(): (
})
}

function _createFilteredRowModel<TFeatures extends TableFeatures>(
table: Table_Internal<TFeatures, any>,
): RowModel<TFeatures, any> {
function _createFilteredRowModel<
TFeatures extends TableFeatures,
TData extends RowData = any,
>(table: Table_Internal<TFeatures, TData>): RowModel<TFeatures, TData> {
const rowModel = table.getPreFilteredRowModel()
const { columnFilters, globalFilter } = table.options.state ?? {}

if (!rowModel.rows.length || (!columnFilters?.length && !globalFilter)) {
for (const row of rowModel.flatRows as Array<
Row<TFeatures, any> & Partial<Row_ColumnFiltering<TFeatures, any>>
Row<TFeatures, TData> & Partial<Row_ColumnFiltering<TFeatures, TData>>
>) {
row.columnFilters = {}
row.columnFiltersMeta = {}
}
return rowModel
}

const resolvedColumnFilters: Array<ResolvedColumnFilter<TFeatures, any>> = []
const resolvedGlobalFilters: Array<ResolvedColumnFilter<TFeatures, any>> = []
const resolvedColumnFilters: Array<ResolvedColumnFilter<TFeatures, TData>> =
[]
const resolvedGlobalFilters: Array<ResolvedColumnFilter<TFeatures, TData>> =
[]

columnFilters?.forEach((columnFilter) => {
const column = table_getColumn(table, columnFilter.id)
Expand Down Expand Up @@ -92,7 +99,7 @@ function _createFilteredRowModel<TFeatures extends TableFeatures>(

// Flag the pre-filtered row model with each filter state
for (const row of rowModel.flatRows as Array<
Row<TFeatures, any> & Partial<Row_ColumnFiltering<TFeatures, any>>
Row<TFeatures, TData> & Partial<Row_ColumnFiltering<TFeatures, TData>>
>) {
row.columnFilters = {}

Expand Down Expand Up @@ -142,7 +149,7 @@ function _createFilteredRowModel<TFeatures extends TableFeatures>(
}

const filterRowsImpl = (
row: Row<TFeatures, any> & Row_ColumnFiltering<TFeatures, any>,
row: Row<TFeatures, TData> & Row_ColumnFiltering<TFeatures, TData>,
) => {
// Horizontally filter rows through each column
for (const columnId of filterableIds) {
Expand Down
Loading

0 comments on commit d3e480d

Please sign in to comment.