UNPKG

@tanstack/solid-table

Version:

Headless UI for building powerful tables & datagrids for Solid.

347 lines 17 kB
import { FlexRender } from "./FlexRender.js"; import { SolidTable } from "./createTable.js"; import { AccessorFn, AccessorFnColumnDef, AccessorKeyColumnDef, Cell, CellContext, CellData, Column, ColumnDef, DeepKeys, DeepValue, DisplayColumnDef, GroupColumnDef, Header, IdentifiedColumnDef, NoInfer, Row, RowData, Table, TableFeatures, TableOptions } from "@tanstack/table-core"; import { Component, JSXElement } from "solid-js"; //#region src/createTableHook.d.ts type ComponentType<T extends Record<string, any>> = Component<T>; /** * Enhanced CellContext with pre-bound cell components. * The `cell` property includes the registered cellComponents. */ type AppCellContext<TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData, TCellComponents extends Record<string, ComponentType<any>>> = { cell: Cell<TFeatures, TData, TValue> & TCellComponents & { FlexRender: () => JSXElement; }; column: Column<TFeatures, TData, TValue>; getValue: CellContext<TFeatures, TData, TValue>['getValue']; renderValue: CellContext<TFeatures, TData, TValue>['renderValue']; row: Row<TFeatures, TData>; table: Table<TFeatures, TData>; }; /** * Enhanced HeaderContext with pre-bound header components. * The `header` property includes the registered headerComponents. */ type AppHeaderContext<TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData, THeaderComponents extends Record<string, ComponentType<any>>> = { column: Column<TFeatures, TData, TValue>; header: Header<TFeatures, TData, TValue> & THeaderComponents & { FlexRender: () => JSXElement; }; table: Table<TFeatures, TData>; }; /** * Template type for column definitions that can be a string or a function. */ type AppColumnDefTemplate<TProps extends object> = string | ((props: TProps) => any); /** * Enhanced column definition base with pre-bound components in cell/header/footer contexts. */ type AppColumnDefBase<TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData, TCellComponents extends Record<string, ComponentType<any>>, THeaderComponents extends Record<string, ComponentType<any>>> = Omit<IdentifiedColumnDef<TFeatures, TData, TValue>, 'cell' | 'header' | 'footer'> & { cell?: AppColumnDefTemplate<AppCellContext<TFeatures, TData, TValue, TCellComponents>>; header?: AppColumnDefTemplate<AppHeaderContext<TFeatures, TData, TValue, THeaderComponents>>; footer?: AppColumnDefTemplate<AppHeaderContext<TFeatures, TData, TValue, THeaderComponents>>; }; /** * Enhanced display column definition with pre-bound components. */ type AppDisplayColumnDef<TFeatures extends TableFeatures, TData extends RowData, TCellComponents extends Record<string, ComponentType<any>>, THeaderComponents extends Record<string, ComponentType<any>>> = Omit<DisplayColumnDef<TFeatures, TData, unknown>, 'cell' | 'header' | 'footer'> & { cell?: AppColumnDefTemplate<AppCellContext<TFeatures, TData, unknown, TCellComponents>>; header?: AppColumnDefTemplate<AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>>; footer?: AppColumnDefTemplate<AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>>; }; /** * Enhanced group column definition with pre-bound components. */ type AppGroupColumnDef<TFeatures extends TableFeatures, TData extends RowData, TCellComponents extends Record<string, ComponentType<any>>, THeaderComponents extends Record<string, ComponentType<any>>> = Omit<GroupColumnDef<TFeatures, TData, unknown>, 'cell' | 'header' | 'footer' | 'columns'> & { cell?: AppColumnDefTemplate<AppCellContext<TFeatures, TData, unknown, TCellComponents>>; header?: AppColumnDefTemplate<AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>>; footer?: AppColumnDefTemplate<AppHeaderContext<TFeatures, TData, unknown, THeaderComponents>>; columns?: ReadonlyArray<ColumnDef<TFeatures, TData, unknown>>; }; /** * Enhanced column helper with pre-bound components in cell/header/footer contexts. * This enables TypeScript to know about the registered components when defining columns. */ type AppColumnHelper<TFeatures extends TableFeatures, TData extends RowData, TCellComponents extends Record<string, ComponentType<any>>, THeaderComponents extends Record<string, ComponentType<any>>> = { /** * Creates a data column definition with an accessor key or function. * The cell, header, and footer contexts include pre-bound components. */ accessor: <TAccessor extends AccessorFn<TData> | DeepKeys<TData>, TValue extends (TAccessor extends AccessorFn<TData, infer TReturn> ? TReturn : TAccessor extends DeepKeys<TData> ? DeepValue<TData, TAccessor> : never)>(accessor: TAccessor, column: TAccessor extends AccessorFn<TData> ? AppColumnDefBase<TFeatures, TData, TValue, TCellComponents, THeaderComponents> & { id: string; } : AppColumnDefBase<TFeatures, TData, TValue, TCellComponents, THeaderComponents>) => TAccessor extends AccessorFn<TData> ? AccessorFnColumnDef<TFeatures, TData, TValue> : AccessorKeyColumnDef<TFeatures, TData, TValue>; /** * Wraps an array of column definitions to preserve each column's individual TValue type. */ columns: <TColumns extends ReadonlyArray<ColumnDef<TFeatures, TData, any>>>(columns: [...TColumns]) => Array<ColumnDef<TFeatures, TData, any>> & [...TColumns]; /** * Creates a display column definition for non-data columns. * The cell, header, and footer contexts include pre-bound components. */ display: (column: AppDisplayColumnDef<TFeatures, TData, TCellComponents, THeaderComponents>) => DisplayColumnDef<TFeatures, TData, unknown>; /** * Creates a group column definition with nested child columns. * The cell, header, and footer contexts include pre-bound components. */ group: (column: AppGroupColumnDef<TFeatures, TData, TCellComponents, THeaderComponents>) => GroupColumnDef<TFeatures, TData, unknown>; }; /** * Options for creating a table hook with pre-bound components and default table options. * Extends all TableOptions except 'columns' | 'data' | 'store' | 'state' | 'initialState'. */ type CreateTableHookOptions<TFeatures extends TableFeatures, TTableComponents extends Record<string, ComponentType<any>>, TCellComponents extends Record<string, ComponentType<any>>, THeaderComponents extends Record<string, ComponentType<any>>> = Omit<TableOptions<TFeatures, any>, 'columns' | 'data' | 'store' | 'state' | 'initialState'> & { /** * Table-level components that need access to the table instance. * These are available directly on the table object returned by createAppTable. * Use `useTableContext()` inside these components. * @example { PaginationControls, GlobalFilter, RowCount } */ tableComponents?: TTableComponents; /** * Cell-level components that need access to the cell instance. * These are available on the cell object passed to AppCell's children. * Use `useCellContext()` inside these components. * @example { TextCell, NumberCell, DateCell, CurrencyCell } */ cellComponents?: TCellComponents; /** * Header-level components that need access to the header instance. * These are available on the header object passed to AppHeader/AppFooter's children. * Use `useHeaderContext()` inside these components. * @example { SortIndicator, ColumnFilter, ResizeHandle } */ headerComponents?: THeaderComponents; }; interface AppTableProps { children: JSXElement; } /** * Props for AppCell component. */ interface AppCellProps<TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData, TCellComponents extends Record<string, ComponentType<any>>> { cell: Cell<TFeatures, TData, TValue>; children: (cell: Cell<TFeatures, TData, TValue> & TCellComponents & { FlexRender: () => JSXElement; }) => JSXElement; } /** * Props for AppHeader/AppFooter component. */ interface AppHeaderProps<TFeatures extends TableFeatures, TData extends RowData, TValue extends CellData, THeaderComponents extends Record<string, ComponentType<any>>> { header: Header<TFeatures, TData, TValue>; children: (header: Header<TFeatures, TData, TValue> & THeaderComponents & { FlexRender: () => JSXElement; }) => JSXElement; } /** * Component type for AppCell - wraps a cell and provides cell context. */ interface AppCellComponent<TFeatures extends TableFeatures, TData extends RowData, TCellComponents extends Record<string, ComponentType<any>>> { <TValue extends CellData = CellData>(props: AppCellProps<TFeatures, TData, TValue, TCellComponents>): JSXElement; } /** * Component type for AppHeader/AppFooter - wraps a header and provides header context. */ interface AppHeaderComponent<TFeatures extends TableFeatures, TData extends RowData, THeaderComponents extends Record<string, ComponentType<any>>> { <TValue extends CellData = CellData>(props: AppHeaderProps<TFeatures, TData, TValue, THeaderComponents>): JSXElement; } /** * Component type for AppTable - root wrapper with optional Subscribe */ interface AppTableComponent<_TFeatures extends TableFeatures> { (props: AppTableProps): JSXElement; } /** * Extended table API returned by createAppTable with all App wrapper components */ type AppSolidTable<TFeatures extends TableFeatures, TData extends RowData, TTableComponents extends Record<string, ComponentType<any>>, TCellComponents extends Record<string, ComponentType<any>>, THeaderComponents extends Record<string, ComponentType<any>>> = SolidTable<TFeatures, TData> & NoInfer<TTableComponents> & { /** * Root wrapper component that provides table context. * @example * ```tsx * <table.AppTable> * <table>...</table> * </table.AppTable> * ``` */ AppTable: AppTableComponent<TFeatures>; /** * Wraps a cell and provides cell context with pre-bound cellComponents. * @example * ```tsx * <table.AppCell cell={cell}> * {(c) => <td><c.TextCell /></td>} * </table.AppCell> * ``` */ AppCell: AppCellComponent<TFeatures, TData, NoInfer<TCellComponents>>; /** * Wraps a header and provides header context with pre-bound headerComponents. * @example * ```tsx * <table.AppHeader header={header}> * {(h) => <th><h.SortIndicator /></th>} * </table.AppHeader> * ``` */ AppHeader: AppHeaderComponent<TFeatures, TData, NoInfer<THeaderComponents>>; /** * Wraps a footer and provides header context with pre-bound headerComponents. * @example * ```tsx * <table.AppFooter header={footer}> * {(f) => <td><table.FlexRender footer={footer} /></td>} * </table.AppFooter> * ``` */ AppFooter: AppHeaderComponent<TFeatures, TData, NoInfer<THeaderComponents>>; /** * Convenience FlexRender component attached to the table instance. * Renders cell, header, or footer content from column definitions. * @example * ```tsx * <table.FlexRender header={header} /> * <table.FlexRender cell={cell} /> * <table.FlexRender footer={footer} /> * ``` */ FlexRender: typeof FlexRender; }; interface CreateTableHookResult<TFeatures extends TableFeatures, TTableComponents extends Record<string, ComponentType<any>>, TCellComponents extends Record<string, ComponentType<any>>, THeaderComponents extends Record<string, ComponentType<any>>> { /** The features object that was passed to `createTableHook`. */ appFeatures: TFeatures; /** * A column helper pre-bound to `TFeatures` and the registered components, so * the cell/header/footer render props expose the bound components. */ createAppColumnHelper: <TData extends RowData>() => AppColumnHelper<TFeatures, TData, TCellComponents, THeaderComponents>; /** * Creates a table with the `App*` wrapper components and registered * `tableComponents` attached. `TData` is inferred from the `data` option. */ createAppTable: <TData extends RowData>(tableOptions: Omit<TableOptions<TFeatures, TData>, 'features'>) => AppSolidTable<TFeatures, TData, TTableComponents, TCellComponents, THeaderComponents>; /** * Reads the table provided by the nearest `<table.AppTable>`. This is the same * extended instance `createAppTable` returns, so the `App*` components and your * `tableComponents` are available on it. */ useTableContext: <TData extends RowData = RowData>() => AppSolidTable<TFeatures, TData, TTableComponents, TCellComponents, THeaderComponents>; /** * Reads the cell provided by the nearest `<table.AppCell>`, extended with your * `cellComponents` and a context-bound `FlexRender`. */ useCellContext: <TValue extends CellData = CellData>() => Cell<TFeatures, any, TValue> & TCellComponents & { FlexRender: () => JSXElement; }; /** * Reads the header provided by the nearest `<table.AppHeader>` / * `<table.AppFooter>`, extended with your `headerComponents` and a * context-bound `FlexRender`. */ useHeaderContext: <TValue extends CellData = CellData>() => Header<TFeatures, any, TValue> & THeaderComponents & { FlexRender: () => JSXElement; }; } /** * Creates a custom table hook with pre-bound components for composition. * * This is the table equivalent of TanStack Form's `createFormHook`. It allows you to: * - Define features, row models, and default options once, shared across all tables * - Register reusable table, cell, and header components * - Access table/cell/header instances via context in those components * - Get a `createAppTable` hook that returns an extended table with App wrapper components * - Get a `createAppColumnHelper` function pre-bound to your features * * @example * ```tsx * // hooks/table.ts * export const { * createAppTable, * createAppColumnHelper, * useTableContext, * useCellContext, * useHeaderContext, * } = createTableHook({ * features: tableFeatures({ * rowPaginationFeature, * rowSortingFeature, * columnFilteringFeature, * paginatedRowModel: createPaginatedRowModel(), * sortedRowModel: createSortedRowModel(), * filteredRowModel: createFilteredRowModel(), * sortFns, * filterFns, * }), * tableComponents: { PaginationControls, RowCount }, * cellComponents: { TextCell, NumberCell }, * headerComponents: { SortIndicator, ColumnFilter }, * }) * * // Create column helper with TFeatures already bound * const columnHelper = createAppColumnHelper<Person>() * * // components/table-components.tsx * function PaginationControls() { * const table = useTableContext() // TFeatures already known! * return ( * <table.Subscribe> * {(atoms) => <span>Page {atoms.pagination.get().pageIndex + 1}</span>} * </table.Subscribe> * ) * } * * // features/users.tsx * function UsersTable({ data }: { data: Person[] }) { * const table = createAppTable({ * columns, * data, // TData inferred from Person[] * }) * * return ( * <table.AppTable> * <table> * <thead> * <For each={table.getHeaderGroups()}> * {(headerGroup) => ( * <tr> * <For each={headerGroup.headers}> * {(h) => ( * <table.AppHeader header={h}> * {(header) => ( * <th> * <header.FlexRender /> * <header.SortIndicator /> * </th> * )} * </table.AppHeader> * )} * </For> * </tr> * )} * </For> * </thead> * <tbody> * <For each={table.getRowModel().rows}> * {(row) => ( * <tr> * <For each={row.getAllCells()}> * {(c) => ( * <table.AppCell cell={c}> * {(cell) => <td><cell.TextCell /></td>} * </table.AppCell> * )} * </For> * </tr> * )} * </For> * </tbody> * </table> * <table.PaginationControls /> * </table.AppTable> * ) * } * ``` */ declare function createTableHook<TFeatures extends TableFeatures, const TTableComponents extends Record<string, ComponentType<any>>, const TCellComponents extends Record<string, ComponentType<any>>, const THeaderComponents extends Record<string, ComponentType<any>>>({ tableComponents, cellComponents, headerComponents, ...defaultTableOptions }: CreateTableHookOptions<TFeatures, TTableComponents, TCellComponents, THeaderComponents>): CreateTableHookResult<TFeatures, TTableComponents, TCellComponents, THeaderComponents>; //#endregion export { AppCellComponent, AppCellContext, AppCellProps, AppColumnDefBase, AppColumnDefTemplate, AppColumnHelper, AppDisplayColumnDef, AppGroupColumnDef, AppHeaderComponent, AppHeaderContext, AppHeaderProps, AppSolidTable, AppTableComponent, AppTableProps, ComponentType, CreateTableHookOptions, CreateTableHookResult, createTableHook };