import { callMemoOrStaticFn, tableMemo } from '../../utils' import { getCanGlobalFilter } from '../../core/featureStubs' import { column_getFacetedRowModel, table_getGlobalFacetedRowModel, } from './columnFacetingFeature.utils' import type { Row } from '../../types/Row' import type { Table, Table_Internal } from '../../types/Table' import type { TableFeatures } from '../../types/TableFeatures' import type { RowData } from '../../types/type-utils' /** * Creates a memoized faceted unique values helper for faceted filtering. * * The returned function derives facet data from the table row model and relevant filter state so filter UIs can display available values. */ export function createFacetedUniqueValues< TFeatures extends TableFeatures, TData extends RowData = any, >(): ( table: Table, columnId: string, ) => () => Map { return (_table, columnId) => { const table = _table as unknown as Table_Internal return tableMemo({ feature: 'columnFacetingFeature', table, fnName: 'table.getFacetedUniqueValues', memoDeps: () => { if (columnId === '__global__') { return [ callMemoOrStaticFn( table, 'getGlobalFacetedRowModel', table_getGlobalFacetedRowModel, ).flatRows, ] } const column = table.getColumn(columnId) if (!column) return [table.getPreFilteredRowModel().flatRows] return [ callMemoOrStaticFn( column, 'getFacetedRowModel', column_getFacetedRowModel, table, ).flatRows, ] }, fn: (flatRows) => _createFacetedUniqueValues(table, columnId, flatRows), }) } } function _createFacetedUniqueValues< TFeatures extends TableFeatures, TData extends RowData = any, >( table: Table_Internal, columnId: string, flatRows: Array>, ): Map { // The global context aggregates unique values across every column that // participates in global filtering const columnIds = columnId === '__global__' ? table .getAllLeafColumns() .filter((column) => getCanGlobalFilter(column)) .map((column) => column.id) : [columnId] const facetedUniqueValues = new ArrayKeyMap() for (let i = 0; i < flatRows.length; i++) { for (let c = 0; c < columnIds.length; c++) { // the declared return type is Array, but rows return undefined for // columns without an accessor (e.g. display columns) const values = flatRows[i]!.getUniqueValues(columnIds[c]!) as Array | undefined if (!values) continue for (let j = 0; j < values.length; j++) { const value = values[j] const previousValue = facetedUniqueValues.get(value) facetedUniqueValues.set( value, previousValue === undefined ? 1 : previousValue + 1, ) } } } return facetedUniqueValues } /** * A Map subclass that normalises array keys so that: * - Two distinct array instances with the same contents map to the same entry. * - A serialised string like `'["a","b"]'` never collides with an array `['a','b']`. * * Internally arrays are stored under a null-byte-prefixed JSON key, which cannot * appear in ordinary strings. */ class ArrayKeyMap extends Map { static toKey(key: unknown): unknown { return Array.isArray(key) ? '\0' + JSON.stringify(key) : key } override get(key: unknown): number | undefined { return super.get(ArrayKeyMap.toKey(key)) } override set(key: unknown, value: number): this { return super.set(ArrayKeyMap.toKey(key), value) } override has(key: unknown): boolean { return super.has(ArrayKeyMap.toKey(key)) } }