import { RowModel } from '..' import { TableFeature } from '../core/table' import { BuiltInFilterFn, filterFns } from '../filterFns' import { Column, OnChangeFn, Table, Row, Updater, RowData, FilterMeta, FilterFns, } from '../types' import { functionalUpdate, isFunction, makeStateUpdater } from '../utils' export interface FiltersTableState { columnFilters: ColumnFiltersState globalFilter: any } export type ColumnFiltersState = ColumnFilter[] export interface ColumnFilter { id: string value: unknown } export interface ResolvedColumnFilter { id: string resolvedValue: unknown filterFn: FilterFn } export interface FilterFn { ( row: Row, columnId: string, filterValue: any, addMeta: (meta: FilterMeta) => void ): boolean resolveFilterValue?: TransformFilterValueFn autoRemove?: ColumnFilterAutoRemoveTestFn } export type TransformFilterValueFn = ( value: any, column?: Column ) => unknown export type ColumnFilterAutoRemoveTestFn = ( value: any, column?: Column ) => boolean export type CustomFilterFns = Record< string, FilterFn > export type FilterFnOption = | 'auto' | BuiltInFilterFn | keyof FilterFns | FilterFn export interface FiltersColumnDef { filterFn?: FilterFnOption enableColumnFilter?: boolean enableGlobalFilter?: boolean } export interface FiltersColumn { getAutoFilterFn: () => FilterFn | undefined getFilterFn: () => FilterFn | undefined setFilterValue: (updater: Updater) => void getCanFilter: () => boolean getCanGlobalFilter: () => boolean getFacetedRowModel: () => RowModel _getFacetedRowModel?: () => RowModel getIsFiltered: () => boolean getFilterValue: () => unknown getFilterIndex: () => number getFacetedUniqueValues: () => Map _getFacetedUniqueValues?: () => Map getFacetedMinMaxValues: () => undefined | [number, number] _getFacetedMinMaxValues?: () => undefined | [number, number] } export interface FiltersRow { columnFilters: Record columnFiltersMeta: Record } interface FiltersOptionsBase { enableFilters?: boolean manualFiltering?: boolean filterFromLeafRows?: boolean maxLeafRowFilterDepth?: number getFilteredRowModel?: (table: Table) => () => RowModel // Column onColumnFiltersChange?: OnChangeFn enableColumnFilters?: boolean // Global globalFilterFn?: FilterFnOption onGlobalFilterChange?: OnChangeFn enableGlobalFilter?: boolean getColumnCanGlobalFilter?: (column: Column) => boolean // Faceting getFacetedRowModel?: ( table: Table, columnId: string ) => () => RowModel getFacetedUniqueValues?: ( table: Table, columnId: string ) => () => Map getFacetedMinMaxValues?: ( table: Table, columnId: string ) => () => undefined | [number, number] } type ResolvedFilterFns = keyof FilterFns extends never ? { filterFns?: Record> } : { filterFns: Record> } export interface FiltersOptions extends FiltersOptionsBase, ResolvedFilterFns {} export interface FiltersInstance { setColumnFilters: (updater: Updater) => void resetColumnFilters: (defaultState?: boolean) => void // Column Filters getPreFilteredRowModel: () => RowModel getFilteredRowModel: () => RowModel _getFilteredRowModel?: () => RowModel // Global Filters setGlobalFilter: (updater: Updater) => void resetGlobalFilter: (defaultState?: boolean) => void getGlobalAutoFilterFn: () => FilterFn | undefined getGlobalFilterFn: () => FilterFn | undefined getGlobalFacetedRowModel: () => RowModel _getGlobalFacetedRowModel?: () => RowModel getGlobalFacetedUniqueValues: () => Map _getGlobalFacetedUniqueValues?: () => Map getGlobalFacetedMinMaxValues: () => undefined | [number, number] _getGlobalFacetedMinMaxValues?: () => undefined | [number, number] } // export const Filters: TableFeature = { getDefaultColumnDef: (): FiltersColumnDef => { return { filterFn: 'auto', } }, getInitialState: (state): FiltersTableState => { return { columnFilters: [], globalFilter: undefined, // filtersProgress: 1, // facetProgress: {}, ...state, } }, getDefaultOptions: ( table: Table ): FiltersOptions => { return { onColumnFiltersChange: makeStateUpdater('columnFilters', table), onGlobalFilterChange: makeStateUpdater('globalFilter', table), filterFromLeafRows: false, maxLeafRowFilterDepth: 100, globalFilterFn: 'auto', getColumnCanGlobalFilter: column => { const value = table .getCoreRowModel() .flatRows[0]?._getAllCellsByColumnId() [column.id]?.getValue() return typeof value === 'string' || typeof value === 'number' }, } as FiltersOptions }, createColumn: ( column: Column, table: Table ): FiltersColumn => { return { getAutoFilterFn: () => { const firstRow = table.getCoreRowModel().flatRows[0] const value = firstRow?.getValue(column.id) if (typeof value === 'string') { return filterFns.includesString } if (typeof value === 'number') { return filterFns.inNumberRange } if (typeof value === 'boolean') { return filterFns.equals } if (value !== null && typeof value === 'object') { return filterFns.equals } if (Array.isArray(value)) { return filterFns.arrIncludes } return filterFns.weakEquals }, getFilterFn: () => { return isFunction(column.columnDef.filterFn) ? column.columnDef.filterFn : column.columnDef.filterFn === 'auto' ? column.getAutoFilterFn() // @ts-ignore : table.options.filterFns?.[column.columnDef.filterFn as string] ?? filterFns[column.columnDef.filterFn as BuiltInFilterFn] }, getCanFilter: () => { return ( (column.columnDef.enableColumnFilter ?? true) && (table.options.enableColumnFilters ?? true) && (table.options.enableFilters ?? true) && !!column.accessorFn ) }, getCanGlobalFilter: () => { return ( (column.columnDef.enableGlobalFilter ?? true) && (table.options.enableGlobalFilter ?? true) && (table.options.enableFilters ?? true) && (table.options.getColumnCanGlobalFilter?.(column) ?? true) && !!column.accessorFn ) }, getIsFiltered: () => column.getFilterIndex() > -1, getFilterValue: () => table.getState().columnFilters?.find(d => d.id === column.id)?.value, getFilterIndex: () => table.getState().columnFilters?.findIndex(d => d.id === column.id) ?? -1, setFilterValue: value => { table.setColumnFilters(old => { const filterFn = column.getFilterFn() const previousfilter = old?.find(d => d.id === column.id) const newFilter = functionalUpdate( value, previousfilter ? previousfilter.value : undefined ) // if ( shouldAutoRemoveFilter( filterFn as FilterFn, newFilter, column ) ) { return old?.filter(d => d.id !== column.id) ?? [] } const newFilterObj = { id: column.id, value: newFilter } if (previousfilter) { return ( old?.map(d => { if (d.id === column.id) { return newFilterObj } return d }) ?? [] ) } if (old?.length) { return [...old, newFilterObj] } return [newFilterObj] }) }, _getFacetedRowModel: table.options.getFacetedRowModel && table.options.getFacetedRowModel(table, column.id), getFacetedRowModel: () => { if (!column._getFacetedRowModel) { return table.getPreFilteredRowModel() } return column._getFacetedRowModel() }, _getFacetedUniqueValues: table.options.getFacetedUniqueValues && table.options.getFacetedUniqueValues(table, column.id), getFacetedUniqueValues: () => { if (!column._getFacetedUniqueValues) { return new Map() } return column._getFacetedUniqueValues() }, _getFacetedMinMaxValues: table.options.getFacetedMinMaxValues && table.options.getFacetedMinMaxValues(table, column.id), getFacetedMinMaxValues: () => { if (!column._getFacetedMinMaxValues) { return undefined } return column._getFacetedMinMaxValues() }, // () => [column.getFacetedRowModel()], // facetedRowModel => getRowModelMinMaxValues(facetedRowModel, column.id), } }, createRow: ( row: Row, table: Table ): FiltersRow => { return { columnFilters: {}, columnFiltersMeta: {}, } }, createTable: ( table: Table ): FiltersInstance => { return { getGlobalAutoFilterFn: () => { return filterFns.includesString }, getGlobalFilterFn: () => { const { globalFilterFn: globalFilterFn } = table.options return isFunction(globalFilterFn) ? globalFilterFn : globalFilterFn === 'auto' ? table.getGlobalAutoFilterFn() // @ts-ignore : table.options.filterFns?.[globalFilterFn as string] ?? filterFns[globalFilterFn as BuiltInFilterFn] }, setColumnFilters: (updater: Updater) => { const leafColumns = table.getAllLeafColumns() const updateFn = (old: ColumnFiltersState) => { return functionalUpdate(updater, old)?.filter(filter => { const column = leafColumns.find(d => d.id === filter.id) if (column) { const filterFn = column.getFilterFn() if (shouldAutoRemoveFilter(filterFn, filter.value, column)) { return false } } return true }) } table.options.onColumnFiltersChange?.(updateFn) }, setGlobalFilter: updater => { table.options.onGlobalFilterChange?.(updater) }, resetGlobalFilter: defaultState => { table.setGlobalFilter( defaultState ? undefined : table.initialState.globalFilter ) }, resetColumnFilters: defaultState => { table.setColumnFilters( defaultState ? [] : table.initialState?.columnFilters ?? [] ) }, getPreFilteredRowModel: () => table.getCoreRowModel(), getFilteredRowModel: () => { if (!table._getFilteredRowModel && table.options.getFilteredRowModel) { table._getFilteredRowModel = table.options.getFilteredRowModel(table) } if (table.options.manualFiltering || !table._getFilteredRowModel) { return table.getPreFilteredRowModel() } return table._getFilteredRowModel() }, _getGlobalFacetedRowModel: table.options.getFacetedRowModel && table.options.getFacetedRowModel(table, '__global__'), getGlobalFacetedRowModel: () => { if (table.options.manualFiltering || !table._getGlobalFacetedRowModel) { return table.getPreFilteredRowModel() } return table._getGlobalFacetedRowModel() }, _getGlobalFacetedUniqueValues: table.options.getFacetedUniqueValues && table.options.getFacetedUniqueValues(table, '__global__'), getGlobalFacetedUniqueValues: () => { if (!table._getGlobalFacetedUniqueValues) { return new Map() } return table._getGlobalFacetedUniqueValues() }, _getGlobalFacetedMinMaxValues: table.options.getFacetedMinMaxValues && table.options.getFacetedMinMaxValues(table, '__global__'), getGlobalFacetedMinMaxValues: () => { if (!table._getGlobalFacetedMinMaxValues) { return } return table._getGlobalFacetedMinMaxValues() }, } }, } export function shouldAutoRemoveFilter( filterFn?: FilterFn, value?: any, column?: Column ) { return ( (filterFn && filterFn.autoRemove ? filterFn.autoRemove(value, column) : false) || typeof value === 'undefined' || (typeof value === 'string' && !value) ) }